Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
commit
7a0b480b15
|
@ -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',
|
||||
// },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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 <span>--</span>;
|
||||
}
|
||||
|
||||
let list = value;
|
||||
if (property && typeof value[0] === 'object') {
|
||||
list = value.map((item) => item[property]);
|
||||
}
|
||||
const text = list.join(',');
|
||||
if (ellipsis) {
|
||||
return (
|
||||
<Tooltip title={text} placement="topLeft" overlayStyle={{ maxWidth: '400px' }}>
|
||||
<span>{text}</span>;
|
||||
</Tooltip>
|
||||
);
|
||||
} else {
|
||||
return <span>{text}</span>;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default ArrayTableCell;
|
|
@ -6,13 +6,13 @@
|
|||
|
||||
import { Tooltip } from 'antd';
|
||||
|
||||
function renderCell(text?: string | null) {
|
||||
function renderCell(text?: any | null) {
|
||||
return <span>{text ?? '--'}</span>;
|
||||
}
|
||||
|
||||
function CommonTableCell(ellipsis: boolean = false) {
|
||||
if (ellipsis) {
|
||||
return (text?: string | null) => (
|
||||
return (text?: any | null) => (
|
||||
<Tooltip title={text} placement="topLeft" overlayStyle={{ maxWidth: '400px' }}>
|
||||
{renderCell(text)}
|
||||
</Tooltip>
|
||||
|
|
|
@ -1,9 +1,36 @@
|
|||
/*
|
||||
* @Author: 赵伟
|
||||
* @Date: 2024-06-07 11:22:28
|
||||
* @Description: 接口返回的枚举值和共用的枚举值定义在这里
|
||||
*/
|
||||
|
||||
// 公开还是私有 TabKey
|
||||
export enum CommonTabKeys {
|
||||
Private = 'Private', // 私有
|
||||
Public = 'Public', // 公开
|
||||
}
|
||||
|
||||
// 实验状态
|
||||
export enum ExperimentStatus {
|
||||
Running = 'Running', // 运行中
|
||||
Succeeded = 'Succeeded', // 成功
|
||||
Pending = 'Pending', // 启动中
|
||||
Failed = 'Failed', // 失败
|
||||
Error = 'Error', // 错误
|
||||
Terminated = 'Terminated', // 终止
|
||||
Skipped = 'Skipped', // 跳过
|
||||
Omitted = 'Omitted', // 忽略
|
||||
}
|
||||
|
||||
// TensorBoard 状态
|
||||
export enum TensorBoardStatus {
|
||||
Unknown = 'Unknown', // 未知
|
||||
Pending = 'Pending', // 启动中
|
||||
Running = 'Running', // 运行中
|
||||
Terminated = 'Terminated', // 未启动或者已终止
|
||||
Failed = 'Failed', // 失败
|
||||
}
|
||||
|
||||
// 镜像版本状态
|
||||
export enum MirrorVersionStatus {
|
||||
Available = 'available', // 可用
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -8,10 +8,11 @@ import { ResourceData, ResourceType, resourceConfig } from '../../config';
|
|||
import ResourceVersion from '../ResourceVersion';
|
||||
import styles from './index.less';
|
||||
|
||||
// 这里值小写是因为值会写在 url 中
|
||||
export enum ResourceInfoTabKeys {
|
||||
Introduction = 'introduction',
|
||||
Version = 'version',
|
||||
Evolution = 'evolution',
|
||||
Introduction = 'introduction', // 简介
|
||||
Version = 'version', // 版本
|
||||
Evolution = 'evolution', // 演化
|
||||
}
|
||||
|
||||
type ResourceIntroProps = {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,32 +1,50 @@
|
|||
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 {
|
||||
Train = 'train', // 训练
|
||||
Evaluate = 'evaluate', // 评估
|
||||
Train = 'Train', // 训练
|
||||
Evaluate = 'Evaluate', // 评估
|
||||
}
|
||||
|
||||
type TableData = {
|
||||
experiment_ins_id: number;
|
||||
run_id: string;
|
||||
dataset: string[];
|
||||
start_time: string;
|
||||
status: string;
|
||||
metrics_names: string[];
|
||||
metrics: Record<string, number>;
|
||||
params_names: string[];
|
||||
params: Record<string, string>;
|
||||
};
|
||||
|
||||
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<TableData[]>([]);
|
||||
// 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,
|
||||
},
|
||||
);
|
||||
const { message } = App.useApp();
|
||||
// const [pagination, setPagination] = useState<TablePaginationConfig>(
|
||||
// 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);
|
||||
}
|
||||
};
|
||||
|
||||
// 获取对比 url
|
||||
const getExpMetrics = async () => {
|
||||
const [res] = await to(getExpMetricsReq(selectedRowKeys));
|
||||
if (res && res.data) {
|
||||
const url = res.data;
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
};
|
||||
|
||||
// 对比按钮 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 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 (
|
||||
<div className={styles['experiment-comparison']}>
|
||||
<div className={styles['experiment-comparison__header']}>
|
||||
<Button type="default">可视化对比</Button>
|
||||
<Button type="default" onClick={hanldeComparisonClick}>
|
||||
可视化对比
|
||||
</Button>
|
||||
</div>
|
||||
<div className={classNames('vertical-scroll-table', styles['experiment-comparison__table'])}>
|
||||
<div
|
||||
className={classNames(
|
||||
'vertical-scroll-table-no-page',
|
||||
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"
|
||||
pagination={false}
|
||||
bordered={true}
|
||||
// pagination={{
|
||||
// ...pagination,
|
||||
// total: total,
|
||||
// showSizeChanger: true,
|
||||
// showQuickJumper: true,
|
||||
// }}
|
||||
// onChange={handleTableChange}
|
||||
rowKey="run_id"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
.experiment-status-cell {
|
||||
height: 100%;
|
||||
&__label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.experiment-status-cell:hover {
|
||||
.experiment-status-cell__label {
|
||||
display: inline;
|
||||
}
|
||||
}
|
|
@ -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 <span>--</span>;
|
||||
}
|
||||
return (
|
||||
<div className={styles['experiment-status-cell']}>
|
||||
<img style={{ width: '17px', marginRight: '7px' }} src={statusInfo[status]?.icon} />
|
||||
<span
|
||||
style={{ color: statusInfo[status]?.color }}
|
||||
className={styles['experiment-status-cell__label']}
|
||||
>
|
||||
{statusInfo[status]?.label}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ExperimentStatusCell;
|
|
@ -4,9 +4,9 @@
|
|||
* @Description: 日志组件
|
||||
*/
|
||||
|
||||
import { ExperimentStatus } from '@/enums';
|
||||
import { useStateRef } from '@/hooks';
|
||||
import { ExperimentStatus } from '@/pages/Experiment/status';
|
||||
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<Log[]>([]);
|
||||
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);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { ExperimentStatus } from '@/pages/Experiment/status';
|
||||
import { ExperimentLog } from '@/pages/Experiment/training/props';
|
||||
import { ExperimentStatus } from '@/enums';
|
||||
import { ExperimentLog } from '@/pages/Experiment/Info/props';
|
||||
import LogGroup from '../LogGroup';
|
||||
import styles from './index.less';
|
||||
|
||||
|
|
|
@ -5,16 +5,15 @@ import classNames from 'classnames';
|
|||
import styles from './index.less';
|
||||
// import stopImg from '@/assets/img/tensor-board-stop.png';
|
||||
import terminatedImg from '@/assets/img/tensor-board-terminated.png';
|
||||
import { TensorBoardStatus } from '@/enums';
|
||||
|
||||
export enum TensorBoardStatusEnum {
|
||||
Unknown = 'Unknown', // 未知
|
||||
Pending = 'Pending', // 启动中
|
||||
Running = 'Running', // 运行中
|
||||
Terminated = 'Terminated', // 未启动或者已终止
|
||||
Failed = 'Failed', // 失败
|
||||
}
|
||||
type TensorBoardStatusInfo = {
|
||||
label: string;
|
||||
icon: string;
|
||||
classname: string;
|
||||
};
|
||||
|
||||
const statusConfig = {
|
||||
const statusConfig: Record<TensorBoardStatus, TensorBoardStatusInfo> = {
|
||||
Unknown: {
|
||||
label: '未知',
|
||||
icon: terminatedImg,
|
||||
|
@ -43,12 +42,12 @@ const statusConfig = {
|
|||
};
|
||||
|
||||
type TensorBoardStatusProps = {
|
||||
status: TensorBoardStatusEnum;
|
||||
status: TensorBoardStatus;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
function TensorBoardStatus({
|
||||
status = TensorBoardStatusEnum.Unknown,
|
||||
function TensorBoardStatusCell({
|
||||
status = TensorBoardStatus.Unknown,
|
||||
onClick,
|
||||
}: TensorBoardStatusProps) {
|
||||
return (
|
||||
|
@ -64,7 +63,7 @@ function TensorBoardStatus({
|
|||
{statusConfig[status].icon ? (
|
||||
<>
|
||||
<div style={{ margin: '0 6px' }}>|</div>
|
||||
{status === TensorBoardStatusEnum.Pending ? (
|
||||
{status === TensorBoardStatus.Pending ? (
|
||||
<LoadingOutlined className={styles['tensorBoard-status__icon']} />
|
||||
) : (
|
||||
<img
|
||||
|
@ -79,4 +78,4 @@ function TensorBoardStatus({
|
|||
);
|
||||
}
|
||||
|
||||
export default TensorBoardStatus;
|
||||
export default TensorBoardStatusCell;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import CommonTableCell from '@/components/CommonTableCell';
|
||||
import KFIcon from '@/components/KFIcon';
|
||||
import { TensorBoardStatus } from '@/enums';
|
||||
import {
|
||||
deleteExperimentById,
|
||||
deleteQueryByExperimentInsId,
|
||||
|
@ -24,7 +25,7 @@ import { useEffect, useRef, useState } from 'react';
|
|||
import { useNavigate } from 'react-router-dom';
|
||||
import { ComparisonType } from './Comparison';
|
||||
import AddExperimentModal from './components/AddExperimentModal';
|
||||
import TensorBoardStatus, { TensorBoardStatusEnum } from './components/TensorBoardStatus';
|
||||
import TensorBoardStatusCell from './components/TensorBoardStatus';
|
||||
import Styles from './index.less';
|
||||
import { experimentStatusInfo } from './status';
|
||||
|
||||
|
@ -260,12 +261,12 @@ function Experiment() {
|
|||
|
||||
const handleTensorboard = async (experimentIn) => {
|
||||
if (
|
||||
experimentIn.tensorBoardStatus === TensorBoardStatusEnum.Terminated ||
|
||||
experimentIn.tensorBoardStatus === TensorBoardStatusEnum.Failed
|
||||
experimentIn.tensorBoardStatus === TensorBoardStatus.Terminated ||
|
||||
experimentIn.tensorBoardStatus === TensorBoardStatus.Failed
|
||||
) {
|
||||
await runTensorBoard(experimentIn);
|
||||
} else if (
|
||||
experimentIn.tensorBoardStatus === TensorBoardStatusEnum.Running &&
|
||||
experimentIn.tensorBoardStatus === TensorBoardStatus.Running &&
|
||||
experimentIn.tensorboardUrl
|
||||
) {
|
||||
window.open(experimentIn.tensorboardUrl, '_blank');
|
||||
|
@ -457,12 +458,12 @@ function Experiment() {
|
|||
</a>
|
||||
<div className={Styles.tensorBoard}>
|
||||
{item.nodes_result?.tensorboard_log ? (
|
||||
<TensorBoardStatus
|
||||
<TensorBoardStatusCell
|
||||
status={item.tensorBoardStatus}
|
||||
onClick={() => handleTensorboard(item)}
|
||||
></TensorBoardStatus>
|
||||
></TensorBoardStatusCell>
|
||||
) : (
|
||||
'-'
|
||||
'--'
|
||||
)}
|
||||
</div>
|
||||
<div className={Styles.description}>
|
||||
|
|
|
@ -1,23 +1,13 @@
|
|||
import { ExperimentStatus } from '@/enums';
|
||||
import themes from '@/styles/theme.less';
|
||||
|
||||
export interface StatusInfo {
|
||||
export interface ExperimentStatusInfo {
|
||||
label: string;
|
||||
color: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
export enum ExperimentStatus {
|
||||
Running = 'Running',
|
||||
Succeeded = 'Succeeded',
|
||||
Pending = 'Pending',
|
||||
Failed = 'Failed',
|
||||
Error = 'Error',
|
||||
Terminated = 'Terminated',
|
||||
Skipped = 'Skipped',
|
||||
Omitted = 'Omitted',
|
||||
}
|
||||
|
||||
export const experimentStatusInfo: Record<ExperimentStatus, StatusInfo | undefined> = {
|
||||
export const experimentStatusInfo: Record<ExperimentStatus, ExperimentStatusInfo> = {
|
||||
Running: {
|
||||
label: '运行中',
|
||||
color: themes.primaryColor,
|
||||
|
|
|
@ -17,9 +17,9 @@ import { ModelDeploymentData } from '../types';
|
|||
import styles from './index.less';
|
||||
|
||||
export enum ModelDeploymentTabKey {
|
||||
Predict = 'Predict',
|
||||
Guide = 'Guide',
|
||||
Log = 'Log',
|
||||
Predict = 'Predict', // 预测
|
||||
Guide = 'Guide', // 调用指南
|
||||
Log = 'Log', // 服务日志
|
||||
}
|
||||
|
||||
function ModelDeploymentInfo() {
|
||||
|
|
|
@ -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 <span>{(pagination.current! - 1) * pagination.pageSize! + index + 1}</span>;
|
||||
},
|
||||
},
|
||||
|
|
|
@ -26,7 +26,7 @@ export type ModelDeploymentData = {
|
|||
|
||||
// 操作类型
|
||||
export enum ModelDeploymentOperationType {
|
||||
Create = 'Create',
|
||||
Update = 'Update',
|
||||
Restart = 'Restart',
|
||||
Create = 'Create', // 创建
|
||||
Update = 'Update', // 更新
|
||||
Restart = 'Restart', // 重启
|
||||
}
|
||||
|
|
|
@ -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
|
||||
});
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* @Description: 定义全局类型,比如无关联的页面都需要要的类型
|
||||
*/
|
||||
|
||||
import { ExperimentStatus } from '@/pages/Experiment/status';
|
||||
import { ExperimentStatus } from '@/enums';
|
||||
|
||||
// 流水线全局参数
|
||||
export type PipelineGlobalParam = {
|
||||
|
|
|
@ -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<TableCellFormatter> => {
|
||||
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 (
|
||||
<Tooltip title={text} placement="topLeft" overlayStyle={{ maxWidth: '400px' }}>
|
||||
{renderCell(text)}
|
||||
</Tooltip>
|
||||
);
|
||||
} else {
|
||||
return renderCell(text);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function renderCell(text?: any | null) {
|
||||
return <span>{text ?? '--'}</span>;
|
||||
}
|
||||
|
||||
export default tableCellRender;
|
|
@ -23,8 +23,6 @@ import java.util.stream.Collectors;
|
|||
public class AimServiceImpl implements AimService {
|
||||
@Resource
|
||||
private ExperimentInsService experimentInsService;
|
||||
@Resource
|
||||
private ModelDependencyService modelDependencyService;
|
||||
|
||||
@Value("${aim.url}")
|
||||
private String aimUrl;
|
||||
|
@ -44,7 +42,7 @@ public class AimServiceImpl implements AimService {
|
|||
@Override
|
||||
public String getExpMetrics(List<String> runIds) throws Exception {
|
||||
String decode = AIM64EncoderUtil.decode(runIds);
|
||||
return aimUrl+"/api/runs/search/run?query="+decode;
|
||||
return aimUrl+"/metrics?select="+decode;
|
||||
}
|
||||
|
||||
private List<InsMetricInfoVo> getAimRunInfos(boolean isTrain,Integer experimentId) throws Exception {
|
||||
|
@ -56,6 +54,7 @@ public class AimServiceImpl implements AimService {
|
|||
String url = aimProxyUrl+"/api/runs/search/run?query="+encodedUrlString;
|
||||
String s = HttpUtils.sendGetRequest(url);
|
||||
List<Map<String, Object>> response = JacksonUtil.parseJSONStr2MapList(s);
|
||||
System.out.println("response: "+JacksonUtil.toJSONString(response));
|
||||
if (response == null || response.size() == 0){
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
@ -103,20 +102,15 @@ public class AimServiceImpl implements AimService {
|
|||
aimRunInfo.setStatus(ins.getStatus());
|
||||
aimRunInfo.setStartTime(ins.getCreateTime());
|
||||
Map<String, Object> metricRecordMap = JacksonUtil.parseJSONStr2Map(metricRecordString);
|
||||
|
||||
//metricRecord 格式为{"train":[{"task_id":"model-train-35303690","run_id":"5560d78f54314672b60304c8d6ba03b8","experiment_name":"experiment-30-train"}],"evaluate":[{"task_id":"model-train-35303690","run_id":"5560d78f54314672b60304c8d6ba03b8","experiment_name":"experiment-30-train"}]}
|
||||
//遍历metricRecord,找到当前task_id对应的ModelDependency
|
||||
|
||||
if (isTrain){
|
||||
List<Map<String, Object>> trainList = (List<Map<String, Object>>) metricRecordMap.get("train");
|
||||
List<String> trainDateSet = getTrainDateSet(trainList, ins.getId(), isTrain);
|
||||
aimRunInfo.setDataset(trainDateSet);
|
||||
List<Map<String, Object>> records = (List<Map<String, Object>>) metricRecordMap.get("train");
|
||||
List<String> datasetList = getTrainDateSet(records, aimrunId);
|
||||
aimRunInfo.setDataset(datasetList);
|
||||
}else {
|
||||
List<Map<String, Object>> trainList = (List<Map<String, Object>>) metricRecordMap.get("evaluate");
|
||||
List<String> trainDateSet = getTrainDateSet(trainList, ins.getId(), isTrain);
|
||||
aimRunInfo.setDataset(trainDateSet);
|
||||
List<Map<String, Object>> records = (List<Map<String, Object>>) metricRecordMap.get("evaluate");
|
||||
List<String> datasetList = getTrainDateSet(records, aimrunId);
|
||||
aimRunInfo.setDataset(datasetList);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
aimRunInfoList.add(aimRunInfo);
|
||||
|
@ -143,33 +137,21 @@ public class AimServiceImpl implements AimService {
|
|||
}
|
||||
|
||||
|
||||
private List<String> getTrainDateSet(List<Map<String, Object>> trainList,Integer expInsId,boolean isTrain){
|
||||
if (trainList == null || trainList.size() == 0){
|
||||
return new ArrayList<>();
|
||||
}
|
||||
private List<String> getTrainDateSet(List<Map<String, Object>> records, String aimrunId){
|
||||
List<String> datasetList = new ArrayList<>();
|
||||
for (Map<String, Object> trainMap : trainList) {
|
||||
String task_id = (String) trainMap.get("task_id");
|
||||
//modelDependency取到数据集文件
|
||||
ModelDependency modelDependency = modelDependencyService.queryByInsAndTrainTaskId(expInsId, task_id);
|
||||
//把数据集文件组装成String后放进List
|
||||
String datasetString = "";
|
||||
if (isTrain){
|
||||
datasetString = modelDependency.getTrainDataset();
|
||||
}else {
|
||||
datasetString = modelDependency.getTestDataset();
|
||||
}
|
||||
List<Map<String, Object>> datasetListMap = JacksonUtil.parseJSONStr2MapList(datasetString);
|
||||
|
||||
if (datasetListMap != null && datasetListMap.size() > 0){
|
||||
for (Map<String, Object> datasetMap : datasetListMap) {
|
||||
//[{"dataset_id":20,"dataset_version":"v0.1.0","dataset_name":"手写体识别模型依赖测试训练数据集"}]
|
||||
String datasetName = (String) datasetMap.get("dataset_name")+":"+(String) datasetMap.get("dataset_version");
|
||||
for (Map<String, Object> record : records) {
|
||||
if (StringUtils.equals(aimrunId, (String)record.get("run_id"))) {
|
||||
List<Map<String, Object>> datasets = (List<Map<String, Object>>) record.get("datasets");
|
||||
if (datasets == null || datasets.size() == 0){
|
||||
continue;
|
||||
}
|
||||
for (Map<String, Object> dataset : datasets){
|
||||
String datasetName = (String) dataset.get("dataset_name")+":"+(String) dataset.get("dataset_version");
|
||||
datasetList.add(datasetName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return datasetList;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -251,16 +251,14 @@ public class ExperimentServiceImpl implements ExperimentService {
|
|||
if (data == null || MapUtils.isEmpty(data)) {
|
||||
throw new RuntimeException("Failed to run workflow.");
|
||||
}
|
||||
//获取训练参数
|
||||
Map<String, Object> metricRecord = (Map<String, Object>) runResMap.get("metric_record");
|
||||
|
||||
|
||||
|
||||
Map<String, Object> metadata = (Map<String, Object>) data.get("metadata");
|
||||
// 插入记录到实验实例表
|
||||
ExperimentIns experimentIns = new ExperimentIns();
|
||||
if (metricRecord != null){
|
||||
experimentIns.setMetricRecord(JacksonUtil.toJSONString(metricRecord));
|
||||
}
|
||||
//获取训练参数
|
||||
|
||||
experimentIns.setExperimentId(experiment.getId());
|
||||
experimentIns.setArgoInsNs((String) metadata.get("namespace"));
|
||||
experimentIns.setArgoInsName((String) metadata.get("name"));
|
||||
|
@ -271,14 +269,22 @@ public class ExperimentServiceImpl implements ExperimentService {
|
|||
//替换argoInsName
|
||||
String outputString = JsonUtils.mapToJson(output);
|
||||
experimentIns.setNodesResult(outputString.replace("{{workflow.name}}", (String) metadata.get("name")));
|
||||
//插入ExperimentIns表中
|
||||
ExperimentIns insert = experimentInsService.insert(experimentIns);
|
||||
|
||||
//插入到模型依赖关系表
|
||||
|
||||
//得到dependendcy
|
||||
Map<String, Object> converMap2 = JsonUtils.jsonToMap(JacksonUtil.replaceInAarry(convertRes, params));
|
||||
Map<String ,Object> dependendcy = (Map<String, Object>)converMap2.get("model_dependency");
|
||||
Map<String ,Object> trainInfo = (Map<String, Object>)converMap2.get("component_info");
|
||||
|
||||
Map<String, Object> metricRecord = (Map<String, Object>) runResMap.get("metric_record");
|
||||
if (metricRecord != null){
|
||||
//把训练用的数据集也放进去
|
||||
addDatesetToMetric(metricRecord, trainInfo);
|
||||
experimentIns.setMetricRecord(JacksonUtil.toJSONString(metricRecord));
|
||||
}
|
||||
//插入ExperimentIns表中
|
||||
ExperimentIns insert = experimentInsService.insert(experimentIns);
|
||||
//插入到模型依赖关系表
|
||||
if (dependendcy != null && trainInfo != null){
|
||||
insertModelDependency(dependendcy,trainInfo,insert.getId(),experiment.getName());
|
||||
}
|
||||
|
@ -289,6 +295,37 @@ public class ExperimentServiceImpl implements ExperimentService {
|
|||
experiment.setExperimentInsList(updatedExperimentInsList);
|
||||
return experiment;
|
||||
}
|
||||
private void addDatesetToMetric(Map<String, Object> metricRecord, Map<String, Object> trainInfo) {
|
||||
processMetricPart(metricRecord, trainInfo, "train", "model_train");
|
||||
processMetricPart(metricRecord, trainInfo, "evaluate", "model_evaluate");
|
||||
}
|
||||
|
||||
private void processMetricPart(Map<String, Object> metricRecord, Map<String, Object> trainInfo, String metricKey, String trainInfoKey) {
|
||||
List<Map<String, Object>> metricList = (List<Map<String, Object>>) metricRecord.get(metricKey);
|
||||
if (metricList != null) {
|
||||
for (Map<String, Object> metricRecordItem : metricList) {
|
||||
String taskId = (String) metricRecordItem.get("task_id");
|
||||
Map<String, Object> trainInfoPart = (Map<String, Object>) trainInfo.get(trainInfoKey);
|
||||
if (trainInfoPart != null) {
|
||||
Map<String, Object> trainInfoDetails = (Map<String, Object>) trainInfoPart.get(taskId);
|
||||
if (trainInfoDetails != null) {
|
||||
List<Map<String, Object>> datasets = (List<Map<String, Object>>) trainInfoDetails.get("datasets");
|
||||
if (datasets != null) {
|
||||
//查询名字再回填
|
||||
for (int i = 0; i < datasets.size(); i++) {
|
||||
Dataset dataset = datasetService.queryById((Integer) datasets.get(i).get("dataset_id"));
|
||||
datasets.get(i).put("dataset_name", dataset.getName());
|
||||
}
|
||||
metricRecordItem.put("datasets", datasets);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void insertModelDependency(Map<String ,Object> dependendcy,Map<String ,Object> trainInfo, Integer experimentInsId, String experimentName) throws Exception {
|
||||
Iterator<Map.Entry<String, Object>> dependendcyIterator = dependendcy.entrySet().iterator();
|
||||
Map<String, Object> modelTrain = (Map<String, Object>) trainInfo.get("model_train");
|
||||
|
|
|
@ -306,7 +306,7 @@
|
|||
global_param = #{experimentIns.globalParam},
|
||||
</if>
|
||||
<if test="experimentIns.metricRecord != null and experimentIns.metricRecord != ''">
|
||||
and metric_record = #{experimentIns.metricRecord}
|
||||
metric_record = #{experimentIns.metricRecord},
|
||||
</if>
|
||||
<if test="experimentIns.startTime != null">
|
||||
start_time = #{experimentIns.startTime},
|
||||
|
|
Loading…
Reference in New Issue