Compare commits

...

279 Commits

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

+ 12
- 1
k8s/template-yaml/k8s-5auth.yaml View File

@@ -18,6 +18,11 @@ spec:
image: ${k8s-5auth-image} image: ${k8s-5auth-image}
ports: ports:
- containerPort: 9200 - 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 apiVersion: v1
@@ -28,9 +33,15 @@ metadata:
spec: spec:
type: NodePort type: NodePort
ports: ports:
- port: 9200
- name: http
port: 9200
nodePort: 31206 nodePort: 31206
protocol: TCP protocol: TCP
- name: debug
nodePort: 31221
port: 5005
protocol: TCP
targetPort: 5005
selector: selector:
app: ci4s-auth app: ci4s-auth



+ 27
- 5
react-ui/config/routes.ts View File

@@ -141,12 +141,23 @@ export default [
{ {
name: '实验对比', name: '实验对比',
path: 'compare', 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: '实验实例详情', name: '实验实例详情',
path: 'instance/:experimentId/:id', path: 'instance/:experimentId/:id',
component: './HyperParameter/Instance/index',
routes: [
{
name: '实验实例详情',
path: '',
component: './HyperParameter/Instance/index',
},
{
name: '可视化对比',
path: 'compare-visual',
component: './HyperParameter/Aim/index',
},
],
}, },
], ],
}, },


BIN
react-ui/public/assets/材料科研软件平台使用文档-v1.0.pdf View File


BIN
react-ui/public/assets/材料科研软件平台使用文档.pdf View File


+ 2
- 0
react-ui/src/app.tsx View File

@@ -21,6 +21,7 @@ import {
} from './services/session'; } from './services/session';
import './styles/menu.less'; import './styles/menu.less';
import { needAuth } from './utils'; import { needAuth } from './utils';
// import { closeAllModals } from './utils/modal';
import { gotoLoginPage } from './utils/ui'; import { gotoLoginPage } from './utils/ui';
export { requestConfig as request } from './requestConfig'; export { requestConfig as request } from './requestConfig';


