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