Browse Source

Merge remote-tracking branch 'origin/dev' into dev

dev-lhz
chenzhihang 1 year ago
parent
commit
c6444baf47
12 changed files with 282 additions and 110 deletions
  1. +8
    -0
      react-ui/src/components/BasicInfo/index.less
  2. +49
    -24
      react-ui/src/components/BasicInfo/index.tsx
  3. +6
    -0
      react-ui/src/components/KFSpin/index.tsx
  4. +11
    -2
      react-ui/src/components/MenuIconSelector/index.less
  5. +6
    -0
      react-ui/src/components/MenuIconSelector/index.tsx
  6. +87
    -23
      react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx
  7. +14
    -4
      react-ui/src/pages/Dataset/config.tsx
  8. +3
    -0
      react-ui/src/pages/Experiment/Info/index.jsx
  9. +9
    -0
      react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx
  10. +15
    -1
      react-ui/src/pages/Experiment/components/ExperimentResult/index.tsx
  11. +65
    -51
      react-ui/src/pages/Experiment/components/ExportModelModal/index.tsx
  12. +9
    -5
      react-ui/src/pages/Model/components/NodeTooltips/index.tsx

+ 8
- 0
react-ui/src/components/BasicInfo/index.less View File

@@ -16,6 +16,7 @@

&__label {
position: relative;
flex: none;
color: @text-color-secondary;
text-align: justify;
text-align-last: justify;
@@ -26,6 +27,13 @@
}
}

&__list-value {
display: flex;
flex: 1;
flex-direction: column;
gap: 5px 0;
}

