diff --git a/.gitignore b/.gitignore index 54c883c5..41d080fe 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,4 @@ mvnw /react-ui/types/tsconfig.tsbuildinfo /react-ui/storybook-static /react-ui/.storybook/scripts +/react-ui/dist.zip diff --git a/react-ui/config/proxy.ts b/react-ui/config/proxy.ts index 20769b3c..a742933c 100644 --- a/react-ui/config/proxy.ts +++ b/react-ui/config/proxy.ts @@ -23,7 +23,11 @@ export default { target: 'http://172.20.32.197:31213', // 开发环境 // target: 'http://172.20.32.235:31213', // 测试环境 // target: 'http://172.20.32.44:8082', +<<<<<<< HEAD // target: 'http://172.20.32.164:8082', +======= + target: 'http://172.20.32.164:8082', +>>>>>>> dev-zw // 配置了这个可以从 http 代理到 https // 依赖 origin 的功能可能需要这个,比如 cookie changeOrigin: true, diff --git a/react-ui/mock/components.ts b/react-ui/mock/components.ts new file mode 100644 index 00000000..ab5b5f1f --- /dev/null +++ b/react-ui/mock/components.ts @@ -0,0 +1,1294 @@ +import { defineMock } from 'umi'; + +export default defineMock({ + 'GET /api/mmp/workflow/235': { + code: 200, + msg: '操作成功', + data: { + id: 233, + name: '分布式训练', + description: 'aa', + dag: { + nodes: [ + { + id: 'git-clone-c0724278', + category_id: 1, + component_name: 'git-clone', + component_label: '代码拉取组件', + task_info: { + image: { + type: 'ref', + item_type: 'image', + label: '镜像', + value: null, + visible: false, + editable: false, + require: 1, + default: '', + condition: '', + description: '克隆代码的镜像', + placeholder: '请选择镜像', + rulers: {}, + }, + working_directory: { + type: 'str', + item_type: '', + label: '工作目录', + value: '', + visible: false, + editable: false, + require: 1, + default: '', + placeholder: '请输入工作目录', + condition: '', + description: '容器内的工作目录', + rulers: {}, + }, + command: { + type: 'str', + item_type: '', + label: '启动命令', + value: '', + visible: false, + editable: false, + require: 1, + default: '', + placeholder: '请输入启动命令', + description: '启动命令,不包括运行参数', + rulers: '', + }, + run_args: { + type: 'map', + item_type: '', + label: '运行参数', + value: [], + visible: false, + editable: false, + require: 0, + default: '', + placeholder: '', + condition: '', + description: '运行命令的参数', + rulers: '', + }, + resources_standard: { + type: 'select', + item_type: 'resource', + label: '资源', + value: {}, + visible: false, + editable: false, + require: 1, + default: '', + placeholder: '', + condition: '', + description: '资源规格', + rulers: {}, + }, + }, + + control_strategy: { + retry_times: { + type: 'str', + item_type: '', + label: '重试次数', + require: 0, + default: '', + placeholder: '', + describe: '任务重试次数', + visible: true, + editable: false, + condition: '', + value: '', + rulers: {}, + }, + max_run_times: { + type: 'str', + item_type: '', + label: '最大运行时间', + require: 0, + default: '', + placeholder: '', + describe: '最大运行时间', + editable: false, + visible: true, + condition: '', + value: '', + rulers: {}, + }, + }, + + in_parameters: { + '--code_config': { + type: 'ref', + item_type: 'code', + label: '代码配置', + require: 1, + default: '', + placeholder: '私有仓库填写ssh地址,公有仓库填写https git地址', + describe: + '代码配置,支持私有仓库和公有仓库,私有仓库填写ssh地址,公有仓库填写https git地址', + editable: false, + visible: true, + condition: '', + value: { + id: 21, + code_repo_name: '原子掺杂识别', + code_repo_vis: 1, + is_public: true, + git_url: 'https://gitlink.org.cn/somunslotus/material-atom-predict.git', + git_branch: 'master', + verify_mode: null, + git_user_name: null, + git_password: null, + ssh_key: null, + create_by: 'admin', + create_time: '2025-03-12T16:46:08.000+08:00', + update_by: 'admin', + update_time: '2025-03-14T14:59:19.000+08:00', + state: 1, + }, + rulers: {}, + showValue: '原子掺杂识别', + fromSelect: true, + }, + }, + + out_parameters: { + '--code_output': { + type: 'str', + item_type: 'code', + label: '代码保存路径', + require: 1, + default: '/code', + editable: false, + visible: true, + placeholder: '代码保存路径', + describe: '代码保存路径', + condition: '', + showValue: '/mycode', + value: '/mycode', + rulers: {}, + fromSelect: false, + }, + }, + + available_range: 0, + description: '代码拉取组件', + icon_path: 'component-icon-1', + create_by: 'admin', + create_time: '2024-09-02T06:08:06.000+08:00', + update_by: 'admin', + update_time: '2024-09-02T06:08:06.000+08:00', + state: 1, + env_variables: [], + x: 612, + y: 215, + label: '代码拉取', + img: '/assets/images/component-icon-1.png', + isCluster: false, + formError: false, + type: 'rect-node', + size: [110, 36], + labelCfg: { + style: { + fill: 'transparent', + fontSize: 0, + boxShadow: '0px 0px 12px rgba(75, 84, 137, 0.05)', + overflow: 'hidden', + x: -20, + y: 0, + textAlign: 'left', + textBaseline: 'middle', + }, + }, + style: { + active: { + fill: 'rgb(247, 250, 255)', + stroke: 'rgb(95, 149, 255)', + lineWidth: 2, + shadowColor: 'rgb(95, 149, 255)', + shadowBlur: 10, + }, + selected: { + fill: 'rgb(255, 255, 255)', + stroke: 'rgb(95, 149, 255)', + lineWidth: 4, + shadowColor: 'rgb(95, 149, 255)', + shadowBlur: 10, + 'text-shape': { + fontWeight: 500, + }, + }, + highlight: { + fill: 'rgb(223, 234, 255)', + stroke: '#4572d9', + lineWidth: 2, + 'text-shape': { + fontWeight: 500, + }, + }, + inactive: { + fill: 'rgb(247, 250, 255)', + stroke: 'rgb(191, 213, 255)', + lineWidth: 1, + }, + disable: { + fill: 'rgb(250, 250, 250)', + stroke: 'rgb(224, 224, 224)', + lineWidth: 1, + }, + fill: '#fff', + stroke: 'transparent', + cursor: 'pointer', + radius: 8, + shadowColor: 'rgba(75, 84, 137, 0.4)', + shadowBlur: 6, + shadowOffsetX: 0, + shadowOffsetY: 0, + overflow: 'hidden', + lineWidth: 0.5, + }, + depth: 0, + }, + { + id: 'model-train-39e9bc7c', + category_id: 2, + component_name: 'model-train', + component_label: '模型训练', + task_info: { + image: { + type: 'ref', + item_type: 'image', + label: '镜像', + value: {}, + visible: true, + editable: true, + require: 1, + default: '', + condition: '', + description: '镜像', + placeholder: '', + rulers: {}, + }, + working_directory: { + type: 'str', + item_type: '', + label: '工作目录', + value: '{{git-clone-c0724278.--code_output}}', + visible: true, + editable: true, + require: 1, + default: '', + placeholder: '', + condition: '', + description: '容器内的工作目录', + rulers: {}, + }, + command: { + type: 'str', + item_type: '', + label: '启动命令', + value: 'conda run -n atom-predict python recognize_dophant/egnn/train_pl_vor.py', + visible: true, + editable: true, + require: 1, + default: '', + placeholder: '', + description: '启动命令,不包括运行参数', + rulers: '', + }, + run_args: { + type: 'map', + item_type: '', + label: '运行参数', + value: [], + visible: true, + editable: true, + require: 0, + default: '', + placeholder: '', + condition: '', + description: '运行命令的参数', + rulers: '', + }, + resources_standard: { + type: 'select', + item_type: 'resource', + label: '资源', + value: { + id: 30, + resource_id: 4, + computing_resource: 'CPU', + standard: { + name: 'CPU', + value: { + gpu: 0, + cpu: 4, + memory: '8GB', + }, + }, + description: 'GPU: 0, CPU:4, 内存: 8GB', + cpu_cores: 4, + memory_gb: 8, + gpu_memory_gb: 0, + gpu_nums: 0, + credit_per_hour: 2.0, + labels: 'accelertor=cpu', + create_by: 'admin', + create_time: '2024-04-19T11:39:40.000+08:00', + update_by: 'admin', + update_time: '2024-04-19T11:39:40.000+08:00', + state: 1, + }, + visible: true, + editable: true, + require: 1, + default: '', + placeholder: '', + condition: '', + description: '资源规格', + rulers: {}, + }, + }, + + control_strategy: { + retry_times: { + type: 'str', + item_type: '', + label: '重试次数', + require: 0, + default: '', + placeholder: '', + describe: '任务重试次数', + visible: true, + editable: true, + condition: '', + value: '', + rulers: {}, + }, + max_run_times: { + type: 'str', + item_type: '', + label: '最大运行时间', + require: 0, + default: '', + placeholder: '', + describe: '最大运行时间', + editable: true, + visible: true, + condition: '', + value: '', + rulers: {}, + }, + }, + in_parameters: { + '--dataset': { + type: 'ref', + item_type: 'dataset', + label: '选择数据集', + require: 1, + default: '', + placeholder: '', + describe: '选择数据集', + condition: '', + visible: true, + editable: true, + value: { + id: '73', + name: '原子掺杂识别场景测试', + version: 'v1', + path: 'fanshuai/datasets/73/fanshuai_dataset_20250519103524/v1/dataset', + identifier: 'fanshuai_dataset_20250519103524', + owner: 'fanshuai', + }, + rulers: {}, + showValue: '原子掺杂识别场景测试:v1', + fromSelect: true, + activeTab: 'Private', + expandedKeys: ['73'], + checkedKeys: ['73-v1'], + }, + '--model_name': { + type: 'ref', + item_type: 'model', + label: '选择模型', + require: 0, + default: '', + placeholder: '', + describe: '最大运行时间', + editable: true, + visible: true, + condition: '', + value: { + id: '39', + name: '原子参杂识别模型', + version: 'v1', + path: 'fanshuai/model/39/fanshuai_model_20250513113514/v1/model', + identifier: 'fanshuai_model_20250513113514', + owner: 'fanshuai', + }, + rulers: {}, + showValue: '原子参杂识别模型:v1', + fromSelect: true, + activeTab: 'Private', + expandedKeys: ['39'], + checkedKeys: ['39-v1'], + }, + }, + out_parameters: { + '--model_output': { + type: 'str', + item_type: '', + label: '模型输出路径', + require: 1, + showValue: '/model', + default: '', + placeholder: '', + describe: '模型输出路径', + editable: true, + visible: true, + rulers: {}, + condition: '', + value: '/model', + fromSelect: false, + }, + }, + available_range: 1, + description: '通用模型训练组件介绍', + icon_path: 'component-icon-2', + create_by: 'admin', + create_time: '2024-05-28T07:33:53.000+08:00', + update_by: 'admin', + update_time: '2024-05-28T07:33:53.000+08:00', + state: 1, + env_variables: [], + x: 596, + y: 348, + label: '模型训练', + img: '/assets/images/component-icon-2.png', + isCluster: false, + formError: false, + type: 'rect-node', + size: [110, 36], + labelCfg: { + style: { + fill: 'transparent', + fontSize: 0, + boxShadow: '0px 0px 12px rgba(75, 84, 137, 0.05)', + overflow: 'hidden', + x: -20, + y: 0, + textAlign: 'left', + textBaseline: 'middle', + }, + }, + style: { + active: { + fill: 'rgb(247, 250, 255)', + stroke: 'rgb(95, 149, 255)', + lineWidth: 2, + shadowColor: 'rgb(95, 149, 255)', + shadowBlur: 10, + }, + selected: { + fill: 'rgb(255, 255, 255)', + stroke: 'rgb(95, 149, 255)', + lineWidth: 4, + shadowColor: 'rgb(95, 149, 255)', + shadowBlur: 10, + 'text-shape': { + fontWeight: 500, + }, + }, + highlight: { + fill: 'rgb(223, 234, 255)', + stroke: '#4572d9', + lineWidth: 2, + 'text-shape': { + fontWeight: 500, + }, + }, + inactive: { + fill: 'rgb(247, 250, 255)', + stroke: 'rgb(191, 213, 255)', + lineWidth: 1, + }, + disable: { + fill: 'rgb(250, 250, 250)', + stroke: 'rgb(224, 224, 224)', + lineWidth: 1, + }, + fill: '#fff', + stroke: 'transparent', + cursor: 'pointer', + radius: 8, + shadowColor: 'rgba(75, 84, 137, 0.4)', + shadowBlur: 6, + shadowOffsetX: 0, + shadowOffsetY: 0, + overflow: 'hidden', + lineWidth: 0.5, + }, + depth: 0, + }, + { + id: 'model-evaluate-c5b68e7c', + category_id: 4, + component_name: 'model-evaluate', + component_label: '模型测试', + task_info: { + image: { + type: 'ref', + item_type: 'image', + label: '镜像', + value: { + id: 15, + image_id: 17, + version: 'v1', + description: null, + url: '172.20.32.187/machine-learning/atom-egnn:v2', + tag_name: 'v1', + file_size: '125MB', + status: 'Available', + create_by: 'fanshuai', + create_time: '2024-04-18T00:00:00.000+08:00', + update_by: 'admin', + update_time: '2024-04-18T00:00:00.000+08:00', + state: 1, + host_ip: null, + }, + visible: false, + editable: false, + require: 0, + default: '', + condition: '', + description: '镜像', + placeholder: '', + rulers: {}, + }, + working_directory: { + type: 'str', + item_type: '', + label: '工作目录', + value: '{{git-clone-c0724278.--code_output}}', + visible: false, + editable: false, + require: 0, + default: '', + placeholder: '', + condition: '', + description: '容器内的工作目录', + rulers: {}, + }, + command: { + type: 'str', + item_type: '', + label: '启动命令', + value: 'conda run -n atom-predict python recognize_dophant/egnn/test_pl_vor.py', + visible: false, + editable: false, + require: 0, + default: '', + placeholder: '', + description: '启动命令,不包括运行参数', + rulers: '', + }, + run_args: { + type: 'map', + item_type: '', + label: '运行参数', + value: [], + visible: false, + editable: false, + require: 0, + default: '', + placeholder: '', + condition: '', + description: '运行命令的参数', + rulers: '', + }, + resources_standard: { + type: 'select', + item_type: 'resource', + label: '资源', + value: { + id: 30, + resource_id: 4, + computing_resource: 'CPU', + standard: { + name: 'CPU', + value: { + gpu: 0, + cpu: 4, + memory: '8GB', + }, + }, + description: 'GPU: 0, CPU:4, 内存: 8GB', + cpu_cores: 4, + memory_gb: 8, + gpu_memory_gb: 0, + gpu_nums: 0, + credit_per_hour: 2.0, + labels: 'accelertor=cpu', + create_by: 'admin', + create_time: '2024-04-19T11:39:40.000+08:00', + update_by: 'admin', + update_time: '2024-04-19T11:39:40.000+08:00', + state: 1, + }, + visible: true, + editable: true, + require: 1, + default: '', + placeholder: '', + condition: '', + description: '资源规格', + rulers: {}, + }, + }, + + control_strategy: { + retry_times: { + type: 'str', + item_type: '', + label: '重试次数', + require: 0, + default: '', + placeholder: '', + describe: '任务重试次数', + visible: true, + editable: true, + condition: '', + value: '', + rulers: {}, + }, + max_run_times: { + type: 'str', + item_type: '', + label: '最大运行时间', + require: 0, + default: '', + placeholder: '', + describe: '最大运行时间', + editable: true, + visible: true, + condition: '', + value: '', + rulers: {}, + }, + }, + + in_parameters: { + '--dataset': { + type: 'ref', + item_type: 'dataset', + label: '选择数据集', + require: 1, + default: '', + placeholder: '', + describe: '选择数据集', + condition: '', + editable: true, + visible: true, + value: { + id: '74', + name: '原子掺杂识别模型测试数据集', + version: 'v2', + path: 'fanshuai/datasets/74/fanshuai_dataset_20250519103749/v2/dataset', + identifier: 'fanshuai_dataset_20250519103749', + owner: 'fanshuai', + }, + rulers: {}, + showValue: '原子掺杂识别模型测试数据集:v2', + fromSelect: true, + activeTab: 'Private', + expandedKeys: ['74'], + checkedKeys: ['74-v2'], + }, + '--model_name': { + type: 'ref', + item_type: 'model', + label: '选择模型', + require: 1, + editable: true, + visible: true, + rulers: {}, + default: '', + placeholder: '', + describe: '这里是这个参数的描述和备注', + condition: '', + value: '{{model-train-39e9bc7c.--model_output}}', + showValue: '{{model-train-39e9bc7c.--model_output}}', + fromSelect: true, + }, + }, + out_parameters: { + '--model_output': { + type: 'str', + item_type: '', + label: '模型测试结果路径', + editable: true, + visible: true, + rulers: {}, + default: '', + placeholder: '', + describe: '这里是这个参数的描述和备注', + condition: '', + require: 1, + showValue: '/result', + value: '/result', + fromSelect: false, + }, + }, + available_range: 1, + description: '模型测试', + icon_path: 'component-icon-4', + create_by: 'admin', + create_time: '2024-05-24T07:00:10.000+08:00', + update_by: 'admin', + update_time: '2024-05-24T07:00:10.000+08:00', + state: 1, + env_variables: [], + x: 600, + y: 460, + label: '模型评估', + img: '/assets/images/component-icon-4.png', + isCluster: false, + formError: false, + type: 'rect-node', + size: [110, 36], + labelCfg: { + style: { + fill: 'transparent', + fontSize: 0, + boxShadow: '0px 0px 12px rgba(75, 84, 137, 0.05)', + overflow: 'hidden', + x: -20, + y: 0, + textAlign: 'left', + textBaseline: 'middle', + }, + }, + style: { + active: { + fill: 'rgb(247, 250, 255)', + stroke: 'rgb(95, 149, 255)', + lineWidth: 2, + shadowColor: 'rgb(95, 149, 255)', + shadowBlur: 10, + }, + selected: { + fill: 'rgb(255, 255, 255)', + stroke: 'rgb(95, 149, 255)', + lineWidth: 4, + shadowColor: 'rgb(95, 149, 255)', + shadowBlur: 10, + 'text-shape': { + fontWeight: 500, + }, + }, + highlight: { + fill: 'rgb(223, 234, 255)', + stroke: '#4572d9', + lineWidth: 2, + 'text-shape': { + fontWeight: 500, + }, + }, + inactive: { + fill: 'rgb(247, 250, 255)', + stroke: 'rgb(191, 213, 255)', + lineWidth: 1, + }, + disable: { + fill: 'rgb(250, 250, 250)', + stroke: 'rgb(224, 224, 224)', + lineWidth: 1, + }, + fill: '#fff', + stroke: 'transparent', + cursor: 'pointer', + radius: 8, + shadowColor: 'rgba(75, 84, 137, 0.4)', + shadowBlur: 6, + shadowOffsetX: 0, + shadowOffsetY: 0, + overflow: 'hidden', + lineWidth: 0.5, + }, + depth: 0, + }, + { + id: 'model-export-edcf438e', + category_id: 6, + component_name: 'model-export', + component_label: '模型导出', + task_info: { + image: { + type: 'ref', + item_type: 'image', + label: '镜像', + value: {}, + visible: true, + editable: true, + require: 1, + default: '', + condition: '', + description: '镜像', + placeholder: '', + rulers: {}, + }, + working_directory: { + type: 'str', + item_type: '', + label: '工作目录', + value: '{{git-clone-c0724278.--code_output}}', + visible: true, + editable: true, + require: 1, + default: '', + placeholder: '', + condition: '', + description: '容器内的工作目录', + rulers: {}, + }, + command: { + type: 'str', + item_type: '', + label: '启动命令', + value: 'conda run -n atom-predict python recognize_dophant/egnn/test_pl_vor.py', + visible: true, + editable: true, + require: 1, + default: '', + placeholder: '', + description: '启动命令,不包括运行参数', + rulers: '', + }, + run_args: { + type: 'map', + item_type: '', + label: '运行参数', + value: [], + visible: true, + editable: true, + require: 0, + default: '', + placeholder: '', + condition: '', + description: '运行命令的参数', + rulers: '', + }, + resources_standard: { + type: 'select', + item_type: 'resource', + label: '资源', + value: {}, + visible: false, + editable: false, + require: 1, + default: '', + placeholder: '', + condition: '', + description: '资源规格', + rulers: {}, + }, + }, + control_strategy: { + retry_times: { + type: 'str', + item_type: '', + label: '重试次数', + require: 0, + default: '', + placeholder: '', + describe: '任务重试次数', + visible: true, + editable: true, + condition: '', + value: '', + rulers: {}, + }, + max_run_times: { + type: 'str', + item_type: '', + label: '最大运行时间', + require: 0, + default: '', + placeholder: '', + describe: '最大运行时间', + editable: true, + visible: true, + condition: '', + value: '', + rulers: {}, + }, + }, + + in_parameters: { + '--model_source': { + type: 'str', + item_type: '', + label: '模型来源', + require: 1, + default: '', + placeholder: '模型来源', + describe: '模型来源', + editable: true, + visible: true, + condition: '', + value: '{{model-train-39e9bc7c.--model_output}}', + rulers: {}, + fromSelect: true, + showValue: '{{model-train-39e9bc7c.--model_output}}', + }, + '--model_id': { + type: 'select', + item_type: 'model', + label: '导出到模型', + require: 1, + default: '', + placeholder: '', + describe: '导出到模型', + editable: true, + visible: true, + condition: '', + value: { + id: '76', + name: '原子掺杂识别模型场景测试', + identifier: 'fanshuai_model_20250519105223', + owner: 'fanshuai', + }, + rulers: {}, + }, + '--version': { + type: 'str', + item_type: '', + label: '模型版本', + require: 1, + choice: [], + default: '1', + placeholder: '', + describe: '模型版本', + editable: false, + condition: '', + showValue: '${model_version}', + value: '${model_version}', + fromSelect: false, + }, + '--description': { + type: 'str', + item_type: '', + label: '版本描述', + require: 1, + choice: [], + default: '', + placeholder: '版本描述', + describe: '版本描述', + editable: false, + condition: '', + showValue: '流水线自动导出', + value: '流水线自动导出', + fromSelect: false, + }, + }, + available_range: 0, + description: '模型导出', + icon_path: 'component-icon-8', + create_by: 'admin', + create_time: '2024-05-29T01:12:01.000+08:00', + update_by: 'admin', + update_time: '2024-05-29T09:11:55.000+08:00', + state: 1, + env_variables: [], + x: 592, + y: 581, + label: '模型导出', + img: '/assets/images/component-icon-8.png', + isCluster: false, + formError: false, + type: 'rect-node', + size: [110, 36], + labelCfg: { + style: { + fill: 'transparent', + fontSize: 0, + boxShadow: '0px 0px 12px rgba(75, 84, 137, 0.05)', + overflow: 'hidden', + x: -20, + y: 0, + textAlign: 'left', + textBaseline: 'middle', + }, + }, + style: { + active: { + fill: 'rgb(247, 250, 255)', + stroke: 'rgb(95, 149, 255)', + lineWidth: 2, + shadowColor: 'rgb(95, 149, 255)', + shadowBlur: 10, + }, + selected: { + fill: 'rgb(255, 255, 255)', + stroke: 'rgb(95, 149, 255)', + lineWidth: 4, + shadowColor: 'rgb(95, 149, 255)', + shadowBlur: 10, + 'text-shape': { + fontWeight: 500, + }, + }, + highlight: { + fill: 'rgb(223, 234, 255)', + stroke: '#4572d9', + lineWidth: 2, + 'text-shape': { + fontWeight: 500, + }, + }, + inactive: { + fill: 'rgb(247, 250, 255)', + stroke: 'rgb(191, 213, 255)', + lineWidth: 1, + }, + disable: { + fill: 'rgb(250, 250, 250)', + stroke: 'rgb(224, 224, 224)', + lineWidth: 1, + }, + fill: '#fff', + stroke: 'transparent', + cursor: 'pointer', + radius: 8, + shadowColor: 'rgba(75, 84, 137, 0.4)', + shadowBlur: 6, + shadowOffsetX: 0, + shadowOffsetY: 0, + overflow: 'hidden', + lineWidth: 0.5, + }, + depth: 0, + }, + ], + edges: [ + { + source: 'git-clone-c0724278', + target: 'model-train-39e9bc7c', + style: { + endArrow: { + path: 'M 6,0 L 9,-1.5 L 9,1.5 Z', + d: 4.5, + fill: '#CDD0DC', + }, + cursor: 'pointer', + lineWidth: 1, + lineAppendWidth: 4, + opacity: 1, + stroke: '#CDD0DC', + radius: 1, + active: { + stroke: 'rgb(95, 149, 255)', + lineWidth: 1, + }, + selected: { + stroke: 'rgb(95, 149, 255)', + lineWidth: 2, + shadowColor: 'rgb(95, 149, 255)', + shadowBlur: 10, + 'text-shape': { + fontWeight: 500, + }, + }, + highlight: { + stroke: 'rgb(95, 149, 255)', + lineWidth: 2, + 'text-shape': { + fontWeight: 500, + }, + }, + inactive: { + stroke: 'rgb(234, 234, 234)', + lineWidth: 1, + }, + disable: { + stroke: 'rgb(245, 245, 245)', + lineWidth: 1, + }, + }, + labelCfg: { + autoRotate: true, + style: { + fontSize: 10, + fill: '#FFF', + }, + }, + id: 'edge-0.163955357654560491741769193740', + startPoint: { + x: 612, + y: 233.25, + anchorIndex: 1, + }, + endPoint: { + x: 596, + y: 329.75, + anchorIndex: 0, + }, + sourceAnchor: 1, + targetAnchor: 0, + type: 'cubic-vertical', + curvePosition: [0.5, 0.5], + minCurveOffset: [0, 0], + depth: 0, + }, + { + source: 'model-train-39e9bc7c', + target: 'model-evaluate-c5b68e7c', + style: { + endArrow: { + path: 'M 6,0 L 9,-1.5 L 9,1.5 Z', + d: 4.5, + fill: '#CDD0DC', + }, + cursor: 'pointer', + lineWidth: 1, + lineAppendWidth: 4, + opacity: 1, + stroke: '#CDD0DC', + radius: 1, + active: { + stroke: 'rgb(95, 149, 255)', + lineWidth: 1, + }, + selected: { + stroke: 'rgb(95, 149, 255)', + lineWidth: 2, + shadowColor: 'rgb(95, 149, 255)', + shadowBlur: 10, + 'text-shape': { + fontWeight: 500, + }, + }, + highlight: { + stroke: 'rgb(95, 149, 255)', + lineWidth: 2, + 'text-shape': { + fontWeight: 500, + }, + }, + inactive: { + stroke: 'rgb(234, 234, 234)', + lineWidth: 1, + }, + disable: { + stroke: 'rgb(245, 245, 245)', + lineWidth: 1, + }, + }, + labelCfg: { + autoRotate: true, + style: { + fontSize: 10, + fill: '#FFF', + }, + }, + id: 'edge-0.150681812754051241741769198063', + startPoint: { + x: 596, + y: 366.25, + anchorIndex: 1, + }, + endPoint: { + x: 600, + y: 441.75, + anchorIndex: 0, + }, + sourceAnchor: 1, + targetAnchor: 0, + type: 'cubic-vertical', + curvePosition: [0.5, 0.5], + minCurveOffset: [0, 0], + depth: 0, + }, + { + source: 'model-evaluate-c5b68e7c', + target: 'model-export-edcf438e', + style: { + endArrow: { + path: 'M 6,0 L 9,-1.5 L 9,1.5 Z', + d: 4.5, + fill: '#CDD0DC', + }, + cursor: 'pointer', + lineWidth: 1, + lineAppendWidth: 4, + opacity: 1, + stroke: '#CDD0DC', + radius: 1, + active: { + stroke: 'rgb(95, 149, 255)', + lineWidth: 1, + }, + selected: { + stroke: 'rgb(95, 149, 255)', + lineWidth: 2, + shadowColor: 'rgb(95, 149, 255)', + shadowBlur: 10, + 'text-shape': { + fontWeight: 500, + }, + }, + highlight: { + stroke: 'rgb(95, 149, 255)', + lineWidth: 2, + 'text-shape': { + fontWeight: 500, + }, + }, + inactive: { + stroke: 'rgb(234, 234, 234)', + lineWidth: 1, + }, + disable: { + stroke: 'rgb(245, 245, 245)', + lineWidth: 1, + }, + }, + labelCfg: { + autoRotate: true, + style: { + fontSize: 10, + fill: '#FFF', + }, + }, + id: 'edge-0.40652053406360491741769205906', + startPoint: { + x: 600, + y: 478.25, + anchorIndex: 1, + }, + endPoint: { + x: 592, + y: 562.75, + anchorIndex: 0, + }, + sourceAnchor: 1, + targetAnchor: 0, + type: 'cubic-vertical', + curvePosition: [0.5, 0.5], + minCurveOffset: [0, 0], + depth: 0, + }, + ], + combos: [], + }, + global_param: null, + create_by: 'fanshuai', + create_time: '2025-06-19T19:50:47.000+08:00', + update_by: 'fanshuai', + update_time: '2025-06-24T09:28:28.000+08:00', + state: 1, + }, + }, +}); diff --git a/react-ui/package.json b/react-ui/package.json index 0b7249fa..10086d10 100644 --- a/react-ui/package.json +++ b/react-ui/package.json @@ -8,7 +8,7 @@ "build": "max build", "deploy": "npm run build && npm run gh-pages", "dev": "npm run start:dev", - "dev-no-sso": "cross-env NO_SSO=true npm run start:dev", + "dev-no-sso": "cross-env NO_SSO=true npm run start:mock", "docker-hub:build": "docker build -f Dockerfile.hub -t ant-design-pro ./", "docker-prod:build": "docker-compose -f ./docker/docker-compose.yml build", "docker-prod:dev": "docker-compose -f ./docker/docker-compose.yml up", diff --git a/react-ui/src/components/CodeSelect/index.tsx b/react-ui/src/components/CodeSelect/index.tsx index 12f52c07..2ea28ccb 100644 --- a/react-ui/src/components/CodeSelect/index.tsx +++ b/react-ui/src/components/CodeSelect/index.tsx @@ -4,7 +4,7 @@ * @Description: 流水线选择代码配置表单 */ -import CodeSelectorModal from '@/components/CodeSelectorModal'; +import CodeSelectorModal, { CodeConfigData } from '@/components/CodeSelectorModal'; import KFIcon from '@/components/KFIcon'; import { openAntdModal } from '@/utils/modal'; import { Button } from 'antd'; @@ -18,19 +18,9 @@ export { type ParameterInputValue, } from '../ParameterInput'; -export type CodeSelectProps = ParameterInputProps; - -// 服务的需要的代码配置数据格式 -export type ServerCodeData = { - id: number; - name: string; - code_path: string; - branch: string; - username: string; - password: string; - ssh_private_key: string; - is_public: boolean; -}; +export interface CodeSelectProps extends ParameterInputProps { + value?: CodeConfigData; +} /** 代码配置选择表单组件 */ function CodeSelect({ @@ -44,50 +34,18 @@ function CodeSelect({ }: CodeSelectProps) { // 选择代码配置 const selectResource = () => { - const codeData = value as ServerCodeData; - const defaultSelected = - value && typeof value === 'object' - ? { - id: codeData.id, - code_repo_name: codeData.name, - git_url: codeData.code_path, - git_branch: codeData.branch, - git_user_name: codeData.username, - git_password: codeData.password, - ssh_key: codeData.ssh_private_key, - is_public: codeData.is_public, - } - : undefined; + const defaultSelected: CodeConfigData | undefined = + value && typeof value === 'object' ? value : undefined; const { close } = openAntdModal(CodeSelectorModal, { defaultSelected: defaultSelected, onOk: (res) => { if (res) { - const { - id, - code_repo_name, - git_url, - git_branch, - git_user_name, - git_password, - ssh_key, - is_public, - } = res; - const jsonObj = { - id, - name: code_repo_name, - code_path: git_url, - branch: git_branch, - username: git_user_name, - password: git_password, - ssh_private_key: ssh_key, - is_public, - }; - const jsonObjStr = JSON.stringify(jsonObj); + const { code_repo_name } = res; onChange?.({ - value: jsonObjStr, + ...res, + value: code_repo_name, showValue: code_repo_name, fromSelect: true, - ...jsonObj, }); } else { onChange?.(undefined); diff --git a/react-ui/src/components/FormInfo/index.tsx b/react-ui/src/components/FormInfo/index.tsx index d33d615a..6416e6e5 100644 --- a/react-ui/src/components/FormInfo/index.tsx +++ b/react-ui/src/components/FormInfo/index.tsx @@ -1,3 +1,4 @@ +import { PipelineGlobalParamType, type PipelineGlobalParam } from '@/types'; import { formatEnum } from '@/utils/format'; import { Typography, type SelectProps } from 'antd'; import classNames from 'classnames'; @@ -16,6 +17,8 @@ type FormInfoProps = { options?: SelectProps['options']; /** 自定义节点 label、value 的字段 */ fieldNames?: SelectProps['fieldNames']; + /** 全局参数 */ + globalParams?: PipelineGlobalParam[] | null; /** 自定义类名 */ className?: string; /** 自定义样式 */ @@ -32,12 +35,29 @@ function FormInfo({ select = false, options, fieldNames, + globalParams, className, style, }: FormInfoProps) { let showValue = value; if (value && typeof value === 'object' && valuePropName) { showValue = value[valuePropName]; + const reg = /^\$\{(.*)\}$/; + if (value.fromSelect && Array.isArray(globalParams) && globalParams.length > 0) { + const match = reg.exec(showValue); + if (match) { + const paramName = match[1]; + const foundParam = globalParams.find((v) => v.param_name === paramName); + if (foundParam) { + showValue = + foundParam.param_type === PipelineGlobalParamType.Boolean // 布尔类型转换 + ? foundParam.param_value + ? 'true' + : 'false' + : foundParam.param_value; + } + } + } } else if (select === true && options) { let _options: SelectProps['options'] = options; if (fieldNames) { diff --git a/react-ui/src/components/ParameterInput/index.tsx b/react-ui/src/components/ParameterInput/index.tsx index 08cf8649..fef6b900 100644 --- a/react-ui/src/components/ParameterInput/index.tsx +++ b/react-ui/src/components/ParameterInput/index.tsx @@ -9,6 +9,7 @@ import { CloseOutlined } from '@ant-design/icons'; import { ConfigProvider, Form, Input, Typography } from 'antd'; import { RuleObject } from 'antd/es/form'; import classNames from 'classnames'; +import { ReactNode } from 'react'; import './index.less'; // 如果值是对象时的类型 @@ -55,6 +56,8 @@ export interface ParameterInputProps { disabled?: boolean; /** 元素 id */ id?: string; + /** 带标签的 input,设置后置标签 */ + addonAfter?: ReactNode; } function ParameterInput({ @@ -75,7 +78,7 @@ function ParameterInput({ const valueObj = typeof value === 'string' ? { value: value, fromSelect: false, showValue: value } : value; if (valueObj && !valueObj.showValue) { - valueObj.showValue = valueObj.value; + valueObj.showValue = typeof valueObj.value === 'string' ? valueObj.value : ''; } const isSelect = valueObj?.fromSelect; const placeholder = valueObj?.placeholder || rest?.placeholder; diff --git a/react-ui/src/components/ParameterSelect/config.tsx b/react-ui/src/components/ParameterSelect/config.tsx index 662a7981..71b1ff6b 100644 --- a/react-ui/src/components/ParameterSelect/config.tsx +++ b/react-ui/src/components/ParameterSelect/config.tsx @@ -1,29 +1,34 @@ -import { filterResourceStandard, resourceFieldNames } from '@/hooks/useComputingResource'; +import { DatasetData, ModelData } from '@/pages/Dataset/config'; import { ServiceData } from '@/pages/ModelDeployment/types'; import { getDatasetList, getModelList } from '@/services/dataset/index.js'; import { getServiceListReq } from '@/services/modelDeployment'; +import type { JCCResourceImage, JCCResourceStandard, JCCResourceType } from '@/state/jcdResource'; +import { filterResourceStandard, resourceFieldNames } from '@/state/systemResource'; import { type SelectProps } from 'antd'; -import { pick } from 'lodash'; - -// id 从 number 转换为 string -const convertId = (item: any) => ({ - ...item, - id: JSON.stringify({ - id: `${item.id}`, - name: item.name, - identifier: item.identifier, - owner: item.owner, - }), -}); export type SelectPropsConfig = { - getOptions: () => Promise; // 获取下拉数据 + getOptions?: () => Promise; // 获取下拉数据 fieldNames?: SelectProps['fieldNames']; // 下拉数据字段 optionFilterProp?: SelectProps['optionFilterProp']; // 过滤字段名 filterOption?: SelectProps['filterOption']; // 过滤函数 + isObjectValue: boolean; // value 是对象 + getValue?: (value: any) => string | number; // 对象类型时,获取其值 + getLabel?: (value: any) => string; // 对象类型时,获取其 label }; -export const paramSelectConfig: Record = { +export const ParameterSelectTypeList = [ + 'dataset', + 'model', + 'service', + 'resource', + 'remote-image', + 'remote-resource-type', + 'remote-resource', +] as const; + +export type ParameterSelectDataType = (typeof ParameterSelectTypeList)[number]; + +export const paramSelectConfig: Record = { dataset: { getOptions: async () => { const res = await getDatasetList({ @@ -31,13 +36,16 @@ export const paramSelectConfig: Record = { size: 1000, is_public: false, }); - return res?.data?.content?.map(convertId) ?? []; + return res?.data?.content ?? []; }, - fieldNames: { - label: 'name', - value: 'id', + optionFilterProp: 'label', + getValue: (value: DatasetData) => { + return value.id; + }, + getLabel: (value: DatasetData) => { + return value.name; }, - optionFilterProp: 'name', + isObjectValue: true, }, model: { getOptions: async () => { @@ -46,13 +54,16 @@ export const paramSelectConfig: Record = { size: 1000, is_public: false, }); - return res?.data?.content?.map(convertId) ?? []; + return res?.data?.content ?? []; + }, + optionFilterProp: 'label', + getValue: (value: ModelData) => { + return value.id; }, - fieldNames: { - label: 'name', - value: 'id', + getLabel: (value: ModelData) => { + return value.name; }, - optionFilterProp: 'name', + isObjectValue: true, }, service: { getOptions: async () => { @@ -60,25 +71,58 @@ export const paramSelectConfig: Record = { page: 0, size: 1000, }); - return ( - res?.data?.content?.map((item: ServiceData) => ({ - label: item.service_name, - value: JSON.stringify(pick(item, ['id', 'service_name'])), - })) ?? [] - ); - }, - fieldNames: { - label: 'label', - value: 'value', + return res?.data?.content ?? []; }, optionFilterProp: 'label', + getValue: (value: ServiceData) => { + return value.id; + }, + getLabel: (value: ServiceData) => { + return value.service_name; + }, + isObjectValue: true, }, resource: { - getOptions: async () => { - // 不需要这个函数 - return []; - }, fieldNames: resourceFieldNames, filterOption: filterResourceStandard as SelectProps['filterOption'], + isObjectValue: false, + }, + 'remote-resource-type': { + optionFilterProp: 'label', + isObjectValue: false, + getValue: (value: JCCResourceType) => { + return value.value; + }, + getLabel: (value: JCCResourceType) => { + return value.label; + }, + }, + 'remote-image': { + optionFilterProp: 'label', + getValue: (value: JCCResourceImage) => { + return value.imageID; + }, + getLabel: (value: JCCResourceImage) => { + return value.name; + }, + isObjectValue: true, + }, + 'remote-resource': { + optionFilterProp: 'label', + getValue: (value: JCCResourceStandard) => { + return value.id; + }, + getLabel: (value: JCCResourceStandard) => { + const cpu = value.baseResourceSpecs.find((v) => v.type === 'CPU'); + const ram = value.baseResourceSpecs.find((v) => v.type === 'MEMORY' && v.name === 'RAM'); + const vram = value.baseResourceSpecs.find((v) => v.type === 'MEMORY' && v.name === 'VRAM'); + const cpuText = cpu ? `CPU:${cpu.availableValue}, ` : ''; + const ramText = ram ? `内存: ${ram.availableValue}${ram.availableUnit?.toUpperCase()}` : ''; + const vramText = vram + ? `(显存${vram.availableValue}${vram.availableUnit?.toUpperCase()})` + : ''; + return `${value.type}: ${value.availableCount}*${value.name}${vramText}, ${cpuText}${ramText}`; + }, + isObjectValue: true, }, }; diff --git a/react-ui/src/components/ParameterSelect/index.tsx b/react-ui/src/components/ParameterSelect/index.tsx index c9a78069..cdae15ed 100644 --- a/react-ui/src/components/ParameterSelect/index.tsx +++ b/react-ui/src/components/ParameterSelect/index.tsx @@ -4,19 +4,25 @@ * @Description: 参数下拉选择组件,支持资源规格、数据集、模型、服务 */ -import { useComputingResource } from '@/hooks/useComputingResource'; +import jccResourceState from '@/state/jcdResource'; +import systemResourceState, { getSystemResources } from '@/state/systemResource'; import { to } from '@/utils/promise'; +import { useSnapshot } from '@umijs/max'; import { Select, type SelectProps } from 'antd'; -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import FormInfo from '../FormInfo'; -import { paramSelectConfig } from './config'; +import { paramSelectConfig, type ParameterSelectDataType } from './config'; + +export { ParameterSelectTypeList, type ParameterSelectDataType } from './config'; export type ParameterSelectObject = { value: any; [key: string]: any; }; -export type ParameterSelectDataType = 'dataset' | 'model' | 'service' | 'resource'; +type SelectOptions = SelectProps['options']; + +const identityFunc = (value: any) => value; export interface ParameterSelectProps extends SelectProps { /** 类型 */ @@ -25,8 +31,6 @@ export interface ParameterSelectProps extends SelectProps { display?: boolean; /** 值,支持对象,对象必须包含 value */ value?: string | ParameterSelectObject; - /** 用于流水线, 流水线资源规格要求 id 为字符串 */ - isPipeline?: boolean; /** 修改后回调 */ onChange?: (value: string | ParameterSelectObject) => void; } @@ -36,69 +40,126 @@ function ParameterSelect({ dataType, display = false, value, - isPipeline = false, onChange, ...rest }: ParameterSelectProps) { - const [options, setOptions] = useState([]); + const [options, setOptions] = useState([]); const propsConfig = paramSelectConfig[dataType]; - const valueText = typeof value === 'object' && value !== null ? value.value : value; - const [resourceStandardList] = useComputingResource(); - const computingResource = isPipeline - ? resourceStandardList.map((v) => ({ - ...v, - id: String(v.id), - })) - : resourceStandardList; + const { + getLabel = identityFunc, + getValue = identityFunc, + getOptions, + filterOption, + fieldNames, + optionFilterProp, + isObjectValue, + } = propsConfig; + const selectValue = typeof value === 'object' && value !== null ? value.value : value; + // 数据集、模型、服务,对象转换成 json 字符串 + const valueText = + typeof selectValue === 'object' && selectValue !== null ? getValue(selectValue) : selectValue; + const jccResourceSnap = useSnapshot(jccResourceState); + const { getResourceTypes } = jccResourceSnap; + const systemResourceSnap = useSnapshot(systemResourceState); + + const objectOptions = useMemo(() => { + return dataType === 'remote-resource-type' + ? jccResourceSnap.types + : dataType === 'remote-image' + ? jccResourceSnap.images + : dataType === 'remote-resource' + ? jccResourceSnap.resources + : options; + }, [dataType, options, jccResourceSnap.types, jccResourceSnap.images, jccResourceSnap.resources]); + + // 将对象类型转换为 Select Options + const converObjectToOptions = useCallback( + (v: any) => { + return { + label: getLabel(v), + value: getValue(v), + }; + }, + [getLabel, getValue], + ); + + // 数据集、模型、服务获取数据后,进行转换 + const objectSelectOptions = useMemo(() => { + return objectOptions?.map(converObjectToOptions); + }, [converObjectToOptions, objectOptions]); + + // 快速得到选中的对象 + const valueMap = useMemo(() => { + const map = new Map(); + objectOptions?.forEach((v) => { + map.set(getValue(v), v); + }); + + return map; + }, [objectOptions, getValue]); useEffect(() => { // 获取下拉数据 const getSelectOptions = async () => { - if (!propsConfig) { - return; - } - const getOptions = propsConfig.getOptions; - const [res] = await to(getOptions()); - if (res) { - setOptions(res); + if (getOptions) { + const [res] = await to(getOptions()); + if (res) { + setOptions(res); + } + } else if (dataType === 'remote-resource-type') { + getResourceTypes(); + } else if (dataType === 'resource') { + getSystemResources(); } }; - getSelectOptions(); - }, [propsConfig]); + }, [getOptions, dataType, getResourceTypes]); - const selectOptions = dataType === 'resource' ? computingResource : options; + const selectOptions = ( + dataType === 'resource' ? systemResourceSnap.resources : objectSelectOptions + ) as SelectOptions; const handleChange = (text: string) => { - if (typeof value === 'object' && value !== null) { - onChange?.({ - ...value, - value: text, - }); + // 数据集、模型、服务,转换成对象 + if (isObjectValue) { + // 设置为 null 是因为 antv g6 的 bug + // 如果值为 undefined 时, graph.changeData(data) 会保留前面的值 + const selectValue = text ? valueMap.get(text) : null; + if (typeof value === 'object' && value !== null) { + onChange?.({ + ...value, + value: selectValue, + }); + } else { + onChange?.(selectValue); + } } else { - onChange?.(text); + const selectValue = text ? text : ''; + if (typeof value === 'object' && value !== null) { + onChange?.({ + ...value, + value: selectValue, + }); + } else { + onChange?.(selectValue); + } } }; // 只用于展示,FormInfo 组件带有 Tooltip if (display) { return ( - + ); } return ( - { - if (value === 'master') { - return Promise.reject(`数据集版本不能为 master`); - } else if (value === 'origin') { - return Promise.reject(`数据集版本不能为 origin`); - } - return Promise.resolve(); - }, - }, - ]} - > - - - { - if (value === 'master') { - return Promise.reject(`模型版本不能为 master`); - } else if (value === 'origin') { - return Promise.reject(`模型版本不能为 origin`); - } - return Promise.resolve(); - }, - }, - ]} - > - - + { + 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 ( + +
+ + + + { + if (value === 'master') { + return Promise.reject(`${name}版本不能为 master`); + } else if (value === 'origin') { + return Promise.reject(`${name}版本不能为 origin`); + } + return Promise.resolve(); + }, + }, + ]} + > + + + + + +
+
+ ); +} + +export default EditVersionModal; diff --git a/react-ui/src/pages/Dataset/components/ResourceInfo/index.less b/react-ui/src/pages/Dataset/components/ResourceInfo/index.less index 5bd8b25e..9f85e8f0 100644 --- a/react-ui/src/pages/Dataset/components/ResourceInfo/index.less +++ b/react-ui/src/pages/Dataset/components/ResourceInfo/index.less @@ -28,8 +28,15 @@ border-radius: 4px; } + &__desc { + margin-bottom: 0 !important; + color: @text-color; + font-size: @font-size; + } + &__praise { display: flex; + flex: none; align-items: center; justify-content: center; width: 70px; diff --git a/react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx b/react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx index 2f162f9b..544d040a 100644 --- a/react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx +++ b/react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx @@ -4,6 +4,7 @@ * @Description: 数据集、模型详情 */ +import KFEmpty, { EmptyType } from '@/components/KFEmpty'; import KFIcon from '@/components/KFIcon'; import { ResourceData, @@ -19,10 +20,11 @@ import { openAntdModal } from '@/utils/modal'; import { to } from '@/utils/promise'; import { modalConfirm } from '@/utils/ui'; import { useParams, useSearchParams } from '@umijs/max'; -import { App, Button, Flex, Select, Tabs } from 'antd'; +import { App, Button, Flex, Select, Tabs, Typography } 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'; @@ -61,21 +63,24 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { const { message } = App.useApp(); // 获取详情 - const getResourceDetail = useCallback(async () => { - const params = { - id: resourceId, - owner, - name, - identifier, - version, - is_public, - }; - const request = config.getInfo; - const [res] = await to(request(params)); - if (res && res.data) { - setInfo(res.data); - } - }, [config, resourceId, owner, name, identifier, version, is_public]); + const getResourceDetail = useCallback( + async (version: string | undefined) => { + const params = { + id: resourceId, + owner, + name, + identifier, + version, + is_public, + }; + const request = config.getInfo; + const [res] = await to(request(params)); + if (res && res.data) { + setInfo(res.data); + } + }, + [config, resourceId, owner, name, identifier, is_public], + ); // 获取版本列表 const getVersionList = useCallback( @@ -100,14 +105,15 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { } } else { setVersion(undefined); + getResourceDetail(undefined); } }, - [config, owner, identifier, versionParam], + [config, owner, identifier, versionParam, getResourceDetail], ); useEffect(() => { if (version) { - getResourceDetail(); + getResourceDetail(version); } }, [version, getResourceDetail]); @@ -116,7 +122,7 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { }, [getVersionList]); // 新建版本 - const showModal = () => { + const showAddVersionModal = () => { const { close } = openAntdModal(AddVersionModal, { resourceType: resourceType, resourceId: resourceId, @@ -132,6 +138,18 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { }); }; + // 版本编辑 + const showEditVersionModal = () => { + const { close } = openAntdModal(EditVersionModal, { + resourceType: resourceType, + resourceVersion: info, + onOk: () => { + getResourceDetail(); + close(); + }, + }); + }; + // 选择版本 const showVersionSelector = () => { const { close } = openAntdModal(VersionSelectorModal, { @@ -278,44 +296,70 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { {info.praises_count} - - 版本号: - + + + + + ) : ( + - 删除版本 - - + {info.description ?? '暂无描述'} + + )}
- setActiveTab(key)}> -
- {activeTab === ResourceInfoTabKeys.Evolution && } -
+ {version ? ( + <> + setActiveTab(key)}> +
+ {activeTab === ResourceInfoTabKeys.Evolution && } +
+ + ) : ( + + )}
); diff --git a/react-ui/src/pages/Dataset/config.tsx b/react-ui/src/pages/Dataset/config.tsx index 4799c75c..2f43bda5 100644 --- a/react-ui/src/pages/Dataset/config.tsx +++ b/react-ui/src/pages/Dataset/config.tsx @@ -9,11 +9,15 @@ import { deleteDatasetVersion, deleteModel, deleteModelVersion, + editDatasetVersion, + editModelVersion, getDatasetInfo, getDatasetList, + getDatasetNextVersionReq, getDatasetVersionList, getModelInfo, getModelList, + getModelNextVersionReq, getModelVersionList, } from '@/services/dataset/index.js'; import { limitUploadFileType } from '@/utils/ui'; @@ -36,9 +40,11 @@ type ResourceTypeInfo = { getVersions: (params: any) => Promise; // 获取版本列表 deleteRecord: (params: any) => Promise; // 删除 addVersion: (params: any) => Promise; // 新增版本 + editVersion: (params: any) => Promise; // 编辑版本 deleteVersion: (params: any) => Promise; // 删除版本 getInfo: (params: any) => Promise; // 获取详情 compareVersion: (params: any) => Promise; // 版本对比 + getNextVersion: (params: any) => Promise; // 获取下一个版本 name: string; // 名称 typeParamKey: 'data_type' | 'model_type'; // 类型参数名称,获取资源列表接口使用 tagParamKey: 'data_tag' | 'model_tag'; // 标签参数名称,获取资源列表接口使用 @@ -65,9 +71,11 @@ export const resourceConfig: Record = { getVersions: getDatasetVersionList, deleteRecord: deleteDataset, addVersion: addDatasetVersion, + editVersion: editDatasetVersion, deleteVersion: deleteDatasetVersion, getInfo: getDatasetInfo, compareVersion: compareDatasetVersion, + getNextVersion: getDatasetNextVersionReq, name: '数据集', typeParamKey: 'data_type', tagParamKey: 'data_tag', @@ -103,9 +111,11 @@ export const resourceConfig: Record = { getVersions: getModelVersionList, deleteRecord: deleteModel, addVersion: addModelVersion, + editVersion: editModelVersion, deleteVersion: deleteModelVersion, getInfo: getModelInfo, compareVersion: compareModelVersion, + getNextVersion: getModelNextVersionReq, name: '模型', typeParamKey: 'model_type', tagParamKey: 'model_tag', diff --git a/react-ui/src/pages/DevelopmentEnvironment/List/index.tsx b/react-ui/src/pages/DevelopmentEnvironment/List/index.tsx index 27944aac..5395d44e 100644 --- a/react-ui/src/pages/DevelopmentEnvironment/List/index.tsx +++ b/react-ui/src/pages/DevelopmentEnvironment/List/index.tsx @@ -4,10 +4,11 @@ * @Description: 开发环境列表 */ +import { CodeConfigData } from '@/components/CodeSelectorModal'; import KFIcon from '@/components/KFIcon'; import { DevEditorStatus } from '@/enums'; import { useCacheState } from '@/hooks/useCacheState'; -import { useComputingResource } from '@/hooks/useComputingResource'; +import { useSystemResource } from '@/hooks/useComputingResource'; import { DatasetData, ModelData } from '@/pages/Dataset/config'; import { deleteEditorReq, @@ -17,12 +18,7 @@ import { } from '@/services/developmentEnvironment'; import themes from '@/styles/theme.less'; import { parseJsonText } from '@/utils'; -import { - formatCodeConfig, - formatDataset, - formatModel, - type SelectedCodeConfig, -} from '@/utils/format'; +import { formatCodeConfig, formatDataset, formatModel } from '@/utils/format'; import { openAntdModal } from '@/utils/modal'; import { to } from '@/utils/promise'; import SessionStorage from '@/utils/sessionStorage'; @@ -55,7 +51,7 @@ export type EditorData = { dataset?: string | DatasetData; model?: string | ModelData; image?: string; - code_config?: string | SelectedCodeConfig; + code_config?: string | CodeConfigData; }; function EditorList() { @@ -70,7 +66,7 @@ function EditorList() { pageSize: 10, }, ); - const getResourceDescription = useComputingResource()[1]; + const getResourceDescription = useSystemResource(); // 获取编辑器列表 const getEditorList = useCallback(async () => { @@ -211,7 +207,7 @@ function EditorList() { const gotoCodeConfig = (record: EditorData, e: React.MouseEvent) => { e.stopPropagation(); - const codeConfig = record.code_config as SelectedCodeConfig; + const codeConfig = record.code_config as CodeConfigData; const url = formatCodeConfig(codeConfig)?.url; if (url) { window.open(url, '_blank'); diff --git a/react-ui/src/pages/Experiment/Info/index.jsx b/react-ui/src/pages/Experiment/Info/index.jsx index 56f7e979..296dbfe3 100644 --- a/react-ui/src/pages/Experiment/Info/index.jsx +++ b/react-ui/src/pages/Experiment/Info/index.jsx @@ -95,16 +95,13 @@ function ExperimentText() { return; } - const workflow = parseJsonText(dag); + const workflow = dag; const experimentStatusObjs = parseJsonText(nodes_status); 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; @@ -140,8 +137,11 @@ function ExperimentText() { } else if (status === ExperimentStatus.Running) { // 如果状态是 Running,打开第一个 Running 或者 pending 的节点,如果没有,则打开第一个节点 const node = - workflow.nodes.find((item) => item.experimentStatus === ExperimentStatus.Running || item.experimentStatus === ExperimentStatus.Pending) ?? - workflow.nodes[0]; + workflow.nodes.find( + (item) => + item.experimentStatus === ExperimentStatus.Running || + item.experimentStatus === ExperimentStatus.Pending, + ) ?? workflow.nodes[0]; if (node) { setExperimentNodeData(node); openPropsDrawer(); @@ -567,12 +567,13 @@ function ExperimentText() { instanceNodeStatus={experimentNodeData.experimentStatus} instanceNodeStartTime={experimentNodeData.experimentStartTime} instanceNodeEndTime={experimentNodeData.experimentEndTime} + globalParams={experimentIns?.global_param} > ) : null} ); diff --git a/react-ui/src/pages/Experiment/components/AddExperimentModal/index.less b/react-ui/src/pages/Experiment/components/AddExperimentModal/index.less index 2470e868..8cc0f830 100644 --- a/react-ui/src/pages/Experiment/components/AddExperimentModal/index.less +++ b/react-ui/src/pages/Experiment/components/AddExperimentModal/index.less @@ -6,4 +6,10 @@ border: 1px solid #e6e6e6; border-radius: 6px; } + + :global { + .ant-form-item-row { + align-items: center; + } + } } diff --git a/react-ui/src/pages/Experiment/components/AddExperimentModal/index.tsx b/react-ui/src/pages/Experiment/components/AddExperimentModal/index.tsx index 2a15a8e7..4d576819 100644 --- a/react-ui/src/pages/Experiment/components/AddExperimentModal/index.tsx +++ b/react-ui/src/pages/Experiment/components/AddExperimentModal/index.tsx @@ -1,7 +1,7 @@ import createExperimentIcon from '@/assets/img/create-experiment.png'; import editExperimentIcon from '@/assets/img/edit-experiment.png'; import KFModal from '@/components/KFModal'; -import { type PipelineGlobalParam } from '@/types'; +import { PipelineGlobalParamType, type PipelineGlobalParam } from '@/types'; import { to } from '@/utils/promise'; import { Button, Form, Input, Radio, Select, Typography, type FormRule } from 'antd'; import { useState } from 'react'; @@ -32,7 +32,7 @@ interface Workflow { // 根据参数设置输入组件 export const getParamComponent = (paramType: number): JSX.Element => { // 防止后台返回不是 number 类型 - if (Number(paramType) === 3) { + if (Number(paramType) === PipelineGlobalParamType.Boolean) { return ( @@ -50,7 +50,7 @@ export const getParamComponent = (paramType: number): JSX.Element => { export const getParamRules = (paramType: number, required: boolean = false): FormRule[] => { const rules = []; // 防止后台返回不是 number 类型 - if (Number(paramType) === 2) { + if (Number(paramType) === PipelineGlobalParamType.Number) { rules.push({ pattern: /^-?((0(\.0*[1-9]\d*)?)|([1-9]\d*(\.\d+)?))$/, message: '整型必须是数字', @@ -64,10 +64,10 @@ export const getParamRules = (paramType: number, required: boolean = false): For // 根据参数设置 label export const getParamLabel = (param: PipelineGlobalParam): React.ReactNode => { - const paramTypes: Readonly> = { - 1: '字符串', - 2: '整型', - 3: '布尔类型', + const paramTypes: Readonly> = { + [PipelineGlobalParamType.String]: '字符串', + [PipelineGlobalParamType.Number]: '整型', + [PipelineGlobalParamType.Boolean]: '布尔类型', }; const label = param.param_name + `(${paramTypes[param.param_type]})`; return {label}; diff --git a/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx b/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx index 11d0ff2e..7a52060a 100644 --- a/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx +++ b/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx @@ -1,7 +1,7 @@ import RunDuration from '@/components/RunDuration'; import { ExperimentStatus } from '@/enums'; import { experimentStatusInfo } from '@/pages/Experiment/status'; -import { PipelineNodeModelSerialize } from '@/types'; +import { PipelineNodeModelSerialize, type PipelineGlobalParam } from '@/types'; import { formatDate } from '@/utils/date'; import { CloseOutlined, DatabaseOutlined, ProfileOutlined } from '@ant-design/icons'; import { Drawer, Tabs, Typography } from 'antd'; @@ -25,6 +25,7 @@ type ExperimentDrawerProps = { instanceNodeStatus?: ExperimentStatus; // 实例节点状态 instanceNodeStartTime?: string; // 开始时间 instanceNodeEndTime?: string; // 在定时刷新实验实例状态中,会经常变化 + globalParams?: PipelineGlobalParam[] | null; // 全局参数 }; const ExperimentDrawer = ({ @@ -41,6 +42,7 @@ const ExperimentDrawer = ({ instanceNodeStatus, instanceNodeStartTime, instanceNodeEndTime, + globalParams, }: ExperimentDrawerProps) => { // 如果性能有问题,可以进一步拆解 const items = useMemo( @@ -66,7 +68,7 @@ const ExperimentDrawer = ({ key: '2', label: '配置参数', icon: , - children: , + children: , }, { key: '3', @@ -94,6 +96,7 @@ const ExperimentDrawer = ({ experimentName, experimentId, pipelineId, + globalParams, ], ); diff --git a/react-ui/src/pages/Experiment/components/ExperimentParameter/index.less b/react-ui/src/pages/Experiment/components/ExperimentParameter/index.less index c5d9824e..339ef362 100644 --- a/react-ui/src/pages/Experiment/components/ExperimentParameter/index.less +++ b/react-ui/src/pages/Experiment/components/ExperimentParameter/index.less @@ -15,4 +15,24 @@ font-size: @font-size; background: #f8fbff; } + + &__form-list { + :global { + .ant-row { + padding: 0 !important; + } + } + + &:last-child { + :global { + .ant-form-item { + margin-bottom: 0 !important; + } + } + } + } + + &__list-empty { + color: @text-color-tertiary; + } } diff --git a/react-ui/src/pages/Experiment/components/ExperimentParameter/index.tsx b/react-ui/src/pages/Experiment/components/ExperimentParameter/index.tsx index 2bd39c29..bb9c1b63 100644 --- a/react-ui/src/pages/Experiment/components/ExperimentParameter/index.tsx +++ b/react-ui/src/pages/Experiment/components/ExperimentParameter/index.tsx @@ -1,25 +1,92 @@ import FormInfo from '@/components/FormInfo'; -import ParameterSelect from '@/components/ParameterSelect'; +import ParameterSelect, { type ParameterSelectDataType } from '@/components/ParameterSelect'; import SubAreaTitle from '@/components/SubAreaTitle'; -import { PipelineNodeModelSerialize } from '@/types'; -import { Form } from 'antd'; +import { ComponentType } from '@/enums'; +import type { + PipelineGlobalParam, + PipelineNodeModelParameter, + PipelineNodeModelSerialize, +} from '@/types'; +import { Flex, Form } from 'antd'; import styles from './index.less'; type ExperimentParameterProps = { nodeData: PipelineNodeModelSerialize; + globalParams?: PipelineGlobalParam[] | null; // 全局参数 }; -function ExperimentParameter({ nodeData }: ExperimentParameterProps) { +function ExperimentParameter({ nodeData, globalParams }: ExperimentParameterProps) { + // 表单组件 + const getFormComponent = ( + item: { key: string; value: PipelineNodeModelParameter }, + parentName: string, + ) => { + return ( + + {item.value.type === ComponentType.Map && ( + + {(fields) => ( + <> + {fields.length > 0 ? ( + fields.map(({ key, name, ...restField }) => ( + + + + + = + + + + + )) + ) : ( +
+ )} + + )} +
+ )} + {item.value.type === ComponentType.Select && + (['dataset', 'model', 'service', 'resource'].includes(item.value.item_type) ? ( + + ) : null)} + {item.value.type !== ComponentType.Map && item.value.type !== ComponentType.Select && ( + + )} +
+ ); + }; + + // 基本参数 + const basicParametersList = Object.entries(nodeData.task_info ?? {}) + .map(([key, value]) => ({ + key, + value, + })) + .filter((v) => v.value.visible === true); + // 控制策略 - // 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 controlStrategyList = Object.entries(nodeData.control_strategy ?? {}) + .map(([key, value]) => ({ key, value })) + .filter((v) => v.value.visible === true); // 输入参数 const inParametersList = Object.entries(nodeData.in_parameters ?? {}).map(([key, value]) => ({ @@ -80,96 +147,56 @@ function ExperimentParameter({ nodeData }: ExperimentParameterProps) { >
- {hasTaskInfo && ( + + {basicParametersList.length + controlStrategyList.length > 0 && ( +
+ +
+ )} + + {/* 基本参数 */} + {basicParametersList.map((item) => getFormComponent(item, 'task_info'))} + + {/* 控制参数 */} + {controlStrategyList.map((item) => getFormComponent(item, 'control_strategy'))} + + {/* 输入参数 */} + {inParametersList.length > 0 && ( <>
- - - - - - + {inParametersList.map((item) => getFormComponent(item, 'in_parameters'))} + + )} - - - - - - - {/* - - */} - - - - {/* {controlStrategyList.map((item) => ( - - - - ))} */} + {/* 输出参数 */} + {outParametersList.length > 0 && ( + <> +
+ +
+ {outParametersList.map((item) => ( + + + + ))} )} -
- -
- {inParametersList.map((item) => ( - - {item.value.type === 'select' ? ( - ['dataset', 'model', 'service', 'resource'].includes(item.value.item_type) ? ( - - ) : null - ) : ( - - )} - - ))} -
- -
- {outParametersList.map((item) => ( - - - - ))} ); } diff --git a/react-ui/src/pages/Experiment/components/ExportModelModal/index.tsx b/react-ui/src/pages/Experiment/components/ExportModelModal/index.tsx index 60f0555b..a49bf6a6 100644 --- a/react-ui/src/pages/Experiment/components/ExportModelModal/index.tsx +++ b/react-ui/src/pages/Experiment/components/ExportModelModal/index.tsx @@ -3,12 +3,10 @@ import KFModal from '@/components/KFModal'; import { DataSource, ResourceType, - ResourceVersionData, resourceConfig, type ResourceData, } from '@/pages/Dataset/config'; import { to } from '@/utils/promise'; -import { InfoCircleOutlined } from '@ant-design/icons'; import { Form, Input, ModalProps, Select } from 'antd'; import { pick } from 'lodash'; import { useEffect, useState } from 'react'; @@ -44,7 +42,6 @@ function ExportModelModal({ }: ExportModelModalProps) { const [form] = Form.useForm(); const [resources, setResources] = useState([]); - const [versions, setVersions] = useState([]); const config = resourceConfig[resourceType]; const layout = { @@ -77,35 +74,24 @@ function ExportModelModal({ return undefined; }; - // 版本 tooltip - const getTooltip = () => { - const id = form.getFieldValue('id'); - const resource = getSelectedResource(id); - const name = resource?.name ?? ''; - const versionNames = versions.map((item: ResourceVersionData) => item.name).join('、'); - const tooltip = - versions.length > 0 ? `${name}有以下版本:\n${versionNames}\n注意不能重复` : undefined; - return tooltip; - }; - // 处理数据集、模型选择变化 const handleResourceChange = (id: number | undefined) => { if (id) { - getRecourceVersions(id); + getRecourceNextVersion(id); } else { - setVersions([]); + form.setFieldValue('version', ''); } }; - // 获取数据集、模型版本列表 - const getRecourceVersions = async (id: number) => { + // 获取数据集、模型下一个版本 + const getRecourceNextVersion = async (id: number) => { const resource = getSelectedResource(id); if (!resource) { return; } - const [res] = await to(config.getVersions(pick(resource, ['identifier', 'owner']))); + const [res] = await to(config.getNextVersion(pick(resource, ['identifier', 'owner']))); if (res && res.data) { - setVersions(res.data); + form.setFieldValue('version', res.data); } }; @@ -184,15 +170,6 @@ function ExportModelModal({ , - } - : undefined - } rules={[ { required: true, message: `请输入${config.name}版本` }, { @@ -205,8 +182,6 @@ function ExportModelModal({ 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(); } @@ -214,7 +189,13 @@ function ExportModelModal({ }, ]} > - + void; - globalParam?: PipelineGlobalParam[] | null; + globalParams?: PipelineGlobalParam[] | null; }; -function ParamsModal({ open, onCancel, globalParam = [] }: ParamsModalProps) { +function ParamsModal({ open, onCancel, globalParams = [] }: ParamsModalProps) { return ( - {Array.isArray(globalParam) && globalParam.length > 0 ? ( + {Array.isArray(globalParams) && globalParams.length > 0 ? (
@@ -45,9 +45,9 @@ function ParamsModal({ open, onCancel, globalParam = [] }: ParamsModalProps) { {...restField} key={key} name={[name, 'param_value']} - label={getParamLabel(globalParam[name])} + label={getParamLabel(globalParams[name])} > - {getParamComponent(globalParam[name]['param_type'])} + {getParamComponent(globalParams[name]['param_type'])} )) } diff --git a/react-ui/src/pages/Experiment/index.jsx b/react-ui/src/pages/Experiment/index.jsx index 219e7c0d..d0f4b955 100644 --- a/react-ui/src/pages/Experiment/index.jsx +++ b/react-ui/src/pages/Experiment/index.jsx @@ -226,14 +226,14 @@ function Experiment() { if (type === ExperimentCompleted) { const { experimentId, experimentInsId, status, finishTime } = payload; const currentIns = experimentInsList.find((v) => v.id === experimentInsId); - console.log( - '实验实例状态变化', - currentIns?.status, - status, - experimentId, - experimentInsId, - finishTime, - ); + // console.log( + // '实验实例状态变化', + // currentIns?.status, + // status, + // experimentId, + // experimentInsId, + // finishTime, + // ); if ( !currentIns || diff --git a/react-ui/src/pages/HyperParameter/components/CreateForm/ExecuteConfig.tsx b/react-ui/src/pages/HyperParameter/components/CreateForm/ExecuteConfig.tsx index 01bc4001..313f8179 100644 --- a/react-ui/src/pages/HyperParameter/components/CreateForm/ExecuteConfig.tsx +++ b/react-ui/src/pages/HyperParameter/components/CreateForm/ExecuteConfig.tsx @@ -323,6 +323,7 @@ function ExecuteConfig() { className={styles['hyper-parameter__body__name']} {...restField} name={[name, 'name']} + dependencies={fields.map((_, i) => ['parameters', i, 'name'])} required rules={[ { diff --git a/react-ui/src/pages/HyperParameter/components/HyperParameterBasic/index.tsx b/react-ui/src/pages/HyperParameter/components/HyperParameterBasic/index.tsx index 282a5667..ea5ad15b 100644 --- a/react-ui/src/pages/HyperParameter/components/HyperParameterBasic/index.tsx +++ b/react-ui/src/pages/HyperParameter/components/HyperParameterBasic/index.tsx @@ -1,6 +1,6 @@ import ConfigInfo, { type BasicInfoData } from '@/components/ConfigInfo'; import { ExperimentStatus, hyperParameterOptimizedMode } from '@/enums'; -import { useComputingResource } from '@/hooks/useComputingResource'; +import { useSystemResource } from '@/hooks/useComputingResource'; import ExperimentRunBasic from '@/pages/AutoML/components/ExperimentRunBasic'; import { schedulerAlgorithms, @@ -41,7 +41,7 @@ function HyperParameterBasic({ instanceStatus, isInstance = false, }: HyperParameterBasicProps) { - const getResourceDescription = useComputingResource()[1]; + const getResourceDescription = useSystemResource(); const basicDatas: BasicInfoData[] = useMemo(() => { if (!info) { diff --git a/react-ui/src/pages/Mirror/Info/index.tsx b/react-ui/src/pages/Mirror/Info/index.tsx index 30b6ad47..1a925831 100644 --- a/react-ui/src/pages/Mirror/Info/index.tsx +++ b/react-ui/src/pages/Mirror/Info/index.tsx @@ -46,6 +46,7 @@ export type MirrorInfoData = { }; export type MirrorVersionData = { + image_id: number; id: number; version: string; url: string; diff --git a/react-ui/src/pages/ModelDeployment/CreateVersion/index.tsx b/react-ui/src/pages/ModelDeployment/CreateVersion/index.tsx index cd1992c2..a76d67da 100644 --- a/react-ui/src/pages/ModelDeployment/CreateVersion/index.tsx +++ b/react-ui/src/pages/ModelDeployment/CreateVersion/index.tsx @@ -23,7 +23,7 @@ import { removeFormListItem } from '@/utils/ui'; import { MinusCircleOutlined, PlusCircleOutlined, PlusOutlined } from '@ant-design/icons'; import { useNavigate, useParams } from '@umijs/max'; import { App, Button, Col, Flex, Form, Input, InputNumber, Row } from 'antd'; -import { omit, pick } from 'lodash'; +import { omit } from 'lodash'; import { useEffect, useState } from 'react'; import { CreateServiceVersionFrom, ServiceOperationType, ServiceVersionData } from '../types'; import styles from './index.less'; @@ -79,7 +79,7 @@ function CreateServiceVersion() { if (res.model && typeof res.model === 'object') { model = changePropertyName(res.model, { show_value: 'showValue' }); // 接口返回是数据没有 value 值,但是 form 需要 value - model.value = model.showValue; + // model.value = model.showValue; } // 环境变量 if (res.env_variables && typeof res.env_variables === 'object') { @@ -117,7 +117,6 @@ function CreateServiceVersion() { // 创建版本 const createServiceVersion = async (formData: FormData) => { const envList = formData['env_variables']; - const model = formData['model']; const envVariables = envList?.reduce((acc, cur) => { acc[cur.key] = cur.value; return acc; @@ -125,13 +124,9 @@ function CreateServiceVersion() { // 根据后台要求,修改表单数据 const object = { - ...omit(formData, ['replicas', 'env_variables', 'model']), + ...omit(formData, ['replicas', 'env_variables']), replicas: Number(formData.replicas), env_variables: envVariables, - model: changePropertyName( - pick(model, ['id', 'name', 'version', 'path', 'identifier', 'owner', 'showValue']), - { showValue: 'show_value' }, - ), service_id: serviceId, }; @@ -427,6 +422,7 @@ function CreateServiceVersion() { {...restField} name={[name, 'key']} style={{ flex: 1 }} + dependencies={fields.map((_, i) => ['env_variables', i, 'key'])} rules={[ { validator: (_, value) => { diff --git a/react-ui/src/pages/ModelDeployment/ServiceInfo/index.tsx b/react-ui/src/pages/ModelDeployment/ServiceInfo/index.tsx index 617d9a32..fae1e3be 100644 --- a/react-ui/src/pages/ModelDeployment/ServiceInfo/index.tsx +++ b/react-ui/src/pages/ModelDeployment/ServiceInfo/index.tsx @@ -9,7 +9,8 @@ import PageTitle from '@/components/PageTitle'; import SubAreaTitle from '@/components/SubAreaTitle'; import { ServiceRunStatus, serviceStatusOptions } from '@/enums'; import { useCacheState } from '@/hooks/useCacheState'; -import { useComputingResource } from '@/hooks/useComputingResource'; +import { useSystemResource } from '@/hooks/useComputingResource'; +import { ModelData } from '@/pages/Dataset/config'; import { deleteServiceVersionReq, getServiceInfoReq, @@ -18,6 +19,7 @@ import { } from '@/services/modelDeployment'; import themes from '@/styles/theme.less'; import { formatDate } from '@/utils/date'; +import { formatModel } from '@/utils/format'; import { openAntdModal } from '@/utils/modal'; import { to } from '@/utils/promise'; import SessionStorage from '@/utils/sessionStorage'; @@ -87,7 +89,7 @@ function ServiceInfo() { format: formatDate, }, ]; - const getResourceDescription = useComputingResource()[1]; + const getResourceDescription = useSystemResource(); // 获取服务详情 const getServiceInfo = useCallback(async () => { @@ -110,8 +112,8 @@ function ServiceInfo() { if (res && res.data) { const { content = [], totalElements = 0 } = res.data; content.forEach((item: ServiceVersionData) => { - if (item.model && !item.model.show_value) { - item.model.show_value = `${item.model.name}:${item.model.version}`; + if (item.model && !item.model.showValue) { + item.model.showValue = `${item.model.name}:${item.model.version}`; } }); setTableData(content); @@ -258,6 +260,20 @@ function ServiceInfo() { }, }; + // 去模型 + const gotoModel = (record: ServiceVersionData, e: React.MouseEvent) => { + e.stopPropagation(); + + const model = record.model as any as ModelData; + const link = formatModel(model)?.link; + if (link) { + setCacheState({ + pagination, + }); + navigate(link); + } + }; + const columns: TableProps['columns'] = [ { title: '序号', @@ -278,10 +294,12 @@ function ServiceInfo() { }, { title: '模型版本', - dataIndex: ['model', 'show_value'], + dataIndex: ['model', 'showValue'], key: 'model', width: '20%', - render: tableCellRender(true), + render: tableCellRender(true, TableCellValueType.Link, { + onClick: gotoModel, + }), }, { title: '镜像版本', diff --git a/react-ui/src/pages/ModelDeployment/components/VersionBasicInfo/index.tsx b/react-ui/src/pages/ModelDeployment/components/VersionBasicInfo/index.tsx index 2656b946..47d03d61 100644 --- a/react-ui/src/pages/ModelDeployment/components/VersionBasicInfo/index.tsx +++ b/react-ui/src/pages/ModelDeployment/components/VersionBasicInfo/index.tsx @@ -1,6 +1,6 @@ import BasicInfo, { type BasicInfoData } from '@/components/BasicInfo'; import { ServiceRunStatus } from '@/enums'; -import { useComputingResource } from '@/hooks/useComputingResource'; +import { useSystemResource } from '@/hooks/useComputingResource'; import { ServiceVersionData } from '@/pages/ModelDeployment/types'; import { formatDate } from '@/utils/date'; import { formatMirror, formatModel } from '@/utils/format'; @@ -36,7 +36,7 @@ const formatEnvText = (env?: Record) => { }; function VersionBasicInfo({ info }: BasicInfoProps) { - const getResourceDescription = useComputingResource()[1]; + const getResourceDescription = useSystemResource(); const datas: BasicInfoData[] = [ { diff --git a/react-ui/src/pages/ModelDeployment/components/VersionCompareModal/index.tsx b/react-ui/src/pages/ModelDeployment/components/VersionCompareModal/index.tsx index ee92edb2..c31e4700 100644 --- a/react-ui/src/pages/ModelDeployment/components/VersionCompareModal/index.tsx +++ b/react-ui/src/pages/ModelDeployment/components/VersionCompareModal/index.tsx @@ -1,6 +1,6 @@ import KFModal from '@/components/KFModal'; import { ServiceRunStatus } from '@/enums'; -import { useComputingResource } from '@/hooks/useComputingResource'; +import { useSystemResource } from '@/hooks/useComputingResource'; import { type ServiceVersionData } from '@/pages/ModelDeployment/types'; import { getServiceVersionCompareReq } from '@/services/modelDeployment'; import { isEmpty } from '@/utils'; @@ -42,7 +42,7 @@ const formatEnvText = (env: Record) => { function VersionCompareModal({ version1, version2, ...rest }: VersionCompareModalProps) { const [compareData, setCompareData] = useState(undefined); - const getResourceDescription = useComputingResource()[1]; + const getResourceDescription = useSystemResource(); const fields: FiledType[] = useMemo( () => [ diff --git a/react-ui/src/pages/ModelDeployment/types.ts b/react-ui/src/pages/ModelDeployment/types.ts index 3423ce01..d333d9f2 100644 --- a/react-ui/src/pages/ModelDeployment/types.ts +++ b/react-ui/src/pages/ModelDeployment/types.ts @@ -34,7 +34,7 @@ export type ServiceVersionData = { path: string; identifier: string; owner: string; - show_value: string; + showValue: string; }; code_config: { // 代码配置 diff --git a/react-ui/src/pages/Pipeline/Info/index.jsx b/react-ui/src/pages/Pipeline/Info/index.jsx index 4e86dbbb..c0e0c51c 100644 --- a/react-ui/src/pages/Pipeline/Info/index.jsx +++ b/react-ui/src/pages/Pipeline/Info/index.jsx @@ -3,7 +3,7 @@ import { useStateRef } from '@/hooks/useStateRef'; import { useVisible } from '@/hooks/useVisible'; import { getWorkflowById, saveWorkflow } from '@/services/pipeline/index.js'; import themes from '@/styles/theme.less'; -import { fittingString, parseJsonText, s8 } from '@/utils'; +import { fittingString, s8 } from '@/utils'; import { to } from '@/utils/promise'; import G6 from '@antv/g6'; import { useNavigate, useParams } from '@umijs/max'; @@ -54,11 +54,20 @@ const EditPipeline = () => { const onDragEnd = (val) => { const { x, y } = val; const point = graph.getPointByClient(x, y); + + let label = val.label; + const data = graph.save(); + const nodeLabels = data.nodes.map((v) => v.label); + if (nodeLabels.includes(label)) { + label += '-' + s8(); + } + // 元模型 const model = { ...val, x: point.x, y: point.y, + label, id: val.component_name + '-' + s8(), isCluster: false, formError: true, @@ -90,24 +99,29 @@ const EditPipeline = () => { // 保存 const savePipeline = async (isBack) => { - const [globalParamRes, globalParamError] = await to(paramsDrawerRef.current.validateFields()); - if (globalParamError) { - message.error('全局参数配置有误'); - openParamsDrawer(); - return; - } - closeParamsDrawer(); + // 验证全局参数 + // 现在改为关闭的时候就验证了 + // const [globalParamRes, globalParamError] = await to(paramsDrawerRef.current.validateFields()); + // if (globalParamError) { + // message.error('全局参数配置有误'); + // openParamsDrawer(); + // return; + // } + // closeParamsDrawer(); - const [propsRes, propsError] = await to(propsRef.current.validateFields()); - if (propsError) { - message.error('节点必填项必须配置'); - return; - } - propsRef.current.close(); + // 以前没有遮挡【保存】按钮时有用 + // 验证节点必填参数 + // const [propsRes, propsError] = await to(propsRef.current.validateFields()); + // if (propsError) { + // message.error('节点必填项必须配置'); + // return; + // } + // propsRef.current.close(); setTimeout(() => { const data = graph.save(); // console.log(data); + // 验证节点必填参数 const errorNode = data.nodes.find((item) => item.formError === true); if (errorNode) { message.error(`【${errorNode.label}】节点配置验证失败`); @@ -117,11 +131,25 @@ const EditPipeline = () => { } return; } + + // 验证节点名称是否有重命名 + const nodeLabels = data.nodes.map((v) => v.label); + for (let i = 0; i < nodeLabels.length; i++) { + const current = nodeLabels[i]; + for (let j = i + 1; j < nodeLabels.length; j++) { + const next = nodeLabels[j]; + if (current === next) { + message.error(`存在重名的【${current}】节点`); + return; + } + } + } + const params = { ...locationParams, name: workflowInfo?.name, - dag: JSON.stringify(data), - global_param: JSON.stringify(globalParamRes.global_param), + dag: data, + global_param: globalParam, }; saveWorkflow(params).then((ret) => { message.success('保存成功'); @@ -290,7 +318,7 @@ const EditPipeline = () => { const { global_param, dag } = res.data; setGlobalParam(global_param || []); if (dag) { - getGraphData(parseJsonText(dag)); + getGraphData(dag); } } }; @@ -299,13 +327,19 @@ const EditPipeline = () => { const openNodeDrawer = (node, validate = false) => { // 获取所有的上游节点 const parentNodes = findAllParentNodes(graph, node); - // 如果没有打开过全局参数抽屉,获取不到全局参数 - const globalParams = - paramsDrawerRef.current.getFieldsValue().global_param || globalParamRef.current; + // q全局参数 + const globalParams = globalParamRef.current; // 打开节点编辑抽屉 propsRef.current.showDrawer(node.getModel(), globalParams, parentNodes, validate); }; + // 关闭全局参数节点,获取全局参数 + const closeGlobalParamsDrawer = () => { + const { global_param } = paramsDrawerRef.current.getFieldsValue(); + setGlobalParam(global_param); + closeParamsDrawer(); + }; + // 初始化图 const initGraph = () => { const contextMenu = initMenu(); @@ -730,7 +764,7 @@ const EditPipeline = () => { ref={paramsDrawerRef} open={paramsDrawerOpen} globalParam={globalParam} - onClose={closeParamsDrawer} + onClose={closeGlobalParamsDrawer} >
); diff --git a/react-ui/src/pages/Pipeline/Info/utils.tsx b/react-ui/src/pages/Pipeline/Info/utils.tsx index 336a1354..2feb70c6 100644 --- a/react-ui/src/pages/Pipeline/Info/utils.tsx +++ b/react-ui/src/pages/Pipeline/Info/utils.tsx @@ -1,5 +1,4 @@ import { PipelineGlobalParam, PipelineNodeModelParameter } from '@/types'; -import { parseJsonText } from '@/utils'; import { Graph, INode } from '@antv/g6'; import { type MenuProps } from 'antd'; @@ -42,8 +41,7 @@ export function createMenuItems( ): MenuProps['items'] { const nodes: MenuProps['items'] = parentNodes.map((item) => { const model = item.getModel(); - const out_parameters = model.out_parameters as string | undefined | null; - const out_parametersObj = parseJsonText(out_parameters); + const out_parametersObj = model.out_parameters as Record; const outParametersList = Object.keys(out_parametersObj ?? {}); return { key: model.id as string, @@ -72,15 +70,6 @@ export function createMenuItems( } } -export function getInParameterComponent( - parameter: PipelineNodeModelParameter, -): React.ReactNode | null { - if (parameter.value) { - } - - return null; -} - // 判断是否允许输入 export function canInput(parameter: PipelineNodeModelParameter) { const { type, item_type } = parameter; @@ -89,6 +78,9 @@ export function canInput(parameter: PipelineNodeModelParameter) { (item_type === 'dataset' || item_type === 'model' || item_type === 'image' || - item_type === 'code') + item_type === 'code' || + item_type === 'remote-dataset' || + item_type === 'remote-model' || + item_type === 'remote-code') ); } diff --git a/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx b/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx index da764698..17cdeef9 100644 --- a/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx +++ b/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx @@ -1,6 +1,6 @@ import KFIcon from '@/components/KFIcon'; import { getParamComponent, getParamRules } from '@/pages/Experiment/components/AddExperimentModal'; -import { type PipelineGlobalParam } from '@/types'; +import { type PipelineGlobalParam, PipelineGlobalParamType } from '@/types'; import { to } from '@/utils/promise'; import { modalConfirm } from '@/utils/ui'; import { PlusOutlined } from '@ant-design/icons'; @@ -42,6 +42,7 @@ const GlobalParamsDrawer = forwardRef( form.setFieldValue(name, null); }; + // 处理删除 const removeParameter = (name: number, remove: (param: number) => void) => { modalConfirm({ title: '删除后,该全局参数将不可恢复', @@ -52,6 +53,16 @@ const GlobalParamsDrawer = forwardRef( }); }; + // 处理关闭 + const handleClose = async () => { + try { + await form.validateFields(); + onClose(); + } catch { + return false; + } + }; + return ( @@ -81,7 +92,7 @@ const GlobalParamsDrawer = forwardRef( {...restField} name={[name, 'param_name']} label="参数名称" - validateTrigger={[]} + dependencies={fields.map((_, i) => ['global_param', i, 'param_name'])} rules={[ { required: true, message: '请输入参数名称' }, { @@ -97,11 +108,7 @@ const GlobalParamsDrawer = forwardRef( }, ]} > - form.validateFields()} - /> +
handleTypeChange(['global_param', name, 'param_value'])} > - 字符串 - 整型 - 布尔类型 + 字符串 + 整型 + 布尔类型 void; @@ -37,14 +49,10 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete const [stagingItem, setStagingItem] = useState( {} 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([]); + const snap = useSnapshot(state); + const { setCurrentType } = snap; const afterOpenChange = async () => { if (!open) { @@ -53,11 +61,16 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete // 不管是否验证成功,都需要获取表单数据 const fields = form.getFieldsValue(); - // 保存字段顺序 - // const control_strategy = { - // ...stagingItem.control_strategy, - // ...fields.control_strategy, - // }; + // 保持原有字段和顺序 + const task_info = { + ...stagingItem.task_info, + ...fields.task_info, + }; + + const control_strategy = { + ...stagingItem.control_strategy, + ...fields.control_strategy, + }; const in_parameters = { ...stagingItem.in_parameters, @@ -68,17 +81,18 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete ...fields.out_parameters, }; - // console.log('getFieldsValue', fields); + console.log('getFieldsValue', fields); const res = { - ...stagingItem, - ...fields, - // control_strategy: JSON.stringify(control_strategy), - in_parameters: JSON.stringify(in_parameters), - out_parameters: JSON.stringify(out_parameters), + ...omit(stagingItem, ['control_strategy', 'task_info', 'in_parameters', 'out_parameters']), + ...omit(fields, ['control_strategy', 'task_info', 'in_parameters', 'out_parameters']), + task_info: task_info, + control_strategy: control_strategy, + in_parameters: in_parameters, + out_parameters: out_parameters, formError: !!error, }; - // console.log('res', res); + console.log('res', res); onFormChange(res); } }; @@ -96,19 +110,12 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete validate: boolean = false, ) { try { - const nodeData: PipelineNodeModelSerialize = { - ...model, - in_parameters: JSON.parse(model.in_parameters), - out_parameters: JSON.parse(model.out_parameters), - // control_strategy: JSON.parse(model.control_strategy), - }; - // console.log('model', nodeData); setStagingItem({ - ...nodeData, + ...model, }); form.resetFields(); form.setFieldsValue({ - ...nodeData, + ...model, }); if (validate) { form.validateFields(); @@ -120,6 +127,12 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete // 参数下拉菜单 setMenuItems(createMenuItems(params, parentNodes)); + + // 云际组件,设置 store 当前资源类型 + if (model.id.startsWith('remote-task')) { + const resourceType = model.in_parameters['--resource_type'].value; + setCurrentType(resourceType); + } }, close: () => { onClose(); @@ -137,7 +150,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete } }, }), - [form, open], + [form, open, setCurrentType], ); // ref 类型选择 @@ -145,7 +158,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete formItemName: NamePath, item: PipelineNodeModelParameter | Pick, ) => { - if (item.item_type === 'code') { + if (item.item_type === 'code' || item.item_type === 'remote-code') { selectCodeConfig(formItemName, item); } else { selectResource(formItemName, item); @@ -157,47 +170,15 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete formItemName: NamePath, item: PipelineNodeModelParameter | Pick, ) => { - const jsonValue = form.getFieldValue(formItemName)?.value; - const fieldValue = parseJsonText(jsonValue) as ServerCodeData; - const defaultSelected = fieldValue - ? { - id: fieldValue.id, - code_repo_name: fieldValue.name, - git_url: fieldValue.code_path, - git_branch: fieldValue.branch, - git_user_name: fieldValue.username, - git_password: fieldValue.password, - ssh_key: fieldValue.ssh_private_key, - is_public: fieldValue.is_public, - } - : undefined; + const defaultSelected = form.getFieldValue(formItemName)?.value as CodeConfigData; const { close } = openAntdModal(CodeSelectorModal, { defaultSelected, onOk: (res) => { if (res) { - const { - id, - code_repo_name, - git_url, - git_branch, - git_user_name, - git_password, - ssh_key, - is_public, - } = res; - const value = JSON.stringify({ - id, - name: code_repo_name, - code_path: git_url, - branch: git_branch, - username: git_user_name, - password: git_password, - ssh_private_key: ssh_key, - is_public, - }); + const { code_repo_name } = res; form.setFieldValue(formItemName, { ...item, - value, + value: res, showValue: code_repo_name, fromSelect: true, }); @@ -216,9 +197,11 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete let type: ResourceSelectorType; switch (item.item_type) { case 'dataset': + case 'remote-dataset': type = ResourceSelectorType.Dataset; break; case 'model': + case 'remote-model': type = ResourceSelectorType.Model; break; default: @@ -237,36 +220,24 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete onOk: (res) => { if (res) { if (type === ResourceSelectorType.Mirror) { - const { activeTab, id, version, path } = res; - if (formItemName === 'image') { - // 单独的选择镜像 - form.setFieldValue(formItemName, path); - } else { - // 输入参数选择镜像 - form.setFieldValue(formItemName, { - ...item, - value: path, - showValue: path, - fromSelect: true, - activeTab, - expandedKeys: [id], - checkedKeys: [`${id}-${version}`], - }); - } - } else { - const { activeTab, id, name, version, path, identifier, owner } = res; - const value = JSON.stringify({ - id, - name, - version, - path, - identifier, - owner, + const { activeTab, ...rest } = res; + const { url, id, image_id } = rest; + form.setFieldValue(formItemName, { + ...item, + value: rest, + showValue: url, + fromSelect: true, + activeTab, + expandedKeys: [`${image_id}`], + checkedKeys: [`${image_id}-${id}`], }); + } else { + const { activeTab, ...rest } = res; + const { id, name, version } = rest; const showValue = `${name}:${version}`; form.setFieldValue(formItemName, { ...item, - value, + value: rest, showValue, fromSelect: true, activeTab, @@ -275,19 +246,15 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete }); } } else { - if (type === ResourceSelectorType.Mirror && formItemName === 'image') { - form.setFieldValue(formItemName, undefined); - } else { - form.setFieldValue(formItemName, { - ...item, - value: undefined, - showValue: undefined, - fromSelect: false, - activeTab: undefined, - expandedKeys: [], - checkedKeys: [], - }); - } + form.setFieldValue(formItemName, { + ...item, + value: undefined, + showValue: undefined, + fromSelect: false, + activeTab: undefined, + expandedKeys: [], + checkedKeys: [], + }); } form.validateFields([formItemName]); close(); @@ -298,14 +265,14 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete // 获取选择数据集、模型后面按钮 icon const getSelectBtnIcon = (item: { item_type: string }) => { const type = item.item_type; - if (type === 'code') { + if (type === 'code' || type === 'remote-code') { return ; } let selectorType: ResourceSelectorType; - if (type === 'dataset') { + if (type === 'dataset' || type === 'remote-dataset') { selectorType = ResourceSelectorType.Dataset; - } else if (type === 'model') { + } else if (type === 'model' || type === 'remote-model') { selectorType = ResourceSelectorType.Model; } else { selectorType = ResourceSelectorType.Mirror; @@ -323,16 +290,16 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete // form item label const getLabel = ( item: { key: string; value: PipelineNodeModelParameter }, - namePrefix: string, + parentName: string, ) => { - return item.value.type === 'select' ? ( + return item.value.type === ComponentType.Select || item.value.type === ComponentType.Map ? ( item.value.label + '(' + item.key + ')' ) : ( { - handleParameterClick([namePrefix, item.key], { + handleParameterClick([parentName, item.key], { ...item.value, value, fromSelect: true, @@ -380,21 +347,249 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete return rules; }; + // 云际组件,选择类型后,重置镜像和资源,获取镜像、资源列表 + const handleParameterSelect = ( + value: ParameterSelectObject, + itemType: string, + parentName: string, + ) => { + if (itemType === 'remote-resource-type') { + snap.setCurrentType(value.value); + const remoteImage = form.getFieldValue([parentName, '--image']); + form.setFieldValue([parentName, '--image'], { ...remoteImage, value: undefined }); + const remoteResource = form.getFieldValue([parentName, '--resource']); + form.setFieldValue([parentName, '--resource'], { ...remoteResource, value: undefined }); + } + }; + + // 表单组件 + const getFormComponent = ( + item: { key: string; value: PipelineNodeModelParameter }, + parentName: string, + ) => { + return ( + <> + {item.value.type === ComponentType.Ref && ( + + + + + + + + + )} + {item.value.type === ComponentType.Select && + (ParameterSelectTypeList.includes(item.value.item_type as ParameterSelectDataType) ? ( + + + handleParameterSelect( + value as ParameterSelectObject, + item.value.item_type, + parentName, + ) + } + /> + + ) : null)} + {item.value.type === ComponentType.Map && ( + + + {(fields, { add, remove }) => ( + <> + {fields.map(({ key, name, ...restField }, index) => ( + + [ + parentName, + item.key, + 'value', + i, + 'name', + ])} + rules={[ + { + validator: (_, value) => { + if (!value) { + return Promise.reject(new Error('请输入变量名')); + } + if (!/^[a-zA-Z_][a-zA-Z0-9_-]*$/.test(value)) { + return Promise.reject( + new Error( + '变量名只支持字母、数字、下划线、中横线并且必须以字母或下划线开头', + ), + ); + } + // 判断不能重名 + const list = form + .getFieldValue([parentName, item.key, 'value']) + .filter( + (item: FormListVariable | undefined) => + item !== undefined && item !== null, + ); + + const names = list.map((item: FormListVariable) => item.name); + if (new Set(names).size !== names.length) { + return Promise.reject(new Error('变量名不能重复')); + } + return Promise.resolve(); + }, + }, + ]} + > + + + = + + {/* */} + { + handleParameterClick( + [parentName, item.key, 'value', name, 'value'], + { + ...item.value, + value, + fromSelect: true, + showValue: value, + }, + ); + }} + /> + } + > + + + + {index === fields.length - 1 && ( + + )} + + + ))} + {fields.length === 0 && ( + + )} + + )} + + + )} + {item.value.type === ComponentType.Str && ( + + + + )} + + ); + }; + + // 基本参数 + const basicParametersList = Object.entries(stagingItem.task_info ?? {}) + .map(([key, value]) => ({ + key, + value, + })) + .filter((v) => v.value.visible === true); + // 控制策略 - // const controlStrategyList = Object.entries(stagingItem.control_strategy ?? {}).map( - // ([key, value]) => ({ key, value }), - // ); + const controlStrategyList = Object.entries(stagingItem.control_strategy ?? {}) + .map(([key, value]) => ({ key, value })) + .filter((v) => v.value.visible === true); // 输入参数 - const inParametersList = Object.entries(stagingItem.in_parameters ?? {}).map(([key, value]) => ({ - key, - value, - })); + const inParametersList = Object.entries(stagingItem.in_parameters ?? {}) + .map(([key, value]) => ({ + key, + value, + })) + .filter((v) => v.value.visible === true); // 输出参数 - const outParametersList = Object.entries(stagingItem.out_parameters ?? {}).map( - ([key, value]) => ({ key, value }), - ); + const outParametersList = Object.entries(stagingItem.out_parameters ?? {}) + .map(([key, value]) => ({ key, value })) + .filter((v) => v.value.visible === true); return ( - {hasTaskInfo && ( - <> -
- -
- -
- - - - - - -
-
- { - handleParameterClick('working_directory', value); - }} - /> - } - > - - - { - handleParameterClick('command', value); - }} - /> - } - > -