diff --git a/react-ui/src/components/BasicInfo/index.less b/react-ui/src/components/BasicInfo/index.less index dd4d1ab1..53fcb46c 100644 --- a/react-ui/src/components/BasicInfo/index.less +++ b/react-ui/src/components/BasicInfo/index.less @@ -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; diff --git a/react-ui/src/components/BasicInfo/index.tsx b/react-ui/src/components/BasicInfo/index.tsx index 8cfee0ae..c3358af2 100644 --- a/react-ui/src/components/BasicInfo/index.tsx +++ b/react-ui/src/components/BasicInfo/index.tsx @@ -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 = ( - - {showValue} - +
+ {formatValue.map((item: BasicInfoLink) => ( + + ))} +
); - } else if (link && showValue) { + } else if (typeof formatValue === 'object' && formatValue) { valueComponent = ( - - {showValue} - + ); } else { - valueComponent = ( -
- {isEmptyString(showValue) ? '--' : showValue} -
- ); + valueComponent = ; } return (
@@ -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 ( + + {value} + + ); + } else if (link && value) { + return ( + + {value} + + ); + } else { + return ( +
{value || '--'}
+ ); + } +} + export default BasicInfo; diff --git a/react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx b/react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx index 08549ab6..7489666b 100644 --- a/react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx +++ b/react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx @@ -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, 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) => { +const formatMetrics = (map?: Record) => { 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: '模型框架', diff --git a/react-ui/src/pages/Dataset/config.tsx b/react-ui/src/pages/Dataset/config.tsx index 56c88832..9c4d6e47 100644 --- a/react-ui/src/pages/Dataset/config.tsx +++ b/react-ui/src/pages/Dataset/config.tsx @@ -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; // 参数 metrics?: Record; // 指标 - 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; }; diff --git a/react-ui/src/pages/Experiment/Info/index.jsx b/react-ui/src/pages/Experiment/Info/index.jsx index 43ca2a87..d02ae8f8 100644 --- a/react-ui/src/pages/Experiment/Info/index.jsx +++ b/react-ui/src/pages/Experiment/Info/index.jsx @@ -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} diff --git a/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx b/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx index f93950fa..48cd0064 100644 --- a/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx +++ b/react-ui/src/pages/Experiment/components/ExperimentDrawer/index.tsx @@ -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: ( diff --git a/react-ui/src/pages/Experiment/components/ExperimentResult/index.tsx b/react-ui/src/pages/Experiment/components/ExperimentResult/index.tsx index 046c2afe..6c770618 100644 --- a/react-ui/src/pages/Experiment/components/ExperimentResult/index.tsx +++ b/react-ui/src/pages/Experiment/components/ExperimentResult/index.tsx @@ -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([]); @@ -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('导出成功'); diff --git a/react-ui/src/pages/Experiment/components/ExportModelModal/index.tsx b/react-ui/src/pages/Experiment/components/ExportModelModal/index.tsx index cb3eda77..5667a388 100644 --- a/react-ui/src/pages/Experiment/components/ExportModelModal/index.tsx +++ b/react-ui/src/pages/Experiment/components/ExportModelModal/index.tsx @@ -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 { - 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([]); const [versions, setVersions] = useState([]); - 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 > - +