| @@ -96,6 +96,11 @@ export default [ | |||
| path: ':workflowId/:id', | |||
| component: './Experiment/training/index', | |||
| }, | |||
| { | |||
| name: '实验对比', | |||
| path: 'compare', | |||
| component: './Experiment/Comparison/index', | |||
| }, | |||
| ], | |||
| }, | |||
| ], | |||
| @@ -58,7 +58,7 @@ const ResourceIntro = ({ resourceType }: ResourceIntroProps) => { | |||
| }; | |||
| }), | |||
| ); | |||
| if (versionParam) { | |||
| if (versionParam && res.data.includes(versionParam)) { | |||
| setVersion(versionParam); | |||
| versionParam = null; | |||
| } else { | |||
| @@ -38,6 +38,7 @@ export type EditorData = { | |||
| computing_resource: string; | |||
| update_by: string; | |||
| create_time: string; | |||
| url: string; | |||
| }; | |||
| function EditorList() { | |||
| @@ -140,7 +141,14 @@ function EditorList() { | |||
| dataIndex: 'name', | |||
| key: 'name', | |||
| width: '30%', | |||
| render: CommonTableCell(), | |||
| render: (text, record) => | |||
| record.url ? ( | |||
| <a href={record.url} target="_blank" rel="noreferrer"> | |||
| {text} | |||
| </a> | |||
| ) : ( | |||
| <span>{text ?? '--'}</span> | |||
| ), | |||
| }, | |||
| { | |||
| title: '状态', | |||
| @@ -0,0 +1,21 @@ | |||
| .experiment-comparison { | |||
| height: 100%; | |||
| &__header { | |||
| display: flex; | |||
| align-items: center; | |||
| height: 50px; | |||
| margin-bottom: 10px; | |||
| padding: 0 30px; | |||
| background-image: url(@/assets/img/page-title-bg.png); | |||
| background-repeat: no-repeat; | |||
| background-position: top center; | |||
| background-size: 100% 100%; | |||
| } | |||
| &__table { | |||
| height: calc(100% - 60px); | |||
| padding: 20px 30px 0; | |||
| background-color: white; | |||
| border-radius: 10px; | |||
| } | |||
| } | |||
| @@ -0,0 +1,212 @@ | |||
| import CommonTableCell from '@/components/CommonTableCell'; | |||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||
| import { getExpEvaluateInfosReq, getExpTrainInfosReq } from '@/services/experiment'; | |||
| import { to } from '@/utils/promise'; | |||
| import { useSearchParams } from '@umijs/max'; | |||
| import { Button, Table, TablePaginationConfig, TableProps } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import { useEffect, useState } from 'react'; | |||
| import styles from './index.less'; | |||
| export enum ComparisonType { | |||
| Train = 'train', | |||
| Evaluate = 'evaluate', | |||
| } | |||
| 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 [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]); | |||
| const [pagination, setPagination] = useState<TablePaginationConfig>( | |||
| cacheState?.pagination ?? { | |||
| current: 1, | |||
| pageSize: 10, | |||
| }, | |||
| ); | |||
| useEffect(() => { | |||
| getComparisonData(); | |||
| }, [experimentId]); | |||
| // 获取对比数据列表 | |||
| const getComparisonData = async () => { | |||
| const request = | |||
| 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 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, | |||
| onChange: (selectedRowKeys: React.Key[], selectedRows: any[]) => { | |||
| console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows); | |||
| setSelectedRowKeys(selectedRowKeys); | |||
| }, | |||
| }; | |||
| 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(), | |||
| }, | |||
| ], | |||
| }, | |||
| ]; | |||
| return ( | |||
| <div className={styles['experiment-comparison']}> | |||
| <div className={styles['experiment-comparison__header']}> | |||
| <Button type="default">可视化对比</Button> | |||
| </div> | |||
| <div className={classNames('vertical-scroll-table', styles['experiment-comparison__table'])}> | |||
| <Table | |||
| dataSource={tableData} | |||
| columns={columns} | |||
| rowSelection={rowSelection} | |||
| scroll={{ y: 'calc(100% - 55px)' }} | |||
| pagination={{ | |||
| ...pagination, | |||
| total: total, | |||
| showSizeChanger: true, | |||
| showQuickJumper: true, | |||
| }} | |||
| onChange={handleTableChange} | |||
| rowKey="id" | |||
| /> | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| export default ExperimentComparison; | |||
| @@ -18,11 +18,12 @@ import themes from '@/styles/theme.less'; | |||
| import { elapsedTime, formatDate } from '@/utils/date'; | |||
| import { to } from '@/utils/promise'; | |||
| import { modalConfirm } from '@/utils/ui'; | |||
| import { App, Button, ConfigProvider, Space, Table, Tooltip } from 'antd'; | |||
| import { App, Button, ConfigProvider, Dropdown, Space, Table, Tooltip } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import { useEffect, useRef, useState } from 'react'; | |||
| import { useNavigate } from 'react-router-dom'; | |||
| import AddExperimentModal from './components/AddExperimentModal'; | |||
| import { ComparisonType } from './components/ComparisonModal/config'; | |||
| import TensorBoardStatus, { TensorBoardStatusEnum } from './components/TensorBoardStatus'; | |||
| import Styles from './index.less'; | |||
| import { experimentStatusInfo } from './status'; | |||
| @@ -270,6 +271,26 @@ function Experiment() { | |||
| window.open(experimentIn.tensorboardUrl, '_blank'); | |||
| } | |||
| }; | |||
| // 实验对比菜单 | |||
| const getComparisonMenu = (experimentId) => { | |||
| return { | |||
| items: [ | |||
| { | |||
| label: <span>训练对比</span>, | |||
| key: ComparisonType.Train, | |||
| }, | |||
| { | |||
| label: <span>评估对比</span>, | |||
| key: ComparisonType.Evaluate, | |||
| }, | |||
| ], | |||
| onClick: ({ key }) => { | |||
| navgite(`/pipeline/experiment/compare?type=${key}&id=${experimentId}`); | |||
| }, | |||
| }; | |||
| }; | |||
| const columns = [ | |||
| { | |||
| title: '实验名称', | |||
| @@ -320,7 +341,7 @@ function Experiment() { | |||
| { | |||
| title: '操作', | |||
| key: 'action', | |||
| width: 300, | |||
| width: 350, | |||
| render: (_, record) => ( | |||
| <Space size="small"> | |||
| <Button | |||
| @@ -345,6 +366,14 @@ function Experiment() { | |||
| > | |||
| 编辑 | |||
| </Button> | |||
| <Dropdown key="comparison" menu={getComparisonMenu(record.id)}> | |||
| <a onClick={(e) => e.preventDefault()}> | |||
| <Space style={{ padding: '0 7px' }}> | |||
| <KFIcon type="icon-shiyanduibi" /> | |||
| 实验对比 | |||
| </Space> | |||
| </a> | |||
| </Dropdown> | |||
| <ConfigProvider | |||
| theme={{ | |||
| token: { | |||
| @@ -58,7 +58,7 @@ | |||
| } | |||
| .operation { | |||
| width: 284px; | |||
| width: 334px; | |||
| } | |||
| } | |||
| .tableExpandBoxContent { | |||
| @@ -40,7 +40,7 @@ export const requestConfig: RequestConfig = { | |||
| [ | |||
| (response: AxiosResponse) => { | |||
| const { status, data } = response || {}; | |||
| console.log(message, data); | |||
| // console.log(message, data); | |||
| if (status >= 200 && status < 300) { | |||
| if (data && (data instanceof Blob || data.code === 200)) { | |||
| return response; | |||
| @@ -116,3 +116,17 @@ export function getTensorBoardStatusReq(data) { | |||
| data, | |||
| }); | |||
| } | |||
| // 获取当前实验的模型推理指标信息 | |||
| export function getExpEvaluateInfosReq(experimentId) { | |||
| return request(`/api/mmp/aim/getExpEvaluateInfos/${experimentId}`, { | |||
| method: 'GET', | |||
| }); | |||
| } | |||
| // 获取当前实验的模型训练指标信息 | |||
| export function getExpTrainInfosReq(experimentId) { | |||
| return request(`/api/mmp//aim/getExpTrainInfos/${experimentId}`, { | |||
| method: 'GET', | |||
| }); | |||
| } | |||
| @@ -60,8 +60,8 @@ function patchRouteItems(route: any, menu: any, parentPath: string) { | |||
| element: React.createElement(lazy(() => import('@/pages/' + path))), | |||
| path: parentPath + menuItem.path, | |||
| }; | |||
| console.log(newRoute); | |||
| // console.log(newRoute); | |||
| route.children.push(newRoute); | |||
| route.routes.push(newRoute); | |||
| } | |||
| @@ -74,10 +74,7 @@ export function patchRouteWithRemoteMenus(routes: any) { | |||
| } | |||
| let proLayout = null; | |||
| for (const routeItem of routes) { | |||
| if (routeItem.id === 'ant-design-pro-layout') { | |||
| proLayout = routeItem; | |||
| break; | |||
| } | |||
| @@ -101,7 +98,6 @@ export async function refreshToken() { | |||
| } | |||
| export function convertCompatRouters(childrens: API.RoutersMenuItem[]): any[] { | |||
| return childrens.map((item: API.RoutersMenuItem) => { | |||
| return { | |||
| path: item.path, | |||
| @@ -149,12 +145,11 @@ export function getMatchMenuItem( | |||
| const subpath = path.substr(item.path.length + 1); | |||
| const subItem: MenuDataItem[] = getMatchMenuItem(subpath, item.routes); | |||
| items = items.concat(subItem); | |||
| } else { | |||
| const paths = path.split('/'); | |||
| if (paths.length >= 2 && paths[0] === item.path && paths[1] === 'index') { | |||
| console.log(item); | |||
| items.push(item); | |||
| } | |||
| } | |||