diff --git a/react-ui/config/routes.ts b/react-ui/config/routes.ts index 3a58d07b..e89d5d60 100644 --- a/react-ui/config/routes.ts +++ b/react-ui/config/routes.ts @@ -94,7 +94,7 @@ export default [ { name: '实验训练', path: ':workflowId/:id', - component: './Experiment/training/index', + component: './Experiment/Info/index', }, { name: '实验对比', @@ -112,18 +112,18 @@ export default [ { name: '开发环境', path: '', + component: './DevelopmentEnvironment/List', + }, + { + name: '创建编辑器', + path: 'create', + component: './DevelopmentEnvironment/Create', + }, + { + name: '编辑器', + path: 'editor', component: './DevelopmentEnvironment/Editor', }, - // { - // name: '创建编辑器', - // path: 'create', - // component: './DevelopmentEnvironment/Create', - // }, - // { - // name: '编辑器', - // path: 'editor', - // component: './DevelopmentEnvironment/Editor', - // }, ], }, { diff --git a/react-ui/src/components/ArrayTableCell/index.tsx b/react-ui/src/components/ArrayTableCell/index.tsx new file mode 100644 index 00000000..de109ff4 --- /dev/null +++ b/react-ui/src/components/ArrayTableCell/index.tsx @@ -0,0 +1,37 @@ +/* + * @Author: 赵伟 + * @Date: 2024-04-28 14:18:11 + * @Description: 自定义 Table 数组类单元格 + */ + +import { Tooltip } from 'antd'; + +function ArrayTableCell(ellipsis: boolean = false, property?: string) { + return (value?: any | null) => { + if ( + value === undefined || + value === null || + Array.isArray(value) === false || + value.length === 0 + ) { + return --; + } + + let list = value; + if (property && typeof value[0] === 'object') { + list = value.map((item) => item[property]); + } + const text = list.join(','); + if (ellipsis) { + return ( + + {text}; + + ); + } else { + return {text}; + } + }; +} + +export default ArrayTableCell; diff --git a/react-ui/src/components/CommonTableCell/index.tsx b/react-ui/src/components/CommonTableCell/index.tsx index afa5d8d3..c86ef9a9 100644 --- a/react-ui/src/components/CommonTableCell/index.tsx +++ b/react-ui/src/components/CommonTableCell/index.tsx @@ -6,13 +6,13 @@ import { Tooltip } from 'antd'; -function renderCell(text?: string | null) { +function renderCell(text?: any | null) { return {text ?? '--'}; } function CommonTableCell(ellipsis: boolean = false) { if (ellipsis) { - return (text?: string | null) => ( + return (text?: any | null) => ( {renderCell(text)} diff --git a/react-ui/src/enums/index.ts b/react-ui/src/enums/index.ts index df329d87..b75eeca4 100644 --- a/react-ui/src/enums/index.ts +++ b/react-ui/src/enums/index.ts @@ -1,3 +1,9 @@ +/* + * @Author: 赵伟 + * @Date: 2024-06-07 11:22:28 + * @Description: 接口返回的枚举值和共用的枚举值定义在这里 + */ + // 公开还是私有 TabKey export enum CommonTabKeys { Private = 'Private', // 私有 diff --git a/react-ui/src/overrides.less b/react-ui/src/overrides.less index 61102f12..0f84889d 100644 --- a/react-ui/src/overrides.less +++ b/react-ui/src/overrides.less @@ -4,7 +4,7 @@ * @Description: 覆盖 antd 样式 */ -// 设置 Table 可以滑动 +// 设置 Table 可以滑动,带分页 .vertical-scroll-table { .ant-table-wrapper { height: 100%; @@ -30,6 +30,32 @@ } } +// 设置 Table 可以滑动,没有分页 +.vertical-scroll-table-no-page { + .ant-table-wrapper { + height: 100%; + .ant-spin-nested-loading { + height: 100%; + + .ant-spin-container { + height: 100%; + + .ant-table { + height: 100%; + + .ant-table-container { + height: 100%; + + .ant-table-body { + overflow-y: auto !important; + } + } + } + } + } + } +} + // Tabs 样式 // 删除底部白色横线 .ant-tabs { diff --git a/react-ui/src/pages/Experiment/Comparison/index.less b/react-ui/src/pages/Experiment/Comparison/index.less index 288ce2ed..a491c621 100644 --- a/react-ui/src/pages/Experiment/Comparison/index.less +++ b/react-ui/src/pages/Experiment/Comparison/index.less @@ -17,5 +17,17 @@ padding: 20px 30px 0; background-color: white; border-radius: 10px; + + :global { + .ant-table-container { + border: none !important; + } + .ant-table-tbody { + .ant-table-cell { + border-right: none !important; + border-left: none !important; + } + } + } } } diff --git a/react-ui/src/pages/Experiment/Comparison/index.tsx b/react-ui/src/pages/Experiment/Comparison/index.tsx index c0421ac4..59cd3f8b 100644 --- a/react-ui/src/pages/Experiment/Comparison/index.tsx +++ b/react-ui/src/pages/Experiment/Comparison/index.tsx @@ -1,11 +1,16 @@ -import CommonTableCell from '@/components/CommonTableCell'; -import { useCacheState } from '@/hooks/pageCacheState'; -import { getExpEvaluateInfosReq, getExpTrainInfosReq } from '@/services/experiment'; +// import { useCacheState } from '@/hooks/pageCacheState'; +import { + getExpEvaluateInfosReq, + getExpMetricsReq, + getExpTrainInfosReq, +} from '@/services/experiment'; import { to } from '@/utils/promise'; +import tableCellRender, { arrayFormatter, dateFormatter } from '@/utils/table'; import { useSearchParams } from '@umijs/max'; -import { Button, Table, TablePaginationConfig, TableProps } from 'antd'; +import { App, Button, Table, /*TablePaginationConfig,*/ TableProps } from 'antd'; import classNames from 'classnames'; -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; +import ExperimentStatusCell from '../components/ExperimentStatusCell'; import styles from './index.less'; export enum ComparisonType { @@ -13,20 +18,33 @@ export enum ComparisonType { Evaluate = 'Evaluate', // 评估 } +type TableData = { + experiment_ins_id: number; + run_id: string; + dataset: string[]; + start_time: string; + status: string; + metrics_names: string[]; + metrics: Record; + params_names: string[]; + params: Record; +}; + function ExperimentComparison() { const [searchParams] = useSearchParams(); const comparisonType = searchParams.get('type'); - const experimentId = searchParams.get('experimentId'); - const [tableData, setTableData] = useState([]); - const [cacheState, setCacheState] = useCacheState(); - const [total, setTotal] = useState(0); + const experimentId = searchParams.get('id'); + const [tableData, setTableData] = useState([]); + // const [cacheState, setCacheState] = useCacheState(); + // const [total, setTotal] = useState(0); const [selectedRowKeys, setSelectedRowKeys] = useState([]); - const [pagination, setPagination] = useState( - cacheState?.pagination ?? { - current: 1, - pageSize: 10, - }, - ); + const { message } = App.useApp(); + // const [pagination, setPagination] = useState( + // cacheState?.pagination ?? { + // current: 1, + // pageSize: 10, + // }, + // ); useEffect(() => { getComparisonData(); @@ -38,20 +56,39 @@ function ExperimentComparison() { comparisonType === ComparisonType.Train ? getExpTrainInfosReq : getExpEvaluateInfosReq; const [res] = await to(request(experimentId)); if (res && res.data) { - const { content = [], totalElements = 0 } = res.data; - setTableData(content); - setTotal(totalElements); + // const { content = [], totalElements = 0 } = res.data; + setTableData(res.data); + // setTotal(totalElements); } }; - // 分页切换 - const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => { - if (action === 'paginate') { - setPagination(pagination); + // 获取对比 url + const getExpMetrics = async () => { + const [res] = await to(getExpMetricsReq(selectedRowKeys)); + if (res && res.data) { + const url = res.data; + window.open(url, '_blank'); } - // console.log(pagination, filters, sorter, action); }; + // 对比按钮 click + const hanldeComparisonClick = () => { + if (selectedRowKeys.length < 2) { + message.error('请至少选择两项进行对比'); + return; + } + getExpMetrics(); + }; + + // 分页切换 + // const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => { + // if (action === 'paginate') { + // setPagination(pagination); + // } + // // console.log(pagination, filters, sorter, action); + // }; + + // 选择行 const rowSelection: TableProps['rowSelection'] = { type: 'checkbox', selectedRowKeys, @@ -61,148 +98,96 @@ function ExperimentComparison() { }, }; - const columns: TableProps['columns'] = [ - { - title: '基本信息', - children: [ - { - title: '实例ID', - dataIndex: 'name', - key: 'name', - width: '30%', - render: CommonTableCell(), - }, - { - title: '运行时间', - dataIndex: 'name', - key: 'name', - width: '30%', - render: CommonTableCell(), - }, - { - title: '运行状态', - dataIndex: 'name', - key: 'name', - width: '30%', - render: CommonTableCell(), - }, - { - title: '训练数据集', - dataIndex: 'name', - key: 'name', - width: '30%', - render: CommonTableCell(), - }, - { - title: '增量训练', - dataIndex: 'name', - key: 'name', - width: '30%', - render: CommonTableCell(), - }, - ], - }, - { - title: '训练参数', - children: [ - { - title: 'batchsize', - dataIndex: 'name', - key: 'name', - width: '30%', - render: CommonTableCell(), - }, - { - title: 'config', - dataIndex: 'name', - key: 'name', - width: '30%', - render: CommonTableCell(), - }, - { - title: 'epoch', - dataIndex: 'name', - key: 'name', - width: '30%', - render: CommonTableCell(), - }, - { - title: 'lr', - dataIndex: 'name', - key: 'name', - width: '30%', - render: CommonTableCell(), - }, - { - title: 'warmup_iters', - dataIndex: 'name', - key: 'name', - width: '30%', - render: CommonTableCell(), - }, - ], - }, - { - title: '训练指标', - children: [ - { - title: 'metrc_name', - dataIndex: 'name', - key: 'name', - width: '30%', - render: CommonTableCell(), - }, - { - title: 'test_1', - dataIndex: 'name', - key: 'name', - width: '30%', - render: CommonTableCell(), - }, - { - title: 'test_2', - dataIndex: 'name', - key: 'name', - width: '30%', - render: CommonTableCell(), - }, - { - title: 'test_3', - dataIndex: 'name', - key: 'name', - width: '30%', - render: CommonTableCell(), - }, - { - title: 'test_4', - dataIndex: 'name', - key: 'name', - width: '30%', - render: CommonTableCell(), - }, - ], - }, - ]; + const columns: TableProps['columns'] = useMemo(() => { + const first: TableData | undefined = tableData[0]; + return [ + { + title: '基本信息', + children: [ + { + title: '实例 ID', + dataIndex: 'experiment_ins_id', + key: 'experiment_ins_id', + width: '20%', + render: tableCellRender(), + }, + { + title: '运行时间', + dataIndex: 'start_time', + key: 'start_time', + width: 180, + render: tableCellRender(false, dateFormatter), + }, + { + title: '运行状态', + dataIndex: 'status', + key: 'status', + width: '20%', + render: ExperimentStatusCell, + }, + { + title: '训练数据集', + dataIndex: 'dataset', + key: 'dataset', + width: '20%', + render: tableCellRender(true, arrayFormatter()), + ellipsis: { showTitle: false }, + }, + ], + }, + { + title: '训练参数', + children: first?.params_names.map((name) => ({ + title: name, + dataIndex: ['params', name], + key: name, + width: '20%', + render: tableCellRender(true), + ellipsis: { showTitle: false }, + })), + }, + { + title: '训练指标', + children: first?.metrics_names.map((name) => ({ + title: name, + dataIndex: ['metrics', name], + key: name, + width: '20%', + render: tableCellRender(true), + ellipsis: { showTitle: false }, + })), + }, + ]; + }, [tableData]); return (
- +
-
+
diff --git a/react-ui/src/pages/Experiment/training/index.jsx b/react-ui/src/pages/Experiment/Info/index.jsx similarity index 100% rename from react-ui/src/pages/Experiment/training/index.jsx rename to react-ui/src/pages/Experiment/Info/index.jsx diff --git a/react-ui/src/pages/Experiment/training/index.less b/react-ui/src/pages/Experiment/Info/index.less similarity index 100% rename from react-ui/src/pages/Experiment/training/index.less rename to react-ui/src/pages/Experiment/Info/index.less diff --git a/react-ui/src/pages/Experiment/training/props.less b/react-ui/src/pages/Experiment/Info/props.less similarity index 100% rename from react-ui/src/pages/Experiment/training/props.less rename to react-ui/src/pages/Experiment/Info/props.less diff --git a/react-ui/src/pages/Experiment/training/props.tsx b/react-ui/src/pages/Experiment/Info/props.tsx similarity index 100% rename from react-ui/src/pages/Experiment/training/props.tsx rename to react-ui/src/pages/Experiment/Info/props.tsx diff --git a/react-ui/src/pages/Experiment/components/ExperimentStatusCell/index.less b/react-ui/src/pages/Experiment/components/ExperimentStatusCell/index.less new file mode 100644 index 00000000..a5df71aa --- /dev/null +++ b/react-ui/src/pages/Experiment/components/ExperimentStatusCell/index.less @@ -0,0 +1,12 @@ +.experiment-status-cell { + height: 100%; + &__label { + display: none; + } +} + +.experiment-status-cell:hover { + .experiment-status-cell__label { + display: inline; + } +} diff --git a/react-ui/src/pages/Experiment/components/ExperimentStatusCell/index.tsx b/react-ui/src/pages/Experiment/components/ExperimentStatusCell/index.tsx new file mode 100644 index 00000000..373b6ffa --- /dev/null +++ b/react-ui/src/pages/Experiment/components/ExperimentStatusCell/index.tsx @@ -0,0 +1,28 @@ +/* + * @Author: 赵伟 + * @Date: 2024-04-18 18:35:41 + * @Description: 实验状态 + */ + +import { ExperimentStatus } from '@/enums'; +import { experimentStatusInfo as statusInfo } from '@/pages/Experiment/status'; +import styles from './index.less'; + +function ExperimentStatusCell(status?: ExperimentStatus | null) { + if (status === null || status === undefined || !statusInfo[status]) { + return --; + } + return ( +
+ + + {statusInfo[status]?.label} + +
+ ); +} + +export default ExperimentStatusCell; diff --git a/react-ui/src/pages/Experiment/components/LogGroup/index.tsx b/react-ui/src/pages/Experiment/components/LogGroup/index.tsx index 5c25b860..cd84406d 100644 --- a/react-ui/src/pages/Experiment/components/LogGroup/index.tsx +++ b/react-ui/src/pages/Experiment/components/LogGroup/index.tsx @@ -6,7 +6,7 @@ import { ExperimentStatus } from '@/enums'; import { useStateRef } from '@/hooks'; -import { ExperimentLog } from '@/pages/Experiment/training/props'; +import { ExperimentLog } from '@/pages/Experiment/Info/props'; import { getExperimentPodsLog } from '@/services/experiment/index.js'; import { DoubleRightOutlined, DownOutlined, UpOutlined } from '@ant-design/icons'; import { Button } from 'antd'; @@ -47,7 +47,7 @@ function LogGroup({ const [logList, setLogList, logListRef] = useStateRef([]); const [completed, setCompleted] = useState(false); // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [isMouseDown, setIsMouseDown, isMouseDownRef] = useStateRef(false); + const [_isMouseDown, setIsMouseDown, isMouseDownRef] = useStateRef(false); useEffect(() => { scrollToBottom(false); diff --git a/react-ui/src/pages/Experiment/components/LogList/index.tsx b/react-ui/src/pages/Experiment/components/LogList/index.tsx index d95224f1..0d833107 100644 --- a/react-ui/src/pages/Experiment/components/LogList/index.tsx +++ b/react-ui/src/pages/Experiment/components/LogList/index.tsx @@ -1,5 +1,5 @@ import { ExperimentStatus } from '@/enums'; -import { ExperimentLog } from '@/pages/Experiment/training/props'; +import { ExperimentLog } from '@/pages/Experiment/Info/props'; import LogGroup from '../LogGroup'; import styles from './index.less'; diff --git a/react-ui/src/pages/ModelDeployment/List/index.tsx b/react-ui/src/pages/ModelDeployment/List/index.tsx index bc1f03dd..934b4cbd 100644 --- a/react-ui/src/pages/ModelDeployment/List/index.tsx +++ b/react-ui/src/pages/ModelDeployment/List/index.tsx @@ -166,7 +166,7 @@ function ModelDeployment() { }; // 分页切换 - const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => { + const handleTableChange: TableProps['onChange'] = (pagination, _filters, _sorter, { action }) => { if (action === 'paginate') { setPagination(pagination); } @@ -179,7 +179,7 @@ function ModelDeployment() { dataIndex: 'index', key: 'index', width: '20%', - render(text, record, index) { + render(_text, _record, index) { return {(pagination.current! - 1) * pagination.pageSize! + index + 1}; }, }, diff --git a/react-ui/src/services/experiment/index.js b/react-ui/src/services/experiment/index.js index 89028b24..66d27eb6 100644 --- a/react-ui/src/services/experiment/index.js +++ b/react-ui/src/services/experiment/index.js @@ -126,7 +126,15 @@ export function getExpEvaluateInfosReq(experimentId) { // 获取当前实验的模型训练指标信息 export function getExpTrainInfosReq(experimentId) { - return request(`/api/mmp//aim/getExpTrainInfos/${experimentId}`, { + return request(`/api/mmp/aim/getExpTrainInfos/${experimentId}`, { method: 'GET', }); } + +// 获取当前实验的指标对比地址 +export function getExpMetricsReq(data) { + return request(`/api/mmp/aim/getExpMetrics`, { + method: 'POST', + data + }); +} diff --git a/react-ui/src/utils/table.tsx b/react-ui/src/utils/table.tsx new file mode 100644 index 00000000..3058284a --- /dev/null +++ b/react-ui/src/utils/table.tsx @@ -0,0 +1,68 @@ +/* + * @Author: 赵伟 + * @Date: 2024-06-26 10:05:52 + * @Description: 列表自定义 render + */ + +import { formatDate } from '@/utils/date'; +import { Tooltip } from 'antd'; +import dayjs from 'dayjs'; + +type TableCellFormatter = (value?: any | null) => string | undefined | null; + +// 字符串转换函数 +export const stringFormatter: TableCellFormatter = (value?: any | null) => { + return value; +}; + +// 日期转换函数 +export const dateFormatter: TableCellFormatter = (value?: any | null) => { + if (value === undefined || value === null || value === '') { + return null; + } + if (!dayjs(value).isValid()) { + return null; + } + return formatDate(value); +}; + +// 数组转换函数 +export function arrayFormatter(property?: string) { + return (value?: any | null): ReturnType => { + if ( + value === undefined || + value === null || + Array.isArray(value) === false || + value.length === 0 + ) { + return null; + } + + let list = value; + if (property && typeof value[0] === 'object') { + list = value.map((item) => item[property]); + } + return list.join(','); + }; +} + +function tableCellRender(ellipsis: boolean = false, format: TableCellFormatter = stringFormatter) { + return (value?: any | null) => { + const text = format(value); + if (ellipsis && text) { + return ( + + {renderCell(text)} + + ); + } else { + return renderCell(text); + } + }; +} + +function renderCell(text?: any | null) { + return {text ?? '--'}; +} + +export default tableCellRender;