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/ParameterSelect/index.tsx b/react-ui/src/components/ParameterSelect/index.tsx index db4d64e6..3c1ab102 100644 --- a/react-ui/src/components/ParameterSelect/index.tsx +++ b/react-ui/src/components/ParameterSelect/index.tsx @@ -110,13 +110,14 @@ function ParameterSelect({ onChange?.(selectValue); } } else { + const selectValue = text ? text : ''; if (typeof value === 'object' && value !== null) { onChange?.({ ...value, - value: text, + value: selectValue, }); } else { - onChange?.(text); + onChange?.(selectValue); } } }; diff --git a/react-ui/src/pages/AutoML/components/ExperimentList/index.tsx b/react-ui/src/pages/AutoML/components/ExperimentList/index.tsx index 7951d769..93a1562e 100644 --- a/react-ui/src/pages/AutoML/components/ExperimentList/index.tsx +++ b/react-ui/src/pages/AutoML/components/ExperimentList/index.tsx @@ -169,16 +169,16 @@ function ExperimentList({ type }: ExperimentListProps) { const handleMessage = (e: MessageEvent) => { const { type, payload } = e.data; if (type === ExperimentCompleted) { - const { experimentId, experimentInsId, status, finishTime } = payload; + 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/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.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..f6a3e0d5 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/ViewParamsModal/index.tsx b/react-ui/src/pages/Experiment/components/ViewParamsModal/index.tsx index 3ad671a4..2000d556 100644 --- a/react-ui/src/pages/Experiment/components/ViewParamsModal/index.tsx +++ b/react-ui/src/pages/Experiment/components/ViewParamsModal/index.tsx @@ -14,10 +14,10 @@ import styles from './index.less'; type ParamsModalProps = { open: boolean; onCancel: () => 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/Pipeline/components/GlobalParamsDrawer/index.tsx b/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx index fe5db656..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'; @@ -131,9 +131,9 @@ const GlobalParamsDrawer = forwardRef( handleTypeChange(['global_param', name, 'param_value'])} > - 字符串 - 整型 - 布尔类型 + 字符串 + 整型 + 布尔类型 -
- -
+ {basicParametersList.length + controlStrategyList.length > 0 && ( +
+ +
+ )} {/* 基本参数 */} {basicParametersList.map((item) => ( @@ -663,6 +665,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete ))} )} + {/* 输出参数 */} {outParametersList.length > 0 && ( <> diff --git a/react-ui/src/types.ts b/react-ui/src/types.ts index 61081b39..23f34d7b 100644 --- a/react-ui/src/types.ts +++ b/react-ui/src/types.ts @@ -29,11 +29,17 @@ export type GlobalInitialState = { clientInfo?: ClientInfo; }; +export enum PipelineGlobalParamType { + String = 1, + Number = 2, + Boolean = 3, +} + // 流水线全局参数 export type PipelineGlobalParam = { param_name: string; description: string; - param_type: number; + param_type: PipelineGlobalParamType; param_value: number | string | boolean; is_sensitive: number; };