&__value {
flex: 1;
margin-left: 16px;


+ 49
- 24
react-ui/src/components/BasicInfo/index.tsx View File

@@ -1,14 +1,17 @@
import { isEmptyString } from '@/utils';
import { Link } from '@umijs/max';
import classNames from 'classnames';
import './index.less';

export type BasicInfoLink = {
value: string;
link?: string;
url?: string;
};

export type BasicInfoData = {
label: string;
value?: any;
link?: string;
externalLink?: string;
format?: (_value?: any) => string | undefined;
format?: (_value?: any) => string | BasicInfoLink | BasicInfoLink[] | undefined;
};

type BasicInfoProps = {
@@ -33,32 +36,23 @@ type BasicInfoItemProps = {
labelWidth?: number;
};
function BasicInfoItem({ data, labelWidth = 100 }: BasicInfoItemProps) {
const { label, value, externalLink, link, format } = data;
const showValue = format ? format(value) : value;
const { label, value, format } = data;
const formatValue = format ? format(value) : value;
let valueComponent = undefined;
if (externalLink && showValue) {
if (Array.isArray(formatValue)) {
valueComponent = (
<a
className="kf-basic-info-item__value kf-basic-info-item__link"
href={externalLink}
target="_blank"
rel="noopener noreferrer"
>
{showValue}
</a>
<div className="kf-basic-info-item__list-value">
{formatValue.map((item: BasicInfoLink) => (
<BasicInfoItemValue key={item.value} value={item.value} link={item.link} url={item.url} />
))}
</div>
);
} else if (link && showValue) {
} else if (typeof formatValue === 'object' && formatValue) {
valueComponent = (
<Link to={link} className="kf-basic-info-item__value kf-basic-info-item__link">
{showValue}
</Link>
<BasicInfoItemValue value={formatValue.value} link={formatValue.link} url={formatValue.url} />
);
} else {
valueComponent = (
<div className="kf-basic-info-item__value kf-basic-info-item__text">
{isEmptyString(showValue) ? '--' : showValue}
</div>
);
valueComponent = <BasicInfoItemValue value={formatValue} />;
}
return (
<div className="kf-basic-info-item" key={label}>
@@ -70,4 +64,35 @@ function BasicInfoItem({ data, labelWidth = 100 }: BasicInfoItemProps) {
);
}

type BasicInfoItemValueProps = {
value: string;
link?: string;
url?: string;
};

function BasicInfoItemValue({ value, link, url }: BasicInfoItemValueProps) {
if (url && value) {
return (
<a
className="kf-basic-info-item__value kf-basic-info-item__link"
href={url}
target="_blank"
rel="noopener noreferrer"
>
{value}
</a>
);
} else if (link && value) {
return (
<Link to={link} className="kf-basic-info-item__value kf-basic-info-item__link">
{value}
</Link>
);
} else {
return (
<div className="kf-basic-info-item__value kf-basic-info-item__text">{value || '--'}</div>
);
}
}

export default BasicInfo;

+ 6
- 0
react-ui/src/components/KFSpin/index.tsx View File

@@ -1,3 +1,9 @@
/*
* @Author: 赵伟
* @Date: 2024-09-02 08:42:57
* @Description: 自定义 Spin
*/

import { Spin, SpinProps } from 'antd';
import styles from './index.less';



+ 11
- 2
react-ui/src/components/MenuIconSelector/index.less View File

@@ -1,16 +1,25 @@
.menu-icon-selector {
// grid 布局,每行显示 8 个图标
display: grid;
grid-auto-rows: 1fr;
grid-template-columns: repeat(4, 1fr);
grid-template-columns: repeat(4, 80px);
gap: 20px;
justify-content: space-between;
width: 100%;

&__item {
display: flex;
align-items: center;
justify-content: center;
width: 80x;
height: 80px;
border: 1px solid transparent;
border-radius: 4px;
cursor: pointer;

&:hover {
border-color: @primary-color;
}

&__icon {
display: block;
}


+ 6
- 0
react-ui/src/components/MenuIconSelector/index.tsx View File

@@ -1,3 +1,9 @@
/*
* @Author: 赵伟
* @Date: 2024-04-17 16:59:42
* @Description: 菜单图标选择器
*/

import KFIcon from '@/components/KFIcon';
import KFModal from '@/components/KFModal';
import iconData from '@/iconfont/iconfont-menu.json';


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

@@ -1,6 +1,14 @@
import BasicInfo, { BasicInfoData } from '@/components/BasicInfo';
import SubAreaTitle from '@/components/SubAreaTitle';
import { DatasetData, ModelData, ResourceType } from '@/pages/Dataset/config';
import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceInfo';
import {
DataSource,
DatasetData,
ModelData,
ProjectDependency,
ResourceType,
TrainTask,
} from '@/pages/Dataset/config';
import styles from './index.less';

type ResourceIntroProps = {
@@ -8,29 +16,80 @@ type ResourceIntroProps = {
info: DatasetData | ModelData;
};

// const formatArray = (arr?: ResourceData[]) => {
// if (!arr || arr.length === 0) {
// return '--';
// }
// return arr.map((item) => item.name).join('\n');
// };
const formatDataset = (datasets?: DatasetData[]) => {
if (!datasets || datasets.length === 0) {
return undefined;
}
return datasets.map((item) => ({
value: item.name,
url: `${origin}/dataset/dataset/info/${item.id}?tab=${ResourceInfoTabKeys.Version}&version=${item.version}&name=${item.name}&owner=${item.owner}&identifier=${item.identifier}`,
}));
};

const formatDataset = (arr?: DatasetData[]) => {
if (!arr || arr.length === 0) {
const formatParams = (map?: Record<string, string>, space: string = '') => {
if (!map || Object.keys(map).length === 0) {
return undefined;
}
return arr.map((item) => item.name).join('\n');
return Object.entries(map)
.map(([key, value]) => `${space}${key} : ${value}`)
.join('\n');
};

const formatMap = (map?: Record<string, string>) => {
const formatMetrics = (map?: Record<string, string>) => {
if (!map || Object.keys(map).length === 0) {
return undefined;
}
return Object.entries(map)
.map(([key, value]) => `${key} = ${value}`)
.map(([key, value]) => {
if (typeof value === 'object' && value !== null) {
return `${key} : \n${formatParams(value, ' ')}`;
}
return `${key} : ${value}`;
})
.join('\n');
};

const getProjectUrl = (project?: ProjectDependency) => {
if (!project || !project.url || !project.branch) {
return undefined;
}
const { url, branch } = project;
if (url.endsWith('.git')) {
return `${url.substring(0, url.length - 4)}/tree/${branch}`;
}
};

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

const formatTrainTask = (task?: TrainTask) => {
if (!task) {
return undefined;
}
return {
value: task.name,
url: `${origin}/pipeline/experiment/instance/${task.workflow_id}/${task.ins_id}`,
};
};

const formatSource = (source?: string) => {
if (source === DataSource.Create) {
return '用户上传';
} else if (source === DataSource.HandExport) {
return '手动导入';
} else if (source === DataSource.AtuoExport) {
return '自动导入';
}
return source;
};

const getDatasetDatas = (data: DatasetData): BasicInfoData[] => [
{
label: '数据集名称',
@@ -51,6 +110,12 @@ const getDatasetDatas = (data: DatasetData): BasicInfoData[] => [
{
label: '数据来源',
value: data.dataset_source,
format: formatSource,
},
{
label: '训练任务',
value: data.train_task,
format: formatTrainTask,
},
{
label: '处理代码',
@@ -97,7 +162,8 @@ const getModelDatas = (data: ModelData): BasicInfoData[] => [
},
{
label: '训练代码',
value: data.code,
value: data.project_depency,
format: formatProject,
},
{
label: '训练数据集',
@@ -112,24 +178,22 @@ const getModelDatas = (data: ModelData): BasicInfoData[] => [
{
label: '参数',
value: data.params,
format: formatMap,
format: formatParams,
},
{
label: '指标',
value: data.metrics,
format: formatMap,
},
{
label: '训练任务',
value: data.train_task,
format: (value?: any) => value?.name,
externalLink: data.train_task
? `${location.origin}/pipeline/experiment/instance/${data.train_task.task_id}/${data.train_task.ins_id}`
: '',
format: formatMetrics,
},
{
label: '模型来源',
value: data.model_source,
format: formatSource,
},
{
label: '训练任务',
value: data.train_task,
format: formatTrainTask,
},
{
label: '模型框架',


+ 14
- 4
react-ui/src/pages/Dataset/config.tsx View File

@@ -22,7 +22,8 @@ export enum ResourceType {
}

export enum DataSource {
Export = 'export', // 导出
AtuoExport = 'auto_export', // 自动导出
HandExport = 'hand_export', // 手动导出
Create = 'add', // 新增
}

@@ -137,6 +138,7 @@ export type CategoryData = {

// 数据集、模型列表数据
export interface ResourceData {
resourceType: ResourceType.Dataset | ResourceType.Model; // 用于 ts 类型判断
id: number;
name: string;
identifier: string;
@@ -150,7 +152,7 @@ export interface ResourceData {
version_desc?: string;
usage?: string;
relative_paths?: string;
resourceType: ResourceType.Dataset | ResourceType.Model;
train_task?: TrainTask; // 训练任务
}

// 数据集数据
@@ -174,7 +176,7 @@ export interface ModelData extends ResourceData {
test_datasets?: DatasetData[]; // 测试数据集
params?: Record<string, string>; // 参数
metrics?: Record<string, string>; // 指标
train_task?: TrainTask; // 训练任务
project_depency?: ProjectDependency; // 项目依赖
model_source?: string; // 模型来源
model_version_vos?: ResourceFileData[];
}
@@ -199,5 +201,13 @@ export type ResourceFileData = {
export type TrainTask = {
ins_id: number;
name: string;
task_id: string;
experiment_id: number;
workflow_id: number;
};

// 项目依赖
export type ProjectDependency = {
url: string;
name: string;
branch: string;
};

+ 3
- 0
react-ui/src/pages/Experiment/Info/index.jsx View File

@@ -504,6 +504,9 @@ function ExperimentText() {
key={experimentNodeData.id}
open={propsDrawerOpen}
onClose={closePropsDrawer}
pipelineId={Number(locationParams.workflowId)}
experimentId={experimentIns.experiment_id}
experimentName={experimentIns.experiment_name}
instanceId={experimentIns.id}
instanceName={experimentIns.argo_ins_name}
instanceNamespace={experimentIns.argo_ins_ns}


+ 9
- 0
react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx View File

@@ -13,6 +13,9 @@ import styles from './index.less';
type ExperimentDrawerProps = {
open: boolean;
onClose: () => void;
pipelineId: number; // 流水线 id
experimentId: number; // 实验 id
experimentName: string; // 实验 name
instanceId: number; // 实验实例 id
instanceName: string; // 实验实例 name
instanceNamespace: string; // 实验实例 namespace
@@ -26,6 +29,9 @@ type ExperimentDrawerProps = {
const ExperimentDrawer = ({
open,
onClose,
pipelineId,
experimentId,
experimentName,
instanceId,
instanceName,
instanceNamespace,
@@ -64,6 +70,9 @@ const ExperimentDrawer = ({
label: '输出结果',
children: (
<ExperimentResult
pipelineId={pipelineId}
experimentId={experimentId}
experimentName={experimentName}
experimentInsId={instanceId}
pipelineNodeId={instanceNodeData.id}
></ExperimentResult>


+ 15
- 1
react-ui/src/pages/Experiment/components/ExperimentResult/index.tsx View File

@@ -8,6 +8,9 @@ import ExportModelModal from '../ExportModelModal';
import styles from './index.less';

type ExperimentResultProps = {
pipelineId: number; // 流水线 id
experimentId: number; // 实验 id
experimentName: string; // 实验 name
experimentInsId: number; // 实验实例 id
pipelineNodeId: string; // 流水线节点 id
};
@@ -22,7 +25,13 @@ type ExperimentResultData = {
}[];
};

function ExperimentResult({ experimentInsId, pipelineNodeId }: ExperimentResultProps) {
function ExperimentResult({
pipelineId,
experimentId,
experimentName,
experimentInsId,
pipelineNodeId,
}: ExperimentResultProps) {
const { message } = App.useApp();
const [experimentResults, setExperimentResults] = useState<ExperimentResultData[]>([]);

@@ -46,6 +55,11 @@ function ExperimentResult({ experimentInsId, pipelineNodeId }: ExperimentResultP
// 导出到模型库
const exportToModel = (path: string) => {
const { close } = openAntdModal(ExportModelModal, {
pipelineId,
experimentId,
experimentName,
experimentInsId,
pipelineNodeId,
path,
onOk: () => {
message.success('导出成功');


+ 65
- 51
react-ui/src/pages/Experiment/components/ExportModelModal/index.tsx View File

@@ -1,12 +1,7 @@
import editExperimentIcon from '@/assets/img/edit-experiment.png';
import KFModal from '@/components/KFModal';
import { ResourceVersionData, type ResourceData } from '@/pages/Dataset/config';
import {
addModelVersion,
exportModelReq,
getModelList,
getModelVersionList,
} from '@/services/dataset';
import { DataSource, ResourceVersionData, type ResourceData } from '@/pages/Dataset/config';
import { addModelVersion, getModelList, getModelVersionList } from '@/services/dataset';
import { to } from '@/utils/promise';
import { InfoCircleOutlined } from '@ant-design/icons';
import { Form, Input, ModalProps, Select } from 'antd';
@@ -15,34 +10,48 @@ import { useEffect, useState } from 'react';
import styles from './index.less';

type FormData = {
models_id: string;
id: number;
version: string;
description: string;
version_desc: string;
};

type ExportModelResponce = {
fileName: string;
fileSize: string;
url: string;
};
// type ExportModelResponce = {
// fileName: string;
// fileSize: string;
// url: string;
// };

type CreateModelVersionParams = FormData & {
file_name: string;
file_size: string;
url: string;
// name: string;
};
// type CreateModelVersionParams = FormData & {
// file_name: string;
// file_size: string;
// url: string;
// // name: string;
// };

interface ExportModelModalProps extends Omit<ModalProps, 'onOk'> {
path: string;
pipelineId: number; // 流水线 id
experimentId: number; // 实验 id
experimentName: string; // 实验 name
experimentInsId: number; // 实验实例 id
pipelineNodeId: string; // 流水线节点 id
path: string; // 文件路径
onOk: () => void;
}

function ExportModelModal({ path, onOk, ...rest }: ExportModelModalProps) {
function ExportModelModal({
pipelineId,
experimentId,
experimentName,
experimentInsId,
pipelineNodeId,
path,
onOk,
...rest
}: ExportModelModalProps) {
const [form] = Form.useForm();
const [models, setModels] = useState<ResourceData[]>([]);
const [versions, setVersions] = useState<ResourceVersionData[]>([]);
const [uuid] = useState(Date.now());
// const [uuid] = useState(Date.now());

const layout = {
labelCol: { span: 24 },
@@ -53,10 +62,19 @@ function ExportModelModal({ path, onOk, ...rest }: ExportModelModalProps) {
requestModelList();
}, []);

// 模型版本tooltip
// 获取选中的模型
const getSelectedModel = (id: number | undefined) => {
if (id) {
return models.find((item) => item.id === id);
}
return undefined;
};

// 模型版本 tooltip
const getTooltip = () => {
const id = form.getFieldValue('models_id');
const name = models.find((item) => item.id === id)?.name ?? '';
const id = form.getFieldValue('id');
const model = getSelectedModel(id);
const name = model?.name ?? '';
const versionNames = versions.map((item: ResourceVersionData) => item.name).join('、');
const tooltip =
versions.length > 0 ? `${name}有以下版本:\n${versionNames}\n注意不能重复` : undefined;
@@ -87,7 +105,7 @@ function ExportModelModal({ path, onOk, ...rest }: ExportModelModalProps) {

// 获取模型版本列表
const getModelVersions = async (id: number) => {
const model = models.find((item) => item.id === id);
const model = getSelectedModel(id);
if (!model) {
return;
}
@@ -104,26 +122,26 @@ function ExportModelModal({ path, onOk, ...rest }: ExportModelModalProps) {

// 导出到模型
const exportToModel = async (formData: FormData) => {
const id = form.getFieldValue('id');
const model = getSelectedModel(id);
const params = {
uuid: String(uuid),
path,
...formData,
identifier: model?.identifier,
name: model?.name,
model_source: DataSource.HandExport,
train_task: {
workflow_id: pipelineId,
experiment_id: experimentId,
name: experimentName,
ins_id: experimentInsId,
task_id: pipelineNodeId,
},
model_version_vos: [
{
url: path,
},
],
};
const [res] = await to(exportModelReq(params));
if (res && res.data) {
const files = res.data as ExportModelResponce[];
const params: CreateModelVersionParams[] = files.map((item) => ({
...formData,
file_name: item.fileName,
file_size: item.fileSize,
url: item.url,
}));

createModelVersion(params);
}
};

// 创建模型版本
const createModelVersion = async (params: CreateModelVersionParams[]) => {
const [res] = await to(addModelVersion(params));
if (res) {
onOk();
@@ -152,11 +170,7 @@ function ExportModelModal({ path, onOk, ...rest }: ExportModelModalProps) {
labelAlign="left"
labelWrap
>
<Form.Item
label="模型名称"
name="models_id"
rules={[{ required: true, message: '请选择模型' }]}
>
<Form.Item label="模型名称" name="id" rules={[{ required: true, message: '请选择模型' }]}>
<Select
placeholder="请选择模型"
onChange={handleModelChange}
@@ -194,7 +208,7 @@ function ExportModelModal({ path, onOk, ...rest }: ExportModelModalProps) {
</Form.Item>
<Form.Item
label="版本描述"
name="description"
name="version_desc"
rules={[{ required: true, message: '请输入版本描述' }]}
>
<Input.TextArea


+ 9
- 5
react-ui/src/pages/Model/components/NodeTooltips/index.tsx View File

@@ -16,7 +16,7 @@ function ModelInfo({ resourceId, data, onVersionChange }: ModelInfoProps) {
const gotoExperimentPage = () => {
if (data.model_meta.train_task?.ins_id) {
const { origin } = location;
const url = `${origin}/pipeline/experiment/instance/${data.model_meta.train_task.task_id}/${data.model_meta.train_task.ins_id}`;
const url = `${origin}/pipeline/experiment/instance/${data.model_meta.train_task.workflow_id}/${data.model_meta.train_task.ins_id}`;
window.open(url, '_blank');
}
};
@@ -60,12 +60,12 @@ function ModelInfo({ resourceId, data, onVersionChange }: ModelInfoProps) {
{data.model_meta.model_type || '--'}
</span>
</div>
<div className={styles['node-tooltips__row']}>
{/* <div className={styles['node-tooltips__row']}>
<span className={styles['node-tooltips__row__title']}>模型大小:</span>
<span className={styles['node-tooltips__row__value']}>
{data.model_meta.file_size || '--'}
</span>
</div>
</div> */}
<div className={styles['node-tooltips__row']}>
<span className={styles['node-tooltips__row__title']}>创建时间:</span>
<span className={styles['node-tooltips__row__value']}>
@@ -126,8 +126,12 @@ function DatasetInfo({ data }: { data: TrainDataset }) {

function ProjectInfo({ data }: { data: ProjectDependency }) {
const gotoProjectPage = () => {
const { url } = data;
window.open(url, '_blank');
const { url, branch } = data;
let projectUrl = url;
if (url.endsWith('.git')) {
projectUrl = `${url.substring(0, url.length - 4)}/tree/${branch}`;
}
window.open(projectUrl, '_blank');
};

return (


Loading…
Cancel
Save