Browse Source

feat: 完成数据集和模型对比

pull/169/head
cp3hnu 1 year ago
parent
commit
204ffb1ca3
14 changed files with 571 additions and 20 deletions
  1. BIN
      react-ui/src/assets/img/dataset-version.png
  2. +1
    -1
      react-ui/src/pages/CodeConfig/components/AddCodeConfigModal/index.tsx
  3. +0
    -1
      react-ui/src/pages/Dataset/components/CategoryItem/index.tsx
  4. +31
    -3
      react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx
  5. +5
    -5
      react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx
  6. +120
    -0
      react-ui/src/pages/Dataset/components/VersionCompareModal/index.less
  7. +236
    -0
      react-ui/src/pages/Dataset/components/VersionCompareModal/index.tsx
  8. +59
    -0
      react-ui/src/pages/Dataset/components/VersionSelectorModal/index.less
  9. +63
    -0
      react-ui/src/pages/Dataset/components/VersionSelectorModal/index.tsx
  10. +5
    -0
      react-ui/src/pages/Dataset/config.tsx
  11. +7
    -6
      react-ui/src/pages/Model/components/ModelMetrics/index.tsx
  12. +5
    -4
      react-ui/src/pages/ModelDeployment/components/VersionCompareModal/index.tsx
  13. +16
    -0
      react-ui/src/services/dataset/index.js
  14. +23
    -0
      react-ui/tests/date.test.ts

BIN
react-ui/src/assets/img/dataset-version.png View File

Before After
Width: 78  |  Height: 76  |  Size: 5.1 kB

+ 1
- 1
react-ui/src/pages/CodeConfig/components/AddCodeConfigModal/index.tsx View File

@@ -93,7 +93,7 @@ function AddCodeConfigModal({ opType, codeConfigData, onOk, ...rest }: AddCodeCo
return (
<KFModal
{...rest}
title="新建代码配置"
title={opType === OperationType.Create ? '新建代码配置' : '修改代码配置'}
image={require('@/assets/img/create-experiment.png')}
width={825}
okButtonProps={{


+ 0
- 1
react-ui/src/pages/Dataset/components/CategoryItem/index.tsx View File

@@ -39,7 +39,6 @@ function CategoryItem({ resourceType, item, isSelected, onClick }: CategoryItemP
>
{item.name}
</Typography.Text>
{/* <span className={styles['category-item__name']}>{item.name}</span> */}
</div>
);
}


+ 31
- 3
react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx View File

@@ -22,6 +22,8 @@ import { useEffect, useState } from 'react';
import AddVersionModal from '../AddVersionModal';
import ResourceIntro from '../ResourceIntro';
import ResourceVersion from '../ResourceVersion';
import VersionCompareModal from '../VersionCompareModal';
import VersionSelectorModal from '../VersionSelectorModal';
import styles from './index.less';

// 这里值小写是因为值会写在 url 中
@@ -47,7 +49,7 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => {
const name = searchParams.get('name') || '';
const owner = searchParams.get('owner') || '';
const identifier = searchParams.get('identifier') || '';
const is_public = searchParams.get('is_public') || '';
const is_public = (searchParams.get('is_public') || '') === 'true';
const [versionList, setVersionList] = useState<ResourceVersionData[]>([]);
const [version, setVersion] = useState<string | undefined>(undefined);
const [activeTab, setActiveTab] = useState<string>(defaultTab);
@@ -67,7 +69,7 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => {
name,
identifier,
version,
is_public: is_public === 'true',
is_public: is_public,
});
}
}, [version]);
@@ -121,7 +123,7 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => {
resoureName: info.name,
owner: info.owner,
identifier: info.identifier,
is_public: info.is_public,
is_public: is_public,
onOk: () => {
getVersionList();
close();
@@ -129,6 +131,29 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => {
});
};

// 选择版本
const showVersionSelector = () => {
const { close } = openAntdModal(VersionSelectorModal, {
versions: versionList,
onOk: (version: string[]) => {
showVersionComparison(version);
close();
},
});
};

// 版本对比
const showVersionComparison = (versions: string[]) => {
openAntdModal(VersionCompareModal, {
versions: versions,
resourceType: resourceType,
repo_id: resourceId,
owner: info.owner,
identifier: info.identifier,
is_public: is_public,
});
};

