Compare commits

...

173 Commits
master ... dev

Author SHA1 Message Date
somunslotus 1d117b0ca5 x 2024-09-20 13:56:50 +08:00
somunslotus 911dd2bb72 x 2024-09-20 13:54:08 +08:00
somunslotus 53e725e821 x 2024-09-20 13:51:44 +08:00
somunslotus 64f949ec5e x 2024-09-20 13:49:15 +08:00
somunslotus aa37cfaede x 2024-09-20 13:44:39 +08:00
somunslotus e4858cc976 x 2024-09-20 11:57:07 +08:00
chenzhihang 994696da9a 修改模型导出依赖 2024-09-20 11:16:21 +08:00
chenzhihang 432d7dd9ef Merge remote-tracking branch 'origin/dev-czh' into dev 2024-09-20 09:30:56 +08:00
chenzhihang 1091dfde55 优化查询 2024-09-20 09:29:41 +08:00
chenzhihang 9e2d288fb8 Merge remote-tracking branch 'origin/dev-czh' into dev 2024-09-20 09:15:49 +08:00
chenzhihang 28da9b11b8 修改相对路径 2024-09-20 09:04:53 +08:00
chenzhihang a08754f863 Merge remote-tracking branch 'origin/dev' into dev-czh 2024-09-19 18:12:17 +08:00
chenzhihang e374d39af8 修改时区 2024-09-19 18:07:27 +08:00
chenzhihang 5311545d1b 修改时区 2024-09-19 17:36:07 +08:00
chenzhihang 50e66571a3 Merge remote-tracking branch 'origin/dev' into dev 2024-09-19 17:28:06 +08:00
chenzhihang d5ad62af17 修改相对路径 2024-09-19 17:27:27 +08:00
fanshuai b0ead1807b 不需要用到表了 2024-09-19 17:24:59 +08:00
chenzhihang f9cf9ad312 修改分页查询 2024-09-19 17:07:09 +08:00
chenzhihang b3d274f79c 修改模型依赖 2024-09-19 16:56:05 +08:00
chenzhihang 7f112c3078 Merge remote-tracking branch 'origin/dev' into dev-czh
# Conflicts:
#	ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/ExperimentServiceImpl.java
2024-09-19 16:27:01 +08:00
chenzhihang 50cfefac46 修改模型依赖 2024-09-19 16:23:38 +08:00
fanshuai c62d20f63d 不需要用到表了 2024-09-19 14:36:11 +08:00
fanshuai 80d802391a Merge remote-tracking branch 'origin/dev' into dev 2024-09-19 14:35:55 +08:00
chenzhihang 1ed2d061b3 修改分页查询bug 2024-09-19 11:49:21 +08:00
fanshuai 73fdb64061 Merge remote-tracking branch 'origin/dev' into dev 2024-09-19 11:46:34 +08:00
fanshuai db3ce4581d 换回原来的HTTPUTILS 2024-09-19 11:46:30 +08:00
chenzhihang 469c575250 优化异步执行创建版本 2024-09-19 10:57:19 +08:00
chenzhihang 658ad688d3 优化异步执行创建版本 2024-09-19 10:53:57 +08:00
chenzhihang 39f7b05869 优化异步执行创建版本 2024-09-19 10:02:30 +08:00
chenzhihang 0e8fb0ed0f 优化异步执行创建版本 2024-09-19 09:58:07 +08:00
chenzhihang 83c317b854 优化文件下载 2024-09-19 09:30:58 +08:00
chenzhihang 332959cd21 优化文件下载 2024-09-19 09:23:29 +08:00
chenzhihang 93d9ef8c93 优化文件下载 2024-09-19 09:22:49 +08:00
chenzhihang 68d09fa06d 优化创建版本 2024-09-19 08:37:51 +08:00
chenzhihang 24a51f4ead 异步执行创建 2024-09-18 17:18:07 +08:00
chenzhihang 522ed32ff5 修改分页查询bug 2024-09-18 17:05:31 +08:00
fanshuai 12942ddba2 修改路径 2024-09-18 16:13:38 +08:00
fanshuai d0d25f9cc4 Merge remote-tracking branch 'origin/dev' into dev 2024-09-18 15:47:24 +08:00
fanshuai 6c388f7384 修改路径 2024-09-18 15:47:21 +08:00
chenzhihang 01299087d0 Merge remote-tracking branch 'origin/dev-czh' into dev 2024-09-18 15:45:48 +08:00
chenzhihang d74aa4efa2 修改模型路径 2024-09-18 15:45:27 +08:00
fanshuai 07dae0f917 Merge branch 'dev' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev 2024-09-18 15:34:45 +08:00
fanshuai d9d9406695 修改路径 2024-09-18 15:34:31 +08:00
somunslotus 1b957fd33d x 2024-09-18 15:22:51 +08:00
somunslotus 2c3ed72301 Merge branch 'dev' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev 2024-09-18 15:17:17 +08:00
chenzhihang 4519b6e346 Merge remote-tracking branch 'origin/dev' into dev 2024-09-18 15:16:05 +08:00
chenzhihang 296c791671 修改模型路径 2024-09-18 15:15:33 +08:00
fanshuai c37da222f7 修改路径 2024-09-18 15:10:32 +08:00
somunslotus e81194e291 Merge branch 'dev' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev 2024-09-18 15:10:21 +08:00
fanshuai 375f9bb4a2 修改路径 2024-09-18 15:09:09 +08:00
somunslotus e3cc9a998c Merge branch 'dev' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev 2024-09-18 14:54:09 +08:00
chenzhihang e3dee373d6 修改路径 2024-09-18 14:53:21 +08:00
fanshuai f270ba7c0a 修改路径 2024-09-18 14:49:44 +08:00
somunslotus 7f32d7ce63 Merge branch 'dev' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev 2024-09-18 14:49:42 +08:00
somunslotus 25a5c457eb Merge branch 'dev' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev 2024-09-18 14:35:54 +08:00
cp3hnu 2cd7782bea Merge pull request '合并dev' (#131) from dev-zw into dev 2024-09-18 14:34:33 +08:00
cp3hnu 39e93b563f fix: 模型类型没有选中展示 2024-09-18 14:35:06 +08:00
chenzhihang c1fc913ac1 后端服务pod挂载持久化 2024-09-18 14:34:23 +08:00
chenzhihang 1673d9480f 后端服务pod挂载持久化 2024-09-18 14:34:10 +08:00
somunslotus b4de8aa7d8 modify nginx.cofn 2024-09-18 14:20:16 +08:00
chenzhihang 223f6ba92b Merge remote-tracking branch 'origin/dev' into dev-czh 2024-09-18 13:39:59 +08:00
chenzhihang a5ce1a56fe 修改模型数据集分页查询 2024-09-18 13:35:19 +08:00
somunslotus d429a915eb x 2024-09-18 12:52:50 +08:00
somunslotus 752e2c851c x 2024-09-18 11:58:58 +08:00
cp3hnu eeb0d0c149 fix: 禁止图片拖动 2024-09-18 11:09:46 +08:00
cp3hnu fa18e6a5fe Merge pull request '合并dev-zw' (#130) from dev-zw into dev 2024-09-18 11:09:25 +08:00
fanshuai d38703abd7 改造为可配置使用代理服务器 2024-09-18 10:23:08 +08:00
chenzhihang dac7b2138a Merge remote-tracking branch 'origin/dev' into dev-czh 2024-09-18 09:57:54 +08:00
chenzhihang 8ed145326d 标签优化 2024-09-18 09:51:58 +08:00
fanshuai 2791f9c8db 改造为可配置使用代理服务器 2024-09-14 15:15:29 +08:00
fanshuai d5a02212ff Merge remote-tracking branch 'origin/dev' into dev 2024-09-14 15:08:27 +08:00
fanshuai d87b013ed6 改造为可配置使用代理服务器 2024-09-14 15:07:39 +08:00
chenzhihang 67d62678de ssl问题解决尝试 2024-09-14 14:46:18 +08:00
chenzhihang a6e27df2f9 Merge remote-tracking branch 'origin/dev' into dev 2024-09-14 13:59:44 +08:00
chenzhihang b610ae73cd ssl问题解决尝试 2024-09-14 13:59:35 +08:00
cp3hnu f890b75f80 Merge pull request '合并dev-zw' (#129) from dev-zw into dev 2024-09-14 13:56:55 +08:00
chenzhihang 696bcd0a20 ssl问题解决尝试 2024-09-14 13:56:07 +08:00
chenzhihang d60b57af02 ssl问题解决尝试 2024-09-14 13:48:08 +08:00
chenzhihang 7e90541d4c ssl问题解决尝试 2024-09-14 13:45:54 +08:00
chenzhihang 1155def018 ssl问题解决尝试 2024-09-14 13:44:11 +08:00
chenzhihang f6e7ddd1bf 优化 2024-09-14 11:48:28 +08:00
chenzhihang 07cf1250ac ssl问题解决尝试 2024-09-14 11:00:50 +08:00
chenzhihang 6a737ce855 ssl问题解决尝试 2024-09-14 10:35:36 +08:00
chenzhihang 6bbaa5a4b8 ssl问题解决尝试 2024-09-14 10:29:54 +08:00
cp3hnu d9b70dabee feat: 添加ErrorBoundary 2024-09-14 09:44:51 +08:00
chenzhihang 2a200fcabf Merge remote-tracking branch 'origin/dev' into dev-czh 2024-09-14 08:37:28 +08:00
somunslotus d5f5bae4ce x 2024-09-13 17:25:58 +08:00
chenzhihang f79c193393 模型元数据优化 2024-09-13 16:33:41 +08:00
fanshuai aba0396e06 修改顺序 2024-09-13 15:37:17 +08:00
fanshuai b8f4da80a3 Merge branch 'dev' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev 2024-09-13 15:00:12 +08:00
fanshuai 38ced879f4 修改顺序 2024-09-13 14:59:29 +08:00
chenzhihang f29bb734a2 模型元数据添加创建时间,模型大小字段 2024-09-13 14:48:10 +08:00
chenzhihang c7967bc8e9 Merge remote-tracking branch 'origin/dev-czh' into dev 2024-09-13 14:16:23 +08:00
chenzhihang 81908b4b86 修改发布脚本 2024-09-13 14:16:05 +08:00
chenzhihang 31cf861e87 更换镜像底座 2024-09-13 14:14:21 +08:00
cp3hnu 03c6415376 Merge pull request '合并dev-zw' (#128) from dev-zw-newdataset into dev 2024-09-13 13:56:20 +08:00
cp3hnu ccfe0d981a feat: 完成模型演化 2024-09-13 13:49:07 +08:00
chenzhihang f7d71e3303 修改模型元数据 2024-09-13 13:36:17 +08:00
chenzhihang 1dadf81f94 还原挂载 2024-09-13 11:03:35 +08:00
chenzhihang c87603f993 修改模型元数据 2024-09-13 10:00:35 +08:00
chenzhihang 3955893da0 Merge remote-tracking branch 'origin/dev-zw-newdataset' into dev-czh 2024-09-13 08:36:07 +08:00
cp3hnu b7d81b000b feat: 完成模型改造 2024-09-13 08:33:34 +08:00
chenzhihang 3d36c57fa6 修改模型依赖树接口 2024-09-13 08:32:31 +08:00
fanshuai 7f21bbeef1 Merge branch 'dev' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev 2024-09-12 15:52:38 +08:00
fanshuai f4581d5080 修改datasettemp的xml 2024-09-12 15:52:22 +08:00
chenzhihang 1e5c82566c 修改下载文件名 2024-09-12 15:35:43 +08:00
chenzhihang 78d3a33256 Merge remote-tracking branch 'origin/dev' into dev-czh
# Conflicts:
#	ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/NewDatasetServiceImpl.java
2024-09-12 15:07:29 +08:00
chenzhihang 64b59e8abc 修改版本删除接口 2024-09-12 15:04:22 +08:00
fanshuai 5752c50bf2 适配流水线接口 2024-09-12 14:41:05 +08:00
chenzhihang f8e2c293c7 Merge remote-tracking branch 'origin/dev' into dev-czh
# Conflicts:
#	ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/NewDatasetServiceImpl.java
2024-09-12 11:06:13 +08:00
chenzhihang 0d32f9828b 修改模型dvc接口 2024-09-12 10:59:52 +08:00
fanshuai 2dd0102f34 修改路径 2024-09-12 09:07:59 +08:00
fanshuai 2386d0d9d6 Merge branch 'dev' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev 2024-09-11 09:10:24 +08:00
fanshuai 1e1da4cc28 删除加判断不能删除最后的版本 2024-09-11 09:09:48 +08:00
cp3hnu da3f801fd0 feat: 完成数据集的修改 2024-09-11 09:00:53 +08:00
chenzhihang 35aee7824e Merge remote-tracking branch 'origin/dev-czh' into dev 2024-09-10 17:02:58 +08:00
chenzhihang ef6bfa2d34 修改tensorboard挂载路径 2024-09-10 17:02:34 +08:00
chenzhihang ad0d0c9a1b 修改tensorboard挂载路径 2024-09-10 17:01:34 +08:00
fanshuai 365ebef461 新增存储路径 2024-09-10 16:51:03 +08:00
chenzhihang 173dcb1d66 Merge remote-tracking branch 'origin/dev-czh' into dev 2024-09-10 16:37:49 +08:00
chenzhihang 76a85ff996 修改开发环境挂载路径 2024-09-10 16:37:34 +08:00
chenzhihang 6074c20f12 优化模型接口 2024-09-10 16:36:28 +08:00
chenzhihang 46f49582ee Merge remote-tracking branch 'origin/dev-czh' into dev 2024-09-10 15:53:15 +08:00
chenzhihang e58d73ae12 修改挂载路径 2024-09-10 15:53:03 +08:00
chenzhihang 006b625571 优化模型接口 2024-09-10 15:29:32 +08:00
fanshuai de623ec3c5 Merge branch 'dev' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev 2024-09-10 15:15:55 +08:00
fanshuai 9637209e72 修改从远程master进行创建 2024-09-10 15:15:43 +08:00
chenzhihang 8f9c9d75d7 Merge remote-tracking branch 'origin/dev-czh' into dev 2024-09-10 15:15:15 +08:00
chenzhihang a888b88074 1、修改文件上传路径 2024-09-10 15:14:47 +08:00
fanshuai 9b64f38835 Merge branch 'dev' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev 2024-09-10 14:56:32 +08:00
fanshuai efd815edd7 优化并行处理 2024-09-10 14:56:24 +08:00
chenzhihang 92e77bcfe9 Merge remote-tracking branch 'origin/dev' into dev-czh 2024-09-10 14:54:25 +08:00
chenzhihang 9fcb1a2db0 1、修改文件上传路径
2、修改tensorboard pod挂载方式为hostpath
2024-09-10 14:53:55 +08:00
fanshuai c158ff2e25 Merge branch 'dev' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev 2024-09-10 14:41:54 +08:00
fanshuai 2a54f602cf 优化并行处理 2024-09-10 14:35:46 +08:00
chenzhihang 31455d0420 修改build脚本 2024-09-10 10:41:10 +08:00
chenzhihang e215da82c3 Merge remote-tracking branch 'origin/dev-czh' into dev 2024-09-10 10:27:26 +08:00
chenzhihang 7fa0d7cb64 修改build脚本 2024-09-10 10:27:12 +08:00
chenzhihang 5f2ed6914c Merge remote-tracking branch 'origin/dev-czh' into dev 2024-09-10 09:53:36 +08:00
chenzhihang 38cd15b47a 修改build脚本 2024-09-10 09:53:23 +08:00
chenzhihang ed68d8f504 Merge remote-tracking branch 'origin/dev-czh' into dev 2024-09-10 09:50:46 +08:00
chenzhihang c4b569bb85 修改build脚本 2024-09-10 09:50:32 +08:00
chenzhihang db7c0c77d9 Merge remote-tracking branch 'origin/dev-czh' into dev 2024-09-10 09:40:54 +08:00
chenzhihang 47c4bc86ac 修改build脚本 2024-09-10 09:40:31 +08:00
chenzhihang 6296d2781b Merge remote-tracking branch 'origin/dev-czh' into dev
# Conflicts:
#	ruoyi-auth/src/main/resources/bootstrap.yml
#	ruoyi-gateway/src/main/resources/bootstrap.yml
#	ruoyi-modules/management-platform/src/main/resources/bootstrap.yml
2024-09-10 09:29:20 +08:00
chenzhihang a1c9ef7c3c 修改build脚本 2024-09-10 09:28:49 +08:00
chenzhihang 4e7b18bd2c Merge remote-tracking branch 'origin/master' into dev-czh
# Conflicts:
#	ruoyi-auth/src/main/resources/bootstrap.yml
#	ruoyi-gateway/src/main/resources/bootstrap.yml
#	ruoyi-modules/management-platform/src/main/resources/bootstrap.yml
2024-09-10 09:22:16 +08:00
chenzhihang 3c0e16ea65 Merge remote-tracking branch 'origin/master' into dev
# Conflicts:
#	ruoyi-auth/src/main/resources/bootstrap.yml
#	ruoyi-gateway/src/main/resources/bootstrap.yml
#	ruoyi-modules/management-platform/src/main/resources/bootstrap.yml
#	ruoyi-modules/ruoyi-file/src/main/resources/bootstrap.yml
#	ruoyi-modules/ruoyi-gen/src/main/resources/bootstrap.yml
#	ruoyi-modules/ruoyi-job/src/main/resources/bootstrap.yml
#	ruoyi-modules/ruoyi-system/src/main/resources/bootstrap.yml
2024-09-10 09:18:48 +08:00
chenzhihang 5661fa0bac 修改nacos配置 2024-09-10 09:14:35 +08:00
chenzhihang 1eb68f16a4 Merge remote-tracking branch 'origin/dev-czh' into dev
# Conflicts:
#	ruoyi-modules/management-platform/src/main/resources/bootstrap.yml
2024-09-10 09:09:25 +08:00
chenzhihang 6c81e5611c 修改nacos配置 2024-09-10 09:07:36 +08:00
chenzhihang 083342cc8a 修改nacos配置 2024-09-09 18:59:00 +08:00
fanshuai dd1d1aae26 处理 2024-09-09 17:10:58 +08:00
chenzhihang ecc911a9b0 优化pod挂载路径 2024-09-09 16:11:08 +08:00
chenzhihang 05f1a9880d 1、修改文件上传路径
2、修改pod挂载方式为hostpath
2024-09-09 14:53:17 +08:00
fanshuai 23c2bb1770 处理LUJ 2024-09-06 17:11:25 +08:00
fanshuai 81366d0edd 处理死锁 2024-09-06 16:44:17 +08:00
fanshuai 53046a939a update dataset version 2024-09-06 16:39:52 +08:00
fanshuai 156888782e update dataset version 2024-09-06 16:20:01 +08:00
chenzhihang 77fadb29d9 优化模型接口 2024-09-06 14:02:34 +08:00
chenzhihang ee25849a36 Merge remote-tracking branch 'origin/dev-czh' into dev
# Conflicts:
#	ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/DatasetServiceImpl.java
2024-09-06 11:35:16 +08:00
chenzhihang a5d4469ad7 删除模型接口 2024-09-06 11:14:49 +08:00
fanshuai 6fdb5bcf78 update dataset version 2024-09-06 09:34:54 +08:00
chenzhihang e8cea9df83 修改模型查询树 2024-09-05 17:45:32 +08:00
fanshuai 7cbd258afe update dataset version 2024-09-05 17:10:34 +08:00
chenzhihang 4ec2ee52b3 解决合并冲突 2024-09-05 16:58:13 +08:00
chenzhihang 666a185a0f Merge remote-tracking branch 'origin/dev' into dev-czh
# Conflicts:
#	ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/DatasetServiceImpl.java
#	ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/utils/DVCUtils.java
2024-09-05 16:42:09 +08:00
chenzhihang 16332d4d0b 新模型dvc管理 2024-09-05 16:33:03 +08:00
fanshuai 230029734c update dataset version 2024-09-05 15:31:35 +08:00
fanshuai c3723ee716 update pom version 2024-09-05 08:35:29 +08:00
cp3hnu 877816cb14 feat: 修改系统管理-角色分配 2024-09-04 10:27:26 +08:00
chenzhihang a912b24ad2 Merge remote-tracking branch 'origin/dev-czh' into dev 2024-09-03 16:36:09 +08:00
chenzhihang 3d67f58e5b 新模型dvc管理 2024-09-03 16:35:32 +08:00
159 changed files with 5733 additions and 1833 deletions

2
k8s/build-java.sh Normal file → Executable file
View File

@ -1,6 +1,8 @@
#!/bin/bash
baseDir="/home/somuns/ci4s"
#判断$1是否为all如果是则编译所有模块否则只编译management-platform模块
if [ "$1" == "all" ]; then
buildDir=$baseDir

10
k8s/build-node.sh Normal file → Executable file
View File

@ -3,12 +3,12 @@
baseDir="/home/somuns/ci4s"
cd ${baseDir}/react-ui
npm install
# npm install
if [ $? -ne 0 ]; then
echo "Failed to install npm depend package"
exit 1
fi
#if [ $? -ne 0 ]; then
# echo "Failed to install npm depend package"
# exit 1
#fi
npm run build

5
k8s/build.sh Normal file → Executable file
View File

@ -43,18 +43,23 @@ cd ${baseDir}
# 拉取指定分支的最新代码
echo "Checking out and pulling branch $branch..."
git stash
git checkout $branch
if [ $? -ne 0 ]; then
echo "切换到分支 $branch 失败,请检查分支名称是否正确!"
exit 1
fi
git stash
git pull origin $branch
if [ $? -ne 0 ]; then
echo "拉取代码失败,请检查网络或联系管理员!"
exit 1
fi
chmod +777 ${baseDir}/k8s/*.sh
# 创建目录
mkdir -p ${baseDir}/k8s/dockerfiles/jar
mkdir -p ${baseDir}/k8s/dockerfiles/html

0
k8s/build_and_deploy.sh Normal file → Executable file
View File

2
k8s/deploy.sh Normal file → Executable file
View File

@ -143,7 +143,7 @@ fi
if [ "$service" == "all" ]; then
#部署前端
build_and_deploy "nginx-dockerfile" "172.20.32.187/ci4s/ci4s-front:${tag}" "k8s-12front.yaml"
# build_and_deploy "nginx-dockerfile" "172.20.32.187/ci4s/ci4s-front:${tag}" "k8s-12front.yaml"
#部署管理平台
build_and_deploy "managent-dockerfile" "172.20.32.187/ci4s/ci4s-managent:${tag}" "k8s-7management.yaml"
#部署认证中心

View File

@ -1,6 +1,6 @@
# 基础镜像
#FROM openjdk:8-jre
FROM 172.20.32.187/ci4s/openjdk:8-jre
FROM 172.20.32.187/ci4s/openjdk-dvc:2024829
# author
MAINTAINER ruoyi

View File

@ -1,60 +1,131 @@
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 8000;
server_name localhost;
location /api/{
rewrite ^/prod-api/(.*)$ /$1 break;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://ci4s-gateway-service.argo.svc:8082/;
}
location /label-studio {
rewrite ^/prod-api/(.*)$ /$1 break;
proxy_pass http://label-studio-ls-app.label-data.svc:80/;
proxy_hide_header X-Frame-Options;
add_header X-Frame-Options "ALLOW-FROM http://label-studio-ls-app.label-data.svc:80/";
}
location /api/v1/model/ {
proxy_pass http://pipeline-convert-service.argo.svc:80;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location / {
rewrite ^/prod-api/(.*)$ /$1 break;
root /home/ruoyi/projects/ruoyi-ui;
try_files $uri $uri/ /index.html;
index index.html index.htm;
}
location @router {
rewrite ^.*$ /index.html last;
}
# 避免actuator暴露
if ($request_uri ~ "/actuator") {
return 403;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
client_max_body_size 20480m;
error_log /var/log/nginx/error.log debug;
server {
listen 8000;
server_name localhost;
location /api/{
# rewrite ^/prod-api/(.*)$ /$1 break;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://ci4s-gateway-service.argo.svc:8082/;
proxy_connect_timeout 500s; # 设置连接超时时间为 120
proxy_read_timeout 500s; # 设置读取超时时间为 120
proxy_send_timeout 500s; # 设置发送超时时间为 120
}
location /label-studio/ {
# rewrite ^/label-studio/(.*)$ /$1 break;
proxy_pass http://label-studio-service.argo.svc:8080/projects/;
proxy_hide_header X-Frame-Options;
add_header X-Frame-Options ALLOWALL;
}
location / {
rewrite ^/prod-api/(.*)$ /$1 break;
root /home/ruoyi/projects/ruoyi-ui;
try_files $uri $uri/ /index.html;
index index.html index.htm;
}
location /api/v1/model/ {
proxy_pass http://pipeline-convert-service.argo.svc:80;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# location /api/v1/realtimeStatus {
# proxy_pass http://argo-server.argo.svc:2746/api/v1/workflow-events/argo;
# proxy_set_header REMOTE-HOST $remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# }
location /api/v1/tensorboard/show {
# 提取查询参数中的 `svc`
set $svc "";
if ($arg_svc) {
set $svc $arg_svc;
}
# 将请求转发到动态生成的内部服务地址
proxy_pass http://$svc.argo.svc:6006;
# 传递必要的头信息
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 对于 WebSocket 应用很重要
proxy_buffering off;
}
location /api/v1/realtimeStatus {
rewrite ^/api/v1/realtimeStatus(.*)$ /api/v1/workflow-events/argo$1 break;
proxy_pass https://argo-server.argo.svc:2746;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 保留查询参数
proxy_set_header X-Original-URI $request_uri;
# 禁用缓冲
proxy_buffering off;
# 增加超时时间
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_connect_timeout 60s;
# 设置传递的请求头
# proxy_set_header Connection '';
# chunked_transfer_encoding off;
# 如果需要保留自定义头部
proxy_set_header Accept 'text/event-stream';
}
location /newlog/realtimeLog {
proxy_pass http://loki.loki-log.svc:3100/loki/api/v1/tail;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location @router {
rewrite ^.*$ /index.html last;
}
# 避免actuator暴露
if ($request_uri ~ "/actuator") {
return 403;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}

View File

@ -0,0 +1,60 @@
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 8000;
server_name localhost;
location /api/{
rewrite ^/prod-api/(.*)$ /$1 break;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://ci4s-gateway-service.argo.svc:8082/;
}
location /label-studio {
rewrite ^/prod-api/(.*)$ /$1 break;
proxy_pass http://label-studio-ls-app.label-data.svc:80/;
proxy_hide_header X-Frame-Options;
add_header X-Frame-Options "ALLOW-FROM http://label-studio-ls-app.label-data.svc:80/";
}
location /api/v1/model/ {
proxy_pass http://pipeline-convert-service.argo.svc:80;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location / {
rewrite ^/prod-api/(.*)$ /$1 break;
root /home/ruoyi/projects/ruoyi-ui;
try_files $uri $uri/ /index.html;
index index.html index.htm;
}
location @router {
rewrite ^.*$ /index.html last;
}
# 避免actuator暴露
if ($request_uri ~ "/actuator") {
return 403;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}

View File

@ -1,5 +1,5 @@
# 基础镜像
FROM 172.20.32.187/ci4s/openjdk:8-jre
FROM 172.20.32.187/ci4s/openjdk-dvc:2024829
#FROM openjdk:8-jre
# author
MAINTAINER ruoyi

View File

@ -1,6 +1,6 @@
# 基础镜像
#FROM openjdk:8-jre
FROM 172.20.32.187/ci4s/openjdk:8-jre
FROM 172.20.32.187/ci4s/openjdk-dvc:2024829
# author
MAINTAINER ruoyi

View File

@ -1,6 +1,6 @@
# 基础镜像
#FROM openjdk:8-jre
FROM 172.20.32.187/ci4s/openjdk:8-jre
FROM 172.20.32.187/ci4s/openjdk-dvc:2024829
# author
MAINTAINER ruoyi

View File

@ -1,6 +1,6 @@
# 基础镜像
#FROM openjdk:8-jre
FROM 172.20.32.187/ci4s/openjdk:8-jre
FROM 172.20.32.187/ci4s/openjdk-dvc:2024829
# author
MAINTAINER ruoyi

View File

@ -1,6 +1,6 @@
# 基础镜像
#FROM openjdk:8-jre
FROM 172.20.32.187/ci4s/openjdk:8-jre
#FROM 172.20.32.187/ci4s/openjdk:8u162
FROM 172.20.32.187/ci4s/openjdk-dvc:2024829
# author
MAINTAINER ruoyi

View File

@ -1,6 +1,6 @@
# 基础镜像
#FROM openjdk:8-jre
FROM 172.20.32.187/ci4s/openjdk:8-jre
FROM 172.20.32.187/ci4s/openjdk-dvc:2024829
# author
MAINTAINER ruoyi

View File

@ -1,6 +1,6 @@
# 基础镜像
#FROM openjdk:8-jre
FROM 172.20.32.187/ci4s/openjdk:8-jre
FROM 172.20.32.187/ci4s/openjdk-dvc:2024829
# author
MAINTAINER ruoyi

View File

@ -18,6 +18,14 @@ spec:
image: 172.20.32.187/ci4s/managent:20240401
ports:
- containerPort: 9213
volumeMounts:
- name: resource
mountPath: /home/resource/
volumes:
- name: resource
hostPath:
path: /home/resource/
type: DirectoryOrCreate
---
apiVersion: v1

View File

@ -15,10 +15,21 @@ spec:
spec:
containers:
- name: ci4s-management-platform
image: 172.20.32.187/ci4s/managent:202406121003
image: 172.20.32.187/ci4s/ci4s-managent:202409201355
env:
- name: TZ
value: Asia/Shanghai
- name: JAVA_TOOL_OPTIONS
value: "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:5005"
ports:
- containerPort: 9213
volumeMounts:
- name: resource-volume
mountPath: /home/resource/
volumes:
- name: resource-volume
persistentVolumeClaim:
claimName: platform-data-pvc-nfs
---
apiVersion: v1
kind: Service
@ -28,9 +39,15 @@ metadata:
spec:
type: NodePort
ports:
- port: 9213
- name: http
port: 9213
nodePort: 31208
protocol: TCP
- name: debug
nodePort: 34567
port: 5005
protocol: TCP
targetPort: 5005
selector:
app: ci4s-management-platform

View File

@ -16,9 +16,20 @@ spec:
containers:
- name: ci4s-management-platform
image: ${k8s-7management-image}
env:
- name: TZ
value: Asia/Shanghai
- name: JAVA_TOOL_OPTIONS
value: "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:5005"
ports:
- containerPort: 9213
volumeMounts:
- name: resource-volume
mountPath: /home/resource/
volumes:
- name: resource-volume
persistentVolumeClaim:
claimName: platform-data-pvc-nfs
---
apiVersion: v1
kind: Service
@ -28,9 +39,15 @@ metadata:
spec:
type: NodePort
ports:
- port: 9213
- name: http
port: 9213
nodePort: 31208
protocol: TCP
- name: debug
nodePort: 31219
port: 5005
protocol: TCP
targetPort: 5005
selector:
app: ci4s-management-platform

9
react-ui/.gitignore vendored
View File

@ -40,4 +40,11 @@ screenshot
build
pnpm-lock.yaml
pnpm-lock.yaml
/src/services/codeConfig/index.js
/src/pages/CodeConfig/components/AddCodeConfigModal/index.less
/src/pages/CodeConfig/List/index.less
/src/pages/Dataset/components/ResourceItem/index.less
/src/pages/CodeConfig/components/AddCodeConfigModal/index.tsx
/src/pages/CodeConfig/components/CodeConfigItem/index.tsx
/src/pages/Dataset/components/ResourceItem/index.tsx

View File

@ -22,6 +22,7 @@ export { requestConfig as request } from './requestConfig';
// const isDev = process.env.NODE_ENV === 'development';
import { type GlobalInitialState } from '@/types';
import { menuItemRender } from '@/utils/menuRender';
import ErrorBoundary from './components/ErrorBoundary';
import { gotoLoginPage } from './utils/ui';
/**
@ -65,6 +66,7 @@ export async function getInitialState(): Promise<GlobalInitialState> {
// ProLayout 支持的api https://procomponents.ant.design/components/layout
export const layout: RuntimeConfig['layout'] = ({ initialState }) => {
return {
ErrorBoundary: ErrorBoundary,
rightContentRender: false,
waterMarkProps: {
// content: initialState?.currentUser?.nickName,

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,44 @@
.kf-basic-info {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 20px 40px;
align-items: flex-start;
width: 80%;
}
.kf-basic-info-item {
display: flex;
align-items: flex-start;
width: calc(50% - 20px);
font-size: 16px;
line-height: 1.6;
&__label {
position: relative;
color: @text-color-secondary;
text-align: justify;
text-align-last: justify;
&::after {
position: absolute;
content: ':';
}
}
&__value {
flex: 1;
margin-left: 16px;
white-space: pre-line;
word-break: break-all;
}
&__text {
color: @text-color;
}
&__link:hover {
text-decoration: underline @underline-color;
text-underline-offset: 3px;
}
}

View File

@ -0,0 +1,73 @@
import { isEmptyString } from '@/utils';
import { Link } from '@umijs/max';
import classNames from 'classnames';
import './index.less';
export type BasicInfoData = {
label: string;
value?: any;
link?: string;
externalLink?: string;
format?: (_value?: any) => string | undefined;
};
type BasicInfoProps = {
datas: BasicInfoData[];
className?: string;
style?: React.CSSProperties;
labelWidth?: number;
};
function BasicInfo({ datas, className, style, labelWidth = 100 }: BasicInfoProps) {
return (
<div className={classNames('kf-basic-info', className)} style={style}>
{datas.map((item) => (
<BasicInfoItem key={item.label} data={item} labelWidth={labelWidth} />
))}
</div>
);
}
type BasicInfoItemProps = {
data: BasicInfoData;
labelWidth?: number;
};
function BasicInfoItem({ data, labelWidth = 100 }: BasicInfoItemProps) {
const { label, value, externalLink, link, format } = data;
const showValue = format ? format(value) : value;
let valueComponent = undefined;
if (externalLink && showValue) {
valueComponent = (
<a
className="kf-basic-info-item__value kf-basic-info-item__link"
href={externalLink}
target="_blank"
rel="noopener noreferrer"
>
{showValue}
</a>
);
} else if (link && showValue) {
valueComponent = (
<Link to={link} className="kf-basic-info-item__value kf-basic-info-item__link">
{showValue}
</Link>
);
} else {
valueComponent = (
<div className="kf-basic-info-item__value kf-basic-info-item__text">
{isEmptyString(showValue) ? '--' : showValue}
</div>
);
}
return (
<div className="kf-basic-info-item" key={label}>
<div className="kf-basic-info-item__label" style={{ width: labelWidth }}>
{label}
</div>
{valueComponent}
</div>
);
}
export default BasicInfo;

View File

@ -0,0 +1,78 @@
import KFEmpty, { EmptyType } from '@/components/KFEmpty';
import { Button } from 'antd';
import { Component, ReactNode } from 'react';
interface ErrorBoundaryProps {
children: ReactNode;
fallback?: ReactNode; // Optional fallback UI to show in case of error
}
interface ErrorBoundaryState {
hasError: boolean;
error: Error | null;
}
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = {
hasError: false,
error: null,
};
}
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
// Update state so the next render shows the fallback UI
return { hasError: true, error };
}
// componentDidCatch(error: Error, errorInfo: ErrorInfo) {
// // You can log the error to an error reporting service here
// console.error('Error caught by ErrorBoundary:', error.message, errorInfo.componentStack);
// }
render() {
if (this.state.hasError) {
return this.props.fallback || <ErrorBoundaryFallback error={this.state.error} />;
}
return this.props.children;
}
}
function ErrorBoundaryFallback({ error }: { error: Error | null }) {
const message = error && error instanceof Error ? error.message : 'Unknown error';
const errorMsg =
process.env.NODE_ENV === 'development' ? message : '非常抱歉,程序运行错误,\n我们会尽快修复。';
return (
<KFEmpty
style={{ height: '100vh' }}
type={EmptyType.NotFound}
title="出错了"
content={errorMsg}
footer={() => {
return (
<>
<Button
type="default"
onClick={() => {
window.history.pushState({}, '', '/');
window.location.reload();
}}
>
</Button>
<Button
type="primary"
style={{ marginLeft: 20 }}
onClick={() => window.location.reload()}
>
</Button>
</>
);
}}
></KFEmpty>
);
}
export default ErrorBoundary;

View File

@ -9,6 +9,7 @@ import {
} from '@/utils/sessionStorage';
import classNames from 'classnames';
import { useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import './index.less';
export enum IframePageType {
@ -61,7 +62,7 @@ function IframePage({ type, className, style }: IframePageProps) {
return (
<div className={classNames('kf-iframe-page', className)} style={style}>
{loading && <KFSpin />}
{loading && createPortal(<KFSpin size="large" />, document.body)}
<FullScreenFrame url={iframeUrl} onload={hideLoading} onerror={hideLoading} />
</div>
);

View File

@ -0,0 +1,40 @@
.kf-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
&__image {
width: 475px;
}
&__title {
margin-top: 15px;
color: @text-color;
font-weight: 500;
font-size: 30px;
text-align: center;
}
&__content {
max-width: 50%;
margin-top: 15px;
color: @text-color-secondary;
font-size: 15px;
white-space: pre-line;
text-align: center;
}
&__footer {
display: flex;
align-items: center;
justify-content: center;
margin-top: 20px;
margin-bottom: 30px;
&__back-btn {
height: 32px;
}
}
}

View File

@ -0,0 +1,67 @@
import { Button } from 'antd';
import classNames from 'classnames';
import './index.less';
export enum EmptyType {
NoData = 'NoData',
NotFound = 'NotFound',
Developing = 'Developing',
}
type EmptyProps = {
className?: string;
style?: React.CSSProperties;
type: EmptyType;
title?: string;
content?: string;
hasFooter?: boolean;
footer?: () => React.ReactNode;
buttonTitle?: string;
onRefresh?: () => void;
};
function getEmptyImage(type: EmptyType) {
switch (type) {
case EmptyType.NoData:
return require('@/assets/img/no-data.png');
case EmptyType.NotFound:
return require('@/assets/img/404.png');
case EmptyType.Developing:
return require('@/assets/img/missing-back.png');
}
}
function KFEmpty({
className,
style,
type,
title,
content,
hasFooter = true,
footer,
buttonTitle = '刷新',
onRefresh,
}: EmptyProps) {
const image = getEmptyImage(type);
return (
<div className={classNames('kf-empty', className)} style={style}>
<img className="kf-empty__image" src={image} draggable={false} alt="" />
<div className="kf-empty__title">{title}</div>
<div className="kf-empty__content">{content}</div>
{hasFooter && (
<div className="kf-empty__footer">
{footer ? (
footer()
) : (
<Button className="kf-empty__footer__back-btn" type="primary" onClick={onRefresh}>
{buttonTitle}
</Button>
)}
</div>
)}
</div>
);
}
export default KFEmpty;

View File

@ -4,12 +4,12 @@
right: 0;
bottom: 0;
left: 0;
z-index: 1000;
z-index: 1001;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: rgba(255, 255, 255, 0.5);
background-color: rgba(255, 255, 255, 0.3);
&__label {
margin-top: 20px;

View File

@ -18,7 +18,7 @@ type ModalTitleProps = {
function ModalTitle({ title, image, style, className }: ModalTitleProps) {
return (
<div className={classNames('kf-modal-title', className)} style={style}>
{image && <img className={'kf-modal-title__image'} src={image} alt="" />}
{image && <img className={'kf-modal-title__image'} src={image} draggable={false} alt="" />}
{title}
</div>
);

View File

@ -14,7 +14,15 @@ const filterResourceStandard: SelectProps<string, ComputingResource>['filterOpti
};
// id 从 number 转换为 string
const convertId = (item: any) => ({ ...item, id: String(item.id) });
const convertId = (item: any) => ({
...item,
id: JSON.stringify({
id: item.id,
name: item.name,
identifier: item.identifier,
owner: item.owner,
}),
});
export type SelectPropsConfig = {
getOptions: () => Promise<any>; // 获取下拉数据

View File

@ -11,6 +11,7 @@ import ParameterInput, { type ParameterInputProps } from '../ParameterInput';
import './index.less';
export { requiredValidator, type ParameterInputObject } from '../ParameterInput';
export { ResourceSelectorType, selectorTypeConfig, type ResourceSelectorResponse };
type ResourceSelectProps = {
type: ResourceSelectorType;
@ -36,7 +37,7 @@ function ResourceSelect({ type, value, onChange, ...rest }: ResourceSelectProps)
onOk: (res) => {
setSelectedResource(res);
if (res) {
const { activeTab, id, name, version, path } = res;
const { activeTab, id, name, version, path, identifier, owner } = res;
if (type === ResourceSelectorType.Mirror) {
onChange?.({
value: path,
@ -49,8 +50,11 @@ function ResourceSelect({ type, value, onChange, ...rest }: ResourceSelectProps)
} else {
const jsonObj = {
id,
name,
version,
path,
identifier,
owner,
};
const jsonObjStr = JSON.stringify(jsonObj);
const showValue = `${name}:${version}`;

View File

@ -17,7 +17,7 @@ type SubAreaTitleProps = {
function SubAreaTitle({ title, image, style, className }: SubAreaTitleProps) {
return (
<div className={classNames('kf-subarea-title', className)} style={style}>
<img src={image} width={14} />
<img src={image} width={14} draggable={false} alt="" />
<span style={{ marginLeft: '8px' }}>{title}</span>
</div>
);

File diff suppressed because one or more lines are too long

View File

@ -116,6 +116,10 @@
}
}
.ant-input.ant-input-disabled {
height: 46px;
}
// 选择框高度为46px
.ant-select-single {
height: 46px;

View File

@ -1,18 +1,20 @@
import { history } from '@umijs/max';
import { Button, Result } from 'antd';
import React from 'react';
import KFEmpty, { EmptyType } from '@/components/KFEmpty';
import { useNavigate } from '@umijs/max';
const NoFoundPage: React.FC = () => (
<Result
status="404"
title="404"
subTitle="Sorry, the page you visited does not exist."
extra={
<Button type="primary" onClick={() => history.push('/')}>
Back Home
</Button>
}
/>
);
const NoFoundPage = () => {
const navigate = useNavigate();
return (
<KFEmpty
style={{ height: '100vh' }}
type={EmptyType.NotFound}
title="404"
content={'很抱歉,您访问的页面地址有误,\n或者该页面不存在。'}
hasFooter={true}
buttonTitle="返回首页"
onRefresh={() => navigate('/')}
></KFEmpty>
);
};
export default NoFoundPage;

View File

@ -1,9 +1,10 @@
import KFEmpty, { EmptyType } from '@/components/KFEmpty';
import KFIcon from '@/components/KFIcon';
import { deleteCodeConfigReq, getCodeConfigListReq } from '@/services/codeConfig';
import { openAntdModal } from '@/utils/modal';
import { to } from '@/utils/promise';
import { modalConfirm } from '@/utils/ui';
import { App, Button, Empty, Input, Pagination, PaginationProps } from 'antd';
import { App, Button, Input, Pagination, PaginationProps } from 'antd';
import { useEffect, useState } from 'react';
import AddCodeConfigModal, { OperationType } from '../components/AddCodeConfigModal';
import CodeConfigItem from '../components/CodeConfigItem';
@ -31,7 +32,7 @@ export type ResourceListRef = {
};
function CodeConfigList() {
const [dataList, setDataList] = useState<CodeConfigData[]>([]);
const [dataList, setDataList] = useState<CodeConfigData[] | undefined>(undefined);
const [total, setTotal] = useState(0);
const [pagination, setPagination] = useState<PaginationProps>({
current: 1,
@ -56,6 +57,9 @@ function CodeConfigList() {
if (res && res.data && res.data.content) {
setDataList(res.data.content);
setTotal(res.data.totalElements);
} else {
setDataList([]);
setTotal(0);
}
};
@ -117,7 +121,7 @@ function CodeConfigList() {
return (
<div className={styles['code-config-list']}>
<div className={styles['code-config-list__header']}>
<span>{total}</span>
<span>{total} </span>
<div>
<Input.Search
placeholder="按代码仓库名称筛选"
@ -139,10 +143,10 @@ function CodeConfigList() {
</Button>
</div>
</div>
{dataList?.length !== 0 ? (
{dataList && dataList.length !== 0 && (
<>
<div className={styles['code-config-list__content']}>
{dataList?.map((item) => (
{dataList.map((item) => (
<CodeConfigItem
item={item}
key={item.id}
@ -161,10 +165,16 @@ function CodeConfigList() {
{...pagination}
/>
</>
) : (
<div className={styles['code-config-list__empty']}>
<Empty></Empty>
</div>
)}
{dataList && dataList.length === 0 && (
<KFEmpty
className={styles['code-config-list__empty']}
type={EmptyType.NoData}
title="暂无数据"
content={'很抱歉,没有搜索到您想要的内容\n建议刷新试试'}
hasFooter={true}
onRefresh={getDataList}
/>
)}
</div>
);

View File

@ -63,11 +63,16 @@ function CodeConfigItem({ item, onClick, onEdit, onRemove }: CodeConfigItemProps
</div>
<Flex justify="space-between">
<div className={styles['code-config-item__time']}>
<img style={{ width: '17px', marginRight: '6px' }} src={creatByImg} alt="" />
<img
style={{ width: '17px', marginRight: '6px' }}
src={creatByImg}
alt=""
draggable={false}
/>
<span>{item.create_by}</span>
</div>
<div className={styles['code-config-item__time']}>
<img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" />
<img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" draggable={false} />
<span>: {formatDate(item.update_time, 'YYYY-MM-DD')}</span>
</div>
</Flex>

View File

@ -1,6 +1,7 @@
.upload-tip {
margin-top: 5px;
color: @error-color;
color: @text-color-secondary;
font-size: 14px;
}
.upload-button {

View File

@ -1,9 +1,8 @@
import { getAccessToken } from '@/access';
import { DictValueEnumObj } from '@/components/DictTag';
import KFIcon from '@/components/KFIcon';
import KFModal from '@/components/KFModal';
import { addDatesetAndVesion } from '@/services/dataset/index.js';
import { getDictSelectOption } from '@/services/system/dict';
import { CategoryData, ResourceType, resourceConfig } from '@/pages/Dataset/config';
import { addDataset } from '@/services/dataset/index.js';
import { to } from '@/utils/promise';
import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui';
import {
@ -19,8 +18,7 @@ import {
type UploadProps,
} from 'antd';
import { omit } from 'lodash';
import { useEffect, useState } from 'react';
import { CategoryData } from '../../config';
import { useState } from 'react';
import styles from './index.less';
interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> {
@ -31,15 +29,15 @@ interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> {
function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalProps) {
const [uuid] = useState(Date.now());
const [clusterOptions, setClusterOptions] = useState<DictValueEnumObj[]>([]);
// const [clusterOptions, setClusterOptions] = useState<DictValueEnumObj[]>([]);
useEffect(() => {
getClusterOptions();
}, []);
// useEffect(() => {
// getClusterOptions();
// }, []);
// 上传组件参数
const uploadProps: UploadProps = {
action: '/api/mmp/dataset/upload',
action: resourceConfig[ResourceType.Dataset].uploadAction,
headers: {
Authorization: getAccessToken() || '',
},
@ -47,16 +45,16 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr
};
// 获取集群版本数据
const getClusterOptions = async () => {
const [res] = await to(getDictSelectOption('available_cluster'));
if (res) {
setClusterOptions(res);
}
};
// const getClusterOptions = async () => {
// const [res] = await to(getDictSelectOption('available_cluster'));
// if (res) {
// setClusterOptions(res);
// }
// };
// 上传请求
const createDataset = async (params: any) => {
const [res] = await to(addDatesetAndVesion(params));
const [res] = await to(addDataset(params));
if (res) {
message.success('创建成功');
onOk?.();
@ -94,7 +92,13 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr
}}
destroyOnClose
>
<Form name="form" layout="vertical" onFinish={onFinish} autoComplete="off">
<Form
name="form"
layout="vertical"
onFinish={onFinish}
initialValues={{ is_public: false }}
autoComplete="off"
>
<Form.Item
label="数据集名称"
name="name"
@ -106,7 +110,7 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr
},
]}
>
<Input placeholder="请输入数据名称" showCount allowClear maxLength={64} />
<Input placeholder="请输入数据名称" showCount allowClear maxLength={50} />
</Form.Item>
<Form.Item
label="数据集版本"
@ -116,6 +120,14 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr
required: true,
message: '请输入数据集版本',
},
{
validator: (_rule, value) => {
if (value === 'master') {
return Promise.reject(`版本不能为 master`);
}
return Promise.resolve();
},
},
]}
>
<Input placeholder="请输入数据集版本" showCount allowClear maxLength={64} />
@ -125,7 +137,7 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr
allowClear
placeholder="请选择数据集分类"
options={typeList}
fieldNames={{ label: 'name', value: 'id' }}
fieldNames={{ label: 'name', value: 'name' }}
optionFilterProp="name"
showSearch
/>
@ -135,14 +147,14 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr
allowClear
placeholder="请选择研究方向/应用领域"
options={tagList}
fieldNames={{ label: 'name', value: 'id' }}
fieldNames={{ label: 'name', value: 'name' }}
optionFilterProp="name"
showSearch
/>
</Form.Item>
<Form.Item label="集群版本" name="available_cluster">
{/* <Form.Item label="" name="available_cluster">
<Select allowClear placeholder="请选择集群版本" options={clusterOptions} />
</Form.Item>
</Form.Item> */}
<Form.Item
label="数据集简介"
name="description"
@ -156,15 +168,19 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr
<Input.TextArea
placeholder="请输入数据集简介"
showCount
maxLength={256}
maxLength={200}
autoSize={{ minRows: 2, maxRows: 6 }}
allowClear
/>
</Form.Item>
<Form.Item label="选择流水线" name="range">
<Form.Item
label="可见性"
name="is_public"
rules={[{ required: true, message: '请选择可见性' }]}
>
<Radio.Group>
<Radio value="0"></Radio>
<Radio value="1"></Radio>
<Radio value={false}></Radio>
<Radio value={true}></Radio>
</Radio.Group>
</Form.Item>
<Form.Item
@ -187,7 +203,7 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr
>
</Button>
<div className={styles['upload-tip']}>.zip,.tgz格式文</div>
<div className={styles['upload-tip']}> .zip .tgz </div>
</Upload>
</Form.Item>
</Form>

View File

@ -1,7 +1,7 @@
import { getAccessToken } from '@/access';
import KFIcon from '@/components/KFIcon';
import KFModal from '@/components/KFModal';
import { CategoryData } from '@/pages/Dataset/config';
import { CategoryData, ResourceType, resourceConfig } from '@/pages/Dataset/config';
import { addModel } from '@/services/dataset/index.js';
import { to } from '@/utils/promise';
import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui';
@ -9,6 +9,7 @@ import {
Button,
Form,
Input,
Radio,
Select,
Upload,
UploadFile,
@ -31,7 +32,7 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps)
// 上传组件参数
const uploadProps: UploadProps = {
action: '/api/mmp/models/upload',
action: resourceConfig[ResourceType.Model].uploadAction,
headers: {
Authorization: getAccessToken() || '',
},
@ -53,7 +54,7 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps)
if (validateUploadFiles(fileList)) {
const params = {
...omit(formData, ['fileList']),
models_version_vos: fileList.map((item) => {
model_version_vos: fileList.map((item) => {
const data = item.response?.data?.[0] ?? {};
return {
file_name: data.fileName,
@ -77,7 +78,13 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps)
form: 'form',
}}
>
<Form name="form" layout="vertical" onFinish={onFinish} autoComplete="off">
<Form
name="form"
layout="vertical"
onFinish={onFinish}
autoComplete="off"
initialValues={{ is_public: false }}
>
<Form.Item
label="模型名称"
name="name"
@ -88,9 +95,8 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps)
},
]}
>
<Input placeholder="请输入模型名称" showCount allowClear maxLength={64} />
<Input placeholder="请输入模型名称" showCount allowClear maxLength={50} />
</Form.Item>
<Form.Item
label="模型版本"
name="version"
@ -99,9 +105,37 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps)
required: true,
message: '请输入模型版本',
},
{
validator: (_rule, value) => {
if (value === 'master') {
return Promise.reject(`版本不能为 master`);
}
return Promise.resolve();
},
},
]}
>
<Input placeholder="请输入模型版本" allowClear maxLength={64} />
<Input placeholder="请输入模型版本" showCount allowClear maxLength={64} />
</Form.Item>
<Form.Item label="模型框架" name="model_type">
<Select
allowClear
placeholder="请选择模型类型"
options={typeList}
fieldNames={{ label: 'name', value: 'name' }}
optionFilterProp="name"
showSearch
/>
</Form.Item>
<Form.Item label="模型能力" name="model_tag">
<Select
allowClear
placeholder="请选择模型标签"
options={tagList}
fieldNames={{ label: 'name', value: 'name' }}
optionFilterProp="name"
showSearch
/>
</Form.Item>
<Form.Item
label="模型简介"
@ -115,37 +149,21 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps)
>
<Input.TextArea
placeholder="请输入模型简介"
showCount
maxLength={256}
maxLength={200}
autoSize={{ minRows: 2, maxRows: 6 }}
showCount
allowClear
/>
</Form.Item>
{/* <Form.Item label="" name="available_range">
<Form.Item
label="可见性"
name="is_public"
rules={[{ required: true, message: '请选择可见性' }]}
>
<Radio.Group>
<Radio value="0"></Radio>
<Radio value="1"></Radio>
<Radio value={false}></Radio>
<Radio value={true}></Radio>
</Radio.Group>
</Form.Item> */}
<Form.Item label="模型框架" name="model_type">
<Select
allowClear
placeholder="请选择模型类型"
options={typeList}
fieldNames={{ label: 'name', value: 'id' }}
optionFilterProp="name"
showSearch
/>
</Form.Item>
<Form.Item label="模型能力" name="model_tag">
<Select
allowClear
placeholder="请选择模型标签"
options={tagList}
fieldNames={{ label: 'name', value: 'id' }}
optionFilterProp="name"
showSearch
/>
</Form.Item>
<Form.Item
label="模型文件"

