| @@ -22,7 +22,7 @@ enum TabKeys { | |||
| History = 'history', | |||
| } | |||
| const NodePrefix = 'auto-hpo'; | |||
| const NodePrefix = 'auto-ml'; | |||
| function AutoMLInstance() { | |||
| const [activeTab, setActiveTab] = useState<string>(TabKeys.Params); | |||
| @@ -26,7 +26,7 @@ | |||
| &__text { | |||
| font-family: 'Roboto Mono', 'Menlo', 'Consolas', 'Monaco', monospace; | |||
| white-space: pre-wrap; | |||
| white-space: pre; | |||
| } | |||
| &__images { | |||
| @@ -17,6 +17,7 @@ import styles from './index.less'; | |||
| export type LogGroupProps = ExperimentLog & { | |||
| status?: ExperimentStatus; // 实验状态 | |||
| listId: string; | |||
| }; | |||
| type Log = { | |||
| @@ -25,25 +26,13 @@ type Log = { | |||
| pod_name: string; // pod名称 | |||
| }; | |||
| // 滚动到底部 | |||
| const scrollToBottom = (smooth: boolean = true) => { | |||
| const element = document.getElementById('log-list'); | |||
| if (element) { | |||
| const optons: ScrollToOptions = { | |||
| top: element.scrollHeight, | |||
| behavior: smooth ? 'smooth' : 'instant', | |||
| }; | |||
| element.scrollTo(optons); | |||
| } | |||
| }; | |||
| function LogGroup({ | |||
| log_type = 'normal', | |||
| pod_name = '', | |||
| log_content = '', | |||
| start_time, | |||
| status, | |||
| listId, | |||
| }: LogGroupProps) { | |||
| const [collapse, setCollapse] = useState(true); | |||
| const [logList, setLogList, logListRef] = useStateRef<Log[]>([]); | |||
| @@ -135,7 +124,7 @@ function LogGroup({ | |||
| const setupSockect = () => { | |||
| let { host } = location; | |||
| if (process.env.NODE_ENV === 'development') { | |||
| host = '172.20.32.197:31213'; | |||
| host = '172.20.32.181:31213'; | |||
| } | |||
| const socket = new WebSocket( | |||
| `ws://${host}/newlog/realtimeLog?start=${start_time}&query={pod="${pod_name}"}`, | |||
| @@ -210,6 +199,18 @@ function LogGroup({ | |||
| } | |||
| }; | |||
| // 滚动到底部 | |||
| const scrollToBottom = (smooth: boolean = true) => { | |||
| const element = document.getElementById(listId); | |||
| if (element) { | |||
| const optons: ScrollToOptions = { | |||
| top: element.scrollHeight, | |||
| behavior: smooth ? 'smooth' : 'instant', | |||
| }; | |||
| element.scrollTo(optons); | |||
| } | |||
| }; | |||
| const showLog = (log_type === 'resource' && !collapse) || log_type === 'normal'; | |||
| const logText = log_content + logList.map((v) => v.log_content).join(''); | |||
| const showMoreBtn = | |||
| @@ -14,6 +14,7 @@ export type ExperimentLog = { | |||
| }; | |||
| type LogListProps = { | |||
| idPrefix?: string; // 当一个页面有多个日志组件时,使用这个变量作为唯一性标识 | |||
| instanceName: string; // 实验实例 name | |||
| instanceNamespace: string; // 实验实例 namespace | |||
| pipelineNodeId: string; // 流水线节点 id | |||
| @@ -23,6 +24,7 @@ type LogListProps = { | |||
| }; | |||
| function LogList({ | |||
| idPrefix, | |||
| instanceName, | |||
| instanceNamespace, | |||
| pipelineNodeId, | |||
| @@ -86,10 +88,15 @@ function LogList({ | |||
| } | |||
| }; | |||
| // 当一个页面有多个日志组件时,使用这个变量作为唯一性标识 | |||
| const listId = idPrefix ? `${idPrefix}-log-list` : 'log-list'; | |||
| return ( | |||
| <div className={styles['log-list']} id="log-list"> | |||
| <div className={styles['log-list']} id={listId}> | |||
| {logList.length > 0 ? ( | |||
| logList.map((v) => <LogGroup key={v.pod_name} {...v} status={instanceNodeStatus} />) | |||
| logList.map((v) => ( | |||
| <LogGroup key={v.pod_name} {...v} listId={listId} status={instanceNodeStatus} /> | |||
| )) | |||
| ) : ( | |||
| <div className={styles['log-list__empty']}>暂无日志</div> | |||
| )} | |||
| @@ -34,7 +34,7 @@ | |||
| &__log { | |||
| height: calc(100% - 10px); | |||
| margin-top: 10px; | |||
| padding: 20px calc(@content-padding - 8px); | |||
| padding: 8px calc(@content-padding - 8px) 20px; | |||
| overflow-y: visible; | |||
| background-color: white; | |||
| border-radius: 10px; | |||
| @@ -1,6 +1,5 @@ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { ExperimentStatus } from '@/enums'; | |||
| import LogList from '@/pages/Experiment/components/LogList'; | |||
| import { getRayInsReq } from '@/services/hyperParameter'; | |||
| import { NodeStatus } from '@/types'; | |||
| import { parseJsonText } from '@/utils'; | |||
| @@ -10,6 +9,7 @@ import { useParams } from '@umijs/max'; | |||
| import { Tabs } from 'antd'; | |||
| import { useEffect, useRef, useState } from 'react'; | |||
| import ExperimentHistory from '../components/ExperimentHistory'; | |||
| import ExperimentLog from '../components/ExperimentLog'; | |||
| import ExperimentResult from '../components/ExperimentResult'; | |||
| import HyperParameterBasic from '../components/HyperParameterBasic'; | |||
| import { HyperParameterData, HyperParameterInstanceData } from '../types'; | |||
| @@ -22,8 +22,6 @@ enum TabKeys { | |||
| History = 'history', | |||
| } | |||
| const NodePrefix = 'auto-hpo'; | |||
| function HyperParameterInstance() { | |||
| const [activeTab, setActiveTab] = useState<string>(TabKeys.Params); | |||
| const [experimentInfo, setExperimentInfo] = useState<HyperParameterData | undefined>(undefined); | |||
| @@ -32,6 +30,7 @@ function HyperParameterInstance() { | |||
| ); | |||
| // 超参数寻优运行有3个节点,运行状态取工作流状态,而不是 auto-hpo 节点状态 | |||
| const [workflowStatus, setWorkflowStatus] = useState<NodeStatus | undefined>(undefined); | |||
| const [nodes, setNodes] = useState<Record<string, NodeStatus> | undefined>(undefined); | |||
| const params = useParams(); | |||
| const instanceId = safeInvoke(Number)(params.id); | |||
| const evtSourceRef = useRef<EventSource | null>(null); | |||
| @@ -72,30 +71,27 @@ function HyperParameterInstance() { | |||
| setExperimentInfo(paramJson); | |||
| } | |||
| setInstanceInfo(info); | |||
| // 这个接口返回的状态有延时,SSE 返回的状态是最新的 | |||
| // SSE 调用时,不需要解析 node_status, 也不要重新建立 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) => { | |||
| if (key.startsWith(NodePrefix)) { | |||
| const nodeStatus = nodeStatusJson[key]; | |||
| info.nodeStatus = nodeStatus; | |||
| } else if (key.startsWith('workflow')) { | |||
| setNodes(nodeStatusJson); | |||
| Object.keys(nodeStatusJson).some((key) => { | |||
| if (key.startsWith('workflow')) { | |||
| const workflowStatus = nodeStatusJson[key]; | |||
| setWorkflowStatus(workflowStatus); | |||
| return true; | |||
| } | |||
| return false; | |||
| }); | |||
| } | |||
| setInstanceInfo(info); | |||
| // 运行中或者等待中,开启 SSE | |||
| if (status === ExperimentStatus.Pending || status === ExperimentStatus.Running) { | |||
| @@ -106,9 +102,9 @@ function HyperParameterInstance() { | |||
| const setupSSE = (name: string, namespace: string) => { | |||
| let { origin } = location; | |||
| if (process.env.NODE_ENV === 'development') { | |||
| origin = 'http://172.20.32.197:31213'; | |||
| } | |||
| // if (process.env.NODE_ENV === 'development') { | |||
| // origin = 'http://172.20.32.197:31213'; | |||
| // } | |||
| const params = encodeURIComponent(`metadata.namespace=${namespace},metadata.name=${name}`); | |||
| const evtSource = new EventSource( | |||
| `${origin}/api/v1/realtimeStatus?listOptions.fieldSelector=${params}`, | |||
| @@ -123,20 +119,12 @@ function HyperParameterInstance() { | |||
| if (dataJson) { | |||
| const nodes = dataJson?.result?.object?.status?.nodes; | |||
| if (nodes) { | |||
| const nodeStatus = Object.values(nodes).find((node: any) => | |||
| node.displayName.startsWith(NodePrefix), | |||
| ) as NodeStatus; | |||
| const workflowStatus = Object.values(nodes).find((node: any) => | |||
| node.displayName.startsWith('workflow'), | |||
| ) as NodeStatus; | |||
| // 节点状态 | |||
| if (nodeStatus) { | |||
| setInstanceInfo((prev) => ({ | |||
| ...prev!, | |||
| nodeStatus: nodeStatus, | |||
| })); | |||
| } | |||
| // 节点 | |||
| setNodes(nodes); | |||
| // 设置工作流状态 | |||
| if (workflowStatus) { | |||
| @@ -188,16 +176,7 @@ function HyperParameterInstance() { | |||
| icon: <KFIcon type="icon-rizhi1" />, | |||
| children: ( | |||
| <div className={styles['hyper-parameter-instance__log']}> | |||
| {instanceInfo && instanceInfo.nodeStatus && ( | |||
| <LogList | |||
| instanceName={instanceInfo.argo_ins_name} | |||
| instanceNamespace={instanceInfo.argo_ins_ns} | |||
| pipelineNodeId={instanceInfo.nodeStatus.displayName} | |||
| workflowId={instanceInfo.nodeStatus.id} | |||
| instanceNodeStartTime={instanceInfo.nodeStatus.startedAt} | |||
| instanceNodeStatus={instanceInfo.nodeStatus.phase as ExperimentStatus} | |||
| ></LogList> | |||
| )} | |||
| {instanceInfo && nodes && <ExperimentLog instanceInfo={instanceInfo} nodes={nodes} />} | |||
| </div> | |||
| ), | |||
| }, | |||
| @@ -208,9 +187,7 @@ function HyperParameterInstance() { | |||
| key: TabKeys.Result, | |||
| label: '实验结果', | |||
| icon: <KFIcon type="icon-shiyanjieguo1" />, | |||
| children: ( | |||
| <ExperimentResult fileUrl={instanceInfo?.result_txt} fileList={instanceInfo?.file_list} /> | |||
| ), | |||
| children: <ExperimentResult fileUrl={instanceInfo?.result_txt} />, | |||
| }, | |||
| { | |||
| key: TabKeys.History, | |||
| @@ -33,3 +33,26 @@ | |||
| } | |||
| } | |||
| } | |||
| .cell-index { | |||
| position: relative; | |||
| width: 100%; | |||
| padding-left: 20px; | |||
| text-align: left; | |||
| &__best-tag { | |||
| margin-left: 8px; | |||
| padding: 1px 10px; | |||
| color: @primary-color; | |||
| font-weight: normal; | |||
| font-size: 13px; | |||
| background-color: .addAlpha(@primary-color, 0.1) []; | |||
| border: 1px solid .addAlpha(@primary-color, 0.5) []; | |||
| border-radius: 2px; | |||
| } | |||
| } | |||
| .table-best-row { | |||
| color: @primary-color; | |||
| font-weight: bold; | |||
| } | |||
| @@ -1,6 +1,8 @@ | |||
| import { HyperParameterTrialList } from '@/pages/HyperParameter/types'; | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { HyperParameterFileList, HyperParameterTrialList } from '@/pages/HyperParameter/types'; | |||
| import { downLoadZip } from '@/utils/downloadfile'; | |||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | |||
| import { Table, Tooltip, type TableProps } from 'antd'; | |||
| import { Button, Table, Tooltip, type TableProps } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import styles from './index.less'; | |||
| @@ -15,14 +17,21 @@ function ExperimentHistory({ trialList = [] }: ExperimentHistoryProps) { | |||
| const paramsNames = Object.keys(config); | |||
| const metricNames = Object.keys(metricAnalysis); | |||
| const columns: TableProps<HyperParameterTrialList>['columns'] = [ | |||
| const trialColumns: TableProps<HyperParameterTrialList>['columns'] = [ | |||
| { | |||
| title: '序号', | |||
| dataIndex: 'index', | |||
| key: 'index', | |||
| width: 100, | |||
| width: 120, | |||
| align: 'center', | |||
| render: tableCellRender(false, TableCellValueType.Index), | |||
| render: (_text, record, index: number) => { | |||
| return ( | |||
| <div className={styles['cell-index']}> | |||
| <span className={styles['cell-index__text']}>{index + 1}</span> | |||
| {record.is_best && <span className={styles['cell-index__best-tag']}>最佳</span>} | |||
| </div> | |||
| ); | |||
| }, | |||
| }, | |||
| { | |||
| title: '运行次数', | |||
| @@ -51,7 +60,7 @@ function ExperimentHistory({ trialList = [] }: ExperimentHistoryProps) { | |||
| ]; | |||
| if (paramsNames.length) { | |||
| columns.push({ | |||
| trialColumns.push({ | |||
| title: '运行参数', | |||
| dataIndex: 'config', | |||
| key: 'config', | |||
| @@ -74,7 +83,7 @@ function ExperimentHistory({ trialList = [] }: ExperimentHistoryProps) { | |||
| } | |||
| if (metricNames.length) { | |||
| columns.push({ | |||
| trialColumns.push({ | |||
| title: `指标分析(${first.metric ?? ''})`, | |||
| dataIndex: 'metrics', | |||
| key: 'metrics', | |||
| @@ -96,6 +105,51 @@ function ExperimentHistory({ trialList = [] }: ExperimentHistoryProps) { | |||
| }); | |||
| } | |||
| const fileColumns: TableProps<HyperParameterFileList>['columns'] = [ | |||
| { | |||
| title: '文件名称', | |||
| dataIndex: 'name', | |||
| key: 'name', | |||
| render: tableCellRender(false), | |||
| }, | |||
| { | |||
| title: '文件大小', | |||
| dataIndex: 'size', | |||
| key: 'size', | |||
| width: 200, | |||
| render: tableCellRender(false), | |||
| }, | |||
| { | |||
| title: '操作', | |||
| dataIndex: 'option', | |||
| width: 160, | |||
| key: 'option', | |||
| render: (_: any, record: HyperParameterFileList) => { | |||
| return ( | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="download" | |||
| icon={<KFIcon type="icon-xiazai" />} | |||
| onClick={() => { | |||
| if (record.isFile) { | |||
| downLoadZip(`/api/mmp/minioStorage/downloadFile`, { path: record.url }); | |||
| } else { | |||
| downLoadZip(`/api/mmp/minioStorage/download`, { path: record.url }); | |||
| } | |||
| }} | |||
| > | |||
| 下载 | |||
| </Button> | |||
| ); | |||
| }, | |||
| }, | |||
| ]; | |||
| const expandedRowRender = (record: HyperParameterTrialList) => ( | |||
| <Table columns={fileColumns} dataSource={[record.file]} pagination={false} rowKey="name" /> | |||
| ); | |||
| return ( | |||
| <div className={styles['experiment-history']}> | |||
| <div className={styles['experiment-history__content']}> | |||
| @@ -106,12 +160,14 @@ function ExperimentHistory({ trialList = [] }: ExperimentHistoryProps) { | |||
| )} | |||
| > | |||
| <Table | |||
| rowClassName={(record) => (record.is_best ? styles['table-best-row'] : '')} | |||
| dataSource={trialList} | |||
| columns={columns} | |||
| columns={trialColumns} | |||
| pagination={false} | |||
| bordered={true} | |||
| scroll={{ y: 'calc(100% - 110px)', x: '100%' }} | |||
| rowKey="trial_id" | |||
| expandable={{ expandedRowRender }} | |||
| /> | |||
| </div> | |||
| </div> | |||
| @@ -0,0 +1,16 @@ | |||
| .experiment-log { | |||
| height: 100%; | |||
| &__tabs { | |||
| height: 100%; | |||
| :global { | |||
| .ant-tabs-nav-list { | |||
| padding-left: 0 !important; | |||
| background: none !important; | |||
| } | |||
| } | |||
| &__log { | |||
| height: 100%; | |||
| } | |||
| } | |||
| } | |||
| @@ -1,50 +1,78 @@ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { ExperimentStatus } from '@/enums'; | |||
| import LogList from '@/pages/Experiment/components/LogList'; | |||
| import { HyperParameterInstanceData } from '@/pages/HyperParameter/types'; | |||
| import { NodeStatus } from '@/types'; | |||
| import { Tabs } from 'antd'; | |||
| import { useEffect } from 'react'; | |||
| import styles from './index.less'; | |||
| type ExperimentLogProps = { | |||
| instanceInfo: HyperParameterInstanceData; | |||
| nodes: Record<string, NodeStatus>; | |||
| }; | |||
| function ExperimentLog({ instanceInfo }: ExperimentLogProps) { | |||
| function ExperimentLog({ instanceInfo, nodes }: ExperimentLogProps) { | |||
| let hpoNodeStatus: NodeStatus | undefined; | |||
| let frameworkCloneNodeStatus: NodeStatus | undefined; | |||
| let trainCloneNodeStatus: NodeStatus | undefined; | |||
| Object.keys(nodes) | |||
| .sort((key1, key2) => { | |||
| const node1 = nodes[key1]; | |||
| const node2 = nodes[key2]; | |||
| return new Date(node1.startedAt).getTime() - new Date(node2.startedAt).getTime(); | |||
| }) | |||
| .forEach((key) => { | |||
| const node = nodes[key]; | |||
| if (node.displayName.startsWith('auto-hpo')) { | |||
| hpoNodeStatus = node; | |||
| } else if (node.displayName.startsWith('git-clone') && !frameworkCloneNodeStatus) { | |||
| frameworkCloneNodeStatus = node; | |||
| } else if ( | |||
| node.displayName.startsWith('git-clone') && | |||
| frameworkCloneNodeStatus && | |||
| node.displayName !== frameworkCloneNodeStatus?.displayName | |||
| ) { | |||
| trainCloneNodeStatus = node; | |||
| } | |||
| }); | |||
| const tabItems = [ | |||
| { | |||
| key: 'git-clone-1', | |||
| key: 'git-clone-framework', | |||
| label: '框架代码日志', | |||
| icon: <KFIcon type="icon-jibenxinxi" />, | |||
| // icon: <KFIcon type="icon-rizhi1" />, | |||
| children: ( | |||
| <div className={styles['auto-ml-instance__log']}> | |||
| {instanceInfo && instanceInfo.nodeStatus && ( | |||
| <div className={styles['experiment-log__tabs__log']}> | |||
| {frameworkCloneNodeStatus && ( | |||
| <LogList | |||
| idPrefix="git-clone-framework" | |||
| instanceName={instanceInfo.argo_ins_name} | |||
| instanceNamespace={instanceInfo.argo_ins_ns} | |||
| pipelineNodeId={instanceInfo.nodeStatus.displayName} | |||
| workflowId={instanceInfo.nodeStatus.id} | |||
| instanceNodeStartTime={instanceInfo.nodeStatus.startedAt} | |||
| instanceNodeStatus={instanceInfo.nodeStatus.phase as ExperimentStatus} | |||
| pipelineNodeId={frameworkCloneNodeStatus.displayName} | |||
| workflowId={frameworkCloneNodeStatus.id} | |||
| instanceNodeStartTime={frameworkCloneNodeStatus.startedAt} | |||
| instanceNodeStatus={frameworkCloneNodeStatus.phase as ExperimentStatus} | |||
| ></LogList> | |||
| )} | |||
| </div> | |||
| ), | |||
| }, | |||
| { | |||
| key: 'git-clone-2', | |||
| key: 'git-clone-train', | |||
| label: '训练代码日志', | |||
| icon: <KFIcon type="icon-rizhi1" />, | |||
| // icon: <KFIcon type="icon-rizhi1" />, | |||
| children: ( | |||
| <div className={styles['auto-ml-instance__log']}> | |||
| {instanceInfo && instanceInfo.nodeStatus && ( | |||
| <div className={styles['experiment-log__tabs__log']}> | |||
| {trainCloneNodeStatus && ( | |||
| <LogList | |||
| idPrefix="git-clone-train" | |||
| instanceName={instanceInfo.argo_ins_name} | |||
| instanceNamespace={instanceInfo.argo_ins_ns} | |||
| pipelineNodeId={instanceInfo.nodeStatus.displayName} | |||
| workflowId={instanceInfo.nodeStatus.id} | |||
| instanceNodeStartTime={instanceInfo.nodeStatus.startedAt} | |||
| instanceNodeStatus={instanceInfo.nodeStatus.phase as ExperimentStatus} | |||
| pipelineNodeId={trainCloneNodeStatus.displayName} | |||
| workflowId={trainCloneNodeStatus.id} | |||
| instanceNodeStartTime={trainCloneNodeStatus.startedAt} | |||
| instanceNodeStatus={trainCloneNodeStatus.phase as ExperimentStatus} | |||
| ></LogList> | |||
| )} | |||
| </div> | |||
| @@ -53,17 +81,18 @@ function ExperimentLog({ instanceInfo }: ExperimentLogProps) { | |||
| { | |||
| key: 'auto-hpo', | |||
| label: '超参寻优日志', | |||
| icon: <KFIcon type="icon-rizhi1" />, | |||
| // icon: <KFIcon type="icon-rizhi1" />, | |||
| children: ( | |||
| <div className={styles['auto-ml-instance__log']}> | |||
| {instanceInfo && instanceInfo.nodeStatus && ( | |||
| <div className={styles['experiment-log__tabs__log']}> | |||
| {hpoNodeStatus && ( | |||
| <LogList | |||
| idPrefix="auto-hpo" | |||
| instanceName={instanceInfo.argo_ins_name} | |||
| instanceNamespace={instanceInfo.argo_ins_ns} | |||
| pipelineNodeId={instanceInfo.nodeStatus.displayName} | |||
| workflowId={instanceInfo.nodeStatus.id} | |||
| instanceNodeStartTime={instanceInfo.nodeStatus.startedAt} | |||
| instanceNodeStatus={instanceInfo.nodeStatus.phase as ExperimentStatus} | |||
| pipelineNodeId={hpoNodeStatus.displayName} | |||
| workflowId={hpoNodeStatus.id} | |||
| instanceNodeStartTime={hpoNodeStatus.startedAt} | |||
| instanceNodeStatus={hpoNodeStatus.phase as ExperimentStatus} | |||
| ></LogList> | |||
| )} | |||
| </div> | |||
| @@ -75,7 +104,7 @@ function ExperimentLog({ instanceInfo }: ExperimentLogProps) { | |||
| return ( | |||
| <div className={styles['experiment-log']}> | |||
| <Tabs className={styles['auto-ml-instance__tabs']} items={tabItems} /> | |||
| <Tabs className={styles['experiment-log__tabs']} items={tabItems} /> | |||
| </div> | |||
| ); | |||
| } | |||
| @@ -12,6 +12,6 @@ | |||
| &__text { | |||
| font-family: 'Roboto Mono', 'Menlo', 'Consolas', 'Monaco', monospace; | |||
| white-space: pre-wrap; | |||
| white-space: pre; | |||
| } | |||
| } | |||
| @@ -1,45 +1,16 @@ | |||
| import InfoGroup from '@/components/InfoGroup'; | |||
| import { HyperParameterFileList } from '@/pages/HyperParameter/types'; | |||
| import { getFileReq } from '@/services/file'; | |||
| import { to } from '@/utils/promise'; | |||
| import tableCellRender, { TableCellValueType } from '@/utils/table'; | |||
| import { Table, type TableProps } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import { useEffect, useState } from 'react'; | |||
| import styles from './index.less'; | |||
| type ExperimentResultProps = { | |||
| fileList?: HyperParameterFileList[]; | |||
| fileUrl?: string; | |||
| }; | |||
| function ExperimentResult({ fileList, fileUrl }: ExperimentResultProps) { | |||
| function ExperimentResult({ fileUrl }: ExperimentResultProps) { | |||
| const [result, setResult] = useState<string | undefined>(''); | |||
| const columns: TableProps<HyperParameterFileList>['columns'] = [ | |||
| { | |||
| title: '序号', | |||
| dataIndex: 'index', | |||
| key: 'index', | |||
| width: 120, | |||
| align: 'center', | |||
| render: tableCellRender(false, TableCellValueType.Index), | |||
| }, | |||
| { | |||
| title: '文件名称', | |||
| dataIndex: 'name', | |||
| key: 'name', | |||
| render: tableCellRender(false), | |||
| }, | |||
| { | |||
| title: '文件大小', | |||
| dataIndex: 'size', | |||
| key: 'size', | |||
| width: 200, | |||
| render: tableCellRender(false), | |||
| }, | |||
| ]; | |||
| useEffect(() => { | |||
| if (fileUrl) { | |||
| getResultFile(); | |||
| @@ -56,23 +27,7 @@ function ExperimentResult({ fileList, fileUrl }: ExperimentResultProps) { | |||
| return ( | |||
| <div className={styles['experiment-result']}> | |||
| <InfoGroup title="文件列表" style={{ margin: '16px 0' }}> | |||
| <div | |||
| className={classNames( | |||
| 'vertical-scroll-table-no-page', | |||
| styles['experiment-result__table'], | |||
| )} | |||
| > | |||
| <Table | |||
| dataSource={fileList} | |||
| columns={columns} | |||
| pagination={false} | |||
| scroll={{ y: 'calc(100% - 55px)', x: '100%' }} | |||
| rowKey="name" | |||
| /> | |||
| </div> | |||
| </InfoGroup> | |||
| <InfoGroup title="实验结果" height={420} width="100%"> | |||
| <InfoGroup title="最佳实验结果" width="100%"> | |||
| <div className={styles['experiment-result__text']}>{result}</div> | |||
| </InfoGroup> | |||
| </div> | |||
| @@ -71,9 +71,14 @@ export type HyperParameterTrialList = { | |||
| config?: Record<string, any>; | |||
| metric_analysis?: Record<string, any>; | |||
| metric: string; | |||
| file: HyperParameterFileList; | |||
| is_best?: boolean; | |||
| }; | |||
| export type HyperParameterFileList = { | |||
| name?: string; | |||
| size?: string; | |||
| name: string; | |||
| size: string; | |||
| url: string; | |||
| isFile: boolean; | |||
| children?: HyperParameterFileList[]; | |||
| }; | |||