// 版本变化
const handleVersionChange = (value: string) => {
setVersion(value);
@@ -237,6 +262,9 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => {
<Button type="default" onClick={showModal} icon={<KFIcon type="icon-xinjian2" />}>
创建新版本
</Button>
<Button type="default" style={{ marginLeft: '20px' }} onClick={showVersionSelector}>
版本对比
</Button>
<Button
type="default"
style={{ marginLeft: 'auto', marginRight: 0 }}


+ 5
- 5
react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx View File

@@ -24,7 +24,7 @@ type ResourceIntroProps = {
version?: string;
};

const formatDataset = (datasets?: DatasetData[]) => {
export const formatDataset = (datasets?: DatasetData[]) => {
if (!datasets || datasets.length === 0) {
return undefined;
}
@@ -34,7 +34,7 @@ const formatDataset = (datasets?: DatasetData[]) => {
}));
};

const getProjectUrl = (project?: ProjectDependency) => {
export const getProjectUrl = (project?: ProjectDependency) => {
if (!project || !project.url || !project.branch) {
return undefined;
}
@@ -42,7 +42,7 @@ const getProjectUrl = (project?: ProjectDependency) => {
return getGitUrl(url, branch);
};

const formatProject = (project?: ProjectDependency) => {
export const formatProject = (project?: ProjectDependency) => {
if (!project) {
return undefined;
}
@@ -52,7 +52,7 @@ const formatProject = (project?: ProjectDependency) => {
};
};

const formatTrainTask = (task?: TrainTask) => {
export const formatTrainTask = (task?: TrainTask) => {
if (!task) {
return undefined;
}
@@ -62,7 +62,7 @@ const formatTrainTask = (task?: TrainTask) => {
};
};

const formatSource = (source?: string) => {
export const formatSource = (source?: string) => {
if (source === DataSource.Create) {
return '用户上传';
} else if (source === DataSource.HandExport) {


+ 120
- 0
react-ui/src/pages/Dataset/components/VersionCompareModal/index.less View File

@@ -0,0 +1,120 @@
@purple-color: #6516ff;

.title(@color, @background) {
width: 100%;
margin-bottom: 20px;
color: @color;
font-weight: 500;
font-size: @font-size;
line-height: 42px;
text-align: center;
background: @background;
border-radius: 4px 4px 0 0;
.singleLine();
}

.text() {
margin-bottom: 20px !important;
color: @text-color-secondary;
font-size: 13px;
line-height: 22px;
word-break: break-all;
.singleLine();
}

.version-container(@background) {
flex: 1;
min-width: 0;
background: @background;
border-radius: 4px;
}

.version-compare {
:global {
.ant-modal-content {
padding: 40px 40px 25px !important;
}
.ant-modal-header {
margin-bottom: 20px !important;
}
.kf-modal-title {
color: @text-color;
font-weight: 500;
font-size: 20px;
}
}

&__container {
display: flex;
flex-wrap: nowrap;
gap: 0 5px;
align-items: stretch;
height: 100%;
}

&__fields {
flex: none;
width: 117px;
padding: 0 15px;
background: rgba(255, 255, 255, 0.1);
border: 1px solid .addAlpha(@primary-color, 0.2) [];
border-radius: 4px;
box-shadow: 0px 3px 6px .addAlpha(@primary-color, 0.1) [] inset;

&__title {
margin-bottom: 20px;
color: @text-color;
font-size: @font-size;
line-height: 42px;
}
&__text {
.text();

&--different {
color: @error-color;
}
}
}

&__left {
.version-container(.addAlpha(@primary-color, 0.04) []);

&__title {
.title(@primary-color, linear-gradient(
159.9deg,rgba(138, 177, 255, 0.5) 0%,
rgba(22, 100, 255, 0.5) 100%
));
}

&__text {
padding: 0 15px;
text-align: center;
.text();

&--different {
color: @primary-color;
}
}
}

&__right {
.version-container(rgba(100, 30, 237, 0.04));
&__title {
.title(@purple-color, linear-gradient(
159.9deg,
rgba(193, 138, 255, 0.5) 0%,
rgba(146, 22, 255, 0.5) 100%
));
}

&__text {
padding: 0 15px;
text-align: center;
.text();

&--different {
color: @purple-color;
}
}
}
}

+ 236
- 0
react-ui/src/pages/Dataset/components/VersionCompareModal/index.tsx View File

@@ -0,0 +1,236 @@
import KFModal from '@/components/KFModal';
import {
DatasetData,
ModelData,
ProjectDependency,
ResourceType,
TrainTask,
resourceConfig,
} from '@/pages/Dataset/config';
import { isEmpty } from '@/utils';
import { to } from '@/utils/promise';
import { Typography, type ModalProps } from 'antd';
import classNames from 'classnames';
import { useEffect, useMemo, useState } from 'react';
import { formatSource } from '../ResourceIntro';
import styles from './index.less';

type CompareData = {
differences: Record<string, any>;
version1: DatasetData | ModelData;
version2: DatasetData | ModelData;
};

type FiledType<T extends DatasetData | ModelData> = {
key: keyof T;
text: string;
format?: (data: any) => any;
};

interface VersionCompareModalProps extends Omit<ModalProps, 'onOk'> {
versions: string[];
resourceType: ResourceType;
identifier: string;
is_public: boolean;
owner: string;
repo_id: number;
}

const formatDataset = (datasets?: DatasetData[]) => {
if (!datasets || datasets.length === 0) {
return undefined;
}
return datasets.map((item) => `${item.name}:${item.version}`).join(',');
};

const formatProject = (project?: ProjectDependency) => {
if (!project) {
return undefined;
}
return project.name;
};

export const formatTrainTask = (task?: TrainTask) => {
if (!task) {
return undefined;
}
return `${task.name}:${task.ins_id}`;
};

function VersionCompareModal({
versions,
resourceType,
identifier,
is_public,
owner,
repo_id,
...rest
}: VersionCompareModalProps) {
const [compareData, setCompareData] = useState<CompareData | undefined>(undefined);
const config = resourceConfig[resourceType];
const fields: FiledType<DatasetData>[] | FiledType<ModelData>[] = useMemo(
() =>
resourceType === ResourceType.Dataset
? [
{
key: 'dataset_source',
text: '数据来源',
format: formatSource,
},
{
key: 'train_task',
text: '训练任务',
format: formatTrainTask,
},
{
key: 'processing_code',
text: '处理代码',
format: formatProject,
},
{
key: 'description',
text: '版本描述',
},
]
: [
{
key: 'image',
text: '训练镜像',
},
{
key: 'project_depency',
text: '训练代码',
format: formatProject,
},
{
key: 'train_datasets',
text: '训练数据集',
format: formatDataset,
},
{
key: 'test_datasets',
text: '测试数据集',
format: formatDataset,
},
{
key: 'model_source',
text: '模型来源',
format: formatSource,
},
{
key: 'train_task',
text: '训练任务',
format: formatTrainTask,
},
{
key: 'description',
text: '版本描述',
},
],
[],
);

useEffect(() => {
getServiceVersionCompare();
}, []);

// 获取对比数据
const getServiceVersionCompare = async () => {
const params = {
versions,
identifier,
is_public,
owner,
repo_id,
};
const request = config.compareVersion;
const [res] = await to(request(params));
if (res && res.data) {
setCompareData(res.data);
}
};

// 获取值
function getValue<T extends DatasetData | ModelData>(
data: T,
key: keyof T,
format?: (data: any) => any,
) {
const value = data[key];
return format ? format(value) : value;
}

const {
version1: v1 = {} as DatasetData | ModelData,
version2: v2 = {} as DatasetData | ModelData,
differences = {},
} = compareData || {};

const isDifferent = (key: string) => {
return Object.keys(differences).includes(key);
};

return (
<KFModal
{...rest}
title={`${config.name}版本对比`}
width={825}
footer={null}
className={styles['version-compare']}
>
<div className={styles['version-compare__container']}>
<div className={styles['version-compare__fields']}>
<div className={styles['version-compare__fields__title']}>基础版本号</div>
{fields.map(({ key, text }) => (
<div
className={classNames(styles['version-compare__fields__text'], {
[styles['version-compare__fields__text--different']]: isDifferent(key),
})}
key={key}
>
{text}
</div>
))}
</div>
<div className={styles['version-compare__left']}>
<div className={styles['version-compare__left__title']}>{v1.version}</div>
{fields.map(({ key, format }) => {
const text = getValue(v1, key as keyof typeof v1, format);
return (
<div
key={key}
className={classNames(styles['version-compare__left__text'], {
[styles['version-compare__left__text--different']]: isDifferent(key),
})}
>
<Typography.Text ellipsis={{ tooltip: text }} style={{ color: 'inherit' }}>
{isEmpty(text) ? '--' : text}
</Typography.Text>
</div>
);
})}
</div>
<div className={styles['version-compare__right']}>
<div className={styles['version-compare__right__title']}>{v2.version}</div>
{fields.map(({ key, format }) => {
const text = getValue(v2, key as keyof typeof v2, format);
return (
<div
key={key}
className={classNames(styles['version-compare__right__text'], {
[styles['version-compare__right__text--different']]: isDifferent(key),
})}
>
<Typography.Text ellipsis={{ tooltip: text }} style={{ color: 'inherit' }}>
{isEmpty(text) ? '--' : text}
</Typography.Text>
</div>
);
})}
</div>
</div>
</KFModal>
);
}

export default VersionCompareModal;

+ 59
- 0
react-ui/src/pages/Dataset/components/VersionSelectorModal/index.less View File

@@ -0,0 +1,59 @@
.version-selector-modal {
:global {
.ant-modal-content {
padding: 40px !important;
}
.kf-modal-title {
color: @text-color;
font-weight: 500;
font-size: 20px;
}
}
}

.version-selector {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 20px;
width: 100%;
padding: 30px 13px 20px;

:global {
.ant-checkbox-group {
display: flex;
flex-direction: column;
gap: 10px 0;
}
}

&__item {
display: flex;
flex-direction: column;
align-items: center;
width: 103px;
padding: 15px;
color: @text-color-secondary;
font-size: 15px;
border: 1px solid rgba(136, 149, 168, 0.16);
border-radius: 6px;

img {
width: 26px;
height: 26px;
margin-bottom: 4px;
}

&:hover {
color: @text-color-secondary;
background-color: .addAlpha(@primary-color, 0.08) [];
border: 1px solid transparent;
}

&--active {
color: @primary-color !important;
background-color: .addAlpha(@primary-color, 0.08) [] !important;
border: 1px solid @primary-color !important;
}
}
}

+ 63
- 0
react-ui/src/pages/Dataset/components/VersionSelectorModal/index.tsx View File

@@ -0,0 +1,63 @@
import KFModal from '@/components/KFModal';
import { ResourceVersionData } from '@/pages/Dataset/config';
import { Typography, message, type ModalProps } from 'antd';
import classNames from 'classnames';
import { useState } from 'react';
import styles from './index.less';

interface VersionSelectorModalProps extends Omit<ModalProps, 'onOk'> {
versions: ResourceVersionData[];
onOk: (versions: string[]) => void;
}

function VersionSelectorModal({ versions, onOk, ...rest }: VersionSelectorModalProps) {
const [selVersions, setSelVersions] = useState<string[]>([]);

const handleOk = () => {
if (selVersions.length !== 2) {
message.error('请选择两个版本进行对比');
return;
}
onOk(selVersions);
};

const handleClick = (version: string) => {
setSelVersions((prev) => {
if (prev.includes(version)) {
return prev.filter((item) => item !== version);
}
return [...prev, version];
});
};

return (
<KFModal
{...rest}
title="请选择版本"
width={825}
onOk={handleOk}
className={styles['version-selector-modal']}
>
<div className={styles['version-selector']}>
{versions.map((item) => {
return (
<div
key={item.name}
className={classNames(styles['version-selector__item'], {
[styles['version-selector__item--active']]: selVersions.includes(item.name),
})}
onClick={() => handleClick(item.name)}
>
<img src={require('@/assets/img/dataset-version.png')} alt="" draggable={false} />
<Typography.Text ellipsis={{ tooltip: item.name }} style={{ color: 'inherit' }}>
{item.name}
</Typography.Text>
</div>
);
})}
</div>
</KFModal>
);
}

export default VersionSelectorModal;

+ 5
- 0
react-ui/src/pages/Dataset/config.tsx View File

@@ -3,6 +3,8 @@ import { CommonTabKeys } from '@/enums';
import {
addDatasetVersion,
addModelVersion,
compareDatasetVersion,
compareModelVersion,
deleteDataset,
deleteDatasetVersion,
deleteModel,
@@ -35,6 +37,7 @@ type ResourceTypeInfo = {
addVersion: (params: any) => Promise<any>; // 新增版本
deleteVersion: (params: any) => Promise<any>; // 删除版本
getInfo: (params: any) => Promise<any>; // 获取详情
compareVersion: (params: any) => Promise<any>; // 版本对比
name: string; // 名称
typeParamKey: 'data_type' | 'model_type'; // 类型参数名称,获取资源列表接口使用
tagParamKey: 'data_tag' | 'model_tag'; // 标签参数名称,获取资源列表接口使用
@@ -63,6 +66,7 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = {
addVersion: addDatasetVersion,
deleteVersion: deleteDatasetVersion,
getInfo: getDatasetInfo,
compareVersion: compareDatasetVersion,
name: '数据集',
typeParamKey: 'data_type',
tagParamKey: 'data_tag',
@@ -100,6 +104,7 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = {
addVersion: addModelVersion,
deleteVersion: deleteModelVersion,
getInfo: getModelInfo,
compareVersion: compareModelVersion,
name: '模型',
typeParamKey: 'model_type',
tagParamKey: 'model_tag',


+ 7
- 6
react-ui/src/pages/Model/components/ModelMetrics/index.tsx View File

@@ -58,18 +58,15 @@ function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsPr
checkSingleMetrics,
] = useCheck(allMetricsNames);

useEffect(() => {
getModelPageVersions();
}, []);

useEffect(() => {
if (selectedMetrics.length !== 0 && selectedRowKeys.length !== 0) {
getModelVersionsMetrics();
} else {
setChartData(undefined);
}
}, [selectedMetrics, selectedRowKeys]);
}, [selectedMetrics, selectedRowKeys, identifier, resourceId]);

// 版本切换,自动勾选当前版本
useEffect(() => {
const curRow = tableData.find((item) => item.name === version);
if (
@@ -80,7 +77,11 @@ function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsPr
) {
setSelectedRowKeys([version, ...selectedRowKeys]);
}
}, [version]);
}, [tableData, version]);

useEffect(() => {
getModelPageVersions();
}, [pagination, identifier, owner]);

// 获取模型版本列表,带有参数和指标数据
const getModelPageVersions = async () => {


+ 5
- 4
react-ui/src/pages/ModelDeployment/components/VersionCompareModal/index.tsx View File

@@ -3,6 +3,7 @@ import { ServiceRunStatus } from '@/enums';
import { useComputingResource } from '@/hooks/resource';
import { type ServiceVersionData } from '@/pages/ModelDeployment/types';
import { getServiceVersionCompareReq } from '@/services/modelDeployment';
import { isEmpty } from '@/utils';
import { to } from '@/utils/promise';
import { Typography, type ModalProps } from 'antd';
import classNames from 'classnames';
@@ -24,7 +25,7 @@ type FiledType = {
format?: (data: any) => any;
};

interface CreateMirrorModalProps extends Omit<ModalProps, 'onOk'> {
interface VersionCompareModalProps extends Omit<ModalProps, 'onOk'> {
version1: string;
version2: string;
}
@@ -39,7 +40,7 @@ const formatEnvText = (env: Record<string, string>) => {
.join(',');
};

function VersionCompareModal({ version1, version2, ...rest }: CreateMirrorModalProps) {
function VersionCompareModal({ version1, version2, ...rest }: VersionCompareModalProps) {
const [compareData, setCompareData] = useState<CompareData | undefined>(undefined);
const getResourceDescription = useComputingResource()[2];

@@ -165,7 +166,7 @@ function VersionCompareModal({ version1, version2, ...rest }: CreateMirrorModalP
})}
>
<Typography.Text ellipsis={{ tooltip: text }} style={{ color: 'inherit' }}>
{text}
{isEmpty(text) ? '--' : text}
</Typography.Text>
</div>
);
@@ -183,7 +184,7 @@ function VersionCompareModal({ version1, version2, ...rest }: CreateMirrorModalP
})}
>
<Typography.Text ellipsis={{ tooltip: text }} style={{ color: 'inherit' }}>
{text}
{isEmpty(text) ? '--' : text}
</Typography.Text>
</div>
);


+ 16
- 0
react-ui/src/services/dataset/index.js View File

@@ -75,6 +75,14 @@ export function deleteDatasetVersion(params) {
});
}

// 数据集版本对比
export function compareDatasetVersion(data) {
return request(`/api/mmp/newdataset/getVersionsCompare`, {
method: 'POST',
data,
});
}

// ----------------------------模型---------------------------------

// 分页查询模型列表
@@ -165,4 +173,12 @@ export function getModelVersionsMetricsReq(data) {
method: 'POST',
data
});
}

// 模型版本对比
export function compareModelVersion(data) {
return request(`/api/mmp/newmodel/getVersionsCompare`, {
method: 'POST',
data,
});
}

+ 23
- 0
react-ui/tests/date.test.ts View File

@@ -0,0 +1,23 @@
import { canBeConvertToDate } from '../src/utils/date';

describe('canBeConvertToDate()', () => {
test('null', () => {
expect(canBeConvertToDate(null)).toBe(false);
});

test('undefined', () => {
expect(canBeConvertToDate(undefined)).toBe(false);
});

test('empty string', () => {
expect(canBeConvertToDate('')).toBe(false);
});

test('valid string', () => {
expect(canBeConvertToDate('2024-10-10T10:10:10+08:00')).toBe(true);
});

test('numeric', () => {
expect(canBeConvertToDate(0)).toBe(true);
});
});

Loading…
Cancel
Save