| @@ -148,6 +148,11 @@ export default [ | |||
| path: 'compare-visual', | |||
| component: './Experiment/Aim/index', | |||
| }, | |||
| { | |||
| name: '可视化', | |||
| path: 'visual', | |||
| component: './Experiment/Tensorboard/index', | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| @@ -292,7 +292,7 @@ function ExecuteConfig() { | |||
| <Form.Item | |||
| label="集成最佳模型数量" | |||
| name="ensemble_nbest" | |||
| tooltip="仅集成最佳的N个模型" | |||
| tooltip="仅集成最佳的N个模型,必须是大于等于1的整数" | |||
| > | |||
| <InputNumber placeholder="请输入集成最佳模型数量" min={1} precision={0} /> | |||
| </Form.Item> | |||
| @@ -419,6 +419,7 @@ function ExecuteConfig() { | |||
| <Form.Item | |||
| label="交叉验证折数" | |||
| name="folds" | |||
| tooltip="交叉验证折数必须是大于等于2的整数" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| @@ -426,7 +427,7 @@ function ExecuteConfig() { | |||
| }, | |||
| ]} | |||
| > | |||
| <InputNumber placeholder="请输入交叉验证折数" min={1} precision={0} /> | |||
| <InputNumber placeholder="请输入交叉验证折数" min={2} precision={0} /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| @@ -126,7 +126,7 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||
| <Form.Item label="模型框架" name="model_type"> | |||
| <Select | |||
| allowClear | |||
| placeholder="请选择模型类型" | |||
| placeholder="请选择模型框架" | |||
| options={typeList} | |||
| fieldNames={{ label: 'name', value: 'name' }} | |||
| optionFilterProp="name" | |||
| @@ -136,7 +136,7 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||
| <Form.Item label="模型能力" name="model_tag"> | |||
| <Select | |||
| allowClear | |||
| placeholder="请选择模型标签" | |||
| placeholder="请选择模型能力" | |||
| options={tagList} | |||
| fieldNames={{ label: 'name', value: 'name' }} | |||
| optionFilterProp="name" | |||
| @@ -18,10 +18,12 @@ import { experimentStatusInfo } from '../status'; | |||
| import styles from './index.less'; | |||
| let graph = null; | |||
| const NodePrefix = 'workflow'; | |||
| function ExperimentText() { | |||
| const [experimentIns, setExperimentIns] = useState(undefined); | |||
| const [experimentNodeData, setExperimentNodeData, experimentNodeDataRef] = useStateRef(undefined); | |||
| const [workflowStatus, setWorkflowStatus] = useState(undefined); | |||
| const graphRef = useRef(); | |||
| const workflowRef = useRef(); | |||
| const locationParams = useParams(); // 新版本获取路由参数接口 | |||
| @@ -89,10 +91,22 @@ function ExperimentText() { | |||
| const { status, nodes_status, argo_ins_ns, argo_ins_name, finish_time } = res.data; | |||
| const workflowData = workflowRef.current; | |||
| const experimentStatusObjs = parseJsonText(nodes_status); | |||
| workflowData.nodes.forEach((item) => { | |||
| const experimentNode = experimentStatusObjs?.[item.id]; | |||
| updateWorkflowNode(item, experimentNode); | |||
| }); | |||
| if (experimentStatusObjs) { | |||
| workflowData.nodes.forEach((item) => { | |||
| const experimentNode = experimentStatusObjs?.[item.id]; | |||
| updateWorkflowNode(item, experimentNode); | |||
| }); | |||
| // 处理workflow状态 | |||
| Object.keys(experimentStatusObjs).some((key) => { | |||
| if (key.startsWith(NodePrefix)) { | |||
| const workflowStatus = experimentStatusObjs[key]; | |||
| setWorkflowStatus(workflowStatus); | |||
| return true; | |||
| } | |||
| return false; | |||
| }); | |||
| } | |||
| // 绘制图 | |||
| getGraphData(workflowData, true); | |||
| @@ -147,6 +161,15 @@ function ExperimentText() { | |||
| status: phase, | |||
| })); | |||
| const workflowStatus = Object.values(nodes).find((node) => | |||
| node.displayName.startsWith(NodePrefix), | |||
| ); | |||
| // 设置工作流状态 | |||
| if (workflowStatus) { | |||
| setWorkflowStatus(workflowStatus); | |||
| } | |||
| const workflowData = workflowRef.current; | |||
| workflowData.nodes.forEach((item) => { | |||
| const experimentNode = Object.values(nodes).find((node) => node.displayName === item.id); | |||
| @@ -471,13 +494,13 @@ function ExperimentText() { | |||
| <div className={styles['pipeline-container']}> | |||
| <div className={styles['pipeline-container__top']}> | |||
| <div className={styles['pipeline-container__top__info']}> | |||
| 启动时间:{formatDate(experimentIns?.create_time)} | |||
| 启动时间:{formatDate(workflowStatus?.startedAt)} | |||
| </div> | |||
| <div className={styles['pipeline-container__top__info']}> | |||
| 执行时长: | |||
| <RunDuration | |||
| createTime={experimentIns?.create_time} | |||
| finishTime={experimentIns?.finish_time} | |||
| createTime={workflowStatus?.startedAt} | |||
| finishTime={workflowStatus?.finishedAt} | |||
| /> | |||
| </div> | |||
| <div className={styles['pipeline-container__top__info']}> | |||
| @@ -488,11 +511,11 @@ function ExperimentText() { | |||
| height: '8px', | |||
| borderRadius: '50%', | |||
| marginRight: '6px', | |||
| backgroundColor: experimentStatusInfo[experimentIns?.status]?.color, | |||
| backgroundColor: experimentStatusInfo[workflowStatus?.phase]?.color, | |||
| }} | |||
| ></div> | |||
| <span style={{ color: experimentStatusInfo[experimentIns?.status]?.color }}> | |||
| {experimentStatusInfo[experimentIns?.status]?.label} | |||
| <span style={{ color: experimentStatusInfo[workflowStatus?.phase]?.color }}> | |||
| {experimentStatusInfo[workflowStatus?.phase]?.label} | |||
| </span> | |||
| </div> | |||
| <Button | |||
| @@ -0,0 +1,12 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2025-03-31 16:38:59 | |||
| * @Description: 实验可视化 Tensorboard | |||
| */ | |||
| import IframePage, { IframePageType } from '@/components/IFramePage'; | |||
| function TensorboardPage() { | |||
| return <IframePage type={IframePageType.TensorBoard}></IframePage>; | |||
| } | |||
| export default TensorboardPage; | |||
| @@ -2,6 +2,7 @@ import KFIcon from '@/components/KFIcon'; | |||
| import PageTitle from '@/components/PageTitle'; | |||
| import { ExperimentStatus, TensorBoardStatus } from '@/enums'; | |||
| import { useCacheState } from '@/hooks/useCacheState'; | |||
| import { useServerTime } from '@/hooks/useServerTime'; | |||
| import { | |||
| deleteExperimentById, | |||
| getExperiment, | |||
| @@ -17,6 +18,7 @@ import { getWorkflow } from '@/services/pipeline/index.js'; | |||
| import themes from '@/styles/theme.less'; | |||
| import { ExperimentCompleted } from '@/utils/constant'; | |||
| import { to } from '@/utils/promise'; | |||
| import SessionStorage from '@/utils/sessionStorage'; | |||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | |||
| import { modalConfirm } from '@/utils/ui'; | |||
| import { App, Button, ConfigProvider, Dropdown, Input, Space, Table, Tooltip } from 'antd'; | |||
| @@ -28,7 +30,6 @@ import AddExperimentModal from './components/AddExperimentModal'; | |||
| import ExperimentInstanceList from './components/ExperimentInstanceList'; | |||
| import styles from './index.less'; | |||
| import { experimentStatusInfo } from './status'; | |||
| import { useServerTime } from '@/hooks/useServerTime'; | |||
| // 定时器 | |||
| const timerIds = new Map(); | |||
| @@ -372,7 +373,10 @@ function Experiment() { | |||
| experimentIn.tensorBoardStatus === TensorBoardStatus.Running && | |||
| experimentIn.tensorboardUrl | |||
| ) { | |||
| window.open(experimentIn.tensorboardUrl, '_blank'); | |||
| const url = experimentIn.tensorboardUrl; | |||
| SessionStorage.setItem(SessionStorage.tensorBoardUrlKey, url); | |||
| navigateToUrl(`/pipeline/experiment/visual`); | |||
| // window.open(experimentIn.tensorboardUrl, '_blank'); | |||
| } | |||
| }; | |||
| @@ -125,6 +125,7 @@ function MirrorInfo() { | |||
| current: tableData.length === 1 ? Math.max(1, prev.current! - 1) : prev.current, | |||
| }; | |||
| }); | |||
| getMirrorInfo(); | |||
| } | |||
| }; | |||