View File

@ -21,14 +21,16 @@ import styles from '../AddDatasetModal/index.less';
interface AddVersionModalProps extends Omit<ModalProps, 'onOk'> {
resourceType: ResourceType;
resourceId: number;
initialName: string;
identifier: string;
resoureName: string;
onOk: () => void;
}
function AddVersionModal({
resourceType,
resourceId,
initialName,
resoureName,
identifier,
onOk,
...rest
}: AddVersionModalProps) {
@ -58,17 +60,20 @@ function AddVersionModal({
const onFinish = (formData: any) => {
const fileList: UploadFile[] = formData['fileList'] ?? [];
if (validateUploadFiles(fileList)) {
const otherParams = omit(formData, ['fileList']);
const params = fileList.map((item) => {
const version_vos = fileList.map((item) => {
const data = item.response?.data?.[0] ?? {};
return {
...otherParams,
[config.idParamKey]: resourceId,
file_name: data.fileName,
file_size: data.fileSize,
url: data.url,
};
});
const params = {
id: resourceId,
identifier,
[config.filePropKey]: version_vos,
...omit(formData, 'fileList'),
};
createDatasetVersion(params);
}
};
@ -90,7 +95,7 @@ function AddVersionModal({
name="form"
layout="vertical"
initialValues={{
name: initialName,
name: resoureName,
}}
onFinish={onFinish}
autoComplete="off"
@ -115,13 +120,21 @@ function AddVersionModal({
required: true,
message: `请输入${name}版本`,
},
{
validator: (_rule, value) => {
if (value === 'master') {
return Promise.reject(`版本不能为 master`);
}
return Promise.resolve();
},
},
]}
>
<Input placeholder={`请输入${name}版本`} maxLength={64} showCount allowClear />
</Form.Item>
<Form.Item
label="版本描述"
name="description"
name="version_desc"
rules={[
{
required: true,
@ -158,7 +171,7 @@ function AddVersionModal({
</Button>
{resourceType === ResourceType.Dataset && (
<div className={styles['upload-tip']}>.zip格式文件</div>
<div className={styles['upload-tip']}> .zip </div>
)}
</Upload>
</Form.Item>

View File

@ -23,12 +23,14 @@ function CategoryItem({ resourceType, item, isSelected, onClick }: CategoryItemP
style={{ width: '22px' }}
src={`/assets/images/${config.prefix}/${item.path}.png`}
alt=""
draggable={false}
/>
<img
className={styles['category-item__active-icon']}
style={{ width: '22px' }}
src={`/assets/images/${config.prefix}/${item.path}-hover.png`}
alt=""
draggable={false}
/>
<span className={styles['category-item__name']}>{item.name}</span>
</div>

View File

@ -3,17 +3,12 @@ import { CategoryData, ResourceType, resourceConfig } from '../../config';
import CategoryItem from '../CategoryItem';
import styles from './index.less';
export type CategoryValue = {
dataType: number | undefined;
dataTag: number | undefined;
};
type CategoryProps = {
resourceType: ResourceType; // 资源类型,数据集还是模型
typeList: CategoryData[];
tagList: CategoryData[];
activeType?: number;
activeTag?: number;
activeType?: string;
activeTag?: string;
onTypeSelect: (value: CategoryData) => void;
onTagSelect: (value: CategoryData) => void;
onSearch: (value: string) => void;
@ -44,7 +39,7 @@ function CategoryList({
resourceType={resourceType}
item={item}
onClick={onTypeSelect}
isSelected={item.id === activeType}
isSelected={item.name === activeType}
></CategoryItem>
))}
</Flex>
@ -58,7 +53,7 @@ function CategoryList({
resourceType={resourceType}
item={item}
onClick={onTagSelect}
isSelected={item.id === activeTag}
isSelected={item.name === activeTag}
></CategoryItem>
))}
</Flex>

View File

@ -0,0 +1,66 @@
.resource-info {
height: 100%;
&__top {
width: 100%;
height: 125px;
margin-bottom: 10px;
padding: 20px 30px;
background-image: url(@/assets/img/dataset-intro-top.png);
background-repeat: no-repeat;
background-position: top center;
background-size: 100% 100%;
&__name {
margin-right: 10px;
color: @text-color;
font-weight: 500;
font-size: 20px;
}
&__tag {
padding: 4px 10px;
color: @primary-color;
font-size: 14px;
background: .addAlpha(@primary-color, 0.1) [];
border-radius: 4px;
}
:global {
.ant-btn-dangerous {
background-color: transparent !important;
}
}
}
&__bottom {
position: relative;
height: calc(100% - 135px);
padding: 8px 30px 20px;
background: #ffffff;
border-radius: 10px;
box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09);
&__legend {
position: absolute;
top: 20px;
right: 30px;
}
:global {
.ant-tabs {
height: 100%;
.ant-tabs-content-holder {
height: 100%;
.ant-tabs-content {
height: 100%;
.ant-tabs-tabpane {
height: 100%;
overflow-y: auto;
}
}
}
}
}
}
}

View File

@ -0,0 +1,247 @@
/*
* @Author:
* @Date: 2024-09-06 09:23:15
* @Description:
*/
import KFIcon from '@/components/KFIcon';
import {
ResourceData,
ResourceType,
ResourceVersionData,
resourceConfig,
} from '@/pages/Dataset/config';
import GraphLegend from '@/pages/Model/components/GraphLegend';
import ModelEvolution from '@/pages/Model/components/ModelEvolution';
import { openAntdModal } from '@/utils/modal';
import { to } from '@/utils/promise';
import { modalConfirm } from '@/utils/ui';
import { useParams, useSearchParams } from '@umijs/max';
import { App, Button, Flex, Select, Tabs } from 'antd';
import { useEffect, useState } from 'react';
import AddVersionModal from '../AddVersionModal';
import ResourceIntro from '../ResourceIntro';
import ResourceVersion from '../ResourceVersion';
import styles from './index.less';
// 这里值小写是因为值会写在 url 中
export enum ResourceInfoTabKeys {
Introduction = 'introduction', // 简介
Version = 'version', // 版本
Evolution = 'evolution', // 演化
}
type ResourceInfoProps = {
resourceType: ResourceType;
};
const ResourceInfo = ({ resourceType }: ResourceInfoProps) => {
const [info, setInfo] = useState<ResourceData>({} as ResourceData);
const locationParams = useParams();
const [searchParams] = useSearchParams();
const resourceId = Number(locationParams.id);
// 模型演化传入的 tab
const defaultTab = searchParams.get('tab') || ResourceInfoTabKeys.Introduction;
// 模型演化传入的版本
let versionParam = searchParams.get('version');
const name = searchParams.get('name') || '';
const owner = searchParams.get('owner') || '';
const identifier = searchParams.get('identifier') || '';
const [versionList, setVersionList] = useState<ResourceVersionData[]>([]);
const [version, setVersion] = useState<string | undefined>(undefined);
const [activeTab, setActiveTab] = useState<string>(defaultTab);
const config = resourceConfig[resourceType];
const typeName = config.name; // 数据集/模型
const { message } = App.useApp();
useEffect(() => {
getVersionList();
}, [resourceId, owner, identifier]);
useEffect(() => {
if (version) {
getResourceDetail({
id: resourceId,
owner,
name,
identifier,
version,
});
}
}, [version]);
// 获取详情
const getResourceDetail = async (params: {
owner: string;
name: string;
id: number;
identifier: string;
version?: string;
}) => {
const request = config.getInfo;
const [res] = await to(request(params));
if (res) {
setInfo(res.data);
}
};
// 获取版本列表
const getVersionList = async () => {
const request = config.getVersions;
const [res] = await to(
request({
owner,
identifier,
}),
);
if (res && res.data && res.data.length > 0) {
setVersionList(res.data);
if (
versionParam &&
res.data.find((item: ResourceVersionData) => item.name === versionParam)
) {
setVersion(versionParam);
versionParam = null;
} else {
setVersion(res.data[0].name);
}
} else {
setVersion(undefined);
}
};
// 新建版本
const showModal = () => {
const { close } = openAntdModal(AddVersionModal, {
resourceType: resourceType,
resourceId: resourceId,
resoureName: info.name,
identifier: info.identifier,
onOk: () => {
getVersionList();
close();
},
});
};
// 版本变化
const handleVersionChange = (value: string) => {
setVersion(value);
};
// 删除版本
const deleteVersion = async () => {
const request = config.deleteVersion;
const params = {
id: resourceId,
owner,
identifier,
relative_paths: info.relative_paths,
version,
};
const [res] = await to(request(params));
if (res) {
message.success('删除成功');
getVersionList();
}
};
// 处理删除
const hanldeDelete = () => {
modalConfirm({
title: '删除后,该版本将不可恢复',
content: '是否确认删除?',
okText: '确认',
cancelText: '取消',
onOk: () => {
deleteVersion();
},
});
};
const items = [
{
key: ResourceInfoTabKeys.Introduction,
label: `${typeName}简介`,
icon: <KFIcon type="icon-moxingjianjie" />,
children: <ResourceIntro resourceType={resourceType} info={info}></ResourceIntro>,
},
{
key: ResourceInfoTabKeys.Version,
label: `${typeName}文件`,
icon: <KFIcon type="icon-moxingwenjian" />,
children: <ResourceVersion resourceType={resourceType} info={info}></ResourceVersion>,
},
];
if (resourceType === ResourceType.Model) {
items.push({
key: ResourceInfoTabKeys.Evolution,
label: `模型演化`,
icon: <KFIcon type="icon-moxingyanhua1" />,
children: (
<ModelEvolution
resourceId={resourceId}
version={version}
identifier={identifier}
isActive={activeTab === ResourceInfoTabKeys.Evolution}
onVersionChange={handleVersionChange}
></ModelEvolution>
),
});
}
const typePropertyName = config.typeParamKey as keyof ResourceData;
const tagPropertyName = config.tagParamKey as keyof ResourceData;
return (
<div className={styles['resource-info']}>
<div className={styles['resource-info__top']}>
<Flex align="center" gap={10} style={{ marginBottom: '20px' }}>
<div className={styles['resource-info__top__name']}>{info.name}</div>
{info[typePropertyName] && (
<div className={styles['resource-info__top__tag']}>
{(info[typePropertyName] as string) || '--'}
</div>
)}
{info[tagPropertyName] && (
<div className={styles['resource-info__top__tag']}>
{(info[tagPropertyName] as string) || '--'}
</div>
)}
</Flex>
<Flex align="center">
<span style={{ marginRight: '10px' }}></span>
<Select
placeholder="请选择版本号"
style={{ width: '160px', marginRight: '20px' }}
value={version}
onChange={handleVersionChange}
fieldNames={{ label: 'name', value: 'name' }}
options={versionList}
/>
<Button type="default" onClick={showModal} icon={<KFIcon type="icon-xinjian2" />}>
</Button>
<Button
type="default"
style={{ marginLeft: 'auto', marginRight: 0 }}
onClick={hanldeDelete}
icon={<KFIcon type="icon-shanchu" />}
disabled={!version}
danger
>
</Button>
</Flex>
</div>
<div className={styles['resource-info__bottom']}>
<Tabs activeKey={activeTab} items={items} onChange={(key) => setActiveTab(key)}></Tabs>
<div className={styles['resource-info__bottom__legend']}>
{activeTab === ResourceInfoTabKeys.Evolution && <GraphLegend />}
</div>
</div>
</div>
);
};
export default ResourceInfo;

View File

@ -1,65 +1,10 @@
.resource-intro {
height: 100%;
&__top {
width: 100%;
margin-top: 24px;
&__basic {
width: 100%;
height: 110px;
margin-bottom: 10px;
padding: 20px 30px 0;
background-image: url(@/assets/img/dataset-intro-top.png);
background-repeat: no-repeat;
background-position: top center;
background-size: 100% 100%;
&__name {
margin-bottom: 12px;
color: @text-color;
font-size: 20px;
}
&__tag {
margin-right: 10px;
padding: 4px 10px;
color: @primary-color;
font-size: 14px;
background: rgba(22, 100, 255, 0.1);
border-radius: 4px;
}
}
&__bottom {
height: calc(100% - 120px);
padding: 8px 30px 20px;
background: #ffffff;
border-radius: 10px;
box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09);
:global {
.ant-tabs {
height: 100%;
.ant-tabs-content-holder {
height: 100%;
.ant-tabs-content {
height: 100%;
.ant-tabs-tabpane {
height: 100%;
overflow-y: auto;
}
}
}
}
}
}
&__title {
margin: 30px 0 10px;
color: @text-color;
font-weight: 500;
font-size: @font-size;
}
&__intro {
color: @text-color-secondary;
font-size: 14px;
&__usage {
width: 100%;
}
}

View File

@ -1,156 +1,181 @@
import KFIcon from '@/components/KFIcon';
import ModelEvolution from '@/pages/Model/components/ModelEvolution';
import { to } from '@/utils/promise';
import { useParams, useSearchParams } from '@umijs/max';
import { Flex, Tabs } from 'antd';
import { useEffect, useState } from 'react';
import { ResourceData, ResourceType, resourceConfig } from '../../config';
import ResourceVersion from '../ResourceVersion';
import BasicInfo, { BasicInfoData } from '@/components/BasicInfo';
import SubAreaTitle from '@/components/SubAreaTitle';
import { DatasetData, ModelData, ResourceType } from '@/pages/Dataset/config';
import styles from './index.less';
// 这里值小写是因为值会写在 url 中
export enum ResourceInfoTabKeys {
Introduction = 'introduction', // 简介
Version = 'version', // 版本
Evolution = 'evolution', // 演化
}
type ResourceIntroProps = {
resourceType: ResourceType;
info: DatasetData | ModelData;
};
const ResourceIntro = ({ resourceType }: ResourceIntroProps) => {
const [info, setInfo] = useState<ResourceData>({} as ResourceData);
const locationParams = useParams();
const [searchParams] = useSearchParams();
const defaultTab = searchParams.get('tab') || ResourceInfoTabKeys.Introduction;
let versionParam = searchParams.get('version');
const [versionList, setVersionList] = useState([]);
const [version, setVersion] = useState<string | undefined>(undefined);
const [activeTab, setActiveTab] = useState<string>(defaultTab);
const resourceId = Number(locationParams.id);
const config = resourceConfig[resourceType];
const typeName = config.name; // 数据集/模型
// const formatArray = (arr?: ResourceData[]) => {
// if (!arr || arr.length === 0) {
// return '--';
// }
// return arr.map((item) => item.name).join('\n');
// };
useEffect(() => {
getModelByDetail();
getVersionList();
}, [resourceId]);
// 获取详情
const getModelByDetail = async () => {
const request = config.getInfo;
const [res] = await to(request(resourceId));
if (res) {
setInfo(res.data);
}
};
// 获取版本列表
const getVersionList = async () => {
const request = config.getVersions;
const [res] = await to(request(resourceId));
if (res && res.data && res.data.length > 0) {
setVersionList(
res.data.map((item: string) => {
return {
label: item,
value: item,
};
}),
);
if (versionParam && res.data.includes(versionParam)) {
setVersion(versionParam);
versionParam = null;
} else {
setVersion(res.data[0]);
}
} else {
setVersion(undefined);
}
};
// 版本变化
const handleVersionChange = (value: string) => {
setVersion(value);
};
const items = [
{
key: ResourceInfoTabKeys.Introduction,
label: `${typeName}简介`,
icon: <KFIcon type="icon-moxingjianjie" />,
children: (
<>
<div className={styles['resource-intro__title']}></div>
<div className={styles['resource-intro__intro']}>{info.description}</div>
</>
),
},
{
key: ResourceInfoTabKeys.Version,
label: `${typeName}文件/版本`,
icon: <KFIcon type="icon-moxingwenjian" />,
children: (
<ResourceVersion
resourceType={resourceType}
resourceId={resourceId}
resourceName={info.name}
isPublic={info.available_range === 1}
versionList={versionList}
version={version}
isActive={activeTab === ResourceInfoTabKeys.Version}
getVersionList={getVersionList}
onVersionChange={handleVersionChange}
></ResourceVersion>
),
},
];
if (resourceType === ResourceType.Model) {
items.push({
key: ResourceInfoTabKeys.Evolution,
label: `模型演化`,
icon: <KFIcon type="icon-moxingyanhua1" />,
children: (
<ModelEvolution
resourceId={resourceId}
versionList={versionList}
version={version}
isActive={activeTab === ResourceInfoTabKeys.Evolution}
onVersionChange={handleVersionChange}
></ModelEvolution>
),
});
const formatDataset = (arr?: DatasetData[]) => {
if (!arr || arr.length === 0) {
return undefined;
}
return arr.map((item) => item.name).join('\n');
};
const infoTypePropertyName = config.infoTypePropertyName as keyof ResourceData;
const infoTagPropertyName = config.infoTagPropertyName as keyof ResourceData;
const formatMap = (map?: Record<string, string>) => {
if (!map || Object.keys(map).length === 0) {
return undefined;
}
return Object.entries(map)
.map(([key, value]) => `${key} = ${value}`)
.join('\n');
};
const getDatasetDatas = (data: DatasetData): BasicInfoData[] => [
{
label: '数据集名称',
value: data.name,
},
{
label: '版本',
value: data.version,
},
{
label: '创建人',
value: data.create_by,
},
{
label: '更新时间',
value: data.update_time,
},
{
label: '数据来源',
value: data.dataset_source,
},
{
label: '处理代码',
value: data.processing_code,
},
{
label: '数据集分类',
value: data.data_type,
},
{
label: '研究方向',
value: data.data_tag,
},
{
label: '数据集描述',
value: data.description,
},
{
label: '版本描述',
value: data.version_desc,
},
];
const getModelDatas = (data: ModelData): BasicInfoData[] => [
{
label: '模型名称',
value: data.name,
},
{
label: '版本',
value: data.version,
},
{
label: '创建人',
value: data.create_by,
},
{
label: '更新时间',
value: data.update_time,
},
{
label: '训练镜像',
value: data.image,
},
{
label: '训练代码',
value: data.code,
},
{
label: '训练数据集',
value: data.train_datasets,
format: formatDataset,
},
{
label: '测试数据集',
value: data.test_datasets,
format: formatDataset,
},
{
label: '参数',
value: data.params,
format: formatMap,
},
{
label: '指标',
value: data.metrics,
format: formatMap,
},
{
label: '训练任务',
value: data.train_task,
format: (value?: any) => value?.name,
externalLink: data.train_task
? `${location.origin}/pipeline/experiment/instance/${data.train_task.task_id}/${data.train_task.ins_id}`
: '',
},
{
label: '模型来源',
value: data.model_source,
},
{
label: '模型框架',
value: data.model_type,
},
{
label: '模型能力',
value: data.model_tag,
},
{
label: '模型描述',
value: data.description,
},
{
label: '版本描述',
value: data.version_desc,
},
];
function ResourceIntro({ resourceType, info }: ResourceIntroProps) {
const basicDatas: BasicInfoData[] =
resourceType === ResourceType.Dataset
? getDatasetDatas(info as DatasetData)
: getModelDatas(info as ModelData);
return (
<div className={styles['resource-intro']}>
<div className={styles['resource-intro__top']}>
<div className={styles['resource-intro__top__name']}>{info.name}</div>
<Flex align="center">
<div className={styles['resource-intro__top__tag']}>
{typeName} id{info.id}
</div>
{info[infoTypePropertyName] && (
<div className={styles['resource-intro__top__tag']}>
{info[infoTypePropertyName] || '--'}
</div>
)}
{info[infoTagPropertyName] && (
<div className={styles['resource-intro__top__tag']}>
{info[infoTagPropertyName] || '--'}
</div>
)}
</Flex>
</div>
<div className={styles['resource-intro__bottom']}>
<Tabs activeKey={activeTab} items={items} onChange={(key) => setActiveTab(key)}></Tabs>
<SubAreaTitle
title="基本信息"
image={require('@/assets/img/mirror-basic.png')}
style={{ marginBottom: '26px' }}
></SubAreaTitle>
<div className={styles['resource-intro__basic']}>
<BasicInfo datas={basicDatas} labelWidth={86}></BasicInfo>
</div>
<SubAreaTitle
title="实例用法"
image={require('@/assets/img/usage-icon.png')}
style={{ margin: '40px 0 24px' }}
></SubAreaTitle>
<div
className={styles['resource-intro__usage']}
dangerouslySetInnerHTML={{ __html: info.usage ?? '暂无实例用法' }}
></div>
</div>
);
};
}
export default ResourceIntro;

View File

@ -39,12 +39,20 @@ function ResourceItem({ item, isPublic, onClick, onRemove }: ResourceItemProps)
<div className={styles['resource-item__description']}>{item.description}</div>
<Flex justify="space-between">
<div className={styles['resource-item__time']}>
<img style={{ width: '17px', marginRight: '6px' }} src={creatByImg} alt="" />
<span>{item.create_by}</span>
<img
style={{ width: '17px', marginRight: '6px' }}
src={creatByImg}
draggable={false}
alt=""
/>
<span>{item.create_by ?? ''}</span>
</div>
<div className={styles['resource-item__time']}>
<img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" />
<span>: {formatDate(item.update_time, 'YYYY-MM-DD')}</span>
<img style={{ width: '12px', marginRight: '5px' }} src={clock} draggable={false} alt="" />
<span>
{'最近更新: '}
{item.update_time ? formatDate(item.update_time, 'YYYY-MM-DD') : item.time_ago ?? ''}
</span>
</div>
</Flex>
</div>

View File

@ -36,4 +36,8 @@
text-align: right;
}
}
&__empty {
flex: 1;
}
}

View File

@ -1,3 +1,4 @@
import KFEmpty, { EmptyType } from '@/components/KFEmpty';
import KFIcon from '@/components/KFIcon';
import { CommonTabKeys } from '@/enums';
import AddModelModal from '@/pages/Dataset/components/AddModelModal';
@ -6,6 +7,7 @@ import { to } from '@/utils/promise';
import { modalConfirm } from '@/utils/ui';
import { useNavigate } from '@umijs/max';
import { App, Button, Input, Pagination, PaginationProps } from 'antd';
import { pick } from 'lodash';
import { Ref, forwardRef, useEffect, useImperativeHandle, useState } from 'react';
import { CategoryData, ResourceData, ResourceType, resourceConfig } from '../../config';
import AddDatasetModal from '../AddDatasetModal';
@ -18,8 +20,8 @@ export type ResourceListRef = {
type ResourceListProps = {
resourceType: ResourceType;
dataType?: number;
dataTag?: number;
dataType?: string;
dataTag?: string;
isPublic: boolean;
typeList: CategoryData[];
tagList: CategoryData[];
@ -43,7 +45,7 @@ function ResourceList(
ref: Ref<ResourceListRef>,
) {
const navigate = useNavigate();
const [dataList, setDataList] = useState<ResourceData[]>([]);
const [dataList, setDataList] = useState<ResourceData[] | undefined>(undefined);
const [total, setTotal] = useState(0);
const [pagination, setPagination] = useState<PaginationProps>(
initialPagination ?? {
@ -71,7 +73,8 @@ function ResourceList(
});
setSearchText('');
setInputText('');
setDataList([]);
setDataList(undefined);
setTotal(0);
},
};
},
@ -80,12 +83,12 @@ function ResourceList(
// 获取数据请求
const getDataList = async () => {
const params = {
const params: Record<string, any> = {
page: pagination.current! - 1,
size: pagination.pageSize,
is_public: isPublic,
[config.typeParamKey]: dataType,
[config.tagParamKey]: dataTag,
available_range: isPublic ? 1 : 0,
name: searchText !== '' ? searchText : undefined,
};
const request = config.getList;
@ -93,13 +96,16 @@ function ResourceList(
if (res && res.data && res.data.content) {
setDataList(res.data.content);
setTotal(res.data.totalElements);
} else {
setDataList([]);
setTotal(0);
}
};
// 删除请求
const deleteRecord = async (id: number) => {
const deleteRecord = async (params: { owner: string; identifier: string; repo_id?: number }) => {
const request = config.deleteRecord;
const [res] = await to(request(id));
const [res] = await to(request(params));
if (res) {
getDataList();
message.success('删除成功');
@ -116,7 +122,7 @@ function ResourceList(
modalConfirm({
title: config.deleteModalTitle,
onOk: () => {
deleteRecord(record.id);
deleteRecord(pick(record, ['owner', 'identifier', 'id']));
},
});
};
@ -131,7 +137,9 @@ function ResourceList(
activeTag: dataTag,
});
const prefix = config.prefix;
navigate(`/dataset/${prefix}/info/${record.id}`);
navigate(
`/dataset/${prefix}/info/${record.id}?name=${record.name}&owner=${record.owner}&identifier=${record.identifier}`,
);
};
// 分页切换
@ -158,7 +166,7 @@ function ResourceList(
return (
<div className={styles['resource-list']}>
<div className={styles['resource-list__header']}>
<span>{total}</span>
<span>{total} </span>
<div>
<Input.Search
placeholder={`${config.name}名称筛选`}
@ -182,26 +190,40 @@ function ResourceList(
)}
</div>
</div>
<div className={styles['resource-list__content']}>
{dataList?.map((item) => (
<ResourceItem
item={item}
key={item.id}
isPublic={isPublic}
onRemove={handleRemove}
onClick={handleClick}
></ResourceItem>
))}
</div>
<Pagination
total={total}
showSizeChanger
defaultPageSize={20}
pageSizeOptions={[20, 40, 60, 80, 100]}
showQuickJumper
onChange={handlePageChange}
{...pagination}
/>
{dataList && dataList.length > 0 && (
<>
<div className={styles['resource-list__content']}>
{dataList?.map((item) => (
<ResourceItem
item={item}
key={item.id}
isPublic={isPublic}
onRemove={handleRemove}
onClick={handleClick}
></ResourceItem>
))}
</div>
<Pagination
total={total}
showSizeChanger
defaultPageSize={20}
pageSizeOptions={[20, 40, 60, 80, 100]}
showQuickJumper
onChange={handlePageChange}
{...pagination}
/>
</>
)}
{dataList && dataList.length === 0 && (
<KFEmpty
className={styles['resource-list__empty']}
type={EmptyType.NoData}
title="暂无数据"
content={'很抱歉,没有搜索到您想要的内容\n建议刷新试试'}
hasFooter={true}
onRefresh={getDataList}
/>
)}
</div>
);
}

View File

@ -18,8 +18,8 @@ function ResourcePage({ resourceType }: ResourcePageProps) {
const [activeTab, setActiveTab] = useState<string>(cacheState?.activeTab ?? CommonTabKeys.Public);
const [typeList, setTypeList] = useState<CategoryData[]>([]);
const [tagList, setTagList] = useState<CategoryData[]>([]);
const [activeType, setActiveType] = useState<number | undefined>(cacheState?.activeType);
const [activeTag, setActiveTag] = useState<number | undefined>(cacheState?.activeTag);
const [activeType, setActiveType] = useState<string | undefined>(cacheState?.activeType);
const [activeTag, setActiveTag] = useState<string | undefined>(cacheState?.activeTag);
const dataListRef = useRef<ResourceListRef>(null);
const config = resourceConfig[resourceType];
@ -34,12 +34,12 @@ function ResourcePage({ resourceType }: ResourcePageProps) {
// 选择类型
const chooseType = (record: CategoryData) => {
setActiveType((prev) => (prev === record.id ? undefined : record.id));
setActiveType((prev) => (prev === record.name ? undefined : record.name));
};
// 选择 Tag
const chooseTag = (record: CategoryData) => {
setActiveTag((prev) => (prev === record.id ? undefined : record.id));
setActiveTag((prev) => (prev === record.name ? undefined : record.name));
};
// 获取分类

View File

@ -1,125 +1,36 @@
import CommonTableCell from '@/components/CommonTableCell';
import DateTableCell from '@/components/DateTableCell';
import KFIcon from '@/components/KFIcon';
import { useEffectWhen } from '@/hooks';
import AddVersionModal from '@/pages/Dataset/components/AddVersionModal';
import {
ResourceData,
ResourceFileData,
ResourceType,
ResourceVersionData,
resourceConfig,
} from '@/pages/Dataset/config';
import { downLoadZip } from '@/utils/downloadfile';
import { openAntdModal } from '@/utils/modal';
import { to } from '@/utils/promise';
import { modalConfirm } from '@/utils/ui';
import { App, Button, Flex, Select, Table } from 'antd';
import { useState } from 'react';
import { Button, Flex, Table } from 'antd';
import styles from './index.less';
type ResourceVersionProps = {
resourceType: ResourceType;
resourceId: number;
resourceName: string;
isPublic: boolean;
versionList: ResourceVersionData[];
version?: string;
isActive: boolean;
getVersionList: () => void;
onVersionChange: (version: string) => void;
info: ResourceData;
};
function ResourceVersion({
resourceType,
resourceId,
resourceName,
isPublic,
versionList,
version,
isActive,
getVersionList,
onVersionChange,
}: ResourceVersionProps) {
const [fileList, setFileList] = useState<ResourceFileData[]>([]);
const { message } = App.useApp();
function ResourceVersion({ resourceType, info }: ResourceVersionProps) {
const config = resourceConfig[resourceType];
// 获取版本文件列表
useEffectWhen(
() => {
if (version) {
getFileList(version);
} else {
setFileList([]);
}
},
[resourceId, version],
isActive,
);
// 获取版本下的文件列表
const getFileList = async (version: string) => {
const params = {
version,
[config.fileReqParamKey]: resourceId,
};
const request = config.getFiles;
const [res] = await to(request(params));
if (res) {
setFileList(res?.data?.content ?? []);
}
};
// 删除版本
const deleteVersion = async () => {
const request = config.deleteVersion;
const params = {
[config.idParamKey]: resourceId,
version,
};
const [res] = await to(request(params));
if (res) {
getVersionList();
message.success('删除成功');
}
};
// 新建版本
const showModal = () => {
const { close } = openAntdModal(AddVersionModal, {
resourceType: resourceType,
resourceId: resourceId,
initialName: resourceName,
onOk: () => {
getVersionList();
close();
},
});
};
// 处理删除
const hanldeDelete = () => {
modalConfirm({
title: '删除后,该版本将不可恢复',
content: '是否确认删除?',
okText: '确认',
cancelText: '取消',
onOk: () => {
deleteVersion();
},
});
};
const filePropKey = config.filePropKey as keyof ResourceData;
const fileList = (info[filePropKey] ?? []) as ResourceFileData[];
fileList.forEach((item) => (item.update_time = info.update_time));
// 全部导出
const handleExport = async () => {
const url = config.downloadAllAction;
downLoadZip(url, { models_id: resourceId, version });
downLoadZip(url, { name: info.name, id: info.id, version: info.version });
};
// 单个导出
const downloadAlone = (record: ResourceFileData) => {
const downloadAlone = async (record: ResourceFileData) => {
const url = config.downloadSingleAction;
downLoadZip(`${url}/${record.id}`);
downLoadZip(url, { url: record.url });
};
const columns = [
@ -142,12 +53,6 @@ function ResourceVersion({
</a>
),
},
{
title: '版本号',
dataIndex: 'version',
key: 'version',
render: CommonTableCell(),
},
{
title: '文件大小',
dataIndex: 'file_size',
@ -163,7 +68,7 @@ function ResourceVersion({
{
title: '操作',
dataIndex: 'option',
width: '100px',
width: 160,
key: 'option',
render: (_: any, record: ResourceFileData) => [
<Button
@ -183,32 +88,9 @@ function ResourceVersion({
<div className={styles['resource-version']}>
<Flex justify="space-between" align="center" style={{ margin: '30px 0' }}>
<Flex align="center">
<span style={{ marginRight: '10px' }}></span>
<Select
placeholder="请选择版本号"
style={{ width: '160px', marginRight: '20px' }}
value={version}
onChange={onVersionChange}
options={versionList}
/>
<Button type="default" onClick={showModal} icon={<KFIcon type="icon-xinjian2" />}>
</Button>
</Flex>
<Flex align="center">
{!isPublic && (
<Button
type="default"
style={{ marginRight: '20px' }}
onClick={hanldeDelete}
icon={<KFIcon type="icon-shanchu" />}
>
</Button>
)}
<Button
type="default"
disabled={!version}
disabled={fileList.length === 0}
onClick={handleExport}
icon={<KFIcon type="icon-xiazai" />}
>
@ -216,12 +98,7 @@ function ResourceVersion({
</Button>
</Flex>
</Flex>
<div style={{ marginBottom: '30px', fontSize: '15px' }}>
{fileList.length > 0 && fileList[0].description
? '版本描述:' + fileList[0].description
: null}
</div>
<Table columns={columns} dataSource={fileList} pagination={false} rowKey="id" />
<Table columns={columns} dataSource={fileList} pagination={false} rowKey="url" />
</div>
);
}

View File

@ -1,20 +1,18 @@
import KFIcon from '@/components/KFIcon';
import { CommonTabKeys } from '@/enums';
import {
addDatasetVersionDetail,
addModelsVersionDetail,
addDatasetVersion,
addModelVersion,
deleteDataset,
deleteDatasetVersion,
deleteModel,
deleteModelVersion,
getDatasetById,
getDatasetInfo,
getDatasetList,
getDatasetVersionIdList,
getDatasetVersionsById,
getModelById,
getDatasetVersionList,
getModelInfo,
getModelList,
getModelVersionIdList,
getModelVersionsById,
getModelVersionList,
} from '@/services/dataset/index.js';
import type { TabsProps } from 'antd';
@ -26,15 +24,14 @@ export enum ResourceType {
type ResourceTypeInfo = {
getList: (params: any) => Promise<any>; // 获取资源列表
getVersions: (params: any) => Promise<any>; // 获取版本列表
getFiles: (params: any) => Promise<any>; // 获取版本下的文件列表
deleteRecord: (params: any) => Promise<any>; // 删除
addVersion: (params: any) => Promise<any>; // 新增版本
deleteVersion: (params: any) => Promise<any>; // 删除版本
getInfo: (params: any) => Promise<any>; // 获取详情
name: string; // 名称
typeParamKey: string; // 类型参数名称,获取资源列表接口使用
tagParamKey: string; // 标签参数名称,获取资源列表接口使用
fileReqParamKey: 'models_id' | 'dataset_id'; // 文件请求参数名称,获取文件列表接口使用
typeParamKey: 'data_type' | 'model_type'; // 类型参数名称,获取资源列表接口使用
tagParamKey: 'data_tag' | 'model_tag'; // 标签参数名称,获取资源列表接口使用
filePropKey: 'dataset_version_vos' | 'model_version_vos'; // 文件列表属性
tabItems: TabsProps['items']; // tab 列表
typeTitle: string; // 类型标题
tagTitle: string; // 标签标题
@ -43,28 +40,24 @@ type ResourceTypeInfo = {
prefix: string; // 图片资源、详情 url 的前缀
deleteModalTitle: string; // 删除弹框的title
addBtnTitle: string; // 新增按钮的title
idParamKey: 'models_id' | 'dataset_id'; // 新建版本、删除版本接口,版本 id 的参数名称
uploadAction: string; // 上传接口 url
uploadAccept?: string; // 上传文件类型
downloadAllAction: string; // 批量下载接口 url
downloadSingleAction: string; // 单个下载接口 url
infoTypePropertyName: string; // 详情数据中,类型属性名称
infoTagPropertyName: string; // 详情数据中,标签属性名称
};
export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = {
[ResourceType.Dataset]: {
getList: getDatasetList,
getVersions: getDatasetVersionsById,
getFiles: getDatasetVersionIdList,
getVersions: getDatasetVersionList,
deleteRecord: deleteDataset,
addVersion: addDatasetVersionDetail,
addVersion: addDatasetVersion,
deleteVersion: deleteDatasetVersion,
getInfo: getDatasetById,
getInfo: getDatasetInfo,
name: '数据集',
typeParamKey: 'data_type',
tagParamKey: 'data_tag',
fileReqParamKey: 'dataset_id',
filePropKey: 'dataset_version_vos',
tabItems: [
{
key: CommonTabKeys.Public,
@ -84,26 +77,22 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = {
prefix: 'dataset',
deleteModalTitle: '确定删除该条数据集实例吗?',
addBtnTitle: '新建数据集',
idParamKey: 'dataset_id',
uploadAction: '/api/mmp/dataset/upload',
uploadAction: '/api/mmp/newdataset/upload',
uploadAccept: '.zip,.tgz',
downloadAllAction: '/api/mmp/dataset/downloadAllFilesl',
downloadSingleAction: '/api/mmp/dataset/download',
infoTypePropertyName: 'dataset_type_name',
infoTagPropertyName: 'dataset_tag_name',
downloadAllAction: '/api/mmp/newdataset/downloadAllFiles',
downloadSingleAction: '/api/mmp/newdataset/downloadSinggerFile',
},
[ResourceType.Model]: {
getList: getModelList,
getVersions: getModelVersionsById,
getFiles: getModelVersionIdList,
getVersions: getModelVersionList,
deleteRecord: deleteModel,
addVersion: addModelsVersionDetail,
addVersion: addModelVersion,
deleteVersion: deleteModelVersion,
getInfo: getModelById,
getInfo: getModelInfo,
name: '模型',
typeParamKey: 'model_type',
tagParamKey: 'model_tag',
fileReqParamKey: 'models_id',
filePropKey: 'model_version_vos',
tabItems: [
{
key: CommonTabKeys.Public,
@ -123,13 +112,10 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = {
prefix: 'model',
deleteModalTitle: '确定删除该条模型实例吗?',
addBtnTitle: '新建模型',
idParamKey: 'models_id',
uploadAction: '/api/mmp/models/upload',
uploadAction: '/api/mmp/newmodel/upload',
uploadAccept: undefined,
downloadAllAction: '/api/mmp/models/downloadAllFiles',
downloadSingleAction: '/api/mmp/models/download_model',
infoTypePropertyName: 'model_type_name',
infoTagPropertyName: 'model_tag_name',
downloadAllAction: '/api/mmp/newmodel/downloadAllFiles',
downloadSingleAction: '/api/mmp/newmodel/downloadSingleFile',
},
};
@ -141,36 +127,69 @@ export type CategoryData = {
path: string;
};
// 资源数据
export type ResourceData = {
// 数据集、模型列表数据
export interface ResourceData {
id: number;
name: string;
description: string;
create_by: string;
update_time: string;
available_range: number;
model_type_name?: string;
model_tag_name?: string;
dataset_type_name?: string;
dataset_tag_name?: string;
};
identifier: string;
owner: string;
version: string;
is_public: boolean;
description?: string;
create_by?: string;
update_time?: string;
time_ago?: string;
version_desc?: string;
usage?: string;
relative_paths?: string;
resourceType: ResourceType.Dataset | ResourceType.Model;
}
// 数据集数据
export interface DatasetData extends ResourceData {
resourceType: ResourceType.Dataset; // 用于区别类型
data_type?: string; // 数据集分类
data_tag?: string; // 研究方向
processing_code?: string; // 处理代码
dataset_source?: string; // 数据来源
dataset_version_vos?: ResourceFileData[];
}
// 模型数据
export interface ModelData extends ResourceData {
resourceType: ResourceType.Model; // 用于区别类型
model_type?: string; // 模型框架
model_tag?: string; // 模型能力
image?: string; // 训练镜像
code?: string; // 训练镜像
train_datasets?: DatasetData[]; // 训练数据集
test_datasets?: DatasetData[]; // 测试数据集
params?: Record<string, string>; // 参数
metrics?: Record<string, string>; // 指标
train_task?: TrainTask; // 训练任务
model_source?: string; // 模型来源
model_version_vos?: ResourceFileData[];
}
// 版本数据
export type ResourceVersionData = {
label: string;
value: string;
name: string;
http_url: string;
tar_url: string;
zip_url: string;
};
// 版本文件数据
export type ResourceFileData = {
id: number;
file_name: string;
file_size: string;
description: string;
create_by: string;
create_time: string;
update_by: string;
update_time: string;
url: string;
version: string;
update_time?: string;
};
// 训练任务
export type TrainTask = {
ins_id: number;
name: string;
task_id: string;
};

View File

@ -1,8 +1,8 @@
import ResourceIntro from '@/pages/Dataset/components/ResourceIntro';
import ResourceInfo from '@/pages/Dataset/components/ResourceInfo';
import { ResourceType } from '@/pages/Dataset/config';
function DatasetIntro() {
return <ResourceIntro resourceType={ResourceType.Dataset} />;
function DatasetInfo() {
return <ResourceInfo resourceType={ResourceType.Dataset} />;
}
export default DatasetIntro;
export default DatasetInfo;

View File

@ -8,11 +8,11 @@ import KFRadio, { type KFRadioItem } from '@/components/KFRadio';
import PageTitle from '@/components/PageTitle';
import ResourceSelect, {
requiredValidator,
ResourceSelectorType,
type ParameterInputObject,
} from '@/components/ResourceSelect';
import SubAreaTitle from '@/components/SubAreaTitle';
import { useComputingResource } from '@/hooks/resource';
import { ResourceSelectorType } from '@/pages/Pipeline/components/ResourceSelectorModal';
import { createEditorReq } from '@/services/developmentEnvironment';
import { to } from '@/utils/promise';
import { useNavigate } from '@umijs/max';
@ -90,7 +90,6 @@ function EditorCreate() {
<Form
name="editor-create"
labelCol={{ flex: '100px' }}
wrapperCol={{ flex: 1 }}
labelAlign="left"
form={form}
initialValues={{ computing_resource: ComputingResourceType.GPU }}

View File

@ -34,7 +34,7 @@ function ExperimentComparison() {
// const [cacheState, setCacheState] = useCacheState();
// const [total, setTotal] = useState(0);
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const [loading, setLoading] = useState(false);
// const [loading, setLoading] = useState(false);
const { message } = App.useApp();
const config = useMemo(() => comparisonConfig[comparisonType], [comparisonType]);
// const [pagination, setPagination] = useState<TablePaginationConfig>(
@ -50,11 +50,11 @@ function ExperimentComparison() {
// 获取对比数据列表
const getComparisonData = async () => {
setLoading(true);
// setLoading(true);
const request =
comparisonType === ComparisonType.Train ? getExpTrainInfosReq : getExpEvaluateInfosReq;
const [res] = await to(request(experimentId));
setLoading(false);
// setLoading(false);
if (res && res.data) {
// const { content = [], totalElements = 0 } = res.data;
setTableData(res.data);
@ -204,7 +204,7 @@ function ExperimentComparison() {
scroll={{ y: 'calc(100% - 55px)', x: '100%' }}
pagination={false}
bordered={true}
loading={loading}
// loading={loading}
// pagination={{
// ...pagination,
// total: total,

View File

@ -116,6 +116,8 @@ function ExperimentInstanceComponent({
<img
style={{ width: '17px', marginRight: '7px' }}
src={experimentStatusInfo[item.status as ExperimentStatus]?.icon}
draggable={false}
alt=""
/>
<span
style={{ color: experimentStatusInfo[item.status as ExperimentStatus]?.color }}

View File

@ -14,7 +14,12 @@ function ExperimentStatusCell(status?: ExperimentStatus | null) {
}
return (
<div className={styles['experiment-status-cell']}>
<img style={{ width: '17px', marginRight: '7px' }} src={statusInfo[status]?.icon} />
<img
style={{ width: '17px', marginRight: '7px' }}
src={statusInfo[status]?.icon}
draggable={false}
alt=""
/>
<span
style={{ color: statusInfo[status]?.color }}
className={styles['experiment-status-cell__label']}

View File

@ -2,10 +2,10 @@ import editExperimentIcon from '@/assets/img/edit-experiment.png';
import KFModal from '@/components/KFModal';
import { type ResourceData } from '@/pages/Dataset/config';
import {
addModelsVersionDetail,
addModelVersion,
exportModelReq,
getModelList,
getModelVersionsById,
getModelVersionList,
} from '@/services/dataset';
import { to } from '@/utils/promise';
import { InfoCircleOutlined } from '@ant-design/icons';
@ -85,7 +85,7 @@ function ExportModelModal({ path, onOk, ...rest }: ExportModelModalProps) {
// 获取模型版本列表
const getModelVersions = async (id: number) => {
const [res] = await to(getModelVersionsById(id));
const [res] = await to(getModelVersionList(id));
if (res && res.data) {
setVersions(res.data);
}
@ -118,7 +118,7 @@ function ExportModelModal({ path, onOk, ...rest }: ExportModelModalProps) {
// 创建模型版本
const createModelVersion = async (params: CreateModelVersionParams[]) => {
const [res] = await to(addModelsVersionDetail(params));
const [res] = await to(addModelVersion(params));
if (res) {
onOk();
}

View File

@ -70,6 +70,8 @@ function TensorBoardStatusCell({
className={styles['tensorBoard-status__icon']}
src={statusConfig[status].icon}
onClick={onClick}
draggable={false}
alt=""
/>
)}
</>

View File

@ -385,6 +385,8 @@ function Experiment() {
style={{ width: '17px', marginRight: '6px' }}
key={index}
src={experimentStatusInfo[item].icon}
draggable={false}
alt=""
/>
);
})

View File

@ -8,6 +8,7 @@ import DateTableCell from '@/components/DateTableCell';
import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle';
import SubAreaTitle from '@/components/SubAreaTitle';
import { MirrorVersionStatus } from '@/enums';
import { useDomSize } from '@/hooks';
import { useCacheState } from '@/hooks/pageCacheState';
import {
@ -36,7 +37,7 @@ import { useEffect, useMemo, useState } from 'react';
import MirrorStatusCell from '../components/MirrorStatusCell';
import styles from './index.less';
type MirrorInfoData = {
export type MirrorInfoData = {
name?: string;
description?: string;
version_count?: string;
@ -44,13 +45,14 @@ type MirrorInfoData = {
image_type?: number;
};
type MirrorVersionData = {
export type MirrorVersionData = {
id: number;
version: string;
url: string;
status: string;
status: MirrorVersionStatus;
file_size: string;
create_time: string;
tag_name: string;
};
function MirrorInfo() {

View File

@ -1,16 +1,11 @@
.model-evolution {
width: 100%;
height: 100%;
overflow-x: hidden;
background-color: white;
&__top {
padding: 30px 0;
color: @text-color;
font-size: @font-size-content;
}
&__graph {
height: calc(100% - 92px);
height: calc(100%);
background-color: @background-color;
background-image: url(@/assets/img/pipeline-canvas-bg.png);
background-size: 100% 100%;

View File

@ -5,15 +5,12 @@
*/
import { useEffectWhen } from '@/hooks';
import { ResourceVersionData } from '@/pages/Dataset/config';
import { getModelAtlasReq } from '@/services/dataset/index.js';
import themes from '@/styles/theme.less';
import { to } from '@/utils/promise';
import G6, { G6GraphEvent, Graph, INode } from '@antv/g6';
// @ts-ignore
import { Flex, Select } from 'antd';
import { useEffect, useRef, useState } from 'react';
import GraphLegend from '../GraphLegend';
import NodeTooltips from '../NodeTooltips';
import styles from './index.less';
import type { ModelDepsData, ProjectDependency, TrainDataset } from './utils';
@ -29,7 +26,7 @@ import {
type modeModelEvolutionProps = {
resourceId: number;
versionList: ResourceVersionData[];
identifier: string;
version?: string;
isActive: boolean;
onVersionChange: (version: string) => void;
@ -38,7 +35,7 @@ type modeModelEvolutionProps = {
let graph: Graph;
function ModelEvolution({
resourceId,
versionList,
identifier,
version,
isActive,
onVersionChange,
@ -147,14 +144,15 @@ function ModelEvolution({
// 更加缩放,调整 tooltip 位置
const offsetX = (nodeWidth * zoom) / 4;
const offsetY = (nodeHeight * zoom) / 4;
point.x += offsetX;
const canvasWidth = graphRef.current!.clientWidth;
if (point.x + 300 > canvasWidth) {
point.x = canvasWidth - 300;
if (point.x + 300 > canvasWidth + 30) {
point.x = canvasWidth + 30 - 300;
}
setHoverNodeData(model);
setNodeToolTipX(point.x + offsetX);
setNodeToolTipX(point.x);
setNodeToolTipY(graphRef.current!.clientHeight - point.y + offsetY);
setShowNodeTooltip(true);
});
@ -217,7 +215,8 @@ function ModelEvolution({
// 获取模型依赖
const getModelAtlas = async () => {
const params = {
current_model_id: resourceId,
id: resourceId,
identifier,
version,
};
const [res] = await to(getModelAtlasReq(params));
@ -249,18 +248,6 @@ function ModelEvolution({
return (
<div className={styles['model-evolution']}>
<Flex align="center" className={styles['model-evolution__top']}>
<span style={{ marginRight: '10px' }}></span>
<Select
placeholder="请选择版本号"
style={{ width: '160px', marginRight: '20px' }}
value={version}
allowClear
onChange={onVersionChange}
options={versionList}
/>
<GraphLegend style={{ marginRight: 0, marginLeft: 'auto' }}></GraphLegend>
</Flex>
<div className={styles['model-evolution__graph']} id="canvas" ref={graphRef}></div>
{(showNodeTooltip || enterTooltip) && (
<NodeTooltips

View File

@ -1,3 +1,4 @@
import { TrainTask } from '@/pages/Dataset/config';
import { changePropertyName, fittingString } from '@/utils';
import { EdgeConfig, GraphData, LayoutConfig, NodeConfig, TreeGraphData, Util } from '@antv/g6';
// @ts-ignore
@ -31,16 +32,12 @@ export type Rect = {
height: number;
};
export type TrainTask = {
ins_id: number;
name: string;
task_id: string;
};
export interface TrainDataset extends NodeConfig {
dataset_id: number;
dataset_name: string;
dataset_version: string;
repo_id: number;
name: string;
version: string;
identifier: string;
owner: string;
model_type: NodeType.TestDataset | NodeType.TrainDataset;
}
@ -51,34 +48,33 @@ export interface ProjectDependency extends NodeConfig {
model_type: NodeType.Project;
}
export type ModalDetail = {
export type ModelMeta = {
train_datasets?: TrainDataset[];
test_datasets?: TrainDataset[];
project_depency?: ProjectDependency;
train_task?: TrainTask;
name: string;
available_range: number;
file_name: string;
file_size: string;
description: string;
model_type_name: string;
model_tag_name: string;
version: string;
model_source: string;
model_type: string;
create_time: string;
file_size: string;
is_public: boolean;
};
export interface ModelDepsAPIData {
current_model_id: number;
repo_id: number;
model_name: string;
version: string;
workflow_id: number;
exp_ins_id: number;
model_type: NodeType.Children | NodeType.Current | NodeType.Parent;
current_model_name: string;
project_dependency?: ProjectDependency;
test_dataset: TrainDataset[];
train_dataset: TrainDataset[];
train_task: TrainTask;
model_version_dependcy_vo: ModalDetail;
children_models: ModelDepsAPIData[];
parent_models: ModelDepsAPIData[];
model_meta: ModelMeta;
child_model_list: ModelDepsAPIData[];
parent_model_vo?: ModelDepsAPIData;
}
export interface ModelDepsData extends Omit<ModelDepsAPIData, 'children_models'>, TreeGraphData {
export interface ModelDepsData extends Omit<ModelDepsAPIData, 'child_model_list'>, TreeGraphData {
children: ModelDepsData[];
expanded: boolean; // 是否展开
level: number; // 层级,从 0 开始
@ -92,8 +88,11 @@ export function normalizeChildren(data: ModelDepsData[]) {
item.model_type = NodeType.Children;
item.expanded = false;
item.level = 0;
item.datasetLen = item.train_dataset.length + item.test_dataset.length;
item.id = `$M_${item.current_model_id}_${item.version}`;
item.datasetLen = getDatasetLen(
item.model_meta.train_datasets,
item.model_meta.test_datasets,
);
item.id = `$M_${item.repo_id}_${item.version}`;
item.label = getLabel(item);
item.style = getStyle(NodeType.Children);
normalizeChildren(item.children);
@ -104,16 +103,17 @@ export function normalizeChildren(data: ModelDepsData[]) {
// 获取 label
export function getLabel(node: ModelDepsData | ModelDepsAPIData) {
return (
fittingString(
`${node.model_version_dependcy_vo.name ?? ''}`,
nodeWidth - labelPadding,
nodeFontSize,
) +
fittingString(`${node.model_name ?? ''}`, nodeWidth - labelPadding, nodeFontSize) +
'\n' +
fittingString(`${node.version}`, nodeWidth - labelPadding, nodeFontSize)
);
}
// 获取数据集数量
export function getDatasetLen(train?: TrainDataset[], test?: TrainDataset[]) {
return (train?.length || 0) + (test?.length || 0);
}
// 获取 style
export function getStyle(model_type: NodeType) {
let fill = '';
@ -148,41 +148,43 @@ export function getStyle(model_type: NodeType) {
export function normalizeTreeData(apiData: ModelDepsAPIData): ModelDepsData {
// 将 children_models 转换成 children
let normalizedData = changePropertyName(apiData, {
children_models: 'children',
child_model_list: 'children',
}) as ModelDepsData;
// 设置当前模型的数据
normalizedData.model_type = NodeType.Current;
normalizedData.id = `$M_${normalizedData.current_model_id}_${normalizedData.version}`;
normalizedData.id = `$M_${normalizedData.repo_id}_${normalizedData.version}`;
normalizedData.label = getLabel(normalizedData);
normalizedData.style = getStyle(NodeType.Current);
normalizedData.expanded = true;
normalizedData.datasetLen =
normalizedData.train_dataset.length + normalizedData.test_dataset.length;
normalizedData.datasetLen = getDatasetLen(
normalizedData.model_meta.train_datasets,
normalizedData.model_meta.test_datasets,
);
normalizeChildren(normalizedData.children as ModelDepsData[]);
normalizedData.level = 0;
// 将 parent_models 转换成树形结构
let parent_models = normalizedData.parent_models || [];
while (parent_models.length > 0) {
const parent = parent_models[0];
let parent_model = normalizedData.parent_model_vo;
while (parent_model) {
const parent = parent_model;
normalizedData = {
...parent,
expanded: false,
level: 0,
datasetLen: parent.train_dataset.length + parent.test_dataset.length,
datasetLen: getDatasetLen(parent.model_meta.train_datasets, parent.model_meta.test_datasets),
model_type: NodeType.Parent,
id: `$M_${parent.current_model_id}_${parent.version}`,
id: `$M_${parent.repo_id}_${parent.version}`,
label: getLabel(parent),
style: getStyle(NodeType.Parent),
children: [
{
...normalizedData,
parent_models: [],
parent_model: null,
},
],
};
parent_models = normalizedData.parent_models || [];
parent_model = normalizedData.parent_model_vo;
}
return normalizedData;
}
@ -195,11 +197,12 @@ export function getGraphData(data: ModelDepsData, hierarchyNodes: ModelDepsData[
getWidth: () => nodeWidth,
getVGap: (node: NodeConfig) => {
const model = node as ModelDepsData;
const { model_type, expanded, project_dependency } = model;
const { model_type, expanded, model_meta } = model;
const { project_depency } = model_meta;
if (model_type === NodeType.Current || model_type === NodeType.Parent) {
return vGap / 2;
}
const selfGap = expanded && project_dependency?.url ? nodeHeight + vGap : 0;
const selfGap = expanded && project_depency?.url ? nodeHeight + vGap : 0;
const nextNode = getSameHierarchyNextNode(model, hierarchyNodes);
if (!nextNode) {
return vGap / 2;
@ -254,28 +257,35 @@ const addDatasetDependency = (
nodes: NodeConfig[],
edges: EdgeConfig[],
) => {
const { train_dataset, test_dataset, id } = data;
train_dataset.forEach((item) => {
item.id = `$DTrain_${id}_${item.dataset_id}_${item.dataset_version}`;
const { repo_id, model_meta } = data;
const { train_datasets, test_datasets } = model_meta;
train_datasets?.forEach((item) => {
if (!item.repo_id) {
item.repo_id = item.id;
}
item.id = `$DTrain_${repo_id}_${item.repo_id}_${item.version}`;
item.model_type = NodeType.TrainDataset;
item.style = getStyle(NodeType.TrainDataset);
});
test_dataset.forEach((item) => {
item.id = `$DTest_${id}_${item.dataset_id}_${item.dataset_version}`;
test_datasets?.forEach((item) => {
if (!item.repo_id) {
item.repo_id = item.id;
}
item.id = `$DTest_${repo_id}_${item.repo_id}_${item.version}`;
item.model_type = NodeType.TestDataset;
item.style = getStyle(NodeType.TestDataset);
});
datasetNodes.length = 0;
const len = train_dataset.length + test_dataset.length;
[...train_dataset, ...test_dataset].forEach((item, index) => {
const len = getDatasetLen(train_datasets, test_datasets);
[...(train_datasets ?? []), ...(test_datasets ?? [])].forEach((item, index) => {
const node = { ...item };
node.type = 'ellipse';
node.size = [ellipseWidth, nodeHeight];
node.label =
fittingString(node.dataset_name, ellipseWidth - labelPadding, nodeFontSize) +
fittingString(node.name, ellipseWidth - labelPadding, nodeFontSize) +
'\n' +
fittingString(node.dataset_version, ellipseWidth - labelPadding, nodeFontSize);
fittingString(node.version, ellipseWidth - labelPadding, nodeFontSize);
const half = len / 2 - 0.5;
node.x = currentNode.x! - (half - index) * (ellipseWidth + datasetHGap);
@ -299,10 +309,11 @@ const addProjectDependency = (
nodes: NodeConfig[],
edges: EdgeConfig[],
) => {
const { project_dependency, id } = data;
if (project_dependency?.url) {
const node = { ...project_dependency };
node.id = `$P_${id}_${node.url}_${node.branch}`;
const { repo_id, model_meta } = data;
const { project_depency } = model_meta;
if (project_depency?.url) {
const node = { ...project_depency };
node.id = `$P_${repo_id}_${node.url}_${node.branch}`;
node.model_type = NodeType.Project;
node.type = 'rect';
node.label = fittingString(node.name, nodeWidth - labelPadding, nodeFontSize);
@ -322,6 +333,7 @@ const addProjectDependency = (
}
};
/*
// 判断两个矩形是否相交
function isRectanglesOverlap(rect1: Rect, rect2: Rect) {
const a2x = rect1.x + rect1.width / 2;
@ -366,6 +378,7 @@ function adjustDatasetPosition(node: NodeConfig) {
});
}
}
*/
// 层级遍历树结构
export function traverseHierarchically(data: ModelDepsData | undefined): ModelDepsData[] {

View File

@ -2,6 +2,7 @@
position: absolute;
bottom: -100px;
left: -300px;
z-index: 10;
width: 300px;
padding: 10px;
background: white;
@ -50,6 +51,7 @@
flex: 1;
min-width: 0;
font-weight: 500;
word-break: break-all;
&:hover {
text-decoration: underline @underline-color;

View File

@ -1,4 +1,4 @@
import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceIntro';
import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceInfo';
import { formatDate } from '@/utils/date';
import { useNavigate } from '@umijs/max';
import { ModelDepsData, NodeType, ProjectDependency, TrainDataset } from '../ModelEvolution/utils';
@ -14,9 +14,9 @@ function ModelInfo({ resourceId, data, onVersionChange }: ModelInfoProps) {
const navigate = useNavigate();
const gotoExperimentPage = () => {
if (data.train_task?.ins_id) {
if (data.model_meta.train_task?.ins_id) {
const { origin } = location;
const url = `${origin}/pipeline/experiment/instance/${data.workflow_id}/${data.train_task.ins_id}`;
const url = `${origin}/pipeline/experiment/instance/${data.model_meta.train_task.task_id}/${data.model_meta.train_task.ins_id}`;
window.open(url, '_blank');
}
};
@ -25,10 +25,10 @@ function ModelInfo({ resourceId, data, onVersionChange }: ModelInfoProps) {
if (data.model_type === NodeType.Current) {
return;
}
if (data.current_model_id === resourceId) {
if (data.repo_id === resourceId) {
onVersionChange?.(data.version);
} else {
const path = `/dataset/model/info/${data.current_model_id}?tab=${ResourceInfoTabKeys.Evolution}&version=${data.version}`;
const path = `/dataset/model/info/${data.repo_id}?tab=${ResourceInfoTabKeys.Evolution}&version=${data.version}&name=${data.model_name}&owner=${data.owner}&identifier=${data.identifier}`;
navigate(path);
}
};
@ -40,12 +40,10 @@ function ModelInfo({ resourceId, data, onVersionChange }: ModelInfoProps) {
<div className={styles['node-tooltips__row']}>
<span className={styles['node-tooltips__row__title']}></span>
{data.model_type === NodeType.Current ? (
<span className={styles['node-tooltips__row__value']}>
{data.model_version_dependcy_vo?.name || '--'}
</span>
<span className={styles['node-tooltips__row__value']}>{data.model_name || '--'}</span>
) : (
<ValueLink
value={data.model_version_dependcy_vo?.name}
value={data.model_name}
className={styles['node-tooltips__row__link']}
nullClassName={styles['node-tooltips__row__value']}
onClick={gotoModelPage}
@ -59,25 +57,25 @@ function ModelInfo({ resourceId, data, onVersionChange }: ModelInfoProps) {
<div className={styles['node-tooltips__row']}>
<span className={styles['node-tooltips__row__title']}></span>
<span className={styles['node-tooltips__row__value']}>
{data.model_version_dependcy_vo?.model_type_name || '--'}
{data.model_meta.model_type || '--'}
</span>
</div>
<div className={styles['node-tooltips__row']}>
<span className={styles['node-tooltips__row__title']}></span>
<span className={styles['node-tooltips__row__value']}>
{data.model_version_dependcy_vo?.file_size || '--'}
{data.model_meta.file_size || '--'}
</span>
</div>
<div className={styles['node-tooltips__row']}>
<span className={styles['node-tooltips__row__title']}></span>
<span className={styles['node-tooltips__row__value']}>
{formatDate(data.model_version_dependcy_vo?.create_time)}
{formatDate(data.model_meta.create_time || '--')}
</span>
</div>
<div className={styles['node-tooltips__row']}>
<span className={styles['node-tooltips__row__title']}></span>
<span className={styles['node-tooltips__row__value']}>
{data.model_version_dependcy_vo?.available_range === 1 ? '公开' : '私有'}
{data.model_meta.is_public ? '公开' : '私有'}
</span>
</div>
</div>
@ -86,7 +84,7 @@ function ModelInfo({ resourceId, data, onVersionChange }: ModelInfoProps) {
<div className={styles['node-tooltips__row']}>
<span className={styles['node-tooltips__row__title']}></span>
<ValueLink
value={data.train_task?.name}
value={data.model_meta.train_task?.name}
className={styles['node-tooltips__row__link']}
nullClassName={styles['node-tooltips__row__value']}
onClick={gotoExperimentPage}
@ -100,7 +98,7 @@ function ModelInfo({ resourceId, data, onVersionChange }: ModelInfoProps) {
function DatasetInfo({ data }: { data: TrainDataset }) {
const gotoDatasetPage = () => {
const { origin } = location;
const url = `${origin}/dataset/dataset/info/${data.dataset_id}?tab=${ResourceInfoTabKeys.Version}&version=${data.dataset_version}`;
const url = `${origin}/dataset/dataset/info/${data.repo_id}?tab=${ResourceInfoTabKeys.Version}&version=${data.version}&name=${data.name}&owner=${data.owner}&identifier=${data.identifier}`;
window.open(url, '_blank');
};
@ -111,7 +109,7 @@ function DatasetInfo({ data }: { data: TrainDataset }) {
<div className={styles['node-tooltips__row']}>
<span className={styles['node-tooltips__row__title']}></span>
<ValueLink
value={data.dataset_name}
value={data.name}
className={styles['node-tooltips__row__link']}
nullClassName={styles['node-tooltips__row__value']}
onClick={gotoDatasetPage}
@ -119,9 +117,7 @@ function DatasetInfo({ data }: { data: TrainDataset }) {
</div>
<div className={styles['node-tooltips__row']}>
<span className={styles['node-tooltips__row__title']}></span>
<span className={styles['node-tooltips__row__value']}>
{data.dataset_version || '--'}
</span>
<span className={styles['node-tooltips__row__value']}>{data.version || '--'}</span>
</div>
</div>
</>

View File

@ -1,8 +1,8 @@
import ResourceIntro from '@/pages/Dataset/components/ResourceIntro';
import ResourceInfo from '@/pages/Dataset/components/ResourceInfo';
import { ResourceType } from '@/pages/Dataset/config';
function ModelIntro() {
return <ResourceIntro resourceType={ResourceType.Model} />;
function ModelInfo() {
return <ResourceInfo resourceType={ResourceType.Model} />;
}
export default ModelIntro;
export default ModelInfo;

View File

@ -7,12 +7,12 @@ import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle';
import ResourceSelect, {
requiredValidator,
ResourceSelectorType,
type ParameterInputObject,
} from '@/components/ResourceSelect';
import SubAreaTitle from '@/components/SubAreaTitle';
import { CommonTabKeys } from '@/enums';
import { useComputingResource } from '@/hooks/resource';
import { ResourceSelectorType } from '@/pages/Pipeline/components/ResourceSelectorModal';
import {
createModelDeploymentReq,
restartModelDeploymentReq,

View File

@ -67,7 +67,7 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) {
<KFModal
{...rest}
title="选择代码配置"
image={require('@/assets/img/edit-experiment.png')}
image={require('@/assets/img/modal-code-config.png')}
width={920}
footer={null}
destroyOnClose

View File

@ -48,6 +48,7 @@ const ModelMenu = ({ onComponentDragEnd }: ModelMenuProps) => {
<img
style={{ height: '16px', marginRight: '15px' }}
src={`/assets/images/${ele.icon_path}.png`}
draggable={false}
alt=""
/>
)}

View File

@ -204,11 +204,14 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
});
}
} else {
const { activeTab, id, name, version, path } = res;
const { activeTab, id, name, version, path, identifier, owner } = res;
const value = JSON.stringify({
id,
name,
version,
path,
identifier,
owner,
});
const showValue = `${name}:${version}`;
form.setFieldValue(formItemName, {
@ -245,6 +248,10 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
// 获取选择数据集、模型后面按钮 icon
const getSelectBtnIcon = (item: { item_type: string }) => {
const type = item.item_type;
if (type === 'code') {
return <KFIcon type="icon-xuanzedaimapeizhi" />;
}
let selectorType: ResourceSelectorType;
if (type === 'dataset') {
selectorType = ResourceSelectorType.Dataset;

View File

@ -1,17 +1,21 @@
import datasetImg from '@/assets/img/modal-select-dataset.png';
import mirrorImg from '@/assets/img/modal-select-mirror.png';
import modelImg from '@/assets/img/modal-select-model.png';
import { CommonTabKeys, MirrorVersionStatus } from '@/enums';
import { AvailableRange, CommonTabKeys } from '@/enums';
import { ResourceData, ResourceVersionData } from '@/pages/Dataset/config';
import { MirrorVersionData } from '@/pages/Mirror/Info';
import { MirrorData } from '@/pages/Mirror/List';
import {
getDatasetInfo,
getDatasetList,
getDatasetVersionIdList,
getDatasetVersionsById,
getDatasetVersionList,
getModelInfo,
getModelList,
getModelVersionIdList,
getModelVersionsById,
getModelVersionList,
} from '@/services/dataset/index.js';
import { getMirrorListReq, getMirrorVersionListReq } from '@/services/mirror';
import type { TabsProps } from 'antd';
import type { TabsProps, TreeDataNode } from 'antd';
import { pick } from 'lodash';
export enum ResourceSelectorType {
Model = 'Model', // 模型
@ -19,111 +23,229 @@ export enum ResourceSelectorType {
Mirror = 'Mirror', //镜像
}
export type MirrorVersion = {
id: number; // 镜像版本 id
status: MirrorVersionStatus; // 镜像版本状态
tag_name: string; // 镜像版本 name
url: string; // 镜像版本路径
// 数据集、模型列表转为树形结构
const convertDatasetToTreeData = (list: ResourceData[]): TreeDataNode[] => {
return list.map((v) => ({
...v,
key: `${v.id}`,
title: v.name,
isLeaf: false,
checkable: false,
}));
};
export type SelectorTypeInfo = {
getList: (params: any) => Promise<any>; // 获取资源列表
getVersions: (params: any) => Promise<any>; // 获取资源版本列表
getFiles: (params: any) => Promise<any>; // 获取资源文件列表
handleVersionResponse: (res: any) => any[]; // 处理版本列表接口数据
modalIcon: string; // modal icon
buttonIcon: string; // button icon
name: string; // 名称
litReqParamKey: 'available_range' | 'image_type'; // 表示是公开还是私有的参数名称,获取资源列表接口使用
fileReqParamKey: 'models_id' | 'dataset_id'; // 文件请求参数名称,获取文件列表接口使用
tabItems: TabsProps['items']; // tab 列表
buttontTitle: string; // 按钮 title
// 镜像列表转为树形结构
const convertMirrorToTreeData = (list: MirrorData[]): TreeDataNode[] => {
return list.map((v) => ({
key: `${v.id}`,
title: v.name,
isLeaf: false,
checkable: false,
}));
};
// 获取镜像文件列表,为了兼容数据集和模型
const getMirrorFilesReq = ({ id, version }: { id: number; version: string }): Promise<any> => {
const index = version.indexOf('-');
const url = version.slice(index + 1);
return Promise.resolve({
data: {
// 数据集版本列表转为树形结构
const convertDatasetVersionToTreeData = (
parentId: string,
info: ResourceData,
list: ResourceVersionData[],
): TreeDataNode[] => {
return list.map((item: ResourceVersionData) => ({
...pick(info, ['id', 'name', 'owner', 'identifier']),
version: item.name,
title: item.name,
key: `${parentId}-${item.name}`,
isLeaf: true,
checkable: true,
}));
};
// 镜像版本列表转为树形结构
const convertMirrorVersionToTreeData = (
parentId: string,
list: MirrorVersionData[],
): TreeDataNode[] => {
return list.map((item: MirrorVersionData) => ({
url: item.url,
title: item.tag_name,
key: `${parentId}-${item.id}`,
isLeaf: true,
checkable: true,
}));
};
interface SelectorTypeInfo {
getList: (isPublic: boolean) => Promise<any>; // 获取资源列表
getVersions: (parentKey: string, parentNode: any) => Promise<any>; // 获取资源版本列表
getFiles: (parentKey: string, parentNode: any) => Promise<any>; // 获取资源文件列表
readonly modalIcon: string; // modal icon
readonly buttonIcon: string; // button icon
readonly name: string; // 名称
readonly tabItems: TabsProps['items']; // tab 列表
readonly buttontTitle: string; // 按钮 title
}
export class DatasetSelector implements SelectorTypeInfo {
readonly name = '数据集';
readonly modalIcon = datasetImg;
readonly buttonIcon = 'icon-xuanzeshujuji';
readonly tabItems = [
{
key: CommonTabKeys.Private,
label: '我的数据集',
},
{
key: CommonTabKeys.Public,
label: '公开数据集',
},
];
readonly buttontTitle = '选择数据集';
async getList(isPublic: boolean) {
const res = await getDatasetList({ is_public: isPublic, page: 0, size: 2000 });
if (res && res.data) {
const list = res.data.content || [];
return convertDatasetToTreeData(list);
} else {
return Promise.reject('获取数据集列表失败');
}
}
async getVersions(parentKey: string, parentNode: ResourceData) {
const res = await getDatasetVersionList(pick(parentNode, ['owner', 'identifier']));
if (res && res.data) {
const list = res.data;
return convertDatasetVersionToTreeData(parentKey, parentNode, list);
} else {
return Promise.reject('获取数据集版本列表失败');
}
}
async getFiles(_parentKey: string, parentNode: ResourceData & ResourceVersionData) {
const params = pick(parentNode, ['owner', 'identifier', 'id', 'name', 'version']);
const res = await getDatasetInfo(params);
if (res && res.data) {
const path = res.data.relative_paths || '';
const list = res.data.dataset_version_vos || [];
return {
path,
content: list,
};
} else {
return Promise.reject('获取数据集文件列表失败');
}
}
}
export class ModelSelector implements SelectorTypeInfo {
readonly name = '模型';
readonly modalIcon = modelImg;
readonly buttonIcon = 'icon-xuanzemoxing';
readonly tabItems = [
{
key: CommonTabKeys.Private,
label: '我的模型',
},
{
key: CommonTabKeys.Public,
label: '公开模型',
},
];
readonly buttontTitle = '选择模型';
async getList(isPublic: boolean) {
const res = await getModelList({ is_public: isPublic, page: 0, size: 2000 });
if (res && res.data) {
const list = res.data.content || [];
return convertDatasetToTreeData(list);
} else {
return Promise.reject('获取模型列表失败');
}
}
async getVersions(key: string, parentNode: ResourceData) {
const res = await getModelVersionList(pick(parentNode, ['owner', 'identifier']));
if (res && res.data) {
const list = res.data;
return convertDatasetVersionToTreeData(key, parentNode, list);
} else {
return Promise.reject('获取模型版本列表失败');
}
}
async getFiles(_parentKey: string, parentNode: ResourceData & ResourceVersionData) {
const params = pick(parentNode, ['owner', 'identifier', 'id', 'name', 'version']);
const res = await getModelInfo(params);
if (res && res.data) {
const path = res.data.relative_paths || '';
const list = res.data.model_version_vos || [];
return {
path,
content: list,
};
} else {
return Promise.reject('获取模型文件列表失败');
}
}
}
export class MirrorSelector implements SelectorTypeInfo {
readonly name = '镜像';
readonly modalIcon = mirrorImg;
readonly buttonIcon = 'icon-xuanzejingxiang';
readonly tabItems = [
{
key: CommonTabKeys.Private,
label: '我的镜像',
},
{
key: CommonTabKeys.Public,
label: '公开镜像',
},
];
readonly buttontTitle = '选择镜像';
async getList(isPublic: boolean) {
const res = await getMirrorListReq({
image_type: isPublic ? AvailableRange.Public : AvailableRange.Private,
page: 0,
size: 2000,
});
if (res && res.data) {
const list = res.data.content || [];
return convertMirrorToTreeData(list);
} else {
return Promise.reject('获取镜像列表失败');
}
}
async getVersions(parentKey: string) {
const res = await getMirrorVersionListReq({
image_id: parentKey,
page: 0,
size: 2000,
});
if (res && res.data) {
const list = res.data.content || [];
return convertMirrorVersionToTreeData(parentKey, list);
} else {
return Promise.reject('获取镜像版本列表失败');
}
}
async getFiles(_parentKey: string, parentNode: MirrorVersionData) {
const { url } = parentNode;
return {
path: url,
content: [
{
id: `${id}-${version}`,
url: url,
file_name: `${url}`,
},
],
},
});
};
};
}
}
export const selectorTypeConfig: Record<ResourceSelectorType, SelectorTypeInfo> = {
[ResourceSelectorType.Model]: {
getList: getModelList,
getVersions: getModelVersionsById,
getFiles: getModelVersionIdList,
handleVersionResponse: (res) => res.data || [],
name: '模型',
modalIcon: modelImg,
buttonIcon: 'icon-xuanzemoxing',
litReqParamKey: 'available_range',
fileReqParamKey: 'models_id',
tabItems: [
{
key: CommonTabKeys.Private,
label: '我的模型',
},
{
key: CommonTabKeys.Public,
label: '公开模型',
},
],
buttontTitle: '选择模型',
},
[ResourceSelectorType.Dataset]: {
getList: getDatasetList,
getVersions: getDatasetVersionsById,
getFiles: getDatasetVersionIdList,
handleVersionResponse: (res) => res.data || [],
name: '数据集',
modalIcon: datasetImg,
buttonIcon: 'icon-xuanzeshujuji',
litReqParamKey: 'available_range',
fileReqParamKey: 'dataset_id',
tabItems: [
{
key: CommonTabKeys.Private,
label: '我的数据集',
},
{
key: CommonTabKeys.Public,
label: '公开数据集',
},
],
buttontTitle: '选择数据集',
},
[ResourceSelectorType.Mirror]: {
getList: getMirrorListReq,
getVersions: (id: number) => getMirrorVersionListReq({ image_id: id, page: 0, size: 200 }),
getFiles: getMirrorFilesReq,
handleVersionResponse: (res) =>
res.data?.content?.filter((v: MirrorVersion) => v.status === MirrorVersionStatus.Available) ||
[],
name: '镜像',
modalIcon: mirrorImg,
buttonIcon: 'icon-xuanzejingxiang',
litReqParamKey: 'image_type',
fileReqParamKey: 'dataset_id',
tabItems: [
{
key: CommonTabKeys.Private,
label: '我的镜像',
},
{
key: CommonTabKeys.Public,
label: '公开镜像',
},
],
buttontTitle: '选择镜像',
},
[ResourceSelectorType.Model]: new ModelSelector(),
[ResourceSelectorType.Dataset]: new DatasetSelector(),
[ResourceSelectorType.Mirror]: new MirrorSelector(),
};

View File

@ -34,6 +34,11 @@
border-bottom: 1px solid @border-color-secondary;
border-radius: 0;
}
&__tree-title {
display: inline-block;
.singleLine();
}
}
&__right {

View File

@ -6,34 +6,27 @@
import KFModal from '@/components/KFModal';
import { CommonTabKeys } from '@/enums';
import { ResourceFileData } from '@/pages/Dataset/config';
import { to } from '@/utils/promise';
import { Icon } from '@umijs/max';
import type { GetRef, ModalProps, TreeDataNode, TreeProps } from 'antd';
import { Input, Tabs, Tree } from 'antd';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { MirrorVersion, ResourceSelectorType, selectorTypeConfig } from './config';
import { ResourceSelectorType, selectorTypeConfig } from './config';
import styles from './index.less';
export { ResourceSelectorType, selectorTypeConfig };
// 选择数据集\模型\镜像的返回类型
export type ResourceSelectorResponse = {
id: number; // 数据集\模型\镜像 id
id: string; // 数据集\模型\镜像 id
name: string; // 数据集\模型\镜像 name
version: string; // 数据集\模型\镜像版本
path: string; // 数据集\模型\镜像版本路径
identifier: string; // 数据集\模型 identifier
owner: string; // 数据集\模型 owner
activeTab: CommonTabKeys; // 是我的还是公开的
};
type ResourceGroup = {
id: number; // 数据集\模型\镜像 id
name: string; // 数据集\模型\镜像 name
};
type ResourceFile = {
id: number; // 文件 id
file_name: string; // 文件 name
};
export interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> {
type: ResourceSelectorType; // 数据集\模型\镜像
defaultExpandedKeys?: React.Key[];
@ -44,39 +37,8 @@ export interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> {
type TreeRef = GetRef<typeof Tree<TreeDataNode>>;
// list 数据转成 treeData
const convertToTreeData = (list: ResourceGroup[]): TreeDataNode[] => {
return list.map((v) => ({
title: v.name,
key: v.id,
isLeaf: false,
checkable: false,
}));
};
// 版本数据转成 treeData
const convertVersionToTreeData = (parentId: number) => {
return (item: string | MirrorVersion): TreeDataNode => {
if (typeof item === 'string') {
return {
title: item,
key: `${parentId}-${item}`,
isLeaf: true,
checkable: true,
};
} else {
return {
title: item.tag_name,
key: `${parentId}-${item.id}-${item.url}`,
isLeaf: true,
checkable: true,
};
}
};
};
// 更新树形结构的 children
const updateChildren = (parentId: number, children: TreeDataNode[]) => {
const updateChildren = (parentId: string, children: TreeDataNode[]) => {
return (node: TreeDataNode) => {
if (node.key === parentId) {
return {
@ -91,7 +53,7 @@ const updateChildren = (parentId: number, children: TreeDataNode[]) => {
// 得到数据集\模型\镜像 id 和下属版本号
const getIdAndVersion = (versionKey: string) => {
const index = versionKey.indexOf('-');
const id = Number(versionKey.slice(0, index));
const id = versionKey.slice(0, index);
const version = versionKey.slice(index + 1);
return {
id,
@ -112,11 +74,11 @@ function ResourceSelectorModal({
const [checkedKeys, setCheckedKeys] = useState<React.Key[]>([]);
const [loadedKeys, setLoadedKeys] = useState<React.Key[]>([]);
const [originTreeData, setOriginTreeData] = useState<TreeDataNode[]>([]);
const [files, setFiles] = useState<ResourceFile[]>([]);
const [files, setFiles] = useState<ResourceFileData[]>([]);
const [versionPath, setVersionPath] = useState('');
const [searchText, setSearchText] = useState('');
const [fisrtLoadList, setFisrtLoadList] = useState(false);
const [fisrtLoadVersions, setFisrtLoadVersions] = useState(false);
const [firstLoadList, setFirstLoadList] = useState(false);
const [firstLoadVersions, setFirstLoadVersions] = useState(false);
const treeRef = useRef<TreeRef>(null);
const config = selectorTypeConfig[type];
@ -140,18 +102,10 @@ function ResourceSelectorModal({
// 获取数据集\模型\镜像列表
const getTreeData = async () => {
const available_range = activeTab === CommonTabKeys.Private ? 0 : 1;
const params = {
page: 0,
size: 1000,
[config.litReqParamKey]: available_range,
};
const getListReq = config.getList;
const [res] = await to(getListReq(params));
const isPublic = activeTab === CommonTabKeys.Private ? false : true;
const [res] = await to(config.getList(isPublic));
if (res) {
const list = res.data?.content || [];
const treeData = convertToTreeData(list);
setOriginTreeData(treeData);
setOriginTreeData(res);
// 恢复上一次的 Expand 操作
restoreLastExpand();
@ -161,21 +115,22 @@ function ResourceSelectorModal({
};
// 获取数据集\模型\镜像版本列表
const getVersions = async (parentId: number) => {
const getVersionsReq = config.getVersions;
const [res, error] = await to(getVersionsReq(parentId));
const getVersions = async (parentId: string, parentNode: any) => {
const [res, error] = await to(config.getVersions(parentId, parentNode));
if (res) {
const list = config.handleVersionResponse(res);
const children = list.map(convertVersionToTreeData(parentId));
// 更新 treeData children
setOriginTreeData((prev) => prev.map(updateChildren(parentId, children)));
setOriginTreeData((prev) => prev.map(updateChildren(parentId, res)));
// 缓存 loadedKeys
const index = loadedKeys.find((v) => v === parentId);
if (!index) {
setLoadedKeys((prev) => prev.concat(parentId));
}
// 恢复上一次的 Check 操作
restoreLastCheck(parentId);
setTimeout(() => {
restoreLastCheck(parentId, res);
}, 300);
} else {
setExpandedKeys([]);
return Promise.reject(error);
@ -183,14 +138,11 @@ function ResourceSelectorModal({
};
// 获取版本下的文件
const getFiles = async (id: number, version: string) => {
const getFilesReq = config.getFiles;
const paramsKey = config.fileReqParamKey;
const params = { version: version, [paramsKey]: id };
const [res] = await to(getFilesReq(params));
const getFiles = async (parentId: string, parentNode: any) => {
const [res] = await to(config.getFiles(parentId, parentNode));
if (res) {
setVersionPath(res.data?.path || '');
setFiles(res.data?.content || []);
setVersionPath(res.path);
setFiles(res.content);
} else {
setVersionPath('');
setFiles([]);
@ -198,11 +150,11 @@ function ResourceSelectorModal({
};
// 动态加载 tree children
const onLoadData = ({ key, children }: TreeDataNode) => {
const onLoadData = ({ key, children, ...rest }: TreeDataNode) => {
if (children) {
return Promise.resolve();
} else {
return getVersions(key as number);
return getVersions(key as string, rest);
}
};
@ -213,14 +165,15 @@ function ResourceSelectorModal({
};
// 选中
const onCheck: TreeProps['onCheck'] = (checkedKeysValue) => {
const onCheck: TreeProps['onCheck'] = (checkedKeysValue, { checkedNodes }) => {
const lastKeys = (checkedKeysValue as React.Key[]).slice(-1);
setCheckedKeys(lastKeys);
if (lastKeys.length) {
if (lastKeys.length && checkedNodes.length) {
const last = lastKeys[0] as string;
const { id, version } = getIdAndVersion(last);
getFiles(id, version);
const lastNode = checkedNodes[checkedNodes.length - 1];
getFiles(last, lastNode);
} else {
setVersionPath('');
setFiles([]);
}
};
@ -229,10 +182,10 @@ function ResourceSelectorModal({
// 判断是否有 defaultExpandedKeys如果有设置 expandedKeys
// fisrtLoadList 标志位
const restoreLastExpand = () => {
if (!fisrtLoadList && defaultExpandedKeys.length > 0) {
if (!firstLoadList && defaultExpandedKeys.length > 0) {
setTimeout(() => {
setExpandedKeys(defaultExpandedKeys);
setFisrtLoadList(true);
setFirstLoadList(true);
setTimeout(() => {
treeRef.current?.scrollTo({ key: defaultExpandedKeys[0], align: 'bottom' });
}, 100);
@ -243,16 +196,17 @@ function ResourceSelectorModal({
// 恢复上一次的 Check 操作
// 判断是否有 defaultCheckedKeys如果有设置 checkedKeys并且调用获取文件列表接口
// fisrtLoadVersions 标志位
const restoreLastCheck = (parentId: number) => {
if (!fisrtLoadVersions && defaultCheckedKeys.length > 0) {
const restoreLastCheck = (parentId: string, versions: TreeDataNode[]) => {
if (!firstLoadVersions && defaultCheckedKeys.length > 0) {
const last = defaultCheckedKeys[0] as string;
const { id, version } = getIdAndVersion(last);
const { id } = getIdAndVersion(last);
// 判断正在打开的 id 和 defaultCheckedKeys 的 id 是否一致
if (id === parentId) {
setTimeout(() => {
setCheckedKeys(defaultCheckedKeys);
getFiles(id, version);
setFisrtLoadVersions(true);
const parentNode = versions.find((v) => v.key === last);
getFiles(last, parentNode);
setFirstLoadVersions(true);
setTimeout(() => {
treeRef?.current?.scrollTo({
key: defaultCheckedKeys[0],
@ -269,12 +223,17 @@ function ResourceSelectorModal({
if (checkedKeys.length > 0) {
const last = checkedKeys[0] as string;
const { id, version } = getIdAndVersion(last);
const name = (treeData.find((v) => Number(v.key) === id)?.title ?? '') as string;
const treeNode = treeData.find((v) => v.key === id) as any;
const name = (treeNode?.title ?? '') as string;
const identifier = (treeNode?.identifier ?? '') as string;
const owner = (treeNode?.owner ?? '') as string;
const res = {
id,
name,
path: versionPath,
version,
identifier,
owner,
activeTab: activeTab as CommonTabKeys,
};
onOk?.(res);
@ -324,13 +283,23 @@ function ResourceSelectorModal({
expandedKeys={expandedKeys}
onExpand={onExpand}
checkable
titleRender={(nodeData) => {
return (
<span
className={styles['model-selector__left__tree-title']}
style={{ width: nodeData.isLeaf ? '370px' : '420px' }}
>
{nodeData.title as string}
</span>
);
}}
/>
</div>
<div className={styles['model-selector__right']}>
<div className={styles['model-selector__right__title']}>{fileTitle}</div>
<div className={styles['model-selector__right__files']}>
{files.map((v) => (
<div key={v.id} className={styles['model-selector__right__files__file']}>
<div key={v.url} className={styles['model-selector__right__files__file']}>
{v.file_name}
</div>
))}

View File

@ -129,7 +129,7 @@ const AuthUserTableList: React.FC = () => {
{
title: <FormattedMessage id="pages.searchTable.titleOption" defaultMessage="操作" />,
dataIndex: 'option',
width: '60px',
width: '160px',
valueType: 'option',
render: (_, record) => [
<Button

View File

@ -111,10 +111,7 @@ const DataScopeForm: React.FC<DataScopeFormProps> = (props) => {
grid={true}
layout="horizontal"
onFinish={handleFinish}
initialValues={{
login_password: '',
confirm_password: '',
}}
submitter={false}
{...formLayout}
size="large"
labelAlign="right"
@ -203,8 +200,9 @@ const DataScopeForm: React.FC<DataScopeFormProps> = (props) => {
})}
required={dataScopeType === '1'}
hidden={dataScopeType !== '1'}
style={{ width: '100%', padding: '0 4px' }}
>
<Row gutter={[16, 16]}>
<Row gutter={[16, 16]} style={{ marginTop: '10px' }}>
<Col md={24}>
<Checkbox.Group
options={[
@ -235,7 +233,7 @@ const DataScopeForm: React.FC<DataScopeFormProps> = (props) => {
}
}}
onExpand={(expandedKeys: Key[]) => {
setDeptTreeExpandKey(deptTreeExpandKey.concat(expandedKeys));
setDeptTreeExpandKey(expandedKeys);
}}
/>
</Col>

View File

@ -0,0 +1,12 @@
.user-selector-modal {
:global {
// 输入框高度为46px
.ant-input-affix-wrapper {
padding-top: 4px !important;
padding-bottom: 4px !important;
.ant-input {
height: 22px !important;
}
}
}
}

View File

@ -10,6 +10,7 @@ import {
} from '@ant-design/pro-components';
import { FormattedMessage, useIntl } from '@umijs/max';
import React, { useEffect, useRef, useState } from 'react';
import styles from './UserSelectorModal.less';
/* *
*
* @author whiteshader@163.com
@ -90,7 +91,7 @@ const UserSelectorModal: React.FC<DataScopeFormProps> = (props) => {
return (
<KFModal
width={800}
width={920}
title={intl.formatMessage({
id: 'system.role.auth.user',
defaultMessage: '选择用户',
@ -99,6 +100,7 @@ const UserSelectorModal: React.FC<DataScopeFormProps> = (props) => {
destroyOnClose
onOk={handleOk}
onCancel={handleCancel}
className={styles['user-selector-modal']}
>
<ProTable<API.System.User>
headerTitle={intl.formatMessage({

View File

@ -34,7 +34,7 @@ const AuthRoleForm: React.FC<AuthRoleFormProps> = (props) => {
return (
<KFModal
width={640}
width={680}
title={intl.formatMessage({
id: 'system.user.auth.role',
defaultMessage: '分配角色',
@ -50,10 +50,10 @@ const AuthRoleForm: React.FC<AuthRoleFormProps> = (props) => {
grid={true}
layout="horizontal"
onFinish={handleFinish}
initialValues={{
login_password: '',
confirm_password: '',
}}
submitter={false}
size="large"
labelAlign="right"
autoComplete="off"
>
<ProFormSelect
name="roleIds"

View File

@ -39,7 +39,7 @@ const UpdateForm: React.FC<UpdateFormProps> = (props) => {
return (
<KFModal
width={640}
width={680}
title={intl.formatMessage({
id: 'system.user.reset.password',
defaultMessage: '密码重置',
@ -58,8 +58,12 @@ const UpdateForm: React.FC<UpdateFormProps> = (props) => {
password: '',
confirm_password: '',
}}
submitter={false}
size="large"
labelAlign="right"
autoComplete="off"
>
<p>{props.values.userName}</p>
<p> {props.values.userName} </p>
<ProFormText.Password
name="password"
label="登录密码"

View File

@ -62,6 +62,8 @@ const UserForm: React.FC<UserFormProps> = (props) => {
loginIp: props.values.loginIp,
loginDate: props.values.loginDate,
remark: props.values.remark,
gitLinkUsername: props.values.gitLinkUsername,
gitLinkPassword: props.values.gitLinkPassword,
});
}, [form, props]);
@ -275,6 +277,28 @@ const UserForm: React.FC<UserFormProps> = (props) => {
colProps={{ md: 12, xl: 12 }}
rules={[{ required: true, message: '请选择角色!' }]}
/>
<ProFormText
name="gitLinkUsername"
label="Git 用户名"
placeholder="请输入 Git 用户名"
colProps={{ xs: 24, md: 12, xl: 12 }}
rules={[
{
required: true,
message: '请输入 Git 用户名!',
},
]}
/>
<ProFormText.Password
name="gitLinkPassword"
label="Git 密码"
placeholder="请输入 Git 密码"
colProps={{ xs: 24, md: 12, xl: 12 }}
fieldProps={{
autoComplete: 'new-password',
}}
rules={props.values.userId ? [] : [{ required: true, message: '请输入 Git 密码!' }]}
/>
<ProFormTextArea
name="remark"
label={intl.formatMessage({

View File

@ -116,7 +116,12 @@ const AvatarCropperForm: React.FC<AvatarCropperProps> = (props) => {
</Col>
<Col span={12} order={2}>
<div className={styles.avatarPreview}>
<img src={previewData} style={{ height: '100%', width: '100%' }} />
<img
src={previewData}
style={{ height: '100%', width: '100%' }}
draggable={false}
alt=""
/>
</div>
</Col>
</Row>

View File

@ -141,7 +141,7 @@ const Center: React.FC = () => {
setCropperModalOpen(true);
}}
>
<img alt="" src={currentUser.avatar} />
<img src={currentUser.avatar} draggable={false} alt="" />
</div>
{renderUserInfo(currentUser)}
<Divider dashed />

View File

@ -11,7 +11,7 @@ import styles from './login.less';
const LoginInputPrefix = ({ icon }: { icon: string }) => {
return (
<div className={styles['login-input-prefix']}>
<img className={styles['login-input-prefix__icon']} src={icon} alt="" />
<img className={styles['login-input-prefix__icon']} src={icon} alt="" draggable={false} />
<div className={styles['login-input-prefix__line']}></div>
</div>
);
@ -98,6 +98,7 @@ const Login = () => {
<img
src={require('@/assets/img/logo.png')}
style={{ width: '32px', marginRight: '12px' }}
draggable={false}
alt=""
/>
@ -107,6 +108,7 @@ const Login = () => {
<img
src={require('@/assets/img/login-ai-logo.png')}
className={styles['user-login__left__title__img']}
draggable={false}
alt=""
/>
</div>
@ -116,6 +118,7 @@ const Login = () => {
<img
className={styles['user-login__left__bottom-img']}
src={require('@/assets/img/login-left-image.png')}
draggable={false}
alt=""
/>
</div>

View File

@ -31,6 +31,8 @@ function ExperimentTable({ tableData = [], style }: ExperimentTableProps) {
src={experimentStatusInfo[item.status as ExperimentStatus]?.icon}
width={17}
height={17}
draggable={false}
alt=""
/>
</div>
<div className={styles['experiment-table__duration']}>

View File

@ -44,6 +44,7 @@ function WorkArrow({
<img
className={styles['work-arrow__img']}
src={require('@/assets/img/blue-triangle.png')}
draggable={false}
alt=""
width={10}
height={9}

View File

@ -10,7 +10,7 @@ type TotalStatisticsProps = {
function TotalStatistics({ icon = '', title = '', count = 0, style }: TotalStatisticsProps) {
return (
<div className={styles['total-statistics']} style={style}>
<img className={styles['total-statistics__icon']} src={icon} />
<img className={styles['total-statistics__icon']} src={icon} draggable={false} alt="" />
<div>
<div className={styles['total-statistics__title']}>
<span>{title}</span>

View File

@ -20,7 +20,15 @@ function UserSpace({ users = [] }: UserSpaceProps) {
className={styles['user-space__avatar']}
src={currentUser?.avatar}
alt=""
icon={<img src={require('@/assets/img/avatar-default.png')} width={56} height={56} />}
icon={
<img
src={require('@/assets/img/avatar-default.png')}
width={56}
height={56}
draggable={false}
alt=""
/>
}
></Avatar>
<div className={styles['user-space__name']}>{currentUser?.nickName}</div>
<div className={styles['user-space__role']}>{currentUser?.roleNames?.[0]?.roleName}</div>
@ -34,7 +42,7 @@ function UserSpace({ users = [] }: UserSpaceProps) {
<div className={styles['user-space__participant__count']}>8</div>
</Space>
<Flex align="center" gap={12} wrap="wrap">
{users?.map((item, index) => {
{users?.map((_item, index) => {
return (
<Avatar
key={index}
@ -42,7 +50,13 @@ function UserSpace({ users = [] }: UserSpaceProps) {
src={require(`@/assets/img/user-avatar/${index + 1}.png`)}
alt=""
icon={
<img src={require('@/assets/img/avatar-default.png')} width={36} height={36} />
<img
src={require('@/assets/img/avatar-default.png')}
width={36}
height={36}
draggable={false}
alt=""
/>
}
></Avatar>
);

View File

@ -15,7 +15,13 @@ function WorkspaceIntro() {
type="primary"
style={{ marginRight: '20px' }}
icon={
<img src={require('@/assets/img/functional-material.png')} width={19} height={19} />
<img
src={require('@/assets/img/functional-material.png')}
width={19}
height={19}
draggable={false}
alt=""
/>
}
>
@ -23,7 +29,13 @@ function WorkspaceIntro() {
<Button
type="default"
icon={
<img src={require('@/assets/img/molecular-material.png')} width={19} height={19} />
<img
src={require('@/assets/img/molecular-material.png')}
width={19}
height={19}
draggable={false}
alt=""
/>
}
>
@ -34,6 +46,8 @@ function WorkspaceIntro() {
<img
className={styles['workspace-intro__icon']}
src={require('@/assets/img/workspace-intro-icon.png')}
draggable={false}
alt=""
/>
</div>
</div>

View File

@ -53,6 +53,7 @@
width: 64px;
height: 64px;
background-color: white;
border-radius: 10px;
cursor: pointer;
}
}

View File

@ -76,7 +76,8 @@ function Workspace() {
className={styles['workspace__robot-img']}
src={require('@/assets/img/robot.png')}
onClick={handleClick}
onDragStart={(e) => e.preventDefault()}
draggable={false}
alt=""
></img>
</Draggable>

View File

@ -1,10 +1,20 @@
import missingPage from '@/assets/img/missing-back.png';
import KFEmpty, { EmptyType } from '@/components/KFEmpty';
import { useNavigate } from '@umijs/max';
const MissingPage = () => (
<div style={{ width: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<img src={missingPage} style={{ width: '575px', margin: '278px 0 44px 0' }} alt="" />
<span style={{ color: '#575757', fontSize: '16px' }}>页面开发中敬请期待......</span>
</div>
);
const MissingPage = () => {
const navigate = useNavigate();
return (
<KFEmpty
style={{ height: '100%' }}
type={EmptyType.Developing}
title="敬请期待~"
content={'很抱歉,您访问的正在开发中,\n请耐心等待。'}
hasFooter={true}
buttonTitle="返回首页"
onRefresh={() => navigate('/')}
></KFEmpty>
);
};
export default MissingPage;

View File

@ -7,6 +7,7 @@ import type { AxiosRequestConfig, AxiosResponse, RequestConfig, RequestOptions }
import { message } from 'antd';
import { clearSessionToken, getAccessToken } from './access';
import { setRemoteMenu } from './services/session';
import Loading from './utils/loading';
import { gotoLoginPage } from './utils/ui';
// [antd: Notification] You are calling notice in render which will break in React 18 concurrent mode. Please trigger in effect instead.
@ -25,6 +26,7 @@ const popupError = (error: string, skipErrorHandler: boolean | undefined = false
* @doc https://umijs.org/docs/max/request#配置
*/
export const requestConfig: RequestConfig = {
timeout: 120 * 1000,
requestInterceptors: [
(url: string, options: AxiosRequestConfig) => {
const headers = options.headers ?? {};
@ -36,12 +38,14 @@ export const requestConfig: RequestConfig = {
headers['Authorization'] = `Bearer ${accessToken}`;
}
}
Loading.show();
return { url, options };
},
],
responseInterceptors: [
[
(response: AxiosResponse) => {
Loading.hide();
const { status, data, config } = response || {};
const skipErrorHandler = (config as RequestOptions)?.skipErrorHandler;
if (status >= 200 && status < 300) {
@ -63,6 +67,7 @@ export const requestConfig: RequestConfig = {
}
},
(error: Error) => {
Loading.hide();
popupError(error.message ?? '请求失败');
return Promise.reject(error);
},

View File

@ -1,140 +1,153 @@
import { request } from '@umijs/max';
// 分页查询数据集
export function getDatasetList(params) {
return request(`/api/mmp/dataset`, {
method: 'GET',
params,
});
}
// 分页查询模型
export function getModelList(params) {
return request(`/api/mmp/models`, {
method: 'GET',
params,
});
}
// 新增数据集
export function addDatesetAndVesion(data) {
return request(`/api/mmp/dataset/addDatasetAndVersion`, {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=UTF-8',
},
data,
});
}
// 新增模型
export function addModel(data) {
return request(`/api/mmp/models/addModelAndVersion`, {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=UTF-8',
},
data,
});
}
// 查询数据集简介
export function getDatasetById(id) {
return request(`/api/mmp/dataset/${id}`, {
method: 'GET',
});
}
// 查询左侧列表
// 查询数据集、模型分类
export function getAssetIcon(params) {
return request(`/api/mmp/assetIcon`, {
method: 'GET',
params,
});
}
// 查询模型简介
export function getModelById(id) {
return request(`/api/mmp/models/${id}`, {
method: 'GET',
});
}
// 查询数据版本集
export function getDatasetVersionsById(id) {
return request(`/api/mmp/dataset/versions/${id}`, {
method: 'GET',
});
}
// 查询模型版本集
export function getModelVersionsById(id) {
return request(`/api/mmp/models/versions/${id}`, {
method: 'GET',
});
}
// 分页查询数据集
export function getDatasetVersionIdList(params) {
return request(`/api/mmp/datasetVersion/versions`, {
// ----------------------------数据集---------------------------------
// 分页查询数据集列表
export function getDatasetList(params) {
return request(`/api/mmp/newdataset/queryDatasets`, {
method: 'GET',
params,
});
}
// 根据版本查询模型
export function getModelVersionIdList(params) {
return request(`/api/mmp/modelsVersion/versions`, {
// 查询数据集详情
export function getDatasetInfo(params) {
return request(`/api/mmp/newdataset/getDatasetDetail`, {
method: 'GET',
params,
});
}
// 新增数据集
export function addDataset(data) {
return request(`/api/mmp/newdataset/addDatasetAndVersion`, {
method: 'POST',
data,
});
}
// 删除数据集
export function deleteDatasetVersion(params) {
return request(`/api/mmp/datasetVersion/deleteVersion`, {
export function deleteDataset(params) {
return request(`/api/mmp/newdataset/deleteDataset`, {
method: 'DELETE',
params,
});
}
// 删除模型
export function deleteModelVersion(params) {
return request(`/api/mmp/modelsVersion/deleteVersion`, {
method: 'DELETE',
// 查询数据集版本列表
export function getDatasetVersionList(params) {
return request(`/api/mmp/newdataset/getVersionList`, {
method: 'GET',
params,
});
}
// 新增数据集版本
export function addDatasetVersionDetail(data) {
return request(`/api/mmp/datasetVersion/addDatasetVersions`, {
export function addDatasetVersion(data) {
return request(`/api/mmp/newdataset/addVersion`, {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=UTF-8',
},
data,
});
}
// 新增模型版本
export function addModelsVersionDetail(data) {
return request(`/api/mmp/modelsVersion/addModelVersions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=UTF-8',
},
data,
});
}
// 下载数据集
export function exportDataset(id) {
return request(`/api/mmp/dataset/download/${id}`, {
// 下载数据集所有文件
export function downloadAllFiles(params) {
return request(`/api/mmp/newdataset/downloadAllFiles`, {
method: 'GET',
params
});
}
// 删除模型集
export function deleteModel(id) {
return request(`/api/mmp/models/${id}`, {
// 下载数据集单个文件
export function downloadSingleFile(params) {
return request(`/api/mmp/newdataset/downloadSinggerFile`, {
method: 'GET',
params,
});
}
// 删除数据集版本
export function deleteDatasetVersion(params) {
return request(`/api/mmp/newdataset/deleteDatasetVersion`, {
method: 'DELETE',
params,
});
}
// 删除数据集
export function deleteDataset(id) {
return request(`/api/mmp/dataset/${id}`, {
method: 'DELETE',
// ----------------------------模型---------------------------------
// 分页查询模型列表
export function getModelList(params) {
return request(`/api/mmp/newmodel/queryModels`, {
method: 'GET',
params,
});
}
// 获取模型依赖
export function getModelAtlasReq(data) {
return request(`/api/mmp/modelDependency/queryModelAtlas`, {
// 新增模型
export function addModel(data) {
return request(`/api/mmp/newmodel/addModel`, {
method: 'POST',
data
data,
});
}
// 删除模型
export function deleteModel(params) {
return request(`/api/mmp/newmodel/delete`, {
method: 'DELETE',
params,
});
}
// 查询模型详情
export function getModelInfo(params) {
return request(`/api/mmp/newmodel/getModelDetail`, {
method: 'GET',
params,
});
}
// 查询模型版本列表
export function getModelVersionList(params) {
return request(`/api/mmp/newmodel/getVersionList`, {
method: 'GET',
params,
});
}
// 新增模型版本
export function addModelVersion(data) {
return request(`/api/mmp/newmodel/addVersion`, {
method: 'POST',
data,
});
}
// 删除模型版本
export function deleteModelVersion(params) {
return request(`/api/mmp/newmodel/deleteVersion`, {
method: 'DELETE',
params,
});
}
// 获取模型依赖
export function getModelAtlasReq(params) {
return request(`/api/mmp/newmodel/getModelDependencyTree`, {
method: 'GET',
params
});
}

Some files were not shown because too many files have changed in this diff Show More