@@ -96,6 +97,7 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => {
}, },
onPageChange: () => { onPageChange: () => {
const { location } = history; const { location } = history;
// closeAllModals();
// 如果没有登录,重定向到 login // 如果没有登录,重定向到 login
if (!initialState?.currentUser && needAuth(location.pathname)) { if (!initialState?.currentUser && needAuth(location.pathname)) {
gotoLoginPage(); gotoLoginPage();


+ 45
- 14
react-ui/src/components/IFramePage/index.tsx View File

@@ -1,12 +1,11 @@
import FullScreenFrame from '@/components/FullScreenFrame'; import FullScreenFrame from '@/components/FullScreenFrame';
import KFSpin from '@/components/KFSpin';
import { getKnowledgeGraphUrl, getLabelStudioUrl } from '@/services/developmentEnvironment'; import { getKnowledgeGraphUrl, getLabelStudioUrl } from '@/services/developmentEnvironment';
import Loading from '@/utils/loading';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage'; import SessionStorage from '@/utils/sessionStorage';
import { FloatButton } from 'antd'; import { FloatButton } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import './index.less'; import './index.less';


export enum IframePageType { export enum IframePageType {
@@ -54,9 +53,13 @@ const getRequestAPI = (type: IframePageType): (() => Promise<any>) => {


type IframePageProps = { type IframePageProps = {
/** 子系统 */ /** 子系统 */
type: IframePageType;
type?: IframePageType;
/** url */
url?: string;
/** 是否可以在页签上打开 */ /** 是否可以在页签上打开 */
openInTab?: boolean; openInTab?: boolean;
/** 是否显示加载 */
showLoading?: boolean;
/** 自定义样式类名 */ /** 自定义样式类名 */
className?: string; className?: string;
/** 自定义样式 */ /** 自定义样式 */
@@ -64,32 +67,60 @@ type IframePageProps = {
}; };


/** 系统内嵌 iframe,目前系统有数据标注、应用开发、开发环境、GitLink 四个子系统,使用时可以添加其他子系统 */ /** 系统内嵌 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 [iframeUrl, setIframeUrl] = useState('');
const [loading, setLoading] = useState(false);
// const [loading, setLoading] = useState(false);


useEffect(() => { useEffect(() => {
const requestIframeUrl = async () => {
setLoading(true);
const requestIframeUrl = async (type: IframePageType) => {
if (showLoading) {
Loading.show();
}
const [res] = await to(getRequestAPI(type)()); const [res] = await to(getRequestAPI(type)());
if (res && res.data) { if (res && res.data) {
setIframeUrl(res.data); setIframeUrl(res.data);
} else { } else {
setLoading(false);
if (showLoading) {
Loading.hide();
}
} }
}; };


requestIframeUrl();
}, [type]);
if (type) {
requestIframeUrl(type);
} else if (url) {
if (showLoading) {
Loading.show();
}

setIframeUrl(url);
}
}, [type, url, showLoading]);

const handleLoad = () => {
if (showLoading) {
Loading.hide();
}
};


const hideLoading = () => {
setLoading(false);
const handleError = (error?: React.SyntheticEvent<HTMLIFrameElement, Event>) => {
console.log('error', error);
if (showLoading) {
Loading.hide();
}
}; };


return ( return (
<div className={classNames('kf-iframe-page', className)} style={style}> <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')} />} {openInTab && <FloatButton onClick={() => window.open(iframeUrl, '_blank')} />}
</div> </div>
); );


+ 6
- 7
react-ui/src/components/ResourceSelectorModal/config.tsx View File

@@ -1,7 +1,7 @@
import datasetImg from '@/assets/img/modal-select-dataset.png'; import datasetImg from '@/assets/img/modal-select-dataset.png';
import mirrorImg from '@/assets/img/modal-select-mirror.png'; import mirrorImg from '@/assets/img/modal-select-mirror.png';
import modelImg from '@/assets/img/modal-select-model.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 { ResourceData, ResourceVersionData } from '@/pages/Dataset/config';
import { MirrorVersionData } from '@/pages/Mirror/Info'; import { MirrorVersionData } from '@/pages/Mirror/Info';
import { MirrorData } from '@/pages/Mirror/List'; 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) => ({ return list.map((v) => ({
...v, ...v,
key: `${v.id}`, key: `${v.id}`,
title: v.name,
title: isPublic ? `${v.name} (${v.owner})` : v.name,
isLeaf: false, isLeaf: false,
checkable: false, checkable: false,
})); }));
@@ -106,7 +106,7 @@ export class DatasetSelector implements SelectorTypeInfo {
const res = await getDatasetList({ is_public: isPublic, page: 0, size: 2000 }); const res = await getDatasetList({ is_public: isPublic, page: 0, size: 2000 });
if (res && res.data) { if (res && res.data) {
const list = res.data.content || []; const list = res.data.content || [];
return convertDatasetToTreeData(list);
return convertDatasetToTreeData(list, isPublic);
} else { } else {
return Promise.reject('获取数据集列表失败'); return Promise.reject('获取数据集列表失败');
} }
@@ -158,7 +158,7 @@ export class ModelSelector implements SelectorTypeInfo {
const res = await getModelList({ is_public: isPublic, page: 0, size: 2000 }); const res = await getModelList({ is_public: isPublic, page: 0, size: 2000 });
if (res && res.data) { if (res && res.data) {
const list = res.data.content || []; const list = res.data.content || [];
return convertDatasetToTreeData(list);
return convertDatasetToTreeData(list, isPublic);
} else { } else {
return Promise.reject('获取模型列表失败'); return Promise.reject('获取模型列表失败');
} }
@@ -224,8 +224,7 @@ export class MirrorSelector implements SelectorTypeInfo {
image_id: parentKey, image_id: parentKey,
page: 0, page: 0,
size: 2000, size: 2000,
status: 'available',
state: 1,
status: MirrorVersionStatus.Available,
}); });
if (res && res.data) { if (res && res.data) {
const list = res.data.content || []; const list = res.data.content || [];


+ 11
- 4
react-ui/src/components/RightContent/AvatarDropdown.tsx View File

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


+ 10
- 1
react-ui/src/components/RunDuration/index.tsx View File

@@ -10,13 +10,21 @@ type RunDurationProps = {
}; };
function RunDuration({ createTime, finishTime, className, style }: RunDurationProps) { function RunDuration({ createTime, finishTime, className, style }: RunDurationProps) {
const [now] = useServerTime(); 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(() => { useEffect(() => {
if (finishTime) { if (finishTime) {
setCurrentTime(new Date(finishTime)); setCurrentTime(new Date(finishTime));
} else { } else {
setCurrentTime(now());
const timer = setInterval(() => { const timer = setInterval(() => {
setCurrentTime(now()); setCurrentTime(now());
}, 1000); }, 1000);
@@ -25,6 +33,7 @@ function RunDuration({ createTime, finishTime, className, style }: RunDurationPr
}; };
} }
}, [finishTime, now]); }, [finishTime, now]);

return ( return (
<span className={className} style={style}> <span className={className} style={style}>
{elapsedTime(createTime, currentTime)} {elapsedTime(createTime, currentTime)}


+ 3
- 3
react-ui/src/enums/index.ts View File

@@ -33,7 +33,7 @@ export enum TensorBoardStatus {
Unknown = 'Unknown', // 未知 Unknown = 'Unknown', // 未知
Pending = 'Pending', // 启动中 Pending = 'Pending', // 启动中
Running = 'Running', // 运行中 Running = 'Running', // 运行中
Terminated = 'Terminated', // 未启动或者已终止
Terminated = 'Terminated', // 未启动
Failed = 'Failed', // 失败 Failed = 'Failed', // 失败
} }


@@ -95,8 +95,8 @@ export enum AutoMLType {


export const autoMLTypeOptions = [ export const autoMLTypeOptions = [
{ label: '表格', value: AutoMLType.Table }, { label: '表格', value: AutoMLType.Table },
{ label: '文本分类', value: AutoMLType.Text },
{ label: '视频分类', value: AutoMLType.Video },
{ label: '文本', value: AutoMLType.Text },
{ label: '视频', value: AutoMLType.Video },
]; ];


// 自动化任务类型 // 自动化任务类型


+ 22
- 10
react-ui/src/hooks/useSSE.ts View File

@@ -1,11 +1,24 @@
import { parseJsonText } from '@/utils';
import { useEffect } from 'react';
import { ExperimentStatus } from '@/enums'; import { ExperimentStatus } from '@/enums';
import { NodeStatus } from '@/types'; 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(() => { useEffect(() => {
if (isRunning) { if (isRunning) {
const { origin } = location; const { origin } = location;
@@ -22,8 +35,8 @@ export const useSSE = (experimentInsId: number, status: ExperimentStatus, name:
const dataJson = parseJsonText(data); const dataJson = parseJsonText(data);
const statusData = dataJson?.result?.object?.status; const statusData = dataJson?.result?.object?.status;
if (statusData) { 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 () => { return () => {
evtSource.close(); evtSource.close();
}
};
} }
}, [experimentInsId, isRunning, name, namespace, onMessage]);
}, [experimentId, experimentInsId, isRunning, name, namespace, onMessage]);
}; };

+ 8
- 10
react-ui/src/pages/ActiveLearn/Instance/index.tsx View File

@@ -51,13 +51,12 @@ function ActiveLearnInstance() {
const [res] = await to(getActiveLearnInsReq(instanceId)); const [res] = await to(getActiveLearnInsReq(instanceId));
if (res && res.data) { if (res && res.data) {
const info = res.data as ActiveLearnInstanceData; 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); const paramJson = parseJsonText(param);
if (paramJson) { if (paramJson) {
setExperimentInfo({ setExperimentInfo({
...paramJson.data, ...paramJson.data,
create_time,
}); });
} }


@@ -69,7 +68,7 @@ function ActiveLearnInstance() {
return; return;
} }


// 进行节点状态
// 设置总 workflow 状态
const nodeStatusJson = parseJsonText(node_status); const nodeStatusJson = parseJsonText(node_status);
if (nodeStatusJson) { if (nodeStatusJson) {
setNodes(nodeStatusJson); setNodes(nodeStatusJson);
@@ -106,18 +105,17 @@ function ActiveLearnInstance() {
if (dataJson) { if (dataJson) {
const nodes = dataJson?.result?.object?.status?.nodes; const nodes = dataJson?.result?.object?.status?.nodes;
if (nodes) { if (nodes) {
// 节点
// 设置节点
setNodes(nodes); setNodes(nodes);


// 设置总 workflow 状态
const workflowStatus = Object.values(nodes).find((node: any) => const workflowStatus = Object.values(nodes).find((node: any) =>
node.displayName.startsWith(NodePrefix), node.displayName.startsWith(NodePrefix),
) as NodeStatus; ) as NodeStatus;

// 设置工作流状态
if (workflowStatus) { if (workflowStatus) {
setWorkflowStatus(workflowStatus); setWorkflowStatus(workflowStatus);


// 实验结束,关闭 SSE
// 实验结束,关闭 SSE,获取实验实例结果
if ( if (
workflowStatus.phase !== ExperimentStatus.Pending && workflowStatus.phase !== ExperimentStatus.Pending &&
workflowStatus.phase !== ExperimentStatus.Running workflowStatus.phase !== ExperimentStatus.Running
@@ -152,8 +150,8 @@ function ActiveLearnInstance() {
<ActiveLearnBasic <ActiveLearnBasic
className={styles['active-learn-instance__basic']} className={styles['active-learn-instance__basic']}
info={experimentInfo} info={experimentInfo}
runStatus={workflowStatus}
instanceStatus={instanceInfo?.status}
workflowStatus={workflowStatus}
instanceStatus={instanceInfo?.status as ExperimentStatus}
isInstance isInstance
/> />
), ),
@@ -181,7 +179,7 @@ function ActiveLearnInstance() {
}, },
{ {
key: TabKeys.History, key: TabKeys.History,
label: '训练列表',
label: '运行列表',
icon: <KFIcon type="icon-Trialliebiao" />, icon: <KFIcon type="icon-Trialliebiao" />,
children: ( children: (
<ExperimentHistory <ExperimentHistory


+ 5
- 9
react-ui/src/pages/ActiveLearn/components/ActiveLearnBasic/index.tsx View File

@@ -28,14 +28,14 @@ type BasicInfoProps = {
info?: ActiveLearnData; info?: ActiveLearnData;
className?: string; className?: string;
isInstance?: boolean; isInstance?: boolean;
runStatus?: NodeStatus;
workflowStatus?: NodeStatus;
instanceStatus?: ExperimentStatus; instanceStatus?: ExperimentStatus;
}; };


function BasicInfo({ function BasicInfo({
info, info,
className, className,
runStatus,
workflowStatus,
instanceStatus, instanceStatus,
isInstance = false, isInstance = false,
}: BasicInfoProps) { }: BasicInfoProps) {
@@ -154,7 +154,7 @@ function BasicInfo({
value: info.dataset_py, value: info.dataset_py,
}, },
{ {
label: '数据集类名',
label: '数据集处理类名',
value: info.dataset_class_name, value: info.dataset_class_name,
}, },
{ {
@@ -212,12 +212,8 @@ function BasicInfo({


return ( return (
<div className={classNames(styles['active-learn-basic'], className)}> <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 && ( {!isInstance && (
<ConfigInfo <ConfigInfo


+ 14
- 4
react-ui/src/pages/ActiveLearn/components/CreateForm/ExecuteConfig.tsx View File

@@ -17,6 +17,11 @@ import {


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


+ 4
- 0
react-ui/src/pages/ActiveLearn/components/CreateForm/utils.ts View File

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

+ 7
- 2
react-ui/src/pages/ActiveLearn/components/ExperimentLog/index.tsx View File

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


+ 11
- 9
react-ui/src/pages/AutoML/Instance/index.tsx View File

@@ -51,7 +51,7 @@ function AutoMLInstance() {
const [res] = await to(getExperimentInsReq(instanceId)); const [res] = await to(getExperimentInsReq(instanceId));
if (res && res.data) { if (res && res.data) {
const info = res.data as AutoMLInstanceData; 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); setType(type);
// 解析配置参数 // 解析配置参数
@@ -59,7 +59,6 @@ function AutoMLInstance() {
if (paramJson) { if (paramJson) {
setAutoMLInfo({ setAutoMLInfo({
...paramJson.data, ...paramJson.data,
create_time,
type, type,
}); });
} }
@@ -95,7 +94,10 @@ function AutoMLInstance() {
}; };


const setupSSE = (name: string, namespace: string) => { 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 params = encodeURIComponent(`metadata.namespace=${namespace},metadata.name=${name}`);
const evtSource = new EventSource( const evtSource = new EventSource(
`${origin}/api/v1/realtimeStatus?listOptions.fieldSelector=${params}`, `${origin}/api/v1/realtimeStatus?listOptions.fieldSelector=${params}`,
@@ -110,17 +112,17 @@ function AutoMLInstance() {
if (dataJson) { if (dataJson) {
const nodes = dataJson?.result?.object?.status?.nodes; const nodes = dataJson?.result?.object?.status?.nodes;
if (nodes) { if (nodes) {
// 节点
// 设置节点
setNodes(nodes); setNodes(nodes);


// 设置总 workflow 状态
const workflowStatus = Object.values(nodes).find((node: any) => const workflowStatus = Object.values(nodes).find((node: any) =>
node.displayName.startsWith(NodePrefix), node.displayName.startsWith(NodePrefix),
) as NodeStatus; ) as NodeStatus;

if (workflowStatus) { if (workflowStatus) {
setWorkflowStatus(workflowStatus); setWorkflowStatus(workflowStatus);


// 实验结束,关闭 SSE
// 实验结束,关闭 SSE,获取实验实例结果
if ( if (
workflowStatus.phase !== ExperimentStatus.Pending && workflowStatus.phase !== ExperimentStatus.Pending &&
workflowStatus.phase !== ExperimentStatus.Running workflowStatus.phase !== ExperimentStatus.Running
@@ -155,8 +157,8 @@ function AutoMLInstance() {
<AutoMLBasic <AutoMLBasic
className={styles['auto-ml-instance__basic']} className={styles['auto-ml-instance__basic']}
info={autoMLInfo} info={autoMLInfo}
runStatus={workflowStatus}
instanceStatus={instanceInfo?.status}
workflowStatus={workflowStatus}
instanceStatus={instanceInfo?.status as ExperimentStatus}
isInstance isInstance
/> />
), ),
@@ -202,7 +204,7 @@ function AutoMLInstance() {
} }
: { : {
key: TabKeys.History, key: TabKeys.History,
label: '试验列表',
label: '运行列表',
icon: <KFIcon type="icon-Trialliebiao" />, icon: <KFIcon type="icon-Trialliebiao" />,
children: ( children: (
<ExperimentHistory <ExperimentHistory


+ 1
- 1
react-ui/src/pages/AutoML/List/index.tsx View File

@@ -1,7 +1,7 @@
/* /*
* @Author: 赵伟 * @Author: 赵伟
* @Date: 2024-04-16 13:58:08 * @Date: 2024-04-16 13:58:08
* @Description: 自机器学习列表
* @Description: 自机器学习列表
*/ */


import ExperimentList, { ExperimentListType } from '../components/ExperimentList'; import ExperimentList, { ExperimentListType } from '../components/ExperimentList';


+ 44
- 9
react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx View File

@@ -7,10 +7,21 @@ import {
autoMLTaskTypeOptions, autoMLTaskTypeOptions,
} from '@/enums'; } from '@/enums';
import { useComputingResource } from '@/hooks/useComputingResource'; import { useComputingResource } from '@/hooks/useComputingResource';
import {
classificationAlgorithms,
featureAlgorithms,
regressorAlgorithms,
} from '@/pages/AutoML/components/CreateForm/utils';
import { AutoMLData } from '@/pages/AutoML/types'; import { AutoMLData } from '@/pages/AutoML/types';
import { type NodeStatus } from '@/types'; import { type NodeStatus } from '@/types';
import { parseJsonText } from '@/utils'; 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 classNames from 'classnames';
import { useMemo } from 'react'; import { useMemo } from 'react';
import ExperimentRunBasic from '../ExperimentRunBasic'; import ExperimentRunBasic from '../ExperimentRunBasic';
@@ -21,6 +32,7 @@ const formatOptimizeMode = (value: boolean) => {
return value ? '越大越好' : '越小越好'; return value ? '越大越好' : '越小越好';
}; };


// 格式化权重
const formatMetricsWeight = (value: string) => { const formatMetricsWeight = (value: string) => {
if (!value) { if (!value) {
return '--'; return '--';
@@ -34,18 +46,33 @@ const formatMetricsWeight = (value: string) => {
.join('\n'); .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 = { type AutoMLBasicProps = {
info?: AutoMLData; info?: AutoMLData;
className?: string; className?: string;
isInstance?: boolean; isInstance?: boolean;
runStatus?: NodeStatus;
workflowStatus?: NodeStatus;
instanceStatus?: ExperimentStatus; instanceStatus?: ExperimentStatus;
instanceCreateTime?: string;
}; };


function AutoMLBasic({ function AutoMLBasic({
info, info,
className, className,
runStatus,
workflowStatus,
instanceStatus, instanceStatus,
isInstance = false, isInstance = false,
}: AutoMLBasicProps) { }: AutoMLBasicProps) {
@@ -95,10 +122,12 @@ function AutoMLBasic({
{ {
label: '特征预处理算法', label: '特征预处理算法',
value: info.include_feature_preprocessor, value: info.include_feature_preprocessor,
format: formatAlgorithm(featureAlgorithms),
}, },
{ {
label: '排除的特征预处理算法', label: '排除的特征预处理算法',
value: info.exclude_feature_preprocessor, value: info.exclude_feature_preprocessor,
format: formatAlgorithm(featureAlgorithms),
}, },
{ {
label: info.task_type === AutoMLTaskType.Regression ? '回归算法' : '分类算法', label: info.task_type === AutoMLTaskType.Regression ? '回归算法' : '分类算法',
@@ -106,6 +135,11 @@ function AutoMLBasic({
info.task_type === AutoMLTaskType.Regression info.task_type === AutoMLTaskType.Regression
? info.include_regressor ? info.include_regressor
: info.include_classifier, : info.include_classifier,
format: formatAlgorithm(
info.task_type === AutoMLTaskType.Regression
? regressorAlgorithms
: classificationAlgorithms,
),
}, },
{ {
label: info.task_type === AutoMLTaskType.Regression ? '排除的回归算法' : '排除的分类算法', label: info.task_type === AutoMLTaskType.Regression ? '排除的回归算法' : '排除的分类算法',
@@ -113,6 +147,11 @@ function AutoMLBasic({
info.task_type === AutoMLTaskType.Regression info.task_type === AutoMLTaskType.Regression
? info.exclude_regressor ? info.exclude_regressor
: info.exclude_classifier, : info.exclude_classifier,
format: formatAlgorithm(
info.task_type === AutoMLTaskType.Regression
? regressorAlgorithms
: classificationAlgorithms,
),
}, },
{ {
label: '集成方式', label: '集成方式',
@@ -292,12 +331,8 @@ function AutoMLBasic({


return ( return (
<div className={classNames(styles['auto-ml-basic'], className)}> <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 && ( {!isInstance && (
<ConfigInfo <ConfigInfo


+ 6
- 67
react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfig.tsx View File

@@ -8,69 +8,7 @@ import {
autoMLTaskTypeOptions, autoMLTaskTypeOptions,
} from '@/enums'; } from '@/enums';
import { Col, Form, InputNumber, Radio, Row, Select, Switch } from 'antd'; 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 = [ export const classificationMetrics = [
@@ -280,9 +218,9 @@ function ExecuteConfig() {
<Form.Item <Form.Item
label="集成模型数量" label="集成模型数量"
name="ensemble_size" name="ensemble_size"
tooltip="集成模型数量,如果设置为0,则没有集成。默认50"
tooltip="集成模型数量,必须是大于等于1的整数,默认50"
> >
<InputNumber placeholder="请输入集成模型数量" min={0} precision={0} />
<InputNumber placeholder="请输入集成模型数量" min={1} precision={0} />
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
@@ -292,7 +230,7 @@ function ExecuteConfig() {
<Form.Item <Form.Item
label="集成最佳模型数量" label="集成最佳模型数量"
name="ensemble_nbest" name="ensemble_nbest"
tooltip="仅集成最佳的N个模型"
tooltip="仅集成最佳的N个模型,必须是大于等于1的整数"
> >
<InputNumber placeholder="请输入集成最佳模型数量" min={1} precision={0} /> <InputNumber placeholder="请输入集成最佳模型数量" min={1} precision={0} />
</Form.Item> </Form.Item>
@@ -419,6 +357,7 @@ function ExecuteConfig() {
<Form.Item <Form.Item
label="交叉验证折数" label="交叉验证折数"
name="folds" name="folds"
tooltip="交叉验证折数必须是大于等于2的整数"
rules={[ rules={[
{ {
required: true, required: true,
@@ -426,7 +365,7 @@ function ExecuteConfig() {
}, },
]} ]}
> >
<InputNumber placeholder="请输入交叉验证折数" min={1} precision={0} />
<InputNumber placeholder="请输入交叉验证折数" min={2} precision={0} />
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>


+ 85
- 0
react-ui/src/pages/AutoML/components/CreateForm/utils.ts 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' },
];

+ 0
- 4
react-ui/src/pages/AutoML/components/ExperimentInstanceList/index.less View File

@@ -54,10 +54,6 @@
display: flex; display: flex;
align-items: center; align-items: center;
width: 200px; width: 200px;

.statusIcon {
visibility: visible;
}
} }
} }




+ 16
- 8
react-ui/src/pages/AutoML/components/ExperimentInstanceList/index.tsx View File

@@ -34,7 +34,14 @@ function ExperimentInstanceList({
}: ExperimentInstanceListProps) { }: ExperimentInstanceListProps) {
const { message } = App.useApp(); const { message } = App.useApp();
const allIntanceIds = useMemo(() => { 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]); }, [experimentInsList]);
const [ const [
selectedIns, selectedIns,
@@ -126,7 +133,12 @@ function ExperimentInstanceList({
<div> <div>
<div className={styles.tableExpandBox} style={{ paddingBottom: '16px' }}> <div className={styles.tableExpandBox} style={{ paddingBottom: '16px' }}>
<div className={styles.check}> <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>
<div className={styles.index}>序号</div> <div className={styles.index}>序号</div>
<div className={styles.description}>运行时长</div> <div className={styles.description}>运行时长</div>
@@ -171,12 +183,8 @@ function ExperimentInstanceList({
{index + 1} {index + 1}
</a> </a>
<ExperimentInstanceComponent <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> ></ExperimentInstanceComponent>
<div className={styles.operation}> <div className={styles.operation}>
<Button <Button


+ 35
- 32
react-ui/src/pages/AutoML/components/ExperimentInstanceList/instance.tsx View File

@@ -2,67 +2,70 @@ import RunDuration from '@/components/RunDuration';
import { ExperimentStatus } from '@/enums'; import { ExperimentStatus } from '@/enums';
import { useSSE, type MessageHandler } from '@/hooks/useSSE'; import { useSSE, type MessageHandler } from '@/hooks/useSSE';
import { experimentStatusInfo } from '@/pages/Experiment/status'; import { experimentStatusInfo } from '@/pages/Experiment/status';
import { ExperimentInstance, NodeStatus } from '@/types';
import { ExperimentCompleted } from '@/utils/constant'; import { ExperimentCompleted } from '@/utils/constant';
import { formatDate } from '@/utils/date'; import { formatDate } from '@/utils/date';
import { getWorkflowStatus } from '@/utils/experiment';
import { Typography } from 'antd'; import { Typography } from 'antd';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import styles from './index.less'; 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( const handleSSEMessage: MessageHandler = useCallback(
(experimentInsId: number, status: string, finish_time: string) => {
(experimentId: number, experimentInsId: number, status: string, finishTime: string) => {
window.postMessage({ window.postMessage({
type: ExperimentCompleted, type: ExperimentCompleted,
payload: { payload: {
id: experimentInsId,
experimentId,
experimentInsId,
status, 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 ( return (
<React.Fragment> <React.Fragment>
<div className={styles.description}> <div className={styles.description}>
<RunDuration createTime={create_time} finishTime={finish_time} />
<RunDuration createTime={createTime} finishTime={finishTime} />
</div> </div>
<div className={styles.startTime}> <div className={styles.startTime}>
<Typography.Text ellipsis={{ tooltip: formatDate(create_time) }}>
{formatDate(create_time)}
<Typography.Text ellipsis={{ tooltip: formatDate(createTime) }}>
{formatDate(createTime)}
</Typography.Text> </Typography.Text>
</div> </div>
<div className={styles.statusBox}> <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> </div>
</React.Fragment> </React.Fragment>
); );
} }


export default ExperimentInstance;
export default ExperimentInstanceComponent;

+ 14
- 3
react-ui/src/pages/AutoML/components/ExperimentList/config.ts View File

@@ -8,6 +8,7 @@ import {
batchDeleteActiveLearnInsReq, batchDeleteActiveLearnInsReq,
deleteActiveLearnInsReq, deleteActiveLearnInsReq,
deleteActiveLearnReq, deleteActiveLearnReq,
editActiveLearnInsReq,
getActiveLearnInsListReq, getActiveLearnInsListReq,
getActiveLearnListReq, getActiveLearnListReq,
runActiveLearnReq, runActiveLearnReq,
@@ -17,6 +18,7 @@ import {
batchDeleteExperimentInsReq, batchDeleteExperimentInsReq,
deleteAutoMLReq, deleteAutoMLReq,
deleteExperimentInsReq, deleteExperimentInsReq,
editExperimentInsReq,
getAutoMLListReq, getAutoMLListReq,
getExperimentInsListReq, getExperimentInsListReq,
runAutoMLReq, runAutoMLReq,
@@ -26,6 +28,7 @@ import {
batchDeleteRayInsReq, batchDeleteRayInsReq,
deleteRayInsReq, deleteRayInsReq,
deleteRayReq, deleteRayReq,
editRayInsReq,
getRayInsListReq, getRayInsListReq,
getRayListReq, getRayListReq,
runRayReq, runRayReq,
@@ -39,18 +42,20 @@ export enum ExperimentListType {
} }


type ExperimentListInfo = { 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>; // 删除 deleteRecordReq: (params: any) => Promise<any>; // 删除
runRecordReq: (params: any) => Promise<any>; // 运行 runRecordReq: (params: any) => Promise<any>; // 运行
deleteInsReq: (params: any) => Promise<any>; // 删除实例 deleteInsReq: (params: any) => Promise<any>; // 删除实例
batchDeleteInsReq: (params: any) => Promise<any>; // 批量删除实例 batchDeleteInsReq: (params: any) => Promise<any>; // 批量删除实例
stopInsReq: (params: any) => Promise<any>; // 终止实例 stopInsReq: (params: any) => Promise<any>; // 终止实例
editInsReq: (params: any) => Promise<any>; // 编辑实例
title: string; // 标题 title: string; // 标题
pathPrefix: string; // 路由路径前缀 pathPrefix: string; // 路由路径前缀
idProperty: string; // ID属性 idProperty: string; // ID属性
nameProperty: string; // 名称属性 nameProperty: string; // 名称属性
descProperty: string; // 描述属性 descProperty: string; // 描述属性
idInsProperty: string; // 实例返回的ID属性
}; };


export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo> = { export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo> = {
@@ -62,11 +67,13 @@ export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo
deleteInsReq: deleteExperimentInsReq, deleteInsReq: deleteExperimentInsReq,
batchDeleteInsReq: batchDeleteExperimentInsReq, batchDeleteInsReq: batchDeleteExperimentInsReq,
stopInsReq: stopExperimentInsReq, stopInsReq: stopExperimentInsReq,
title: '自主机器学习',
editInsReq: editExperimentInsReq,
title: '自动机器学习',
pathPrefix: 'automl', pathPrefix: 'automl',
nameProperty: 'name', nameProperty: 'name',
descProperty: 'description', descProperty: 'description',
idProperty: 'machineLearnId', idProperty: 'machineLearnId',
idInsProperty: 'machine_learn_id',
}, },
[ExperimentListType.HyperParameter]: { [ExperimentListType.HyperParameter]: {
getListReq: getRayListReq, getListReq: getRayListReq,
@@ -76,11 +83,13 @@ export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo
deleteInsReq: deleteRayInsReq, deleteInsReq: deleteRayInsReq,
batchDeleteInsReq: batchDeleteRayInsReq, batchDeleteInsReq: batchDeleteRayInsReq,
stopInsReq: stopRayInsReq, stopInsReq: stopRayInsReq,
editInsReq: editRayInsReq,
title: '超参数自动寻优', title: '超参数自动寻优',
pathPrefix: 'hyperparameter', pathPrefix: 'hyperparameter',
nameProperty: 'name', nameProperty: 'name',
descProperty: 'description', descProperty: 'description',
idProperty: 'rayId', idProperty: 'rayId',
idInsProperty: 'ray_id',
}, },
[ExperimentListType.ActiveLearn]: { [ExperimentListType.ActiveLearn]: {
getListReq: getActiveLearnListReq, getListReq: getActiveLearnListReq,
@@ -90,10 +99,12 @@ export const experimentListConfig: Record<ExperimentListType, ExperimentListInfo
deleteInsReq: deleteActiveLearnInsReq, deleteInsReq: deleteActiveLearnInsReq,
batchDeleteInsReq: batchDeleteActiveLearnInsReq, batchDeleteInsReq: batchDeleteActiveLearnInsReq,
stopInsReq: stopActiveLearnInsReq, stopInsReq: stopActiveLearnInsReq,
editInsReq: editActiveLearnInsReq,
title: '自动学习', title: '自动学习',
pathPrefix: 'active-learn', pathPrefix: 'active-learn',
nameProperty: 'name', nameProperty: 'name',
descProperty: 'description', descProperty: 'description',
idProperty: 'activeLearnId', idProperty: 'activeLearnId',
idInsProperty: 'active_learn_id',
}, },
}; };

+ 126
- 61
react-ui/src/pages/AutoML/components/ExperimentList/index.tsx View File

@@ -30,7 +30,7 @@ import {
} from 'antd'; } from 'antd';
import { type SearchProps } from 'antd/es/input'; import { type SearchProps } from 'antd/es/input';
import classNames from 'classnames'; import classNames from 'classnames';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import ExperimentInstanceList from '../ExperimentInstanceList'; import ExperimentInstanceList from '../ExperimentInstanceList';
import { ExperimentListType, experimentListConfig } from './config'; import { ExperimentListType, experimentListConfig } from './config';
import styles from './index.less'; import styles from './index.less';
@@ -52,7 +52,6 @@ function ExperimentList({ type }: ExperimentListProps) {
const [experimentInsList, setExperimentInsList] = useState<ExperimentInstanceData[]>([]); const [experimentInsList, setExperimentInsList] = useState<ExperimentInstanceData[]>([]);
const [expandedRowKeys, setExpandedRowKeys] = useState<number[]>([]); const [expandedRowKeys, setExpandedRowKeys] = useState<number[]>([]);
const [experimentInsTotal, setExperimentInsTotal] = useState(0); const [experimentInsTotal, setExperimentInsTotal] = useState(0);
const [now] = useServerTime();
const [pagination, setPagination] = useState<TablePaginationConfig>( const [pagination, setPagination] = useState<TablePaginationConfig>(
cacheState?.pagination ?? { cacheState?.pagination ?? {
current: 1, current: 1,
@@ -60,38 +59,37 @@ function ExperimentList({ type }: ExperimentListProps) {
}, },
); );
const config = experimentListConfig[type]; const config = experimentListConfig[type];
const timerRef = useRef<ReturnType<typeof window.setTimeout> | undefined>();

// 获取自主机器学习或超参数自动优化列表
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]);
const [now] = useServerTime();


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( const getExperimentInsList = useCallback(
async (recordId: number, page: number, size: number) => {
async (recordId: number, page: number, size: number, skipLoading: boolean = false) => {
const params = { const params = {
[config.idProperty]: recordId, [config.idProperty]: recordId,
page: page, page: page,
size: size, size: size,
}; };
const request = config.getInsListReq; const request = config.getInsListReq;
const [res] = await to(request(params));
const [res] = await to(request(params, skipLoading));
if (res && res.data) { if (res && res.data) {
const { content = [], totalElements = 0 } = res.data; const { content = [], totalElements = 0 } = res.data;
try { try {
@@ -111,59 +109,115 @@ function ExperimentList({ type }: ExperimentListProps) {


// 刷新实验列表状态, // 刷新实验列表状态,
// TODO: 目前是直接刷新实验列表,后续需要优化,只刷新状态 // TODO: 目前是直接刷新实验列表,后续需要优化,只刷新状态
const refreshExperimentList = useCallback(() => {
getAutoMLList();
}, [getAutoMLList]);
const refreshExperimentList = useCallback(
(skipLoading: boolean = false) => {
getExperimentList(skipLoading);
},
[getExperimentList],
);


// 刷新实验实例列表 // 刷新实验实例列表
const refreshExperimentIns = useCallback( const refreshExperimentIns = useCallback(
(experimentId: number) => {
(experimentId: number, skipLoading: boolean = false) => {
const length = experimentInsList.length; const length = experimentInsList.length;
getExperimentInsList(experimentId, 0, length);
getExperimentInsList(experimentId, 0, length, skipLoading);
}, },
[getExperimentInsList, experimentInsList], [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(() => { useEffect(() => {
const handleMessage = (e: MessageEvent) => { const handleMessage = (e: MessageEvent) => {
const { type, payload } = e.data; const { type, payload } = e.data;
if (type === ExperimentCompleted) { 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); window.addEventListener('message', handleMessage);
return () => { return () => {
window.removeEventListener('message', handleMessage); window.removeEventListener('message', handleMessage);
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = undefined;
}
}; };
}, [refreshExperimentList]);
}, [experimentInsList, editExperimentIns]);


// 搜索 // 搜索
const onSearch: SearchProps['onSearch'] = (value) => { const onSearch: SearchProps['onSearch'] = (value) => {
@@ -207,6 +261,7 @@ function ExperimentList({ type }: ExperimentListProps) {
setCacheState({ setCacheState({
pagination, pagination,
searchText, searchText,
expandedRowKeys,
}); });


if (record) { if (record) {
@@ -225,6 +280,7 @@ function ExperimentList({ type }: ExperimentListProps) {
setCacheState({ setCacheState({
pagination, pagination,
searchText, searchText,
expandedRowKeys,
}); });


navigate(`info/${record.id}`); navigate(`info/${record.id}`);
@@ -237,8 +293,8 @@ function ExperimentList({ type }: ExperimentListProps) {
if (res) { if (res) {
message.success('运行成功'); message.success('运行成功');
setExpandedRowKeys([record.id]); setExpandedRowKeys([record.id]);
refreshExperimentList();
getExperimentInsList(record.id, 0, 5);
// getExperimentInsList(record.id, 0, 5);
// refreshExperimentList();
} }
}; };


@@ -248,8 +304,8 @@ function ExperimentList({ type }: ExperimentListProps) {
setExperimentInsList([]); setExperimentInsList([]);
if (expanded) { if (expanded) {
setExpandedRowKeys([record.id]); setExpandedRowKeys([record.id]);
getExperimentInsList(record.id, 0, 5);
refreshExperimentList();
// getExperimentInsList(record.id, 0, 5);
// refreshExperimentList();
} else { } else {
setExpandedRowKeys([]); setExpandedRowKeys([]);
} }
@@ -257,6 +313,11 @@ function ExperimentList({ type }: ExperimentListProps) {


// 跳转到实验实例详情 // 跳转到实验实例详情
const gotoInstanceInfo = (autoML: AutoMLData, record: ExperimentInstanceData) => { const gotoInstanceInfo = (autoML: AutoMLData, record: ExperimentInstanceData) => {
setCacheState({
pagination,
searchText,
expandedRowKeys,
});
navigate(`instance/${autoML.id}/${record.id}`); navigate(`instance/${autoML.id}/${record.id}`);
}; };


@@ -269,8 +330,7 @@ function ExperimentList({ type }: ExperimentListProps) {


// 实验实例终止 // 实验实例终止
const handleInstanceTerminate = async (experimentIns: ExperimentInstanceData) => { const handleInstanceTerminate = async (experimentIns: ExperimentInstanceData) => {
// 刷新实验列表
refreshExperimentList();
// 修改实例的状态和结束时间
setExperimentInsList((prevList) => { setExperimentInsList((prevList) => {
return prevList.map((item) => { return prevList.map((item) => {
if (item.id === experimentIns.id) { if (item.id === experimentIns.id) {
@@ -283,6 +343,11 @@ function ExperimentList({ type }: ExperimentListProps) {
return item; return item;
}); });
}); });
// 刷新实验列表和实例列表
refreshExperimentList(true);
if (expandedRowKeys.length > 0) {
refreshExperimentIns(expandedRowKeys[0]);
}
}; };
// --------------------------- Table --------------------------- // --------------------------- Table ---------------------------
// 分页切换 // 分页切换
@@ -330,7 +395,7 @@ function ExperimentList({ type }: ExperimentListProps) {
}, },
...diffColumns, ...diffColumns,
{ {
title: '创建时间',
title: '更新时间',
dataIndex: 'update_time', dataIndex: 'update_time',
key: 'update_time', key: 'update_time',
width: '20%', width: '20%',


+ 7
- 0
react-ui/src/pages/AutoML/components/ExperimentLog/empty.tsx View File

@@ -0,0 +1,7 @@
import styles from './index.less';

function EmptyLog() {
return <div className={styles['empty-log']}>暂无日志</div>;
}

export default EmptyLog;

+ 11
- 0
react-ui/src/pages/AutoML/components/ExperimentLog/index.less View File

@@ -5,3 +5,14 @@
height: 100%; 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;
}

+ 4
- 1
react-ui/src/pages/AutoML/components/ExperimentLog/index.tsx View File

@@ -2,6 +2,7 @@ import { ExperimentStatus } from '@/enums';
import { AutoMLInstanceData } from '@/pages/AutoML/types'; import { AutoMLInstanceData } from '@/pages/AutoML/types';
import LogList from '@/pages/Experiment/components/LogList'; import LogList from '@/pages/Experiment/components/LogList';
import { NodeStatus } from '@/types'; import { NodeStatus } from '@/types';
import EmptyLog from './empty';
import styles from './index.less'; import styles from './index.less';


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


+ 18
- 16
react-ui/src/pages/AutoML/components/ExperimentRunBasic/index.tsx View File

@@ -3,59 +3,61 @@ import RunDuration from '@/components/RunDuration';
import { ExperimentStatus } from '@/enums'; import { ExperimentStatus } from '@/enums';
import { experimentStatusInfo } from '@/pages/Experiment/status'; import { experimentStatusInfo } from '@/pages/Experiment/status';
import { type NodeStatus } from '@/types'; import { type NodeStatus } from '@/types';
import { getExperimentInstanceStatus } from '@/utils/experiment';
import { formatDate } from '@/utils/format'; import { formatDate } from '@/utils/format';
import { Flex } from 'antd'; import { Flex } from 'antd';
import { useMemo } from 'react'; import { useMemo } from 'react';


type ExperimentRunBasicProps = { type ExperimentRunBasicProps = {
create_time?: string;
runStatus?: NodeStatus;
workflowStatus?: NodeStatus;
instanceStatus?: ExperimentStatus; instanceStatus?: ExperimentStatus;
}; };


function ExperimentRunBasic({ create_time, runStatus, instanceStatus }: ExperimentRunBasicProps) {
function ExperimentRunBasic({ workflowStatus, instanceStatus }: ExperimentRunBasicProps) {
const instanceDatas = useMemo(() => { 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]; const statusInfo = experimentStatusInfo[status];


return [ return [
{ {
label: '启动时间', label: '启动时间',
value: formatDate(create_time),
value: formatDate(workflowStatus?.startedAt),
}, },
{ {
label: '执行时长', label: '执行时长',
value: <RunDuration createTime={create_time} finishTime={runStatus.finishedAt} />,
value: (
<RunDuration
createTime={workflowStatus?.startedAt}
finishTime={workflowStatus?.finishedAt}
/>
),
}, },
{ {
label: '状态', label: '状态',
value: (
value: statusInfo ? (
<Flex align="center"> <Flex align="center">
<img <img
style={{ width: '17px', marginRight: '7px' }} style={{ width: '17px', marginRight: '7px' }}
src={statusInfo?.icon}
src={statusInfo.icon}
draggable={false} draggable={false}
alt="" alt=""
/> />
<div <div
style={{ style={{
color: statusInfo?.color,
color: statusInfo.color,
fontSize: '15px', fontSize: '15px',
lineHeight: 1.6, lineHeight: 1.6,
}} }}
> >
{statusInfo?.label}
{statusInfo.label}
</div> </div>
</Flex> </Flex>
) : (
'--'
), ),
}, },
]; ];
}, [runStatus, create_time, instanceStatus]);
}, [workflowStatus, instanceStatus]);


return ( return (
<ConfigInfo <ConfigInfo


+ 8
- 0
react-ui/src/pages/AutoML/components/ExperimentVisualResult/index.less View File

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

&__empty {
height: 100%;
}
}

+ 69
- 17
react-ui/src/pages/AutoML/components/ExperimentVisualResult/index.tsx View File

@@ -5,10 +5,15 @@
*/ */


import IframePage, { IframePageType } from '@/components/IFramePage'; 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 { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage'; 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 = { type TensorBoardProps = {
namespace?: string; namespace?: string;
@@ -16,28 +21,75 @@ type TensorBoardProps = {
}; };


function ExperimentVisualResult({ namespace, path }: 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);

// 获取 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]);

// 运行 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) { if (namespace && path) {
runTensorBoard(); runTensorBoard();
} }
}, [namespace, path]);
}, [namespace, path, runTensorBoard]);


return <>{tensorboardUrl && <IframePage type={IframePageType.TensorBoard}></IframePage>}</>;
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; export default ExperimentVisualResult;

+ 4
- 2
react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx View File

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


+ 6
- 4
react-ui/src/pages/Dataset/components/AddModelModal/index.tsx View File

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


+ 4
- 2
react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx View File

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


+ 121
- 0
react-ui/src/pages/Dataset/components/EditVersionModal/index.tsx 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;

+ 4
- 0
react-ui/src/pages/Dataset/components/ResourceInfo/index.less View File

@@ -42,6 +42,10 @@
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;


&:hover {
border-color: .addAlpha(@primary-color, 0.5) [];
}

&--praised { &--praised {
color: @primary-color; color: @primary-color;
} }


+ 21
- 0
react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx View File

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


+ 1
- 3
react-ui/src/pages/Dataset/components/ResourceItem/index.tsx View File

@@ -14,9 +14,7 @@ type ResourceItemProps = {
}; };


function ResourceItem({ item, isPublic, onClick, onRemove }: 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 ?? ''; const create_by = item.create_by ?? '';
return ( return (
<div className={styles['resource-item']} onClick={() => onClick(item)}> <div className={styles['resource-item']} onClick={() => onClick(item)}>


+ 7
- 0
react-ui/src/pages/Dataset/components/ResourceList/index.tsx View File

@@ -16,6 +16,7 @@ import styles from './index.less';


export type ResourceListRef = { export type ResourceListRef = {
reset: () => void; reset: () => void;
resetPage: () => void;
}; };


type ResourceListProps = { type ResourceListProps = {
@@ -97,6 +98,12 @@ function ResourceList(
setDataList(undefined); setDataList(undefined);
setTotal(0); setTotal(0);
}, },
resetPage: () => {
setPagination((prev) => ({
...prev,
current: 1,
}));
},
}; };
}, },
[], [],


+ 3
- 1
react-ui/src/pages/Dataset/components/ResourcePage/index.tsx View File

@@ -56,11 +56,13 @@ function ResourcePage({ resourceType }: ResourcePageProps) {


// 选择类型 // 选择类型
const chooseType = (record: CategoryData) => { const chooseType = (record: CategoryData) => {
dataListRef.current?.resetPage();
setActiveType((prev) => (prev === record.name ? undefined : record.name)); setActiveType((prev) => (prev === record.name ? undefined : record.name));
}; };


// 选择 Tag // 选择 Tag
const chooseTag = (record: CategoryData) => { const chooseTag = (record: CategoryData) => {
dataListRef.current?.resetPage();
setActiveTag((prev) => (prev === record.name ? undefined : record.name)); setActiveTag((prev) => (prev === record.name ? undefined : record.name));
}; };


@@ -96,7 +98,7 @@ function ResourcePage({ resourceType }: ResourcePageProps) {
dataType={activeType} dataType={activeType}
dataTag={activeTag} dataTag={activeTag}
initialSearchText={cacheState?.searchText} initialSearchText={cacheState?.searchText}
initialPagination={cacheState?.initialPagination}
initialPagination={cacheState?.pagination}
setCacheState={setCacheState} setCacheState={setCacheState}
></ResourceList> ></ResourceList>
</Flex> </Flex>


+ 6
- 0
react-ui/src/pages/Dataset/config.tsx View File

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


// 数据集数据 // 数据集数据


+ 1
- 1
react-ui/src/pages/Docs/index.tsx View File

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


+ 1
- 1
react-ui/src/pages/Experiment/Comparison/index.tsx View File

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




+ 102
- 54
react-ui/src/pages/Experiment/Info/index.jsx View File

@@ -3,10 +3,10 @@ import { ExperimentStatus } from '@/enums';
import { useStateRef } from '@/hooks/useStateRef'; import { useStateRef } from '@/hooks/useStateRef';
import { useVisible } from '@/hooks/useVisible'; import { useVisible } from '@/hooks/useVisible';
import { getExperimentIns } from '@/services/experiment/index.js'; import { getExperimentIns } from '@/services/experiment/index.js';
import { getWorkflowById } from '@/services/pipeline/index.js';
import themes from '@/styles/theme.less'; import themes from '@/styles/theme.less';
import { fittingString, parseJsonText } from '@/utils'; import { fittingString, parseJsonText } from '@/utils';
import { formatDate } from '@/utils/date'; import { formatDate } from '@/utils/date';
import { getExperimentInstanceStatus } from '@/utils/experiment';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import G6, { Util } from '@antv/g6'; import G6, { Util } from '@antv/g6';
import { Button } from 'antd'; import { Button } from 'antd';
@@ -18,10 +18,12 @@ import { experimentStatusInfo } from '../status';
import styles from './index.less'; import styles from './index.less';


let graph = null; let graph = null;
const NodePrefix = 'workflow';


function ExperimentText() { function ExperimentText() {
const [experimentIns, setExperimentIns] = useState(undefined); const [experimentIns, setExperimentIns] = useState(undefined);
const [experimentNodeData, setExperimentNodeData, experimentNodeDataRef] = useStateRef(undefined); const [experimentNodeData, setExperimentNodeData, experimentNodeDataRef] = useStateRef(undefined);
const [workflowStatus, setWorkflowStatus] = useState(undefined);
const graphRef = useRef(); const graphRef = useRef();
const workflowRef = useRef(); const workflowRef = useRef();
const locationParams = useParams(); // 新版本获取路由参数接口 const locationParams = useParams(); // 新版本获取路由参数接口
@@ -32,10 +34,12 @@ function ExperimentText() {
const evtSourceRef = useRef(); const evtSourceRef = useRef();
const width = 110; const width = 110;
const height = 36; const height = 36;
const status = getExperimentInstanceStatus(experimentIns?.status, workflowStatus);
const statusInfo = experimentStatusInfo[status];


useEffect(() => { useEffect(() => {
initGraph(); initGraph();
getWorkflow();
getExperimentInstance();


return () => { return () => {
if (evtSourceRef.current) { 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 getExperimentInstance = async () => {
const [res] = await to(getExperimentIns(locationParams.id)); const [res] = await to(getExperimentIns(locationParams.id));
if (res && res.data && workflowRef.current) {
if (res && res.data) {
setExperimentIns(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); 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) { if (status === ExperimentStatus.Pending) {
// 如果状态是 Pending, 打开第一个节点 // 如果状态是 Pending, 打开第一个节点
const node = workflowData.nodes[0];
const node = workflow.nodes[0];
if (node) { if (node) {
setExperimentNodeData(node); setExperimentNodeData(node);
openPropsDrawer(); openPropsDrawer();
} }
} else if (status === ExperimentStatus.Running) { } else if (status === ExperimentStatus.Running) {
// 如果状态是 Running,打开第一个运行中的节点,如果没有运行中的节点,则打开第一个节点
// 如果状态是 Running,打开第一个 Running 或者 pending 的节点,如果没有,则打开第一个节点
const node = 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) { if (node) {
setExperimentNodeData(node); setExperimentNodeData(node);
openPropsDrawer(); openPropsDrawer();
@@ -135,23 +168,36 @@ function ExperimentText() {
return; return;
} }
try { try {
const dataJson = JSON.parse(data);
const dataJson = parseJsonText(data);
const statusData = dataJson?.result?.object?.status; const statusData = dataJson?.result?.object?.status;
if (!statusData) { if (!statusData) {
return; 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; const workflowData = workflowRef.current;
workflowData.nodes.forEach((item) => { workflowData.nodes.forEach((item) => {
const experimentNode = Object.values(nodes).find((node) => node.displayName === item.id); const experimentNode = Object.values(nodes).find((node) => node.displayName === item.id);
updateWorkflowNode(item, experimentNode); updateWorkflowNode(item, experimentNode);
}); });

// 绘制图
getGraphData(workflowData, false); getGraphData(workflowData, false);


// 更新打开的抽屉数据 // 更新打开的抽屉数据
@@ -177,6 +223,7 @@ function ExperimentText() {
evtSourceRef.current = evtSource; evtSourceRef.current = evtSource;
}; };


// 更新各个节点
function updateWorkflowNode(workflowNode, statusNode) { function updateWorkflowNode(workflowNode, statusNode) {
if (!statusNode) { if (!statusNode) {
return; return;
@@ -471,29 +518,30 @@ function ExperimentText() {
<div className={styles['pipeline-container']}> <div className={styles['pipeline-container']}>
<div className={styles['pipeline-container__top']}> <div className={styles['pipeline-container__top']}>
<div className={styles['pipeline-container__top__info']}> <div className={styles['pipeline-container__top__info']}>
启动时间:{formatDate(experimentIns?.create_time)}
启动时间:{formatDate(workflowStatus?.startedAt)}
</div> </div>
<div className={styles['pipeline-container__top__info']}> <div className={styles['pipeline-container__top__info']}>
执行时长: 执行时长:
<RunDuration <RunDuration
createTime={experimentIns?.create_time}
finishTime={experimentIns?.finish_time}
createTime={workflowStatus?.startedAt}
finishTime={workflowStatus?.finishedAt}
/> />
</div> </div>
<div className={styles['pipeline-container__top__info']}> <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> </div>
<Button <Button
className={styles['pipeline-container__top__param-button']} className={styles['pipeline-container__top__param-button']}


+ 12
- 0
react-ui/src/pages/Experiment/Tensorboard/index.tsx 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;

+ 11
- 18
react-ui/src/pages/Experiment/components/AddExperimentModal/index.tsx 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 类型 // 防止后台返回不是 number 类型
if (Number(paramType) === 3) { if (Number(paramType) === 3) {
return ( return (
@@ -40,9 +40,9 @@ export const getParamComponent = (paramType: number, isSensitive?: number): JSX.
</Radio.Group> </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 />; return <Input placeholder="请输入值" allowClear />;
}; };


@@ -95,8 +95,8 @@ function AddExperimentModal({
}; };


const layout = { const layout = {
labelCol: { span: 4 },
wrapperCol: { span: 20 },
labelCol: { span: 5 },
wrapperCol: { span: 19 },
}; };


const paramLayout = { const paramLayout = {
@@ -181,13 +181,13 @@ function AddExperimentModal({
/> />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label="选择流水线"
label="选择流水线模板"
name="workflow_id" name="workflow_id"
rules={[{ required: true, message: '请选择流水线' }]}
rules={[{ required: true, message: '请选择流水线模板' }]}
> >
<Select <Select
disabled={workflowDisabled} disabled={workflowDisabled}
placeholder="请选择流水线"
placeholder="请选择流水线模板"
onChange={handleWorkflowChange} onChange={handleWorkflowChange}
> >
{Array.isArray(workflowList) {Array.isArray(workflowList)
@@ -202,11 +202,7 @@ function AddExperimentModal({
</Select> </Select>
</Form.Item> </Form.Item>
{globalParam.length > 0 && ( {globalParam.length > 0 && (
<Form.Item
label="运行参数"
tooltip="展示关联的流水线的参数,脱敏的参数以xxxx展示"
{...tailLayout}
>
<Form.Item label="运行参数" tooltip="展示关联的流水线的参数" {...tailLayout}>
<div className={styles.global_param_item}> <div className={styles.global_param_item}>
<Form.List name="global_param"> <Form.List name="global_param">
{(fields) => {(fields) =>
@@ -219,10 +215,7 @@ function AddExperimentModal({
name={[name, 'param_value']} name={[name, 'param_value']}
rules={getParamRules(globalParam[name]['param_type'], true)} rules={getParamRules(globalParam[name]['param_type'], true)}
> >
{getParamComponent(
globalParam[name]['param_type'],
globalParam[name]['is_sensitive'],
)}
{getParamComponent(globalParam[name]['param_type'])}
</Form.Item> </Form.Item>
)) ))
} }


+ 0
- 4
react-ui/src/pages/Experiment/components/ExperimentInstanceList/index.less View File

@@ -55,10 +55,6 @@
display: flex; display: flex;
align-items: center; align-items: center;
width: 160px; width: 160px;

.statusIcon {
visibility: visible;
}
} }
} }




+ 15
- 10
react-ui/src/pages/Experiment/components/ExperimentInstanceList/index.tsx View File

@@ -39,7 +39,14 @@ function ExperimentInstanceList({
}: ExperimentInstanceListProps) { }: ExperimentInstanceListProps) {
const { message } = App.useApp(); const { message } = App.useApp();
const allIntanceIds = useMemo(() => { 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]); }, [experimentInsList]);
const [ const [
selectedIns, selectedIns,
@@ -127,7 +134,12 @@ function ExperimentInstanceList({
<div> <div>
<div className={styles.tableExpandBox} style={{ paddingBottom: '16px' }}> <div className={styles.tableExpandBox} style={{ paddingBottom: '16px' }}>
<div className={styles.check}> <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>
<div className={styles.index}>序号</div> <div className={styles.index}>序号</div>
<div className={styles.tensorBoard}>可视化</div> <div className={styles.tensorBoard}>可视化</div>
@@ -185,14 +197,7 @@ function ExperimentInstanceList({
)} )}
</div> </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}> <div className={styles.operation}>
<Button <Button


+ 35
- 32
react-ui/src/pages/Experiment/components/ExperimentInstanceList/instance.tsx View File

@@ -2,69 +2,72 @@ import RunDuration from '@/components/RunDuration';
import { ExperimentStatus } from '@/enums'; import { ExperimentStatus } from '@/enums';
import { useSSE, type MessageHandler } from '@/hooks/useSSE'; import { useSSE, type MessageHandler } from '@/hooks/useSSE';
import { experimentStatusInfo } from '@/pages/Experiment/status'; import { experimentStatusInfo } from '@/pages/Experiment/status';
import { ExperimentInstance, NodeStatus } from '@/types';
import { ExperimentCompleted } from '@/utils/constant'; import { ExperimentCompleted } from '@/utils/constant';
import { formatDate } from '@/utils/date'; import { formatDate } from '@/utils/date';
import { getWorkflowStatus } from '@/utils/experiment';
import { Typography } from 'antd'; import { Typography } from 'antd';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import styles from './index.less'; 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( const handleSSEMessage: MessageHandler = useCallback(
(experimentInsId: number, status: string, finish_time: string) => {
(experimentId: number, experimentInsId: number, status: string, finishTime: string) => {
window.postMessage({ window.postMessage({
type: ExperimentCompleted, type: ExperimentCompleted,
payload: { payload: {
id: experimentInsId,
experimentId,
experimentInsId,
status, 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 ( return (
<React.Fragment> <React.Fragment>
<div className={styles.description}> <div className={styles.description}>
<div style={{ width: '50%' }}> <div style={{ width: '50%' }}>
<RunDuration createTime={create_time} finishTime={finish_time} />
<RunDuration createTime={createTime} finishTime={finishTime} />
</div> </div>
<div style={{ width: '50%' }} className={styles.startTime}> <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> </Typography.Text>
</div> </div>
</div> </div>
<div className={styles.statusBox}> <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> </div>
</React.Fragment> </React.Fragment>
); );
} }


export default ExperimentInstance;
export default ExperimentInstanceComponent;

+ 60
- 50
react-ui/src/pages/Experiment/components/ExperimentParameter/index.tsx View File

@@ -11,9 +11,15 @@ type ExperimentParameterProps = {


function ExperimentParameter({ nodeData }: 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]) => ({ const inParametersList = Object.entries(nodeData.in_parameters ?? {}).map(([key, value]) => ({
@@ -74,54 +80,58 @@ function ExperimentParameter({ nodeData }: ExperimentParameterProps) {
> >
<FormInfo /> <FormInfo />
</Form.Item> </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']}> <div className={styles['experiment-parameter__title']}>
<SubAreaTitle <SubAreaTitle
image={require('@/assets/img/duty-message.png')} image={require('@/assets/img/duty-message.png')}


+ 13
- 1
react-ui/src/pages/Experiment/components/ExportModelModal/index.tsx View File

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


+ 4
- 1
react-ui/src/pages/Experiment/components/LogGroup/index.tsx View File

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


+ 1
- 4
react-ui/src/pages/Experiment/components/ViewParamsModal/index.tsx View File

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


+ 189
- 136
react-ui/src/pages/Experiment/index.jsx View File

@@ -2,8 +2,10 @@ import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle'; import PageTitle from '@/components/PageTitle';
import { ExperimentStatus, TensorBoardStatus } from '@/enums'; import { ExperimentStatus, TensorBoardStatus } from '@/enums';
import { useCacheState } from '@/hooks/useCacheState'; import { useCacheState } from '@/hooks/useCacheState';
import { useServerTime } from '@/hooks/useServerTime';
import { import {
deleteExperimentById, deleteExperimentById,
editExperimentInsReq,
getExperiment, getExperiment,
getExperimentById, getExperimentById,
getQueryByExperimentId, getQueryByExperimentId,
@@ -17,6 +19,7 @@ import { getWorkflow } from '@/services/pipeline/index.js';
import themes from '@/styles/theme.less'; import themes from '@/styles/theme.less';
import { ExperimentCompleted } from '@/utils/constant'; import { ExperimentCompleted } from '@/utils/constant';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage';
import tableCellRender, { TableCellValueType } from '@/utils/table'; import tableCellRender, { TableCellValueType } from '@/utils/table';
import { modalConfirm } from '@/utils/ui'; import { modalConfirm } from '@/utils/ui';
import { App, Button, ConfigProvider, Dropdown, Input, Space, Table, Tooltip } from 'antd'; 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 ExperimentInstanceList from './components/ExperimentInstanceList';
import styles from './index.less'; import styles from './index.less';
import { experimentStatusInfo } from './status'; import { experimentStatusInfo } from './status';
import { useServerTime } from '@/hooks/useServerTime';


// 定时器 // 定时器
const timerIds = new Map(); const timerIds = new Map();
@@ -39,7 +41,7 @@ function Experiment() {
const [workflowList, setWorkflowList] = useState([]); const [workflowList, setWorkflowList] = useState([]);
const [experimentId, setExperimentId] = useState(null); const [experimentId, setExperimentId] = useState(null);
const [experimentInsList, setExperimentInsList] = useState([]); const [experimentInsList, setExperimentInsList] = useState([]);
const [expandedRowKeys, setExpandedRowKeys] = useState(null);
const [expandedRowKeys, setExpandedRowKeys] = useState([]);
const [total, setTotal] = useState(0); const [total, setTotal] = useState(0);
const [isAdd, setIsAdd] = useState(true); const [isAdd, setIsAdd] = useState(true);
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
@@ -59,29 +61,137 @@ function Experiment() {
const timerRef = useRef(); const timerRef = useRef();


// 获取实验列表 // 获取实验列表
const getExperimentList = useCallback(async () => {
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],
);

// 刷新实验列表状态,
// 目前是直接刷新实验列表,后续需要优化,只刷新状态
const refreshExperimentList = useCallback(
(skipLoading = false) => {
getExperimentList(skipLoading);
},
[getExperimentList],
);

// 获取 TensorBoard 状态
const getTensorBoardStatus = useCallback(async (experimentIn) => {
const params = { const params = {
page: pagination.current - 1,
size: pagination.pageSize,
name: searchText || undefined,
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(getExperiment(params));
if (res && res.data && Array.isArray(res.data.content)) {
setExperimentList(
res.data.content.map((item) => {
return { ...item, key: item.id };
}),
);

setTotal(res.data.totalElements);
const [res] = await to(getTensorBoardStatusReq(params));
if (res && res.data) {
setExperimentInsList((prevList) => {
return prevList.map((item) => {
if (item.id === experimentIn.id) {
return {
...item,
tensorBoardStatus: res.data.status,
tensorboardUrl: res.data.url,
};
}
return item;
});
});

let timerId = timerIds.get(experimentIn.id);
if (timerId) {
clearTimeout(timerId);
timerIds.delete(experimentIn.id);
}
timerId = setTimeout(() => {
getTensorBoardStatus(experimentIn);
}, 10 * 1000);
timerIds.set(experimentIn.id, timerId);
} }
}, [pagination, searchText]);
}, []);


// 刷新实验列表状态,
// 目前是直接刷新实验列表,后续需要优化,只刷新状态
const refreshExperimentList = useCallback(() => {
getExperimentList();
}, [getExperimentList]);
// 获取实验实例列表
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(() => { useEffect(() => {
@@ -104,52 +214,66 @@ function Experiment() {
clearExperimentInTimers(); clearExperimentInTimers();
}; };
}, []); }, []);

// 获取实验列表 // 获取实验列表
useEffect(() => { useEffect(() => {
getExperimentList(); getExperimentList();
}, [getExperimentList]); }, [getExperimentList]);


// 新增,删除版本时,重置分页,然后刷新版本列表
// 更新实验实例状态
useEffect(() => { useEffect(() => {
const handleMessage = (e) => { const handleMessage = (e) => {
const { type, payload } = e.data; const { type, payload } = e.data;
if (type === ExperimentCompleted) { 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);
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); window.addEventListener('message', handleMessage);
return () => { return () => {
window.removeEventListener('message', handleMessage); window.removeEventListener('message', handleMessage);
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = undefined;
}
}; };
}, [refreshExperimentList]);
}, [experimentInsList, editExperimentIns]);


// 搜索 // 搜索
const onSearch = (value) => { const onSearch = (value) => {
@@ -160,44 +284,6 @@ function Experiment() {
})); }));
}; };


// 获取实验实例列表
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 // 运行 TensorBoard
const runTensorBoard = async (experimentIn) => { const runTensorBoard = async (experimentIn) => {
const params = { const params = {
@@ -217,49 +303,16 @@ function Experiment() {
} }
}; };


// 获取 TensorBoard 状态
const getTensorBoardStatus = 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(getTensorBoardStatusReq(params));
if (res && res.data) {
setExperimentInsList((prevList) => {
return prevList.map((item) => {
if (item.id === experimentIn.id) {
return {
...item,
tensorBoardStatus: res.data.status,
tensorboardUrl: res.data.url,
};
}
return item;
});
});

let timerId = timerIds.get(experimentIn.id);
if (timerId) {
clearTimeout(timerId);
timerIds.delete(experimentIn.id);
}
timerId = setTimeout(() => {
getTensorBoardStatus(experimentIn);
}, 10 * 1000);
timerIds.set(experimentIn.id, timerId);
}
};

// 展开实例 // 展开实例
const expandChange = (e, record) => {
const expandChange = (expanded, record) => {
clearExperimentInTimers(); clearExperimentInTimers();
setExperimentInsList([]); setExperimentInsList([]);
if (record.id === expandedRowKeys) {
setExpandedRowKeys(null);
} else {
getQueryByExperiment(record.id, 0, 5);
if (expanded) {
setExpandedRowKeys([record.id]);
getExperimentInsList(record.id, 0, 5);
refreshExperimentList(); refreshExperimentList();
} else {
setExpandedRowKeys([]);
} }
}; };


@@ -337,8 +390,9 @@ function Experiment() {
const [res] = await to(runExperiments(id)); const [res] = await to(runExperiments(id));
if (res) { if (res) {
message.success('运行成功'); message.success('运行成功');
setExpandedRowKeys([id]);
refreshExperimentList(); refreshExperimentList();
getQueryByExperiment(id, 0, 5);
getExperimentInsList(id, 0, 5);
} }
}; };


@@ -372,14 +426,16 @@ function Experiment() {
experimentIn.tensorBoardStatus === TensorBoardStatus.Running && experimentIn.tensorBoardStatus === TensorBoardStatus.Running &&
experimentIn.tensorboardUrl 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) => { const handleInstanceTerminate = async (experimentIn) => {
// 刷新实验列表
refreshExperimentList();
// 修改实例的状态和结束时间
setExperimentInsList((prevList) => { setExperimentInsList((prevList) => {
return prevList.map((item) => { return prevList.map((item) => {
if (item.id === experimentIn.id) { if (item.id === experimentIn.id) {
@@ -392,6 +448,9 @@ function Experiment() {
return item; 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 loadMoreExperimentIns = () => {
const page = Math.round(experimentInsList.length / 5); const page = Math.round(experimentInsList.length / 5);
getQueryByExperiment(expandedRowKeys, page, 5);
getExperimentInsList(expandedRowKeys[0], page, 5);
}; };


// 处理删除 // 处理删除
@@ -607,7 +660,7 @@ function Experiment() {
></ExperimentInstanceList> ></ExperimentInstanceList>
), ),
onExpand: expandChange, onExpand: expandChange,
expandedRowKeys: [expandedRowKeys],
expandedRowKeys: expandedRowKeys,
}} }}
/> />
</div> </div>


+ 12
- 0
react-ui/src/pages/HyperParameter/Aim/index.tsx 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;

+ 1
- 1
react-ui/src/pages/HyperParameter/Info/index.tsx View File

@@ -1,7 +1,7 @@
/* /*
* @Author: 赵伟 * @Author: 赵伟
* @Date: 2024-04-16 13:58:08 * @Date: 2024-04-16 13:58:08
* @Description: 自机器学习详情
* @Description: 自机器学习详情
*/ */
import PageTitle from '@/components/PageTitle'; import PageTitle from '@/components/PageTitle';
import { getRayInfoReq } from '@/services/hyperParameter'; import { getRayInfoReq } from '@/services/hyperParameter';


+ 6
- 8
react-ui/src/pages/HyperParameter/Instance/index.tsx View File

@@ -51,7 +51,7 @@ function HyperParameterInstance() {
const [res] = await to(getRayInsReq(instanceId)); const [res] = await to(getRayInsReq(instanceId));
if (res && res.data) { if (res && res.data) {
const info = res.data as HyperParameterInstanceData; 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; const paramJson = parseJsonText(param).data;
if (paramJson) { if (paramJson) {
@@ -72,7 +72,6 @@ function HyperParameterInstance() {
} }
setExperimentInfo({ setExperimentInfo({
...paramJson, ...paramJson,
create_time,
}); });
} }


@@ -121,18 +120,17 @@ function HyperParameterInstance() {
if (dataJson) { if (dataJson) {
const nodes = dataJson?.result?.object?.status?.nodes; const nodes = dataJson?.result?.object?.status?.nodes;
if (nodes) { if (nodes) {
// 节点
// 设置节点
setNodes(nodes); setNodes(nodes);


// 设置总 workflow 状态
const workflowStatus = Object.values(nodes).find((node: any) => const workflowStatus = Object.values(nodes).find((node: any) =>
node.displayName.startsWith(NodePrefix), node.displayName.startsWith(NodePrefix),
) as NodeStatus; ) as NodeStatus;

// 设置工作流状态
if (workflowStatus) { if (workflowStatus) {
setWorkflowStatus(workflowStatus); setWorkflowStatus(workflowStatus);


// 实验结束,关闭 SSE
// 实验结束,关闭 SSE,获取实验实例结果
if ( if (
workflowStatus.phase !== ExperimentStatus.Pending && workflowStatus.phase !== ExperimentStatus.Pending &&
workflowStatus.phase !== ExperimentStatus.Running workflowStatus.phase !== ExperimentStatus.Running
@@ -167,8 +165,8 @@ function HyperParameterInstance() {
<HyperParameterBasic <HyperParameterBasic
className={styles['hyper-parameter-instance__basic']} className={styles['hyper-parameter-instance__basic']}
info={experimentInfo} info={experimentInfo}
runStatus={workflowStatus}
instanceStatus={instanceInfo?.status}
workflowStatus={workflowStatus}
instanceStatus={instanceInfo?.status as ExperimentStatus}
isInstance isInstance
/> />
), ),


+ 5
- 1
react-ui/src/pages/HyperParameter/components/ExperimentHistory/index.tsx View File

@@ -3,7 +3,9 @@ import TrialStatusCell from '@/pages/HyperParameter/components/TrialStatusCell';
import { HyperParameterTrial } from '@/pages/HyperParameter/types'; import { HyperParameterTrial } from '@/pages/HyperParameter/types';
import { getExpMetricsReq } from '@/services/hyperParameter'; import { getExpMetricsReq } from '@/services/hyperParameter';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage';
import tableCellRender, { TableCellValueType } from '@/utils/table'; import tableCellRender, { TableCellValueType } from '@/utils/table';
import { useNavigate } from '@umijs/max';
import { App, Button, Table, type TableProps } from 'antd'; import { App, Button, Table, type TableProps } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@@ -36,6 +38,7 @@ function ExperimentHistory({ trialList = [] }: ExperimentHistoryProps) {
const metricAnalysis: Record<string, any> = first?.metric_analysis ?? {}; const metricAnalysis: Record<string, any> = first?.metric_analysis ?? {};
const paramsNames = Object.keys(config); const paramsNames = Object.keys(config);
const metricNames = Object.keys(metricAnalysis); const metricNames = Object.keys(metricAnalysis);
const navigate = useNavigate();


const trialColumns: TableProps<HyperParameterTrial>['columns'] = [ const trialColumns: TableProps<HyperParameterTrial>['columns'] = [
{ {
@@ -160,7 +163,8 @@ function ExperimentHistory({ trialList = [] }: ExperimentHistoryProps) {
const [res] = await to(getExpMetricsReq(selectedRowKeys)); const [res] = await to(getExpMetricsReq(selectedRowKeys));
if (res && res.data) { if (res && res.data) {
const url = res.data; const url = res.data;
window.open(url, '_blank');
SessionStorage.setItem(SessionStorage.aimUrlKey, url);
navigate('compare-visual');
} }
}; };




+ 7
- 2
react-ui/src/pages/HyperParameter/components/ExperimentLog/index.tsx View File

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


+ 5
- 54
react-ui/src/pages/HyperParameter/components/HyperParameterBasic/index.tsx View File

@@ -1,9 +1,7 @@
import ConfigInfo, { type BasicInfoData } from '@/components/ConfigInfo'; import ConfigInfo, { type BasicInfoData } from '@/components/ConfigInfo';
import RunDuration from '@/components/RunDuration';
import { ExperimentStatus, hyperParameterOptimizedMode } from '@/enums'; import { ExperimentStatus, hyperParameterOptimizedMode } from '@/enums';
import { useComputingResource } from '@/hooks/useComputingResource'; import { useComputingResource } from '@/hooks/useComputingResource';
import ExperimentRunBasic from '@/pages/AutoML/components/ExperimentRunBasic'; import ExperimentRunBasic from '@/pages/AutoML/components/ExperimentRunBasic';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import { import {
schedulerAlgorithms, schedulerAlgorithms,
searchAlgorithms, searchAlgorithms,
@@ -18,7 +16,6 @@ import {
formatMirror, formatMirror,
formatModel, formatModel,
} from '@/utils/format'; } from '@/utils/format';
import { Flex } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { useMemo } from 'react'; import { useMemo } from 'react';
import ParameterInfo from '../ParameterInfo'; import ParameterInfo from '../ParameterInfo';
@@ -33,14 +30,14 @@ type HyperParameterBasicProps = {
info?: HyperParameterData; info?: HyperParameterData;
className?: string; className?: string;
isInstance?: boolean; isInstance?: boolean;
runStatus?: NodeStatus;
workflowStatus?: NodeStatus;
instanceStatus?: ExperimentStatus; instanceStatus?: ExperimentStatus;
}; };


function HyperParameterBasic({ function HyperParameterBasic({
info, info,
className, className,
runStatus,
workflowStatus,
instanceStatus, instanceStatus,
isInstance = false, isInstance = false,
}: HyperParameterBasicProps) { }: HyperParameterBasicProps) {
@@ -83,7 +80,7 @@ function HyperParameterBasic({
} }
return [ return [
{ {
label: '代码',
label: '代码配置',
value: info.code_config, value: info.code_config,
format: formatCodeConfig, format: formatCodeConfig,
}, },
@@ -145,56 +142,10 @@ function HyperParameterBasic({
]; ];
}, [info, getResourceDescription]); }, [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 ( return (
<div className={classNames(styles['hyper-parameter-basic'], className)}> <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 && ( {!isInstance && (
<ConfigInfo <ConfigInfo


+ 8
- 0
react-ui/src/pages/Mirror/Create/index.less View File

@@ -9,6 +9,14 @@
background-color: white; background-color: white;
border-radius: 10px; border-radius: 10px;


&__name-row {
:global {
.ant-form-item-row {
flex-wrap: nowrap;
}
}
}

&__type { &__type {
color: @text-color; color: @text-color;
font-size: @font-size-input-lg; font-size: @font-size-input-lg;


+ 29
- 6
react-ui/src/pages/Mirror/Create/index.tsx View File

@@ -123,8 +123,6 @@ function MirrorCreate() {
return true; return true;
}; };


const descTitle = isAddVersion ? '版本描述' : '镜像描述';

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


+ 5
- 3
react-ui/src/pages/Mirror/Info/index.tsx View File

@@ -125,6 +125,7 @@ function MirrorInfo() {
current: tableData.length === 1 ? Math.max(1, prev.current! - 1) : prev.current, current: tableData.length === 1 ? Math.max(1, prev.current! - 1) : prev.current,
}; };
}); });
getMirrorInfo();
} }
}; };


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


+ 13
- 9
react-ui/src/pages/Model/components/MetricsChart/index.tsx View File

@@ -65,14 +65,18 @@ export type MetricsChartProps = {
function MetricsChart({ name, chartData }: MetricsChartProps) { function MetricsChart({ name, chartData }: MetricsChartProps) {
const chartRef = useRef<HTMLDivElement>(null); const chartRef = useRef<HTMLDivElement>(null);
const xAxisData = chartData[0]?.iters; 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( const options: echarts.EChartsOption = useMemo(
() => ({ () => ({
@@ -158,7 +162,7 @@ function MetricsChart({ name, chartData }: MetricsChartProps) {


// 组件卸载 // 组件卸载
return () => { return () => {
// myChart.dispose() 销毁实例
// 销毁实例
chart.dispose(); chart.dispose();
}; };
}, [options]); }, [options]);


+ 1
- 1
react-ui/src/pages/ModelDeployment/CreateVersion/index.tsx View File

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


+ 29
- 26
react-ui/src/pages/ModelDeployment/VersionInfo/index.tsx View File

@@ -3,16 +3,16 @@
* @Date: 2024-04-16 13:58:08 * @Date: 2024-04-16 13:58:08
* @Description: 服务版本详情 * @Description: 服务版本详情
*/ */
import FullScreenFrame from '@/components/FullScreenFrame';
import IframePage from '@/components/IFramePage';
import KFIcon from '@/components/KFIcon'; import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle'; import PageTitle from '@/components/PageTitle';
import { ServiceRunStatus } from '@/enums';
import { getServiceVersionInfoReq } from '@/services/modelDeployment'; import { getServiceVersionInfoReq } from '@/services/modelDeployment';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import { useParams } from '@umijs/max'; import { useParams } from '@umijs/max';
import { Tabs } from 'antd'; import { Tabs } from 'antd';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import ServerLog from '../components/ServerLog'; import ServerLog from '../components/ServerLog';
import UserGuide from '../components/UserGuide';
import VersionBasicInfo from '../components/VersionBasicInfo'; import VersionBasicInfo from '../components/VersionBasicInfo';
import { ServiceVersionData } from '../types'; import { ServiceVersionData } from '../types';
import styles from './index.less'; import styles from './index.less';
@@ -50,32 +50,35 @@ function ServiceVersionInfo() {
icon: <KFIcon type="icon-jibenxinxi" />, icon: <KFIcon type="icon-jibenxinxi" />,
children: <VersionBasicInfo info={versionInfo} />, 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 ( return (
<div className={styles['service-version-info']}> <div className={styles['service-version-info']}>
<PageTitle title="服务版本详情"></PageTitle> <PageTitle title="服务版本详情"></PageTitle>


+ 9
- 1
react-ui/src/pages/ModelDeployment/components/ServerLog/index.less View File

@@ -11,10 +11,18 @@
font-family: 'Roboto Mono', 'Menlo', 'Consolas', 'Monaco', monospace; font-family: 'Roboto Mono', 'Menlo', 'Consolas', 'Monaco', monospace;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center;
align-items: left;

&--empty {
align-items: center;
}


&__more { &__more {
padding: 20px 0; padding: 20px 0;
} }

&::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.5);
}
} }
} }

+ 9
- 2
react-ui/src/pages/ModelDeployment/components/ServerLog/index.tsx View File

@@ -3,6 +3,7 @@ import { getServiceVersionLogReq } from '@/services/modelDeployment';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import { DoubleRightOutlined } from '@ant-design/icons'; import { DoubleRightOutlined } from '@ant-design/icons';
import { Button, DatePicker, type TimeRangePickerProps } from 'antd'; import { Button, DatePicker, type TimeRangePickerProps } from 'antd';
import classNames from 'classnames';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import styles from './index.less'; import styles from './index.less';
@@ -106,6 +107,8 @@ function ServerLog({ info }: ServerLogProps) {
} }
}; };


const logContent = logData.map((v) => v.log_content).join('');

return ( return (
<div className={styles['server-log']}> <div className={styles['server-log']}>
<div> <div>
@@ -122,9 +125,9 @@ function ServerLog({ info }: ServerLogProps) {
查询 查询
</Button> </Button>
</div> </div>
{logData.length > 0 && (
{logContent ? (
<div className={styles['server-log__data']} id="server-log"> <div className={styles['server-log__data']} id="server-log">
<div>{logData.map((v) => v.log_content).join('') || '暂无日志'}</div>
<div>{logContent}</div>
{hasMore && ( {hasMore && (
<Button <Button
type="text" type="text"
@@ -137,6 +140,10 @@ function ServerLog({ info }: ServerLogProps) {
</Button> </Button>
)} )}
</div> </div>
) : (
<div className={classNames(styles['server-log__data'], styles['server-log__data--empty'])}>
暂无日志
</div>
)} )}
</div> </div>
); );


+ 1
- 1
react-ui/src/pages/Pipeline/Info/index.jsx View File

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


+ 2
- 2
react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx View File

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


+ 130
- 92
react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx View File

@@ -19,6 +19,7 @@ import { openAntdModal } from '@/utils/modal';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import { INode } from '@antv/g6'; import { INode } from '@antv/g6';
import { Button, Drawer, Form, Input, MenuProps } from 'antd'; import { Button, Drawer, Form, Input, MenuProps } from 'antd';
import { RuleObject } from 'antd/es/form';
import { NamePath } from 'antd/es/form/interface'; import { NamePath } from 'antd/es/form/interface';
import { forwardRef, useImperativeHandle, useState } from 'react'; import { forwardRef, useImperativeHandle, useState } from 'react';
import PropsLabel from '../PropsLabel'; import PropsLabel from '../PropsLabel';
@@ -34,6 +35,12 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
const [stagingItem, setStagingItem] = useState<PipelineNodeModelSerialize>( const [stagingItem, setStagingItem] = useState<PipelineNodeModelSerialize>(
{} as 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 [open, setOpen] = useState(false);
const [menuItems, setMenuItems] = useState<MenuProps['items']>([]); const [menuItems, setMenuItems] = useState<MenuProps['items']>([]);


@@ -45,10 +52,11 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
const fields = form.getFieldsValue(); 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 = { const in_parameters = {
...stagingItem.in_parameters, ...stagingItem.in_parameters,
...fields.in_parameters, ...fields.in_parameters,
@@ -63,7 +71,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
const res = { const res = {
...stagingItem, ...stagingItem,
...fields, ...fields,
control_strategy: JSON.stringify(control_strategy),
// control_strategy: JSON.stringify(control_strategy),
in_parameters: JSON.stringify(in_parameters), in_parameters: JSON.stringify(in_parameters),
out_parameters: JSON.stringify(out_parameters), out_parameters: JSON.stringify(out_parameters),
formError: !!error, formError: !!error,
@@ -90,7 +98,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
...model, ...model,
in_parameters: JSON.parse(model.in_parameters), in_parameters: JSON.parse(model.in_parameters),
out_parameters: JSON.parse(model.out_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); // console.log('model', nodeData);
setStagingItem({ 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 }) => { 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, 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]) => ({ const inParametersList = Object.entries(stagingItem.in_parameters ?? {}).map(([key, value]) => ({
@@ -396,71 +429,73 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
> >
<Input disabled /> <Input disabled />
</Form.Item> </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>
<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> </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" name="mount_path"
label={ label={
<PropsLabel <PropsLabel
@@ -473,23 +508,23 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
} }
> >
<Input placeholder="请输入挂载路径" allowClear /> <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 <Form.Item
key={item.key} key={item.key}
name={['control_strategy', item.key]} name={['control_strategy', item.key]}
@@ -499,7 +534,10 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
> >
<ParameterInput placeholder={item.value.placeholder} allowClear></ParameterInput> <ParameterInput placeholder={item.value.placeholder} allowClear></ParameterInput>
</Form.Item> </Form.Item>
))}
))} */}
</>
)}

{/* 输入参数 */} {/* 输入参数 */}
{inParametersList.length > 0 && ( {inParametersList.length > 0 && (
<> <>


+ 2
- 2
react-ui/src/pages/Pipeline/index.jsx View File

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


+ 1
- 1
react-ui/src/pages/System/Role/authUser.tsx View File

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


+ 1
- 1
react-ui/src/pages/System/Role/components/UserSelectorModal.tsx View File

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


+ 2
- 2
react-ui/src/pages/System/Role/index.tsx View File

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


+ 5
- 8
react-ui/src/pages/System/User/components/DeptTree.tsx View File

@@ -28,8 +28,11 @@ const DeptTree: React.FC<TreeProps> = (props) => {
const res = await getDeptTree({}); const res = await getDeptTree({});
const treeData = res.map((item: any) => ({ ...item, key: item.id })); const treeData = res.map((item: any) => ({ ...item, key: item.id }));
setTreeData(treeData); 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(); hide();
return true; return true;
} catch (error) { } catch (error) {
@@ -41,12 +44,6 @@ const DeptTree: React.FC<TreeProps> = (props) => {
fetchDeptList(); fetchDeptList();
}, []); }, []);


useEffect(() => {
if (treeData.length > 0) {
onSelect(treeData[0]);
}
}, [treeData, onSelect]);

const handleSelect = (keys: React.Key[], info: any) => { const handleSelect = (keys: React.Key[], info: any) => {
setSelectedKeys(keys); setSelectedKeys(keys);
onSelect(info.node); onSelect(info.node);


+ 2
- 3
react-ui/src/pages/System/User/edit.tsx View File

@@ -203,9 +203,8 @@ const UserForm: React.FC<UserFormProps> = (props) => {
required: true, 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位,且必须以字母开头,只支持字母和数字',
}, },
]} ]}
/> />


+ 12
- 1
react-ui/src/pages/Workspace/components/AssetsManagement/index.less View File

@@ -37,7 +37,18 @@
&__summary { &__summary {
display: flex; display: flex;
flex-direction: column; 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 { &__title {
margin-bottom: 12px; margin-bottom: 12px;


+ 16
- 8
react-ui/src/pages/Workspace/components/AssetsManagement/index.tsx View File

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


+ 4
- 4
react-ui/src/pages/Workspace/components/ExperimentTable/index.less View File

@@ -1,6 +1,6 @@
.experiment-table { .experiment-table {
flex: 1; flex: 1;
min-width: 378px;
min-width: 460px;
height: 140px; height: 140px;
padding: 12px; padding: 12px;
background: @workspace-background; background: @workspace-background;
@@ -32,7 +32,7 @@
} }


&__status { &__status {
width: 15%;
width: 25%;
} }


&__duration { &__duration {
@@ -40,11 +40,11 @@
} }


&__date { &__date {
width: calc(60% - 60px);
width: 40%;
} }


&__operation { &__operation {
width: 60px;
width: 10%;


:global { :global {
.ant-btn-link { .ant-btn-link {


+ 8
- 29
react-ui/src/pages/Workspace/components/ExperimentTable/index.tsx View File

@@ -1,10 +1,9 @@
import KFIcon from '@/components/KFIcon';
import { ExperimentStatus, experimentStatusInfo } from '@/pages/Experiment/status';
import { ExperimentInstance } from '@/types'; import { ExperimentInstance } from '@/types';
import { elapsedTime, formatDate } from '@/utils/date';
import { useNavigate } from '@umijs/max'; import { useNavigate } from '@umijs/max';
import { Button, Empty } from 'antd';
import { Empty } from 'antd';
import styles from './index.less'; import styles from './index.less';
import ExperimentInstanceComponent from './instance';

type ExperimentTableProps = { type ExperimentTableProps = {
tableData: ExperimentInstance[]; tableData: ExperimentInstance[];
style?: React.CSSProperties; style?: React.CSSProperties;
@@ -26,31 +25,11 @@ function ExperimentTable({ tableData = [], style }: ExperimentTableProps) {
</div> </div>
{Array.isArray(tableData) && tableData.length > 0 ? ( {Array.isArray(tableData) && tableData.length > 0 ? (
tableData.map((item) => ( 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 <Empty


+ 76
- 0
react-ui/src/pages/Workspace/components/ExperimentTable/instance.tsx 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;

+ 21
- 21
react-ui/src/pages/Workspace/components/QuickStart/index.tsx View File

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


+ 7
- 7
react-ui/src/pages/Workspace/components/TotalStatistics/index.less View File

@@ -3,16 +3,16 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
height: 140px; 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 { &__icon {
width: 63px;
margin-right: 16px;
width: 80px;
margin-right: 20px;
} }


&__title { &__title {


+ 1
- 0
react-ui/src/pages/Workspace/components/UserPoints/index.less View File

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


&__label { &__label {
margin-top: 60px; margin-top: 60px;


+ 1
- 1
react-ui/src/pages/Workspace/components/WorkspaceIntro/index.less View File

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


+ 4
- 5
react-ui/src/pages/Workspace/components/WorkspaceIntro/index.tsx View File

@@ -1,16 +1,15 @@
import { Button } from 'antd';
import styles from './index.less'; import styles from './index.less';


function WorkspaceIntro() { function WorkspaceIntro() {
return ( return (
<div className={styles['workspace-intro']}> <div className={styles['workspace-intro']}>
<div className={styles['workspace-intro__left']}> <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 className={styles['workspace-intro__content']}>
材料领域的自主实验系统是一种用于材料研究和开发的技术平台,它旨在提供实验数据收集、分析和可视化等功能,
智能材料科研平台是用于材料研究和开发的技术平台,它旨在提供实验数据收集、分析和可视化等功能,
以支持材料工程师、科学家和研究人员在材料设计、性能评估和工艺优化方面的工作 以支持材料工程师、科学家和研究人员在材料设计、性能评估和工艺优化方面的工作
</div> </div>
<div className={styles['workspace-intro__buttons']}>
{/* <div className={styles['workspace-intro__buttons']}>
<Button <Button
type="primary" type="primary"
style={{ marginRight: '20px' }} style={{ marginRight: '20px' }}
@@ -40,7 +39,7 @@ function WorkspaceIntro() {
> >
分子材料自主实验系统 分子材料自主实验系统
</Button> </Button>
</div>
</div> */}
</div> </div>
<div className={styles['workspace-intro__right']}> <div className={styles['workspace-intro__right']}>
<img <img


+ 5
- 5
react-ui/src/pages/Workspace/index.less View File

@@ -27,7 +27,7 @@


&__statistics { &__statistics {
flex: none; flex: none;
min-width: 431px;
// min-width: 500px;
background: linear-gradient( background: linear-gradient(
123.08deg, 123.08deg,
rgba(138, 138, 138, 0.06) 1.32%, rgba(138, 138, 138, 0.06) 1.32%,
@@ -35,10 +35,10 @@
); );
border-radius: 4px; border-radius: 4px;


// 媒体查询
@media screen and (max-width: 1600px) {
flex: 1;
}
// // 媒体查询
// @media screen and (max-width: 1600px) {
// flex: 1;
// }
} }
} }
} }


+ 11
- 13
react-ui/src/pages/Workspace/index.tsx View File

@@ -1,18 +1,17 @@
import { useDraggable } from '@/hooks/useDraggable';
// import { useDraggable } from '@/hooks/useDraggable';
import { getWorkspaceOverviewReq } from '@/services/workspace'; import { getWorkspaceOverviewReq } from '@/services/workspace';
import { ExperimentInstance } from '@/types'; import { ExperimentInstance } from '@/types';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import { Divider, Flex } from 'antd'; import { Divider, Flex } from 'antd';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import Draggable from 'react-draggable';
// import Draggable from 'react-draggable';
import AssetsManagement from './components/AssetsManagement'; import AssetsManagement from './components/AssetsManagement';
import ExperimentChart, { type ExperimentStatistics } from './components/ExperimentChart'; import ExperimentChart, { type ExperimentStatistics } from './components/ExperimentChart';
import ExperitableTable from './components/ExperimentTable'; import ExperitableTable from './components/ExperimentTable';
import QuickStart from './components/QuickStart'; import QuickStart from './components/QuickStart';
import RobotFrame from './components/RobotFrame';
// import RobotFrame from './components/RobotFrame';
import TotalStatistics from './components/TotalStatistics'; import TotalStatistics from './components/TotalStatistics';
import UserPoints from './components/UserPoints'; import UserPoints from './components/UserPoints';
import UserSpace from './components/UserSpace';
import WorkspaceIntro from './components/WorkspaceIntro'; import WorkspaceIntro from './components/WorkspaceIntro';
import styles from './index.less'; import styles from './index.less';


@@ -25,10 +24,10 @@ type OverviewData = {


function Workspace() { function Workspace() {
const [overviewData, setOverviewData] = useState<OverviewData>(); 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); const users: number[] = new Array(8).fill(0);


useEffect(() => { useEffect(() => {
@@ -67,7 +66,6 @@ function Workspace() {
count={overviewData?.runningExperimentInsCount} count={overviewData?.runningExperimentInsCount}
/> />
</Flex> </Flex>

<ExperitableTable <ExperitableTable
tableData={overviewData?.latestExperimentInsList || []} tableData={overviewData?.latestExperimentInsList || []}
></ExperitableTable> ></ExperitableTable>
@@ -76,16 +74,16 @@ function Workspace() {
)} )}
</div> </div>
</div> </div>
<UserPoints />
</Flex> </Flex>
<div className={styles['workspace__quick-start']}> <div className={styles['workspace__quick-start']}>
<QuickStart></QuickStart> <QuickStart></QuickStart>
<div className={styles['workspace__user']}> <div className={styles['workspace__user']}>
<UserSpace users={users}></UserSpace>
<UserPoints />
{/* <UserSpace users={users}></UserSpace> */}
<AssetsManagement></AssetsManagement> <AssetsManagement></AssetsManagement>
</div> </div>
</div> </div>
<Draggable onStart={handleStart} onStop={handleStop} onDrag={handleDrag} bounds="body">
{/* <Draggable onStart={handleStart} onStop={handleStop} onDrag={handleDrag} bounds="body">
<img <img
className={styles['workspace__robot-img']} className={styles['workspace__robot-img']}
src={require('@/assets/img/robot.png')} src={require('@/assets/img/robot.png')}
@@ -98,7 +96,7 @@ function Workspace() {
<RobotFrame <RobotFrame
visible={robotFrameVisible} visible={robotFrameVisible}
onClose={() => setRobotFrameVisible(false)} onClose={() => setRobotFrameVisible(false)}
></RobotFrame>
></RobotFrame> */}
</div> </div>
); );
} }


+ 13
- 2
react-ui/src/services/activeLearn/index.js View File

@@ -7,10 +7,11 @@
import { request } from '@umijs/max'; import { request } from '@umijs/max';


// 分页查询超参数自动寻优 // 分页查询超参数自动寻优
export function getActiveLearnListReq(params) {
export function getActiveLearnListReq(params, skipLoading) {
return request(`/api/mmp/activeLearn`, { return request(`/api/mmp/activeLearn`, {
method: 'GET', method: 'GET',
params, params,
skipLoading
}); });
} }


@@ -54,10 +55,11 @@ export function runActiveLearnReq(id) {


// ----------------------- 实验实例 ----------------------- // ----------------------- 实验实例 -----------------------
// 获取实验实例列表 // 获取实验实例列表
export function getActiveLearnInsListReq(params) {
export function getActiveLearnInsListReq(params, skipLoading) {
return request(`/api/mmp/activeLearnIns`, { return request(`/api/mmp/activeLearnIns`, {
method: 'GET', method: 'GET',
params, params,
skipLoading
}); });
} }


@@ -99,3 +101,12 @@ export function getExpMetricsReq(data) {
}); });
} }


// 编辑实验实例
export function editActiveLearnInsReq(data) {
return request(`/api/mmp/activeLearnIns`, {
method: 'PUT',
data,
skipLoading: true,
});
}


+ 13
- 2
react-ui/src/services/autoML/index.js View File

@@ -7,10 +7,11 @@
import { request } from '@umijs/max'; import { request } from '@umijs/max';


// 分页查询自动学习 // 分页查询自动学习
export function getAutoMLListReq(params) {
export function getAutoMLListReq(params, skipLoading) {
return request(`/api/mmp/machineLearn`, { return request(`/api/mmp/machineLearn`, {
method: 'GET', method: 'GET',
params, params,
skipLoading,
}); });
} }


@@ -54,10 +55,11 @@ export function runAutoMLReq(id) {


// ----------------------- 实验实例 ----------------------- // ----------------------- 实验实例 -----------------------
// 获取实验实例列表 // 获取实验实例列表
export function getExperimentInsListReq(params) {
export function getExperimentInsListReq(params, skipLoading) {
return request(`/api/mmp/machineLearnIns`, { return request(`/api/mmp/machineLearnIns`, {
method: 'GET', method: 'GET',
params, params,
skipLoading,
}); });
} }


@@ -89,3 +91,12 @@ export function batchDeleteExperimentInsReq(data) {
data, data,
}); });
} }

// 编辑实验实例
export function editExperimentInsReq(data) {
return request(`/api/mmp/machineLearnIns`, {
method: 'PUT',
data,
skipLoading: true,
});
}

+ 17
- 0
react-ui/src/services/dataset/index.js 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) { export function downloadAllFiles(params) {
return request(`/api/mmp/newdataset/downloadAllFiles`, { 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) { export function deleteModelVersion(params) {
return request(`/api/mmp/newmodel/deleteVersion`, { return request(`/api/mmp/newmodel/deleteVersion`, {


+ 14
- 2
react-ui/src/services/experiment/index.js View File

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

// 编辑实验实例
export function editExperimentInsReq(data) {
return request(`/api/mmp/experimentIns`, {
method: 'PUT',
data,
skipLoading: true,
});
}

// 查询实验实例实时日志 // 查询实验实例实时日志
export function getQueryByExperimentLog(data) { export function getQueryByExperimentLog(data) {
return request('/api/mmp/experimentIns/realTimeLog/', { return request('/api/mmp/experimentIns/realTimeLog/', {


+ 14
- 2
react-ui/src/services/hyperParameter/index.js View File

@@ -7,10 +7,11 @@
import { request } from '@umijs/max'; import { request } from '@umijs/max';


// 分页查询超参数自动寻优 // 分页查询超参数自动寻优
export function getRayListReq(params) {
export function getRayListReq(params, skipLoading) {
return request(`/api/mmp/ray`, { return request(`/api/mmp/ray`, {
method: 'GET', method: 'GET',
params, params,
skipLoading
}); });
} }


@@ -54,10 +55,11 @@ export function runRayReq(id) {


// ----------------------- 实验实例 ----------------------- // ----------------------- 实验实例 -----------------------
// 获取实验实例列表 // 获取实验实例列表
export function getRayInsListReq(params) {
export function getRayInsListReq(params, skipLoading) {
return request(`/api/mmp/rayIns`, { return request(`/api/mmp/rayIns`, {
method: 'GET', method: 'GET',
params, params,
skipLoading,
}); });
} }


@@ -97,3 +99,13 @@ export function getExpMetricsReq(data) {
data, data,
}); });
} }


// 编辑实验实例
export function editRayInsReq(data) {
return request(`/api/mmp/rayIns`, {
method: 'PUT',
data,
skipLoading: true,
});
}

+ 2
- 1
react-ui/src/services/system/role.ts 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 = { const data = {
roleId, roleId,
roleKey,
status, status,
}; };
return request<API.Result>('/api/system/role/changeStatus', { return request<API.Result>('/api/system/role/changeStatus', {


+ 3
- 2
react-ui/src/types.ts View File

@@ -52,6 +52,7 @@ export type ExperimentInstance = {
nodes_result: { nodes_result: {
[key: string]: any; [key: string]: any;
}; };
node_status: string;
nodes_status: string; nodes_status: string;
global_param: PipelineGlobalParam[]; global_param: PipelineGlobalParam[];
tensorBoardStatus?: TensorBoardStatus; tensorBoardStatus?: TensorBoardStatus;
@@ -111,9 +112,9 @@ export type KeysToCamelCase<T> = {
// 序列化后的流水线节点 // 序列化后的流水线节点
export type PipelineNodeModelSerialize = Omit< export type PipelineNodeModelSerialize = Omit<
PipelineNodeModel, PipelineNodeModel,
'control_strategy' | 'in_parameters' | 'out_parameters'
'in_parameters' | 'out_parameters'
> & { > & {
control_strategy: Record<string, PipelineNodeModelParameter>;
// control_strategy: Record<string, PipelineNodeModelParameter>;
in_parameters: Record<string, PipelineNodeModelParameter>; in_parameters: Record<string, PipelineNodeModelParameter>;
out_parameters: Record<string, PipelineNodeModelParameter>; out_parameters: Record<string, PipelineNodeModelParameter>;
}; };


+ 5
- 2
react-ui/src/utils/date.ts View File

@@ -1,4 +1,3 @@
import { now } from '@/hooks/useServerTime';
import dayjs from 'dayjs'; import dayjs from 'dayjs';


/** /**
@@ -13,8 +12,12 @@ export const elapsedTime = (begin?: string | Date | null, end?: string | Date |
return '--'; return '--';
} }


if (end === undefined || end === null) {
return '--';
}

const beginDate = dayjs(begin); const beginDate = dayjs(begin);
const endDate = end === undefined || end === null ? dayjs(now()) : dayjs(end);
const endDate = dayjs(end); // end === undefined || end === null ? dayjs(now()) : dayjs(end);
if (!beginDate.isValid() || !endDate.isValid()) { if (!beginDate.isValid() || !endDate.isValid()) {
return '--'; return '--';
} }


+ 60
- 0
react-ui/src/utils/experiment.ts View File

@@ -0,0 +1,60 @@
import { ExperimentStatus } from '@/enums';
import { NodeStatus } from '@/types';
import { parseJsonText } from './index';

/**
* 获取工作流节点
*
* @param node_status - 流水线workflow节点,json字符串
* @return workflow 节点
*/
export const getWorkflowStatus = (node_status?: string | null) => {
if (!node_status) {
return undefined;
}

const nodeStatusJson = parseJsonText(node_status);
if (!nodeStatusJson) {
return undefined;
}

for (const key in nodeStatusJson) {
if (key.startsWith('workflow')) {
return nodeStatusJson[key];
}
}
return undefined;
};

/**
* 获取实例状态
* 终止或者 workflowStatus 不存在时,取实例状态,否则取流水线状态
*
* @param instanceStatus - 实例状态
* @param workflowStatus - 流水线workflow节点
* @return 实例状态
*/
export const getExperimentInstanceStatus = (
instanceStatus: ExperimentStatus,
workflowStatus?: NodeStatus,
): ExperimentStatus => {
return instanceStatus === ExperimentStatus.Terminated || !workflowStatus
? instanceStatus
: workflowStatus?.phase;
};

/**
* 获取实例状态
* 终止或者 workflowStatus 不存在时,取实例状态,否则取流水线状态
*
* @param instanceStatus - 实例状态
* @param workflowStatus - 流水线workflow节点
* @return 实例状态
*/
export const getInstanceStatusInList = (
instanceStatus: ExperimentStatus,
node_status?: string | null,
): ExperimentStatus => {
const workflowStatus = getWorkflowStatus(node_status);
return getExperimentInstanceStatus(instanceStatus, workflowStatus);
};

+ 11
- 7
react-ui/src/utils/index.ts View File

@@ -6,7 +6,6 @@


import { PageEnum } from '@/enums/pagesEnums'; import { PageEnum } from '@/enums/pagesEnums';
import G6 from '@antv/g6'; import G6 from '@antv/g6';
import { number } from 'echarts';


/** /**
* 生成 8 位随机数 * 生成 8 位随机数
@@ -268,19 +267,25 @@ export const hasNoValue = (value?: any | null): boolean => {
/** /**
* 获取 git 仓库的 url * 获取 git 仓库的 url
* *
* @param {string} url - the url of the git repository
* @param {string} branch - the branch of the repository
* @param {string} [url] - the url of the git repository
* @param {string} [branch] - the branch of the repository
* @return {string} the url of the repository * @return {string} the url of the repository
* *
* If `branch` is given, the url will be in the format of 'http://gitlab.com/user/repo/tree/branch'. * If `branch` is given, the url will be in the format of 'http://gitlab.com/user/repo/tree/branch'.
* Otherwise, the url will be in the format of 'http://gitlab.com/user/repo'. * Otherwise, the url will be in the format of 'http://gitlab.com/user/repo'.
*/ */
export const getGitUrl = (url: string, branch: string): string => {
export const getGitUrl = (url?: string, branch?: string): string => {
if (!url) { if (!url) {
return ''; return '';
} }
const gitUrl = url.replace(/\.git$/, '');
return branch ? `${gitUrl}/tree/${branch}` : gitUrl;
let gitUrlStr = url.replace(/\.git$/, '');
const gitUrl = new URL(gitUrlStr);
if (gitUrl.port === '30202') {
gitUrl.port = '30203'; // 30202 该为 30203
}
gitUrlStr = gitUrl.href;

return branch ? `${gitUrlStr.toString()}/tree/${branch}` : gitUrlStr;
}; };


/** /**
@@ -347,4 +352,3 @@ export const trimCharacter = (str: string, ch: string): string => {
export const convertEmptyStringToUndefined = (value?: string): string | undefined => { export const convertEmptyStringToUndefined = (value?: string): string | undefined => {
return value === '' ? undefined : value; return value === '' ? undefined : value;
}; };


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

Loading…
Cancel
Save