diff --git a/react-ui/src/pages/ActiveLearn/Instance/index.tsx b/react-ui/src/pages/ActiveLearn/Instance/index.tsx index eff6815f..e2ec79a1 100644 --- a/react-ui/src/pages/ActiveLearn/Instance/index.tsx +++ b/react-ui/src/pages/ActiveLearn/Instance/index.tsx @@ -1,5 +1,5 @@ import KFIcon from '@/components/KFIcon'; -import { ExperimentStatus } from '@/enums'; +import { AutoMLTaskType, ExperimentStatus } from '@/enums'; import { getActiveLearnInsReq } from '@/services/activeLearn'; import { NodeStatus } from '@/types'; import { parseJsonText } from '@/utils'; @@ -169,13 +169,20 @@ function ActiveLearnInstance() { key: TabKeys.Result, label: '实验结果', icon: , - children: , + children: ( + + ), }, { key: TabKeys.History, - label: '寻优列表', + label: '训练列表', icon: , - children: , + children: ( + + ), }, ]; diff --git a/react-ui/src/pages/ActiveLearn/components/ExperimentHistory/index.tsx b/react-ui/src/pages/ActiveLearn/components/ExperimentHistory/index.tsx index e95ccd42..58f70b9f 100644 --- a/react-ui/src/pages/ActiveLearn/components/ExperimentHistory/index.tsx +++ b/react-ui/src/pages/ActiveLearn/components/ExperimentHistory/index.tsx @@ -1,112 +1,89 @@ -import { getFileReq } from '@/services/file'; -import { to } from '@/utils/promise'; -import tableCellRender from '@/utils/table'; -import { Table, type TableProps } from 'antd'; +import KFIcon from '@/components/KFIcon'; +import { ActiveLearnTrial } from '@/pages/ActiveLearn/types'; +import TrialFileTree from '@/pages/HyperParameter/components/TrialFileTree'; +import { downloadCommonFile, MimeType } from '@/utils/downloadfile'; +import tableCellRender, { TableCellValueType } from '@/utils/table'; +import { Button, Table, type TableProps } from 'antd'; import classNames from 'classnames'; -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import styles from './index.less'; type ExperimentHistoryProps = { - fileUrl?: string; + trialList: ActiveLearnTrial[]; isClassification: boolean; }; -type TableData = { - id?: string; - accuracy?: number; - duration?: number; - train_loss?: number; - status?: string; - feature?: string; - althorithm?: string; -}; - -function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps) { - const [tableData, setTableData] = useState([]); - useEffect(() => { - if (fileUrl) { - getHistoryFile(); - } - }, [fileUrl]); - - // 获取实验运行历史记录 - const getHistoryFile = async () => { - const [res] = await to(getFileReq(fileUrl)); - if (res) { - const data: any[] = res.data; - const list: TableData[] = data.map((item) => { - return { - id: item[0]?.[0], - accuracy: item[1]?.[5]?.accuracy, - duration: item[1]?.[5]?.duration, - train_loss: item[1]?.[5]?.train_loss, - status: item[1]?.[2]?.['__enum__']?.split('.')?.[1], - }; - }); - list.forEach((item) => { - if (!item.id) return; - const config = (res as any).configs?.[item.id]; - item.feature = config?.['feature_preprocessor:__choice__']; - item.althorithm = isClassification - ? config?.['classifier:__choice__'] - : config?.['regressor:__choice__']; - }); - setTableData(list); - } - }; - - const columns: TableProps['columns'] = [ - { - title: 'ID', - dataIndex: 'id', - key: 'id', - width: 80, - render: tableCellRender(false), - }, - { - title: '准确率', - dataIndex: 'accuracy', - key: 'accuracy', - render: tableCellRender(true), - ellipsis: { showTitle: false }, - }, +function ExperimentHistory({ trialList, isClassification }: ExperimentHistoryProps) { + const [expandedRowKeys, setExpandedRowKeys] = useState([]); + const columns: TableProps['columns'] = [ { - title: '耗时', - dataIndex: 'duration', - key: 'duration', - render: tableCellRender(true), - ellipsis: { showTitle: false }, + title: '序号', + dataIndex: 'index', + key: 'index', + width: 100, + render: tableCellRender(false, TableCellValueType.Index), }, { - title: '训练损失', - dataIndex: 'train_loss', - key: 'train_loss', - render: tableCellRender(true), - ellipsis: { showTitle: false }, + title: '查询数据量', + dataIndex: 'n_instances', + key: 'n_instances', + render: tableCellRender(false), }, { - title: '特征处理', - dataIndex: 'feature', - key: 'feature', - render: tableCellRender(true), - ellipsis: { showTitle: false }, + title: '总数据量', + dataIndex: 'data_num', + key: 'data_num', + render: tableCellRender(false), }, { - title: '算法', - dataIndex: 'althorithm', - key: 'althorithm', - render: tableCellRender(true), - ellipsis: { showTitle: false }, + title: isClassification ? '准确率' : '均方误差', + dataIndex: isClassification ? 'accuracy' : 'mse', + key: 'accuracy', + render: tableCellRender(false), }, { - title: '状态', - dataIndex: 'status', - key: 'status', - width: 120, - render: tableCellRender(false), + title: '查询数据 id', + dataIndex: 'query_idx', + key: 'query_idx', + render: (query_idx: string) => ( +
+ +
+ ), }, ]; + // 自定义展开视图 + const expandedRowRender = (record: ActiveLearnTrial) => { + return ; + }; + + // 展开实例 + const handleExpandChange = (expanded: boolean, record: ActiveLearnTrial) => { + if (expanded) { + setExpandedRowKeys([record.trial_id]); + } else { + setExpandedRowKeys([]); + } + }; + return (
@@ -117,11 +94,17 @@ function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps )} > !!record.file, + }} /> diff --git a/react-ui/src/pages/ActiveLearn/components/ExperimentResult/index.tsx b/react-ui/src/pages/ActiveLearn/components/ExperimentResult/index.tsx index a826155d..e1268907 100644 --- a/react-ui/src/pages/ActiveLearn/components/ExperimentResult/index.tsx +++ b/react-ui/src/pages/ActiveLearn/components/ExperimentResult/index.tsx @@ -1,81 +1,49 @@ import InfoGroup from '@/components/InfoGroup'; -import { getFileReq } from '@/services/file'; -import { to } from '@/utils/promise'; -import { Button, Image } from 'antd'; -import { useEffect, useMemo, useState } from 'react'; +import { downloadCommonFile, MimeType } from '@/utils/downloadfile'; +import { Button } from 'antd'; import styles from './index.less'; type ExperimentResultProps = { fileUrl?: string; - imageUrl?: string; modelPath?: string; }; -function ExperimentResult({ fileUrl, imageUrl, modelPath }: ExperimentResultProps) { - const [result, setResult] = useState(''); - - const images = useMemo(() => { - if (imageUrl) { - return imageUrl.split(',').map((item) => item.trim()); - } - return []; - }, [imageUrl]); - - useEffect(() => { - if (fileUrl) { - getResultFile(); - } - }, [fileUrl]); - - // 获取实验运行历史记录 - const getResultFile = async () => { - const [res] = await to(getFileReq(fileUrl)); - if (res) { - setResult(res as any as string); - } - }; - +function ExperimentResult({ fileUrl, modelPath }: ExperimentResultProps) { return (
- -
{result}
+ + {fileUrl && ( +
+ +
+ )}
- -
- - console.log(`current index: ${current}, prev index: ${prev}`), - }} - > - {images.map((item) => ( - - ))} - -
+ + {modelPath && ( +
+ +
+ )}
- {modelPath && ( -
- 文件名 - save_model.joblib - -
- )}
); } diff --git a/react-ui/src/pages/ActiveLearn/data.json b/react-ui/src/pages/ActiveLearn/data.json deleted file mode 100644 index 765022ea..00000000 --- a/react-ui/src/pages/ActiveLearn/data.json +++ /dev/null @@ -1,120 +0,0 @@ -{ - "workflow-xwnb8": { - "id": "workflow-xwnb8", - "name": "workflow-xwnb8", - "type": "DAG", - "phase": "Failed", - "children": [ - "workflow-xwnb8-1083129199" - ], - "progress": "2/3", - "startedAt": "2025-04-18T06:56:18Z", - "finishedAt": "2025-04-18T06:57:32Z", - "displayName": "workflow-xwnb8", - "templateName": "ml-workflow", - "outboundNodes": [ - "workflow-xwnb8-1355608520" - ], - "templateScope": "local/workflow-xwnb8", - "resourcesDuration": { - "cpu": 42, - "memory": 851, - "nvidia.com/gpu": 10 - } - }, - "git-clone-9d0c5965": { - "id": "workflow-xwnb8-514970004", - "name": "workflow-xwnb8.git-clone-9d0c5965", - "type": "Pod", - "phase": "Succeeded", - "outputs": { - "exitCode": "0", - "artifacts": [ - { - "s3": { - "key": "workflow-xwnb8/workflow-xwnb8-git-clone-9d0c5965-514970004/main.log" - }, - "name": "main-logs" - } - ] - }, - "children": [ - "workflow-xwnb8-1355608520" - ], - "progress": "1/1", - "startedAt": "2025-04-18T06:56:38Z", - "boundaryID": "workflow-xwnb8", - "finishedAt": "2025-04-18T06:56:49Z", - "displayName": "git-clone-9d0c5965", - "hostNodeName": "k8s-node01", - "templateName": "git-clone-9d0c5965", - "templateScope": "local/workflow-xwnb8", - "resourcesDuration": { - "cpu": 1, - "memory": 11 - } - }, - "git-clone-e28c560c": { - "id": "workflow-xwnb8-1083129199", - "name": "workflow-xwnb8.git-clone-e28c560c", - "type": "Pod", - "phase": "Succeeded", - "outputs": { - "exitCode": "0", - "artifacts": [ - { - "s3": { - "key": "workflow-xwnb8/workflow-xwnb8-git-clone-e28c560c-1083129199/main.log" - }, - "name": "main-logs" - } - ] - }, - "children": [ - "workflow-xwnb8-514970004" - ], - "progress": "1/1", - "startedAt": "2025-04-18T06:56:18Z", - "boundaryID": "workflow-xwnb8", - "finishedAt": "2025-04-18T06:56:27Z", - "displayName": "git-clone-e28c560c", - "hostNodeName": "k8s-node01", - "templateName": "git-clone-e28c560c", - "templateScope": "local/workflow-xwnb8", - "resourcesDuration": { - "cpu": 1, - "memory": 11 - } - }, - "active-learn-b708ed0b": { - "id": "workflow-xwnb8-1355608520", - "name": "workflow-xwnb8.active-learn-b708ed0b", - "type": "Pod", - "phase": "Failed", - "message": "Error (exit code 1)", - "outputs": { - "exitCode": "1", - "artifacts": [ - { - "s3": { - "key": "workflow-xwnb8/workflow-xwnb8-active-learn-b708ed0b-1355608520/main.log" - }, - "name": "main-logs" - } - ] - }, - "progress": "0/1", - "startedAt": "2025-04-18T06:57:00Z", - "boundaryID": "workflow-xwnb8", - "finishedAt": "2025-04-18T06:57:27Z", - "displayName": "active-learn-b708ed0b", - "hostNodeName": "k8s-node01", - "templateName": "active-learn-b708ed0b", - "templateScope": "local/workflow-xwnb8", - "resourcesDuration": { - "cpu": 40, - "memory": 829, - "nvidia.com/gpu": 10 - } - } -} \ No newline at end of file diff --git a/react-ui/src/pages/ActiveLearn/types.ts b/react-ui/src/pages/ActiveLearn/types.ts index 0ac447b8..68ca88f2 100644 --- a/react-ui/src/pages/ActiveLearn/types.ts +++ b/react-ui/src/pages/ActiveLearn/types.ts @@ -1,14 +1,7 @@ -/* - * @Author: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git - * @Date: 2025-04-18 08:40:03 - * @LastEditors: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git - * @LastEditTime: 2025-04-18 11:30:21 - * @FilePath: \ci4s\react-ui\src\pages\ActiveLearn\types.ts - * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE - */ import { type ParameterInputObject } from '@/components/ResourceSelect'; import { type NodeStatus } from '@/types'; import { AutoMLTaskType } from '@/enums'; +import { HyperParameterFile } from '@/pages/HyperParameter/types'; // 操作类型 export enum OperationType { @@ -82,4 +75,18 @@ export type ActiveLearnInstanceData = { update_time: string; finish_time: string; nodeStatus?: NodeStatus; + data: string, + trial_list?: ActiveLearnTrial[] }; + +export type ActiveLearnTrial = { + trial_id: string; + n_instances?: number; + data_num?: number; + accuracy?: number; + mse?: number; + query_idx?: string + file?: HyperParameterFile; +}; + +export {type HyperParameterFile} \ No newline at end of file diff --git a/react-ui/src/pages/AutoML/components/ExperimentInstance/index.tsx b/react-ui/src/pages/AutoML/components/ExperimentInstance/index.tsx index 9b27862d..74cf1115 100644 --- a/react-ui/src/pages/AutoML/components/ExperimentInstance/index.tsx +++ b/react-ui/src/pages/AutoML/components/ExperimentInstance/index.tsx @@ -159,6 +159,9 @@ function ExperimentInstanceComponent({ checkSingle(item.id)} + disabled={ + item.status === ExperimentStatus.Running || item.status === ExperimentStatus.Pending + } > - +
{ - const filesToTreeData = ( - files: HyperParameterFile[], - parent?: HyperParameterFile, - ): TreeDataNode[] => - files.map((file) => { - const key = parent ? `${parent.name}/${file.name}` : file.name; - return { - ...file, - key, - title: file.name, - children: file.children ? filesToTreeData(file.children, file) : undefined, - }; - }); - - const treeData: TreeDataNode[] = filesToTreeData([record.file]); - return ( - - { - const label = record.title + (record.isFile ? `(${record.size})` : ''); - return ( - <> - {label} - { - e.stopPropagation(); - downLoadZip( - record.isFile - ? `/api/mmp/minioStorage/downloadFile` - : `/api/mmp/minioStorage/download`, - { path: record.url }, - ); - }} - /> - - ); - }} - /> - - ); + return ; }; // 展开实例 @@ -237,6 +189,7 @@ function ExperimentHistory({ trialList = [] }: ExperimentHistoryProps) { expandedRowRender: expandedRowRender, onExpand: handleExpandChange, expandedRowKeys: expandedRowKeys, + rowExpandable: (record: HyperParameterTrial) => !!record.file, }} rowSelection={rowSelection} /> diff --git a/react-ui/src/pages/HyperParameter/components/TrialFileTree/index.less b/react-ui/src/pages/HyperParameter/components/TrialFileTree/index.less new file mode 100644 index 00000000..9119bf15 --- /dev/null +++ b/react-ui/src/pages/HyperParameter/components/TrialFileTree/index.less @@ -0,0 +1,15 @@ +.trail-file-tree { + :global { + .ant-tree-node-selected { + .trail-file-tree__icon { + color: white; + } + } + + .trail-file-tree__icon { + margin-left: 8px; + color: @primary-color; + } + } + } + \ No newline at end of file diff --git a/react-ui/src/pages/HyperParameter/components/TrialFileTree/index.tsx b/react-ui/src/pages/HyperParameter/components/TrialFileTree/index.tsx new file mode 100644 index 00000000..8746f169 --- /dev/null +++ b/react-ui/src/pages/HyperParameter/components/TrialFileTree/index.tsx @@ -0,0 +1,63 @@ +import InfoGroup from '@/components/InfoGroup'; +import KFIcon from '@/components/KFIcon'; +import { HyperParameterFile } from '@/pages/HyperParameter/types'; +import { downLoadZip } from '@/utils/downloadfile'; +import { Tree, type TreeDataNode } from 'antd'; +import styles from './index.less'; + +const { DirectoryTree } = Tree; + +export type TrialFileTreeProps = { + title: string; + trial: { file?: HyperParameterFile }; +}; + +function TrialFileTree({ trial, title }: TrialFileTreeProps) { + const filesToTreeData = ( + files: HyperParameterFile[], + parent?: HyperParameterFile, + ): TreeDataNode[] => + files.map((file) => { + const key = parent ? `${parent.name}/${file.name}` : file.name; + return { + ...file, + key, + title: file.name, + children: file.children ? filesToTreeData(file.children, file) : undefined, + }; + }); + + const treeData: TreeDataNode[] = filesToTreeData(trial.file ? [trial.file] : []); + return ( + + { + const label = record.title + (record.isFile ? `(${record.size})` : ''); + return ( + <> + {label} + { + e.stopPropagation(); + downLoadZip( + record.isFile + ? `/api/mmp/minioStorage/downloadFile` + : `/api/mmp/minioStorage/download`, + { path: record.url }, + ); + }} + /> + + ); + }} + /> + + ); +} + +export default TrialFileTree; diff --git a/react-ui/src/utils/downloadfile.ts b/react-ui/src/utils/downloadfile.ts index 7d4e46cd..2ee843ab 100644 --- a/react-ui/src/utils/downloadfile.ts +++ b/react-ui/src/utils/downloadfile.ts @@ -1,9 +1,10 @@ import { request } from '@umijs/max'; /** MimeType */ -export const mimeMap = { - xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - zip: 'application/zip', +export enum MimeType { + XLSX = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + ZIP = 'application/zip', + JSON = 'application/json' }; /** @@ -11,7 +12,7 @@ export const mimeMap = { * @param res - blob响应内容 * @param mimeType - MIME类型 */ -export function resolveBlob(res: any, mimeType: string) { +export function resolveBlob(res: any, mimeType: string, specifiedFileName: string = "file") { const aLink = document.createElement('a'); const blob = new Blob([res.data], { type: mimeType }); // 从response的headers中获取filename, @@ -20,7 +21,7 @@ export function resolveBlob(res: any, mimeType: string) { // console.log(res); const contentDisposition = decodeURI(res.headers['content-disposition']); const result = patt.exec(contentDisposition); - let fileName = result ? result[1] : 'file'; + let fileName = result ? result[1] : specifiedFileName; fileName = fileName.replace(/"/g, ''); aLink.style.display = 'none'; aLink.href = URL.createObjectURL(blob); @@ -43,7 +44,7 @@ export function downLoadZip(url: string, params?: any) { responseType: 'blob', getResponse: true, }).then((res) => { - resolveBlob(res, mimeMap.zip); + resolveBlob(res, MimeType.ZIP); }); } @@ -64,7 +65,30 @@ export async function downloadXlsx( responseType: 'blob', getResponse: true, }).then((res) => { - resolveBlob(res, mimeMap.xlsx); + resolveBlob(res, MimeType.XLSX); + }); +} + +/** + * 下载文本文件 + * @param url - url 地址 + * @param type - mime-type + * @param fileName - 文件名 + * @param method - 请求方法 + * @param options - 请求选项 + */ +export function downloadCommonFile(url: string, type: string, fileName: string = "file", method: string = 'GET', options?: Record) { + request(url, { + method: method, + headers: { + isToken: false, + }, + skipValidating: true, + ...options, + responseType: 'blob', + getResponse: true, + }).then((res) => { + resolveBlob(res, type, fileName); }); }