From 2d3a3dabcd5367b84d03aaff4b8928af1fb5bb9e Mon Sep 17 00:00:00 2001 From: zhaowei Date: Tue, 22 Apr 2025 10:52:05 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=87=AA=E5=8A=A8=E5=AD=A6=E4=B9=A0?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E6=94=B9=E6=88=90=E8=B6=85=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E5=AF=BB=E4=BC=98=E7=9A=84=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/ActiveLearn/Instance/index.tsx | 9 ++- .../components/ActiveLearnBasic/index.tsx | 6 +- .../components/CreateForm/BasicConfig.tsx | 4 ++ .../components/ExperimentLog/index.tsx | 4 +- react-ui/src/pages/AutoML/Instance/index.tsx | 61 +++++++++---------- .../AutoML/components/AutoMLBasic/index.tsx | 6 +- .../components/CreateForm/BasicConfig.tsx | 4 ++ .../components/ExperimentLog/index.less | 7 +++ .../AutoML/components/ExperimentLog/index.tsx | 37 +++++++++++ .../pages/HyperParameter/Instance/index.tsx | 7 ++- .../components/CreateForm/BasicConfig.tsx | 4 ++ .../components/ExperimentLog/index.tsx | 4 +- .../components/HyperParameterBasic/index.tsx | 6 +- 13 files changed, 112 insertions(+), 47 deletions(-) diff --git a/react-ui/src/pages/ActiveLearn/Instance/index.tsx b/react-ui/src/pages/ActiveLearn/Instance/index.tsx index e2ec79a1..1e930ae3 100644 --- a/react-ui/src/pages/ActiveLearn/Instance/index.tsx +++ b/react-ui/src/pages/ActiveLearn/Instance/index.tsx @@ -27,7 +27,7 @@ const NodePrefix = 'workflow'; function ActiveLearnInstance() { const [experimentInfo, setExperimentInfo] = useState(undefined); const [instanceInfo, setInstanceInfo] = useState(undefined); - // 超参数寻优运行有3个节点,运行状态取工作流状态,而不是 auto-hpo 节点状态 + // 主动学习运行有3个节点,运行状态取工作流状态,而不是 active-learn 节点状态 const [workflowStatus, setWorkflowStatus] = useState(undefined); const [nodes, setNodes] = useState | undefined>(undefined); const params = useParams(); @@ -49,11 +49,14 @@ function ActiveLearnInstance() { const [res] = await to(getActiveLearnInsReq(instanceId)); if (res && res.data) { const info = res.data as ActiveLearnInstanceData; - const { param, node_status, argo_ins_name, argo_ins_ns, status } = info; + const { param, node_status, argo_ins_name, argo_ins_ns, status, create_time } = info; // 解析配置参数 const paramJson = parseJsonText(param); if (paramJson) { - setExperimentInfo(paramJson.data); + setExperimentInfo({ + ...paramJson.data, + create_time, + }); } setInstanceInfo(info); diff --git a/react-ui/src/pages/ActiveLearn/components/ActiveLearnBasic/index.tsx b/react-ui/src/pages/ActiveLearn/components/ActiveLearnBasic/index.tsx index 46508069..2ea0cb8f 100644 --- a/react-ui/src/pages/ActiveLearn/components/ActiveLearnBasic/index.tsx +++ b/react-ui/src/pages/ActiveLearn/components/ActiveLearnBasic/index.tsx @@ -206,19 +206,19 @@ function BasicInfo({ info, className, runStatus, isInstance = false }: BasicInfo }, [info, getResourceDescription]); const instanceDatas = useMemo(() => { - if (!runStatus) { + if (!info || !runStatus) { return []; } return [ { label: '启动时间', - value: formatDate(runStatus.startedAt), + value: formatDate(info.create_time), ellipsis: true, }, { label: '执行时长', - value: elapsedTime(runStatus.startedAt, runStatus.finishedAt), + value: elapsedTime(info.create_time, runStatus.finishedAt), ellipsis: true, }, { diff --git a/react-ui/src/pages/ActiveLearn/components/CreateForm/BasicConfig.tsx b/react-ui/src/pages/ActiveLearn/components/CreateForm/BasicConfig.tsx index 8829f12a..9d06fd10 100644 --- a/react-ui/src/pages/ActiveLearn/components/CreateForm/BasicConfig.tsx +++ b/react-ui/src/pages/ActiveLearn/components/CreateForm/BasicConfig.tsx @@ -19,6 +19,10 @@ function BasicConfig() { required: true, message: '请输入实验名称', }, + { + max: 64, + message: '实验名称不能超过64个字符', + }, ]} > diff --git a/react-ui/src/pages/ActiveLearn/components/ExperimentLog/index.tsx b/react-ui/src/pages/ActiveLearn/components/ExperimentLog/index.tsx index eb026241..1d9682c3 100644 --- a/react-ui/src/pages/ActiveLearn/components/ExperimentLog/index.tsx +++ b/react-ui/src/pages/ActiveLearn/components/ExperimentLog/index.tsx @@ -5,6 +5,8 @@ import { NodeStatus } from '@/types'; import { Tabs } from 'antd'; import styles from './index.less'; +const NodePrefix = 'active-learn'; + type ExperimentLogProps = { instanceInfo: ActiveLearnInstanceData; nodes: Record; @@ -23,7 +25,7 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) { }) .forEach((key) => { const node = nodes[key]; - if (node.displayName.startsWith('active-learn')) { + if (node.displayName.startsWith(NodePrefix)) { hpoNodeStatus = node; } else if (node.displayName.startsWith('git-clone') && !frameworkCloneNodeStatus) { frameworkCloneNodeStatus = node; diff --git a/react-ui/src/pages/AutoML/Instance/index.tsx b/react-ui/src/pages/AutoML/Instance/index.tsx index 933b496e..1bb5279a 100644 --- a/react-ui/src/pages/AutoML/Instance/index.tsx +++ b/react-ui/src/pages/AutoML/Instance/index.tsx @@ -1,6 +1,5 @@ import KFIcon from '@/components/KFIcon'; import { AutoMLTaskType, ExperimentStatus } from '@/enums'; -import LogList from '@/pages/Experiment/components/LogList'; import { getExperimentInsReq } from '@/services/autoML'; import { NodeStatus } from '@/types'; import { parseJsonText } from '@/utils'; @@ -11,6 +10,7 @@ import { Tabs } from 'antd'; import { useEffect, useRef, useState } from 'react'; import AutoMLBasic from '../components/AutoMLBasic'; import ExperimentHistory from '../components/ExperimentHistory'; +import ExperimentLog from '../components/ExperimentLog'; import ExperimentResult from '../components/ExperimentResult'; import { AutoMLData, AutoMLInstanceData } from '../types'; import styles from './index.less'; @@ -22,11 +22,13 @@ enum TabKeys { History = 'history', } -const NodePrefix = 'auto-ml'; +const NodePrefix = 'workflow'; function AutoMLInstance() { const [autoMLInfo, setAutoMLInfo] = useState(undefined); const [instanceInfo, setInstanceInfo] = useState(undefined); + const [workflowStatus, setWorkflowStatus] = useState(undefined); + const [nodes, setNodes] = useState | undefined>(undefined); const params = useParams(); const instanceId = safeInvoke(Number)(params.id); const evtSourceRef = useRef(null); @@ -46,34 +48,39 @@ function AutoMLInstance() { const [res] = await to(getExperimentInsReq(instanceId)); if (res && res.data) { const info = res.data as AutoMLInstanceData; - const { param, node_status, argo_ins_name, argo_ins_ns, status } = info; + const { param, node_status, argo_ins_name, argo_ins_ns, status, create_time } = info; // 解析配置参数 const paramJson = parseJsonText(param); if (paramJson) { - setAutoMLInfo(paramJson); + setAutoMLInfo({ + ...paramJson, + create_time, + }); } + setInstanceInfo(info); + // 这个接口返回的状态有延时,SSE 返回的状态是最新的 // SSE 调用时,不需要解析 node_status, 也不要重新建立 SSE if (isStatusDetermined) { - setInstanceInfo((prev) => ({ - ...info, - nodeStatus: prev!.nodeStatus, - })); return; } // 进行节点状态 const nodeStatusJson = parseJsonText(node_status); if (nodeStatusJson) { - Object.keys(nodeStatusJson).forEach((key) => { + setNodes(nodeStatusJson); + // 工作流 + Object.keys(nodeStatusJson).some((key) => { if (key.startsWith(NodePrefix)) { - const value = nodeStatusJson[key]; - info.nodeStatus = value; + const workflowStatus = nodeStatusJson[key]; + setWorkflowStatus(workflowStatus); + return true; } + return false; }); } - setInstanceInfo(info); + // 运行中或者等待中,开启 SSE if (status === ExperimentStatus.Pending || status === ExperimentStatus.Running) { setupSSE(argo_ins_name, argo_ins_ns); @@ -97,19 +104,20 @@ function AutoMLInstance() { if (dataJson) { const nodes = dataJson?.result?.object?.status?.nodes; if (nodes) { - const statusData = Object.values(nodes).find((node: any) => + const workflowStatus = Object.values(nodes).find((node: any) => node.displayName.startsWith(NodePrefix), ) as NodeStatus; - if (statusData) { - setInstanceInfo((prev) => ({ - ...prev!, - nodeStatus: statusData, - })); + + // 节点 + setNodes(nodes); + + if (workflowStatus) { + setWorkflowStatus(workflowStatus); // 实验结束,关闭 SSE if ( - statusData.phase !== ExperimentStatus.Pending && - statusData.phase !== ExperimentStatus.Running + workflowStatus.phase !== ExperimentStatus.Pending && + workflowStatus.phase !== ExperimentStatus.Running ) { closeSSE(); getExperimentInsInfo(true); @@ -141,7 +149,7 @@ function AutoMLInstance() { ), @@ -152,16 +160,7 @@ function AutoMLInstance() { icon: , children: (
- {instanceInfo && instanceInfo.nodeStatus && ( - - )} + {instanceInfo && nodes && }
), }, diff --git a/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx b/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx index 5f207b7d..a12671af 100644 --- a/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx +++ b/react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx @@ -193,18 +193,18 @@ function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLB }, [info]); const instanceDatas = useMemo(() => { - if (!runStatus) { + if (!info || !runStatus) { return []; } return [ { label: '启动时间', - value: formatDate(runStatus.startedAt), + value: formatDate(info.create_time), }, { label: '执行时长', - value: elapsedTime(runStatus.startedAt, runStatus.finishedAt), + value: elapsedTime(info.create_time, runStatus.finishedAt), }, { label: '状态', diff --git a/react-ui/src/pages/AutoML/components/CreateForm/BasicConfig.tsx b/react-ui/src/pages/AutoML/components/CreateForm/BasicConfig.tsx index aec61d3f..04b88ea5 100644 --- a/react-ui/src/pages/AutoML/components/CreateForm/BasicConfig.tsx +++ b/react-ui/src/pages/AutoML/components/CreateForm/BasicConfig.tsx @@ -18,6 +18,10 @@ function BasicConfig() { required: true, message: '请输入实验名称', }, + { + max: 64, + message: '实验名称不能超过64个字符', + }, ]} > diff --git a/react-ui/src/pages/AutoML/components/ExperimentLog/index.less b/react-ui/src/pages/AutoML/components/ExperimentLog/index.less index e69de29b..beaacea3 100644 --- a/react-ui/src/pages/AutoML/components/ExperimentLog/index.less +++ b/react-ui/src/pages/AutoML/components/ExperimentLog/index.less @@ -0,0 +1,7 @@ +.experiment-log { + height: 100%; + + &__log { + height: 100%; + } +} diff --git a/react-ui/src/pages/AutoML/components/ExperimentLog/index.tsx b/react-ui/src/pages/AutoML/components/ExperimentLog/index.tsx index e69de29b..219e5782 100644 --- a/react-ui/src/pages/AutoML/components/ExperimentLog/index.tsx +++ b/react-ui/src/pages/AutoML/components/ExperimentLog/index.tsx @@ -0,0 +1,37 @@ +import { ExperimentStatus } from '@/enums'; +import { AutoMLInstanceData } from '@/pages/AutoML/types'; +import LogList from '@/pages/Experiment/components/LogList'; +import { NodeStatus } from '@/types'; +import styles from './index.less'; + +const NodePrefix = 'auto-ml'; + +type ExperimentLogProps = { + instanceInfo: AutoMLInstanceData; + nodes: Record; +}; + +function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) { + const nodeStatus: NodeStatus | undefined = Object.values(nodes).find((node: any) => + node.displayName.startsWith(NodePrefix), + ) as NodeStatus; + + return ( +
+
+ {nodeStatus && ( + + )} +
+
+ ); +} + +export default ExperimentLog; diff --git a/react-ui/src/pages/HyperParameter/Instance/index.tsx b/react-ui/src/pages/HyperParameter/Instance/index.tsx index 93b108db..a2f5ef1d 100644 --- a/react-ui/src/pages/HyperParameter/Instance/index.tsx +++ b/react-ui/src/pages/HyperParameter/Instance/index.tsx @@ -51,7 +51,7 @@ function HyperParameterInstance() { const [res] = await to(getRayInsReq(instanceId)); if (res && res.data) { const info = res.data as HyperParameterInstanceData; - const { param, node_status, argo_ins_name, argo_ins_ns, status } = info; + const { param, node_status, argo_ins_name, argo_ins_ns, status, create_time } = info; // 解析配置参数 const paramJson = parseJsonText(param); if (paramJson) { @@ -70,7 +70,10 @@ function HyperParameterInstance() { if (!Array.isArray(paramJson.points_to_evaluate)) { paramJson.points_to_evaluate = []; } - setExperimentInfo(paramJson); + setExperimentInfo({ + ...paramJson, + create_time, + }); } setInstanceInfo(info); diff --git a/react-ui/src/pages/HyperParameter/components/CreateForm/BasicConfig.tsx b/react-ui/src/pages/HyperParameter/components/CreateForm/BasicConfig.tsx index 8829f12a..9d06fd10 100644 --- a/react-ui/src/pages/HyperParameter/components/CreateForm/BasicConfig.tsx +++ b/react-ui/src/pages/HyperParameter/components/CreateForm/BasicConfig.tsx @@ -19,6 +19,10 @@ function BasicConfig() { required: true, message: '请输入实验名称', }, + { + max: 64, + message: '实验名称不能超过64个字符', + }, ]} > diff --git a/react-ui/src/pages/HyperParameter/components/ExperimentLog/index.tsx b/react-ui/src/pages/HyperParameter/components/ExperimentLog/index.tsx index 1a1257f0..d7639094 100644 --- a/react-ui/src/pages/HyperParameter/components/ExperimentLog/index.tsx +++ b/react-ui/src/pages/HyperParameter/components/ExperimentLog/index.tsx @@ -5,6 +5,8 @@ import { NodeStatus } from '@/types'; import { Tabs } from 'antd'; import styles from './index.less'; +const NodePrefix = 'auto-hpo'; + type ExperimentLogProps = { instanceInfo: HyperParameterInstanceData; nodes: Record; @@ -23,7 +25,7 @@ function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) { }) .forEach((key) => { const node = nodes[key]; - if (node.displayName.startsWith('auto-hpo')) { + if (node.displayName.startsWith(NodePrefix)) { hpoNodeStatus = node; } else if (node.displayName.startsWith('git-clone') && !frameworkCloneNodeStatus) { frameworkCloneNodeStatus = node; diff --git a/react-ui/src/pages/HyperParameter/components/HyperParameterBasic/index.tsx b/react-ui/src/pages/HyperParameter/components/HyperParameterBasic/index.tsx index 77cbff36..7c6c7948 100644 --- a/react-ui/src/pages/HyperParameter/components/HyperParameterBasic/index.tsx +++ b/react-ui/src/pages/HyperParameter/components/HyperParameterBasic/index.tsx @@ -143,19 +143,19 @@ function HyperParameterBasic({ }, [info, getResourceDescription]); const instanceDatas = useMemo(() => { - if (!runStatus) { + if (!info || !runStatus) { return []; } return [ { label: '启动时间', - value: formatDate(runStatus.startedAt), + value: formatDate(info.create_time), ellipsis: true, }, { label: '执行时长', - value: elapsedTime(runStatus.startedAt, runStatus.finishedAt), + value: elapsedTime(info.create_time, runStatus.finishedAt), ellipsis: true, }, {