| @@ -0,0 +1,338 @@ | |||||
| import { defineMock } from 'umi'; | |||||
| export default defineMock({ | |||||
| 'POST /api/mmp/modelDependency/queryModelAtlas': { | |||||
| code: 200, | |||||
| msg: '操作成功', | |||||
| data: { | |||||
| current_model_id: 29, | |||||
| exp_ins_id: 229, | |||||
| version: 'v0.2.0', | |||||
| ref_item: null, | |||||
| train_task: { | |||||
| name: '模型训练测试导出0529', | |||||
| ins_id: 229, | |||||
| task_id: 'model-train-5d76f002', | |||||
| }, | |||||
| train_dataset: [ | |||||
| { | |||||
| dataset_id: 20, | |||||
| dataset_version: 'v0.1.0', | |||||
| dataset_name: '手写体识别模型依赖测试训练数据集', | |||||
| }, | |||||
| ], | |||||
| train_params: ['256', '2'], | |||||
| train_image: '172.20.32.187/machine-learning/pytorch:pytorch_1.9.1_cuda11.1_detection_aim', | |||||
| test_dataset: [ | |||||
| { | |||||
| dataset_id: 20, | |||||
| dataset_version: 'v0.1.0', | |||||
| dataset_name: '手写体识别模型依赖测试训练数据集', | |||||
| }, | |||||
| ], | |||||
| project_dependency: { | |||||
| url: 'https://openi.pcl.ac.cn/somunslotus/somun202304241505581.git', | |||||
| name: 'somun202304241505581', | |||||
| branch: 'train_ci_test', | |||||
| }, | |||||
| parent_models_map: [ | |||||
| { | |||||
| model_id: 29, | |||||
| model_version: 'v0.1.0', | |||||
| model_name: 'mnist模型演化', | |||||
| }, | |||||
| ], | |||||
| parent_models: [ | |||||
| { | |||||
| current_model_id: 29, | |||||
| exp_ins_id: null, | |||||
| version: 'v0.1.0', | |||||
| ref_item: null, | |||||
| train_task: {}, | |||||
| train_dataset: [], | |||||
| train_params: [], | |||||
| train_image: null, | |||||
| test_dataset: [], | |||||
| project_dependency: {}, | |||||
| parent_models_map: [], | |||||
| parent_models: [], | |||||
| children_models: null, | |||||
| workflow_id: null, | |||||
| model_version_dependcy_vo: { | |||||
| name: 'mnist模型演化', | |||||
| description: '手写体识别模型演化', | |||||
| available_range: 0, | |||||
| model_type: '37', | |||||
| model_tag: '46', | |||||
| model_type_name: 'PyTorch', | |||||
| model_tag_name: '图像转文本', | |||||
| url: 'models/admin/1718172558449/mnist_epoch1_0.00.pkl', | |||||
| file_name: 'mnist_epoch1_0.00.pkl', | |||||
| file_size: '176.63 KB', | |||||
| create_by: 'admin', | |||||
| create_time: '2024-06-12T06:09:56.000+00:00', | |||||
| }, | |||||
| }, | |||||
| ], | |||||
| children_models: [ | |||||
| { | |||||
| current_model_id: 29, | |||||
| exp_ins_id: null, | |||||
| version: 'v0.3.0', | |||||
| ref_item: null, | |||||
| train_task: {}, | |||||
| train_dataset: [], | |||||
| train_params: [], | |||||
| train_image: null, | |||||
| test_dataset: [], | |||||
| project_dependency: {}, | |||||
| parent_models_map: [], | |||||
| parent_models: [], | |||||
| children_models: [], | |||||
| workflow_id: null, | |||||
| model_version_dependcy_vo: { | |||||
| name: 'mnist模型演化', | |||||
| description: '手写体识别模型演化', | |||||
| available_range: 0, | |||||
| model_type: '37', | |||||
| model_tag: '46', | |||||
| model_type_name: 'PyTorch', | |||||
| model_tag_name: '图像转文本', | |||||
| url: 'models/admin/1718172558449/mnist_epoch1_0.00.pkl', | |||||
| file_name: 'mnist_epoch1_0.00.pkl', | |||||
| file_size: '176.63 KB', | |||||
| create_by: 'admin', | |||||
| create_time: '2024-06-12T06:09:56.000+00:00', | |||||
| }, | |||||
| }, | |||||
| { | |||||
| current_model_id: 29, | |||||
| exp_ins_id: null, | |||||
| version: 'v0.31.0', | |||||
| ref_item: null, | |||||
| train_task: {}, | |||||
| train_dataset: [], | |||||
| train_params: [], | |||||
| train_image: null, | |||||
| test_dataset: [], | |||||
| project_dependency: {}, | |||||
| parent_models_map: [], | |||||
| parent_models: [], | |||||
| children_models: [], | |||||
| workflow_id: null, | |||||
| model_version_dependcy_vo: { | |||||
| name: 'mnist模型演化', | |||||
| description: '手写体识别模型演化', | |||||
| available_range: 0, | |||||
| model_type: '37', | |||||
| model_tag: '46', | |||||
| model_type_name: 'PyTorch', | |||||
| model_tag_name: '图像转文本', | |||||
| url: 'models/admin/1718172558449/mnist_epoch1_0.00.pkl', | |||||
| file_name: 'mnist_epoch1_0.00.pkl', | |||||
| file_size: '176.63 KB', | |||||
| create_by: 'admin', | |||||
| create_time: '2024-06-12T06:09:56.000+00:00', | |||||
| }, | |||||
| }, | |||||
| { | |||||
| current_model_id: 29, | |||||
| exp_ins_id: null, | |||||
| version: 'v0.4.0', | |||||
| ref_item: null, | |||||
| train_task: {}, | |||||
| train_dataset: [], | |||||
| train_params: [], | |||||
| train_image: null, | |||||
| test_dataset: [], | |||||
| project_dependency: {}, | |||||
| parent_models_map: [], | |||||
| parent_models: [], | |||||
| children_models: [ | |||||
| { | |||||
| current_model_id: 29, | |||||
| exp_ins_id: null, | |||||
| version: 'v0.6.0', | |||||
| ref_item: null, | |||||
| train_task: {}, | |||||
| train_dataset: [], | |||||
| train_params: [], | |||||
| train_image: null, | |||||
| test_dataset: [], | |||||
| project_dependency: {}, | |||||
| parent_models_map: [], | |||||
| parent_models: [], | |||||
| children_models: [], | |||||
| workflow_id: null, | |||||
| model_version_dependcy_vo: { | |||||
| name: 'mnist模型演化', | |||||
| description: '手写体识别模型演化', | |||||
| available_range: 0, | |||||
| model_type: '37', | |||||
| model_tag: '46', | |||||
| model_type_name: 'PyTorch', | |||||
| model_tag_name: '图像转文本', | |||||
| url: 'models/admin/1718172558449/mnist_epoch1_0.00.pkl', | |||||
| file_name: 'mnist_epoch1_0.00.pkl', | |||||
| file_size: '176.63 KB', | |||||
| create_by: 'admin', | |||||
| create_time: '2024-06-12T06:09:56.000+00:00', | |||||
| }, | |||||
| }, | |||||
| { | |||||
| current_model_id: 29, | |||||
| exp_ins_id: null, | |||||
| version: 'v0.7.0', | |||||
| ref_item: null, | |||||
| train_task: {}, | |||||
| train_dataset: [], | |||||
| train_params: [], | |||||
| train_image: null, | |||||
| test_dataset: [], | |||||
| project_dependency: {}, | |||||
| parent_models_map: [], | |||||
| parent_models: [], | |||||
| children_models: [], | |||||
| workflow_id: null, | |||||
| model_version_dependcy_vo: { | |||||
| name: 'mnist模型演化', | |||||
| description: '手写体识别模型演化', | |||||
| available_range: 0, | |||||
| model_type: '37', | |||||
| model_tag: '46', | |||||
| model_type_name: 'PyTorch', | |||||
| model_tag_name: '图像转文本', | |||||
| url: 'models/admin/1718172558449/mnist_epoch1_0.00.pkl', | |||||
| file_name: 'mnist_epoch1_0.00.pkl', | |||||
| file_size: '176.63 KB', | |||||
| create_by: 'admin', | |||||
| create_time: '2024-06-12T06:09:56.000+00:00', | |||||
| }, | |||||
| }, | |||||
| ], | |||||
| workflow_id: null, | |||||
| model_version_dependcy_vo: { | |||||
| name: 'mnist模型演化', | |||||
| description: '手写体识别模型演化', | |||||
| available_range: 0, | |||||
| model_type: '37', | |||||
| model_tag: '46', | |||||
| model_type_name: 'PyTorch', | |||||
| model_tag_name: '图像转文本', | |||||
| url: 'models/admin/1718172558449/mnist_epoch1_0.00.pkl', | |||||
| file_name: 'mnist_epoch1_0.00.pkl', | |||||
| file_size: '176.63 KB', | |||||
| create_by: 'admin', | |||||
| create_time: '2024-06-12T06:09:56.000+00:00', | |||||
| }, | |||||
| }, | |||||
| { | |||||
| current_model_id: 29, | |||||
| exp_ins_id: null, | |||||
| version: 'v0.5.0', | |||||
| ref_item: null, | |||||
| train_task: {}, | |||||
| train_dataset: [], | |||||
| train_params: [], | |||||
| train_image: null, | |||||
| test_dataset: [], | |||||
| project_dependency: {}, | |||||
| parent_models_map: [], | |||||
| parent_models: [], | |||||
| children_models: [ | |||||
| { | |||||
| current_model_id: 29, | |||||
| exp_ins_id: null, | |||||
| version: 'v0.10.0', | |||||
| ref_item: null, | |||||
| train_task: {}, | |||||
| train_dataset: [], | |||||
| train_params: [], | |||||
| train_image: null, | |||||
| test_dataset: [], | |||||
| project_dependency: {}, | |||||
| parent_models_map: [], | |||||
| parent_models: [], | |||||
| children_models: [], | |||||
| workflow_id: null, | |||||
| model_version_dependcy_vo: { | |||||
| name: 'mnist模型演化', | |||||
| description: '手写体识别模型演化', | |||||
| available_range: 0, | |||||
| model_type: '37', | |||||
| model_tag: '46', | |||||
| model_type_name: 'PyTorch', | |||||
| model_tag_name: '图像转文本', | |||||
| url: 'models/admin/1718172558449/mnist_epoch1_0.00.pkl', | |||||
| file_name: 'mnist_epoch1_0.00.pkl', | |||||
| file_size: '176.63 KB', | |||||
| create_by: 'admin', | |||||
| create_time: '2024-06-12T06:09:56.000+00:00', | |||||
| }, | |||||
| }, | |||||
| { | |||||
| current_model_id: 29, | |||||
| exp_ins_id: null, | |||||
| version: 'v0.11.0', | |||||
| ref_item: null, | |||||
| train_task: {}, | |||||
| train_dataset: [], | |||||
| train_params: [], | |||||
| train_image: null, | |||||
| test_dataset: [], | |||||
| project_dependency: {}, | |||||
| parent_models_map: [], | |||||
| parent_models: [], | |||||
| children_models: [], | |||||
| workflow_id: null, | |||||
| model_version_dependcy_vo: { | |||||
| name: 'mnist模型演化', | |||||
| description: '手写体识别模型演化', | |||||
| available_range: 0, | |||||
| model_type: '37', | |||||
| model_tag: '46', | |||||
| model_type_name: 'PyTorch', | |||||
| model_tag_name: '图像转文本', | |||||
| url: 'models/admin/1718172558449/mnist_epoch1_0.00.pkl', | |||||
| file_name: 'mnist_epoch1_0.00.pkl', | |||||
| file_size: '176.63 KB', | |||||
| create_by: 'admin', | |||||
| create_time: '2024-06-12T06:09:56.000+00:00', | |||||
| }, | |||||
| }, | |||||
| ], | |||||
| workflow_id: null, | |||||
| model_version_dependcy_vo: { | |||||
| name: 'mnist模型演化', | |||||
| description: '手写体识别模型演化', | |||||
| available_range: 0, | |||||
| model_type: '37', | |||||
| model_tag: '46', | |||||
| model_type_name: 'PyTorch', | |||||
| model_tag_name: '图像转文本', | |||||
| url: 'models/admin/1718172558449/mnist_epoch1_0.00.pkl', | |||||
| file_name: 'mnist_epoch1_0.00.pkl', | |||||
| file_size: '176.63 KB', | |||||
| create_by: 'admin', | |||||
| create_time: '2024-06-12T06:09:56.000+00:00', | |||||
| }, | |||||
| }, | |||||
| ], | |||||
| workflow_id: 144, | |||||
| model_version_dependcy_vo: { | |||||
| name: 'mnist模型演化', | |||||
| description: '手写体识别模型演化', | |||||
| available_range: 0, | |||||
| model_type: '37', | |||||
| model_tag: '46', | |||||
| model_type_name: 'PyTorch', | |||||
| model_tag_name: '图像转文本', | |||||
| url: 'models/admin/1718172760650/mnist_cnn.pt', | |||||
| file_name: 'mnist_cnn.pt', | |||||
| file_size: '176.76 KB', | |||||
| create_by: 'admin', | |||||
| create_time: '2024-06-12T06:12:42.000+00:00', | |||||
| }, | |||||
| }, | |||||
| }, | |||||
| }); | |||||
| @@ -33,7 +33,7 @@ | |||||
| "serve": "umi-serve", | "serve": "umi-serve", | ||||
| "start": "cross-env UMI_ENV=dev max dev", | "start": "cross-env UMI_ENV=dev max dev", | ||||
| "start:dev": "cross-env REACT_APP_ENV=dev MOCK=none UMI_ENV=dev max dev", | "start:dev": "cross-env REACT_APP_ENV=dev MOCK=none UMI_ENV=dev max dev", | ||||
| "start:no-mock": "cross-env MOCK=none UMI_ENV=dev max dev", | |||||
| "start:mock": "cross-env REACT_APP_ENV=dev UMI_ENV=dev max dev", | |||||
| "start:pre": "cross-env REACT_APP_ENV=pre UMI_ENV=dev max dev", | "start:pre": "cross-env REACT_APP_ENV=pre UMI_ENV=dev max dev", | ||||
| "start:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=dev max dev", | "start:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=dev max dev", | ||||
| "test": "jest", | "test": "jest", | ||||
| @@ -0,0 +1,19 @@ | |||||
| .kf-label-value { | |||||
| display: flex; | |||||
| align-items: flex-start; | |||||
| font-size: 16px; | |||||
| line-height: 1.6; | |||||
| &__label { | |||||
| flex: none; | |||||
| width: 80px; | |||||
| color: @text-color-secondary; | |||||
| } | |||||
| &__value { | |||||
| flex: 1; | |||||
| color: @text-color; | |||||
| white-space: pre-line; | |||||
| word-break: break-all; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,20 @@ | |||||
| import classNames from 'classnames'; | |||||
| import './index.less'; | |||||
| type labelValueProps = { | |||||
| label: string; | |||||
| value?: any; | |||||
| className?: string; | |||||
| style?: React.CSSProperties; | |||||
| }; | |||||
| function LabelValue({ label, value, className, style }: labelValueProps) { | |||||
| return ( | |||||
| <div className={classNames('kf-label-value', className)} style={style}> | |||||
| <div className="kf-label-value__label">{label}</div> | |||||
| <div className="kf-label-value__value">{value ?? '--'}</div> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default LabelValue; | |||||
| @@ -1,4 +1,4 @@ | |||||
| .modal-title { | |||||
| .kf-modal-title { | |||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| color: @primary-color; | color: @primary-color; | ||||
| @@ -6,7 +6,7 @@ | |||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import React from 'react'; | import React from 'react'; | ||||
| import styles from './index.less'; | |||||
| import './index.less'; | |||||
| type ModalTitleProps = { | type ModalTitleProps = { | ||||
| title: React.ReactNode; | title: React.ReactNode; | ||||
| @@ -17,8 +17,8 @@ type ModalTitleProps = { | |||||
| function ModalTitle({ title, image, style, className }: ModalTitleProps) { | function ModalTitle({ title, image, style, className }: ModalTitleProps) { | ||||
| return ( | return ( | ||||
| <div className={classNames(styles['modal-title'], className)} style={style}> | |||||
| {image && <img className={styles['modal-title__image']} src={image} alt="" />} | |||||
| <div className={classNames('kf-modal-title', className)} style={style}> | |||||
| {image && <img className={'kf-modal-title__image'} src={image} alt="" />} | |||||
| {title} | {title} | ||||
| </div> | </div> | ||||
| ); | ); | ||||
| @@ -17,10 +17,10 @@ const filterResourceStandard: SelectProps<string, ComputingResource>['filterOpti | |||||
| const convertId = (item: any) => ({ ...item, id: String(item.id) }); | const convertId = (item: any) => ({ ...item, id: String(item.id) }); | ||||
| export type SelectPropsConfig = { | export type SelectPropsConfig = { | ||||
| getOptions: () => Promise<any>; | |||||
| fieldNames?: SelectProps['fieldNames']; | |||||
| optionFilterProp?: SelectProps['optionFilterProp']; | |||||
| filterOption?: SelectProps['filterOption']; | |||||
| getOptions: () => Promise<any>; // 获取下拉数据 | |||||
| fieldNames?: SelectProps['fieldNames']; // 下拉数据字段 | |||||
| optionFilterProp?: SelectProps['optionFilterProp']; // 过滤字段名 | |||||
| filterOption?: SelectProps['filterOption']; // 过滤函数 | |||||
| }; | }; | ||||
| export const paramSelectConfig: Record<string, SelectPropsConfig> = { | export const paramSelectConfig: Record<string, SelectPropsConfig> = { | ||||
| @@ -1,7 +1,7 @@ | |||||
| import { getSessionStorageItem, removeSessionStorageItem } from '@/utils/sessionStorage'; | import { getSessionStorageItem, removeSessionStorageItem } from '@/utils/sessionStorage'; | ||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| // 获取缓存数据 | |||||
| // 读取缓存数据,组件卸载时清除缓存 | |||||
| export function useSessionStorage<T>(key: string, isObject: boolean, initialValue: T) { | export function useSessionStorage<T>(key: string, isObject: boolean, initialValue: T) { | ||||
| const [storage, setStorage] = useState<T>(initialValue); | const [storage, setStorage] = useState<T>(initialValue); | ||||
| @@ -1,3 +1,4 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import ModelEvolution from '@/pages/Model/components/ModelEvolution'; | import ModelEvolution from '@/pages/Model/components/ModelEvolution'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { useParams, useSearchParams } from '@umijs/max'; | import { useParams, useSearchParams } from '@umijs/max'; | ||||
| @@ -7,21 +8,21 @@ import { ResourceData, ResourceType, resourceConfig } from '../../config'; | |||||
| import ResourceVersion from '../ResourceVersion'; | import ResourceVersion from '../ResourceVersion'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| export enum ResourceInfoTabKeys { | |||||
| Introduction = 'introduction', | |||||
| Version = 'version', | |||||
| Evolution = 'evolution', | |||||
| } | |||||
| type ResourceIntroProps = { | type ResourceIntroProps = { | ||||
| resourceType: ResourceType; | resourceType: ResourceType; | ||||
| }; | }; | ||||
| enum TabKeys { | |||||
| Introduction = '1', | |||||
| Version = '2', | |||||
| Evolution = '3', | |||||
| } | |||||
| const ResourceIntro = ({ resourceType }: ResourceIntroProps) => { | const ResourceIntro = ({ resourceType }: ResourceIntroProps) => { | ||||
| const [info, setInfo] = useState<ResourceData>({} as ResourceData); | const [info, setInfo] = useState<ResourceData>({} as ResourceData); | ||||
| const locationParams = useParams(); | const locationParams = useParams(); | ||||
| const [searchParams] = useSearchParams(); | const [searchParams] = useSearchParams(); | ||||
| const defaultTab = searchParams.get('tab') || '1'; | |||||
| const defaultTab = searchParams.get('tab') || ResourceInfoTabKeys.Introduction; | |||||
| let versionParam = searchParams.get('version'); | let versionParam = searchParams.get('version'); | ||||
| const [versionList, setVersionList] = useState([]); | const [versionList, setVersionList] = useState([]); | ||||
| const [version, setVersion] = useState<string | undefined>(undefined); | const [version, setVersion] = useState<string | undefined>(undefined); | ||||
| @@ -74,8 +75,9 @@ const ResourceIntro = ({ resourceType }: ResourceIntroProps) => { | |||||
| const items = [ | const items = [ | ||||
| { | { | ||||
| key: TabKeys.Introduction, | |||||
| key: ResourceInfoTabKeys.Introduction, | |||||
| label: `${typeName}简介`, | label: `${typeName}简介`, | ||||
| icon: <KFIcon type="icon-moxingjianjie" />, | |||||
| children: ( | children: ( | ||||
| <> | <> | ||||
| <div className={styles['resource-intro__title']}>简介</div> | <div className={styles['resource-intro__title']}>简介</div> | ||||
| @@ -84,8 +86,9 @@ const ResourceIntro = ({ resourceType }: ResourceIntroProps) => { | |||||
| ), | ), | ||||
| }, | }, | ||||
| { | { | ||||
| key: TabKeys.Version, | |||||
| key: ResourceInfoTabKeys.Version, | |||||
| label: `${typeName}文件/版本`, | label: `${typeName}文件/版本`, | ||||
| icon: <KFIcon type="icon-moxingwenjian" />, | |||||
| children: ( | children: ( | ||||
| <ResourceVersion | <ResourceVersion | ||||
| resourceType={resourceType} | resourceType={resourceType} | ||||
| @@ -94,7 +97,7 @@ const ResourceIntro = ({ resourceType }: ResourceIntroProps) => { | |||||
| isPublic={info.available_range === 1} | isPublic={info.available_range === 1} | ||||
| versionList={versionList} | versionList={versionList} | ||||
| version={version} | version={version} | ||||
| isActive={activeTab === TabKeys.Version} | |||||
| isActive={activeTab === ResourceInfoTabKeys.Version} | |||||
| getVersionList={getVersionList} | getVersionList={getVersionList} | ||||
| onVersionChange={handleVersionChange} | onVersionChange={handleVersionChange} | ||||
| ></ResourceVersion> | ></ResourceVersion> | ||||
| @@ -104,15 +107,15 @@ const ResourceIntro = ({ resourceType }: ResourceIntroProps) => { | |||||
| if (resourceType === ResourceType.Model) { | if (resourceType === ResourceType.Model) { | ||||
| items.push({ | items.push({ | ||||
| key: TabKeys.Evolution, | |||||
| key: ResourceInfoTabKeys.Evolution, | |||||
| label: `模型演化`, | label: `模型演化`, | ||||
| icon: <KFIcon type="icon-moxingyanhua1" />, | |||||
| children: ( | children: ( | ||||
| <ModelEvolution | <ModelEvolution | ||||
| resourceId={resourceId} | resourceId={resourceId} | ||||
| resourceName={info.name} | |||||
| versionList={versionList} | versionList={versionList} | ||||
| version={version} | version={version} | ||||
| isActive={activeTab === TabKeys.Evolution} | |||||
| isActive={activeTab === ResourceInfoTabKeys.Evolution} | |||||
| onVersionChange={handleVersionChange} | onVersionChange={handleVersionChange} | ||||
| ></ModelEvolution> | ></ModelEvolution> | ||||
| ), | ), | ||||
| @@ -24,32 +24,32 @@ export enum ResourceType { | |||||
| } | } | ||||
| type ResourceTypeInfo = { | type ResourceTypeInfo = { | ||||
| getList: (params: any) => Promise<any>; | |||||
| getVersions: (params: any) => Promise<any>; | |||||
| getFiles: (params: any) => Promise<any>; | |||||
| deleteRecord: (params: any) => Promise<any>; | |||||
| addVersion: (params: any) => Promise<any>; | |||||
| deleteVersion: (params: any) => Promise<any>; | |||||
| getInfo: (params: any) => Promise<any>; | |||||
| name: string; | |||||
| typeParamKey: string; | |||||
| tagParamKey: string; | |||||
| fileReqParamKey: 'models_id' | 'dataset_id'; | |||||
| tabItems: TabsProps['items']; | |||||
| typeTitle: string; | |||||
| tagTitle: string; | |||||
| getList: (params: any) => Promise<any>; // 获取资源列表 | |||||
| getVersions: (params: any) => Promise<any>; // 获取版本列表 | |||||
| getFiles: (params: any) => Promise<any>; // 获取版本下的文件列表 | |||||
| deleteRecord: (params: any) => Promise<any>; // 删除 | |||||
| addVersion: (params: any) => Promise<any>; // 新增版本 | |||||
| deleteVersion: (params: any) => Promise<any>; // 删除版本 | |||||
| getInfo: (params: any) => Promise<any>; // 获取详情 | |||||
| name: string; // 名称 | |||||
| typeParamKey: string; // 类型参数名称,获取资源列表接口使用 | |||||
| tagParamKey: string; // 标签参数名称,获取资源列表接口使用 | |||||
| fileReqParamKey: 'models_id' | 'dataset_id'; // 文件请求参数名称,获取文件列表接口使用 | |||||
| tabItems: TabsProps['items']; // tab 列表 | |||||
| typeTitle: string; // 类型标题 | |||||
| tagTitle: string; // 标签标题 | |||||
| typeValue: number; // 从 getAssetIcon 接口获取特定值的数据为 type 分类 (category_id === typeValue) | typeValue: number; // 从 getAssetIcon 接口获取特定值的数据为 type 分类 (category_id === typeValue) | ||||
| tagValue: number; // 从 getAssetIcon 接口获取特定值的数据为 tag 分类(category_id === tagValue) | tagValue: number; // 从 getAssetIcon 接口获取特定值的数据为 tag 分类(category_id === tagValue) | ||||
| prefix: string; // 前缀 | |||||
| prefix: string; // 图片资源、详情 url 的前缀 | |||||
| deleteModalTitle: string; // 删除弹框的title | deleteModalTitle: string; // 删除弹框的title | ||||
| addBtnTitle: string; // 新增按钮的title | addBtnTitle: string; // 新增按钮的title | ||||
| idParamKey: 'models_id' | 'dataset_id'; | |||||
| uploadAction: string; | |||||
| uploadAccept?: string; | |||||
| downloadAllAction: string; | |||||
| downloadSingleAction: string; | |||||
| infoTypePropertyName: string; | |||||
| infoTagPropertyName: string; | |||||
| idParamKey: 'models_id' | 'dataset_id'; // 新建版本、删除版本接口,版本 id 的参数名称 | |||||
| uploadAction: string; // 上传接口 url | |||||
| uploadAccept?: string; // 上传文件类型 | |||||
| downloadAllAction: string; // 批量下载接口 url | |||||
| downloadSingleAction: string; // 单个下载接口 url | |||||
| infoTypePropertyName: string; // 详情数据中,类型属性名称 | |||||
| infoTagPropertyName: string; // 详情数据中,标签属性名称 | |||||
| }; | }; | ||||
| export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | ||||
| @@ -27,14 +27,12 @@ type Log = { | |||||
| const scrollToBottom = (smooth: boolean = true) => { | const scrollToBottom = (smooth: boolean = true) => { | ||||
| const element = document.getElementsByClassName('ant-tabs-content-holder')?.[0]; | const element = document.getElementsByClassName('ant-tabs-content-holder')?.[0]; | ||||
| if (element) { | if (element) { | ||||
| if (smooth) { | |||||
| element.scrollTo({ | |||||
| top: element.scrollHeight, | |||||
| behavior: 'smooth', | |||||
| }); | |||||
| } else { | |||||
| element.scrollTo({ top: element.scrollHeight }); | |||||
| } | |||||
| const optons: ScrollToOptions = { | |||||
| top: element.scrollHeight, | |||||
| behavior: smooth ? 'smooth' : 'instant', | |||||
| }; | |||||
| element.scrollTo(optons); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -153,13 +151,9 @@ function LogGroup({ | |||||
| )} | )} | ||||
| <div className={styles['log-group__more-button']}> | <div className={styles['log-group__more-button']}> | ||||
| {showMoreBtn && ( | {showMoreBtn && ( | ||||
| <Button | |||||
| type="text" | |||||
| style={{ color: 'white' }} | |||||
| icon={<DoubleRightOutlined rotate={90} />} | |||||
| onClick={loadMore} | |||||
| > | |||||
| <Button type="text" style={{ color: 'white' }} onClick={loadMore}> | |||||
| 更多 | 更多 | ||||
| <DoubleRightOutlined rotate={90} /> | |||||
| </Button> | </Button> | ||||
| )} | )} | ||||
| </div> | </div> | ||||
| @@ -1,37 +1,55 @@ | |||||
| import { Flex } from 'antd'; | import { Flex } from 'antd'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type GraphLegandData = { | |||||
| type GraphLegendData = { | |||||
| name: string; | name: string; | ||||
| color: string; | color: string; | ||||
| radius: number; | |||||
| radius: number | string; | |||||
| fill: boolean; | fill: boolean; | ||||
| }; | }; | ||||
| type GraphLegandProps = { | |||||
| type GraphLegendProps = { | |||||
| style?: React.CSSProperties; | style?: React.CSSProperties; | ||||
| }; | }; | ||||
| function GraphLegand({ style }: GraphLegandProps) { | |||||
| const legends: GraphLegandData[] = [ | |||||
| function GraphLegend({ style }: GraphLegendProps) { | |||||
| const legends: GraphLegendData[] = [ | |||||
| { | { | ||||
| name: '父模型', | name: '父模型', | ||||
| color: '#76b1ff', | |||||
| color: 'linear-gradient(305deg,#43c9b1 0%,#93dfd1 100%)', | |||||
| radius: 2, | radius: 2, | ||||
| fill: true, | fill: true, | ||||
| }, | }, | ||||
| { | { | ||||
| name: '当前模型', | name: '当前模型', | ||||
| color: '#1664ff', | |||||
| color: 'linear-gradient(139.97deg,#72a1ff 0%,#1664ff 100%)', | |||||
| radius: 2, | radius: 2, | ||||
| fill: true, | fill: true, | ||||
| }, | }, | ||||
| { | { | ||||
| name: '衍生模型', | name: '衍生模型', | ||||
| color: '#b7cfff', | |||||
| color: 'linear-gradient(139.97deg,#72b4ff 0%,#169aff 100%)', | |||||
| radius: 2, | radius: 2, | ||||
| fill: true, | fill: true, | ||||
| }, | }, | ||||
| { | |||||
| name: '训练数据集', | |||||
| color: '#a5d878', | |||||
| radius: '50%', | |||||
| fill: true, | |||||
| }, | |||||
| { | |||||
| name: '测试数据集', | |||||
| color: '#d8b578', | |||||
| radius: '50%', | |||||
| fill: true, | |||||
| }, | |||||
| { | |||||
| name: '项目', | |||||
| color: 'linear-gradient(305deg,#8981ff 0%,#b3a9ff 100%)', | |||||
| radius: 6, | |||||
| fill: true, | |||||
| }, | |||||
| ]; | ]; | ||||
| return ( | return ( | ||||
| <Flex align="center" className={styles['graph-legend']} style={style}> | <Flex align="center" className={styles['graph-legend']} style={style}> | ||||
| @@ -42,7 +60,7 @@ function GraphLegand({ style }: GraphLegandProps) { | |||||
| width: '16px', | width: '16px', | ||||
| height: '12px', | height: '12px', | ||||
| borderRadius: item.radius, | borderRadius: item.radius, | ||||
| backgroundColor: item.color, | |||||
| background: item.color, | |||||
| }} | }} | ||||
| ></div> | ></div> | ||||
| <div className={styles['graph-legend__item__name']}>{item.name}</div> | <div className={styles['graph-legend__item__name']}>{item.name}</div> | ||||
| @@ -52,4 +70,4 @@ function GraphLegand({ style }: GraphLegandProps) { | |||||
| ); | ); | ||||
| } | } | ||||
| export default GraphLegand; | |||||
| export default GraphLegend; | |||||
| @@ -2,268 +2,20 @@ import { useEffectWhen } from '@/hooks'; | |||||
| import { ResourceVersionData } from '@/pages/Dataset/config'; | import { ResourceVersionData } from '@/pages/Dataset/config'; | ||||
| import { getModelAtlasReq } from '@/services/dataset/index.js'; | import { getModelAtlasReq } from '@/services/dataset/index.js'; | ||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { changePropertyName, fittingString } from '@/utils'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import G6, { | |||||
| EdgeConfig, | |||||
| G6GraphEvent, | |||||
| Graph, | |||||
| GraphData, | |||||
| LayoutConfig, | |||||
| NodeConfig, | |||||
| TreeGraphData, | |||||
| Util, | |||||
| } from '@antv/g6'; | |||||
| import G6, { G6GraphEvent, Graph } from '@antv/g6'; | |||||
| // @ts-ignore | // @ts-ignore | ||||
| import Hierarchy from '@antv/hierarchy'; | |||||
| import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceIntro'; | |||||
| import { Flex, Select } from 'antd'; | import { Flex, Select } from 'antd'; | ||||
| import { useEffect, useRef, useState } from 'react'; | import { useEffect, useRef, useState } from 'react'; | ||||
| import GraphLegand from '../GraphLegand'; | |||||
| import GraphLegend from '../GraphLegend'; | |||||
| import NodeTooltips from '../NodeTooltips'; | import NodeTooltips from '../NodeTooltips'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| const nodeWidth = 98; | |||||
| const nodeHeight = 58; | |||||
| const vGap = 58; | |||||
| const hGap = 58; | |||||
| enum NodeType { | |||||
| current = 'current', | |||||
| parent = 'parent', | |||||
| children = 'children', | |||||
| project = 'project', | |||||
| trainDataset = 'trainDataset', | |||||
| testDataset = 'testDataset', | |||||
| } | |||||
| type TrainTask = { | |||||
| ins_id: number; | |||||
| name: string; | |||||
| task_id: string; | |||||
| }; | |||||
| interface TrainDataset extends NodeConfig { | |||||
| dataset_id: number; | |||||
| dataset_name: string; | |||||
| dataset_version: string; | |||||
| model_type: NodeType; | |||||
| } | |||||
| interface ProjectDependency extends NodeConfig { | |||||
| url: string; | |||||
| name: string; | |||||
| branch: string; | |||||
| model_type: NodeType; | |||||
| } | |||||
| type ModalDetail = { | |||||
| name: string; | |||||
| available_range: number; | |||||
| file_name: string; | |||||
| file_size: string; | |||||
| description: string; | |||||
| model_type_name: string; | |||||
| model_tag_name: string; | |||||
| create_time: string; | |||||
| }; | |||||
| interface ModelDepsAPIData { | |||||
| current_model_id: number; | |||||
| version: string; | |||||
| exp_ins_id: number; | |||||
| model_type: NodeType; | |||||
| current_model_name: string; | |||||
| project_dependency: ProjectDependency; | |||||
| test_dataset: TrainDataset[]; | |||||
| train_dataset: TrainDataset[]; | |||||
| train_task: TrainTask; | |||||
| model_version_dependcy_vo: ModalDetail; | |||||
| children_models: ModelDepsAPIData[]; | |||||
| parent_models: ModelDepsAPIData[]; | |||||
| } | |||||
| export interface ModelDepsData extends Omit<ModelDepsAPIData, 'children_models'>, TreeGraphData { | |||||
| children: ModelDepsData[]; | |||||
| } | |||||
| // 规范化子数据 | |||||
| function normalizeChildren(data: ModelDepsData[]) { | |||||
| if (Array.isArray(data)) { | |||||
| data.forEach((item) => { | |||||
| item.model_type = NodeType.children; | |||||
| item.id = `$M_${item.current_model_id}_${item.version}`; | |||||
| item.label = getLabel(item); | |||||
| item.style = getStyle(NodeType.children); | |||||
| normalizeChildren(item.children); | |||||
| }); | |||||
| } | |||||
| } | |||||
| // 获取 label | |||||
| function getLabel(node: { current_model_name: string; version: string }) { | |||||
| return ( | |||||
| fittingString(`${node.current_model_name}`, 87, 8) + | |||||
| '\n' + | |||||
| fittingString(`${node.version}`, 87, 8) | |||||
| ); | |||||
| } | |||||
| // 获取 style | |||||
| function getStyle(model_type: NodeType) { | |||||
| let fill = ''; | |||||
| switch (model_type) { | |||||
| case NodeType.current: | |||||
| fill = '#1664ff'; | |||||
| break; | |||||
| case NodeType.parent: | |||||
| fill = '#76b1ff'; | |||||
| break; | |||||
| case NodeType.children: | |||||
| fill = '#b7cfff'; | |||||
| break; | |||||
| case NodeType.project: | |||||
| fill = '#FA8C16'; | |||||
| break; | |||||
| case NodeType.trainDataset: | |||||
| fill = '#ff0000'; | |||||
| break; | |||||
| case NodeType.testDataset: | |||||
| fill = '#ff00ff'; | |||||
| break; | |||||
| default: | |||||
| break; | |||||
| } | |||||
| return { | |||||
| fill, | |||||
| }; | |||||
| } | |||||
| // 将后台返回的数据转换成树形数据 | |||||
| function normalizeTreeData(apiData: ModelDepsAPIData, currentNodeName: string): ModelDepsData { | |||||
| // 将 children_models 转换成 children | |||||
| let normalizedData = changePropertyName(apiData, { | |||||
| children_models: 'children', | |||||
| }) as ModelDepsData; | |||||
| // 设置当前模型的数据 | |||||
| normalizedData.model_type = NodeType.current; | |||||
| normalizedData.current_model_name = currentNodeName; | |||||
| normalizedData.id = `$M_${normalizedData.current_model_id}_${normalizedData.version}`; | |||||
| normalizedData.label = getLabel(normalizedData); | |||||
| normalizedData.style = getStyle(NodeType.current); | |||||
| normalizeChildren(normalizedData.children as ModelDepsData[]); | |||||
| // 将 parent_models 转换成树形结构 | |||||
| let parent_models = normalizedData.parent_models || []; | |||||
| while (parent_models.length > 0) { | |||||
| const parent = parent_models[0]; | |||||
| normalizedData = { | |||||
| ...parent, | |||||
| model_type: NodeType.parent, | |||||
| id: `$M_${parent.current_model_id}_${parent.version}`, | |||||
| label: getLabel(parent), | |||||
| style: getStyle(NodeType.parent), | |||||
| children: [ | |||||
| { | |||||
| ...normalizedData, | |||||
| parent_models: [], | |||||
| }, | |||||
| ], | |||||
| }; | |||||
| parent_models = normalizedData.parent_models || []; | |||||
| } | |||||
| return normalizedData; | |||||
| } | |||||
| // 将树形数据,使用 Hierarchy 进行布局,计算出坐标,然后转换成 G6 的数据 | |||||
| function getGraphData(data: ModelDepsData): GraphData { | |||||
| const config = { | |||||
| direction: 'LR', | |||||
| getHeight: () => nodeHeight, | |||||
| getWidth: () => nodeWidth, | |||||
| getVGap: () => vGap / 2, | |||||
| getHGap: () => hGap / 2, | |||||
| }; | |||||
| // 树形布局计算出坐标 | |||||
| const treeLayoutData: LayoutConfig = Hierarchy['compactBox'](data, config); | |||||
| const nodes: NodeConfig[] = []; | |||||
| const edges: EdgeConfig[] = []; | |||||
| Util.traverseTree(treeLayoutData, (node: NodeConfig, parent: NodeConfig) => { | |||||
| const data = node.data as ModelDepsData; | |||||
| nodes.push({ | |||||
| ...data, | |||||
| x: node.x, | |||||
| y: node.y, | |||||
| }); | |||||
| if (parent) { | |||||
| edges.push({ | |||||
| source: parent.id, | |||||
| target: node.id, | |||||
| }); | |||||
| } | |||||
| // 当前模型显示数据集和项目 | |||||
| if (data.model_type === NodeType.current) { | |||||
| const { project_dependency, train_dataset, test_dataset } = data; | |||||
| train_dataset.forEach((item) => { | |||||
| item.id = `$DTrain_${item.dataset_id}`; | |||||
| item.model_type = NodeType.trainDataset; | |||||
| item.type = 'ellipse'; | |||||
| item.label = fittingString(`${item.dataset_name}`, 87, 8); | |||||
| item.style = getStyle(NodeType.trainDataset); | |||||
| }); | |||||
| test_dataset.forEach((item) => { | |||||
| item.id = `$DTest_${item.dataset_id}`; | |||||
| item.model_type = NodeType.testDataset; | |||||
| item.type = 'ellipse'; | |||||
| item.label = fittingString(item.dataset_name, 87, 8); | |||||
| item.style = getStyle(NodeType.testDataset); | |||||
| }); | |||||
| const len = train_dataset.length + test_dataset.length; | |||||
| [...train_dataset, ...test_dataset].forEach((item, index) => { | |||||
| const half = len / 2 - 0.5; | |||||
| item.x = node.x! - (half - index) * (nodeWidth + hGap); | |||||
| item.y = node.y! - nodeHeight - vGap; | |||||
| nodes.push(item); | |||||
| edges.push({ | |||||
| source: node.id, | |||||
| target: item.id, | |||||
| sourceAnchor: 2, | |||||
| targetAnchor: 3, | |||||
| type: 'cubic-vertical', | |||||
| }); | |||||
| }); | |||||
| if (project_dependency?.url) { | |||||
| project_dependency.id = `$P_${project_dependency.url}`; | |||||
| project_dependency.model_type = NodeType.project; | |||||
| project_dependency.type = 'rect'; | |||||
| project_dependency.size = [nodeHeight, nodeHeight]; | |||||
| project_dependency.label = fittingString(project_dependency.name, 48, 8); | |||||
| project_dependency.style = getStyle(NodeType.project); | |||||
| project_dependency.x = node.x; | |||||
| project_dependency.y = node.y! + nodeHeight + vGap; | |||||
| nodes.push(project_dependency); | |||||
| edges.push({ | |||||
| source: node.id, | |||||
| target: project_dependency.id, | |||||
| sourceAnchor: 3, | |||||
| targetAnchor: 2, | |||||
| type: 'cubic-vertical', | |||||
| }); | |||||
| } | |||||
| } | |||||
| }); | |||||
| return { nodes, edges }; | |||||
| } | |||||
| import type { ModelDepsData, ProjectDependency, TrainDataset } from './utils'; | |||||
| import { NodeType, getGraphData, nodeHeight, nodeWidth, normalizeTreeData } from './utils'; | |||||
| type modeModelEvolutionProps = { | type modeModelEvolutionProps = { | ||||
| resourceId: number; | resourceId: number; | ||||
| resourceName: string; | |||||
| versionList: ResourceVersionData[]; | versionList: ResourceVersionData[]; | ||||
| version?: string; | version?: string; | ||||
| isActive: boolean; | isActive: boolean; | ||||
| @@ -273,7 +25,6 @@ type modeModelEvolutionProps = { | |||||
| let graph: Graph; | let graph: Graph; | ||||
| function ModelEvolution({ | function ModelEvolution({ | ||||
| resourceId, | resourceId, | ||||
| resourceName, | |||||
| versionList, | versionList, | ||||
| version, | version, | ||||
| isActive, | isActive, | ||||
| @@ -284,20 +35,22 @@ function ModelEvolution({ | |||||
| const [enterTooltip, setEnterTooltip] = useState(false); | const [enterTooltip, setEnterTooltip] = useState(false); | ||||
| const [nodeTooltipX, setNodeToolTipX] = useState(0); | const [nodeTooltipX, setNodeToolTipX] = useState(0); | ||||
| const [nodeTooltipY, setNodeToolTipY] = useState(0); | const [nodeTooltipY, setNodeToolTipY] = useState(0); | ||||
| const [hoverNodeData, setHoverNodeData] = useState<ModelDepsData | undefined>(undefined); | |||||
| const [hoverNodeData, setHoverNodeData] = useState< | |||||
| ModelDepsData | ProjectDependency | TrainDataset | undefined | |||||
| >(undefined); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| initGraph(); | initGraph(); | ||||
| const changSize = () => { | |||||
| const changeSize = () => { | |||||
| if (!graph || graph.get('destroyed')) return; | if (!graph || graph.get('destroyed')) return; | ||||
| if (!graphRef.current) return; | if (!graphRef.current) return; | ||||
| graph.changeSize(graphRef.current.clientWidth, graphRef.current.clientHeight); | graph.changeSize(graphRef.current.clientWidth, graphRef.current.clientHeight); | ||||
| graph.fitView(); | graph.fitView(); | ||||
| }; | }; | ||||
| window.addEventListener('resize', changSize); | |||||
| window.addEventListener('resize', changeSize); | |||||
| return () => { | return () => { | ||||
| window.removeEventListener('resize', changSize); | |||||
| window.removeEventListener('resize', changeSize); | |||||
| }; | }; | ||||
| }, []); | }, []); | ||||
| @@ -344,6 +97,7 @@ function ModelEvolution({ | |||||
| fill: '#ffffff', | fill: '#ffffff', | ||||
| fontSize: 8, | fontSize: 8, | ||||
| textAlign: 'center', | textAlign: 'center', | ||||
| cursor: 'pointer', | |||||
| }, | }, | ||||
| }, | }, | ||||
| }, | }, | ||||
| @@ -383,27 +137,21 @@ function ModelEvolution({ | |||||
| graph.setItemState(nodeItem, 'hover', true); | graph.setItemState(nodeItem, 'hover', true); | ||||
| const model = nodeItem.getModel() as ModelDepsData; | const model = nodeItem.getModel() as ModelDepsData; | ||||
| const { x, y, model_type } = model; | |||||
| if ( | |||||
| model_type === NodeType.project || | |||||
| model_type === NodeType.trainDataset || | |||||
| model_type === NodeType.testDataset | |||||
| ) { | |||||
| return; | |||||
| } | |||||
| const { x, y } = model; | |||||
| const point = graph.getCanvasByPoint(x!, y!); | const point = graph.getCanvasByPoint(x!, y!); | ||||
| const zoom = graph.getZoom(); | |||||
| // 更加缩放,调整 tooltip 位置 | |||||
| const offsetX = (nodeWidth * zoom) / 4; | |||||
| const offsetY = (nodeHeight * zoom) / 4; | |||||
| const canvasWidth = graphRef.current!.clientWidth; | const canvasWidth = graphRef.current!.clientWidth; | ||||
| if (point.x + 300 > canvasWidth) { | if (point.x + 300 > canvasWidth) { | ||||
| point.x = canvasWidth - 300; | point.x = canvasWidth - 300; | ||||
| } | } | ||||
| const zoom = graph.getZoom(); | |||||
| // 更加缩放,调整 tooltip 位置 | |||||
| const offsetY = (nodeHeight * zoom) / 4; | |||||
| setHoverNodeData(model); | setHoverNodeData(model); | ||||
| setNodeToolTipX(point.x); | |||||
| // 92: 版本选择器的高度,296: tooltip的高度 | |||||
| setNodeToolTipY(point.y + 92 - 296 - offsetY); | |||||
| setNodeToolTipX(point.x + offsetX); | |||||
| setNodeToolTipY(graphRef.current!.clientHeight - point.y + offsetY); | |||||
| setShowNodeTooltip(true); | setShowNodeTooltip(true); | ||||
| }); | }); | ||||
| @@ -423,7 +171,7 @@ function ModelEvolution({ | |||||
| case NodeType.children: | case NodeType.children: | ||||
| case NodeType.parent: { | case NodeType.parent: { | ||||
| const { current_model_id, version } = model as ModelDepsData; | const { current_model_id, version } = model as ModelDepsData; | ||||
| url = `${origin}/dataset/model/${current_model_id}?tab=3&version=${version}`; | |||||
| url = `${origin}/dataset/model/${current_model_id}?tab=${ResourceInfoTabKeys.Evolution}&version=${version}`; | |||||
| break; | break; | ||||
| } | } | ||||
| case NodeType.project: { | case NodeType.project: { | ||||
| @@ -434,7 +182,7 @@ function ModelEvolution({ | |||||
| case NodeType.trainDataset: | case NodeType.trainDataset: | ||||
| case NodeType.testDataset: { | case NodeType.testDataset: { | ||||
| const { dataset_id, dataset_version } = model as TrainDataset; | const { dataset_id, dataset_version } = model as TrainDataset; | ||||
| url = `${origin}/dataset/dataset/${dataset_id}?tab=2&version=${dataset_version}`; | |||||
| url = `${origin}/dataset/dataset/${dataset_id}?tab=${ResourceInfoTabKeys.Version}&version=${dataset_version}`; | |||||
| break; | break; | ||||
| } | } | ||||
| default: | default: | ||||
| @@ -464,12 +212,12 @@ function ModelEvolution({ | |||||
| // 获取模型依赖 | // 获取模型依赖 | ||||
| const getModelAtlas = async () => { | const getModelAtlas = async () => { | ||||
| const params = { | const params = { | ||||
| model_id: resourceId, | |||||
| current_model_id: resourceId, | |||||
| version, | version, | ||||
| }; | }; | ||||
| const [res] = await to(getModelAtlasReq(params)); | const [res] = await to(getModelAtlasReq(params)); | ||||
| if (res && res.data) { | if (res && res.data) { | ||||
| const data = normalizeTreeData(res.data, resourceName); | |||||
| const data = normalizeTreeData(res.data); | |||||
| const graphData = getGraphData(data); | const graphData = getGraphData(data); | ||||
| graph.data(graphData); | graph.data(graphData); | ||||
| @@ -502,7 +250,7 @@ function ModelEvolution({ | |||||
| onChange={onVersionChange} | onChange={onVersionChange} | ||||
| options={versionList} | options={versionList} | ||||
| /> | /> | ||||
| <GraphLegand style={{ marginRight: 0, marginLeft: 'auto' }}></GraphLegand> | |||||
| <GraphLegend style={{ marginRight: 0, marginLeft: 'auto' }}></GraphLegend> | |||||
| </Flex> | </Flex> | ||||
| <div className={styles['model-evolution__graph']} id="canvas" ref={graphRef}></div> | <div className={styles['model-evolution__graph']} id="canvas" ref={graphRef}></div> | ||||
| {(showNodeTooltip || enterTooltip) && ( | {(showNodeTooltip || enterTooltip) && ( | ||||
| @@ -0,0 +1,328 @@ | |||||
| import { changePropertyName, fittingString } from '@/utils'; | |||||
| import { EdgeConfig, GraphData, LayoutConfig, NodeConfig, TreeGraphData, Util } from '@antv/g6'; | |||||
| // @ts-ignore | |||||
| import Hierarchy from '@antv/hierarchy'; | |||||
| export const nodeWidth = 110; | |||||
| export const nodeHeight = 50; | |||||
| export const vGap = nodeHeight + 20; | |||||
| export const hGap = nodeHeight + 20; | |||||
| export const ellipseWidth = nodeWidth; | |||||
| // 数据集节点 | |||||
| const datasetNodes: NodeConfig[] = []; | |||||
| export enum NodeType { | |||||
| current = 'current', | |||||
| parent = 'parent', | |||||
| children = 'children', | |||||
| project = 'project', | |||||
| trainDataset = 'trainDataset', | |||||
| testDataset = 'testDataset', | |||||
| } | |||||
| export type Rect = { | |||||
| x: number; // 矩形中心的 x 坐标 | |||||
| y: number; // 矩形中心的 y 坐标 | |||||
| width: number; | |||||
| height: number; | |||||
| }; | |||||
| export type TrainTask = { | |||||
| ins_id: number; | |||||
| name: string; | |||||
| task_id: string; | |||||
| }; | |||||
| export interface TrainDataset extends NodeConfig { | |||||
| dataset_id: number; | |||||
| dataset_name: string; | |||||
| dataset_version: string; | |||||
| model_type: NodeType; | |||||
| } | |||||
| export interface ProjectDependency extends NodeConfig { | |||||
| url: string; | |||||
| name: string; | |||||
| branch: string; | |||||
| model_type: NodeType; | |||||
| } | |||||
| export type ModalDetail = { | |||||
| name: string; | |||||
| available_range: number; | |||||
| file_name: string; | |||||
| file_size: string; | |||||
| description: string; | |||||
| model_type_name: string; | |||||
| model_tag_name: string; | |||||
| create_time: string; | |||||
| }; | |||||
| export interface ModelDepsAPIData { | |||||
| current_model_id: number; | |||||
| version: string; | |||||
| exp_ins_id: number; | |||||
| model_type: NodeType; | |||||
| current_model_name: string; | |||||
| project_dependency: ProjectDependency; | |||||
| test_dataset: TrainDataset[]; | |||||
| train_dataset: TrainDataset[]; | |||||
| train_task: TrainTask; | |||||
| model_version_dependcy_vo: ModalDetail; | |||||
| children_models: ModelDepsAPIData[]; | |||||
| parent_models: ModelDepsAPIData[]; | |||||
| } | |||||
| export interface ModelDepsData extends Omit<ModelDepsAPIData, 'children_models'>, TreeGraphData { | |||||
| children: ModelDepsData[]; | |||||
| } | |||||
| // 规范化子数据 | |||||
| export function normalizeChildren(data: ModelDepsData[]) { | |||||
| if (Array.isArray(data)) { | |||||
| data.forEach((item) => { | |||||
| item.model_type = NodeType.children; | |||||
| item.id = `$M_${item.current_model_id}_${item.version}`; | |||||
| item.label = getLabel(item); | |||||
| item.style = getStyle(NodeType.children); | |||||
| normalizeChildren(item.children); | |||||
| }); | |||||
| } | |||||
| } | |||||
| // 获取 label | |||||
| export function getLabel(node: ModelDepsData | ModelDepsAPIData) { | |||||
| return ( | |||||
| fittingString(`${node.model_version_dependcy_vo.name ?? ''}`, nodeWidth - 12, 8) + | |||||
| '\n' + | |||||
| fittingString(`${node.version}`, nodeWidth - 12, 8) | |||||
| ); | |||||
| } | |||||
| // 获取 style | |||||
| export function getStyle(model_type: NodeType) { | |||||
| let fill = ''; | |||||
| switch (model_type) { | |||||
| case NodeType.current: | |||||
| fill = 'l(0) 0:#72a1ff 1:#1664ff'; | |||||
| break; | |||||
| case NodeType.parent: | |||||
| fill = 'l(0) 0:#93dfd1 1:#43c9b1'; | |||||
| break; | |||||
| case NodeType.children: | |||||
| fill = 'l(0) 0:#72b4ff 1:#169aff'; | |||||
| break; | |||||
| case NodeType.project: | |||||
| fill = 'l(0) 0:#b3a9ff 1:#8981ff'; | |||||
| break; | |||||
| case NodeType.trainDataset: | |||||
| fill = '#a5d878'; | |||||
| break; | |||||
| case NodeType.testDataset: | |||||
| fill = '#d8b578'; | |||||
| break; | |||||
| default: | |||||
| break; | |||||
| } | |||||
| return { | |||||
| fill, | |||||
| }; | |||||
| } | |||||
| // 将后台返回的数据转换成树形数据 | |||||
| export function normalizeTreeData(apiData: ModelDepsAPIData): ModelDepsData { | |||||
| // 将 children_models 转换成 children | |||||
| let normalizedData = changePropertyName(apiData, { | |||||
| children_models: 'children', | |||||
| }) as ModelDepsData; | |||||
| // 设置当前模型的数据 | |||||
| normalizedData.model_type = NodeType.current; | |||||
| normalizedData.id = `$M_${normalizedData.current_model_id}_${normalizedData.version}`; | |||||
| normalizedData.label = getLabel(normalizedData); | |||||
| normalizedData.style = getStyle(NodeType.current); | |||||
| normalizeChildren(normalizedData.children as ModelDepsData[]); | |||||
| // 将 parent_models 转换成树形结构 | |||||
| let parent_models = normalizedData.parent_models || []; | |||||
| while (parent_models.length > 0) { | |||||
| const parent = parent_models[0]; | |||||
| normalizedData = { | |||||
| ...parent, | |||||
| model_type: NodeType.parent, | |||||
| id: `$M_${parent.current_model_id}_${parent.version}`, | |||||
| label: getLabel(parent), | |||||
| style: getStyle(NodeType.parent), | |||||
| children: [ | |||||
| { | |||||
| ...normalizedData, | |||||
| parent_models: [], | |||||
| }, | |||||
| ], | |||||
| }; | |||||
| parent_models = normalizedData.parent_models || []; | |||||
| } | |||||
| return normalizedData; | |||||
| } | |||||
| // 将树形数据,使用 Hierarchy 进行布局,计算出坐标,然后转换成 G6 的数据 | |||||
| export function getGraphData(data: ModelDepsData): GraphData { | |||||
| const config = { | |||||
| direction: 'LR', | |||||
| getHeight: () => nodeHeight, | |||||
| getWidth: () => nodeWidth, | |||||
| getVGap: () => vGap / 2, | |||||
| getHGap: () => hGap / 2, | |||||
| }; | |||||
| // 树形布局计算出坐标 | |||||
| const treeLayoutData: LayoutConfig = Hierarchy['compactBox'](data, config); | |||||
| const nodes: NodeConfig[] = []; | |||||
| const edges: EdgeConfig[] = []; | |||||
| Util.traverseTree(treeLayoutData, (node: NodeConfig, parent: NodeConfig) => { | |||||
| const data = node.data as ModelDepsData; | |||||
| // 当前模型显示数据集和项目 | |||||
| if (data.model_type === NodeType.current) { | |||||
| addDatasetDependency(data, node, nodes, edges); | |||||
| addProjectDependency(data, node, nodes, edges); | |||||
| } else if (data.model_type === NodeType.children) { | |||||
| adjustDatasetPosition(node); | |||||
| } | |||||
| nodes.push({ | |||||
| ...data, | |||||
| x: node.x, | |||||
| y: node.y, | |||||
| }); | |||||
| if (parent) { | |||||
| edges.push({ | |||||
| source: parent.id, | |||||
| target: node.id, | |||||
| }); | |||||
| } | |||||
| }); | |||||
| return { nodes, edges }; | |||||
| } | |||||
| // 将数据集转换成 G6 的数据 | |||||
| const addDatasetDependency = ( | |||||
| data: ModelDepsData, | |||||
| currentNode: NodeConfig, | |||||
| nodes: NodeConfig[], | |||||
| edges: EdgeConfig[], | |||||
| ) => { | |||||
| const { train_dataset, test_dataset } = data; | |||||
| train_dataset.forEach((item) => { | |||||
| item.id = `$DTrain_${item.dataset_id}_${item.dataset_version}`; | |||||
| item.model_type = NodeType.trainDataset; | |||||
| item.style = getStyle(NodeType.trainDataset); | |||||
| }); | |||||
| test_dataset.forEach((item) => { | |||||
| item.id = `$DTest_${item.dataset_id}_${item.dataset_version}`; | |||||
| item.model_type = NodeType.testDataset; | |||||
| item.style = getStyle(NodeType.testDataset); | |||||
| }); | |||||
| datasetNodes.length = 0; | |||||
| const len = train_dataset.length + test_dataset.length; | |||||
| [...train_dataset, ...test_dataset].forEach((item, index) => { | |||||
| const node = { ...item }; | |||||
| node.type = 'ellipse'; | |||||
| node.size = [ellipseWidth, nodeHeight]; | |||||
| node.label = | |||||
| fittingString(node.dataset_name, ellipseWidth - 12, 8) + | |||||
| '\n' + | |||||
| fittingString(node.dataset_version, ellipseWidth - 12, 8); | |||||
| const half = len / 2 - 0.5; | |||||
| node.x = currentNode.x! - (half - index) * (ellipseWidth + hGap / 2); | |||||
| node.y = currentNode.y! - nodeHeight - vGap; | |||||
| nodes.push(node); | |||||
| datasetNodes.push(node); | |||||
| edges.push({ | |||||
| source: currentNode.id, | |||||
| target: node.id, | |||||
| sourceAnchor: 2, | |||||
| targetAnchor: 3, | |||||
| type: 'cubic-vertical', | |||||
| }); | |||||
| }); | |||||
| }; | |||||
| // 将模型依赖数据转换成 G6 的数据 | |||||
| const addProjectDependency = ( | |||||
| data: ModelDepsData, | |||||
| currentNode: NodeConfig, | |||||
| nodes: NodeConfig[], | |||||
| edges: EdgeConfig[], | |||||
| ) => { | |||||
| const { project_dependency } = data; | |||||
| if (project_dependency?.url) { | |||||
| const node = { ...project_dependency }; | |||||
| node.id = `$P_${node.url}_${node.branch}`; | |||||
| node.model_type = NodeType.project; | |||||
| node.type = 'rect'; | |||||
| node.label = fittingString(node.name, nodeWidth - 12, 8); | |||||
| node.style = getStyle(NodeType.project); | |||||
| node.style.radius = nodeHeight / 2; | |||||
| node.x = currentNode.x; | |||||
| node.y = currentNode.y! + nodeHeight + vGap; | |||||
| nodes.push(node); | |||||
| edges.push({ | |||||
| source: currentNode.id, | |||||
| target: node.id, | |||||
| sourceAnchor: 3, | |||||
| targetAnchor: 2, | |||||
| type: 'cubic-vertical', | |||||
| }); | |||||
| } | |||||
| }; | |||||
| // 判断两个矩形是否相交 | |||||
| function isRectanglesOverlap(rect1: Rect, rect2: Rect) { | |||||
| const a2x = rect1.x + rect1.width / 2; | |||||
| const a2y = rect1.y + rect1.height / 2; | |||||
| const b1x = rect2.x - rect2.width / 2; | |||||
| const b1y = rect2.y - rect2.height / 2; | |||||
| return b1y <= a2y && b1x <= a2x; | |||||
| } | |||||
| // 判断子节点是否与数据集节点重叠 | |||||
| function isChildrenOverlapDataset(nodes: NodeConfig[], childrenRect: Rect) { | |||||
| for (const node of nodes) { | |||||
| const rect = { x: node.x!, y: node.y!, width: nodeWidth, height: nodeHeight }; | |||||
| if (isRectanglesOverlap(rect, childrenRect)) { | |||||
| return childrenRect; | |||||
| } | |||||
| } | |||||
| return null; | |||||
| } | |||||
| // 调整数据集位置 | |||||
| function adjustDatasetPosition(node: NodeConfig) { | |||||
| const nodeRect = { | |||||
| x: node.x!, | |||||
| y: node.y!, | |||||
| width: nodeWidth, | |||||
| height: nodeHeight, | |||||
| }; | |||||
| const overlapRect = isChildrenOverlapDataset(datasetNodes, nodeRect); | |||||
| if (overlapRect) { | |||||
| console.log(node); | |||||
| const adjustRect = { | |||||
| x: overlapRect.x - nodeWidth - hGap / 2, | |||||
| y: overlapRect.y, | |||||
| width: overlapRect.width, | |||||
| height: overlapRect.height, | |||||
| }; | |||||
| const lastNode = datasetNodes[datasetNodes.length - 1] as NodeConfig; | |||||
| const distance = lastNode.x! - adjustRect.x; | |||||
| datasetNodes.forEach((item) => { | |||||
| item.x = item.x! - distance; | |||||
| }); | |||||
| } | |||||
| } | |||||
| @@ -1,6 +1,6 @@ | |||||
| .node-tooltips { | .node-tooltips { | ||||
| position: absolute; | position: absolute; | ||||
| top: -100px; | |||||
| bottom: -100px; | |||||
| left: -300px; | left: -300px; | ||||
| width: 300px; | width: 300px; | ||||
| padding: 10px; | padding: 10px; | ||||
| @@ -18,7 +18,7 @@ | |||||
| &__row { | &__row { | ||||
| display: flex; | display: flex; | ||||
| align-items: center; | |||||
| align-items: flex-start; | |||||
| margin: 4px 0; | margin: 4px 0; | ||||
| color: @text-color; | color: @text-color; | ||||
| font-size: 14px; | font-size: 14px; | ||||
| @@ -43,14 +43,13 @@ | |||||
| min-width: 0; | min-width: 0; | ||||
| color: @text-color; | color: @text-color; | ||||
| font-weight: 500; | font-weight: 500; | ||||
| .singleLine(); | |||||
| word-break: break-all; | |||||
| } | } | ||||
| &__link { | &__link { | ||||
| flex: 1; | flex: 1; | ||||
| min-width: 0; | min-width: 0; | ||||
| font-weight: 500; | font-weight: 500; | ||||
| .singleLine(); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,16 +1,16 @@ | |||||
| import { formatDate } from '@/utils/date'; | import { formatDate } from '@/utils/date'; | ||||
| import { ModelDepsData } from '../ModelEvolution'; | |||||
| import { ModelDepsData, NodeType, ProjectDependency, TrainDataset } from '../ModelEvolution/utils'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type NodeTooltipsProps = { | type NodeTooltipsProps = { | ||||
| data: ModelDepsData; | |||||
| data?: ModelDepsData | ProjectDependency | TrainDataset; | |||||
| x: number; | x: number; | ||||
| y: number; | y: number; | ||||
| onMouseEnter?: () => void; | onMouseEnter?: () => void; | ||||
| onMouseLeave?: () => void; | onMouseLeave?: () => void; | ||||
| }; | }; | ||||
| function NodeTooltips({ data, x, y, onMouseEnter, onMouseLeave }: NodeTooltipsProps) { | |||||
| function ModelInfo({ data }: { data: ModelDepsData }) { | |||||
| const gotoExperimentPage = () => { | const gotoExperimentPage = () => { | ||||
| if (data.train_task?.ins_id) { | if (data.train_task?.ins_id) { | ||||
| const { origin } = location; | const { origin } = location; | ||||
| @@ -19,21 +19,18 @@ function NodeTooltips({ data, x, y, onMouseEnter, onMouseLeave }: NodeTooltipsPr | |||||
| }; | }; | ||||
| return ( | return ( | ||||
| <div | |||||
| className={styles['node-tooltips']} | |||||
| style={{ left: `${x}px`, top: `${y}px` }} | |||||
| onMouseEnter={onMouseEnter} | |||||
| onMouseLeave={onMouseLeave} | |||||
| > | |||||
| <> | |||||
| <div className={styles['node-tooltips__title']}>模型信息</div> | <div className={styles['node-tooltips__title']}>模型信息</div> | ||||
| <div> | <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__title']}>模型名称:</span> | ||||
| <span className={styles['node-tooltips__row__value']}>{data.current_model_name}</span> | |||||
| <span className={styles['node-tooltips__row__value']}> | |||||
| {data.model_version_dependcy_vo.name || '--'} | |||||
| </span> | |||||
| </div> | </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__title']}>模型版本:</span> | ||||
| <span className={styles['node-tooltips__row__value']}>{data.version}</span> | |||||
| <span className={styles['node-tooltips__row__value']}>{data.version || '--'}</span> | |||||
| </div> | </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__title']}>模型框架:</span> | ||||
| @@ -68,9 +65,76 @@ function NodeTooltips({ data, x, y, onMouseEnter, onMouseLeave }: NodeTooltipsPr | |||||
| <a className={styles['node-tooltips__row__link']} onClick={gotoExperimentPage}> | <a className={styles['node-tooltips__row__link']} onClick={gotoExperimentPage}> | ||||
| {data.train_task?.name} | {data.train_task?.name} | ||||
| </a> | </a> | ||||
| ) : null} | |||||
| ) : ( | |||||
| '--' | |||||
| )} | |||||
| </div> | |||||
| </div> | |||||
| </> | |||||
| ); | |||||
| } | |||||
| function DatasetInfo({ data }: { data: TrainDataset }) { | |||||
| return ( | |||||
| <> | |||||
| <div className={styles['node-tooltips__title']}>数据集信息</div> | |||||
| <div> | |||||
| <div className={styles['node-tooltips__row']}> | |||||
| <span className={styles['node-tooltips__row__title']}>数据集名称:</span> | |||||
| <span className={styles['node-tooltips__row__value']}>{data.dataset_name || '--'}</span> | |||||
| </div> | |||||
| <div className={styles['node-tooltips__row']}> | |||||
| <span className={styles['node-tooltips__row__title']}>数据集版本:</span> | |||||
| <span className={styles['node-tooltips__row__value']}> | |||||
| {data.dataset_version || '--'} | |||||
| </span> | |||||
| </div> | |||||
| </div> | |||||
| </> | |||||
| ); | |||||
| } | |||||
| function ProjectInfo({ data }: { data: ProjectDependency }) { | |||||
| return ( | |||||
| <> | |||||
| <div className={styles['node-tooltips__title']}>项目信息</div> | |||||
| <div> | |||||
| <div className={styles['node-tooltips__row']}> | |||||
| <span className={styles['node-tooltips__row__title']}>项目名称:</span> | |||||
| <span className={styles['node-tooltips__row__value']}>{data.name || '--'}</span> | |||||
| </div> | |||||
| <div className={styles['node-tooltips__row']}> | |||||
| <span className={styles['node-tooltips__row__title']}>项目分支:</span> | |||||
| <span className={styles['node-tooltips__row__value']}>{data.branch || '--'}</span> | |||||
| </div> | |||||
| <div className={styles['node-tooltips__row']}> | |||||
| <span className={styles['node-tooltips__row__title']}>项目地址:</span> | |||||
| <span className={styles['node-tooltips__row__value']}>{data.url || '--'}</span> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </> | |||||
| ); | |||||
| } | |||||
| function NodeTooltips({ data, x, y, onMouseEnter, onMouseLeave }: NodeTooltipsProps) { | |||||
| if (!data) return null; | |||||
| let Component; | |||||
| const { model_type } = data; | |||||
| if (model_type === NodeType.testDataset || model_type === NodeType.trainDataset) { | |||||
| Component = <DatasetInfo data={data as TrainDataset} />; | |||||
| } else if (model_type === NodeType.project) { | |||||
| Component = <ProjectInfo data={data as ProjectDependency} />; | |||||
| } else { | |||||
| Component = <ModelInfo data={data as ModelDepsData} />; | |||||
| } | |||||
| return ( | |||||
| <div | |||||
| className={styles['node-tooltips']} | |||||
| style={{ left: `${x}px`, bottom: `${y}px` }} | |||||
| onMouseEnter={onMouseEnter} | |||||
| onMouseLeave={onMouseLeave} | |||||
| > | |||||
| {Component} | |||||
| </div> | </div> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -9,37 +9,30 @@ | |||||
| padding: 30px 30px 0; | padding: 30px 30px 0; | ||||
| background-color: white; | background-color: white; | ||||
| border-radius: 10px; | border-radius: 10px; | ||||
| } | |||||
| &__basic { | |||||
| &__item { | |||||
| display: flex; | |||||
| align-items: flex-start; | |||||
| font-size: 16px; | |||||
| line-height: 1.6; | |||||
| &__tabs { | |||||
| flex: 1; | |||||
| min-height: 0; | |||||
| margin-top: 20px; | |||||
| padding-bottom: 10px; | |||||
| .label { | |||||
| flex: none; | |||||
| width: 80px; | |||||
| color: @text-color-secondary; | |||||
| } | |||||
| :global { | |||||
| .ant-tabs { | |||||
| height: 100%; | |||||
| .ant-tabs-nav { | |||||
| margin-bottom: 10px; | |||||
| } | |||||
| .ant-tabs-content { | |||||
| height: 100%; | |||||
| .value { | |||||
| flex: 1; | |||||
| color: @text-color; | |||||
| white-space: pre-line; | |||||
| word-break: break-all; | |||||
| .ant-tabs-tabpane { | |||||
| height: 100%; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| &__guide { | |||||
| flex: 1; | |||||
| margin-top: 10px; | |||||
| padding: 10px; | |||||
| overflow-y: auto; | |||||
| color: white; | |||||
| white-space: pre-wrap; | |||||
| background-color: rgba(0, 0, 0, 0.85); | |||||
| } | |||||
| } | } | ||||
| @@ -6,16 +6,13 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import PageTitle from '@/components/PageTitle'; | import PageTitle from '@/components/PageTitle'; | ||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import { useComputingResource } from '@/hooks/resource'; | |||||
| import { useSessionStorage } from '@/hooks/sessionStorage'; | import { useSessionStorage } from '@/hooks/sessionStorage'; | ||||
| import { getModelDeploymentDocsReq } from '@/services/modelDeployment'; | |||||
| import { formatDate } from '@/utils/date'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { modelDeploymentInfoKey } from '@/utils/sessionStorage'; | import { modelDeploymentInfoKey } from '@/utils/sessionStorage'; | ||||
| import { Col, Row, Tabs, type TabsProps } from 'antd'; | |||||
| import { pick } from 'lodash'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import ModelDeploymentStatusCell from '../components/ModelDeployStatusCell'; | |||||
| import { Tabs, type TabsProps } from 'antd'; | |||||
| import { useState } from 'react'; | |||||
| import BasicInfo from '../components/BasicInfo'; | |||||
| import ServerLog from '../components/ServerLog'; | |||||
| import UserGuide from '../components/UserGuide'; | |||||
| import { ModelDeploymentData } from '../types'; | import { ModelDeploymentData } from '../types'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -25,24 +22,6 @@ export enum ModelDeploymentTabKey { | |||||
| Log = 'Log', | Log = 'Log', | ||||
| } | } | ||||
| const tabItems = [ | |||||
| { | |||||
| key: ModelDeploymentTabKey.Predict, | |||||
| label: '预测', | |||||
| icon: <KFIcon type="icon-yuce" />, | |||||
| }, | |||||
| { | |||||
| key: ModelDeploymentTabKey.Guide, | |||||
| label: '调用指南', | |||||
| icon: <KFIcon type="icon-tiaoyongzhinan" />, | |||||
| }, | |||||
| { | |||||
| key: ModelDeploymentTabKey.Log, | |||||
| label: '服务日志', | |||||
| icon: <KFIcon type="icon-fuwurizhi" />, | |||||
| }, | |||||
| ]; | |||||
| function ModelDeploymentInfo() { | function ModelDeploymentInfo() { | ||||
| const [activeTab, setActiveTab] = useState<string>(ModelDeploymentTabKey.Predict); | const [activeTab, setActiveTab] = useState<string>(ModelDeploymentTabKey.Predict); | ||||
| const [modelDeployementInfo] = useSessionStorage<ModelDeploymentData | undefined>( | const [modelDeployementInfo] = useSessionStorage<ModelDeploymentData | undefined>( | ||||
| @@ -50,38 +29,32 @@ function ModelDeploymentInfo() { | |||||
| true, | true, | ||||
| undefined, | undefined, | ||||
| ); | ); | ||||
| const getResourceDescription = useComputingResource()[2]; | |||||
| const [docs, setDocs] = useState(''); | |||||
| useEffect(() => { | |||||
| getModelDeploymentDocs(); | |||||
| }, [modelDeployementInfo]); | |||||
| // 获取模型部署文档 | |||||
| const getModelDeploymentDocs = async () => { | |||||
| const params = pick(modelDeployementInfo, ['service_id', 'service_ins_id']); | |||||
| const [res] = await to(getModelDeploymentDocsReq(params)); | |||||
| if (res && res.data && res.data.docs) { | |||||
| setDocs(JSON.stringify(res.data.docs, null, 2)); | |||||
| } | |||||
| }; | |||||
| const tabItems = [ | |||||
| { | |||||
| key: ModelDeploymentTabKey.Predict, | |||||
| label: '预测', | |||||
| icon: <KFIcon type="icon-yuce" />, | |||||
| }, | |||||
| { | |||||
| key: ModelDeploymentTabKey.Guide, | |||||
| label: '调用指南', | |||||
| icon: <KFIcon type="icon-tiaoyongzhinan" />, | |||||
| children: <UserGuide info={modelDeployementInfo}></UserGuide>, | |||||
| }, | |||||
| { | |||||
| key: ModelDeploymentTabKey.Log, | |||||
| label: '服务日志', | |||||
| icon: <KFIcon type="icon-fuwurizhi" />, | |||||
| children: <ServerLog info={modelDeployementInfo}></ServerLog>, | |||||
| }, | |||||
| ]; | |||||
| // 切换 Tab,重置数据 | // 切换 Tab,重置数据 | ||||
| const hanleTabChange: TabsProps['onChange'] = (value) => { | const hanleTabChange: TabsProps['onChange'] = (value) => { | ||||
| setActiveTab(value); | setActiveTab(value); | ||||
| }; | }; | ||||
| // 格式化环境变量 | |||||
| const formatEnvText = () => { | |||||
| if (!modelDeployementInfo?.env) { | |||||
| return '--'; | |||||
| } | |||||
| const env = modelDeployementInfo.env; | |||||
| return Object.entries(env) | |||||
| .map(([key, value]) => `${key}: ${value}`) | |||||
| .join('\n'); | |||||
| }; | |||||
| return ( | return ( | ||||
| <div className={styles['model-deployment-info']}> | <div className={styles['model-deployment-info']}> | ||||
| <PageTitle title="服务详情"></PageTitle> | <PageTitle title="服务详情"></PageTitle> | ||||
| @@ -91,125 +64,10 @@ function ModelDeploymentInfo() { | |||||
| image={require('@/assets/img/mirror-basic.png')} | image={require('@/assets/img/mirror-basic.png')} | ||||
| style={{ marginBottom: '26px' }} | style={{ marginBottom: '26px' }} | ||||
| ></SubAreaTitle> | ></SubAreaTitle> | ||||
| <div className={styles['model-deployment-info__basic']}> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>服务名称:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.service_name ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>镜 像:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.image ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>状 态:</div> | |||||
| <div className={styles['value']}> | |||||
| {ModelDeploymentStatusCell(modelDeployementInfo?.status)} | |||||
| </div> | |||||
| </div> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>模 型:</div> | |||||
| <div className={styles['value']}> | |||||
| {modelDeployementInfo?.model?.show_value ?? '--'} | |||||
| </div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>创建人:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.created_by ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>挂载路径:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.model_path ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>API URL:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.url ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>副本数量:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.replicas ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>创建时间:</div> | |||||
| <div className={styles['value']}> | |||||
| {modelDeployementInfo?.create_time | |||||
| ? formatDate(modelDeployementInfo.create_time) | |||||
| : '--'} | |||||
| </div> | |||||
| </div> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>更新时间:</div> | |||||
| <div className={styles['value']}> | |||||
| {modelDeployementInfo?.update_time | |||||
| ? formatDate(modelDeployementInfo.update_time) | |||||
| : '--'} | |||||
| </div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>环境变量:</div> | |||||
| <div className={styles['value']}>{formatEnvText()}</div> | |||||
| </div> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>资源规格:</div> | |||||
| <div className={styles['value']}> | |||||
| {modelDeployementInfo?.resource | |||||
| ? getResourceDescription(modelDeployementInfo.resource) | |||||
| : '--'} | |||||
| </div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40}> | |||||
| <Col span={18}> | |||||
| <div className={styles['model-deployment-info__basic__item']}> | |||||
| <div className={styles['label']}>描 述:</div> | |||||
| <div className={styles['value']}>{modelDeployementInfo?.description ?? '--'}</div> | |||||
| </div> | |||||
| </Col> | |||||
| </Row> | |||||
| <BasicInfo info={modelDeployementInfo} /> | |||||
| <div className={styles['model-deployment-info__content__tabs']}> | |||||
| <Tabs activeKey={activeTab} items={tabItems} onChange={hanleTabChange} /> | |||||
| </div> | </div> | ||||
| <Tabs | |||||
| activeKey={activeTab} | |||||
| style={{ marginTop: '20px' }} | |||||
| items={tabItems} | |||||
| onChange={hanleTabChange} | |||||
| /> | |||||
| {activeTab === ModelDeploymentTabKey.Guide && ( | |||||
| <div className={styles['model-deployment-info__guide']}>{docs}</div> | |||||
| )} | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| ); | ); | ||||
| @@ -0,0 +1,91 @@ | |||||
| import LabelValue from '@/components/LabelValue'; | |||||
| import { useComputingResource } from '@/hooks/resource'; | |||||
| import { ModelDeploymentData } from '@/pages/ModelDeployment/types'; | |||||
| import { formatDate } from '@/utils/date'; | |||||
| import { Col, Row } from 'antd'; | |||||
| import ModelDeploymentStatusCell from '../ModelDeployStatusCell'; | |||||
| type BasicInfoProps = { | |||||
| info?: ModelDeploymentData; | |||||
| }; | |||||
| function BasicInfo({ info }: BasicInfoProps) { | |||||
| const getResourceDescription = useComputingResource()[2]; | |||||
| // 格式化环境变量 | |||||
| const formatEnvText = () => { | |||||
| if (!info?.env) { | |||||
| return '--'; | |||||
| } | |||||
| const env = info.env; | |||||
| return Object.entries(env) | |||||
| .map(([key, value]) => `${key}: ${value}`) | |||||
| .join('\n'); | |||||
| }; | |||||
| return ( | |||||
| <div> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <LabelValue label="服务名称:" value={info?.service_name}></LabelValue> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <LabelValue label="镜 像:" value={info?.image}></LabelValue> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <LabelValue | |||||
| label="状 态:" | |||||
| value={ModelDeploymentStatusCell(info?.status)} | |||||
| ></LabelValue> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <LabelValue label="模 型:" value={info?.model?.show_value}></LabelValue> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <LabelValue label="创建人:" value={info?.created_by}></LabelValue> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <LabelValue label="挂载路径:" value={info?.model_path}></LabelValue> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <LabelValue label="API URL:" value={info?.url}></LabelValue> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <LabelValue label="副本数量:" value={info?.replicas}></LabelValue> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <LabelValue label="创建时间:" value={formatDate(info?.create_time)}></LabelValue> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <LabelValue label="更新时间:" value={formatDate(info?.update_time)}></LabelValue> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||||
| <Col span={10}> | |||||
| <LabelValue label="环境变量:" value={formatEnvText()}></LabelValue> | |||||
| </Col> | |||||
| <Col span={10}> | |||||
| <LabelValue | |||||
| label="资源规格:" | |||||
| value={info?.resource ? getResourceDescription(info.resource) : '--'} | |||||
| ></LabelValue> | |||||
| </Col> | |||||
| </Row> | |||||
| <Row gutter={40}> | |||||
| <Col span={18}> | |||||
| <LabelValue label="描 述:" value={info?.description}></LabelValue> | |||||
| </Col> | |||||
| </Row> | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default BasicInfo; | |||||
| @@ -0,0 +1,16 @@ | |||||
| .server-log { | |||||
| height: 100%; | |||||
| &__data { | |||||
| height: calc(100% - 42px); | |||||
| margin-top: 10px; | |||||
| padding: 10px; | |||||
| overflow-y: auto; | |||||
| color: white; | |||||
| white-space: pre-wrap; | |||||
| background-color: rgba(0, 0, 0, 0.85); | |||||
| &__more { | |||||
| padding: 10px 0; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,147 @@ | |||||
| import { ModelDeploymentData } from '@/pages/ModelDeployment/types'; | |||||
| import { getModelDeploymentLogReq } from '@/services/modelDeployment'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { DoubleRightOutlined } from '@ant-design/icons'; | |||||
| import { Button, DatePicker, type TimeRangePickerProps } from 'antd'; | |||||
| import dayjs from 'dayjs'; | |||||
| import { pick } from 'lodash'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import styles from './index.less'; | |||||
| const { RangePicker } = DatePicker; | |||||
| // 滚动到底部 | |||||
| // const scrollToBottom = (smooth: boolean = true) => { | |||||
| // const element = document.getElementById('server-log'); | |||||
| // if (element) { | |||||
| // const optons: ScrollToOptions = { | |||||
| // top: element.scrollHeight, | |||||
| // behavior: smooth ? 'smooth' : 'instant', | |||||
| // }; | |||||
| // element.scrollTo(optons); | |||||
| // } | |||||
| // }; | |||||
| type LogData = { | |||||
| log_content: string; | |||||
| end_time: string; | |||||
| start_time: string; | |||||
| }; | |||||
| type ServerLogProps = { | |||||
| info?: ModelDeploymentData; | |||||
| }; | |||||
| function ServerLog({ info }: ServerLogProps) { | |||||
| const [dateRange, setDateRange] = useState<NonNullable<TimeRangePickerProps['value']>>([ | |||||
| dayjs().add(-1, 'h'), | |||||
| dayjs(), | |||||
| ]); | |||||
| const [logTime, setLogTime] = useState<[string, string]>([ | |||||
| `${dateRange[0]!.valueOf() * Math.pow(10, 6)}`, | |||||
| `${dateRange[1]!.valueOf() * Math.pow(10, 6)}`, | |||||
| ]); | |||||
| const [logData, setLogData] = useState<LogData[]>([]); | |||||
| const [hasMore, setHasMore] = useState(false); | |||||
| const rangePresets: TimeRangePickerProps['presets'] = [ | |||||
| { label: '最近 1 小时', value: [dayjs().add(-1, 'h'), dayjs()] }, | |||||
| { label: '最近 2 小时', value: [dayjs().add(-2, 'h'), dayjs()] }, | |||||
| { label: '最近 3 小时', value: [dayjs().add(-3, 'h'), dayjs()] }, | |||||
| { label: '最近 1 天', value: [dayjs().add(-1, 'd'), dayjs()] }, | |||||
| { label: '最近 2 天', value: [dayjs().add(-2, 'd'), dayjs()] }, | |||||
| { label: '最近 7 天', value: [dayjs().add(-7, 'd'), dayjs()] }, | |||||
| { label: '最近 14 天', value: [dayjs().add(-14, 'd'), dayjs()] }, | |||||
| { label: '最近 30 天', value: [dayjs().add(-30, 'd'), dayjs()] }, | |||||
| ]; | |||||
| useEffect(() => { | |||||
| getModelDeploymentLog(); | |||||
| }, [info, logTime]); | |||||
| // 获取模型部署日志 | |||||
| const getModelDeploymentLog = async () => { | |||||
| if (info && logTime && logTime.length === 2) { | |||||
| const params = { | |||||
| start_time: logTime[0], | |||||
| end_time: logTime[1], | |||||
| ...pick(info, ['service_id', 'service_ins_id']), | |||||
| }; | |||||
| const [res] = await to(getModelDeploymentLogReq(params)); | |||||
| if (res && res.data) { | |||||
| setLogData((prev) => [...prev, res.data]); | |||||
| setHasMore(!!res.data.log_content); | |||||
| // setTimeout(() => { | |||||
| // scrollToBottom(); | |||||
| // }, 100); | |||||
| } | |||||
| } | |||||
| }; | |||||
| // 搜索 | |||||
| const handleSearch = () => { | |||||
| setLogData([]); | |||||
| setHasMore(false); | |||||
| setLogTime([ | |||||
| `${dateRange[0]!.valueOf() * Math.pow(10, 6)}`, | |||||
| `${dateRange[1]!.valueOf() * Math.pow(10, 6)}`, | |||||
| ]); | |||||
| }; | |||||
| // 加载更多日志 | |||||
| const loadMoreLog = () => { | |||||
| const lastLog = logData[logData.length - 1]; | |||||
| setLogTime([lastLog.start_time, lastLog.end_time]); | |||||
| }; | |||||
| // 禁止选择今天之后和之前31天的日期 | |||||
| const disabledDate: TimeRangePickerProps['disabledDate'] = (currentDate) => { | |||||
| return ( | |||||
| Date.now() - currentDate.valueOf() < 0 || | |||||
| Date.now() - currentDate.valueOf() > 31 * 24 * 60 * 60 * 1000 | |||||
| ); | |||||
| }; | |||||
| // 处理日期变化 | |||||
| const handleRangeChange: TimeRangePickerProps['onChange'] = (dates) => { | |||||
| if (dates) { | |||||
| setDateRange(dates); | |||||
| } | |||||
| }; | |||||
| return ( | |||||
| <div className={styles['server-log']}> | |||||
| <div> | |||||
| <RangePicker | |||||
| presets={rangePresets} | |||||
| showTime | |||||
| value={dateRange} | |||||
| format="YYYY-MM-DD HH:mm:ss" | |||||
| onChange={handleRangeChange} | |||||
| allowClear={false} | |||||
| disabledDate={disabledDate} | |||||
| /> | |||||
| <Button type="default" style={{ marginLeft: '20px' }} onClick={handleSearch}> | |||||
| 查询 | |||||
| </Button> | |||||
| </div> | |||||
| {logData.length > 0 && ( | |||||
| <div className={styles['server-log__data']} id="server-log"> | |||||
| <div>{logData.map((v) => v.log_content).join('') || '暂无日志'}</div> | |||||
| {hasMore && ( | |||||
| <Button | |||||
| type="text" | |||||
| className={styles['server-log__data__more']} | |||||
| style={{ color: 'white' }} | |||||
| onClick={loadMoreLog} | |||||
| > | |||||
| 更多 | |||||
| <DoubleRightOutlined rotate={90} /> | |||||
| </Button> | |||||
| )} | |||||
| </div> | |||||
| )} | |||||
| </div> | |||||
| ); | |||||
| } | |||||
| export default ServerLog; | |||||
| @@ -0,0 +1,8 @@ | |||||
| .user-guide { | |||||
| height: 100%; | |||||
| padding: 10px; | |||||
| overflow-y: auto; | |||||
| color: white; | |||||
| white-space: pre-wrap; | |||||
| background-color: rgba(0, 0, 0, 0.85); | |||||
| } | |||||
| @@ -0,0 +1,32 @@ | |||||
| import { ModelDeploymentData } from '@/pages/ModelDeployment/types'; | |||||
| import { getModelDeploymentDocsReq } from '@/services/modelDeployment'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { pick } from 'lodash'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import styles from './index.less'; | |||||
| type UserGuideProps = { | |||||
| info?: ModelDeploymentData; | |||||
| }; | |||||
| function UserGuide({ info }: UserGuideProps) { | |||||
| const [docs, setDocs] = useState(''); | |||||
| useEffect(() => { | |||||
| getModelDeploymentDocs(); | |||||
| }, [info]); | |||||
| // 获取模型部署文档 | |||||
| const getModelDeploymentDocs = async () => { | |||||
| if (info) { | |||||
| const params = pick(info, ['service_id', 'service_ins_id']); | |||||
| const [res] = await to(getModelDeploymentDocsReq(params)); | |||||
| if (res && res.data && res.data.docs) { | |||||
| setDocs(JSON.stringify(res.data.docs, null, 2)); | |||||
| } | |||||
| } | |||||
| }; | |||||
| return <div className={styles['user-guide']}>{docs}</div>; | |||||
| } | |||||
| export default UserGuide; | |||||
| @@ -27,16 +27,16 @@ export type MirrorVersion = { | |||||
| }; | }; | ||||
| export type SelectorTypeInfo = { | export type SelectorTypeInfo = { | ||||
| getList: (params: any) => Promise<any>; | |||||
| getVersions: (params: any) => Promise<any>; | |||||
| getFiles: (params: any) => Promise<any>; | |||||
| handleVersionResponse: (res: any) => any[]; | |||||
| modalIcon: string; | |||||
| buttonIcon: string; | |||||
| name: string; | |||||
| litReqParamKey: 'available_range' | 'image_type'; | |||||
| fileReqParamKey: 'models_id' | 'dataset_id'; | |||||
| tabItems: TabsProps['items']; | |||||
| getList: (params: any) => Promise<any>; // 获取资源列表 | |||||
| getVersions: (params: any) => Promise<any>; // 获取资源版本列表 | |||||
| getFiles: (params: any) => Promise<any>; // 获取资源文件列表 | |||||
| handleVersionResponse: (res: any) => any[]; // 处理版本列表接口数据 | |||||
| modalIcon: string; // modal icon | |||||
| buttonIcon: string; // button icon | |||||
| name: string; // 名称 | |||||
| litReqParamKey: 'available_range' | 'image_type'; // 表示是公开还是私有的参数名称,获取资源列表接口使用 | |||||
| fileReqParamKey: 'models_id' | 'dataset_id'; // 文件请求参数名称,获取文件列表接口使用 | |||||
| tabItems: TabsProps['items']; // tab 列表 | |||||
| }; | }; | ||||
| // 获取镜像文件列表,为了兼容数据集和模型 | // 获取镜像文件列表,为了兼容数据集和模型 | ||||
| @@ -35,13 +35,11 @@ const EditPipeline = () => { | |||||
| }, []); | }, []); | ||||
| const onDragEnd = (val) => { | const onDragEnd = (val) => { | ||||
| console.log(val); | |||||
| const _x = val.x; | const _x = val.x; | ||||
| const _y = val.y; | const _y = val.y; | ||||
| const point = graph.getPointByClient(_x, _y); | const point = graph.getPointByClient(_x, _y); | ||||
| let model = {}; | |||||
| // 元模型 | // 元模型 | ||||
| model = { | |||||
| const model = { | |||||
| ...val, | ...val, | ||||
| x: point.x, | x: point.x, | ||||
| y: point.y, | y: point.y, | ||||
| @@ -67,3 +67,11 @@ export function getModelDeploymentDocsReq(data: any) { | |||||
| data, | data, | ||||
| }); | }); | ||||
| } | } | ||||
| // 获取模型部署日志 | |||||
| export function getModelDeploymentLogReq(data: any) { | |||||
| return request(`/api/v1/model/getAppLog`, { | |||||
| method: 'POST', | |||||
| data, | |||||
| }); | |||||
| } | |||||
| @@ -71,7 +71,13 @@ export const gotoLoginPage = (toHome: boolean = true) => { | |||||
| } | } | ||||
| }; | }; | ||||
| // 上传文件校验 | |||||
| /** | |||||
| * 验证文件上传 | |||||
| * | |||||
| * @param {UploadFile[]} files - The array of uploaded files. | |||||
| * @param {boolean} [required=true] - Flag indicating if files are required. | |||||
| * @return {boolean} Returns true if all files are valid, false otherwise. | |||||
| */ | |||||
| export const validateUploadFiles = (files: UploadFile[], required: boolean = true): boolean => { | export const validateUploadFiles = (files: UploadFile[], required: boolean = true): boolean => { | ||||
| if (required && files.length === 0) { | if (required && files.length === 0) { | ||||
| message.error('请上传文件'); | message.error('请上传文件'); | ||||
| @@ -95,3 +101,18 @@ export const validateUploadFiles = (files: UploadFile[], required: boolean = tru | |||||
| }); | }); | ||||
| return !hasError; | return !hasError; | ||||
| }; | }; | ||||
| /** | |||||
| * 滚动到底部 | |||||
| * | |||||
| * @param {boolean} smooth - Determines if the scroll should be smooth | |||||
| */ | |||||
| export const scrollToBottom = (element: HTMLElement | null, smooth: boolean = true) => { | |||||
| if (element) { | |||||
| const optons: ScrollToOptions = { | |||||
| top: element.scrollHeight, | |||||
| behavior: smooth ? 'smooth' : 'instant', | |||||
| }; | |||||
| element.scrollTo(optons); | |||||
| } | |||||
| }; | |||||
| @@ -167,8 +167,6 @@ public class ModelDependencyServiceImpl implements ModelDependencyService { | |||||
| modelsVersionquery.setModelsId(currentModelId); | modelsVersionquery.setModelsId(currentModelId); | ||||
| modelsVersionquery.setVersion(modelDependency.getVersion()); | modelsVersionquery.setVersion(modelDependency.getVersion()); | ||||
| ModelsVersion modelsVersion = modelsVersionService.queryByModelsVersion(modelsVersionquery); | ModelsVersion modelsVersion = modelsVersionService.queryByModelsVersion(modelsVersionquery); | ||||
| ExperimentIns experimentIns = experimentInsService.queryById(expInsId); | |||||
| Experiment experiment = experimentService.queryById(experimentIns.getExperimentId()); | |||||
| ModelVersionDependcyVo modelVersionDependcyVo = new ModelVersionDependcyVo(); | ModelVersionDependcyVo modelVersionDependcyVo = new ModelVersionDependcyVo(); | ||||
| modelVersionDependcyVo.setName(models.getName()); | modelVersionDependcyVo.setName(models.getName()); | ||||
| modelVersionDependcyVo.setAvailableRange(models.getAvailableRange()); | modelVersionDependcyVo.setAvailableRange(models.getAvailableRange()); | ||||
| @@ -182,9 +180,16 @@ public class ModelDependencyServiceImpl implements ModelDependencyService { | |||||
| modelVersionDependcyVo.setUrl(modelsVersion.getUrl()); | modelVersionDependcyVo.setUrl(modelsVersion.getUrl()); | ||||
| modelVersionDependcyVo.setCreateBy(modelsVersion.getCreateBy()); | modelVersionDependcyVo.setCreateBy(modelsVersion.getCreateBy()); | ||||
| modelVersionDependcyVo.setCreateTime(modelsVersion.getCreateTime()); | modelVersionDependcyVo.setCreateTime(modelsVersion.getCreateTime()); | ||||
| modelDependcyTreeVo.setWorkflowId(experiment.getWorkflowId()); | |||||
| modelDependcyTreeVo.setModelVersionDependcyVo(modelVersionDependcyVo); | modelDependcyTreeVo.setModelVersionDependcyVo(modelVersionDependcyVo); | ||||
| ExperimentIns experimentIns = experimentInsService.queryById(expInsId); | |||||
| if (experimentIns == null){ | |||||
| return modelDependcyTreeVo; | |||||
| } | |||||
| Experiment experiment = experimentService.queryById(experimentIns.getExperimentId()); | |||||
| if (experiment == null){ | |||||
| return modelDependcyTreeVo; | |||||
| } | |||||
| modelDependcyTreeVo.setWorkflowId(experiment.getWorkflowId()); | |||||
| return modelDependcyTreeVo; | return modelDependcyTreeVo; | ||||
| } | } | ||||
| @@ -27,7 +27,7 @@ | |||||
| id,current_model_id,exp_ins_id,parent_models,ref_item,train_task,train_dataset,train_params,train_image,test_dataset,project_dependency,version,create_by,create_time,update_by,update_time,state | id,current_model_id,exp_ins_id,parent_models,ref_item,train_task,train_dataset,train_params,train_image,test_dataset,project_dependency,version,create_by,create_time,update_by,update_time,state | ||||
| from model_dependency | from model_dependency | ||||
| <where> | <where> | ||||
| parent_models like concat('%', #{model_id}, '%') AND parent_models like concat('%', #{version}, '%') | |||||
| parent_models like concat('%', #{model_id}, '%') AND parent_models like concat('%', #{version}, '%') and state = 1 | |||||
| </where> | </where> | ||||
| </select> | </select> | ||||