Compare commits

..

282 Commits

Author SHA1 Message Date
chenzhihang d779e31761 218超算部署 2026-06-10 16:07:19 +08:00
chenzhihang b7429538a2 218超算部署 2026-06-10 15:54:20 +08:00
chenzhihang 67764ad21c 218超算部署 2026-06-10 14:38:20 +08:00
cp3hnu a006b6deed Merge pull request '合并' (#270) from dev-zw into dev-check 2025-06-23 10:44:16 +08:00
zhaowei 8f015d4d3f feat: 添加缺失值填充 2025-06-23 10:45:42 +08:00
zhaowei 08a1d46102 feat: 数据集 & 模型可以编辑版本描述 2025-06-20 15:51:12 +08:00
chenzhihang 3949bea38e Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 2025-06-20 15:50:38 +08:00
cp3hnu 36b50365a1 Merge pull request '合并' (#269) from dev-zw into dev-check 2025-06-20 15:50:27 +08:00
chenzhihang f76dc346e2 增加修改资源版本功能 2025-06-20 15:49:18 +08:00
zhaowei af78389c11 chore: 修改自动机器学习特征预处理算法中文描述 2025-06-20 13:59:20 +08:00
zchzch 156c791d57 test分支暂时撤回导出到数据集逻辑 2025-06-20 09:04:49 +08:00
zchzch 4fe42ede4e Merge branch 'dev-zch' into dev-check 2025-06-19 15:38:25 +08:00
zhanchunhu c286cd5870 修改工具类 2025-06-19 11:13:38 +08:00
zhanchunhu 3a8ae0e131 修复导出到数据集的zip包打不开的bug 2025-06-19 10:15:26 +08:00
zhaowei e91aade6af feat: 修改使用指南 2025-06-12 09:17:36 +08:00
cp3hnu ea5abbfbba Merge pull request '合并' (#267) from dev-zw into dev-check 2025-06-12 09:16:46 +08:00
chenzhihang 8ed4e3d75d Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 2025-06-09 09:48:12 +08:00
chenzhihang fdad983d9a 优化 2025-06-09 09:47:46 +08:00
cp3hnu 124d970003 Merge pull request '合并' (#266) from dev-zw into dev-check 2025-06-06 16:22:04 +08:00
chenzhihang b4be088a85 优化导出数据集bug 2025-06-06 16:10:26 +08:00
chenzhihang 35f7f4aa5e 回退 2025-06-06 15:56:50 +08:00
chenzhihang 18edf9d512 优化dvc测试 2025-06-06 15:24:10 +08:00
chenzhihang b2bc1ec7bc Merge branch 'dev-opt' into dev-check
# Conflicts:
#	ruoyi-modules/management-platform/src/main/java/com/ruoyi/platform/service/impl/NewDatasetServiceImpl.java
2025-06-06 15:23:03 +08:00
chenzhihang 6d2a84aa93 优化dvc测试 2025-06-06 14:44:01 +08:00
chenzhihang 284115b499 优化dvc测试 2025-06-06 14:31:52 +08:00
zhaowei b11b4d9f78 fix: 数据集、模型版本不能是origin 2025-06-06 14:31:42 +08:00
chenzhihang fb863bf1de 优化dvc测试 2025-06-06 14:14:29 +08:00
chenzhihang 94555dea40 优化dvc测试 2025-06-06 14:00:30 +08:00
chenzhihang 089b13aafb 优化dvc测试 2025-06-06 11:40:52 +08:00
chenzhihang 163e9bab8d 优化dvc测试 2025-06-06 11:23:00 +08:00
chenzhihang 2f618703e4 优化dvc测试 2025-06-06 11:12:48 +08:00
chenzhihang 24798fa4dd 优化dvc测试 2025-06-06 10:56:01 +08:00
chenzhihang 982034e707 回退 2025-06-06 10:47:52 +08:00
chenzhihang 879f1b7966 Merge remote-tracking branch 'origin/dev-check' into dev-opt 2025-06-06 10:45:28 +08:00
chenzhihang dfefc792c4 优化dvc测试 2025-06-06 10:02:00 +08:00
chenzhihang 7fad3190eb 优化dvc测试 2025-06-06 09:51:34 +08:00
chenzhihang cc567ce45a 优化dvc测试 2025-06-06 09:42:59 +08:00
chenzhihang 7985f9f1a1 优化dvc测试 2025-06-06 09:22:42 +08:00
chenzhihang e8cfaaea05 优化dvc测试 2025-06-06 09:05:10 +08:00
chenzhihang ec6dadd55c 优化dvc测试 2025-06-06 08:44:41 +08:00
chenzhihang 561d414514 优化dvc测试 2025-06-06 08:36:31 +08:00
chenzhihang bf77aff1dd 优化dvc测试 2025-06-06 08:25:46 +08:00
chenzhihang 4670b2bb5e 优化dvc测试 2025-06-05 20:22:27 +08:00
chenzhihang 3ea8756d61 优化dvc测试 2025-06-05 17:23:58 +08:00
chenzhihang fcb6664e09 Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 2025-06-05 16:23:13 +08:00
chenzhihang b75dbf88df 优化mysql连接超时重试 2025-06-05 16:22:45 +08:00
cp3hnu 05860a9325 Merge pull request '合并' (#265) from dev-zw into dev-check 2025-06-05 16:16:03 +08:00
zhaowei e8af394523 fix: 超参数寻优添加可视化对比iframe 2025-06-05 15:32:24 +08:00
cp3hnu 1abf90da28 Merge pull request '合并' (#264) from dev-zw into dev-check 2025-06-05 14:03:56 +08:00
zhaowei eafca94b60 fix: 导出到数据集添加is_public参数 2025-06-05 13:49:06 +08:00
cp3hnu bb26e7291f Merge pull request '合并' (#263) from dev-zw into dev-check 2025-06-05 11:44:44 +08:00
zhaowei cd4071149b fix: 导出到数据集添加owner参数 2025-06-05 11:43:16 +08:00
zhaowei f11582bc64 fix: 数据集和模型回退时分页没有设置 2025-06-05 11:19:14 +08:00
cp3hnu a1f9d10617 Merge pull request '合并' (#262) from dev-zw into dev-check 2025-06-05 11:18:27 +08:00
zhaowei fddb63d293 fix: 流水线模板配置参数修改,历史实验实例配置参数变换 2025-06-05 09:27:32 +08:00
cp3hnu 6b8eb0fb33 Merge pull request '合并' (#261) from dev-zw into dev-check 2025-06-05 09:26:37 +08:00
chenzhihang 5a402acd44 优化实验 2025-06-05 08:28:05 +08:00
chenzhihang 8803f78bde 优化实验 2025-06-04 20:00:58 +08:00
chenzhihang 9baf28b312 Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 2025-06-04 14:58:16 +08:00
chenzhihang 0f316b2b80 新增代码,服务统计 2025-06-04 14:57:46 +08:00
chenzhihang 0b0ccd26d5 文件分片上传,断点续传 2025-06-04 14:14:11 +08:00
zhaowei fd7f0008c8 Merge branch 'dev-check' into dev-zw 2025-06-04 10:47:44 +08:00
cp3hnu 7257c82f0e Merge pull request '合并' (#260) from dev-zw into dev-check 2025-06-04 10:46:53 +08:00
zhaowei 531beedac6 chore: merge 2025-06-04 10:45:53 +08:00
zhaowei 0e8efbb692 fix: 工作空间添加代码配置和服务数量 2025-06-04 10:42:05 +08:00
chenzhihang 742be0bb03 Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 2025-06-04 10:18:21 +08:00
zhaowei 512aa61b05 fix: 调整工作空间快速开始按钮的偏移 2025-06-04 09:48:14 +08:00
chenzhihang b6273098c6 新增代码,服务统计 2025-06-04 09:40:59 +08:00
zhaowei 921bc1d49d fix: 调整工作空间样式 2025-06-04 09:31:04 +08:00
zhaowei 78eb6ee12f fix: 修复控制参数转成object的问题 2025-06-03 15:34:42 +08:00
cp3hnu 57a0ec1040 Merge pull request '合并' (#258) from dev-zw into dev-check 2025-06-03 15:34:02 +08:00
cp3hnu 154c834223 Merge pull request '合并' (#257) from dev-zw into dev-check 2025-06-03 14:20:28 +08:00
zhaowei 560ff3411c fix: 实验隐藏流水线控制参数 2025-06-03 14:20:27 +08:00
zhaowei bd269cffbf fix: 流水线隐藏参数 2025-06-03 11:11:56 +08:00
cp3hnu 6646aff405 Merge pull request '合并' (#256) from dev-zw into dev-check 2025-06-03 11:11:13 +08:00
chenzhihang 944c3cc919 文件分片上传,断点续传 2025-06-03 09:29:29 +08:00
zhaowei 1b497e1743 fix: 自动机器学习添加算法描述 2025-05-30 11:46:12 +08:00
cp3hnu ff69531b22 Merge pull request '合并' (#255) from dev-zw into dev-check 2025-05-30 11:45:40 +08:00
chenzhihang 399d611607 优化用户 2025-05-30 11:03:45 +08:00
chenzhihang 8e024c9813 优化用户 2025-05-30 10:49:00 +08:00
chenzhihang 5c9f59887c 优化实验 2025-05-29 20:10:12 +08:00
chenzhihang b3e5eb08e9 优化实验 2025-05-29 20:03:58 +08:00
chenzhihang 6efe25a3f0 Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 2025-05-29 19:53:29 +08:00
chenzhihang 548016091e 优化实验 2025-05-29 19:53:20 +08:00
cp3hnu 5d61ade863 Merge pull request '合并' (#254) from dev-zw into dev-check 2025-05-29 17:10:17 +08:00
zhaowei 458621fe91 fix: 自主机器学习改为自动机器学习 2025-05-29 17:09:19 +08:00
chenzhihang 55b49047c3 Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 2025-05-29 16:59:57 +08:00
chenzhihang 597e16f81f 优化实验 2025-05-29 16:59:44 +08:00
zhaowei aef75f4a37 fix: 修改角色 2025-05-29 14:29:16 +08:00
cp3hnu 153b7de3ec Merge pull request '合并' (#253) from dev-zw into dev-check 2025-05-29 14:28:54 +08:00
chenzhihang 4f415719f8 优化主动学习 2025-05-29 10:28:37 +08:00
chenzhihang aa410acdfe 优化 2025-05-29 09:48:20 +08:00
chenzhihang 4252f1710d 优化项目分页查询 2025-05-28 17:59:46 +08:00
chenzhihang 23af257179 优化项目分页查询 2025-05-28 17:29:36 +08:00
chenzhihang f0b70feca7 优化项目分页查询 2025-05-28 17:14:54 +08:00
chenzhihang b014c9ce92 优化项目分页查询 2025-05-28 16:40:18 +08:00
chenzhihang 976fb1dce5 优化项目分页查询 2025-05-28 16:32:31 +08:00
chenzhihang 3a5845b623 优化项目分页查询 2025-05-28 16:11:37 +08:00
chenzhihang b811bb51cd 优化项目分页查询 2025-05-28 15:48:20 +08:00
chenzhihang 90c958b974 优化项目分页查询 2025-05-28 15:44:13 +08:00
chenzhihang 19a4d6aed3 优化项目分页查询 2025-05-28 15:39:49 +08:00
chenzhihang 6e27e5da0d 优化项目分页查询 2025-05-28 15:21:18 +08:00
chenzhihang 3d0ea6603f 优化项目分页查询 2025-05-28 15:08:11 +08:00
chenzhihang dc7e8dc801 优化项目分页查询 2025-05-28 15:02:23 +08:00
chenzhihang 8e1f5fc587 优化 2025-05-28 14:37:54 +08:00
chenzhihang 746314e5d2 优化 2025-05-28 14:29:13 +08:00
chenzhihang d169d3d9db 优化 2025-05-28 14:15:57 +08:00
chenzhihang 7a939f3f29 优化 2025-05-28 09:38:05 +08:00
chenzhihang dc6cf41651 Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 2025-05-27 16:46:19 +08:00
chenzhihang 17c1c86043 测试 2025-05-27 16:45:36 +08:00
cp3hnu a46a948760 Merge pull request '合并' (#252) from dev-zw into dev-check 2025-05-27 14:19:45 +08:00
zhaowei 40ca029363 fix:自动机器学习添加mlp算法 2025-05-27 14:06:16 +08:00
zhaowei 767a208732 fix:工作空间实验运行时长动态变化 2025-05-27 09:45:05 +08:00
cp3hnu b314476ac3 Merge pull request '合并' (#251) from dev-zw into dev-check 2025-05-27 09:44:51 +08:00
chenzhihang 39c0c2c01a 优化 2025-05-26 15:58:11 +08:00
chenzhihang 873dd0ed5e Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 2025-05-26 13:40:13 +08:00
chenzhihang 2a101d7f47 优化 2025-05-26 13:39:55 +08:00
cp3hnu bcff86269b Merge pull request '合并' (#250) from dev-zw into dev-check 2025-05-26 11:49:09 +08:00
zhaowei b2b74686ca fix: 自动机器学习创建时间改为更新时间 2025-05-26 11:00:52 +08:00
chenzhihang 316cca31a4 优化排序 2025-05-26 10:42:56 +08:00
zhaowei d1c41934b0 fix: 预测有两个loading 2025-05-26 10:23:00 +08:00
zhaowei 274b8612e9 fix: 流水线模型部署服务版本验证 2025-05-26 10:22:22 +08:00
chenzhihang df7460a01b Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 2025-05-26 10:19:56 +08:00
chenzhihang 48d46a0723 优化用户 2025-05-26 10:19:37 +08:00
zhaowei 8f72953683 fix: 服务日志样式错误 2025-05-26 08:55:58 +08:00
cp3hnu 62bd8049ba Merge pull request '合并' (#249) from dev-zw into dev-check 2025-05-26 08:55:47 +08:00
zhaowei 1ec43a60cf fix: 添加预测加载状态 2025-05-23 18:53:28 +08:00
cp3hnu c42cf77939 Merge pull request '合并' (#248) from dev-zw into dev-check 2025-05-23 18:53:23 +08:00
chenzhihang 85c8a3e6dc Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 2025-05-23 17:13:32 +08:00
chenzhihang 96bf3351db 优化积分扣除结束 2025-05-23 17:13:19 +08:00
cp3hnu b2ac5877d0 Merge pull request '合并' (#247) from dev-zw into dev-check 2025-05-23 15:47:16 +08:00
zhaowei 0bace90a23 fix: 全局参数删除脱敏的配置 2025-05-23 15:46:27 +08:00
cp3hnu 4c73d4339d Merge branch 'dev-zw' of code.gitlink.org.cn:ci4s/ci4sManagement-cloud into dev-zw 2025-05-23 15:32:00 +08:00
cp3hnu ef9a78b167 fix: 最近更新时间 2025-05-23 15:31:48 +08:00
chenzhihang 68ad21fadf 优化 2025-05-23 15:08:04 +08:00
chenzhihang 696f939295 优化 2025-05-23 14:59:32 +08:00
chenzhihang cc98a699d4 优化 2025-05-23 14:43:53 +08:00
chenzhihang c11c728c71 Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 2025-05-23 11:44:21 +08:00
chenzhihang 31334c3a11 优化 2025-05-23 11:39:38 +08:00
cp3hnu 3066853aeb Merge pull request '合并' (#246) from dev-zw into dev-check 2025-05-23 10:12:49 +08:00
zhaowei f3f9846dff fix: 模型指标对比图错误 2025-05-23 09:42:58 +08:00
cp3hnu 7715ee272d Merge pull request '合并' (#245) from dev-zw into dev-check 2025-05-23 09:02:14 +08:00
zhaowei 65c588ac8a fix: 集成模型数量>=1 2025-05-23 09:02:04 +08:00
zhaowei 34e2b8bb05 fix: mlp 显示成tablenet 2025-05-23 08:43:57 +08:00
chenzhihang ff07d4c4a8 优化用户 2025-05-22 17:22:22 +08:00
chenzhihang 6ad1910cb0 Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 2025-05-22 17:00:47 +08:00
chenzhihang 879c2e5802 优化更新ray 2025-05-22 16:58:02 +08:00
chenzhihang 50dce4003f 优化用户 2025-05-22 16:54:38 +08:00
cp3hnu e4385544b1 Merge pull request '合并dev-zw' (#244) from dev-zw into dev-check 2025-05-22 16:52:25 +08:00
cp3hnu 4a6f4d2120 fix: 退出登录获取label-studio地址 2025-05-22 16:51:51 +08:00
cp3hnu 8ef236f5b4 feat: 修改服务调用指南 2025-05-22 16:51:16 +08:00
chenzhihang a5914e67c8 优化 2025-05-22 16:35:55 +08:00
chenzhihang cccb2ae8c2 测试登录 2025-05-22 16:28:52 +08:00
chenzhihang e5978fde0c 测试登录 2025-05-22 16:17:54 +08:00
chenzhihang e76a0d7544 测试登录 2025-05-22 16:02:42 +08:00
chenzhihang d19f0cfa82 优化 2025-05-22 15:39:05 +08:00
chenzhihang d3508e6eba Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 2025-05-22 14:31:40 +08:00
chenzhihang b42a63f209 优化 2025-05-22 14:31:21 +08:00
cp3hnu 603b943699 Merge pull request '合并dev' (#243) from dev into dev-check 2025-05-22 11:53:09 +08:00
cp3hnu c8fc258089 Merge pull request '合并dev-zw' (#242) from dev-zw into dev 2025-05-22 11:52:45 +08:00
cp3hnu 53fe983462 fix: 用户管理界面无法退出登录 2025-05-22 11:52:08 +08:00
chenzhihang 1b63a74ce0 优化 2025-05-22 11:37:38 +08:00
chenzhihang c4a3275358 优化 2025-05-22 11:30:53 +08:00
chenzhihang fc026e9d15 优化 2025-05-22 11:21:27 +08:00
chenzhihang 9d3e0ad5f3 Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 2025-05-22 11:05:51 +08:00
chenzhihang a2555f6ac3 优化 2025-05-22 11:05:43 +08:00
cp3hnu fefc545dad Merge pull request '合并dev' (#241) from dev into dev-check 2025-05-22 10:55:42 +08:00
cp3hnu b419ab9485 Merge pull request '合并dev-zw' (#240) from dev-zw into dev 2025-05-22 10:55:17 +08:00
cp3hnu 16d4b476f2 fix: 分配用户创建时间为null 2025-05-22 10:54:50 +08:00
cp3hnu b8049721df fix: 退出登录两次 2025-05-22 10:53:44 +08:00
chenzhihang 272ec6ef97 优化 2025-05-22 10:51:34 +08:00
chenzhihang 46eff25e01 优化 2025-05-22 10:18:00 +08:00
chenzhihang dcc591cacd Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 2025-05-22 10:04:43 +08:00
chenzhihang 4da7042f61 优化 2025-05-22 10:04:27 +08:00
chenzhihang 236ac4da0f 优化 2025-05-22 09:59:31 +08:00
cp3hnu 2b0c11525b test: 添加 giturl 的测试 2025-05-22 09:21:27 +08:00
cp3hnu 67a2b41240 Merge pull request '合并dev' (#239) from dev into dev-check 2025-05-22 08:44:51 +08:00
cp3hnu 7de2295191 Merge pull request '合并dev-zw' (#238) from dev-zw into dev 2025-05-22 08:44:21 +08:00
cp3hnu 0ac624ed2a fix: 实验无法查看更多 2025-05-22 08:43:46 +08:00
chenzhihang 277ed0a710 优化积分扣除结束 2025-05-22 08:26:02 +08:00
chenzhihang 2aaa8a9fe7 Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 2025-05-21 17:19:51 +08:00
chenzhihang 19b797189d 优化积分扣除 2025-05-21 17:19:36 +08:00
cp3hnu 2d5538251e Merge pull request '合并dev' (#237) from dev into dev-check 2025-05-21 17:07:02 +08:00
cp3hnu b142d5985d Merge pull request '合并dev-zw' (#236) from dev-zw into dev 2025-05-21 17:06:33 +08:00
cp3hnu 14a9629167 fix: 添加iframe 加载失败日志 2025-05-21 17:05:22 +08:00
chenzhihang 48fdf61dff 优化积分扣除 2025-05-21 16:39:03 +08:00
chenzhihang 48851a2d4b 优化积分查询 2025-05-21 16:36:46 +08:00
cp3hnu 5fe010f52d fix: 服务只有运行中才显示预测 2025-05-21 16:34:31 +08:00
cp3hnu e6f74dc513 Merge pull request '合并dev' (#235) from dev into dev-check 2025-05-21 16:03:19 +08:00
cp3hnu e4ffcea914 Merge pull request '合并dev' (#234) from dev-zw into dev 2025-05-21 16:02:45 +08:00
cp3hnu 44673f39ed fix: 实验状态不同步 2025-05-21 15:58:25 +08:00
chenzhihang 88227a85cb 优化运行开发环境 2025-05-21 15:43:25 +08:00
chenzhihang a0d17e6dd3 优化查询pod状态 2025-05-21 15:36:27 +08:00
chenzhihang adf3b8d02a 优化查询pod状态 2025-05-21 15:26:58 +08:00
chenzhihang 596aa80315 优化查询pod状态 2025-05-21 15:07:07 +08:00
chenzhihang 7b146651c7 优化创建pod 2025-05-21 14:21:11 +08:00
chenzhihang eb50e76a54 优化实验状态查询 2025-05-21 11:44:00 +08:00
chenzhihang a2f1c0532b 优化dvc 2025-05-21 10:31:23 +08:00
chenzhihang 694f142b3f 优化积分扣除,优化dvc 2025-05-21 09:58:49 +08:00
chenzhihang cdceefcb24 优化实验状态查询 2025-05-21 08:56:22 +08:00
chenzhihang 1cfbd5185c 优化积分扣除 2025-05-21 08:47:28 +08:00
chenzhihang bee9f43762 优化 2025-05-20 17:06:59 +08:00
chenzhihang aa3909a047 优化 2025-05-20 17:03:01 +08:00
chenzhihang 111ade2f49 优化项目排序 2025-05-20 16:49:36 +08:00
chenzhihang 6670aa0658 优化项目排序 2025-05-20 16:39:42 +08:00
chenzhihang 6748c46d6f 优化项目排序 2025-05-20 16:19:21 +08:00
chenzhihang d6ed84ac59 Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 2025-05-20 16:01:34 +08:00
chenzhihang 5a7a188478 优化积分更新 2025-05-20 16:01:21 +08:00
cp3hnu 5d641cb61b Merge pull request '合并dev' (#233) from dev into dev-check 2025-05-20 15:34:25 +08:00
chenzhihang 6809f62b9c 优化 2025-05-20 15:30:05 +08:00
cp3hnu 8110739002 Merge pull request '合并dev-zw' (#232) from dev-zw into dev 2025-05-20 15:29:06 +08:00
cp3hnu 1fd40f927c fix: 用户账号支持4-15位 2025-05-20 15:28:37 +08:00
chenzhihang b02d15662f Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 2025-05-20 15:22:13 +08:00
chenzhihang 20b1e78df7 优化 2025-05-20 15:21:57 +08:00
cp3hnu d7ddcefb96 fix: 自动机器学习创建时间有误 2025-05-20 14:57:49 +08:00
cp3hnu 1f08241ca9 Merge pull request '合并dev' (#231) from dev into dev-check 2025-05-20 14:56:52 +08:00
cp3hnu b4873208c2 Merge pull request '合并dev-zw' (#230) from dev-zw into dev 2025-05-20 14:56:22 +08:00
chenzhihang 25381c26ae 优化 2025-05-20 14:54:56 +08:00
chenzhihang 567263b11a Merge remote-tracking branch 'origin/dev' into dev-check 2025-05-20 14:44:55 +08:00
chenzhihang cdb3fafd14 Merge branch 'dev' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev 2025-05-20 14:44:33 +08:00
chenzhihang 25dec31eaa 优化 2025-05-20 14:44:18 +08:00
cp3hnu 5b8a10006d feat: 删除机器人 2025-05-20 14:17:05 +08:00
zhaowei b0d8a19975 feat: 快速开始删除开发环境 2025-05-20 14:10:24 +08:00
cp3hnu f5d4158532 Merge pull request '合并dev' (#229) from dev into dev-check 2025-05-20 12:44:40 +08:00
cp3hnu dcb73eacf9 Merge pull request '合并dev-zw' (#228) from dev-zw into dev 2025-05-20 12:44:17 +08:00
fanshuai 84dbb1e8fb 修改BUG 【开发环境】新创建开发环境处于列表底部,未处于列表前列 2025-05-20 11:28:47 +08:00
cp3hnu 95cfbefda4 feat: 自动机器学习运行&获取tensorboard状态 2025-05-20 11:23:23 +08:00
fanshuai ae86b2a835 Merge remote-tracking branch 'origin/dev-check' into dev-check 2025-05-20 11:14:55 +08:00
fanshuai 68ee173591 修改BUG 【工作空间】AI资产卡片数据统计错误 2025-05-20 11:14:36 +08:00
chenzhihang 71edeb6922 优化状态更新 2025-05-20 11:13:09 +08:00
chenzhihang a4b82fb9f5 优化状态更新 2025-05-20 10:52:11 +08:00
cp3hnu 9a89988e95 fix: 删除“构建中”状态镜像版本,构建成功/失败状态返回后重新显示在列表 2025-05-20 09:32:11 +08:00
cp3hnu b9f4c48ea6 fix: 位于大于筛选结果的页码,点击左侧边栏筛选,页面提示暂无数据 2025-05-20 09:31:35 +08:00
chenzhihang 8270406d3c 优化http请求 2025-05-19 20:06:16 +08:00
chenzhihang 870fbce684 优化查询代码配置bug 2025-05-19 19:25:33 +08:00
cp3hnu 7a4852908b fix: 添加镜像版本描述 2025-05-19 18:41:20 +08:00
chenzhihang ce0d898af8 优化积分扣除 2025-05-19 17:59:06 +08:00
chenzhihang b5fd8eb031 Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 2025-05-19 16:42:33 +08:00
chenzhihang b9b0db8442 优化异常提示 2025-05-19 16:42:21 +08:00
chenzhihang a2cf8fe75e 优化 2025-05-19 16:36:53 +08:00
cp3hnu 6bb5a9e8e6 Merge pull request '合并dev' (#227) from dev into dev-check 2025-05-19 16:29:41 +08:00
cp3hnu aa980985d5 Merge pull request '合并dev-zw' (#226) from dev-zw into dev 2025-05-19 16:29:20 +08:00
cp3hnu b4d99f8e5c fix: 代码配置30202改为30203 2025-05-19 16:28:29 +08:00
chenzhihang 5d49e2d1b5 优化镜像版本描述 2025-05-19 16:18:52 +08:00
chenzhihang 20ce4e4758 优化镜像版本描述 2025-05-19 16:18:19 +08:00
chenzhihang b994fb3f31 优化 2025-05-19 16:13:24 +08:00
chenzhihang c0320a2a68 优化 2025-05-19 16:09:01 +08:00
chenzhihang 41c2faf9cb 优化 2025-05-19 16:03:17 +08:00
cp3hnu e92ac40694 fix: 列表运行时长和详情运行时长不一致 2025-05-19 15:56:28 +08:00
chenzhihang 45990fa2b7 优化 2025-05-19 15:53:45 +08:00
cp3hnu 9c62812424 fix: 列表运行时长和详情运行时长不一致 2025-05-19 15:29:43 +08:00
chenzhihang d1928d702b 优化 2025-05-19 15:08:03 +08:00
chenzhihang eea826de2c 优化构建镜像 2025-05-19 14:22:39 +08:00
chenzhihang 55fd9d7271 优化代码配置 2025-05-19 14:07:39 +08:00
chenzhihang 3905841db7 优化状态更新 2025-05-19 11:45:56 +08:00
chenzhihang 0a7be4e261 优化结束时间 2025-05-19 11:39:44 +08:00
chenzhihang 47f2c80a00 Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 2025-05-19 11:30:56 +08:00
chenzhihang 9def5a4d7a 优化状态更新 2025-05-19 11:30:38 +08:00
cp3hnu 7fc016c3a0 Merge pull request '合并dev' (#225) from dev into dev-check 2025-05-19 11:05:23 +08:00
cp3hnu 260812125a Merge pull request '合并dev-zw' (#224) from dev-zw into dev 2025-05-19 11:05:01 +08:00
cp3hnu 22f85fb3ae fix: 实验取流水线节点 2025-05-19 11:04:19 +08:00
chenzhihang 765f37ab7c 优化主动学习 2025-05-19 10:57:03 +08:00
chenzhihang 57296d364e 优化主动学习 2025-05-19 10:54:53 +08:00
chenzhihang 04a9f8f125 优化主动学习 2025-05-19 10:37:39 +08:00
chenzhihang 640c49f507 优化 2025-05-19 10:25:27 +08:00
chenzhihang 85c92e8fae 优化主动学习 2025-05-19 10:16:44 +08:00
chenzhihang 7b51ed5bc6 优化主动学习 2025-05-19 10:06:07 +08:00
chenzhihang b78b0075cd 优化主动学习 2025-05-19 09:47:16 +08:00
chenzhihang e4dd26c87d 优化 2025-05-19 09:16:28 +08:00
chenzhihang f5f3aca3e5 Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 2025-05-18 16:02:18 +08:00
chenzhihang 5aa9cdc62f 优化 2025-05-18 16:00:06 +08:00
cp3hnu 5d60751a66 feat: 内嵌tesorboard 2025-05-16 14:14:26 +08:00
cp3hnu 0e54f6e95d Merge pull request '合并dev' (#223) from dev into dev-check 2025-05-15 16:01:45 +08:00
cp3hnu bc18fb14c2 Merge pull request '合并dev-zw' (#222) from dev-zw into dev 2025-05-15 16:01:21 +08:00
cp3hnu c103375c8c fix: 自动机器学习日志一片空白 2025-05-15 16:01:05 +08:00
cp3hnu ebd1d8680a fix: 全选时可以选中运行的实验实例 2025-05-15 15:22:36 +08:00
chenzhihang ac95c975a5 Merge branch 'dev-check' of https://gitlink.org.cn/ci4s/ci4sManagement-cloud into dev-check 2025-05-15 10:55:25 +08:00
chenzhihang c16c9d75a3 Merge remote-tracking branch 'origin/dev' into dev-check 2025-05-15 10:55:06 +08:00
cp3hnu b6f0ffbfe1 style: 调整样式 2025-05-15 10:12:24 +08:00
cp3hnu 039ad81fc7 Merge pull request '合并dev' (#221) from dev into dev-check 2025-05-15 10:08:25 +08:00
cp3hnu e553a21c2f Merge pull request '合并dev-zw' (#220) from dev-zw into dev 2025-05-15 10:08:03 +08:00
zhaowei c719141676 feat: 验收 2025-05-07 16:32:30 +08:00
446 changed files with 4620 additions and 478737 deletions

22
LICENSE
View File

@ -1,21 +1,21 @@
MIT License
Copyright (c) <year> <copyright holders>
Copyright (c) 2020 若依
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

429
README.md
View File

@ -1,312 +1,129 @@
## 复杂智能软件统一管理平台
### 📖 概述
复杂智能软件统一管理平台是一个集数据标注、模型开发、训练部署于一体的综合性AI开发平台。平台提供从数据准备到模型服务的全生命周期管理支持多模态数据处理、自动化机器学习、分布式训练等功能。
### 🎯 核心功能模块
#### 1. 📊 数据标注中心 (Data Annotation)
多模态标注能力
​​图像标注​​: 支持 bounding box、多边形分割、关键点标注、图像分类
​​文本标注​​: 实体识别、文本分类、情感分析、文本摘要
​​音频标注​​: 语音转写、声音事件检测、说话人识别
​​视频标注​​: 视频动作识别、时序标注、多目标跟踪
数据管理
​​版本控制​​: 数据集版本管理,支持回溯和比较不同版本
​​数据可视化​​: 多维数据分布分析和统计可视化
#### 2. 💻 开发环境 (Development Environment)
在线开发工具
Jupyter Lab: 完整的Jupyter环境支持多种kernel
VS Code Online: 基于浏览器的完整IDE体验
SSH终端: 直接访问计算节点,支持远程调试
环境管理
​​预置环境​​: TensorFlow、PyTorch、MXNet等主流框架环境
​​自定义镜像​​: 支持用户构建和分享自定义环境镜像
​​依赖管理​​: 自动解决环境依赖冲突,支持多版本共存
协作开发
​​实时协作​​: 多用户同时编辑和运行代码
​​代码分享​​: 一键分享notebook和代码片段
​​代码审查​​: 集成Git工作流支持代码评审和合并
#### 3. 📁 智能资源库
##### 数据集管理系统
​​元数据管理​​:
数据来源追踪:记录数据采集时间、来源渠道、采集方法
数据质量评估:自动计算数据质量指标(完整性、准确性、一致性)
数据血缘追踪:完整记录数据处理和变换历史
数据权限控制:细粒度的数据访问权限管理
​​版本演化​​:
数据集版本树:可视化展示数据集版本演进关系
差异对比:自动分析不同版本间的数据差异
版本回滚:支持快速回滚到任意历史版本
变更日志:详细记录每次数据变更的内容和原因
##### 模型仓库与演化追踪
​​模型元数据管理​​:
训练配置存档:完整保存训练超参数、环境配置、代码版本
性能指标记录:准确记录模型在各测试集上的性能表现
资源消耗统计记录训练和推理的GPU/CPU/内存使用情况
模型签名:基于模型结构和参数的唯一标识生成
​​模型演化​​
父子关系追踪:清晰记录模型迭代的传承关系
性能对比分析:自动对比不同迭代版本的性能差异
演化路径优化:基于历史迭代数据推荐优化方向
模型血缘分析:完整追溯模型从初始版本到最终版本的全过程
##### 镜像仓库
​​环境镜像管理​​:
基础镜像库:预配置的各种深度学习环境
自定义镜像用户基于Dockerfile构建的个性化环境
镜像版本控制:支持镜像版本管理和回滚
##### 代码库
​​算法模板​​:
预置算法:分类、检测、分割、生成等常见任务模板
最佳实践:经过验证的高效实现方案
可复现配置:确保代码在不同环境下的可复现性
#### 4. 🚀 模型训练与管理平台
##### 训练流水线
​​可视化工作流设计器​​:
拖拽式界面:直观构建复杂训练流水线
模块化组件:数据预处理、模型训练、评估验证等标准化组件
条件分支:支持基于训练结果的动态流水线调整
参数传递:组件间数据和服务的高效传递
​​分布式训练支持​​:
多机多卡训练:自动分布式数据并行和模型并行
弹性训练:动态调整训练资源,支持训练过程中扩容缩容
混合精度训练自动FP16/FP32混合精度优化
梯度优化:梯度压缩、异步更新等分布式优化技术
##### 实验管理系统
​​实验追踪​​:
全量记录:超参数、环境变量、代码版本、数据版本
实时监控:训练指标、资源使用、日志输出的实时采集
比较分析:多实验结果的并行对比和差异分析
实验复现:一键复现历史实验的完整环境
​​可视化分析​​:
训练曲线:损失函数、准确率等指标的可视化展示
超参数分析:超参数与模型性能的关系可视化
模型诊断:梯度分布、激活值分布等模型内部状态分析
数据洞察:训练数据与模型性能的关联分析
##### 自动机器学习
​​端到端自动化​​:
自动特征工程:基于遗传编程的特征自动生成和选择
模型选择优化:自动尝试多种算法和架构组合
超参数自动优化:贝叶斯优化、进化算法等智能搜索策略
神经网络架构搜索:基于强化学习的自动网络结构设计
​​智能优化策略​​:
多目标优化:同时优化多个性能指标(精度、速度、大小)
早停机制:智能判断训练趋势,提前终止无效训练
资源感知:根据可用计算资源自适应调整搜索空间
迁移学习:利用历史实验数据加速新任务优化
##### 主动学习系统
​​智能样本选择​​:
不确定性采样:选择模型最不确定的样本进行标注
多样性采样:确保选择样本的代表性和多样性
预期模型变化:选择能带来最大模型改进的样本
多模态策略:针对不同数据类型采用不同的选择策略
​​迭代优化流程​​:
人机协作循环:自动化的标注-训练-评估迭代流程
进度监控:实时跟踪标注效率和模型提升效果
成本控制:在标注成本和模型性能间智能平衡
效果评估:自动评估主动学习策略的有效性
##### 超参数寻优
​​多策略优化​​:
网格搜索: exhaustive搜索适合小参数空间
随机搜索: 高效的大参数空间探索
贝叶斯优化: 基于高斯过程的智能参数搜索
进化算法: 基于种群进化的全局优化策略
​​高级功能​​:
热启动优化: 利用历史实验数据加速优化过程
多保真度优化: 使用低精度训练快速评估参数组合
并行优化: 同时进行多个参数组合的实验
条件参数空间: 支持参数间的条件依赖关系
大规模训练支持
​​弹性计算​​:
动态资源分配:根据训练阶段自动调整计算资源
容错训练:自动处理节点故障,支持训练恢复
异构计算同时利用CPU、GPU、专用AI芯片
成本优化:智能调度降低训练成本
#### 5. 🌐 应用部署与服务化
模型部署
​​一键部署​​:
多种服务模式实时API、批量处理、流式处理
自动容器化将模型自动打包为Docker容器
服务配置自动生成服务配置和API文档
服务管理
​​弹性伸缩​​:
自动扩缩容:基于流量负载自动调整实例数量
资源优化:智能分配资源,提高资源利用率
成本控制:基于使用模式的弹性计费优化
监控告警
​​服务健康监控​​:
性能指标QPS、延迟、错误率等实时监控
资源监控CPU、内存、GPU使用情况监控
业务指标:自定义业务指标监控和告警
A/B测试
​​多版本对比​​:
流量分配:精确控制不同版本的流量比例
效果评估:自动统计各版本的业务指标
智能推荐:基于效果数据自动推荐最优版本
### 🏗️ 系统架构
#### 前端架构
​​微前端架构​​: 各功能模块独立开发部署
​​响应式设计​​: 支持桌面端和移动端访问
​​实时更新​​: WebSocket实现实时状态更新
#### 后端架构
​​微服务架构​​:
标注服务、训练服务、部署服务等独立微服务
服务发现和负载均衡
容错和熔断机制
#### ​​工作流引擎​​:
基于Argo Workflows的训练流水线
可视化工作流监控和管理
工作流版本控制和模板化
#### 资源管理
GPU资源池: 共享GPU计算资源
​​弹性配额​​: 按需分配计算资源
​​成本优化​​: 智能调度降低计算成本
### 🚀 快速开始
环境要求
Kubernetes集群 1.20+
NVIDIA GPU驱动如使用GPU
存储系统S3兼容存储或NFS
<p align="center">
<img alt="logo" src="https://oscimg.oschina.net/oscnet/up-b99b286755aef70355a7084753f89cdb7c9.png">
</p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">ci4s-cloud 1.0</h1>
<h4 align="center">基于 React 和 Spring Boot/Spring Cloud & Alibaba 前后端分离的分布式微服务架构</h4>
<p align="center">
<a href="https://gitee.com/y_project/RuoYi-Cloud/stargazers"><img src="https://gitee.com/y_project/RuoYi-Cloud/badge/star.svg?theme=dark"></a>
<a href="https://gitee.com/y_project/RuoYi-Cloud"><img src="https://img.shields.io/badge/RuoYi-v3.6.3-brightgreen.svg"></a>
<a href="https://gitee.com/y_project/RuoYi-Cloud/blob/master/LICENSE"><img src="https://img.shields.io/github/license/mashape/apistatus.svg"></a>
</p>
## 平台简介
ci4s-cloud是复杂智能软件统一管理平台。
* 采用前后端分离的模式,微服务版本前端(基于React)。
* 后端采用Spring Boot、Spring Cloud & Alibaba。
* 注册中心、配置中心选型Nacos权限认证使用Redis。
* 流量控制框架选型Sentinel分布式事务选型Seata。
## 系统模块
~~~
com.ruoyi
├── ruoyi-ui // 前端框架 [80]
├── ruoyi-gateway // 网关模块 [8080]
├── ruoyi-auth // 认证中心 [9200]
├── ruoyi-api // 接口模块
│ └── ruoyi-api-system // 系统接口
├── ruoyi-common // 通用模块
│ └── ruoyi-common-core // 核心模块
│ └── ruoyi-common-datascope // 权限范围
│ └── ruoyi-common-datasource // 多数据源
│ └── ruoyi-common-log // 日志记录
│ └── ruoyi-common-redis // 缓存服务
│ └── ruoyi-common-seata // 分布式事务
│ └── ruoyi-common-security // 安全模块
│ └── ruoyi-common-swagger // 系统接口
├── ruoyi-modules // 业务模块
│ └── ruoyi-system // 系统模块 [9201]
│ └── ruoyi-gen // 代码生成 [9202]
│ └── ruoyi-job // 定时任务 [9203]
│ └── ruoyi-file // 文件服务 [9300]
│ └── management-platform // 文件服务 [9300]
├── ruoyi-visual // 图形化管理模块
│ └── ruoyi-visual-monitor // 监控中心 [9100]
├──pom.xml // 公共依赖
~~~
## 架构图
<img src="https://oscimg.oschina.net/oscnet/up-82e9722ecb846786405a904bafcf19f73f3.png"/>
## 内置功能
1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
3. 岗位管理:配置系统用户所属担任职务。
4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。
5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
7. 参数管理:对系统动态配置常用参数。
8. 通知公告:系统通知公告信息发布维护。
9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
10. 登录日志:系统登录日志记录查询包含登录异常。
11. 在线用户:当前系统中活跃用户状态监控。
12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。
13. 代码生成前后端代码的生成java、html、xml、sql支持CRUD下载 。
14. 系统接口根据业务代码自动生成相关的api接口文档。
15. 服务监控监视当前系统CPU、内存、磁盘、堆栈等相关信息。
16. 在线构建器拖动表单元素生成相应的HTML代码。
17. 连接池监视监视当前系统数据库连接池状态可进行分析SQL找出系统性能瓶颈。
## 演示图
<table>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/cd1f90be5f2684f4560c9519c0f2a232ee8.jpg"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/1cbcf0e6f257c7d3a063c0e3f2ff989e4b3.jpg"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-8074972883b5ba0622e13246738ebba237a.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-9f88719cdfca9af2e58b352a20e23d43b12.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-39bf2584ec3a529b0d5a3b70d15c9b37646.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-4148b24f58660a9dc347761e4cf6162f28f.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-b2d62ceb95d2dd9b3fbe157bb70d26001e9.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-d67451d308b7a79ad6819723396f7c3d77a.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/5e8c387724954459291aafd5eb52b456f53.jpg"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/644e78da53c2e92a95dfda4f76e6d117c4b.jpg"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-8370a0d02977eebf6dbf854c8450293c937.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-49003ed83f60f633e7153609a53a2b644f7.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-d4fe726319ece268d4746602c39cffc0621.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-c195234bbcd30be6927f037a6755e6ab69c.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-ece3fd37a3d4bb75a3926e905a3c5629055.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-92ffb7f3835855cff100fa0f754a6be0d99.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-ff9e3066561574aca73005c5730c6a41f15.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-5e4daac0bb59612c5038448acbcef235e3a.png"/></td>
</tr>
</table>
## 复杂智能软件统一管理平台安装部署
上传安装脚本
```
step1: 安装mysql
#### step1: 安装mysql
helm install mysql . -n ci4s-test
step2: 安装redis
#### step2: 安装redis
helm install redis . -n ci4s-test
step3: 安装nacos
#### step3: 安装nacos
在第一步安装的mysql中创建nacos-ci4s-config数据库(选UTF-8)并且运行naocs初始化脚本
kubectl create -f k8s-3nacos.yaml
step4: 安装服务
kubectl create -f *.yaml
```
### 📝 贡献指南
我们欢迎社区贡献欢迎提交PR参与开发。
### 📄 许可证
Apache License 2.0
#### step4: 安装ruoyi服务
kubectl create -f *.yaml

View File

@ -36,7 +36,7 @@ http {
location /minio/ {
# rewrite ^/label-studio/(.*)$ /$1 break;
proxy_pass http://juicefs-s3-gateway.juicefs.svc:9000/;
proxy_pass http://juicefs-gateway-flint-storage.default.svc:30090/;
proxy_hide_header X-Frame-Options;
add_header X-Frame-Options ALLOWALL;
}

View File

@ -18,6 +18,11 @@ spec:
image: ${k8s-5auth-image}
ports:
- containerPort: 9200
env:
- name: TZ
value: Asia/Shanghai
- name: JAVA_TOOL_OPTIONS
value: "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:5005"
---
apiVersion: v1
@ -28,9 +33,15 @@ metadata:
spec:
type: NodePort
ports:
- port: 9200
- name: http
port: 9200
nodePort: 31206
protocol: TCP
- name: debug
nodePort: 31221
port: 5005
protocol: TCP
targetPort: 5005
selector:
app: ci4s-auth

View File

@ -20,8 +20,9 @@ export default {
// localhost:8000/api/** -> https://preview.pro.ant.design/api/**
'/api/': {
// 要代理的地址
target: 'http://172.20.32.185:31213', // 开发环境
// target: 'http://172.20.32.98:8082',
// target: 'http://172.20.32.197:31213', // 开发环境
// target: 'http://172.20.32.235:31213', // 测试环境
target: 'http://172.20.32.44:8082',
// target: 'http://172.20.32.150:8082',
// 配置了这个可以从 http 代理到 https
// 依赖 origin 的功能可能需要这个,比如 cookie

View File

@ -141,12 +141,23 @@ export default [
{
name: '实验对比',
path: 'compare',
component: './Experiment/Comparison/index',
routes: [
{
name: '实验对比',
path: '',
component: './Experiment/Comparison/index',
},
{
name: '可视化对比',
path: 'compare-visual',
component: './Experiment/Aim/index',
},
],
},
{
name: '实验可视化对比',
path: 'compare-visual',
component: './Experiment/Aim/index',
name: '可视化',
path: 'visual',
component: './Experiment/Tensorboard/index',
},
],
},
@ -218,7 +229,18 @@ export default [
{
name: '实验实例详情',
path: 'instance/:experimentId/:id',
component: './HyperParameter/Instance/index',
routes: [
{
name: '实验实例详情',
path: '',
component: './HyperParameter/Instance/index',
},
{
name: '可视化对比',
path: 'compare-visual',
component: './HyperParameter/Aim/index',
},
],
},
],
},

View File

@ -21,6 +21,7 @@ import {
} from './services/session';
import './styles/menu.less';
import { needAuth } from './utils';
// import { closeAllModals } from './utils/modal';
import { gotoLoginPage } from './utils/ui';
export { requestConfig as request } from './requestConfig';
@ -96,6 +97,7 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => {
},
onPageChange: () => {
const { location } = history;
// closeAllModals();
// 如果没有登录,重定向到 login
if (!initialState?.currentUser && needAuth(location.pathname)) {
gotoLoginPage();

View File

@ -1,12 +1,11 @@
import FullScreenFrame from '@/components/FullScreenFrame';
import KFSpin from '@/components/KFSpin';
import { getKnowledgeGraphUrl, getLabelStudioUrl } from '@/services/developmentEnvironment';
import Loading from '@/utils/loading';
import { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage';
import { FloatButton } from 'antd';
import classNames from 'classnames';
import { useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import './index.less';
export enum IframePageType {
@ -54,9 +53,13 @@ const getRequestAPI = (type: IframePageType): (() => Promise<any>) => {
type IframePageProps = {
/** 子系统 */
type: IframePageType;
type?: IframePageType;
/** url */
url?: string;
/** 是否可以在页签上打开 */
openInTab?: boolean;
/** 是否显示加载 */
showLoading?: boolean;
/** 自定义样式类名 */
className?: string;
/** 自定义样式 */
@ -64,32 +67,60 @@ type IframePageProps = {
};
/** 系统内嵌 iframe目前系统有数据标注、应用开发、开发环境、GitLink 四个子系统,使用时可以添加其他子系统 */
function IframePage({ type, openInTab = false, className, style }: IframePageProps) {
function IframePage({
type,
url,
showLoading = true,
openInTab = false,
className,
style,
}: IframePageProps) {
const [iframeUrl, setIframeUrl] = useState('');
const [loading, setLoading] = useState(false);
// const [loading, setLoading] = useState(false);
useEffect(() => {
const requestIframeUrl = async () => {
setLoading(true);
const requestIframeUrl = async (type: IframePageType) => {
if (showLoading) {
Loading.show();
}
const [res] = await to(getRequestAPI(type)());
if (res && res.data) {
setIframeUrl(res.data);
} else {
setLoading(false);
if (showLoading) {
Loading.hide();
}
}
};
requestIframeUrl();
}, [type]);
if (type) {
requestIframeUrl(type);
} else if (url) {
if (showLoading) {
Loading.show();
}
const hideLoading = () => {
setLoading(false);
setIframeUrl(url);
}
}, [type, url, showLoading]);
const handleLoad = () => {
if (showLoading) {
Loading.hide();
}
};
const handleError = (error?: React.SyntheticEvent<HTMLIFrameElement, Event>) => {
console.log('error', error);
if (showLoading) {
Loading.hide();
}
};
return (
<div className={classNames('kf-iframe-page', className)} style={style}>
{loading && createPortal(<KFSpin size="large" />, document.body)}
<FullScreenFrame url={iframeUrl} onLoad={hideLoading} onError={hideLoading} />
{/* {loading && createPortal(<KFSpin size="large" />, document.body)} */}
{iframeUrl && <FullScreenFrame url={iframeUrl} onLoad={handleLoad} onError={handleError} />}
{openInTab && <FloatButton onClick={() => window.open(iframeUrl, '_blank')} />}
</div>
);

View File

@ -1,7 +1,7 @@
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 { AvailableRange, CommonTabKeys } from '@/enums';
import { AvailableRange, CommonTabKeys, MirrorVersionStatus } from '@/enums';
import { ResourceData, ResourceVersionData } from '@/pages/Dataset/config';
import { MirrorVersionData } from '@/pages/Mirror/Info';
import { MirrorData } from '@/pages/Mirror/List';
@ -24,11 +24,11 @@ export enum ResourceSelectorType {
}
// 数据集、模型列表转为树形结构
const convertDatasetToTreeData = (list: ResourceData[]): TreeDataNode[] => {
const convertDatasetToTreeData = (list: ResourceData[], isPublic: boolean): TreeDataNode[] => {
return list.map((v) => ({
...v,
key: `${v.id}`,
title: v.name,
title: isPublic ? `${v.name} (${v.owner})` : v.name,
isLeaf: false,
checkable: false,
}));
@ -106,7 +106,7 @@ export class DatasetSelector implements SelectorTypeInfo {
const res = await getDatasetList({ is_public: isPublic, page: 0, size: 2000 });
if (res && res.data) {
const list = res.data.content || [];
return convertDatasetToTreeData(list);
return convertDatasetToTreeData(list, isPublic);
} else {
return Promise.reject('获取数据集列表失败');
}
@ -158,7 +158,7 @@ export class ModelSelector implements SelectorTypeInfo {
const res = await getModelList({ is_public: isPublic, page: 0, size: 2000 });
if (res && res.data) {
const list = res.data.content || [];
return convertDatasetToTreeData(list);
return convertDatasetToTreeData(list, isPublic);
} else {
return Promise.reject('获取模型列表失败');
}
@ -224,8 +224,7 @@ export class MirrorSelector implements SelectorTypeInfo {
image_id: parentKey,
page: 0,
size: 2000,
status: 'available',
state: 1,
status: MirrorVersionStatus.Available,
});
if (res && res.data) {
const list = res.data.content || [];

View File

@ -1,10 +1,11 @@
import { clearSessionToken } from '@/access';
import { getLabelStudioUrl } from '@/services/developmentEnvironment';
import { setRemoteMenu } from '@/services/session';
import { logout } from '@/services/system/auth';
import { ClientInfo } from '@/types';
import { sleep } from '@/utils/promise';
import { sleep, to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage';
import { gotoLoginPage, oauthLogout } from '@/utils/ui';
import { oauthLogout } from '@/utils/ui';
import { LogoutOutlined, UserOutlined } from '@ant-design/icons';
import { setAlpha } from '@ant-design/pro-components';
import { useEmotionCss } from '@ant-design/use-emotion-css';
@ -63,17 +64,23 @@ const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => {
* 退 url
*/
const loginOut = async () => {
oauthLogout('http://172.20.32.197:31209/oauth/logout');
const [res] = await to(getLabelStudioUrl());
if (res && res.data) {
oauthLogout(`${res.data}/oauth/logout`);
}
// 至少 1 秒后跳转,希望子系统能完成注销
await Promise.all([logout(), sleep(1000)]);
clearSessionToken();
setRemoteMenu(null);
gotoLoginPage();
// 退出 oauth2
const clientInfo: ClientInfo = SessionStorage.getItem(SessionStorage.clientInfoKey, true);
if (clientInfo) {
const { logoutUri } = clientInfo;
location.replace(logoutUri);
}
// setTimeout(() => {
// gotoLoginPage();
// }, 100);
};
const actionClassName = useEmotionCss(({ token }) => {
return {

View File

@ -10,13 +10,21 @@ type RunDurationProps = {
};
function RunDuration({ createTime, finishTime, className, style }: RunDurationProps) {
const [now] = useServerTime();
const [currentTime, setCurrentTime] = useState<Date>(now());
const [currentTime, setCurrentTime] = useState<Date>(finishTime ? new Date(finishTime) : now());
// console.log(
// 'currentTime',
// new Date(createTime ?? 0),
// currentTime,
// (currentTime.getTime() - new Date(createTime ?? 0).getTime()) / 1000,
// );
// 定时刷新耗时
useEffect(() => {
if (finishTime) {
setCurrentTime(new Date(finishTime));
} else {
setCurrentTime(now());
const timer = setInterval(() => {
setCurrentTime(now());
}, 1000);
@ -25,6 +33,7 @@ function RunDuration({ createTime, finishTime, className, style }: RunDurationPr
};
}
}, [finishTime, now]);
return (
<span className={className} style={style}>
{elapsedTime(createTime, currentTime)}

View File

@ -33,7 +33,7 @@ export enum TensorBoardStatus {
Unknown = 'Unknown', // 未知
Pending = 'Pending', // 启动中
Running = 'Running', // 运行中
Terminated = 'Terminated', // 未启动或者已终止
Terminated = 'Terminated', // 未启动
Failed = 'Failed', // 失败
}
@ -95,8 +95,8 @@ export enum AutoMLType {
export const autoMLTypeOptions = [
{ label: '表格', value: AutoMLType.Table },
{ label: '文本分类', value: AutoMLType.Text },
{ label: '视频分类', value: AutoMLType.Video },
{ label: '文本', value: AutoMLType.Text },
{ label: '视频', value: AutoMLType.Video },
];
// 自动化任务类型

View File

@ -1,11 +1,24 @@
import { parseJsonText } from '@/utils';
import { useEffect } from 'react';
import { ExperimentStatus } from '@/enums';
import { NodeStatus } from '@/types';
import { parseJsonText } from '@/utils';
import { useEffect } from 'react';
export type MessageHandler = (experimentInsId: number, status: string, finishedAt: string, nodes: Record<string, NodeStatus>) => void
export const useSSE = (experimentInsId: number, status: ExperimentStatus, name: string, namespace: string, onMessage: MessageHandler) => {
const isRunning = status === ExperimentStatus.Pending || status === ExperimentStatus.Running
export type MessageHandler = (
experimentId: number,
experimentInsId: number,
status: string,
finishTime: string,
nodes: Record<string, NodeStatus>,
) => void;
export const useSSE = (
experimentId: number,
experimentInsId: number,
status: ExperimentStatus,
name: string,
namespace: string,
onMessage: MessageHandler,
) => {
const isRunning = status === ExperimentStatus.Pending || status === ExperimentStatus.Running;
useEffect(() => {
if (isRunning) {
const { origin } = location;
@ -22,8 +35,8 @@ export const useSSE = (experimentInsId: number, status: ExperimentStatus, name:
const dataJson = parseJsonText(data);
const statusData = dataJson?.result?.object?.status;
if (statusData) {
const { finishedAt, phase, nodes } = statusData;
onMessage(experimentInsId, phase, finishedAt, nodes);
const { finishedAt, phase, nodes } = statusData;
onMessage(experimentId, experimentInsId, phase, finishedAt, nodes);
}
};
@ -33,8 +46,7 @@ export const useSSE = (experimentInsId: number, status: ExperimentStatus, name:
return () => {
evtSource.close();
}
};
}
}, [experimentInsId, isRunning, name, namespace, onMessage]);
}, [experimentId, experimentInsId, isRunning, name, namespace, onMessage]);
};

View File

@ -51,13 +51,12 @@ function ActiveLearnInstance() {
const [res] = await to(getActiveLearnInsReq(instanceId));
if (res && res.data) {
const info = res.data as ActiveLearnInstanceData;
const { param, node_status, argo_ins_name, argo_ins_ns, status, create_time } = info;
const { param, node_status, argo_ins_name, argo_ins_ns, status } = info;
// 解析配置参数
const paramJson = parseJsonText(param);
if (paramJson) {
setExperimentInfo({
...paramJson.data,
create_time,
});
}
@ -69,7 +68,7 @@ function ActiveLearnInstance() {
return;
}
// 进行节点状态
// 设置总 workflow 状态
const nodeStatusJson = parseJsonText(node_status);
if (nodeStatusJson) {
setNodes(nodeStatusJson);
@ -106,18 +105,17 @@ function ActiveLearnInstance() {
if (dataJson) {
const nodes = dataJson?.result?.object?.status?.nodes;
if (nodes) {
// 节点
// 设置节点
setNodes(nodes);
// 设置总 workflow 状态
const workflowStatus = Object.values(nodes).find((node: any) =>
node.displayName.startsWith(NodePrefix),
) as NodeStatus;
// 设置工作流状态
if (workflowStatus) {
setWorkflowStatus(workflowStatus);
// 实验结束,关闭 SSE
// 实验结束,关闭 SSE,获取实验实例结果
if (
workflowStatus.phase !== ExperimentStatus.Pending &&
workflowStatus.phase !== ExperimentStatus.Running
@ -152,8 +150,8 @@ function ActiveLearnInstance() {
<ActiveLearnBasic
className={styles['active-learn-instance__basic']}
info={experimentInfo}
runStatus={workflowStatus}
instanceStatus={instanceInfo?.status}
workflowStatus={workflowStatus}
instanceStatus={instanceInfo?.status as ExperimentStatus}
isInstance
/>
),
@ -181,7 +179,7 @@ function ActiveLearnInstance() {
},
{
key: TabKeys.History,
label: '训练列表',
label: '运行列表',
icon: <KFIcon type="icon-Trialliebiao" />,
children: (
<ExperimentHistory

View File

@ -28,14 +28,14 @@ type BasicInfoProps = {
info?: ActiveLearnData;
className?: string;
isInstance?: boolean;
runStatus?: NodeStatus;
workflowStatus?: NodeStatus;
instanceStatus?: ExperimentStatus;
};
function BasicInfo({
info,
className,
runStatus,
workflowStatus,
instanceStatus,
isInstance = false,
}: BasicInfoProps) {
@ -154,7 +154,7 @@ function BasicInfo({
value: info.dataset_py,
},
{
label: '数据集类名',
label: '数据集处理类名',
value: info.dataset_class_name,
},
{
@ -212,12 +212,8 @@ function BasicInfo({
return (
<div className={classNames(styles['active-learn-basic'], className)}>
{isInstance && runStatus && (
<ExperimentRunBasic
create_time={info?.create_time}
runStatus={runStatus}
instanceStatus={instanceStatus}
/>
{isInstance && workflowStatus && (
<ExperimentRunBasic workflowStatus={workflowStatus} instanceStatus={instanceStatus} />
)}
{!isInstance && (
<ConfigInfo

View File

@ -17,6 +17,11 @@ import {
function ExecuteConfig() {
const form = Form.useFormInstance();
const task_type = Form.useWatch('task_type', form);
const queryStrategiesOptions =
task_type === AutoMLTaskType.Classification
? queryStrategies.slice(0, 2)
: queryStrategies.slice(2);
return (
<>
<SubAreaTitle
@ -101,16 +106,16 @@ function ExecuteConfig() {
<Row gutter={8}>
<Col span={10}>
<Form.Item
label="数据集类名"
label="数据集处理类名"
name="dataset_class_name"
rules={[
{
required: true,
message: '请输入数据集类名',
message: '请输入数据集处理类名',
},
]}
>
<Input placeholder="请输入数据集类名" maxLength={64} showCount allowClear />
<Input placeholder="请输入数据集处理类名" maxLength={64} showCount allowClear />
</Form.Item>
</Col>
</Row>
@ -488,7 +493,12 @@ function ExecuteConfig() {
},
]}
>
<Select placeholder="请选择查询策略" options={queryStrategies} showSearch allowClear />
<Select
placeholder="请选择查询策略"
options={queryStrategiesOptions}
showSearch
allowClear
/>
</Form.Item>
</Col>
</Row>

View File

@ -87,4 +87,8 @@ export const queryStrategies = [
label: 'upper_confidence_bound',
value: 'upper_confidence_bound',
},
{
label: 'probability_of_improvement',
value: 'probability_of_improvement',
},
];

View File

@ -1,5 +1,6 @@
import { ExperimentStatus } from '@/enums';
import { ActiveLearnInstanceData } from '@/pages/ActiveLearn/types';
import EmptyLog from '@/pages/AutoML/components/ExperimentLog/empty';
import LogList from '@/pages/Experiment/components/LogList';
import { NodeStatus } from '@/types';
import { Tabs } from 'antd';
@ -64,7 +65,7 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) {
// icon: <KFIcon type="icon-rizhi1" />,
children: (
<div className={styles['experiment-log__tabs__log']}>
{trainCloneNodeStatus && (
{trainCloneNodeStatus ? (
<LogList
instanceName={instanceInfo.argo_ins_name}
instanceNamespace={instanceInfo.argo_ins_ns}
@ -73,6 +74,8 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) {
instanceNodeStartTime={trainCloneNodeStatus.startedAt}
instanceNodeStatus={trainCloneNodeStatus.phase as ExperimentStatus}
></LogList>
) : (
<EmptyLog />
)}
</div>
),
@ -83,7 +86,7 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) {
// icon: <KFIcon type="icon-rizhi1" />,
children: (
<div className={styles['experiment-log__tabs__log']}>
{hpoNodeStatus && (
{hpoNodeStatus ? (
<LogList
instanceName={instanceInfo.argo_ins_name}
instanceNamespace={instanceInfo.argo_ins_ns}
@ -92,6 +95,8 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) {
instanceNodeStartTime={hpoNodeStatus.startedAt}
instanceNodeStatus={hpoNodeStatus.phase as ExperimentStatus}
></LogList>
) : (
<EmptyLog />
)}
</div>
),

View File

@ -51,7 +51,7 @@ function AutoMLInstance() {
const [res] = await to(getExperimentInsReq(instanceId));
if (res && res.data) {
const info = res.data as AutoMLInstanceData;
const { param, node_status, argo_ins_name, argo_ins_ns, status, create_time, type } = info;
const { param, node_status, argo_ins_name, argo_ins_ns, status, type } = info;
setType(type);
// 解析配置参数
@ -59,7 +59,6 @@ function AutoMLInstance() {
if (paramJson) {
setAutoMLInfo({
...paramJson.data,
create_time,
type,
});
}
@ -95,7 +94,10 @@ function AutoMLInstance() {
};
const setupSSE = (name: string, namespace: string) => {
const { origin } = location;
let { origin } = location;
if (process.env.NODE_ENV === 'development') {
origin = 'http://172.20.32.235:31213';
}
const params = encodeURIComponent(`metadata.namespace=${namespace},metadata.name=${name}`);
const evtSource = new EventSource(
`${origin}/api/v1/realtimeStatus?listOptions.fieldSelector=${params}`,
@ -110,17 +112,17 @@ function AutoMLInstance() {
if (dataJson) {
const nodes = dataJson?.result?.object?.status?.nodes;
if (nodes) {
// 节点
// 设置节点
setNodes(nodes);
// 设置总 workflow 状态
const workflowStatus = Object.values(nodes).find((node: any) =>
node.displayName.startsWith(NodePrefix),
) as NodeStatus;
if (workflowStatus) {
setWorkflowStatus(workflowStatus);
// 实验结束,关闭 SSE
// 实验结束,关闭 SSE,获取实验实例结果
if (
workflowStatus.phase !== ExperimentStatus.Pending &&
workflowStatus.phase !== ExperimentStatus.Running
@ -155,8 +157,8 @@ function AutoMLInstance() {
<AutoMLBasic
className={styles['auto-ml-instance__basic']}
info={autoMLInfo}
runStatus={workflowStatus}
instanceStatus={instanceInfo?.status}
workflowStatus={workflowStatus}
instanceStatus={instanceInfo?.status as ExperimentStatus}
isInstance
/>
),
@ -202,7 +204,7 @@ function AutoMLInstance() {
}
: {
key: TabKeys.History,
label: '试验列表',
label: '运行列表',
icon: <KFIcon type="icon-Trialliebiao" />,
children: (
<ExperimentHistory

View File

@ -1,7 +1,7 @@
/*
* @Author:
* @Date: 2024-04-16 13:58:08
* @Description:
* @Description:
*/
import ExperimentList, { ExperimentListType } from '../components/ExperimentList';

View File

@ -7,10 +7,21 @@ import {
autoMLTaskTypeOptions,
} from '@/enums';
import { useComputingResource } from '@/hooks/useComputingResource';
import {
classificationAlgorithms,
featureAlgorithms,
regressorAlgorithms,
} from '@/pages/AutoML/components/CreateForm/utils';
import { AutoMLData } from '@/pages/AutoML/types';
import { type NodeStatus } from '@/types';
import { parseJsonText } from '@/utils';
import { formatBoolean, formatDataset, formatDate, formatEnum } from '@/utils/format';
import {
formatBoolean,
formatDataset,
formatDate,
formatEnum,
type EnumOptions,
} from '@/utils/format';
import classNames from 'classnames';
import { useMemo } from 'react';
import ExperimentRunBasic from '../ExperimentRunBasic';
@ -21,6 +32,7 @@ const formatOptimizeMode = (value: boolean) => {
return value ? '越大越好' : '越小越好';
};
// 格式化权重
const formatMetricsWeight = (value: string) => {
if (!value) {
return '--';
@ -34,18 +46,33 @@ const formatMetricsWeight = (value: string) => {
.join('\n');
};
// 格式化算法
const formatAlgorithm = (algorithms: EnumOptions[]) => {
return (value: string) => {
if (!value) {
return '--';
}
const list = value
.split(',')
.filter((v) => v !== '')
.map((v) => v.trim());
return list.map((v) => formatEnum(algorithms)(v)).join('');
};
};
type AutoMLBasicProps = {
info?: AutoMLData;
className?: string;
isInstance?: boolean;
runStatus?: NodeStatus;
workflowStatus?: NodeStatus;
instanceStatus?: ExperimentStatus;
instanceCreateTime?: string;
};
function AutoMLBasic({
info,
className,
runStatus,
workflowStatus,
instanceStatus,
isInstance = false,
}: AutoMLBasicProps) {
@ -95,10 +122,12 @@ function AutoMLBasic({
{
label: '特征预处理算法',
value: info.include_feature_preprocessor,
format: formatAlgorithm(featureAlgorithms),
},
{
label: '排除的特征预处理算法',
value: info.exclude_feature_preprocessor,
format: formatAlgorithm(featureAlgorithms),
},
{
label: info.task_type === AutoMLTaskType.Regression ? '回归算法' : '分类算法',
@ -106,6 +135,11 @@ function AutoMLBasic({
info.task_type === AutoMLTaskType.Regression
? info.include_regressor
: info.include_classifier,
format: formatAlgorithm(
info.task_type === AutoMLTaskType.Regression
? regressorAlgorithms
: classificationAlgorithms,
),
},
{
label: info.task_type === AutoMLTaskType.Regression ? '排除的回归算法' : '排除的分类算法',
@ -113,6 +147,11 @@ function AutoMLBasic({
info.task_type === AutoMLTaskType.Regression
? info.exclude_regressor
: info.exclude_classifier,
format: formatAlgorithm(
info.task_type === AutoMLTaskType.Regression
? regressorAlgorithms
: classificationAlgorithms,
),
},
{
label: '集成方式',
@ -292,12 +331,8 @@ function AutoMLBasic({
return (
<div className={classNames(styles['auto-ml-basic'], className)}>
{isInstance && runStatus && (
<ExperimentRunBasic
create_time={info?.create_time}
runStatus={runStatus}
instanceStatus={instanceStatus}
/>
{isInstance && workflowStatus && (
<ExperimentRunBasic workflowStatus={workflowStatus} instanceStatus={instanceStatus} />
)}
{!isInstance && (
<ConfigInfo

View File

@ -8,69 +8,7 @@ import {
autoMLTaskTypeOptions,
} from '@/enums';
import { Col, Form, InputNumber, Radio, Row, Select, Switch } from 'antd';
// 分类算法
const classificationAlgorithms = [
'adaboost',
'bernoulli_nb',
'decision_tree',
'extra_trees',
'gaussian_nb',
'gradient_boosting',
'k_nearest_neighbors',
'lda',
'liblinear_svc',
'libsvm_svc',
'mlp',
'multinomial_nb',
'passive_aggressive',
'qda',
'random_forest',
'sgd',
'LightGBMClassification',
'XGBoostClassification',
'StackingClassification',
].map((name) => ({ label: name, value: name }));
// 回归算法
const regressorAlgorithms = [
'adaboost',
'ard_regression',
'decision_tree',
'extra_trees',
'gaussian_process',
'gradient_boosting',
'k_nearest_neighbors',
'liblinear_svr',
'libsvm_svr',
'mlp',
'random_forest',
'sgd',
'LightGBMRegression',
'XGBoostRegression',
].map((name) => ({ label: name, value: name }));
// 特征预处理算法
const featureAlgorithms = [
'densifier',
'extra_trees_preproc_for_classification',
'extra_trees_preproc_for_regression',
'fast_ica',
'feature_agglomeration',
'kernel_pca',
'kitchen_sinks',
'liblinear_svc_preprocessor',
'no_preprocessing',
'nystroem_sampler',
'pca',
'polynomial',
'random_trees_embedding',
'select_percentile_classification',
'select_percentile_regression',
'select_rates_classification',
'select_rates_regression',
'truncatedSVD',
].map((name) => ({ label: name, value: name }));
import { classificationAlgorithms, featureAlgorithms, regressorAlgorithms } from './utils';
// 分类指标
export const classificationMetrics = [
@ -280,9 +218,9 @@ function ExecuteConfig() {
<Form.Item
label="集成模型数量"
name="ensemble_size"
tooltip="集成模型数量,如果设置为0则没有集成。默认50"
tooltip="集成模型数量,必须是大于等于1的整数默认50"
>
<InputNumber placeholder="请输入集成模型数量" min={0} precision={0} />
<InputNumber placeholder="请输入集成模型数量" min={1} precision={0} />
</Form.Item>
</Col>
</Row>
@ -292,7 +230,7 @@ function ExecuteConfig() {
<Form.Item
label="集成最佳模型数量"
name="ensemble_nbest"
tooltip="仅集成最佳的N个模型"
tooltip="仅集成最佳的N个模型必须是大于等于1的整数"
>
<InputNumber placeholder="请输入集成最佳模型数量" min={1} precision={0} />
</Form.Item>
@ -419,6 +357,7 @@ function ExecuteConfig() {
<Form.Item
label="交叉验证折数"
name="folds"
tooltip="交叉验证折数必须是大于等于2的整数"
rules={[
{
required: true,
@ -426,7 +365,7 @@ function ExecuteConfig() {
},
]}
>
<InputNumber placeholder="请输入交叉验证折数" min={1} precision={0} />
<InputNumber placeholder="请输入交叉验证折数" min={2} precision={0} />
</Form.Item>
</Col>
</Row>

View File

@ -0,0 +1,85 @@
// 分类算法
export const classificationAlgorithms = [
{ label: 'adaboost (自适应提升算法)', value: 'adaboost' },
{ label: 'bernoulli_nb (伯努利朴素贝叶斯)', value: 'bernoulli_nb' },
{ label: 'decision_tree (决策树)', value: 'decision_tree' },
{ label: 'extra_trees (极端随机树)', value: 'extra_trees' },
{ label: 'gaussian_nb (高斯朴素贝叶斯)', value: 'gaussian_nb' },
{ label: 'gradient_boosting (梯度提升)', value: 'gradient_boosting' },
{ label: 'k_nearest_neighbors (k近邻)', value: 'k_nearest_neighbors' },
{ label: 'lda (线性判别分析)', value: 'lda' },
{ label: 'liblinear_svc (liblinear支持向量分类)', value: 'liblinear_svc' },
{ label: 'libsvm_svc (libsvm支持向量分类)', value: 'libsvm_svc' },
{ label: 'mlp (多层感知器)', value: 'mlp' },
{ label: 'multinomial_nb (多项式朴素贝叶斯)', value: 'multinomial_nb' },
{ label: 'passive_aggressive (被动攻击算法)', value: 'passive_aggressive' },
{ label: 'qda (二次判别式分析)', value: 'qda' },
{ label: 'random_forest (随机森林)', value: 'random_forest' },
{ label: 'sgd (随机梯度下降)', value: 'sgd' },
{ label: 'tablenet (表格网络)', value: 'tablenet' },
{ label: 'LightGBMClassification (轻量梯度提升机分类)', value: 'LightGBMClassification' },
{ label: 'XGBoostClassification (极端梯度提升机分类)', value: 'XGBoostClassification' },
{ label: 'StackingClassification (堆叠泛化)', value: 'StackingClassification' },
];
// 回归算法
export const regressorAlgorithms = [
{ label: 'adaboost (自适应提升算法)', value: 'adaboost' },
{ label: 'ard_regression (自动相关性确定回归)', value: 'ard_regression' },
{ label: 'decision_tree (决策树)', value: 'decision_tree' },
{ label: 'extra_trees (极端随机树)', value: 'extra_trees' },
{ label: 'gaussian_process (高斯过程回归)', value: 'gaussian_process' },
{ label: 'gradient_boosting (梯度提升)', value: 'gradient_boosting' },
{ label: 'k_nearest_neighbors (梯度提升)', value: 'k_nearest_neighbors' },
{ label: 'liblinear_svr (liblinear支持向量回归)', value: 'liblinear_svr' },
{ label: 'libsvm_svr (libsvm支持向量回归)', value: 'libsvm_svr' },
{ label: 'mlp (多层感知器)', value: 'mlp' },
{ label: 'random_forest (随机森林)', value: 'random_forest' },
{ label: 'sgd (随机梯度下降)', value: 'sgd' },
{ label: 'LightGBMRegression (轻量梯度提升机回归)', value: 'LightGBMRegression' },
{ label: 'XGBoostRegression (极端梯度提升机回归)', value: 'XGBoostRegression' },
];
// 特征预处理算法
export const featureAlgorithms = [
{ label: 'densifier (特征变换-数据增稠)', value: 'densifier' },
{
label: 'extra_trees_preproc_for_classification (特征选择-分类任务极端随机树)',
value: 'extra_trees_preproc_for_classification',
},
{
label: 'extra_trees_preproc_for_regression (特征选择-回归任务极端随机树)',
value: 'extra_trees_preproc_for_regression',
},
{ label: 'fast_ica (特征选择-快速独立成分分析)', value: 'fast_ica' },
{ label: 'feature_agglomeration (特征变换-特征聚合)', value: 'feature_agglomeration' },
{ label: 'kernel_pca (特征选择-核主成分分析)', value: 'kernel_pca' },
{ label: 'kitchen_sinks (特征变换-随机特征映射)', value: 'kitchen_sinks' },
{
label: 'liblinear_svc_preprocessor (特征选择-线性svc预处理器)',
value: 'liblinear_svc_preprocessor',
},
{ label: 'miss_value_impute (缺失值填充)', value: 'miss_value_impute' },
{ label: 'no_preprocessing (无预处理)', value: 'no_preprocessing' },
{ label: 'nystroem_sampler (特征变换-尼斯特罗姆采样器)', value: 'nystroem_sampler' },
{ label: 'pca (特征选择-主成分分析)', value: 'pca' },
{ label: 'polynomial (特征变换-多项式特征扩展)', value: 'polynomial' },
{ label: 'random_trees_embedding (特征变换-随机森林特征嵌入)', value: 'random_trees_embedding' },
{
label: 'select_percentile_classification 特征选择-基于百分位的分类特征选择)',
value: 'select_percentile_classification',
},
{
label: 'select_percentile_regression (特征选择-基于百分位的回归特征选择)',
value: 'select_percentile_regression',
},
{
label: 'select_rates_classification (特征选择-基于比率的分类特征选择)',
value: 'select_rates_classification',
},
{
label: 'select_rates_regression (特征选择-基于比率的回归特征选择)',
value: 'select_rates_regression',
},
{ label: 'truncatedSVD (特征变换-截断奇异值分解)', value: 'truncatedSVD' },
];

View File

@ -54,10 +54,6 @@
display: flex;
align-items: center;
width: 200px;
.statusIcon {
visibility: visible;
}
}
}

View File

@ -34,7 +34,14 @@ function ExperimentInstanceList({
}: ExperimentInstanceListProps) {
const { message } = App.useApp();
const allIntanceIds = useMemo(() => {
return experimentInsList?.map((item) => item.id) || [];
return (
experimentInsList
?.filter(
(item) =>
item.status !== ExperimentStatus.Running && item.status !== ExperimentStatus.Pending,
)
.map((item) => item.id) || []
);
}, [experimentInsList]);
const [
selectedIns,
@ -126,7 +133,12 @@ function ExperimentInstanceList({
<div>
<div className={styles.tableExpandBox} style={{ paddingBottom: '16px' }}>
<div className={styles.check}>
<Checkbox checked={checked} indeterminate={indeterminate} onChange={checkAll}></Checkbox>
<Checkbox
checked={checked}
indeterminate={indeterminate}
disabled={allIntanceIds.length === 0}
onChange={checkAll}
></Checkbox>
</div>
<div className={styles.index}></div>
<div className={styles.description}></div>
@ -171,12 +183,8 @@ function ExperimentInstanceList({
{index + 1}
</a>
<ExperimentInstanceComponent
create_time={item.create_time}
finish_time={item.finish_time}
status={item.status as ExperimentStatus}
argo_ins_name={item.argo_ins_name}
argo_ins_ns={item.argo_ins_ns}
experimentInsId={item.id}
experimentId={item[config['idInsProperty'] as keyof ExperimentInstance] as number}
instance={item}
></ExperimentInstanceComponent>
<div className={styles.operation}>
<Button

View File

@ -2,67 +2,70 @@ import RunDuration from '@/components/RunDuration';
import { ExperimentStatus } from '@/enums';
import { useSSE, type MessageHandler } from '@/hooks/useSSE';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import { ExperimentInstance, NodeStatus } from '@/types';
import { ExperimentCompleted } from '@/utils/constant';
import { formatDate } from '@/utils/date';
import { getWorkflowStatus } from '@/utils/experiment';
import { Typography } from 'antd';
import React, { useCallback } from 'react';
import styles from './index.less';
type ExperimentInstanceProps = {
create_time?: string;
finish_time?: string;
status: ExperimentStatus;
argo_ins_name: string;
argo_ins_ns: string;
experimentInsId: number;
type ExperimentInstanceComponentProps = {
experimentId: number;
instance: ExperimentInstance;
};
function ExperimentInstance({
create_time,
finish_time,
status,
argo_ins_name,
argo_ins_ns,
experimentInsId,
}: ExperimentInstanceProps) {
function ExperimentInstanceComponent({ experimentId, instance }: ExperimentInstanceComponentProps) {
const { id, argo_ins_name, argo_ins_ns, node_status } = instance;
const workflowStatus = getWorkflowStatus(node_status) as NodeStatus | undefined;
const status = instance.status as ExperimentStatus;
const createTime = workflowStatus?.startedAt;
const finishTime = workflowStatus?.finishedAt;
const statusInfo = experimentStatusInfo[status];
const handleSSEMessage: MessageHandler = useCallback(
(experimentInsId: number, status: string, finish_time: string) => {
(experimentId: number, experimentInsId: number, status: string, finishTime: string) => {
window.postMessage({
type: ExperimentCompleted,
payload: {
id: experimentInsId,
experimentId,
experimentInsId,
status,
finish_time,
finishTime,
},
});
},
[],
);
useSSE(experimentInsId, status, argo_ins_name, argo_ins_ns, handleSSEMessage);
useSSE(experimentId, id, status, argo_ins_name, argo_ins_ns, handleSSEMessage);
return (
<React.Fragment>
<div className={styles.description}>
<RunDuration createTime={create_time} finishTime={finish_time} />
<RunDuration createTime={createTime} finishTime={finishTime} />
</div>
<div className={styles.startTime}>
<Typography.Text ellipsis={{ tooltip: formatDate(create_time) }}>
{formatDate(create_time)}
<Typography.Text ellipsis={{ tooltip: formatDate(createTime) }}>
{formatDate(createTime)}
</Typography.Text>
</div>
<div className={styles.statusBox}>
<img
style={{ width: '17px', marginRight: '7px' }}
src={experimentStatusInfo[status]?.icon}
draggable={false}
alt=""
/>
<span style={{ color: experimentStatusInfo[status]?.color }} className={styles.statusIcon}>
{experimentStatusInfo[status]?.label}
</span>
{statusInfo ? (
<>
<img
style={{ width: '17px', marginRight: '7px' }}
src={statusInfo.icon}
draggable={false}
alt=""
/>
<span style={{ color: statusInfo.color }}>{statusInfo.label}</span>
</>
) : (
'--'
)}
</div>
</React.Fragment>
);
}
export default ExperimentInstance;
export default ExperimentInstanceComponent;

View File

@ -8,6 +8,7 @@ import {
batchDeleteActiveLearnInsReq,
deleteActiveLearnInsReq,
deleteActiveLearnReq,
editActiveLearnInsReq,
getActiveLearnInsListReq,
getActiveLearnListReq,
runActiveLearnReq,
@ -17,6 +18,7 @@ import {
batchDeleteExperimentInsReq,
deleteAutoMLReq,
deleteExperimentInsReq,
editExperimentInsReq,
getAutoMLListReq,
getExperimentInsListReq,
runAutoMLReq,
@ -26,6 +28,7 @@ import {
batchDeleteRayInsReq,
deleteRayInsReq,
deleteRayReq,
editRayInsReq,
getRayInsListReq,
getRayListReq,
runRayReq,
@ -39,18 +42,20 @@ export enum ExperimentListType {
}
type ExperimentListInfo = {
getListReq: (params: any) => Promise<any>; // 获取列表
getInsListReq: (params: any) => Promise<any>; // 获取实例列表
getListReq: (params: any, skipLoading?: boolean) => Promise<any>; // 获取列表
getInsListReq: (params: any, skipLoading?: boolean) => Promise<any>; // 获取实例列表
deleteRecordReq: (params: any) => Promise<any>; // 删除
runRecordReq: (params: any) => Promise<any>; // 运行
deleteInsReq: (params: any) => Promise<any>; // 删除实例
batchDeleteInsReq: (params: any) => Promise<any>; // 批量删除实例
stopInsReq: (params: any) => Promise<any>; // 终止实例
editInsReq: (params: any) => Promise<any>; // 编辑实例
title: string; // 标题
pathPrefix: string; // 路由路径前缀
idProperty: string; // ID属性
nameProperty: string; // 名称属性
descProperty: string; // 描述属性
idInsProperty: string; // 实例返回的ID属性
};
export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo> = {
@ -62,11 +67,13 @@ export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo
deleteInsReq: deleteExperimentInsReq,
batchDeleteInsReq: batchDeleteExperimentInsReq,
stopInsReq: stopExperimentInsReq,
title: '自主机器学习',
editInsReq: editExperimentInsReq,
title: '自动机器学习',
pathPrefix: 'automl',
nameProperty: 'name',
descProperty: 'description',
idProperty: 'machineLearnId',
idInsProperty: 'machine_learn_id',
},
[ExperimentListType.HyperParameter]: {
getListReq: getRayListReq,
@ -76,11 +83,13 @@ export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo
deleteInsReq: deleteRayInsReq,
batchDeleteInsReq: batchDeleteRayInsReq,
stopInsReq: stopRayInsReq,
editInsReq: editRayInsReq,
title: '超参数自动寻优',
pathPrefix: 'hyperparameter',
nameProperty: 'name',
descProperty: 'description',
idProperty: 'rayId',
idInsProperty: 'ray_id',
},
[ExperimentListType.ActiveLearn]: {
getListReq: getActiveLearnListReq,
@ -90,10 +99,12 @@ export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo
deleteInsReq: deleteActiveLearnInsReq,
batchDeleteInsReq: batchDeleteActiveLearnInsReq,
stopInsReq: stopActiveLearnInsReq,
editInsReq: editActiveLearnInsReq,
title: '自动学习',
pathPrefix: 'active-learn',
nameProperty: 'name',
descProperty: 'description',
idProperty: 'activeLearnId',
idInsProperty: 'active_learn_id',
},
};

View File

@ -30,7 +30,7 @@ import {
} from 'antd';
import { type SearchProps } from 'antd/es/input';
import classNames from 'classnames';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import ExperimentInstanceList from '../ExperimentInstanceList';
import { ExperimentListType, experimentListConfig } from './config';
import styles from './index.less';
@ -52,7 +52,6 @@ function ExperimentList({ type }: ExperimentListProps) {
const [experimentInsList, setExperimentInsList] = useState<ExperimentInstanceData[]>([]);
const [expandedRowKeys, setExpandedRowKeys] = useState<number[]>([]);
const [experimentInsTotal, setExperimentInsTotal] = useState(0);
const [now] = useServerTime();
const [pagination, setPagination] = useState<TablePaginationConfig>(
cacheState?.pagination ?? {
current: 1,
@ -60,38 +59,37 @@ function ExperimentList({ type }: ExperimentListProps) {
},
);
const config = experimentListConfig[type];
const timerRef = useRef<ReturnType<typeof window.setTimeout> | undefined>();
const [now] = useServerTime();
// 获取自主机器学习或超参数自动优化列表
const getAutoMLList = useCallback(async () => {
const params: Record<string, any> = {
page: pagination.current! - 1,
size: pagination.pageSize,
[config.nameProperty]: searchText || undefined,
};
const request = config.getListReq;
const [res] = await to(request(params));
if (res && res.data) {
const { content = [], totalElements = 0 } = res.data;
setTableData(content);
setTotal(totalElements);
}
}, [pagination, searchText, config]);
useEffect(() => {
getAutoMLList();
}, [getAutoMLList]);
// 获取实验列表
const getExperimentList = useCallback(
async (skipLoading: boolean = false) => {
const params: Record<string, any> = {
page: pagination.current! - 1,
size: pagination.pageSize,
[config.nameProperty]: searchText || undefined,
};
const request = config.getListReq;
const [res] = await to(request(params, skipLoading));
if (res && res.data) {
const { content = [], totalElements = 0 } = res.data;
setTableData(content);
setTotal(totalElements);
}
},
[pagination, searchText, config],
);
// 获取实验实例列表
const getExperimentInsList = useCallback(
async (recordId: number, page: number, size: number) => {
async (recordId: number, page: number, size: number, skipLoading: boolean = false) => {
const params = {
[config.idProperty]: recordId,
page: page,
size: size,
};
const request = config.getInsListReq;
const [res] = await to(request(params));
const [res] = await to(request(params, skipLoading));
if (res && res.data) {
const { content = [], totalElements = 0 } = res.data;
try {
@ -111,59 +109,115 @@ function ExperimentList({ type }: ExperimentListProps) {
// 刷新实验列表状态,
// TODO: 目前是直接刷新实验列表,后续需要优化,只刷新状态
const refreshExperimentList = useCallback(() => {
getAutoMLList();
}, [getAutoMLList]);
const refreshExperimentList = useCallback(
(skipLoading: boolean = false) => {
getExperimentList(skipLoading);
},
[getExperimentList],
);
// 刷新实验实例列表
const refreshExperimentIns = useCallback(
(experimentId: number) => {
(experimentId: number, skipLoading: boolean = false) => {
const length = experimentInsList.length;
getExperimentInsList(experimentId, 0, length);
getExperimentInsList(experimentId, 0, length, skipLoading);
},
[getExperimentInsList, experimentInsList],
);
// 新增,删除版本时,重置分页,然后刷新版本列表
// 更新实验实例状态
const editExperimentIns = useCallback(
async (
experimentId: number,
experimentInsId: number,
status: ExperimentStatus,
argo_ins_name: string,
argo_ins_ns: string,
) => {
const params = {
[config.idInsProperty]: experimentId,
id: experimentInsId,
status: status,
argo_ins_name,
argo_ins_ns,
};
const request = config.editInsReq;
const [res] = await to(request(params));
if (res && res.data) {
refreshExperimentIns(experimentId, true);
refreshExperimentList(true);
}
},
[config, refreshExperimentIns, refreshExperimentList],
);
// 获取实验列表
useEffect(() => {
getExperimentList();
}, [getExperimentList]);
// expandedRowKeys 变化
useEffect(() => {
if (expandedRowKeys.length > 0) {
getExperimentInsList(expandedRowKeys[0], 0, 5);
refreshExperimentList();
}
}, [expandedRowKeys, getExperimentInsList, refreshExperimentList]);
// 实验实例状态变化
useEffect(() => {
const handleMessage = (e: MessageEvent) => {
const { type, payload } = e.data;
if (type === ExperimentCompleted) {
const { id, status, finish_time } = payload;
// 修改实例的状态和结束时间
setExperimentInsList((prev) =>
prev.map((v) =>
v.id === id
? {
...v,
status: status,
finish_time: finish_time,
}
: v,
),
const { experimentId, experimentInsId, status, finishTime } = payload;
const currentIns = experimentInsList.find((v) => v.id === experimentInsId);
console.log(
'实验实例状态变化',
currentIns?.status,
status,
experimentId,
experimentInsId,
finishTime,
);
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = undefined;
if (
!currentIns ||
currentIns.status === ExperimentStatus.Terminated ||
currentIns.status === status
) {
return;
}
timerRef.current = setTimeout(() => {
refreshExperimentList();
}, 10000);
// refreshExperimentList(true);
// refreshExperimentIns(experimentId);
editExperimentIns(
experimentId,
experimentInsId,
status,
currentIns.argo_ins_name,
currentIns.argo_ins_ns,
);
// 修改实例的状态和结束时间
// setExperimentInsList((prev) =>
// prev.map((v) =>
// v.id === experimentInsId
// ? {
// ...v,
// status: status,
// finish_time: finishTime,
// }
// : v,
// ),
// );
}
};
window.addEventListener('message', handleMessage);
return () => {
window.removeEventListener('message', handleMessage);
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = undefined;
}
};
}, [refreshExperimentList]);
}, [experimentInsList, editExperimentIns]);
// 搜索
const onSearch: SearchProps['onSearch'] = (value) => {
@ -207,6 +261,7 @@ function ExperimentList({ type }: ExperimentListProps) {
setCacheState({
pagination,
searchText,
expandedRowKeys,
});
if (record) {
@ -225,6 +280,7 @@ function ExperimentList({ type }: ExperimentListProps) {
setCacheState({
pagination,
searchText,
expandedRowKeys,
});
navigate(`info/${record.id}`);
@ -237,8 +293,8 @@ function ExperimentList({ type }: ExperimentListProps) {
if (res) {
message.success('运行成功');
setExpandedRowKeys([record.id]);
refreshExperimentList();
getExperimentInsList(record.id, 0, 5);
// getExperimentInsList(record.id, 0, 5);
// refreshExperimentList();
}
};
@ -248,8 +304,8 @@ function ExperimentList({ type }: ExperimentListProps) {
setExperimentInsList([]);
if (expanded) {
setExpandedRowKeys([record.id]);
getExperimentInsList(record.id, 0, 5);
refreshExperimentList();
// getExperimentInsList(record.id, 0, 5);
// refreshExperimentList();
} else {
setExpandedRowKeys([]);
}
@ -257,6 +313,11 @@ function ExperimentList({ type }: ExperimentListProps) {
// 跳转到实验实例详情
const gotoInstanceInfo = (autoML: AutoMLData, record: ExperimentInstanceData) => {
setCacheState({
pagination,
searchText,
expandedRowKeys,
});
navigate(`instance/${autoML.id}/${record.id}`);
};
@ -269,8 +330,7 @@ function ExperimentList({ type }: ExperimentListProps) {
// 实验实例终止
const handleInstanceTerminate = async (experimentIns: ExperimentInstanceData) => {
// 刷新实验列表
refreshExperimentList();
// 修改实例的状态和结束时间
setExperimentInsList((prevList) => {
return prevList.map((item) => {
if (item.id === experimentIns.id) {
@ -283,6 +343,11 @@ function ExperimentList({ type }: ExperimentListProps) {
return item;
});
});
// 刷新实验列表和实例列表
refreshExperimentList(true);
if (expandedRowKeys.length > 0) {
refreshExperimentIns(expandedRowKeys[0]);
}
};
// --------------------------- Table ---------------------------
// 分页切换
@ -330,7 +395,7 @@ function ExperimentList({ type }: ExperimentListProps) {
},
...diffColumns,
{
title: '创建时间',
title: '更新时间',
dataIndex: 'update_time',
key: 'update_time',
width: '20%',

View File

@ -0,0 +1,7 @@
import styles from './index.less';
function EmptyLog() {
return <div className={styles['empty-log']}></div>;
}
export default EmptyLog;

View File

@ -5,3 +5,14 @@
height: 100%;
}
}
.empty-log {
height: 100%;
padding: 15px;
color: white;
font-size: 14px;
white-space: pre-line;
text-align: center;
word-break: break-all;
background: #19253b;
}

View File

@ -2,6 +2,7 @@ import { ExperimentStatus } from '@/enums';
import { AutoMLInstanceData } from '@/pages/AutoML/types';
import LogList from '@/pages/Experiment/components/LogList';
import { NodeStatus } from '@/types';
import EmptyLog from './empty';
import styles from './index.less';
const NodePrefix = 'auto-ml';
@ -19,7 +20,7 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) {
return (
<div className={styles['experiment-log']}>
<div className={styles['experiment-log__log']}>
{nodeStatus && (
{nodeStatus ? (
<LogList
instanceName={instanceInfo.argo_ins_name}
instanceNamespace={instanceInfo.argo_ins_ns}
@ -28,6 +29,8 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) {
instanceNodeStartTime={nodeStatus.startedAt}
instanceNodeStatus={nodeStatus.phase as ExperimentStatus}
></LogList>
) : (
<EmptyLog />
)}
</div>
</div>

View File

@ -3,59 +3,61 @@ import RunDuration from '@/components/RunDuration';
import { ExperimentStatus } from '@/enums';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import { type NodeStatus } from '@/types';
import { getExperimentInstanceStatus } from '@/utils/experiment';
import { formatDate } from '@/utils/format';
import { Flex } from 'antd';
import { useMemo } from 'react';
type ExperimentRunBasicProps = {
create_time?: string;
runStatus?: NodeStatus;
workflowStatus?: NodeStatus;
instanceStatus?: ExperimentStatus;
};
function ExperimentRunBasic({ create_time, runStatus, instanceStatus }: ExperimentRunBasicProps) {
function ExperimentRunBasic({ workflowStatus, instanceStatus }: ExperimentRunBasicProps) {
const instanceDatas = useMemo(() => {
if (!runStatus) {
return [];
}
const status =
instanceStatus === ExperimentStatus.Terminated ? instanceStatus : runStatus.phase;
const status = getExperimentInstanceStatus(instanceStatus as ExperimentStatus, workflowStatus);
const statusInfo = experimentStatusInfo[status];
return [
{
label: '启动时间',
value: formatDate(create_time),
value: formatDate(workflowStatus?.startedAt),
},
{
label: '执行时长',
value: <RunDuration createTime={create_time} finishTime={runStatus.finishedAt} />,
value: (
<RunDuration
createTime={workflowStatus?.startedAt}
finishTime={workflowStatus?.finishedAt}
/>
),
},
{
label: '状态',
value: (
value: statusInfo ? (
<Flex align="center">
<img
style={{ width: '17px', marginRight: '7px' }}
src={statusInfo?.icon}
src={statusInfo.icon}
draggable={false}
alt=""
/>
<div
style={{
color: statusInfo?.color,
color: statusInfo.color,
fontSize: '15px',
lineHeight: 1.6,
}}
>
{statusInfo?.label}
{statusInfo.label}
</div>
</Flex>
) : (
'--'
),
},
];
}, [runStatus, create_time, instanceStatus]);
}, [workflowStatus, instanceStatus]);
return (
<ConfigInfo

View File

@ -0,0 +1,8 @@
.experiment-visual {
width: 100%;
height: 100%;
&__empty {
height: 100%;
}
}

View File

@ -5,10 +5,15 @@
*/
import IframePage, { IframePageType } from '@/components/IFramePage';
import { runTensorBoardReq } from '@/services/experiment/index.js';
import KFEmpty, { EmptyType } from '@/components/KFEmpty';
import KFSpin from '@/components/KFSpin';
import { TensorBoardStatus } from '@/enums';
import { getTensorBoardStatusReq, runTensorBoardReq } from '@/services/experiment/index.js';
import { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage';
import { useEffect, useState } from 'react';
import { LoadingOutlined } from '@ant-design/icons';
import { useCallback, useEffect, useState } from 'react';
import styles from './index.less';
type TensorBoardProps = {
namespace?: string;
@ -16,28 +21,75 @@ type TensorBoardProps = {
};
function ExperimentVisualResult({ namespace, path }: TensorBoardProps) {
const [tensorboardUrl, setTensorboardUrl] = useState('');
useEffect(() => {
// 运行 TensorBoard
const runTensorBoard = async () => {
const params = {
namespace: namespace,
path: path,
};
const [res] = await to(runTensorBoardReq(params));
if (res && res.data) {
const url = res.data;
SessionStorage.setItem(SessionStorage.tensorBoardUrlKey, url);
setTensorboardUrl(url);
}
};
const [tensorboardUrl, setTensorboardUrl] = useState<string | undefined | null>(undefined);
const [status, setStatus] = useState<TensorBoardStatus | undefined>(undefined);
if (namespace && path) {
runTensorBoard();
// 获取 TensorBoard 状态
const getTensorBoardStatus = useCallback(async () => {
const params = {
namespace: namespace,
path: path,
};
const [res] = await to(getTensorBoardStatusReq(params));
if (res && res.data) {
const status = res.data.status as TensorBoardStatus | undefined;
setStatus(res.data.status);
if (!status || status === TensorBoardStatus.Pending) {
setTimeout(() => {
getTensorBoardStatus();
}, 5000);
}
}
}, [namespace, path]);
return <>{tensorboardUrl && <IframePage type={IframePageType.TensorBoard}></IframePage>}</>;
// 运行 TensorBoard
const runTensorBoard = useCallback(async () => {
const params = {
namespace: namespace,
path: path,
};
const [res] = await to(runTensorBoardReq(params));
if (res && res.data) {
const url = res.data;
SessionStorage.setItem(SessionStorage.tensorBoardUrlKey, url);
setTensorboardUrl(url);
getTensorBoardStatus();
} else {
setTensorboardUrl(null);
}
}, [namespace, path, getTensorBoardStatus]);
useEffect(() => {
if (namespace && path) {
runTensorBoard();
}
}, [namespace, path, runTensorBoard]);
if (tensorboardUrl === null || status === TensorBoardStatus.Failed) {
return (
<div className={styles['experiment-visual']}>
<KFEmpty
className={styles['experiment-visual__empty']}
type={EmptyType.NoData}
title="运行可视化失败"
buttonTitle="重新运行"
onButtonClick={runTensorBoard}
/>
</div>
);
} else if (status === TensorBoardStatus.Pending) {
return (
<div className={styles['experiment-visual']}>
<KFSpin indicator={<LoadingOutlined spin />} size="large" />
</div>
);
} else if (status === TensorBoardStatus.Running) {
return (
<div className={styles['experiment-visual']}>
<IframePage type={IframePageType.TensorBoard}></IframePage>
</div>
);
}
}
export default ExperimentVisualResult;

View File

@ -118,12 +118,14 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr
},
{
pattern: /^[a-zA-Z0-9._-]+$/,
message: '版本只支持字母、数字、点(.)、下划线(_)、中横线(-)',
message: '数据集版本只支持字母、数字、点(.)、下划线(_)、中横线(-)',
},
{
validator: (_rule, value) => {
if (value === 'master') {
return Promise.reject(`版本不能为 master`);
return Promise.reject(`数据集版本不能为 master`);
} else if (value === 'origin') {
return Promise.reject(`数据集版本不能为 origin`);
}
return Promise.resolve();
},

View File

@ -109,12 +109,14 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps)
},
{
pattern: /^[a-zA-Z0-9._-]+$/,
message: '版本只支持字母、数字、点(.)、下划线(_)、中横线(-)',
message: '模型版本只支持字母、数字、点(.)、下划线(_)、中横线(-)',
},
{
validator: (_rule, value) => {
if (value === 'master') {
return Promise.reject(`版本不能为 master`);
return Promise.reject(`模型版本不能为 master`);
} else if (value === 'origin') {
return Promise.reject(`模型版本不能为 origin`);
}
return Promise.resolve();
},
@ -126,7 +128,7 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps)
<Form.Item label="模型框架" name="model_type">
<Select
allowClear
placeholder="请选择模型类型"
placeholder="请选择模型框架"
options={typeList}
fieldNames={{ label: 'name', value: 'name' }}
optionFilterProp="name"
@ -136,7 +138,7 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps)
<Form.Item label="模型能力" name="model_tag">
<Select
allowClear
placeholder="请选择模型标签"
placeholder="请选择模型能力"
options={tagList}
fieldNames={{ label: 'name', value: 'name' }}
optionFilterProp="name"

View File

@ -132,12 +132,14 @@ function AddVersionModal({
},
{
pattern: /^[a-zA-Z0-9._-]+$/,
message: '版本只支持字母、数字、点(.)、下划线(_)、中横线(-)',
message: `${name}版本只支持字母、数字、点(.)、下划线(_)、中横线(-)`,
},
{
validator: (_rule, value) => {
if (value === 'master') {
return Promise.reject(`版本不能为 master`);
return Promise.reject(`${name}版本不能为 master`);
} else if (value === 'origin') {
return Promise.reject(`${name}版本不能为 origin`);
}
return Promise.resolve();
},

View File

@ -0,0 +1,121 @@
import KFModal from '@/components/KFModal';
import { DataSource, ResourceData, ResourceType, resourceConfig } from '@/pages/Dataset/config';
import { to } from '@/utils/promise';
import { Form, Input, message, type ModalProps } from 'antd';
interface EditVersionModalProps extends Omit<ModalProps, 'onOk'> {
resourceType: ResourceType;
resourceVersion: ResourceData;
onOk: () => void;
}
function EditVersionModal({ resourceType, resourceVersion, onOk, ...rest }: EditVersionModalProps) {
const config = resourceConfig[resourceType];
const { name: resoureName, version, version_desc } = resourceVersion;
// 修改请求
const editDatasetVersion = async (params: any) => {
const request = config.editVersion;
const [res] = await to(request(params));
if (res) {
message.success('编辑成功');
onOk?.();
}
};
// 提交
const onFinish = (formData: any) => {
const params = {
...resourceVersion,
...formData,
[config.sourceParamKey]: DataSource.Create,
};
editDatasetVersion(params);
};
const name = config.name;
return (
<KFModal
{...rest}
title="编辑版本"
image={require('@/assets/img/create-experiment.png')}
width={825}
okButtonProps={{
htmlType: 'submit',
form: 'form',
}}
>
<Form
name="form"
layout="vertical"
initialValues={{
name: resoureName,
version: version,
version_desc: version_desc,
}}
onFinish={onFinish}
autoComplete="off"
>
<Form.Item
label={`${name}名称`}
name="name"
rules={[
{
required: true,
message: `请输入${name}名称`,
},
]}
>
<Input disabled placeholder={`请输入${name}名称`} />
</Form.Item>
<Form.Item
label={`${name}版本`}
name="version"
rules={[
{
required: true,
message: `请输入${name}版本`,
},
{
pattern: /^[a-zA-Z0-9._-]+$/,
message: `${name}版本只支持字母、数字、点(.)、下划线(_)、中横线(-)`,
},
{
validator: (_rule, value) => {
if (value === 'master') {
return Promise.reject(`${name}版本不能为 master`);
} else if (value === 'origin') {
return Promise.reject(`${name}版本不能为 origin`);
}
return Promise.resolve();
},
},
]}
>
<Input placeholder={`请输入${name}版本`} maxLength={64} showCount allowClear disabled />
</Form.Item>
<Form.Item
label="版本描述"
name="version_desc"
rules={[
{
required: true,
message: '请输入版本描述',
},
]}
>
<Input.TextArea
placeholder="请输入版本描述"
autoSize={{ minRows: 2, maxRows: 6 }}
maxLength={200}
showCount
allowClear
/>
</Form.Item>
</Form>
</KFModal>
);
}
export default EditVersionModal;

View File

@ -42,6 +42,10 @@
border-radius: 4px;
cursor: pointer;
&:hover {
border-color: .addAlpha(@primary-color, 0.5) [];
}
&--praised {
color: @primary-color;
}

View File

@ -23,6 +23,7 @@ import { App, Button, Flex, Select, Tabs } from 'antd';
import classNames from 'classnames';
import { useCallback, useEffect, useState } from 'react';
import AddVersionModal from '../AddVersionModal';
import EditVersionModal from '../EditVersionModal';
import ResourceIntro from '../ResourceIntro';
import ResourceVersion from '../ResourceVersion';
import VersionCompareModal from '../VersionCompareModal';
@ -132,6 +133,18 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => {
});
};
// 版本编辑
const showEditVersionModal = () => {
const { close } = openAntdModal(EditVersionModal, {
resourceType: resourceType,
resourceVersion: info,
onOk: () => {
getResourceDetail();
close();
},
});
};
// 选择版本
const showVersionSelector = () => {
const { close } = openAntdModal(VersionSelectorModal, {
@ -291,6 +304,14 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => {
<Button type="default" onClick={showModal} icon={<KFIcon type="icon-xinjian2" />}>
</Button>
<Button
type="default"
style={{ marginLeft: '20px' }}
icon={<KFIcon type="icon-bianji" />}
onClick={showEditVersionModal}
>
</Button>
<Button
type="default"
style={{ marginLeft: '20px' }}

View File

@ -14,9 +14,7 @@ type ResourceItemProps = {
};
function ResourceItem({ item, isPublic, onClick, onRemove }: ResourceItemProps) {
const timeAgo = `更新于${
item.update_time ? formatDate(item.update_time, 'YYYY-MM-DD') : item.time_ago ?? ''
}`;
const timeAgo = `最近更新:${formatDate(item.full_last_update_time, 'YYYY-MM-DD HH:mm')}`;
const create_by = item.create_by ?? '';
return (
<div className={styles['resource-item']} onClick={() => onClick(item)}>

View File

@ -16,6 +16,7 @@ import styles from './index.less';
export type ResourceListRef = {
reset: () => void;
resetPage: () => void;
};
type ResourceListProps = {
@ -97,6 +98,12 @@ function ResourceList(
setDataList(undefined);
setTotal(0);
},
resetPage: () => {
setPagination((prev) => ({
...prev,
current: 1,
}));
},
};
},
[],

View File

@ -56,11 +56,13 @@ function ResourcePage({ resourceType }: ResourcePageProps) {
// 选择类型
const chooseType = (record: CategoryData) => {
dataListRef.current?.resetPage();
setActiveType((prev) => (prev === record.name ? undefined : record.name));
};
// 选择 Tag
const chooseTag = (record: CategoryData) => {
dataListRef.current?.resetPage();
setActiveTag((prev) => (prev === record.name ? undefined : record.name));
};
@ -96,7 +98,7 @@ function ResourcePage({ resourceType }: ResourcePageProps) {
dataType={activeType}
dataTag={activeTag}
initialSearchText={cacheState?.searchText}
initialPagination={cacheState?.initialPagination}
initialPagination={cacheState?.pagination}
setCacheState={setCacheState}
></ResourceList>
</Flex>

View File

@ -9,6 +9,8 @@ import {
deleteDatasetVersion,
deleteModel,
deleteModelVersion,
editDatasetVersion,
editModelVersion,
getDatasetInfo,
getDatasetList,
getDatasetVersionList,
@ -36,6 +38,7 @@ type ResourceTypeInfo = {
getVersions: (params: any) => Promise<any>; // 获取版本列表
deleteRecord: (params: any) => Promise<any>; // 删除
addVersion: (params: any) => Promise<any>; // 新增版本
editVersion: (params: any) => Promise<any>; // 编辑版本
deleteVersion: (params: any) => Promise<any>; // 删除版本
getInfo: (params: any) => Promise<any>; // 获取详情
compareVersion: (params: any) => Promise<any>; // 版本对比
@ -65,6 +68,7 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = {
getVersions: getDatasetVersionList,
deleteRecord: deleteDataset,
addVersion: addDatasetVersion,
editVersion: editDatasetVersion,
deleteVersion: deleteDatasetVersion,
getInfo: getDatasetInfo,
compareVersion: compareDatasetVersion,
@ -103,6 +107,7 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = {
getVersions: getModelVersionList,
deleteRecord: deleteModel,
addVersion: addModelVersion,
editVersion: editModelVersion,
deleteVersion: deleteModelVersion,
getInfo: getModelInfo,
compareVersion: compareModelVersion,
@ -164,6 +169,7 @@ export interface ResourceData {
train_task?: TrainTask; // 训练任务
praises_count: number; // 点赞数
praised: boolean; // 是否点赞
full_last_update_time: string; // 完整的更新时间
}
// 数据集数据

View File

@ -2,7 +2,7 @@ const Docs = () => {
return (
<iframe
style={{ width: '100%', height: '100%', border: 0 }}
src={'/assets/材料科研软件平台使用文档.pdf'}
src={'/assets/材料科研软件平台使用文档-v1.0.pdf'}
></iframe>
);
};

View File

@ -77,7 +77,7 @@ function ExperimentComparison() {
const url = res.data;
// window.open(url, '_blank');
SessionStorage.setItem(SessionStorage.aimUrlKey, url);
navigate('../compare-visual');
navigate('compare-visual');
}
};

View File

@ -3,10 +3,10 @@ import { ExperimentStatus } from '@/enums';
import { useStateRef } from '@/hooks/useStateRef';
import { useVisible } from '@/hooks/useVisible';
import { getExperimentIns } from '@/services/experiment/index.js';
import { getWorkflowById } from '@/services/pipeline/index.js';
import themes from '@/styles/theme.less';
import { fittingString, parseJsonText } from '@/utils';
import { formatDate } from '@/utils/date';
import { getExperimentInstanceStatus } from '@/utils/experiment';
import { to } from '@/utils/promise';
import G6, { Util } from '@antv/g6';
import { Button } from 'antd';
@ -18,10 +18,12 @@ import { experimentStatusInfo } from '../status';
import styles from './index.less';
let graph = null;
const NodePrefix = 'workflow';
function ExperimentText() {
const [experimentIns, setExperimentIns] = useState(undefined);
const [experimentNodeData, setExperimentNodeData, experimentNodeDataRef] = useStateRef(undefined);
const [workflowStatus, setWorkflowStatus] = useState(undefined);
const graphRef = useRef();
const workflowRef = useRef();
const locationParams = useParams(); //
@ -32,10 +34,12 @@ function ExperimentText() {
const evtSourceRef = useRef();
const width = 110;
const height = 36;
const status = getExperimentInstanceStatus(experimentIns?.status, workflowStatus);
const statusInfo = experimentStatusInfo[status];
useEffect(() => {
initGraph();
getWorkflow();
getExperimentInstance();
return () => {
if (evtSourceRef.current) {
@ -61,54 +65,83 @@ function ExperimentText() {
}, []);
// 线
const getWorkflow = async () => {
const [res] = await to(getWorkflowById(locationParams.workflowId));
if (res && res.data && res.data.dag) {
try {
const dag = JSON.parse(res.data.dag);
dag.nodes.forEach((item) => {
item.in_parameters = JSON.parse(item.in_parameters);
item.out_parameters = JSON.parse(item.out_parameters);
item.control_strategy = JSON.parse(item.control_strategy);
item.imgName = item.img.slice(0, item.img.length - 4);
});
workflowRef.current = dag;
getExperimentInstance();
} catch (error) {
// JSON.parse
console.error('JSON.parse error: ', error);
}
}
};
// const getWorkflow = async () => {
// const [res] = await to(getWorkflowById(locationParams.workflowId));
// if (res && res.data && res.data.dag) {
// try {
// const dag = JSON.parse(res.data.dag);
// dag.nodes.forEach((item) => {
// item.in_parameters = JSON.parse(item.in_parameters);
// item.out_parameters = JSON.parse(item.out_parameters);
// item.control_strategy = JSON.parse(item.control_strategy);
// item.imgName = item.img.slice(0, item.img.length - 4);
// });
// workflowRef.current = dag;
// getExperimentInstance();
// } catch (error) {
// // JSON.parse
// console.error('JSON.parse error: ', error);
// }
// }
// };
//
const getExperimentInstance = async () => {
const [res] = await to(getExperimentIns(locationParams.id));
if (res && res.data && workflowRef.current) {
if (res && res.data) {
setExperimentIns(res.data);
const { status, nodes_status, argo_ins_ns, argo_ins_name, finish_time } = res.data;
const workflowData = workflowRef.current;
const { status, nodes_status, argo_ins_ns, argo_ins_name, finish_time, dag } = res.data;
if (!dag) {
return;
}
const workflow = parseJsonText(dag);
const experimentStatusObjs = parseJsonText(nodes_status);
workflowData.nodes.forEach((item) => {
const experimentNode = experimentStatusObjs?.[item.id];
updateWorkflowNode(item, experimentNode);
if (!workflow || !workflow.nodes) {
return;
}
workflow.nodes.forEach((item) => {
item.in_parameters = parseJsonText(item.in_parameters);
item.out_parameters = parseJsonText(item.out_parameters);
item.control_strategy = parseJsonText(item.control_strategy);
item.imgName = item.img.slice(0, item.img.length - 4);
});
workflowRef.current = workflow;
if (experimentStatusObjs) {
//
workflow.nodes.forEach((item) => {
const experimentNode = experimentStatusObjs[item.id];
updateWorkflowNode(item, experimentNode);
});
// workflow
Object.keys(experimentStatusObjs).some((key) => {
if (key.startsWith(NodePrefix)) {
const tempWorkflowStatus = experimentStatusObjs[key];
setWorkflowStatus(tempWorkflowStatus);
return true;
}
return false;
});
}
//
getGraphData(workflowData, true);
getGraphData(workflow, true);
if (status === ExperimentStatus.Pending) {
// Pending,
const node = workflowData.nodes[0];
const node = workflow.nodes[0];
if (node) {
setExperimentNodeData(node);
openPropsDrawer();
}
} else if (status === ExperimentStatus.Running) {
// Running
// Running Running pending
const node =
workflowData.nodes.find((item) => item.experimentStatus === ExperimentStatus.Running) ??
workflowData.nodes[0];
workflow.nodes.find((item) => item.experimentStatus === ExperimentStatus.Running || item.experimentStatus === ExperimentStatus.Pending) ??
workflow.nodes[0];
if (node) {
setExperimentNodeData(node);
openPropsDrawer();
@ -135,23 +168,36 @@ function ExperimentText() {
return;
}
try {
const dataJson = JSON.parse(data);
const dataJson = parseJsonText(data);
const statusData = dataJson?.result?.object?.status;
if (!statusData) {
return;
}
const { startedAt, finishedAt, phase, nodes = {} } = statusData;
setExperimentIns((prev) => ({
...prev,
finish_time: finishedAt,
status: phase,
}));
const { finishedAt, phase, nodes = {} } = statusData;
//
// setExperimentIns((prev) => ({
// ...prev,
// finish_time: finishedAt,
// status: phase,
// }));
// workflow
const tempWorkflowStatus = Object.values(nodes).find((node) =>
node.displayName.startsWith(NodePrefix),
);
if (tempWorkflowStatus) {
setWorkflowStatus(tempWorkflowStatus);
}
//
const workflowData = workflowRef.current;
workflowData.nodes.forEach((item) => {
const experimentNode = Object.values(nodes).find((node) => node.displayName === item.id);
updateWorkflowNode(item, experimentNode);
});
//
getGraphData(workflowData, false);
//
@ -177,6 +223,7 @@ function ExperimentText() {
evtSourceRef.current = evtSource;
};
//
function updateWorkflowNode(workflowNode, statusNode) {
if (!statusNode) {
return;
@ -471,29 +518,30 @@ function ExperimentText() {
<div className={styles['pipeline-container']}>
<div className={styles['pipeline-container__top']}>
<div className={styles['pipeline-container__top__info']}>
启动时间{formatDate(experimentIns?.create_time)}
启动时间{formatDate(workflowStatus?.startedAt)}
</div>
<div className={styles['pipeline-container__top__info']}>
执行时长
<RunDuration
createTime={experimentIns?.create_time}
finishTime={experimentIns?.finish_time}
createTime={workflowStatus?.startedAt}
finishTime={workflowStatus?.finishedAt}
/>
</div>
<div className={styles['pipeline-container__top__info']}>
状态
<div
style={{
width: '8px',
height: '8px',
borderRadius: '50%',
marginRight: '6px',
backgroundColor: experimentStatusInfo[experimentIns?.status]?.color,
}}
></div>
<span style={{ color: experimentStatusInfo[experimentIns?.status]?.color }}>
{experimentStatusInfo[experimentIns?.status]?.label}
</span>
{statusInfo ? (
<>
<img
style={{ width: '17px', marginRight: '7px' }}
src={statusInfo.icon}
draggable={false}
alt=""
/>
<span style={{ color: statusInfo.color }}>{statusInfo.label}</span>
</>
) : (
'--'
)}
</div>
<Button
className={styles['pipeline-container__top__param-button']}

View File

@ -0,0 +1,12 @@
/*
* @Author:
* @Date: 2025-03-31 16:38:59
* @Description: Tensorboard
*/
import IframePage, { IframePageType } from '@/components/IFramePage';
function TensorboardPage() {
return <IframePage type={IframePageType.TensorBoard}></IframePage>;
}
export default TensorboardPage;

View File

@ -30,7 +30,7 @@ interface Workflow {
}
// 根据参数设置输入组件
export const getParamComponent = (paramType: number, isSensitive?: number): JSX.Element => {
export const getParamComponent = (paramType: number): JSX.Element => {
// 防止后台返回不是 number 类型
if (Number(paramType) === 3) {
return (
@ -40,9 +40,9 @@ export const getParamComponent = (paramType: number, isSensitive?: number): JSX.
</Radio.Group>
);
}
if (isSensitive && Number(isSensitive) === 1) {
return <Input.Password placeholder="请输入值" visibilityToggle={false} allowClear />;
}
// if (isSensitive && Number(isSensitive) === 1) {
// return <Input.Password placeholder="请输入值" visibilityToggle={false} allowClear />;
// }
return <Input placeholder="请输入值" allowClear />;
};
@ -95,8 +95,8 @@ function AddExperimentModal({
};
const layout = {
labelCol: { span: 4 },
wrapperCol: { span: 20 },
labelCol: { span: 5 },
wrapperCol: { span: 19 },
};
const paramLayout = {
@ -181,13 +181,13 @@ function AddExperimentModal({
/>
</Form.Item>
<Form.Item
label="选择流水线"
label="选择流水线模板"
name="workflow_id"
rules={[{ required: true, message: '请选择流水线' }]}
rules={[{ required: true, message: '请选择流水线模板' }]}
>
<Select
disabled={workflowDisabled}
placeholder="请选择流水线"
placeholder="请选择流水线模板"
onChange={handleWorkflowChange}
>
{Array.isArray(workflowList)
@ -202,11 +202,7 @@ function AddExperimentModal({
</Select>
</Form.Item>
{globalParam.length > 0 && (
<Form.Item
label="运行参数"
tooltip="展示关联的流水线的参数脱敏的参数以xxxx展示"
{...tailLayout}
>
<Form.Item label="运行参数" tooltip="展示关联的流水线的参数" {...tailLayout}>
<div className={styles.global_param_item}>
<Form.List name="global_param">
{(fields) =>
@ -219,10 +215,7 @@ function AddExperimentModal({
name={[name, 'param_value']}
rules={getParamRules(globalParam[name]['param_type'], true)}
>
{getParamComponent(
globalParam[name]['param_type'],
globalParam[name]['is_sensitive'],
)}
{getParamComponent(globalParam[name]['param_type'])}
</Form.Item>
))
}

View File

@ -55,10 +55,6 @@
display: flex;
align-items: center;
width: 160px;
.statusIcon {
visibility: visible;
}
}
}

View File

@ -39,7 +39,14 @@ function ExperimentInstanceList({
}: ExperimentInstanceListProps) {
const { message } = App.useApp();
const allIntanceIds = useMemo(() => {
return experimentInsList?.map((item) => item.id) || [];
return (
experimentInsList
?.filter(
(item) =>
item.status !== ExperimentStatus.Running && item.status !== ExperimentStatus.Pending,
)
.map((item) => item.id) || []
);
}, [experimentInsList]);
const [
selectedIns,
@ -127,7 +134,12 @@ function ExperimentInstanceList({
<div>
<div className={styles.tableExpandBox} style={{ paddingBottom: '16px' }}>
<div className={styles.check}>
<Checkbox checked={checked} indeterminate={indeterminate} onChange={checkAll}></Checkbox>
<Checkbox
checked={checked}
indeterminate={indeterminate}
disabled={allIntanceIds.length === 0}
onChange={checkAll}
></Checkbox>
</div>
<div className={styles.index}></div>
<div className={styles.tensorBoard}></div>
@ -185,14 +197,7 @@ function ExperimentInstanceList({
)}
</div>
<ExperimentInstanceComponent
create_time={item.create_time}
finish_time={item.finish_time}
status={item.status as ExperimentStatus}
argo_ins_name={item.argo_ins_name}
argo_ins_ns={item.argo_ins_ns}
experimentInsId={item.id}
></ExperimentInstanceComponent>
<ExperimentInstanceComponent instance={item}></ExperimentInstanceComponent>
<div className={styles.operation}>
<Button

View File

@ -2,69 +2,72 @@ import RunDuration from '@/components/RunDuration';
import { ExperimentStatus } from '@/enums';
import { useSSE, type MessageHandler } from '@/hooks/useSSE';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import { ExperimentInstance, NodeStatus } from '@/types';
import { ExperimentCompleted } from '@/utils/constant';
import { formatDate } from '@/utils/date';
import { getWorkflowStatus } from '@/utils/experiment';
import { Typography } from 'antd';
import React, { useCallback } from 'react';
import styles from './index.less';
type ExperimentInstanceProps = {
create_time?: string;
finish_time?: string;
status: ExperimentStatus;
argo_ins_name: string;
argo_ins_ns: string;
experimentInsId: number;
type ExperimentInstanceComponentProps = {
instance: ExperimentInstance;
};
function ExperimentInstance({
create_time,
finish_time,
status,
argo_ins_name,
argo_ins_ns,
experimentInsId,
}: ExperimentInstanceProps) {
function ExperimentInstanceComponent({ instance }: ExperimentInstanceComponentProps) {
const { id, experiment_id, argo_ins_name, argo_ins_ns, nodes_status, create_time, finish_time } =
instance;
const workflowStatus = getWorkflowStatus(nodes_status) as NodeStatus | undefined;
const status = instance.status as ExperimentStatus;
const createTime = workflowStatus?.startedAt ?? create_time;
const finishTime = workflowStatus?.finishedAt ?? finish_time;
const statusInfo = experimentStatusInfo[status];
const handleSSEMessage: MessageHandler = useCallback(
(experimentInsId: number, status: string, finish_time: string) => {
(experimentId: number, experimentInsId: number, status: string, finishTime: string) => {
window.postMessage({
type: ExperimentCompleted,
payload: {
id: experimentInsId,
experimentId,
experimentInsId,
status,
finish_time,
finishTime,
},
});
},
[],
);
useSSE(experimentInsId, status, argo_ins_name, argo_ins_ns, handleSSEMessage);
useSSE(experiment_id, id, status, argo_ins_name, argo_ins_ns, handleSSEMessage);
return (
<React.Fragment>
<div className={styles.description}>
<div style={{ width: '50%' }}>
<RunDuration createTime={create_time} finishTime={finish_time} />
<RunDuration createTime={createTime} finishTime={finishTime} />
</div>
<div style={{ width: '50%' }} className={styles.startTime}>
<Typography.Text ellipsis={{ tooltip: formatDate(create_time) }}>
{formatDate(create_time)}
<Typography.Text ellipsis={{ tooltip: formatDate(createTime) }}>
{formatDate(createTime)}
</Typography.Text>
</div>
</div>
<div className={styles.statusBox}>
<img
style={{ width: '17px', marginRight: '7px' }}
src={experimentStatusInfo[status]?.icon}
draggable={false}
alt=""
/>
<span style={{ color: experimentStatusInfo[status]?.color }} className={styles.statusIcon}>
{experimentStatusInfo[status]?.label}
</span>
{statusInfo ? (
<>
<img
style={{ width: '17px', marginRight: '7px' }}
src={statusInfo.icon}
draggable={false}
alt=""
/>
<span style={{ color: statusInfo.color }}>{statusInfo.label}</span>
</>
) : (
'--'
)}
</div>
</React.Fragment>
);
}
export default ExperimentInstance;
export default ExperimentInstanceComponent;

View File

@ -11,9 +11,15 @@ type ExperimentParameterProps = {
function ExperimentParameter({ nodeData }: ExperimentParameterProps) {
// 控制策略
const controlStrategyList = Object.entries(nodeData.control_strategy ?? {}).map(
([key, value]) => ({ key, value }),
);
// const controlStrategyList = Object.entries(nodeData.control_strategy ?? {}).map(
// ([key, value]) => ({ key, value }),
// );
const nodeId = nodeData.id;
const hasTaskInfo =
nodeId &&
!nodeId.startsWith('git-clone') &&
!nodeId.startsWith('dataset-export') &&
!nodeId.startsWith('model-export');
// 输入参数
const inParametersList = Object.entries(nodeData.in_parameters ?? {}).map(([key, value]) => ({
@ -74,54 +80,58 @@ function ExperimentParameter({ nodeData }: ExperimentParameterProps) {
>
<FormInfo />
</Form.Item>
<div className={styles['experiment-parameter__title']}>
<SubAreaTitle
image={require('@/assets/img/duty-message.png')}
title="任务信息"
></SubAreaTitle>
</div>
<Form.Item
label="镜像"
name="image"
rules={[
{
required: true,
message: '请输入镜像',
},
]}
>
<FormInfo />
</Form.Item>
<Form.Item label="工作目录" name="working_directory">
<FormInfo />
</Form.Item>
{hasTaskInfo && (
<>
<div className={styles['experiment-parameter__title']}>
<SubAreaTitle
image={require('@/assets/img/duty-message.png')}
title="任务信息"
></SubAreaTitle>
</div>
<Form.Item
label="镜像"
name="image"
rules={[
{
required: true,
message: '请输入镜像',
},
]}
>
<FormInfo />
</Form.Item>
<Form.Item label="工作目录" name="working_directory">
<FormInfo />
</Form.Item>
<Form.Item label="启动命令" name="command">
<FormInfo textArea />
</Form.Item>
<Form.Item
label="资源规格"
name="resources_standard"
rules={[
{
required: true,
message: '请输入资源规格',
},
]}
>
<ParameterSelect dataType="resource" placeholder="请选择资源规格" display />
</Form.Item>
<Form.Item label="挂载路径" name="mount_path">
<FormInfo />
</Form.Item>
<Form.Item label="环境变量" name="env_variables">
<FormInfo textArea />
</Form.Item>
{controlStrategyList.map((item) => (
<Form.Item key={item.key} name={['control_strategy', item.key]} label={item.value.label}>
<FormInfo valuePropName="showValue" />
</Form.Item>
))}
<Form.Item label="启动命令" name="command">
<FormInfo textArea />
</Form.Item>
<Form.Item
label="资源规格"
name="resources_standard"
rules={[
{
required: true,
message: '请输入资源规格',
},
]}
>
<ParameterSelect dataType="resource" placeholder="请选择资源规格" display />
</Form.Item>
{/* <Form.Item label="" name="mount_path">
<FormInfo />
</Form.Item> */}
<Form.Item label="环境变量" name="env_variables">
<FormInfo textArea />
</Form.Item>
{/* {controlStrategyList.map((item) => (
<Form.Item key={item.key} name={['control_strategy', item.key]} label={item.value.label}>
<FormInfo valuePropName="showValue" />
</Form.Item>
))} */}
</>
)}
<div className={styles['experiment-parameter__title']}>
<SubAreaTitle
image={require('@/assets/img/duty-message.png')}

View File

@ -121,6 +121,8 @@ function ExportModelModal({
const params = {
...formData,
identifier: resource?.identifier,
owner: resource?.owner,
is_public: resource?.is_public,
name: resource?.name,
[config.sourceParamKey]: DataSource.HandExport,
train_task: {
@ -174,6 +176,8 @@ function ExportModelModal({
onChange={handleResourceChange}
options={resources}
fieldNames={{ label: 'name', value: 'id' }}
optionFilterProp="name"
showSearch
allowClear
></Select>
</Form.Item>
@ -191,9 +195,17 @@ function ExportModelModal({
}
rules={[
{ required: true, message: `请输入${config.name}版本` },
{
pattern: /^[a-zA-Z0-9._-]+$/,
message: `${config.name}版本只支持字母、数字、点(.)、下划线(_)、中横线(-)`,
},
{
validator: (_, value) => {
if (value && versions.map((item) => item.name).includes(value)) {
if (value === 'master') {
return Promise.reject(`${config.name}版本不能为 master`);
} else if (value === 'origin') {
return Promise.reject(`${config.name}版本不能为 origin`);
} else if (value && versions.map((item) => item.name).includes(value)) {
return Promise.reject(`${config.name}版本已存在`);
} else {
return Promise.resolve();

View File

@ -54,7 +54,10 @@ function LogGroup({
useEffect(() => {
// 建立 socket 连接
const setupSockect = () => {
const { host } = location;
let { host } = location;
if (process.env.NODE_ENV === 'development') {
host = '172.20.32.235:31213';
}
const socket = new WebSocket(
`ws://${host}/newlog/realtimeLog?start=${start_time}&query={pod="${pod_name}"}`,
);

View File

@ -47,10 +47,7 @@ function ParamsModal({ open, onCancel, globalParam = [] }: ParamsModalProps) {
name={[name, 'param_value']}
label={getParamLabel(globalParam[name])}
>
{getParamComponent(
globalParam[name]['param_type'],
globalParam[name]['is_sensitive'],
)}
{getParamComponent(globalParam[name]['param_type'])}
</Form.Item>
))
}

View File

@ -2,8 +2,10 @@ import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle';
import { ExperimentStatus, TensorBoardStatus } from '@/enums';
import { useCacheState } from '@/hooks/useCacheState';
import { useServerTime } from '@/hooks/useServerTime';
import {
deleteExperimentById,
editExperimentInsReq,
getExperiment,
getExperimentById,
getQueryByExperimentId,
@ -17,6 +19,7 @@ import { getWorkflow } from '@/services/pipeline/index.js';
import themes from '@/styles/theme.less';
import { ExperimentCompleted } from '@/utils/constant';
import { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage';
import tableCellRender, { TableCellValueType } from '@/utils/table';
import { modalConfirm } from '@/utils/ui';
import { App, Button, ConfigProvider, Dropdown, Input, Space, Table, Tooltip } from 'antd';
@ -28,7 +31,6 @@ import AddExperimentModal from './components/AddExperimentModal';
import ExperimentInstanceList from './components/ExperimentInstanceList';
import styles from './index.less';
import { experimentStatusInfo } from './status';
import { useServerTime } from '@/hooks/useServerTime';
//
const timerIds = new Map();
@ -39,7 +41,7 @@ function Experiment() {
const [workflowList, setWorkflowList] = useState([]);
const [experimentId, setExperimentId] = useState(null);
const [experimentInsList, setExperimentInsList] = useState([]);
const [expandedRowKeys, setExpandedRowKeys] = useState(null);
const [expandedRowKeys, setExpandedRowKeys] = useState([]);
const [total, setTotal] = useState(0);
const [isAdd, setIsAdd] = useState(true);
const [isModalOpen, setIsModalOpen] = useState(false);
@ -59,166 +61,38 @@ function Experiment() {
const timerRef = useRef();
//
const getExperimentList = useCallback(async () => {
const params = {
page: pagination.current - 1,
size: pagination.pageSize,
name: searchText || undefined,
};
const [res] = await to(getExperiment(params));
if (res && res.data && Array.isArray(res.data.content)) {
setExperimentList(
res.data.content.map((item) => {
return { ...item, key: item.id };
}),
);
const getExperimentList = useCallback(
async (skipLoading = false) => {
const params = {
page: pagination.current - 1,
size: pagination.pageSize,
name: searchText || undefined,
};
const [res] = await to(getExperiment(params, skipLoading));
if (res && res.data && Array.isArray(res.data.content)) {
setExperimentList(
res.data.content.map((item) => {
return { ...item, key: item.id };
}),
);
setTotal(res.data.totalElements);
}
}, [pagination, searchText]);
setTotal(res.data.totalElements);
}
},
[pagination, searchText],
);
//
//
const refreshExperimentList = useCallback(() => {
getExperimentList();
}, [getExperimentList]);
// 线
useEffect(() => {
// 线
const getWorkflowList = async () => {
const queryFlow = {
offset: 1,
page: 0,
size: 10000,
name: null,
};
const [res] = await to(getWorkflow(queryFlow));
if (res && res.data && res.data.content) {
setWorkflowList(res.data.content);
}
};
getWorkflowList();
return () => {
clearExperimentInTimers();
};
}, []);
//
useEffect(() => {
getExperimentList();
}, [getExperimentList]);
//
useEffect(() => {
const handleMessage = (e) => {
const { type, payload } = e.data;
if (type === ExperimentCompleted) {
const { id, status, finish_time } = payload;
//
setExperimentInsList((prev) =>
prev.map((v) =>
v.id === id
? {
...v,
status: status,
finish_time: finish_time,
}
: v,
),
);
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = undefined;
}
timerRef.current = setTimeout(() => {
refreshExperimentList();
}, 10000);
}
};
window.addEventListener('message', handleMessage);
return () => {
window.removeEventListener('message', handleMessage);
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = undefined;
}
};
}, [refreshExperimentList]);
//
const onSearch = (value) => {
setSearchText(value);
setPagination((prev) => ({
...prev,
current: 1,
}));
};
//
const getQueryByExperiment = async (experimentId, page, size = 5) => {
const params = {
experimentId: experimentId,
page: page,
size: size,
};
const [res, error] = await to(getQueryByExperimentId(params));
if (res && res.data) {
const { content = [], totalElements = 0 } = res.data;
setExpandedRowKeys(experimentId);
try {
const list = content.map((v) => {
const nodes_result = v.nodes_result ? JSON.parse(v.nodes_result) : {};
return {
...v,
nodes_result,
};
});
if (page === 0) {
setExperimentInsList(list);
clearExperimentInTimers();
} else {
setExperimentInsList((prev) => [...prev, ...list]);
}
setExperimentInsTotal(totalElements);
// TensorBoard
list.forEach((item) => {
if (item.nodes_result?.tensorboard_log) {
getTensorBoardStatus(item);
}
});
} catch (error) {
console.error('JSON parse error: ', error);
}
}
};
// TensorBoard
const runTensorBoard = async (experimentIn) => {
const params = {
namespace: experimentIn.nodes_result.tensorboard_log.namespace,
path: experimentIn.nodes_result.tensorboard_log.path,
pvc_name: experimentIn.nodes_result.tensorboard_log.pvc_name,
};
const [res] = await to(runTensorBoardReq(params));
if (res) {
experimentIn.tensorboardUrl = res.data;
const timerId = timerIds.get(experimentIn.id);
if (timerId) {
clearTimeout(timerId);
timerIds.delete(experimentIn.id);
getTensorBoardStatus(experimentIn);
}
}
};
const refreshExperimentList = useCallback(
(skipLoading = false) => {
getExperimentList(skipLoading);
},
[getExperimentList],
);
// TensorBoard
const getTensorBoardStatus = async (experimentIn) => {
const getTensorBoardStatus = useCallback(async (experimentIn) => {
const params = {
namespace: experimentIn.nodes_result.tensorboard_log.namespace,
path: experimentIn.nodes_result.tensorboard_log.path,
@ -249,17 +123,196 @@ function Experiment() {
}, 10 * 1000);
timerIds.set(experimentIn.id, timerId);
}
}, []);
//
const getExperimentInsList = useCallback(
async (experimentId, page, size = 5, skipLoading = false) => {
const params = {
experimentId: experimentId,
page: page,
size: size,
};
const [res, error] = await to(getQueryByExperimentId(params, skipLoading));
if (res && res.data) {
const { content = [], totalElements = 0 } = res.data;
try {
const list = content.map((v) => {
const nodes_result = v.nodes_result ? JSON.parse(v.nodes_result) : {};
return {
...v,
nodes_result,
};
});
if (page === 0) {
setExperimentInsList(list);
clearExperimentInTimers();
} else {
setExperimentInsList((prev) => [...prev, ...list]);
}
setExperimentInsTotal(totalElements);
// TensorBoard
list.forEach((item) => {
if (item.nodes_result?.tensorboard_log) {
getTensorBoardStatus(item);
}
});
} catch (error) {
console.error('JSON parse error: ', error);
}
}
},
[getTensorBoardStatus],
);
//
const refreshExperimentIns = useCallback(
(experimentId, skipLoading = false) => {
const length = experimentInsList.length;
getExperimentInsList(experimentId, 0, length, skipLoading);
},
[experimentInsList, getExperimentInsList],
);
//
const editExperimentIns = useCallback(
async (experimentId, experimentInsId, status, argo_ins_name, argo_ins_ns) => {
const params = {
experiment_id: experimentId,
id: experimentInsId,
status: status,
argo_ins_name,
argo_ins_ns,
};
const [res, error] = await to(editExperimentInsReq(params));
if (res && res.data) {
refreshExperimentIns(experimentId, true);
refreshExperimentList(true);
}
},
[refreshExperimentIns, refreshExperimentList],
);
// 线
useEffect(() => {
// 线
const getWorkflowList = async () => {
const queryFlow = {
offset: 1,
page: 0,
size: 10000,
name: null,
};
const [res] = await to(getWorkflow(queryFlow));
if (res && res.data && res.data.content) {
setWorkflowList(res.data.content);
}
};
getWorkflowList();
return () => {
clearExperimentInTimers();
};
}, []);
//
useEffect(() => {
getExperimentList();
}, [getExperimentList]);
//
useEffect(() => {
const handleMessage = (e) => {
const { type, payload } = e.data;
if (type === ExperimentCompleted) {
const { experimentId, experimentInsId, status, finishTime } = payload;
const currentIns = experimentInsList.find((v) => v.id === experimentInsId);
console.log(
'实验实例状态变化',
currentIns?.status,
status,
experimentId,
experimentInsId,
finishTime,
);
if (
!currentIns ||
currentIns.status === ExperimentStatus.Terminated ||
currentIns.status === status
) {
return;
}
editExperimentIns(
experimentId,
experimentInsId,
status,
currentIns.argo_ins_name,
currentIns.argo_ins_ns,
);
// refreshExperimentList(true);
// refreshExperimentIns(experimentId);
//
// setExperimentInsList((prev) =>
// prev.map((v) =>
// v.id === experimentInsId
// ? {
// ...v,
// status: status,
// finish_time: finishTime,
// }
// : v,
// ),
// );
}
};
window.addEventListener('message', handleMessage);
return () => {
window.removeEventListener('message', handleMessage);
};
}, [experimentInsList, editExperimentIns]);
//
const onSearch = (value) => {
setSearchText(value);
setPagination((prev) => ({
...prev,
current: 1,
}));
};
// TensorBoard
const runTensorBoard = async (experimentIn) => {
const params = {
namespace: experimentIn.nodes_result.tensorboard_log.namespace,
path: experimentIn.nodes_result.tensorboard_log.path,
pvc_name: experimentIn.nodes_result.tensorboard_log.pvc_name,
};
const [res] = await to(runTensorBoardReq(params));
if (res) {
experimentIn.tensorboardUrl = res.data;
const timerId = timerIds.get(experimentIn.id);
if (timerId) {
clearTimeout(timerId);
timerIds.delete(experimentIn.id);
getTensorBoardStatus(experimentIn);
}
}
};
//
const expandChange = (e, record) => {
const expandChange = (expanded, record) => {
clearExperimentInTimers();
setExperimentInsList([]);
if (record.id === expandedRowKeys) {
setExpandedRowKeys(null);
} else {
getQueryByExperiment(record.id, 0, 5);
if (expanded) {
setExpandedRowKeys([record.id]);
getExperimentInsList(record.id, 0, 5);
refreshExperimentList();
} else {
setExpandedRowKeys([]);
}
};
@ -337,8 +390,9 @@ function Experiment() {
const [res] = await to(runExperiments(id));
if (res) {
message.success('运行成功');
setExpandedRowKeys([id]);
refreshExperimentList();
getQueryByExperiment(id, 0, 5);
getExperimentInsList(id, 0, 5);
}
};
@ -372,14 +426,16 @@ function Experiment() {
experimentIn.tensorBoardStatus === TensorBoardStatus.Running &&
experimentIn.tensorboardUrl
) {
window.open(experimentIn.tensorboardUrl, '_blank');
const url = experimentIn.tensorboardUrl;
SessionStorage.setItem(SessionStorage.tensorBoardUrlKey, url);
navigateToUrl(`/pipeline/experiment/visual`);
// window.open(experimentIn.tensorboardUrl, '_blank');
}
};
//
const handleInstanceTerminate = async (experimentIn) => {
//
refreshExperimentList();
//
setExperimentInsList((prevList) => {
return prevList.map((item) => {
if (item.id === experimentIn.id) {
@ -392,6 +448,9 @@ function Experiment() {
return item;
});
});
//
refreshExperimentList(true);
refreshExperimentIns(experimentIn.experiment_id);
};
//
@ -413,16 +472,10 @@ function Experiment() {
};
};
//
const refreshExperimentIns = (experimentId) => {
const length = experimentInsList.length;
getQueryByExperiment(experimentId, 0, length);
};
//
const loadMoreExperimentIns = () => {
const page = Math.round(experimentInsList.length / 5);
getQueryByExperiment(expandedRowKeys, page, 5);
getExperimentInsList(expandedRowKeys[0], page, 5);
};
//
@ -607,7 +660,7 @@ function Experiment() {
></ExperimentInstanceList>
),
onExpand: expandChange,
expandedRowKeys: [expandedRowKeys],
expandedRowKeys: expandedRowKeys,
}}
/>
</div>

View File

@ -0,0 +1,12 @@
/*
* @Author:
* @Date: 2025-03-31 16:38:59
* @Description: Aim
*/
import IframePage, { IframePageType } from '@/components/IFramePage';
function AimPage() {
return <IframePage type={IframePageType.Aim}></IframePage>;
}
export default AimPage;

View File

@ -1,7 +1,7 @@
/*
* @Author:
* @Date: 2024-04-16 13:58:08
* @Description:
* @Description:
*/
import PageTitle from '@/components/PageTitle';
import { getRayInfoReq } from '@/services/hyperParameter';

View File

@ -51,7 +51,7 @@ function HyperParameterInstance() {
const [res] = await to(getRayInsReq(instanceId));
if (res && res.data) {
const info = res.data as HyperParameterInstanceData;
const { param, node_status, argo_ins_name, argo_ins_ns, status, create_time } = info;
const { param, node_status, argo_ins_name, argo_ins_ns, status } = info;
// 解析配置参数
const paramJson = parseJsonText(param).data;
if (paramJson) {
@ -72,7 +72,6 @@ function HyperParameterInstance() {
}
setExperimentInfo({
...paramJson,
create_time,
});
}
@ -121,18 +120,17 @@ function HyperParameterInstance() {
if (dataJson) {
const nodes = dataJson?.result?.object?.status?.nodes;
if (nodes) {
// 节点
// 设置节点
setNodes(nodes);
// 设置总 workflow 状态
const workflowStatus = Object.values(nodes).find((node: any) =>
node.displayName.startsWith(NodePrefix),
) as NodeStatus;
// 设置工作流状态
if (workflowStatus) {
setWorkflowStatus(workflowStatus);
// 实验结束,关闭 SSE
// 实验结束,关闭 SSE,获取实验实例结果
if (
workflowStatus.phase !== ExperimentStatus.Pending &&
workflowStatus.phase !== ExperimentStatus.Running
@ -167,8 +165,8 @@ function HyperParameterInstance() {
<HyperParameterBasic
className={styles['hyper-parameter-instance__basic']}
info={experimentInfo}
runStatus={workflowStatus}
instanceStatus={instanceInfo?.status}
workflowStatus={workflowStatus}
instanceStatus={instanceInfo?.status as ExperimentStatus}
isInstance
/>
),

View File

@ -3,7 +3,9 @@ import TrialStatusCell from '@/pages/HyperParameter/components/TrialStatusCell';
import { HyperParameterTrial } from '@/pages/HyperParameter/types';
import { getExpMetricsReq } from '@/services/hyperParameter';
import { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage';
import tableCellRender, { TableCellValueType } from '@/utils/table';
import { useNavigate } from '@umijs/max';
import { App, Button, Table, type TableProps } from 'antd';
import classNames from 'classnames';
import { useEffect, useState } from 'react';
@ -36,6 +38,7 @@ function ExperimentHistory({ trialList = [] }: ExperimentHistoryProps) {
const metricAnalysis: Record<string, any> = first?.metric_analysis ?? {};
const paramsNames = Object.keys(config);
const metricNames = Object.keys(metricAnalysis);
const navigate = useNavigate();
const trialColumns: TableProps<HyperParameterTrial>['columns'] = [
{
@ -160,7 +163,8 @@ function ExperimentHistory({ trialList = [] }: ExperimentHistoryProps) {
const [res] = await to(getExpMetricsReq(selectedRowKeys));
if (res && res.data) {
const url = res.data;
window.open(url, '_blank');
SessionStorage.setItem(SessionStorage.aimUrlKey, url);
navigate('compare-visual');
}
};

View File

@ -1,4 +1,5 @@
import { ExperimentStatus } from '@/enums';
import EmptyLog from '@/pages/AutoML/components/ExperimentLog/empty';
import LogList from '@/pages/Experiment/components/LogList';
import { HyperParameterInstanceData } from '@/pages/HyperParameter/types';
import { NodeStatus } from '@/types';
@ -64,7 +65,7 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) {
// icon: <KFIcon type="icon-rizhi1" />,
children: (
<div className={styles['experiment-log__tabs__log']}>
{trainCloneNodeStatus && (
{trainCloneNodeStatus ? (
<LogList
instanceName={instanceInfo.argo_ins_name}
instanceNamespace={instanceInfo.argo_ins_ns}
@ -73,6 +74,8 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) {
instanceNodeStartTime={trainCloneNodeStatus.startedAt}
instanceNodeStatus={trainCloneNodeStatus.phase as ExperimentStatus}
></LogList>
) : (
<EmptyLog />
)}
</div>
),
@ -83,7 +86,7 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) {
// icon: <KFIcon type="icon-rizhi1" />,
children: (
<div className={styles['experiment-log__tabs__log']}>
{hpoNodeStatus && (
{hpoNodeStatus ? (
<LogList
instanceName={instanceInfo.argo_ins_name}
instanceNamespace={instanceInfo.argo_ins_ns}
@ -92,6 +95,8 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) {
instanceNodeStartTime={hpoNodeStatus.startedAt}
instanceNodeStatus={hpoNodeStatus.phase as ExperimentStatus}
></LogList>
) : (
<EmptyLog />
)}
</div>
),

View File

@ -1,9 +1,7 @@
import ConfigInfo, { type BasicInfoData } from '@/components/ConfigInfo';
import RunDuration from '@/components/RunDuration';
import { ExperimentStatus, hyperParameterOptimizedMode } from '@/enums';
import { useComputingResource } from '@/hooks/useComputingResource';
import ExperimentRunBasic from '@/pages/AutoML/components/ExperimentRunBasic';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import {
schedulerAlgorithms,
searchAlgorithms,
@ -18,7 +16,6 @@ import {
formatMirror,
formatModel,
} from '@/utils/format';
import { Flex } from 'antd';
import classNames from 'classnames';
import { useMemo } from 'react';
import ParameterInfo from '../ParameterInfo';
@ -33,14 +30,14 @@ type HyperParameterBasicProps = {
info?: HyperParameterData;
className?: string;
isInstance?: boolean;
runStatus?: NodeStatus;
workflowStatus?: NodeStatus;
instanceStatus?: ExperimentStatus;
};
function HyperParameterBasic({
info,
className,
runStatus,
workflowStatus,
instanceStatus,
isInstance = false,
}: HyperParameterBasicProps) {
@ -83,7 +80,7 @@ function HyperParameterBasic({
}
return [
{
label: '代码',
label: '代码配置',
value: info.code_config,
format: formatCodeConfig,
},
@ -145,56 +142,10 @@ function HyperParameterBasic({
];
}, [info, getResourceDescription]);
const instanceDatas = useMemo(() => {
if (!info || !runStatus) {
return [];
}
return [
{
label: '启动时间',
value: formatDate(info.create_time),
ellipsis: true,
},
{
label: '执行时长',
value: <RunDuration createTime={info.create_time} finishTime={runStatus.finishedAt} />,
ellipsis: true,
},
{
label: '状态',
value: (
<Flex align="center">
<img
style={{ width: '17px', marginRight: '7px' }}
src={experimentStatusInfo[runStatus.phase]?.icon}
draggable={false}
alt=""
/>
<div
style={{
color: experimentStatusInfo[runStatus?.phase]?.color,
fontSize: '15px',
lineHeight: 1.6,
}}
>
{experimentStatusInfo[runStatus?.phase]?.label}
</div>
</Flex>
),
ellipsis: true,
},
];
}, [runStatus, info]);
return (
<div className={classNames(styles['hyper-parameter-basic'], className)}>
{isInstance && runStatus && (
<ExperimentRunBasic
create_time={info?.create_time}
runStatus={runStatus}
instanceStatus={instanceStatus}
/>
{isInstance && workflowStatus && (
<ExperimentRunBasic workflowStatus={workflowStatus} instanceStatus={instanceStatus} />
)}
{!isInstance && (
<ConfigInfo

View File

@ -9,6 +9,14 @@
background-color: white;
border-radius: 10px;
&__name-row {
:global {
.ant-form-item-row {
flex-wrap: nowrap;
}
}
}
&__type {
color: @text-color;
font-size: @font-size-input-lg;

View File

@ -123,8 +123,6 @@ function MirrorCreate() {
return true;
};
const descTitle = isAddVersion ? '版本描述' : '镜像描述';
return (
<div className={styles['mirror-create']}>
<PageTitle title={!isAddVersion ? '创建镜像' : '新增镜像版本'}></PageTitle>
@ -161,6 +159,7 @@ function MirrorCreate() {
message: '只支持小写字母、数字、点(.)、下划线(_)、中横线(-)、斜杠(/)',
},
]}
className={styles['mirror-create__content__name-row']}
>
<Input
placeholder="请输入镜像名称"
@ -193,21 +192,45 @@ function MirrorCreate() {
</Form.Item>
</Col>
</Row>
{!isAddVersion && (
<Row gutter={10}>
<Col span={20}>
<Form.Item
label="镜像描述"
name="description"
rules={[
{
required: true,
message: '请输入镜像描述',
},
]}
>
<Input.TextArea
autoSize={{ minRows: 2, maxRows: 6 }}
placeholder="请输入镜像描述"
maxLength={128}
showCount
allowClear
/>
</Form.Item>
</Col>
</Row>
)}
<Row gutter={10}>
<Col span={20}>
<Form.Item
label={descTitle}
name="description"
label="版本描述"
name="version_description"
rules={[
{
required: true,
message: `请输入${descTitle}`,
message: '请输入版本描述',
},
]}
>
<Input.TextArea
autoSize={{ minRows: 2, maxRows: 6 }}
placeholder={`请输入${descTitle}`}
placeholder="请输入版本描述"
maxLength={128}
showCount
allowClear

View File

@ -125,6 +125,7 @@ function MirrorInfo() {
current: tableData.length === 1 ? Math.max(1, prev.current! - 1) : prev.current,
};
});
getMirrorInfo();
}
};
@ -164,20 +165,21 @@ function MirrorInfo() {
title: '镜像版本',
dataIndex: 'tag_name',
key: 'tag_name',
width: '25%',
width: '30%',
render: tableCellRender(),
},
{
title: '镜像地址',
dataIndex: 'url',
key: 'url',
width: '25%',
width: '40%',
render: tableCellRender('auto', TableCellValueType.Text, { copyable: true }),
},
{
title: '版本描述',
dataIndex: 'description',
key: 'description',
width: '30%',
render: tableCellRender(true),
},
{
@ -209,7 +211,7 @@ function MirrorInfo() {
hidden: isPublic,
render: (_: any, record: MirrorVersionData) => (
<div>
{!isPublic && (
{!isPublic && record.status && record.status !== MirrorVersionStatus.Building && (
<ConfigProvider
theme={{
token: {

View File

@ -65,14 +65,18 @@ export type MetricsChartProps = {
function MetricsChart({ name, chartData }: MetricsChartProps) {
const chartRef = useRef<HTMLDivElement>(null);
const xAxisData = chartData[0]?.iters;
const seriesData = chartData.map((item) => {
return {
name: item.version,
type: 'line' as const,
smooth: true,
data: item.values,
};
});
const seriesData = useMemo(
() =>
chartData.map((item) => {
return {
name: item.version,
type: 'line' as const,
smooth: true,
data: item.values,
};
}),
[chartData],
);
const options: echarts.EChartsOption = useMemo(
() => ({
@ -158,7 +162,7 @@ function MetricsChart({ name, chartData }: MetricsChartProps) {
// 组件卸载
return () => {
// myChart.dispose() 销毁实例
// 销毁实例
chart.dispose();
};
}, [options]);

View File

@ -238,7 +238,7 @@ function CreateServiceVersion() {
},
{
pattern: /^[a-zA-Z0-9._-]+$/,
message: '版本只支持字母、数字、点(.)、下划线(_)、中横线(-)',
message: '服务支持字母、数字、点(.)、下划线(_)、中横线(-)',
},
]}
>

View File

@ -3,16 +3,16 @@
* @Date: 2024-04-16 13:58:08
* @Description:
*/
import FullScreenFrame from '@/components/FullScreenFrame';
import IframePage from '@/components/IFramePage';
import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle';
import { ServiceRunStatus } from '@/enums';
import { getServiceVersionInfoReq } from '@/services/modelDeployment';
import { to } from '@/utils/promise';
import { useParams } from '@umijs/max';
import { Tabs } from 'antd';
import { useEffect, useState } from 'react';
import ServerLog from '../components/ServerLog';
import UserGuide from '../components/UserGuide';
import VersionBasicInfo from '../components/VersionBasicInfo';
import { ServiceVersionData } from '../types';
import styles from './index.less';
@ -50,32 +50,35 @@ function ServiceVersionInfo() {
icon: <KFIcon type="icon-jibenxinxi" />,
children: <VersionBasicInfo info={versionInfo} />,
},
{
key: ModelDeploymentTabKey.Predict,
label: '预测',
icon: <KFIcon type="icon-yuce" />,
children: (
<div style={{ height: '100%', width: '100%' }}>
{versionInfo?.page_path && (
<FullScreenFrame url={versionInfo?.page_path}></FullScreenFrame>
)}
</div>
),
},
{
key: ModelDeploymentTabKey.Guide,
label: '调用指南',
icon: <KFIcon type="icon-tiaoyongzhinan" />,
children: <UserGuide info={versionInfo}></UserGuide>,
},
{
key: ModelDeploymentTabKey.Log,
label: '服务日志',
icon: <KFIcon type="icon-fuwurizhi" />,
children: <ServerLog info={versionInfo}></ServerLog>,
},
];
if (versionInfo?.run_state === ServiceRunStatus.Running) {
if (versionInfo?.page_path) {
tabItems.push({
key: ModelDeploymentTabKey.Predict,
label: '预测',
icon: <KFIcon type="icon-yuce" />,
children: <IframePage url={versionInfo?.page_path} showLoading={false}></IframePage>,
});
}
if (versionInfo?.doc_path) {
tabItems.push({
key: ModelDeploymentTabKey.Guide,
label: '调用指南',
icon: <KFIcon type="icon-tiaoyongzhinan" />,
children: <IframePage url={versionInfo?.doc_path}></IframePage>,
});
}
}
tabItems.push({
key: ModelDeploymentTabKey.Log,
label: '服务日志',
icon: <KFIcon type="icon-fuwurizhi" />,
children: <ServerLog info={versionInfo}></ServerLog>,
});
return (
<div className={styles['service-version-info']}>
<PageTitle title="服务版本详情"></PageTitle>

View File

@ -11,10 +11,18 @@
font-family: 'Roboto Mono', 'Menlo', 'Consolas', 'Monaco', monospace;
display: flex;
flex-direction: column;
align-items: center;
align-items: left;
&--empty {
align-items: center;
}
&__more {
padding: 20px 0;
}
&::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.5);
}
}
}

View File

@ -3,6 +3,7 @@ import { getServiceVersionLogReq } from '@/services/modelDeployment';
import { to } from '@/utils/promise';
import { DoubleRightOutlined } from '@ant-design/icons';
import { Button, DatePicker, type TimeRangePickerProps } from 'antd';
import classNames from 'classnames';
import dayjs from 'dayjs';
import { useEffect, useState } from 'react';
import styles from './index.less';
@ -106,6 +107,8 @@ function ServerLog({ info }: ServerLogProps) {
}
};
const logContent = logData.map((v) => v.log_content).join('');
return (
<div className={styles['server-log']}>
<div>
@ -122,9 +125,9 @@ function ServerLog({ info }: ServerLogProps) {
</Button>
</div>
{logData.length > 0 && (
{logContent ? (
<div className={styles['server-log__data']} id="server-log">
<div>{logData.map((v) => v.log_content).join('') || '暂无日志'}</div>
<div>{logContent}</div>
{hasMore && (
<Button
type="text"
@ -137,6 +140,10 @@ function ServerLog({ info }: ServerLogProps) {
</Button>
)}
</div>
) : (
<div className={classNames(styles['server-log__data'], styles['server-log__data--empty'])}>
</div>
)}
</div>
);

View File

@ -110,7 +110,7 @@ const EditPipeline = () => {
// console.log(data);
const errorNode = data.nodes.find((item) => item.formError === true);
if (errorNode) {
message.error(`${errorNode.label}】节点必填项必须配置`);
message.error(`${errorNode.label}】节点配置验证失败`);
const graphNode = graph.findById(errorNode.id);
if (graphNode) {
openNodeDrawer(graphNode, true);

View File

@ -148,7 +148,7 @@ const GlobalParamsDrawer = forwardRef(
>
{getParamComponent(type)}
</Form.Item>
{type !== 3 && (
{/* {type !== 3 && (
<Form.Item
{...restField}
name={[name, 'is_sensitive']}
@ -161,7 +161,7 @@ const GlobalParamsDrawer = forwardRef(
<Radio value={0}></Radio>
</Radio.Group>
</Form.Item>
)}
)} */}
</>
);
}}

View File

@ -19,6 +19,7 @@ import { openAntdModal } from '@/utils/modal';
import { to } from '@/utils/promise';
import { INode } from '@antv/g6';
import { Button, Drawer, Form, Input, MenuProps } from 'antd';
import { RuleObject } from 'antd/es/form';
import { NamePath } from 'antd/es/form/interface';
import { forwardRef, useImperativeHandle, useState } from 'react';
import PropsLabel from '../PropsLabel';
@ -34,6 +35,12 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
const [stagingItem, setStagingItem] = useState<PipelineNodeModelSerialize>(
{} as PipelineNodeModelSerialize,
);
const nodeId = Form.useWatch('id', form) as string;
const hasTaskInfo =
nodeId &&
!nodeId.startsWith('git-clone') &&
!nodeId.startsWith('dataset-export') &&
!nodeId.startsWith('model-export');
const [open, setOpen] = useState(false);
const [menuItems, setMenuItems] = useState<MenuProps['items']>([]);
@ -45,10 +52,11 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
const fields = form.getFieldsValue();
// 保存字段顺序
const control_strategy = {
...stagingItem.control_strategy,
...fields.control_strategy,
};
// const control_strategy = {
// ...stagingItem.control_strategy,
// ...fields.control_strategy,
// };
const in_parameters = {
...stagingItem.in_parameters,
...fields.in_parameters,
@ -63,7 +71,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
const res = {
...stagingItem,
...fields,
control_strategy: JSON.stringify(control_strategy),
// control_strategy: JSON.stringify(control_strategy),
in_parameters: JSON.stringify(in_parameters),
out_parameters: JSON.stringify(out_parameters),
formError: !!error,
@ -90,7 +98,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
...model,
in_parameters: JSON.parse(model.in_parameters),
out_parameters: JSON.parse(model.out_parameters),
control_strategy: JSON.parse(model.control_strategy),
// control_strategy: JSON.parse(model.control_strategy),
};
// console.log('model', nodeData);
setStagingItem({
@ -309,22 +317,47 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
);
};
// 模型部署-服务版本验证
const serviceVersionValidator = (_rule: RuleObject, value: any) => {
// 输入参数值都是对象,如果是手动输入,要求 /^[a-zA-Z0-9._-]+$/
if (
typeof value === 'object' &&
value &&
!value.fromSelect &&
value.value &&
!/^[a-zA-Z0-9._-]+$/.test(value.value)
) {
return Promise.reject('服务版本只支持字母、数字、点(.)、下划线(_)、中横线(-)');
}
return Promise.resolve();
};
// 必填项校验规则
const getFormRules = (item: { key: string; value: PipelineNodeModelParameter }) => {
return item.value.require
const id = form.getFieldValue('id') as string;
const rules = item.value.require
? [
{
validator: requiredValidator,
message: '必填项',
},
]
: [];
// 模型部署-服务版本验证
if (id.startsWith('model-deploy') && item.key === '--version') {
rules.push({
validator: serviceVersionValidator,
});
}
return rules;
};
// 控制策略
const controlStrategyList = Object.entries(stagingItem.control_strategy ?? {}).map(
([key, value]) => ({ key, value }),
);
// const controlStrategyList = Object.entries(stagingItem.control_strategy ?? {}).map(
// ([key, value]) => ({ key, value }),
// );
// 输入参数
const inParametersList = Object.entries(stagingItem.in_parameters ?? {}).map(([key, value]) => ({
@ -396,71 +429,73 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
>
<Input disabled />
</Form.Item>
<div className={styles['pipeline-drawer__title']}>
<SubAreaTitle
image={require('@/assets/img/duty-message.png')}
title="任务信息"
></SubAreaTitle>
</div>
<Form.Item label="镜像" required>
<div className={styles['pipeline-drawer__ref-row']}>
<Form.Item name="image" noStyle rules={[{ required: true, message: '请输入镜像' }]}>
<Input placeholder="请输入或选择镜像" allowClear />
{hasTaskInfo && (
<>
<div className={styles['pipeline-drawer__title']}>
<SubAreaTitle
image={require('@/assets/img/duty-message.png')}
title="任务信息"
></SubAreaTitle>
</div>
<Form.Item label="镜像" required>
<div className={styles['pipeline-drawer__ref-row']}>
<Form.Item name="image" noStyle rules={[{ required: true, message: '请输入镜像' }]}>
<Input placeholder="请输入或选择镜像" allowClear />
</Form.Item>
<Form.Item noStyle>
<Button
type="link"
size="small"
icon={getSelectBtnIcon({ item_type: 'image' })}
onClick={() => selectResource('image', { item_type: 'image' })}
className={styles['pipeline-drawer__ref-row__select-button']}
>
</Button>
</Form.Item>
</div>
</Form.Item>
<Form.Item noStyle>
<Button
type="link"
size="small"
icon={getSelectBtnIcon({ item_type: 'image' })}
onClick={() => selectResource('image', { item_type: 'image' })}
className={styles['pipeline-drawer__ref-row__select-button']}
>
</Button>
<Form.Item
name="working_directory"
label={
<PropsLabel
menuItems={menuItems}
title="工作目录"
onClick={(value) => {
handleParameterClick('working_directory', value);
}}
/>
}
>
<Input placeholder="请输入工作目录" allowClear />
</Form.Item>
</div>
</Form.Item>
<Form.Item
name="working_directory"
label={
<PropsLabel
menuItems={menuItems}
title="工作目录"
onClick={(value) => {
handleParameterClick('working_directory', value);
}}
/>
}
>
<Input placeholder="请输入工作目录" allowClear />
</Form.Item>
<Form.Item
name="command"
label={
<PropsLabel
menuItems={menuItems}
title="启动命令"
onClick={(value) => {
handleParameterClick('command', value);
}}
/>
}
>
<TextArea placeholder="请输入启动命令" allowClear />
</Form.Item>
<Form.Item
label="资源规格"
name="resources_standard"
rules={[
{
required: true,
message: '请选择资源规格',
},
]}
>
<ParameterSelect dataType="resource" placeholder="请选择资源规格" />
</Form.Item>
<Form.Item
<Form.Item
name="command"
label={
<PropsLabel
menuItems={menuItems}
title="启动命令"
onClick={(value) => {
handleParameterClick('command', value);
}}
/>
}
>
<TextArea placeholder="请输入启动命令" allowClear />
</Form.Item>
<Form.Item
label="资源规格"
name="resources_standard"
rules={[
{
required: true,
message: '请选择资源规格',
},
]}
>
<ParameterSelect dataType="resource" placeholder="请选择资源规格" />
</Form.Item>
{/* <Form.Item
name="mount_path"
label={
<PropsLabel
@ -473,23 +508,23 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
}
>
<Input placeholder="请输入挂载路径" allowClear />
</Form.Item>
<Form.Item
name="env_variables"
label={
<PropsLabel
menuItems={menuItems}
title="环境变量"
onClick={(value) => {
handleParameterClick('env_variables', value);
}}
/>
}
>
<TextArea placeholder="请输入环境变量" allowClear />
</Form.Item>
{/* 控制参数 */}
{controlStrategyList.map((item) => (
</Form.Item> */}
<Form.Item
name="env_variables"
label={
<PropsLabel
menuItems={menuItems}
title="环境变量"
onClick={(value) => {
handleParameterClick('env_variables', value);
}}
/>
}
>
<TextArea placeholder="请输入环境变量" allowClear />
</Form.Item>
{/* 控制参数 */}
{/* {controlStrategyList.map((item) => (
<Form.Item
key={item.key}
name={['control_strategy', item.key]}
@ -499,7 +534,10 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
>
<ParameterInput placeholder={item.value.placeholder} allowClear></ParameterInput>
</Form.Item>
))}
))} */}
</>
)}
{/* 输入参数 */}
{inParametersList.length > 0 && (
<>

View File

@ -190,7 +190,7 @@ const Pipeline = () => {
}),
},
{
title: '流水线名称',
title: '流水线模板名称',
dataIndex: 'name',
key: 'name',
width: '50%',
@ -199,7 +199,7 @@ const Pipeline = () => {
}),
},
{
title: '流水线描述',
title: '流水线模板描述',
dataIndex: 'description',
key: 'description',
width: '50%',

View File

@ -113,7 +113,7 @@ const AuthUserTableList: React.FC = () => {
dataIndex: 'createTime',
valueType: 'dateRange',
render: (_, record) => {
return <span>{record.createTime.toString()} </span>;
return <span>{record.createTime?.toString()} </span>;
},
hideInSearch: true,
},

View File

@ -84,7 +84,7 @@ const UserSelectorModal: React.FC<DataScopeFormProps> = (props) => {
valueType: 'dateRange',
hideInSearch: true,
render: (_, record) => {
return <span>{record.createTime.toString()} </span>;
return <span>{record.createTime?.toString()} </span>;
},
},
];

View File

@ -177,7 +177,7 @@ const RoleTableList: React.FC = () => {
confirm({
title: `确认要${text}${record.roleName}角色吗?`,
onOk() {
changeRoleStatus(record.roleId, newStatus).then((resp) => {
changeRoleStatus(record.roleId, record.roleKey, newStatus).then((resp) => {
if (resp.code === 200) {
messageApi.open({
type: 'success',
@ -240,7 +240,7 @@ const RoleTableList: React.FC = () => {
dataIndex: 'createTime',
valueType: 'dateRange',
render: (_, record) => {
return <span>{record.createTime.toString()} </span>;
return <span>{record.createTime?.toString()} </span>;
},
search: {
transform: (value) => {

View File

@ -28,8 +28,11 @@ const DeptTree: React.FC<TreeProps> = (props) => {
const res = await getDeptTree({});
const treeData = res.map((item: any) => ({ ...item, key: item.id }));
setTreeData(treeData);
setExpandedKeys([treeData[0].key]);
setSelectedKeys([treeData[0].key]);
if (treeData.length > 0) {
onSelect(treeData[0]);
setExpandedKeys([treeData[0].key]);
setSelectedKeys([treeData[0].key]);
}
hide();
return true;
} catch (error) {
@ -41,12 +44,6 @@ const DeptTree: React.FC<TreeProps> = (props) => {
fetchDeptList();
}, []);
useEffect(() => {
if (treeData.length > 0) {
onSelect(treeData[0]);
}
}, [treeData, onSelect]);
const handleSelect = (keys: React.Key[], info: any) => {
setSelectedKeys(keys);
onSelect(info.node);

View File

@ -203,9 +203,8 @@ const UserForm: React.FC<UserFormProps> = (props) => {
required: true,
},
{
pattern: /^[a-zA-Z](?:[a-zA-Z0-9_.-]*[a-zA-Z0-9])?$/,
message:
'只能包含数字,字母,下划线(_),中横线(-),英文句号(.),且必须以字母开头,数字或字母结尾',
pattern: /^[a-zA-Z][a-zA-Z0-9]{3,14}$/,
message: '用户账号长度为4 ~ 15位且必须以字母开头只支持字母和数字',
},
]}
/>

View File

@ -37,7 +37,18 @@
&__summary {
display: flex;
flex-direction: column;
width: 33.33%;
width: 40%;
text-align: left;
&:nth-child(3n+2) {
text-align: center;
width: 30%;
}
&:nth-child(3n) {
text-align: right;
width: 30%;
}
&__title {
margin-bottom: 12px;

View File

@ -16,7 +16,7 @@ function AssetsManagement() {
};
const [res] = await to(getWorkspaceAssetCountReq(params));
if (res && res.data) {
const { component, dataset, image, model, workflow } = res.data;
const { dataset, image, model, workflow, codeConfig, service } = res.data;
const items = [
{
title: '数据集',
@ -30,10 +30,10 @@ function AssetsManagement() {
title: '镜像',
value: image,
},
{
title: '组件',
value: component,
},
// {
// title: '组件',
// value: component,
// },
// {
// title: '代码配置',
// value: 0,
@ -42,6 +42,14 @@ function AssetsManagement() {
title: '流水线模版',
value: workflow,
},
{
title: '代码配置',
value: codeConfig,
},
{
title: '服务',
value: service,
},
];
setAssetCounts(items);
}
@ -53,7 +61,7 @@ function AssetsManagement() {
return (
<div className={styles['assets-management']}>
<Flex justify="space-between">
<div className={styles['assets-management__title']}>AI资产</div>
<div className={styles['assets-management__title']}></div>
<Select
size="small"
value={type}
@ -67,11 +75,11 @@ function AssetsManagement() {
/>
</Flex>
{/* <div className={styles['assets-management__increase']}>今日新增数量5</div> */}
<Flex gap="22px 0" wrap="wrap" style={{ marginTop: '40px' }}>
<Flex gap="22px 0" wrap="wrap" style={{ marginTop: '40px', padding: '0 8px' }}>
{assetCounts.map((item, index) => (
<div className={styles['assets-management__summary']} key={index}>
<div className={styles['assets-management__summary__title']}>{item.title}</div>
<div className={styles['assets-management__summary__value']}>{item.value}</div>
<div className={styles['assets-management__summary__value']}>{item.value ?? '-'}</div>
</div>
))}
</Flex>

View File

@ -1,6 +1,6 @@
.experiment-table {
flex: 1;
min-width: 378px;
min-width: 460px;
height: 140px;
padding: 12px;
background: @workspace-background;
@ -32,7 +32,7 @@
}
&__status {
width: 15%;
width: 25%;
}
&__duration {
@ -40,11 +40,11 @@
}
&__date {
width: calc(60% - 60px);
width: 40%;
}
&__operation {
width: 60px;
width: 10%;
:global {
.ant-btn-link {

View File

@ -1,10 +1,9 @@
import KFIcon from '@/components/KFIcon';
import { ExperimentStatus, experimentStatusInfo } from '@/pages/Experiment/status';
import { ExperimentInstance } from '@/types';
import { elapsedTime, formatDate } from '@/utils/date';
import { useNavigate } from '@umijs/max';
import { Button, Empty } from 'antd';
import { Empty } from 'antd';
import styles from './index.less';
import ExperimentInstanceComponent from './instance';
type ExperimentTableProps = {
tableData: ExperimentInstance[];
style?: React.CSSProperties;
@ -26,31 +25,11 @@ function ExperimentTable({ tableData = [], style }: ExperimentTableProps) {
</div>
{Array.isArray(tableData) && tableData.length > 0 ? (
tableData.map((item) => (
<div className={styles['experiment-table__content']} key={item.id}>
<div className={styles['experiment-table__status']} style={{ paddingLeft: '6.5px' }}>
<img
src={experimentStatusInfo[item.status as ExperimentStatus]?.icon}
width={17}
height={17}
draggable={false}
alt=""
/>
</div>
<div className={styles['experiment-table__duration']}>
{elapsedTime(item.create_time, item.finish_time)}
</div>
<div className={styles['experiment-table__date']}>{formatDate(item.create_time)}</div>
<div className={styles['experiment-table__operation']}>
<Button
size="small"
type="link"
icon={<KFIcon type="icon-xiangqing2" font={14} />}
onClick={() => gotoExperiment(item)}
>
</Button>
</div>
</div>
<ExperimentInstanceComponent
instance={item}
key={item.id}
onClick={() => gotoExperiment(item)}
/>
))
) : (
<Empty

View File

@ -0,0 +1,76 @@
import KFIcon from '@/components/KFIcon';
import RunDuration from '@/components/RunDuration';
import { ExperimentStatus } from '@/enums';
import { useSSE, type MessageHandler } from '@/hooks/useSSE';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import { ExperimentInstance, NodeStatus } from '@/types';
import { formatDate } from '@/utils/date';
import { getExperimentInstanceStatus, getWorkflowStatus } from '@/utils/experiment';
import { Button } from 'antd';
import { useCallback, useState } from 'react';
import styles from './index.less';
const NodePrefix = 'workflow';
type ExperimentInstanceComponentProps = {
instance: ExperimentInstance;
onClick: () => void;
};
function ExperimentInstanceComponent({ instance, onClick }: ExperimentInstanceComponentProps) {
const { experiment_id, id, argo_ins_name, argo_ins_ns, nodes_status } = instance;
const initialWorkflowStatus = getWorkflowStatus(nodes_status) as NodeStatus | undefined;
const [workflowStatus, setWorkflowStatus] = useState<NodeStatus | undefined>(
initialWorkflowStatus,
);
const status = getExperimentInstanceStatus(instance.status as ExperimentStatus, workflowStatus);
const createTime = workflowStatus?.startedAt;
const finishTime = workflowStatus?.finishedAt;
const statusInfo = experimentStatusInfo[status];
const handleSSEMessage: MessageHandler = useCallback(
(
_experimentId: number,
_experimentInsId: number,
_status: string,
_finishTime: string,
nodes,
) => {
if (nodes) {
// 设置总 workflow 状态
const workflowStatus = Object.values(nodes).find((node: any) =>
node.displayName.startsWith(NodePrefix),
) as NodeStatus;
if (workflowStatus) {
setWorkflowStatus(workflowStatus);
}
}
},
[],
);
useSSE(experiment_id, id, status, argo_ins_name, argo_ins_ns, handleSSEMessage);
return (
<div className={styles['experiment-table__content']} key={id}>
<div className={styles['experiment-table__status']} style={{ paddingLeft: '6.5px' }}>
<img src={statusInfo?.icon} width={17} height={17} draggable={false} alt="" />
</div>
<div className={styles['experiment-table__duration']}>
<RunDuration createTime={createTime} finishTime={finishTime} />
</div>
<div className={styles['experiment-table__date']}>{formatDate(createTime)}</div>
<div className={styles['experiment-table__operation']}>
<Button
size="small"
type="link"
icon={<KFIcon type="icon-xiangqing2" font={14} />}
onClick={onClick}
>
</Button>
</div>
</div>
);
}
export default ExperimentInstanceComponent;

View File

@ -16,13 +16,13 @@ function QuickStart() {
const changeScale = () => {
// body 的宽度 - 菜单的宽度 - 两个 padding - 右边用户管理的宽度 - 右边用户管理的 marginLeft - 滚动条的宽度,
const width = document.body.offsetWidth - 256 - 80 - 60 - 326 - 15 - 8;
if (width >= 1223) {
const spaceX = (width - 192 * 5 - 60) / 7;
if (width >= 1002) {
const spaceX = (width - 192 * 4 - 60) / 6;
setSpace(spaceX);
setCanvasWidth('100%');
setScale(1.0);
} else {
const ratio = width / 1223;
const ratio = width / 1002;
setCanvasWidth('1223px');
setSpace(29);
setScale(ratio);
@ -54,56 +54,56 @@ function QuickStart() {
>
<WorkFlow
content="为开发者提供数据智能标注与数据回流服务"
buttonText="数据准备"
buttonTop={40}
buttonText="数据标注"
buttonTop={20}
x={left}
y={309}
onClick={() => navigate('/datasetPreparation/datasetAnnotation')}
/>
<WorkFlow
{/* <WorkFlow
content="为开发者提供定制化编辑器,开发者可根据自己需求选择配置,保存编译器中的调试环境为镜像供训练使用"
buttonText="开发环境"
buttonTop={20}
x={left + 192 + space}
y={301}
onClick={() => navigate('/developmentEnvironment')}
/>
/> */}
<WorkFlow
content="为开发者提供定制化编辑器,开发者可根据自己需求选择配置,保存编译器中的调试环境为镜像供训练使用"
tips="可视化建模Designer"
buttonText="流水线"
buttonText="流水线模板"
buttonTop={20}
x={left + 2 * (192 + space)}
x={left + 1 * (192 + space)}
y={276}
onClick={() => navigate('/pipeline/template')}
/>
<WorkFlow
content="开发者可以在这里运行流水线模板,产生实验实例,对比实验训练过程与产生的实验训练数据"
buttonText="实验"
buttonTop={40}
x={left + 3 * (192 + space)}
buttonTop={20}
x={left + 2 * (192 + space)}
y={295}
onClick={() => navigate('/pipeline/experiment')}
/>
<WorkFlow
content="支持异构硬件(CPU/GPU)的模型加载,高吞吐,低延迟;支持大规模复杂模型的一键部署,实时弹性扩缩容;提供完整的运维监控体系。"
tips="模型在线服务"
buttonText="模型在线部署"
buttonText="服务"
buttonTop={20}
x={left + 4 * (192 + space) + 60 + space}
x={left + 3 * (192 + space) + 60 + space}
y={263}
onClick={() => navigate('/dataset/modelDeployment')}
/>
<div
className={styles['quick-start__content__canvas__model']}
style={{ top: '358px', left: left + 4 * (192 + space) + 'px' }}
style={{ top: '358px', left: left + 3 * (192 + space) + 'px' }}
>
<KFIcon type="icon-moxingguanli" font={38} />
<span></span>
</div>
<div
className={styles['quick-start__content__canvas__task']}
style={{ top: '110px', left: left + 2 * (192 + space) + 56 + taskLeftArrowWidth + 16 }}
style={{ top: '110px', left: left + 1 * (192 + space) + 56 + taskLeftArrowWidth + 16 }}
>
<KFIcon type="icon-tiaoduguanli" font={13} style={{ marginRight: '5px' }} />
<span></span>
@ -117,7 +117,7 @@ function QuickStart() {
arrorwTop={-4}
borderBottom={1}
/>
<WorkArrow
{/* <WorkArrow
x={left + 2 * 192 + space + 1}
y={378}
width={arrowWidth}
@ -125,9 +125,9 @@ function QuickStart() {
arrowLeft={arrowWidth}
arrorwTop={-4}
borderBottom={1}
/>
/> */}
<WorkArrow
x={left + 4 * 192 + 3 * space + 1}
x={left + 3 * 192 + 2 * space + 1}
y={378}
width={arrowWidth + 10}
height={1}
@ -136,7 +136,7 @@ function QuickStart() {
borderBottom={1}
/>
<WorkArrow
x={left + 4 * 192 + 60 + 4 * space + 1 - 10}
x={left + 3 * 192 + 60 + 3 * space + 1 - 10}
y={378}
width={arrowWidth + 10}
height={1}
@ -145,7 +145,7 @@ function QuickStart() {
borderBottom={1}
/>
<WorkArrow
x={left + 2 * (192 + space) + 56}
x={left + 1 * (192 + space) + 56}
y={139}
width={taskLeftArrowWidth}
height={120}
@ -155,7 +155,7 @@ function QuickStart() {
borderTop={1}
/>
<WorkArrow
x={left + 2 * (192 + space) + 56 + taskLeftArrowWidth + 16 + 131 + 4}
x={left + 1 * (192 + space) + 56 + taskLeftArrowWidth + 16 + 131 + 4}
y={127}
width={taskRightArrowWidth}
height={156}

View File

@ -3,16 +3,16 @@
align-items: center;
justify-content: center;
height: 140px;
padding: 0 16px;
padding: 0 35px;
// 媒体查询
@media screen and (max-width: 1600px) {
flex: auto;
}
// // 媒体查询
// @media screen and (max-width: 1600px) {
// flex: auto;
// }
&__icon {
width: 63px;
margin-right: 16px;
width: 80px;
margin-right: 20px;
}
&__title {

View File

@ -7,6 +7,7 @@
height: 228px;
padding: 0 20px;
.backgroundFullImage(url(@/assets/img/user-points-bg.png));
margin-bottom: 16px;
&__label {
margin-top: 60px;

View File

@ -2,7 +2,7 @@
display: flex;
align-items: center;
margin-bottom: 16px;
padding: 0 30px;
padding: 20px 30px;
background-image: url(@/assets/img/workspace-intro.png);
background-repeat: no-repeat;
background-position: top right;

View File

@ -1,16 +1,15 @@
import { Button } from 'antd';
import styles from './index.less';
function WorkspaceIntro() {
return (
<div className={styles['workspace-intro']}>
<div className={styles['workspace-intro__left']}>
<div className={styles['workspace-intro__title']}></div>
<div className={styles['workspace-intro__title']}></div>
<div className={styles['workspace-intro__content']}>
</div>
<div className={styles['workspace-intro__buttons']}>
{/* <div className={styles['workspace-intro__buttons']}>
<Button
type="primary"
style={{ marginRight: '20px' }}
@ -40,7 +39,7 @@ function WorkspaceIntro() {
>
</Button>
</div>
</div> */}
</div>
<div className={styles['workspace-intro__right']}>
<img

View File

@ -27,7 +27,7 @@
&__statistics {
flex: none;
min-width: 431px;
// min-width: 500px;
background: linear-gradient(
123.08deg,
rgba(138, 138, 138, 0.06) 1.32%,
@ -35,10 +35,10 @@
);
border-radius: 4px;
// 媒体查询
@media screen and (max-width: 1600px) {
flex: 1;
}
// // 媒体查询
// @media screen and (max-width: 1600px) {
// flex: 1;
// }
}
}
}

View File

@ -1,18 +1,17 @@
import { useDraggable } from '@/hooks/useDraggable';
// import { useDraggable } from '@/hooks/useDraggable';
import { getWorkspaceOverviewReq } from '@/services/workspace';
import { ExperimentInstance } from '@/types';
import { to } from '@/utils/promise';
import { Divider, Flex } from 'antd';
import { useEffect, useState } from 'react';
import Draggable from 'react-draggable';
// import Draggable from 'react-draggable';
import AssetsManagement from './components/AssetsManagement';
import ExperimentChart, { type ExperimentStatistics } from './components/ExperimentChart';
import ExperitableTable from './components/ExperimentTable';
import QuickStart from './components/QuickStart';
import RobotFrame from './components/RobotFrame';
// import RobotFrame from './components/RobotFrame';
import TotalStatistics from './components/TotalStatistics';
import UserPoints from './components/UserPoints';
import UserSpace from './components/UserSpace';
import WorkspaceIntro from './components/WorkspaceIntro';
import styles from './index.less';
@ -25,10 +24,10 @@ type OverviewData = {
function Workspace() {
const [overviewData, setOverviewData] = useState<OverviewData>();
const [robotFrameVisible, setRobotFrameVisible] = useState(false);
const { handleStart, handleStop, handleDrag, handleClick } = useDraggable(() =>
setRobotFrameVisible((prev) => !prev),
);
// const [robotFrameVisible, setRobotFrameVisible] = useState(false);
// const { handleStart, handleStop, handleDrag, handleClick } = useDraggable(() =>
// setRobotFrameVisible((prev) => !prev),
// );
const users: number[] = new Array(8).fill(0);
useEffect(() => {
@ -67,7 +66,6 @@ function Workspace() {
count={overviewData?.runningExperimentInsCount}
/>
</Flex>
<ExperitableTable
tableData={overviewData?.latestExperimentInsList || []}
></ExperitableTable>
@ -76,16 +74,16 @@ function Workspace() {
)}
</div>
</div>
<UserPoints />
</Flex>
<div className={styles['workspace__quick-start']}>
<QuickStart></QuickStart>
<div className={styles['workspace__user']}>
<UserSpace users={users}></UserSpace>
<UserPoints />
{/* <UserSpace users={users}></UserSpace> */}
<AssetsManagement></AssetsManagement>
</div>
</div>
<Draggable onStart={handleStart} onStop={handleStop} onDrag={handleDrag} bounds="body">
{/* <Draggable onStart={handleStart} onStop={handleStop} onDrag={handleDrag} bounds="body">
<img
className={styles['workspace__robot-img']}
src={require('@/assets/img/robot.png')}
@ -98,7 +96,7 @@ function Workspace() {
<RobotFrame
visible={robotFrameVisible}
onClose={() => setRobotFrameVisible(false)}
></RobotFrame>
></RobotFrame> */}
</div>
);
}

View File

@ -7,10 +7,11 @@
import { request } from '@umijs/max';
// 分页查询超参数自动寻优
export function getActiveLearnListReq(params) {
export function getActiveLearnListReq(params, skipLoading) {
return request(`/api/mmp/activeLearn`, {
method: 'GET',
params,
skipLoading
});
}
@ -54,10 +55,11 @@ export function runActiveLearnReq(id) {
// ----------------------- 实验实例 -----------------------
// 获取实验实例列表
export function getActiveLearnInsListReq(params) {
export function getActiveLearnInsListReq(params, skipLoading) {
return request(`/api/mmp/activeLearnIns`, {
method: 'GET',
params,
skipLoading
});
}
@ -99,3 +101,12 @@ export function getExpMetricsReq(data) {
});
}
// 编辑实验实例
export function editActiveLearnInsReq(data) {
return request(`/api/mmp/activeLearnIns`, {
method: 'PUT',
data,
skipLoading: true,
});
}

View File

@ -7,10 +7,11 @@
import { request } from '@umijs/max';
// 分页查询自动学习
export function getAutoMLListReq(params) {
export function getAutoMLListReq(params, skipLoading) {
return request(`/api/mmp/machineLearn`, {
method: 'GET',
params,
skipLoading,
});
}
@ -54,10 +55,11 @@ export function runAutoMLReq(id) {
// ----------------------- 实验实例 -----------------------
// 获取实验实例列表
export function getExperimentInsListReq(params) {
export function getExperimentInsListReq(params, skipLoading) {
return request(`/api/mmp/machineLearnIns`, {
method: 'GET',
params,
skipLoading,
});
}
@ -89,3 +91,12 @@ export function batchDeleteExperimentInsReq(data) {
data,
});
}
// 编辑实验实例
export function editExperimentInsReq(data) {
return request(`/api/mmp/machineLearnIns`, {
method: 'PUT',
data,
skipLoading: true,
});
}

View File

@ -58,6 +58,14 @@ export function addDatasetVersion(data) {
});
}
// 编辑数据集版本
export function editDatasetVersion(data) {
return request(`/api/mmp/newdataset/updateVersionDesc`, {
method: 'PUT',
data,
});
}
// 下载数据集所有文件
export function downloadAllFiles(params) {
return request(`/api/mmp/newdataset/downloadAllFiles`, {
@ -132,6 +140,15 @@ export function addModelVersion(data) {
});
}
// 编辑模型版本
export function editModelVersion(data) {
return request(`/api/mmp/newmodel/updateVersionDesc`, {
method: 'PUT',
data,
});
}
// 删除模型版本
export function deleteModelVersion(params) {
return request(`/api/mmp/newmodel/deleteVersion`, {

View File

@ -1,9 +1,10 @@
import { request } from '@umijs/max';
// 查询实验列表
export function getExperiment(params) {
export function getExperiment(params, skipLoading) {
return request(`/api/mmp/experiment`, {
method: 'GET',
params,
skipLoading,
});
}
// 运行实验
@ -28,10 +29,11 @@ export function deleteExperimentById(id) {
});
}
// 根据id查询实验实例
export function getQueryByExperimentId(params) {
export function getQueryByExperimentId(params, skipLoading) {
return request(`/api/mmp/experimentIns`, {
method: 'GET',
params,
skipLoading
});
}
// 根据id删除实验实例
@ -53,6 +55,16 @@ export function putQueryByExperimentInsId(id) {
method: 'PUT',
});
}
// 编辑实验实例
export function editExperimentInsReq(data) {
return request(`/api/mmp/experimentIns`, {
method: 'PUT',
data,
skipLoading: true,
});
}
// 查询实验实例实时日志
export function getQueryByExperimentLog(data) {
return request('/api/mmp/experimentIns/realTimeLog/', {

View File

@ -7,10 +7,11 @@
import { request } from '@umijs/max';
// 分页查询超参数自动寻优
export function getRayListReq(params) {
export function getRayListReq(params, skipLoading) {
return request(`/api/mmp/ray`, {
method: 'GET',
params,
skipLoading
});
}
@ -54,10 +55,11 @@ export function runRayReq(id) {
// ----------------------- 实验实例 -----------------------
// 获取实验实例列表
export function getRayInsListReq(params) {
export function getRayInsListReq(params, skipLoading) {
return request(`/api/mmp/rayIns`, {
method: 'GET',
params,
skipLoading,
});
}
@ -97,3 +99,13 @@ export function getExpMetricsReq(data) {
data,
});
}
// 编辑实验实例
export function editRayInsReq(data) {
return request(`/api/mmp/rayIns`, {
method: 'PUT',
data,
skipLoading: true,
});
}

View File

@ -70,9 +70,10 @@ export function updateRoleDataScope(data: Record<string, any>) {
}
// 角色状态修改
export function changeRoleStatus(roleId: number, status: string) {
export function changeRoleStatus(roleId: number, roleKey: string, status: string) {
const data = {
roleId,
roleKey,
status,
};
return request<API.Result>('/api/system/role/changeStatus', {

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