diff --git a/react-ui/config/proxy.ts b/react-ui/config/proxy.ts index 50acb581..3b7a8381 100644 --- a/react-ui/config/proxy.ts +++ b/react-ui/config/proxy.ts @@ -22,8 +22,8 @@ export default { // 要代理的地址 // target: 'http://172.20.32.197:31213', // 开发环境 // target: 'http://172.20.32.235:31213', // 测试环境 - target: 'http://172.20.32.44:8082', - // target: 'http://172.20.32.150:8082', + // target: 'http://172.20.32.44:8082', + target: 'http://172.20.32.164:8082', // 配置了这个可以从 http 代理到 https // 依赖 origin 的功能可能需要这个,比如 cookie changeOrigin: true, diff --git a/react-ui/config/routes.ts b/react-ui/config/routes.ts index ec2d251f..199769fe 100644 --- a/react-ui/config/routes.ts +++ b/react-ui/config/routes.ts @@ -417,6 +417,18 @@ export default [ }, ], }, + { + name: '知识图谱', + path: 'knowledge', + routes: [ + { + name: '知识图谱', + path: '', + key: 'knowledge', + component: './Knowledge/index', + }, + ], + }, ], }, { @@ -583,18 +595,6 @@ export default [ }, ], }, - { - name: '知识图谱', - path: '/knowledge', - routes: [ - { - name: '知识图谱', - path: '', - key: 'knowledge', - component: './Knowledge/index', - }, - ], - }, { path: '*', layout: false, 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 21710bca..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", @@ -35,7 +35,7 @@ "serve": "umi-serve", "start": "cross-env UMI_ENV=dev max dev", "start:dev": "cross-env REACT_APP_ENV=dev MOCK=none UMI_ENV=dev UMI_DEV_SERVER_COMPRESS=none max dev", - "start:mock": "cross-env REACT_APP_ENV=dev UMI_ENV=dev max dev", + "start:mock": "cross-env REACT_APP_ENV=dev UMI_ENV=dev UMI_DEV_SERVER_COMPRESS=none max dev", "start:pre": "cross-env REACT_APP_ENV=pre UMI_ENV=dev max dev", "start:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=dev max dev", "storybook": "storybook dev -p 6006", diff --git a/react-ui/src/app.tsx b/react-ui/src/app.tsx index 91b0389b..c0ea08c5 100644 --- a/react-ui/src/app.tsx +++ b/react-ui/src/app.tsx @@ -21,7 +21,7 @@ import { } from './services/session'; import './styles/menu.less'; import { needAuth } from './utils'; -// import { closeAllModals } from './utils/modal'; +import { closeAllModals } from './utils/modal'; import { gotoLoginPage } from './utils/ui'; export { requestConfig as request } from './requestConfig'; @@ -30,15 +30,14 @@ export { requestConfig as request } from './requestConfig'; */ export async function getInitialState(): Promise { const fetchUserInfo = async () => { + globalGetSeverTime(); try { - globalGetSeverTime(); const response = await getUserInfo(); return { ...response.user, avatar: response.user.avatar || require('@/assets/img/avatar-default.png'), permissions: response.permissions, - roles: response.roles, - roleNames: response.user.roles, + roleNames: response.roles, } as API.CurrentUser; } catch (error) { console.error('getInitialState', error); @@ -47,11 +46,8 @@ export async function getInitialState(): Promise { return undefined; }; - // 如果不是登录页面,执行 - const { location } = history; - - // console.log('getInitialState', needAuth(location.pathname)); - if (needAuth(location.pathname)) { + const token = getAccessToken(); + if (token) { const currentUser = await fetchUserInfo(); return { fetchUserInfo, @@ -72,9 +68,6 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { return { ErrorBoundary: ErrorBoundary, rightContentRender: false, - waterMarkProps: { - // content: initialState?.currentUser?.nickName, - }, menu: { locale: false, // 每当 initialState?.currentUser?.userid 发生修改时重新执行 request @@ -85,46 +78,9 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { if (!initialState?.currentUser?.userId) { return []; } - // console.log('get menus') - // initialState.currentUser 中包含了所有用户信息 - // console.log('get routers') - // setInitialState((preInitialState) => ({ - // ...preInitialState, - // menus, - // })); return getRemoteMenu(); }, }, - onPageChange: () => { - const { location } = history; - // closeAllModals(); - // 如果没有登录,重定向到 login - if (!initialState?.currentUser && needAuth(location.pathname)) { - gotoLoginPage(); - } - }, - layoutBgImgList: [ - { - src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/D2LWSqNny4sAAAAAAAAAAAAAFl94AQBr', - left: 85, - bottom: 100, - height: '303px', - }, - { - src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/C2TWRpJpiC0AAAAAAAAAAAAAFl94AQBr', - bottom: -68, - right: -45, - height: '303px', - }, - { - src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/F6vSTbj8KpYAAAAAAAAAAAAAFl94AQBr', - bottom: 0, - left: 0, - width: '331px', - }, - ], - // 自定义 403 页面 - // unAccessible:
unAccessible
, childrenRender: (children) => { // 增加一个 loading 的状态 // if (initialState?.loading) return ; @@ -161,9 +117,26 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { }; export const onRouteChange: RuntimeConfig['onRouteChange'] = async (e) => { + // console.log('onRouteChange'); + + // 路由切换时,尤其是回退时,关闭打开的弹框 + closeAllModals(); + const { location } = e; + const token = getAccessToken(); + // 没有 token,跳转到登录页面 + if (!token && needAuth(location.pathname)) { + gotoLoginPage(); + return; + } + + // 有 token, 登录页面直接跳转到首页 + if (token && !needAuth(location.pathname)) { + history.push('/'); + } + const menus = getRemoteMenu(); - // console.log('onRouteChange', menus); + // 没有菜单,刷新页面 if (menus === null && needAuth(location.pathname)) { history.go(0); } @@ -181,10 +154,12 @@ export const patchClientRoutes: RuntimeConfig['patchClientRoutes'] = (e) => { export function render(oldRender: () => void) { // console.log('render'); const token = getAccessToken(); - if (!token || token?.length === 0) { + if (!token) { oldRender(); return; } + + // 有 token,获取路由 getRoutersInfo() .then((res) => { setRemoteMenu(res); diff --git a/react-ui/src/components/CodeConfigItem/index.less b/react-ui/src/components/CodeConfigItem/index.less index ce6cb059..bc450f47 100644 --- a/react-ui/src/components/CodeConfigItem/index.less +++ b/react-ui/src/components/CodeConfigItem/index.less @@ -1,11 +1,22 @@ .code-config-item { position: relative; - width: calc(25% - 7.5px); + width: calc(33.33% - 7px); padding: 15px; background-color: .addAlpha(@primary-color, 0.04) []; border: 1px solid transparent; border-radius: 4px; - cursor: pointer; + + &__checkbox { + flex: 1; + min-width: 0; + + :global { + .ant-checkbox + span { + flex: 1; + min-width: 0; + } + } + } &__name { margin-right: 8px; @@ -38,6 +49,8 @@ margin-bottom: 10px !important; color: @text-color-secondary; font-size: 13px; + cursor: pointer; + word-break: break-all; } &__branch { @@ -46,11 +59,17 @@ } &:hover { + background-color: .addAlpha(@primary-color, 0.08) []; + } + + &--active { border-color: @primary-color; box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1); } - &:hover &__name { + &--active &__name { color: @primary-color; } + + } diff --git a/react-ui/src/components/CodeConfigItem/index.tsx b/react-ui/src/components/CodeConfigItem/index.tsx index 942fa203..afe3e6ee 100644 --- a/react-ui/src/components/CodeConfigItem/index.tsx +++ b/react-ui/src/components/CodeConfigItem/index.tsx @@ -1,25 +1,51 @@ import { type CodeConfigData } from '@/pages/CodeConfig/List'; -import { Flex, Typography } from 'antd'; +import { getGitUrl } from '@/utils'; +import { Checkbox, Flex, Typography } from 'antd'; +import { type CheckboxChangeEvent } from 'antd/es/checkbox'; import classNames from 'classnames'; import { useState } from 'react'; import styles from './index.less'; type CodeConfigItemProps = { item: CodeConfigData; - onClick?: (item: CodeConfigData) => void; + checked: boolean; + onChange?: (item: CodeConfigData, checked: boolean) => void; }; -function CodeConfigItem({ item, onClick }: CodeConfigItemProps) { +function CodeConfigItem({ item, checked, onChange }: CodeConfigItemProps) { const [isEllipsis, setIsEllipsis] = useState(false); + + const openProject = (e: React.MouseEvent) => { + e.stopPropagation(); + const { git_url, git_branch } = item; + const url = getGitUrl(git_url, git_branch); + window.open(url, '_blank'); + }; + + const handleChange = (e: CheckboxChangeEvent) => { + onChange?.(item, e.target.checked); + }; + return ( -
onClick?.(item)}> +
- - {item.code_repo_name} - + + {item.code_repo_name} + +
setIsEllipsis(ellipsis), + tooltip: isEllipsis ? item.git_url : false, + onEllipsis: (ellipsis) => setIsEllipsis(ellipsis), // 必须这样,不然不能省略 }} + onClick={openProject} > {item.git_url} diff --git a/react-ui/src/components/CodeSelect/index.tsx b/react-ui/src/components/CodeSelect/index.tsx index 059d857f..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,7 +18,9 @@ export { type ParameterInputValue, } from '../ParameterInput'; -type CodeSelectProps = ParameterInputProps; +export interface CodeSelectProps extends ParameterInputProps { + value?: CodeConfigData; +} /** 代码配置选择表单组件 */ function CodeSelect({ @@ -32,26 +34,18 @@ function CodeSelect({ }: CodeSelectProps) { // 选择代码配置 const selectResource = () => { + 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 } = - 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, - }; - 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/CodeSelectorModal/index.less b/react-ui/src/components/CodeSelectorModal/index.less index 82d73ff8..eaff45d7 100644 --- a/react-ui/src/components/CodeSelectorModal/index.less +++ b/react-ui/src/components/CodeSelectorModal/index.less @@ -17,6 +17,7 @@ margin-bottom: 30px; overflow-x: hidden; overflow-y: auto; + padding-bottom: 10px; } &__empty { diff --git a/react-ui/src/components/CodeSelectorModal/index.tsx b/react-ui/src/components/CodeSelectorModal/index.tsx index 430971e6..afb68aec 100644 --- a/react-ui/src/components/CodeSelectorModal/index.tsx +++ b/react-ui/src/components/CodeSelectorModal/index.tsx @@ -7,7 +7,8 @@ import KFIcon from '@/components/KFIcon'; import KFModal from '@/components/KFModal'; import { type CodeConfigData } from '@/pages/CodeConfig/List'; -import { getCodeConfigListReq } from '@/services/codeConfig'; +import { getCodeConfigListReq, getCodeConfigPageNumReq } from '@/services/codeConfig'; +import { CustomPartial } from '@/types'; import { to } from '@/utils/promise'; import type { ModalProps, PaginationProps } from 'antd'; import { Empty, Input, Pagination } from 'antd'; @@ -17,24 +18,68 @@ import './index.less'; export { type CodeConfigData }; +export type SelectCodeData = CustomPartial< + CodeConfigData, + | 'id' + | 'code_repo_name' + | 'git_url' + | 'git_branch' + | 'git_user_name' + | 'git_password' + | 'ssh_key' + | 'is_public' +>; + export interface CodeSelectorModalProps extends Omit { - onOk?: (params: CodeConfigData | undefined) => void; + defaultSelected?: SelectCodeData; + onOk?: (params: SelectCodeData | undefined) => void; } /** 选择代码配置的弹窗,推荐使用函数的方式打开 */ -function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) { +function CodeSelectorModal({ defaultSelected, onOk, ...rest }: CodeSelectorModalProps) { + const DefaultPageSize = 18; const [dataList, setDataList] = useState([]); const [total, setTotal] = useState(0); - const [pagination, setPagination] = useState({ - current: 1, - pageSize: 20, - }); const [searchText, setSearchText] = useState(undefined); const [inputText, setInputText] = useState(undefined); + const [selected, setSelected] = useState(defaultSelected); + const [isScrolled, setIsScrolled] = useState(false); + const [pagination, setPagination] = useState({ + current: defaultSelected?.id ? 0 : 1, // 为 0 时,不请求,等待接口返回选中的代码配置在第几页 + pageSize: DefaultPageSize, + }); + + useEffect(() => { + const getCodeConfigPageNum = async (id: number, size: number) => { + const [res] = await to( + getCodeConfigPageNumReq(id, { + size, + }), + ); + if (res) { + setPagination({ + current: typeof res.data === 'number' ? Math.max(0, res.data) + 1 : 1, + pageSize: DefaultPageSize, + }); + } else { + setPagination({ + current: 1, + pageSize: DefaultPageSize, + }); + } + }; + if (defaultSelected?.id) { + getCodeConfigPageNum(defaultSelected?.id, DefaultPageSize); + } + }, [defaultSelected?.id]); useEffect(() => { // 获取数据请求 const getDataList = async () => { + // 为 0 时,不请求,等待接口返回选中的代码配置在第几页 + if (pagination.current === 0) { + return; + } const params = { page: pagination.current! - 1, size: pagination.pageSize, @@ -50,6 +95,16 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) { getDataList(); }, [pagination, searchText]); + useEffect(() => { + if (dataList.length > 0 && !isScrolled && defaultSelected?.id) { + const selectedItem = document.getElementById(`code-config-item-${defaultSelected?.id}`); + if (selectedItem) { + selectedItem.scrollIntoView(); + } + setIsScrolled(true); + } + }, [isScrolled, dataList, defaultSelected?.id]); + // 搜索 const handleSearch = (value: string) => { setSearchText(value); @@ -59,8 +114,12 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) { })); }; - const handleClick = (item: CodeConfigData) => { - onOk?.(item); + const handleChange = (item: CodeConfigData, checked: boolean) => { + if (checked) { + setSelected(item); + } else { + setSelected(undefined); + } }; // 分页切换 @@ -77,7 +136,7 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) { title="选择代码配置" image={require('@/assets/img/modal-code-config.png')} width={920} - footer={null} + onOk={() => onOk?.(selected)} destroyOnClose >
@@ -93,23 +152,31 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) { prefix={ } - // prefix={ - // - // } /> {dataList?.length !== 0 ? ( <>
{dataList?.map((item) => ( - + ))}
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..722c174c 100644 --- a/react-ui/src/components/ParameterSelect/config.tsx +++ b/react-ui/src/components/ParameterSelect/config.tsx @@ -1,26 +1,18 @@ 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 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']; // 过滤函数 + getValue: (value: any) => string | number; + getLabel: (value: any) => string; + isObjectValue: boolean; // value 是对象 }; export const paramSelectConfig: Record = { @@ -31,13 +23,16 @@ export const paramSelectConfig: Record = { size: 1000, is_public: false, }); - return res?.data?.content?.map(convertId) ?? []; + return res?.data?.content ?? []; + }, + optionFilterProp: 'label', + getValue: (value: DatasetData) => { + return value.id; }, - fieldNames: { - label: 'name', - value: 'id', + getLabel: (value: DatasetData) => { + return value.name; }, - optionFilterProp: 'name', + isObjectValue: true, }, model: { getOptions: async () => { @@ -46,13 +41,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: ModelData) => { + return value.id; + }, + getLabel: (value: ModelData) => { + return value.name; }, - optionFilterProp: 'name', + isObjectValue: true, }, service: { getOptions: async () => { @@ -60,25 +58,28 @@ 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'], + // 不会用到 + getValue: () => { + return ''; + }, + // 不会用的 + getLabel: () => { + return ''; + }, + isObjectValue: false, }, }; diff --git a/react-ui/src/components/ParameterSelect/index.tsx b/react-ui/src/components/ParameterSelect/index.tsx index c9a78069..3c1ab102 100644 --- a/react-ui/src/components/ParameterSelect/index.tsx +++ b/react-ui/src/components/ParameterSelect/index.tsx @@ -7,7 +7,7 @@ import { useComputingResource } from '@/hooks/useComputingResource'; import { to } from '@/utils/promise'; import { Select, type SelectProps } from 'antd'; -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import FormInfo from '../FormInfo'; import { paramSelectConfig } from './config'; @@ -36,69 +36,106 @@ function ParameterSelect({ dataType, display = false, value, - isPipeline = false, + // isPipeline = false, onChange, ...rest }: ParameterSelectProps) { const [options, setOptions] = useState([]); const propsConfig = paramSelectConfig[dataType]; - const valueText = typeof value === 'object' && value !== null ? value.value : value; + const { + getLabel, + getValue, + 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 [resourceStandardList] = useComputingResource(); - const computingResource = isPipeline - ? resourceStandardList.map((v) => ({ - ...v, - id: String(v.id), - })) - : resourceStandardList; + // const computingResource = isPipeline + // ? resourceStandardList.map((v) => ({ + // ...v, + // id: String(v.id), + // })) + // : resourceStandardList; + + const objectOptions = useMemo(() => { + return options?.map((v) => ({ + label: getLabel(v), + value: getValue(v), + })); + }, [getLabel, getValue, options]); + + const valueMap = useMemo(() => { + const map = new Map(); + options?.forEach((v) => { + map.set(getValue(v), v); + }); + + return map; + }, [options, 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); + } } }; getSelectOptions(); - }, [propsConfig]); + }, [getOptions]); - const selectOptions = dataType === 'resource' ? computingResource : options; + const selectOptions = dataType === 'resource' ? resourceStandardList : objectOptions; 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 ( form.validateFields()} - /> + handleTypeChange(['global_param', name, 'param_value'])} > - 字符串 - 整型 - 布尔类型 + 字符串 + 整型 + 布尔类型 void; @@ -35,12 +43,6 @@ 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([]); @@ -51,11 +53,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, @@ -66,17 +73,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); } }; @@ -94,19 +102,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(); @@ -155,23 +156,15 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete formItemName: NamePath, item: PipelineNodeModelParameter | Pick, ) => { + 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 } = - 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, - }); + const { code_repo_name } = res; form.setFieldValue(formItemName, { ...item, - value, + value: res, showValue: code_repo_name, fromSelect: true, }); @@ -211,36 +204,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, @@ -249,19 +230,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(); @@ -297,16 +274,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, @@ -354,21 +331,228 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete return rules; }; + // 表单组件 + const getFormComponent = ( + item: { key: string; value: PipelineNodeModelParameter }, + parentName: string, + ) => { + return ( + <> + {item.value.type === ComponentType.Ref && ( + + + + + + + + + )} + {item.value.type === ComponentType.Select && + (['dataset', 'model', 'service', 'resource'].includes(item.value.item_type) ? ( + + + + ) : 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); - }} - /> - } - > -