Compare commits

...

279 Commits

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

---
apiVersion: v1
@@ -28,9 +33,15 @@ metadata:
spec:
type: NodePort
ports:
- port: 9200
- name: http
port: 9200
nodePort: 31206
protocol: TCP
- name: debug
nodePort: 31221
port: 5005
protocol: TCP
targetPort: 5005
selector:
app: ci4s-auth


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

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


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';
import './styles/menu.less';
import { needAuth } from './utils';
// import { closeAllModals } from './utils/modal';
import { gotoLoginPage } from './utils/ui';
export { requestConfig as request } from './requestConfig';

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


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

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

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

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

/** 系统内嵌 iframe,目前系统有数据标注、应用开发、开发环境、GitLink 四个子系统,使用时可以添加其他子系统 */
function IframePage({ type, openInTab = false, className, style }: IframePageProps) {
function IframePage({
type,
url,
showLoading = true,
openInTab = false,
className,
style,
}: IframePageProps) {
const [iframeUrl, setIframeUrl] = useState('');
const [loading, setLoading] = useState(false);
// const [loading, setLoading] = useState(false);

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

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

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 (
<div className={classNames('kf-iframe-page', className)} style={style}>
{loading && createPortal(<KFSpin size="large" />, document.body)}
<FullScreenFrame url={iframeUrl} onLoad={hideLoading} onError={hideLoading} />
{/* {loading && createPortal(<KFSpin size="large" />, document.body)} */}
{iframeUrl && <FullScreenFrame url={iframeUrl} onLoad={handleLoad} onError={handleError} />}
{openInTab && <FloatButton onClick={() => window.open(iframeUrl, '_blank')} />}
</div>
);


+ 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 mirrorImg from '@/assets/img/modal-select-mirror.png';
import modelImg from '@/assets/img/modal-select-model.png';
import { AvailableRange, CommonTabKeys } from '@/enums';
import { AvailableRange, CommonTabKeys, MirrorVersionStatus } from '@/enums';
import { ResourceData, ResourceVersionData } from '@/pages/Dataset/config';
import { MirrorVersionData } from '@/pages/Mirror/Info';
import { MirrorData } from '@/pages/Mirror/List';
@@ -24,11 +24,11 @@ export enum ResourceSelectorType {
}

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


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

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


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

@@ -10,13 +10,21 @@ type RunDurationProps = {
};
function RunDuration({ createTime, finishTime, className, style }: RunDurationProps) {
const [now] = useServerTime();
const [currentTime, setCurrentTime] = useState<Date>(now());
const [currentTime, setCurrentTime] = useState<Date>(finishTime ? new Date(finishTime) : now());

// console.log(
// 'currentTime',
// new Date(createTime ?? 0),
// currentTime,
// (currentTime.getTime() - new Date(createTime ?? 0).getTime()) / 1000,
// );

// 定时刷新耗时
useEffect(() => {
if (finishTime) {
setCurrentTime(new Date(finishTime));
} else {
setCurrentTime(now());
const timer = setInterval(() => {
setCurrentTime(now());
}, 1000);
@@ -25,6 +33,7 @@ function RunDuration({ createTime, finishTime, className, style }: RunDurationPr
};
}
}, [finishTime, now]);

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


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

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

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

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

// 自动化任务类型


+ 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 { NodeStatus } from '@/types';
import { parseJsonText } from '@/utils';
import { useEffect } from 'react';

export type MessageHandler = (experimentInsId: number, status: string, finishedAt: string, nodes: Record<string, NodeStatus>) => void
export const useSSE = (experimentInsId: number, status: ExperimentStatus, name: string, namespace: string, onMessage: MessageHandler) => {
const isRunning = status === ExperimentStatus.Pending || status === ExperimentStatus.Running
export type MessageHandler = (
experimentId: number,
experimentInsId: number,
status: string,
finishTime: string,
nodes: Record<string, NodeStatus>,
) => void;
export const useSSE = (
experimentId: number,
experimentInsId: number,
status: ExperimentStatus,
name: string,
namespace: string,
onMessage: MessageHandler,
) => {
const isRunning = status === ExperimentStatus.Pending || status === ExperimentStatus.Running;
useEffect(() => {
if (isRunning) {
const { origin } = location;
@@ -22,8 +35,8 @@ export const useSSE = (experimentInsId: number, status: ExperimentStatus, name:
const dataJson = parseJsonText(data);
const statusData = dataJson?.result?.object?.status;
if (statusData) {
const { finishedAt, phase, nodes } = statusData;
onMessage(experimentInsId, phase, finishedAt, nodes);
const { finishedAt, phase, nodes } = statusData;
onMessage(experimentId, experimentInsId, phase, finishedAt, nodes);
}
};

@@ -33,8 +46,7 @@ export const useSSE = (experimentInsId: number, status: ExperimentStatus, name:

return () => {
evtSource.close();
}
};
}
}, [experimentInsId, isRunning, name, namespace, onMessage]);
}, [experimentId, experimentInsId, isRunning, name, namespace, onMessage]);
};

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

@@ -51,13 +51,12 @@ function ActiveLearnInstance() {
const [res] = await to(getActiveLearnInsReq(instanceId));
if (res && res.data) {
const info = res.data as ActiveLearnInstanceData;
const { param, node_status, argo_ins_name, argo_ins_ns, status, create_time } = info;
const { param, node_status, argo_ins_name, argo_ins_ns, status } = info;
// 解析配置参数
const paramJson = parseJsonText(param);
if (paramJson) {
setExperimentInfo({
...paramJson.data,
create_time,
});
}

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

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

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

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

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


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

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

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

return (
<div className={classNames(styles['active-learn-basic'], className)}>
{isInstance && runStatus && (
<ExperimentRunBasic
create_time={info?.create_time}
runStatus={runStatus}
instanceStatus={instanceStatus}
/>
{isInstance && workflowStatus && (
<ExperimentRunBasic workflowStatus={workflowStatus} instanceStatus={instanceStatus} />
)}
{!isInstance && (
<ConfigInfo


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

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

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


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

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

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

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


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

@@ -51,7 +51,7 @@ function AutoMLInstance() {
const [res] = await to(getExperimentInsReq(instanceId));
if (res && res.data) {
const info = res.data as AutoMLInstanceData;
const { param, node_status, argo_ins_name, argo_ins_ns, status, create_time, type } = info;
const { param, node_status, argo_ins_name, argo_ins_ns, status, type } = info;

setType(type);
// 解析配置参数
@@ -59,7 +59,6 @@ function AutoMLInstance() {
if (paramJson) {
setAutoMLInfo({
...paramJson.data,
create_time,
type,
});
}
@@ -95,7 +94,10 @@ function AutoMLInstance() {
};

const setupSSE = (name: string, namespace: string) => {
const { origin } = location;
let { origin } = location;
if (process.env.NODE_ENV === 'development') {
origin = 'http://172.20.32.235:31213';
}
const params = encodeURIComponent(`metadata.namespace=${namespace},metadata.name=${name}`);
const evtSource = new EventSource(
`${origin}/api/v1/realtimeStatus?listOptions.fieldSelector=${params}`,
@@ -110,17 +112,17 @@ function AutoMLInstance() {
if (dataJson) {
const nodes = dataJson?.result?.object?.status?.nodes;
if (nodes) {
// 节点
// 设置节点
setNodes(nodes);

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

if (workflowStatus) {
setWorkflowStatus(workflowStatus);

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


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

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

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

// 格式化权重
const formatMetricsWeight = (value: string) => {
if (!value) {
return '--';
@@ -34,18 +46,33 @@ const formatMetricsWeight = (value: string) => {
.join('\n');
};

// 格式化算法
const formatAlgorithm = (algorithms: EnumOptions[]) => {
return (value: string) => {
if (!value) {
return '--';
}
const list = value
.split(',')
.filter((v) => v !== '')
.map((v) => v.trim());
return list.map((v) => formatEnum(algorithms)(v)).join(',');
};
};

type AutoMLBasicProps = {
info?: AutoMLData;
className?: string;
isInstance?: boolean;
runStatus?: NodeStatus;
workflowStatus?: NodeStatus;
instanceStatus?: ExperimentStatus;
instanceCreateTime?: string;
};

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

return (
<div className={classNames(styles['auto-ml-basic'], className)}>
{isInstance && runStatus && (
<ExperimentRunBasic
create_time={info?.create_time}
runStatus={runStatus}
instanceStatus={instanceStatus}
/>
{isInstance && workflowStatus && (
<ExperimentRunBasic workflowStatus={workflowStatus} instanceStatus={instanceStatus} />
)}
{!isInstance && (
<ConfigInfo


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

@@ -8,69 +8,7 @@ import {
autoMLTaskTypeOptions,
} from '@/enums';
import { Col, Form, InputNumber, Radio, Row, Select, Switch } from 'antd';

// 分类算法
const classificationAlgorithms = [
'adaboost',
'bernoulli_nb',
'decision_tree',
'extra_trees',
'gaussian_nb',
'gradient_boosting',
'k_nearest_neighbors',
'lda',
'liblinear_svc',
'libsvm_svc',
'mlp',
'multinomial_nb',
'passive_aggressive',
'qda',
'random_forest',
'sgd',
'LightGBMClassification',
'XGBoostClassification',
'StackingClassification',
].map((name) => ({ label: name, value: name }));

// 回归算法
const regressorAlgorithms = [
'adaboost',
'ard_regression',
'decision_tree',
'extra_trees',
'gaussian_process',
'gradient_boosting',
'k_nearest_neighbors',
'liblinear_svr',
'libsvm_svr',
'mlp',
'random_forest',
'sgd',
'LightGBMRegression',
'XGBoostRegression',
].map((name) => ({ label: name, value: name }));

// 特征预处理算法
const featureAlgorithms = [
'densifier',
'extra_trees_preproc_for_classification',
'extra_trees_preproc_for_regression',
'fast_ica',
'feature_agglomeration',
'kernel_pca',
'kitchen_sinks',
'liblinear_svc_preprocessor',
'no_preprocessing',
'nystroem_sampler',
'pca',
'polynomial',
'random_trees_embedding',
'select_percentile_classification',
'select_percentile_regression',
'select_rates_classification',
'select_rates_regression',
'truncatedSVD',
].map((name) => ({ label: name, value: name }));
import { classificationAlgorithms, featureAlgorithms, regressorAlgorithms } from './utils';

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


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


+ 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 { useSSE, type MessageHandler } from '@/hooks/useSSE';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import { ExperimentInstance, NodeStatus } from '@/types';
import { ExperimentCompleted } from '@/utils/constant';
import { formatDate } from '@/utils/date';
import { getWorkflowStatus } from '@/utils/experiment';
import { Typography } from 'antd';
import React, { useCallback } from 'react';
import styles from './index.less';

type ExperimentInstanceProps = {
create_time?: string;
finish_time?: string;
status: ExperimentStatus;
argo_ins_name: string;
argo_ins_ns: string;
experimentInsId: number;
type ExperimentInstanceComponentProps = {
experimentId: number;
instance: ExperimentInstance;
};

function ExperimentInstance({
create_time,
finish_time,
status,
argo_ins_name,
argo_ins_ns,
experimentInsId,
}: ExperimentInstanceProps) {
function ExperimentInstanceComponent({ experimentId, instance }: ExperimentInstanceComponentProps) {
const { id, argo_ins_name, argo_ins_ns, node_status } = instance;
const workflowStatus = getWorkflowStatus(node_status) as NodeStatus | undefined;
const status = instance.status as ExperimentStatus;
const createTime = workflowStatus?.startedAt;
const finishTime = workflowStatus?.finishedAt;
const statusInfo = experimentStatusInfo[status];
const handleSSEMessage: MessageHandler = useCallback(
(experimentInsId: number, status: string, finish_time: string) => {
(experimentId: number, experimentInsId: number, status: string, finishTime: string) => {
window.postMessage({
type: ExperimentCompleted,
payload: {
id: experimentInsId,
experimentId,
experimentInsId,
status,
finish_time,
finishTime,
},
});
},
[],
);
useSSE(experimentInsId, status, argo_ins_name, argo_ins_ns, handleSSEMessage);
useSSE(experimentId, id, status, argo_ins_name, argo_ins_ns, handleSSEMessage);

return (
<React.Fragment>
<div className={styles.description}>
<RunDuration createTime={create_time} finishTime={finish_time} />
<RunDuration createTime={createTime} finishTime={finishTime} />
</div>
<div className={styles.startTime}>
<Typography.Text ellipsis={{ tooltip: formatDate(create_time) }}>
{formatDate(create_time)}
<Typography.Text ellipsis={{ tooltip: formatDate(createTime) }}>
{formatDate(createTime)}
</Typography.Text>
</div>
<div className={styles.statusBox}>
<img
style={{ width: '17px', marginRight: '7px' }}
src={experimentStatusInfo[status]?.icon}
draggable={false}
alt=""
/>
<span style={{ color: experimentStatusInfo[status]?.color }} className={styles.statusIcon}>
{experimentStatusInfo[status]?.label}
</span>
{statusInfo ? (
<>
<img
style={{ width: '17px', marginRight: '7px' }}
src={statusInfo.icon}
draggable={false}
alt=""
/>
<span style={{ color: statusInfo.color }}>{statusInfo.label}</span>
</>
) : (
'--'
)}
</div>
</React.Fragment>
);
}

export default ExperimentInstance;
export default ExperimentInstanceComponent;

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

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

type ExperimentListInfo = {
getListReq: (params: any) => Promise<any>; // 获取列表
getInsListReq: (params: any) => Promise<any>; // 获取实例列表
getListReq: (params: any, skipLoading?: boolean) => Promise<any>; // 获取列表
getInsListReq: (params: any, skipLoading?: boolean) => Promise<any>; // 获取实例列表
deleteRecordReq: (params: any) => Promise<any>; // 删除
runRecordReq: (params: any) => Promise<any>; // 运行
deleteInsReq: (params: any) => Promise<any>; // 删除实例
batchDeleteInsReq: (params: any) => Promise<any>; // 批量删除实例
stopInsReq: (params: any) => Promise<any>; // 终止实例
editInsReq: (params: any) => Promise<any>; // 编辑实例
title: string; // 标题
pathPrefix: string; // 路由路径前缀
idProperty: string; // ID属性
nameProperty: string; // 名称属性
descProperty: string; // 描述属性
idInsProperty: string; // 实例返回的ID属性
};

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

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

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

// 获取自主机器学习或超参数自动优化列表
const 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(
async (recordId: number, page: number, size: number) => {
async (recordId: number, page: number, size: number, skipLoading: boolean = false) => {
const params = {
[config.idProperty]: recordId,
page: page,
size: size,
};
const request = config.getInsListReq;
const [res] = await to(request(params));
const [res] = await to(request(params, skipLoading));
if (res && res.data) {
const { content = [], totalElements = 0 } = res.data;
try {
@@ -111,59 +109,115 @@ function ExperimentList({ type }: ExperimentListProps) {

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

// 刷新实验实例列表
const refreshExperimentIns = useCallback(
(experimentId: number) => {
(experimentId: number, skipLoading: boolean = false) => {
const length = experimentInsList.length;
getExperimentInsList(experimentId, 0, length);
getExperimentInsList(experimentId, 0, length, skipLoading);
},
[getExperimentInsList, experimentInsList],
);

// 新增,删除版本时,重置分页,然后刷新版本列表
// 更新实验实例状态
const editExperimentIns = useCallback(
async (
experimentId: number,
experimentInsId: number,
status: ExperimentStatus,
argo_ins_name: string,
argo_ins_ns: string,
) => {
const params = {
[config.idInsProperty]: experimentId,
id: experimentInsId,
status: status,
argo_ins_name,
argo_ins_ns,
};
const request = config.editInsReq;
const [res] = await to(request(params));
if (res && res.data) {
refreshExperimentIns(experimentId, true);
refreshExperimentList(true);
}
},
[config, refreshExperimentIns, refreshExperimentList],
);

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

// expandedRowKeys 变化
useEffect(() => {
if (expandedRowKeys.length > 0) {
getExperimentInsList(expandedRowKeys[0], 0, 5);
refreshExperimentList();
}
}, [expandedRowKeys, getExperimentInsList, refreshExperimentList]);

// 实验实例状态变化
useEffect(() => {
const handleMessage = (e: MessageEvent) => {
const { type, payload } = e.data;
if (type === ExperimentCompleted) {
const { id, status, finish_time } = payload;

// 修改实例的状态和结束时间
setExperimentInsList((prev) =>
prev.map((v) =>
v.id === id
? {
...v,
status: status,
finish_time: finish_time,
}
: v,
),
const { experimentId, experimentInsId, status, finishTime } = payload;
const currentIns = experimentInsList.find((v) => v.id === experimentInsId);
console.log(
'实验实例状态变化',
currentIns?.status,
status,
experimentId,
experimentInsId,
finishTime,
);

if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = undefined;
if (
!currentIns ||
currentIns.status === ExperimentStatus.Terminated ||
currentIns.status === status
) {
return;
}

timerRef.current = setTimeout(() => {
refreshExperimentList();
}, 10000);
// refreshExperimentList(true);
// refreshExperimentIns(experimentId);
editExperimentIns(
experimentId,
experimentInsId,
status,
currentIns.argo_ins_name,
currentIns.argo_ins_ns,
);

// 修改实例的状态和结束时间
// setExperimentInsList((prev) =>
// prev.map((v) =>
// v.id === experimentInsId
// ? {
// ...v,
// status: status,
// finish_time: finishTime,
// }
// : v,
// ),
// );
}
};

window.addEventListener('message', handleMessage);
return () => {
window.removeEventListener('message', handleMessage);
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = undefined;
}
};
}, [refreshExperimentList]);
}, [experimentInsList, editExperimentIns]);

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

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

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

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

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

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

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


+ 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%;
}
}

.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 LogList from '@/pages/Experiment/components/LogList';
import { NodeStatus } from '@/types';
import EmptyLog from './empty';
import styles from './index.less';

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


+ 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 { experimentStatusInfo } from '@/pages/Experiment/status';
import { type NodeStatus } from '@/types';
import { getExperimentInstanceStatus } from '@/utils/experiment';
import { formatDate } from '@/utils/format';
import { Flex } from 'antd';
import { useMemo } from 'react';

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

function ExperimentRunBasic({ create_time, runStatus, instanceStatus }: ExperimentRunBasicProps) {
function ExperimentRunBasic({ workflowStatus, instanceStatus }: ExperimentRunBasicProps) {
const instanceDatas = useMemo(() => {
if (!runStatus) {
return [];
}

const status =
instanceStatus === ExperimentStatus.Terminated ? instanceStatus : runStatus.phase;
const status = getExperimentInstanceStatus(instanceStatus as ExperimentStatus, workflowStatus);
const statusInfo = experimentStatusInfo[status];

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

return (
<ConfigInfo


+ 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 { runTensorBoardReq } from '@/services/experiment/index.js';
import KFEmpty, { EmptyType } from '@/components/KFEmpty';
import KFSpin from '@/components/KFSpin';
import { TensorBoardStatus } from '@/enums';
import { getTensorBoardStatusReq, runTensorBoardReq } from '@/services/experiment/index.js';
import { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage';
import { useEffect, useState } from 'react';
import { LoadingOutlined } from '@ant-design/icons';
import { useCallback, useEffect, useState } from 'react';
import styles from './index.less';

type TensorBoardProps = {
namespace?: string;
@@ -16,28 +21,75 @@ type TensorBoardProps = {
};

function ExperimentVisualResult({ namespace, path }: TensorBoardProps) {
const [tensorboardUrl, setTensorboardUrl] = useState('');
useEffect(() => {
// 运行 TensorBoard
const runTensorBoard = async () => {
const params = {
namespace: namespace,
path: path,
};
const [res] = await to(runTensorBoardReq(params));
if (res && res.data) {
const url = res.data;
SessionStorage.setItem(SessionStorage.tensorBoardUrlKey, url);
setTensorboardUrl(url);
const [tensorboardUrl, setTensorboardUrl] = useState<string | undefined | null>(undefined);
const [status, setStatus] = useState<TensorBoardStatus | undefined>(undefined);

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

+ 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._-]+$/,
message: '版本只支持字母、数字、点(.)、下划线(_)、中横线(-)',
message: '数据集版本只支持字母、数字、点(.)、下划线(_)、中横线(-)',
},
{
validator: (_rule, value) => {
if (value === 'master') {
return Promise.reject(`版本不能为 master`);
return Promise.reject(`数据集版本不能为 master`);
} else if (value === 'origin') {
return Promise.reject(`数据集版本不能为 origin`);
}
return Promise.resolve();
},


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


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

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


+ 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;
cursor: pointer;

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

&--praised {
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 { useCallback, useEffect, useState } from 'react';
import AddVersionModal from '../AddVersionModal';
import EditVersionModal from '../EditVersionModal';
import ResourceIntro from '../ResourceIntro';
import ResourceVersion from '../ResourceVersion';
import VersionCompareModal from '../VersionCompareModal';
@@ -132,6 +133,18 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => {
});
};

// 版本编辑
const showEditVersionModal = () => {
const { close } = openAntdModal(EditVersionModal, {
resourceType: resourceType,
resourceVersion: info,
onOk: () => {
getResourceDetail();
close();
},
});
};

// 选择版本
const showVersionSelector = () => {
const { close } = openAntdModal(VersionSelectorModal, {
@@ -291,6 +304,14 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => {
<Button type="default" onClick={showModal} icon={<KFIcon type="icon-xinjian2" />}>
创建新版本
</Button>
<Button
type="default"
style={{ marginLeft: '20px' }}
icon={<KFIcon type="icon-bianji" />}
onClick={showEditVersionModal}
>
版本编辑
</Button>
<Button
type="default"
style={{ marginLeft: '20px' }}


+ 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) {
const timeAgo = `更新于${
item.update_time ? formatDate(item.update_time, 'YYYY-MM-DD') : item.time_ago ?? ''
}`;
const timeAgo = `最近更新:${formatDate(item.full_last_update_time, 'YYYY-MM-DD HH:mm')}`;
const create_by = item.create_by ?? '';
return (
<div className={styles['resource-item']} onClick={() => onClick(item)}>


+ 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 = {
reset: () => void;
resetPage: () => void;
};

type ResourceListProps = {
@@ -97,6 +98,12 @@ function ResourceList(
setDataList(undefined);
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) => {
dataListRef.current?.resetPage();
setActiveType((prev) => (prev === record.name ? undefined : record.name));
};

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

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


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

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

// 数据集数据


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

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


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

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



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

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

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

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

return () => {
if (evtSourceRef.current) {
@@ -61,54 +65,83 @@ function ExperimentText() {
}, []);

// 获取流水线模版
const getWorkflow = async () => {
const [res] = await to(getWorkflowById(locationParams.workflowId));
if (res && res.data && res.data.dag) {
try {
const dag = JSON.parse(res.data.dag);
dag.nodes.forEach((item) => {
item.in_parameters = JSON.parse(item.in_parameters);
item.out_parameters = JSON.parse(item.out_parameters);
item.control_strategy = JSON.parse(item.control_strategy);
item.imgName = item.img.slice(0, item.img.length - 4);
});
workflowRef.current = dag;
getExperimentInstance();
} catch (error) {
// JSON.parse 错误
console.error('JSON.parse error: ', error);
}
}
};
// const getWorkflow = async () => {
// const [res] = await to(getWorkflowById(locationParams.workflowId));
// if (res && res.data && res.data.dag) {
// try {
// const dag = JSON.parse(res.data.dag);
// dag.nodes.forEach((item) => {
// item.in_parameters = JSON.parse(item.in_parameters);
// item.out_parameters = JSON.parse(item.out_parameters);
// item.control_strategy = JSON.parse(item.control_strategy);
// item.imgName = item.img.slice(0, item.img.length - 4);
// });
// workflowRef.current = dag;
// getExperimentInstance();
// } catch (error) {
// // JSON.parse 错误
// console.error('JSON.parse error: ', error);
// }
// }
// };

// 获取实验实例
const getExperimentInstance = async () => {
const [res] = await to(getExperimentIns(locationParams.id));
if (res && res.data && workflowRef.current) {
if (res && res.data) {
setExperimentIns(res.data);
const { status, nodes_status, argo_ins_ns, argo_ins_name, finish_time } = res.data;
const workflowData = workflowRef.current;
const { status, nodes_status, argo_ins_ns, argo_ins_name, finish_time, dag } = res.data;
if (!dag) {
return;
}

const workflow = parseJsonText(dag);
const experimentStatusObjs = parseJsonText(nodes_status);
workflowData.nodes.forEach((item) => {
const experimentNode = experimentStatusObjs?.[item.id];
updateWorkflowNode(item, experimentNode);
if (!workflow || !workflow.nodes) {
return;
}

workflow.nodes.forEach((item) => {
item.in_parameters = parseJsonText(item.in_parameters);
item.out_parameters = parseJsonText(item.out_parameters);
item.control_strategy = parseJsonText(item.control_strategy);
item.imgName = item.img.slice(0, item.img.length - 4);
});
workflowRef.current = workflow;

if (experimentStatusObjs) {
// 更新各个节点
workflow.nodes.forEach((item) => {
const experimentNode = experimentStatusObjs[item.id];
updateWorkflowNode(item, experimentNode);
});

// 设置 workflow 总状态
Object.keys(experimentStatusObjs).some((key) => {
if (key.startsWith(NodePrefix)) {
const tempWorkflowStatus = experimentStatusObjs[key];
setWorkflowStatus(tempWorkflowStatus);
return true;
}
return false;
});
}

// 绘制图
getGraphData(workflowData, true);
getGraphData(workflow, true);

if (status === ExperimentStatus.Pending) {
// 如果状态是 Pending, 打开第一个节点
const node = workflowData.nodes[0];
const node = workflow.nodes[0];
if (node) {
setExperimentNodeData(node);
openPropsDrawer();
}
} else if (status === ExperimentStatus.Running) {
// 如果状态是 Running,打开第一个运行中的节点,如果没有运行中的节点,则打开第一个节点
// 如果状态是 Running,打开第一个 Running 或者 pending 的节点,如果没有,则打开第一个节点
const node =
workflowData.nodes.find((item) => item.experimentStatus === ExperimentStatus.Running) ??
workflowData.nodes[0];
workflow.nodes.find((item) => item.experimentStatus === ExperimentStatus.Running || item.experimentStatus === ExperimentStatus.Pending) ??
workflow.nodes[0];
if (node) {
setExperimentNodeData(node);
openPropsDrawer();
@@ -135,23 +168,36 @@ function ExperimentText() {
return;
}
try {
const dataJson = JSON.parse(data);
const dataJson = parseJsonText(data);
const statusData = dataJson?.result?.object?.status;
if (!statusData) {
return;
}
const { startedAt, finishedAt, phase, nodes = {} } = statusData;
setExperimentIns((prev) => ({
...prev,
finish_time: finishedAt,
status: phase,
}));
const { finishedAt, phase, nodes = {} } = statusData;

// 更新实验实例状态和结束时间
// setExperimentIns((prev) => ({
// ...prev,
// finish_time: finishedAt,
// status: phase,
// }));

// 设置总 workflow 状态
const tempWorkflowStatus = Object.values(nodes).find((node) =>
node.displayName.startsWith(NodePrefix),
);
if (tempWorkflowStatus) {
setWorkflowStatus(tempWorkflowStatus);
}

// 更新各个节点
const workflowData = workflowRef.current;
workflowData.nodes.forEach((item) => {
const experimentNode = Object.values(nodes).find((node) => node.displayName === item.id);
updateWorkflowNode(item, experimentNode);
});

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

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

// 更新各个节点
function updateWorkflowNode(workflowNode, statusNode) {
if (!statusNode) {
return;
@@ -471,29 +518,30 @@ function ExperimentText() {
<div className={styles['pipeline-container']}>
<div className={styles['pipeline-container__top']}>
<div className={styles['pipeline-container__top__info']}>
启动时间:{formatDate(experimentIns?.create_time)}
启动时间:{formatDate(workflowStatus?.startedAt)}
</div>
<div className={styles['pipeline-container__top__info']}>
执行时长:
<RunDuration
createTime={experimentIns?.create_time}
finishTime={experimentIns?.finish_time}
createTime={workflowStatus?.startedAt}
finishTime={workflowStatus?.finishedAt}
/>
</div>
<div className={styles['pipeline-container__top__info']}>
状态:
<div
style={{
width: '8px',
height: '8px',
borderRadius: '50%',
marginRight: '6px',
backgroundColor: experimentStatusInfo[experimentIns?.status]?.color,
}}
></div>
<span style={{ color: experimentStatusInfo[experimentIns?.status]?.color }}>
{experimentStatusInfo[experimentIns?.status]?.label}
</span>
{statusInfo ? (
<>
<img
style={{ width: '17px', marginRight: '7px' }}
src={statusInfo.icon}
draggable={false}
alt=""
/>
<span style={{ color: statusInfo.color }}>{statusInfo.label}</span>
</>
) : (
'--'
)}
</div>
<Button
className={styles['pipeline-container__top__param-button']}


+ 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 类型
if (Number(paramType) === 3) {
return (
@@ -40,9 +40,9 @@ export const getParamComponent = (paramType: number, isSensitive?: number): JSX.
</Radio.Group>
);
}
if (isSensitive && Number(isSensitive) === 1) {
return <Input.Password placeholder="请输入值" visibilityToggle={false} allowClear />;
}
// if (isSensitive && Number(isSensitive) === 1) {
// return <Input.Password placeholder="请输入值" visibilityToggle={false} allowClear />;
// }
return <Input placeholder="请输入值" allowClear />;
};

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

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

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


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

@@ -55,10 +55,6 @@
display: flex;
align-items: center;
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) {
const { message } = App.useApp();
const allIntanceIds = useMemo(() => {
return experimentInsList?.map((item) => item.id) || [];
return (
experimentInsList
?.filter(
(item) =>
item.status !== ExperimentStatus.Running && item.status !== ExperimentStatus.Pending,
)
.map((item) => item.id) || []
);
}, [experimentInsList]);
const [
selectedIns,
@@ -127,7 +134,12 @@ function ExperimentInstanceList({
<div>
<div className={styles.tableExpandBox} style={{ paddingBottom: '16px' }}>
<div className={styles.check}>
<Checkbox checked={checked} indeterminate={indeterminate} onChange={checkAll}></Checkbox>
<Checkbox
checked={checked}
indeterminate={indeterminate}
disabled={allIntanceIds.length === 0}
onChange={checkAll}
></Checkbox>
</div>
<div className={styles.index}>序号</div>
<div className={styles.tensorBoard}>可视化</div>
@@ -185,14 +197,7 @@ function ExperimentInstanceList({
)}
</div>

<ExperimentInstanceComponent
create_time={item.create_time}
finish_time={item.finish_time}
status={item.status as ExperimentStatus}
argo_ins_name={item.argo_ins_name}
argo_ins_ns={item.argo_ins_ns}
experimentInsId={item.id}
></ExperimentInstanceComponent>
<ExperimentInstanceComponent instance={item}></ExperimentInstanceComponent>

<div className={styles.operation}>
<Button


+ 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 { useSSE, type MessageHandler } from '@/hooks/useSSE';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import { ExperimentInstance, NodeStatus } from '@/types';
import { ExperimentCompleted } from '@/utils/constant';
import { formatDate } from '@/utils/date';
import { getWorkflowStatus } from '@/utils/experiment';
import { Typography } from 'antd';
import React, { useCallback } from 'react';
import styles from './index.less';

type ExperimentInstanceProps = {
create_time?: string;
finish_time?: string;
status: ExperimentStatus;
argo_ins_name: string;
argo_ins_ns: string;
experimentInsId: number;
type ExperimentInstanceComponentProps = {
instance: ExperimentInstance;
};

function ExperimentInstance({
create_time,
finish_time,
status,
argo_ins_name,
argo_ins_ns,
experimentInsId,
}: ExperimentInstanceProps) {
function ExperimentInstanceComponent({ instance }: ExperimentInstanceComponentProps) {
const { id, experiment_id, argo_ins_name, argo_ins_ns, nodes_status, create_time, finish_time } =
instance;
const workflowStatus = getWorkflowStatus(nodes_status) as NodeStatus | undefined;
const status = instance.status as ExperimentStatus;
const createTime = workflowStatus?.startedAt ?? create_time;
const finishTime = workflowStatus?.finishedAt ?? finish_time;
const statusInfo = experimentStatusInfo[status];

const handleSSEMessage: MessageHandler = useCallback(
(experimentInsId: number, status: string, finish_time: string) => {
(experimentId: number, experimentInsId: number, status: string, finishTime: string) => {
window.postMessage({
type: ExperimentCompleted,
payload: {
id: experimentInsId,
experimentId,
experimentInsId,
status,
finish_time,
finishTime,
},
});
},
[],
);
useSSE(experimentInsId, status, argo_ins_name, argo_ins_ns, handleSSEMessage);
useSSE(experiment_id, id, status, argo_ins_name, argo_ins_ns, handleSSEMessage);

return (
<React.Fragment>
<div className={styles.description}>
<div style={{ width: '50%' }}>
<RunDuration createTime={create_time} finishTime={finish_time} />
<RunDuration createTime={createTime} finishTime={finishTime} />
</div>
<div style={{ width: '50%' }} className={styles.startTime}>
<Typography.Text ellipsis={{ tooltip: formatDate(create_time) }}>
{formatDate(create_time)}
<Typography.Text ellipsis={{ tooltip: formatDate(createTime) }}>
{formatDate(createTime)}
</Typography.Text>
</div>
</div>
<div className={styles.statusBox}>
<img
style={{ width: '17px', marginRight: '7px' }}
src={experimentStatusInfo[status]?.icon}
draggable={false}
alt=""
/>
<span style={{ color: experimentStatusInfo[status]?.color }} className={styles.statusIcon}>
{experimentStatusInfo[status]?.label}
</span>
{statusInfo ? (
<>
<img
style={{ width: '17px', marginRight: '7px' }}
src={statusInfo.icon}
draggable={false}
alt=""
/>
<span style={{ color: statusInfo.color }}>{statusInfo.label}</span>
</>
) : (
'--'
)}
</div>
</React.Fragment>
);
}

export default ExperimentInstance;
export default ExperimentInstanceComponent;

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

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

function ExperimentParameter({ nodeData }: ExperimentParameterProps) {
// 控制策略
const controlStrategyList = Object.entries(nodeData.control_strategy ?? {}).map(
([key, value]) => ({ key, value }),
);
// const controlStrategyList = Object.entries(nodeData.control_strategy ?? {}).map(
// ([key, value]) => ({ key, value }),
// );
const nodeId = nodeData.id;
const hasTaskInfo =
nodeId &&
!nodeId.startsWith('git-clone') &&
!nodeId.startsWith('dataset-export') &&
!nodeId.startsWith('model-export');

// 输入参数
const inParametersList = Object.entries(nodeData.in_parameters ?? {}).map(([key, value]) => ({
@@ -74,54 +80,58 @@ function ExperimentParameter({ nodeData }: ExperimentParameterProps) {
>
<FormInfo />
</Form.Item>
<div className={styles['experiment-parameter__title']}>
<SubAreaTitle
image={require('@/assets/img/duty-message.png')}
title="任务信息"
></SubAreaTitle>
</div>
<Form.Item
label="镜像"
name="image"
rules={[
{
required: true,
message: '请输入镜像',
},
]}
>
<FormInfo />
</Form.Item>
<Form.Item label="工作目录" name="working_directory">
<FormInfo />
</Form.Item>
{hasTaskInfo && (
<>
<div className={styles['experiment-parameter__title']}>
<SubAreaTitle
image={require('@/assets/img/duty-message.png')}
title="任务信息"
></SubAreaTitle>
</div>
<Form.Item
label="镜像"
name="image"
rules={[
{
required: true,
message: '请输入镜像',
},
]}
>
<FormInfo />
</Form.Item>
<Form.Item label="工作目录" name="working_directory">
<FormInfo />
</Form.Item>

<Form.Item label="启动命令" name="command">
<FormInfo textArea />
</Form.Item>
<Form.Item
label="资源规格"
name="resources_standard"
rules={[
{
required: true,
message: '请输入资源规格',
},
]}
>
<ParameterSelect dataType="resource" placeholder="请选择资源规格" display />
</Form.Item>
<Form.Item label="挂载路径" name="mount_path">
<FormInfo />
</Form.Item>
<Form.Item label="环境变量" name="env_variables">
<FormInfo textArea />
</Form.Item>
{controlStrategyList.map((item) => (
<Form.Item key={item.key} name={['control_strategy', item.key]} label={item.value.label}>
<FormInfo valuePropName="showValue" />
</Form.Item>
))}
<Form.Item label="启动命令" name="command">
<FormInfo textArea />
</Form.Item>
<Form.Item
label="资源规格"
name="resources_standard"
rules={[
{
required: true,
message: '请输入资源规格',
},
]}
>
<ParameterSelect dataType="resource" placeholder="请选择资源规格" display />
</Form.Item>
{/* <Form.Item label="挂载路径" name="mount_path">
<FormInfo />
</Form.Item> */}
<Form.Item label="环境变量" name="env_variables">
<FormInfo textArea />
</Form.Item>
{/* {controlStrategyList.map((item) => (
<Form.Item key={item.key} name={['control_strategy', item.key]} label={item.value.label}>
<FormInfo valuePropName="showValue" />
</Form.Item>
))} */}
</>
)}
<div className={styles['experiment-parameter__title']}>
<SubAreaTitle
image={require('@/assets/img/duty-message.png')}


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

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


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

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


+ 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']}
label={getParamLabel(globalParam[name])}
>
{getParamComponent(
globalParam[name]['param_type'],
globalParam[name]['is_sensitive'],
)}
{getParamComponent(globalParam[name]['param_type'])}
</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 { ExperimentStatus, TensorBoardStatus } from '@/enums';
import { useCacheState } from '@/hooks/useCacheState';
import { useServerTime } from '@/hooks/useServerTime';
import {
deleteExperimentById,
editExperimentInsReq,
getExperiment,
getExperimentById,
getQueryByExperimentId,
@@ -17,6 +19,7 @@ import { getWorkflow } from '@/services/pipeline/index.js';
import themes from '@/styles/theme.less';
import { ExperimentCompleted } from '@/utils/constant';
import { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage';
import tableCellRender, { TableCellValueType } from '@/utils/table';
import { modalConfirm } from '@/utils/ui';
import { App, Button, ConfigProvider, Dropdown, Input, Space, Table, Tooltip } from 'antd';
@@ -28,7 +31,6 @@ import AddExperimentModal from './components/AddExperimentModal';
import ExperimentInstanceList from './components/ExperimentInstanceList';
import styles from './index.less';
import { experimentStatusInfo } from './status';
import { useServerTime } from '@/hooks/useServerTime';

// 定时器
const timerIds = new Map();
@@ -39,7 +41,7 @@ function Experiment() {
const [workflowList, setWorkflowList] = useState([]);
const [experimentId, setExperimentId] = useState(null);
const [experimentInsList, setExperimentInsList] = useState([]);
const [expandedRowKeys, setExpandedRowKeys] = useState(null);
const [expandedRowKeys, setExpandedRowKeys] = useState([]);
const [total, setTotal] = useState(0);
const [isAdd, setIsAdd] = useState(true);
const [isModalOpen, setIsModalOpen] = useState(false);
@@ -59,29 +61,137 @@ function Experiment() {
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 = {
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(() => {
@@ -104,52 +214,66 @@ function Experiment() {
clearExperimentInTimers();
};
}, []);

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

// 新增,删除版本时,重置分页,然后刷新版本列表
// 更新实验实例状态
useEffect(() => {
const handleMessage = (e) => {
const { type, payload } = e.data;
if (type === ExperimentCompleted) {
const { id, status, finish_time } = payload;

// 修改实例的状态和结束时间
setExperimentInsList((prev) =>
prev.map((v) =>
v.id === id
? {
...v,
status: status,
finish_time: finish_time,
}
: v,
),
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);
return () => {
window.removeEventListener('message', handleMessage);
if (timerRef.current) {
clearTimeout(timerRef.current);
timerRef.current = undefined;
}
};
}, [refreshExperimentList]);
}, [experimentInsList, editExperimentIns]);

// 搜索
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
const runTensorBoard = async (experimentIn) => {
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();
setExperimentInsList([]);
if (record.id === expandedRowKeys) {
setExpandedRowKeys(null);
} else {
getQueryByExperiment(record.id, 0, 5);
if (expanded) {
setExpandedRowKeys([record.id]);
getExperimentInsList(record.id, 0, 5);
refreshExperimentList();
} else {
setExpandedRowKeys([]);
}
};

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

@@ -372,14 +426,16 @@ function Experiment() {
experimentIn.tensorBoardStatus === TensorBoardStatus.Running &&
experimentIn.tensorboardUrl
) {
window.open(experimentIn.tensorboardUrl, '_blank');
const url = experimentIn.tensorboardUrl;
SessionStorage.setItem(SessionStorage.tensorBoardUrlKey, url);
navigateToUrl(`/pipeline/experiment/visual`);
// window.open(experimentIn.tensorboardUrl, '_blank');
}
};

// 实验实例终止
const handleInstanceTerminate = async (experimentIn) => {
// 刷新实验列表
refreshExperimentList();
// 修改实例的状态和结束时间
setExperimentInsList((prevList) => {
return prevList.map((item) => {
if (item.id === experimentIn.id) {
@@ -392,6 +448,9 @@ function Experiment() {
return item;
});
});
// 刷新实验列表和实例列表
refreshExperimentList(true);
refreshExperimentIns(experimentIn.experiment_id);
};

// 实验对比菜单
@@ -413,16 +472,10 @@ function Experiment() {
};
};

// 刷新实验实例列表
const refreshExperimentIns = (experimentId) => {
const length = experimentInsList.length;
getQueryByExperiment(experimentId, 0, length);
};

// 加载更多实验实例
const loadMoreExperimentIns = () => {
const page = Math.round(experimentInsList.length / 5);
getQueryByExperiment(expandedRowKeys, page, 5);
getExperimentInsList(expandedRowKeys[0], page, 5);
};

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


+ 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: 赵伟
* @Date: 2024-04-16 13:58:08
* @Description: 自机器学习详情
* @Description: 自机器学习详情
*/
import PageTitle from '@/components/PageTitle';
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));
if (res && res.data) {
const info = res.data as HyperParameterInstanceData;
const { param, node_status, argo_ins_name, argo_ins_ns, status, create_time } = info;
const { param, node_status, argo_ins_name, argo_ins_ns, status } = info;
// 解析配置参数
const paramJson = parseJsonText(param).data;
if (paramJson) {
@@ -72,7 +72,6 @@ function HyperParameterInstance() {
}
setExperimentInfo({
...paramJson,
create_time,
});
}

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

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

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

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


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

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



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

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


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

function HyperParameterBasic({
info,
className,
runStatus,
workflowStatus,
instanceStatus,
isInstance = false,
}: HyperParameterBasicProps) {
@@ -83,7 +80,7 @@ function HyperParameterBasic({
}
return [
{
label: '代码',
label: '代码配置',
value: info.code_config,
format: formatCodeConfig,
},
@@ -145,56 +142,10 @@ function HyperParameterBasic({
];
}, [info, getResourceDescription]);

const instanceDatas = useMemo(() => {
if (!info || !runStatus) {
return [];
}

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

return (
<div className={classNames(styles['hyper-parameter-basic'], className)}>
{isInstance && runStatus && (
<ExperimentRunBasic
create_time={info?.create_time}
runStatus={runStatus}
instanceStatus={instanceStatus}
/>
{isInstance && workflowStatus && (
<ExperimentRunBasic workflowStatus={workflowStatus} instanceStatus={instanceStatus} />
)}
{!isInstance && (
<ConfigInfo


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

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

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

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


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

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

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

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


+ 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,
};
});
getMirrorInfo();
}
};

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


+ 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) {
const chartRef = useRef<HTMLDivElement>(null);
const xAxisData = chartData[0]?.iters;
const seriesData = chartData.map((item) => {
return {
name: item.version,
type: 'line' as const,
smooth: true,
data: item.values,
};
});
const seriesData = useMemo(
() =>
chartData.map((item) => {
return {
name: item.version,
type: 'line' as const,
smooth: true,
data: item.values,
};
}),
[chartData],
);

const options: echarts.EChartsOption = useMemo(
() => ({
@@ -158,7 +162,7 @@ function MetricsChart({ name, chartData }: MetricsChartProps) {

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


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

@@ -238,7 +238,7 @@ function CreateServiceVersion() {
},
{
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
* @Description: 服务版本详情
*/
import FullScreenFrame from '@/components/FullScreenFrame';
import IframePage from '@/components/IFramePage';
import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle';
import { ServiceRunStatus } from '@/enums';
import { getServiceVersionInfoReq } from '@/services/modelDeployment';
import { to } from '@/utils/promise';
import { useParams } from '@umijs/max';
import { Tabs } from 'antd';
import { useEffect, useState } from 'react';
import ServerLog from '../components/ServerLog';
import UserGuide from '../components/UserGuide';
import VersionBasicInfo from '../components/VersionBasicInfo';
import { ServiceVersionData } from '../types';
import styles from './index.less';
@@ -50,32 +50,35 @@ function ServiceVersionInfo() {
icon: <KFIcon type="icon-jibenxinxi" />,
children: <VersionBasicInfo info={versionInfo} />,
},
{
key: ModelDeploymentTabKey.Predict,
label: '预测',
icon: <KFIcon type="icon-yuce" />,
children: (
<div style={{ height: '100%', width: '100%' }}>
{versionInfo?.page_path && (
<FullScreenFrame url={versionInfo?.page_path}></FullScreenFrame>
)}
</div>
),
},
{
key: ModelDeploymentTabKey.Guide,
label: '调用指南',
icon: <KFIcon type="icon-tiaoyongzhinan" />,
children: <UserGuide info={versionInfo}></UserGuide>,
},
{
key: ModelDeploymentTabKey.Log,
label: '服务日志',
icon: <KFIcon type="icon-fuwurizhi" />,
children: <ServerLog info={versionInfo}></ServerLog>,
},
];

if (versionInfo?.run_state === ServiceRunStatus.Running) {
if (versionInfo?.page_path) {
tabItems.push({
key: ModelDeploymentTabKey.Predict,
label: '预测',
icon: <KFIcon type="icon-yuce" />,
children: <IframePage url={versionInfo?.page_path} showLoading={false}></IframePage>,
});
}

if (versionInfo?.doc_path) {
tabItems.push({
key: ModelDeploymentTabKey.Guide,
label: '调用指南',
icon: <KFIcon type="icon-tiaoyongzhinan" />,
children: <IframePage url={versionInfo?.doc_path}></IframePage>,
});
}
}

tabItems.push({
key: ModelDeploymentTabKey.Log,
label: '服务日志',
icon: <KFIcon type="icon-fuwurizhi" />,
children: <ServerLog info={versionInfo}></ServerLog>,
});

return (
<div className={styles['service-version-info']}>
<PageTitle title="服务版本详情"></PageTitle>


+ 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;
display: flex;
flex-direction: column;
align-items: center;
align-items: left;

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

&__more {
padding: 20px 0;
}

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

+ 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 { DoubleRightOutlined } from '@ant-design/icons';
import { Button, DatePicker, type TimeRangePickerProps } from 'antd';
import classNames from 'classnames';
import dayjs from 'dayjs';
import { useEffect, useState } from 'react';
import styles from './index.less';
@@ -106,6 +107,8 @@ function ServerLog({ info }: ServerLogProps) {
}
};

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

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


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

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


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

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


+ 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 { INode } from '@antv/g6';
import { Button, Drawer, Form, Input, MenuProps } from 'antd';
import { RuleObject } from 'antd/es/form';
import { NamePath } from 'antd/es/form/interface';
import { forwardRef, useImperativeHandle, useState } from 'react';
import PropsLabel from '../PropsLabel';
@@ -34,6 +35,12 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
const [stagingItem, setStagingItem] = useState<PipelineNodeModelSerialize>(
{} as PipelineNodeModelSerialize,
);
const nodeId = Form.useWatch('id', form) as string;
const hasTaskInfo =
nodeId &&
!nodeId.startsWith('git-clone') &&
!nodeId.startsWith('dataset-export') &&
!nodeId.startsWith('model-export');
const [open, setOpen] = useState(false);
const [menuItems, setMenuItems] = useState<MenuProps['items']>([]);

@@ -45,10 +52,11 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
const fields = form.getFieldsValue();

// 保存字段顺序
const control_strategy = {
...stagingItem.control_strategy,
...fields.control_strategy,
};
// const control_strategy = {
// ...stagingItem.control_strategy,
// ...fields.control_strategy,
// };

const in_parameters = {
...stagingItem.in_parameters,
...fields.in_parameters,
@@ -63,7 +71,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
const res = {
...stagingItem,
...fields,
control_strategy: JSON.stringify(control_strategy),
// control_strategy: JSON.stringify(control_strategy),
in_parameters: JSON.stringify(in_parameters),
out_parameters: JSON.stringify(out_parameters),
formError: !!error,
@@ -90,7 +98,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
...model,
in_parameters: JSON.parse(model.in_parameters),
out_parameters: JSON.parse(model.out_parameters),
control_strategy: JSON.parse(model.control_strategy),
// control_strategy: JSON.parse(model.control_strategy),
};
// console.log('model', nodeData);
setStagingItem({
@@ -309,22 +317,47 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
);
};

// 模型部署-服务版本验证
const serviceVersionValidator = (_rule: RuleObject, value: any) => {
// 输入参数值都是对象,如果是手动输入,要求 /^[a-zA-Z0-9._-]+$/
if (
typeof value === 'object' &&
value &&
!value.fromSelect &&
value.value &&
!/^[a-zA-Z0-9._-]+$/.test(value.value)
) {
return Promise.reject('服务版本只支持字母、数字、点(.)、下划线(_)、中横线(-)');
}

return Promise.resolve();
};

// 必填项校验规则
const getFormRules = (item: { key: string; value: PipelineNodeModelParameter }) => {
return item.value.require
const id = form.getFieldValue('id') as string;
const rules = item.value.require
? [
{
validator: requiredValidator,
message: '必填项',
},
]
: [];

// 模型部署-服务版本验证
if (id.startsWith('model-deploy') && item.key === '--version') {
rules.push({
validator: serviceVersionValidator,
});
}

return rules;
};

// 控制策略
const controlStrategyList = Object.entries(stagingItem.control_strategy ?? {}).map(
([key, value]) => ({ key, value }),
);
// const controlStrategyList = Object.entries(stagingItem.control_strategy ?? {}).map(
// ([key, value]) => ({ key, value }),
// );

// 输入参数
const inParametersList = Object.entries(stagingItem.in_parameters ?? {}).map(([key, value]) => ({
@@ -396,71 +429,73 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
>
<Input disabled />
</Form.Item>
<div className={styles['pipeline-drawer__title']}>
<SubAreaTitle
image={require('@/assets/img/duty-message.png')}
title="任务信息"
></SubAreaTitle>
</div>
<Form.Item label="镜像" required>
<div className={styles['pipeline-drawer__ref-row']}>
<Form.Item name="image" noStyle rules={[{ required: true, message: '请输入镜像' }]}>
<Input placeholder="请输入或选择镜像" allowClear />
{hasTaskInfo && (
<>
<div className={styles['pipeline-drawer__title']}>
<SubAreaTitle
image={require('@/assets/img/duty-message.png')}
title="任务信息"
></SubAreaTitle>
</div>
<Form.Item label="镜像" required>
<div className={styles['pipeline-drawer__ref-row']}>
<Form.Item name="image" noStyle rules={[{ required: true, message: '请输入镜像' }]}>
<Input placeholder="请输入或选择镜像" allowClear />
</Form.Item>
<Form.Item noStyle>
<Button
type="link"
size="small"
icon={getSelectBtnIcon({ item_type: 'image' })}
onClick={() => selectResource('image', { item_type: 'image' })}
className={styles['pipeline-drawer__ref-row__select-button']}
>
选择镜像
</Button>
</Form.Item>
</div>
</Form.Item>
<Form.Item noStyle>
<Button
type="link"
size="small"
icon={getSelectBtnIcon({ item_type: 'image' })}
onClick={() => selectResource('image', { item_type: 'image' })}
className={styles['pipeline-drawer__ref-row__select-button']}
>
选择镜像
</Button>
<Form.Item
name="working_directory"
label={
<PropsLabel
menuItems={menuItems}
title="工作目录"
onClick={(value) => {
handleParameterClick('working_directory', value);
}}
/>
}
>
<Input placeholder="请输入工作目录" allowClear />
</Form.Item>
</div>
</Form.Item>
<Form.Item
name="working_directory"
label={
<PropsLabel
menuItems={menuItems}
title="工作目录"
onClick={(value) => {
handleParameterClick('working_directory', value);
}}
/>
}
>
<Input placeholder="请输入工作目录" allowClear />
</Form.Item>
<Form.Item
name="command"
label={
<PropsLabel
menuItems={menuItems}
title="启动命令"
onClick={(value) => {
handleParameterClick('command', value);
}}
/>
}
>
<TextArea placeholder="请输入启动命令" allowClear />
</Form.Item>
<Form.Item
label="资源规格"
name="resources_standard"
rules={[
{
required: true,
message: '请选择资源规格',
},
]}
>
<ParameterSelect dataType="resource" placeholder="请选择资源规格" />
</Form.Item>
<Form.Item
<Form.Item
name="command"
label={
<PropsLabel
menuItems={menuItems}
title="启动命令"
onClick={(value) => {
handleParameterClick('command', value);
}}
/>
}
>
<TextArea placeholder="请输入启动命令" allowClear />
</Form.Item>
<Form.Item
label="资源规格"
name="resources_standard"
rules={[
{
required: true,
message: '请选择资源规格',
},
]}
>
<ParameterSelect dataType="resource" placeholder="请选择资源规格" />
</Form.Item>
{/* <Form.Item
name="mount_path"
label={
<PropsLabel
@@ -473,23 +508,23 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
}
>
<Input placeholder="请输入挂载路径" allowClear />
</Form.Item>
<Form.Item
name="env_variables"
label={
<PropsLabel
menuItems={menuItems}
title="环境变量"
onClick={(value) => {
handleParameterClick('env_variables', value);
}}
/>
}
>
<TextArea placeholder="请输入环境变量" allowClear />
</Form.Item>
{/* 控制参数 */}
{controlStrategyList.map((item) => (
</Form.Item> */}
<Form.Item
name="env_variables"
label={
<PropsLabel
menuItems={menuItems}
title="环境变量"
onClick={(value) => {
handleParameterClick('env_variables', value);
}}
/>
}
>
<TextArea placeholder="请输入环境变量" allowClear />
</Form.Item>
{/* 控制参数 */}
{/* {controlStrategyList.map((item) => (
<Form.Item
key={item.key}
name={['control_strategy', item.key]}
@@ -499,7 +534,10 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
>
<ParameterInput placeholder={item.value.placeholder} allowClear></ParameterInput>
</Form.Item>
))}
))} */}
</>
)}

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


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

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


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

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


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


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

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

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


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

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


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

@@ -37,7 +37,18 @@
&__summary {
display: flex;
flex-direction: column;
width: 33.33%;
width: 40%;
text-align: left;

&:nth-child(3n+2) {
text-align: center;
width: 30%;
}

&:nth-child(3n) {
text-align: right;
width: 30%;
}

&__title {
margin-bottom: 12px;


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


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

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

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

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

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

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

:global {
.ant-btn-link {


+ 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 { elapsedTime, formatDate } from '@/utils/date';
import { useNavigate } from '@umijs/max';
import { Button, Empty } from 'antd';
import { Empty } from 'antd';
import styles from './index.less';
import ExperimentInstanceComponent from './instance';

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


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


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

@@ -3,16 +3,16 @@
align-items: center;
justify-content: center;
height: 140px;
padding: 0 16px;
padding: 0 35px;

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

&__icon {
width: 63px;
margin-right: 16px;
width: 80px;
margin-right: 20px;
}

&__title {


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

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

&__label {
margin-top: 60px;


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

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


+ 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';

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


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

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

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

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


+ 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 { ExperimentInstance } from '@/types';
import { to } from '@/utils/promise';
import { Divider, Flex } from 'antd';
import { useEffect, useState } from 'react';
import Draggable from 'react-draggable';
// import Draggable from 'react-draggable';
import AssetsManagement from './components/AssetsManagement';
import ExperimentChart, { type ExperimentStatistics } from './components/ExperimentChart';
import ExperitableTable from './components/ExperimentTable';
import QuickStart from './components/QuickStart';
import RobotFrame from './components/RobotFrame';
// import RobotFrame from './components/RobotFrame';
import TotalStatistics from './components/TotalStatistics';
import UserPoints from './components/UserPoints';
import UserSpace from './components/UserSpace';
import WorkspaceIntro from './components/WorkspaceIntro';
import styles from './index.less';

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

function Workspace() {
const [overviewData, setOverviewData] = useState<OverviewData>();
const [robotFrameVisible, setRobotFrameVisible] = useState(false);
const { handleStart, handleStop, handleDrag, handleClick } = useDraggable(() =>
setRobotFrameVisible((prev) => !prev),
);
// const [robotFrameVisible, setRobotFrameVisible] = useState(false);
// const { handleStart, handleStop, handleDrag, handleClick } = useDraggable(() =>
// setRobotFrameVisible((prev) => !prev),
// );
const users: number[] = new Array(8).fill(0);

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

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


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

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

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

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

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

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

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


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

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

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

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

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

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

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

+ 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) {
return request(`/api/mmp/newdataset/downloadAllFiles`, {
@@ -132,6 +140,15 @@ export function addModelVersion(data) {
});
}

// 编辑模型版本
export function editModelVersion(data) {
return request(`/api/mmp/newmodel/updateVersionDesc`, {
method: 'PUT',
data,
});
}


// 删除模型版本
export function deleteModelVersion(params) {
return request(`/api/mmp/newmodel/deleteVersion`, {


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

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

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

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


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

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

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

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

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

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


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

+ 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 = {
roleId,
roleKey,
status,
};
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: {
[key: string]: any;
};
node_status: string;
nodes_status: string;
global_param: PipelineGlobalParam[];
tensorBoardStatus?: TensorBoardStatus;
@@ -111,9 +112,9 @@ export type KeysToCamelCase<T> = {
// 序列化后的流水线节点
export type PipelineNodeModelSerialize = Omit<
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>;
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';

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

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

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()) {
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 G6 from '@antv/g6';
import { number } from 'echarts';

/**
* 生成 8 位随机数
@@ -268,19 +267,25 @@ export const hasNoValue = (value?: any | null): boolean => {
/**
* 获取 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
*
* 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'.
*/
export const getGitUrl = (url: string, branch: string): string => {
export const getGitUrl = (url?: string, branch?: string): string => {
if (!url) {
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 => {
return value === '' ? undefined : value;
};


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

Loading…
Cancel
Save