| @@ -65,3 +65,4 @@ mvnw | |||
| /react-ui/types/tsconfig.tsbuildinfo | |||
| /react-ui/storybook-static | |||
| /react-ui/.storybook/scripts | |||
| /react-ui/dist.zip | |||
| @@ -1,8 +1,9 @@ | |||
| import { filterResourceStandard, resourceFieldNames } from '@/hooks/useComputingResource'; | |||
| import { DatasetData, ModelData } from '@/pages/Dataset/config'; | |||
| import { ServiceData } from '@/pages/ModelDeployment/types'; | |||
| import { getDatasetList, getModelList } from '@/services/dataset/index.js'; | |||
| import { getServiceListReq } from '@/services/modelDeployment'; | |||
| import type { JCCResourceImage, JCCResourceStandard, JCCResourceType } from '@/state/jcdResource'; | |||
| import { filterResourceStandard, resourceFieldNames } from '@/state/systemResource'; | |||
| import { type SelectProps } from 'antd'; | |||
| export type SelectPropsConfig = { | |||
| @@ -10,12 +11,21 @@ export type SelectPropsConfig = { | |||
| fieldNames?: SelectProps['fieldNames']; // 下拉数据字段 | |||
| optionFilterProp?: SelectProps['optionFilterProp']; // 过滤字段名 | |||
| filterOption?: SelectProps['filterOption']; // 过滤函数 | |||
| getValue: (value: any) => string | number; | |||
| getLabel: (value: any) => string; | |||
| isObjectValue: boolean; // value 是对象 | |||
| getValue?: (value: any) => string | number; // 对象类型时,获取其值 | |||
| getLabel?: (value: any) => string; // 对象类型时,获取其 label | |||
| }; | |||
| export const paramSelectConfig: Record<string, SelectPropsConfig> = { | |||
| export type ParameterSelectDataType = | |||
| | 'dataset' | |||
| | 'model' | |||
| | 'service' | |||
| | 'resource' | |||
| | 'remote-image' | |||
| | 'remote-resource-type' | |||
| | 'remote-resource'; | |||
| export const paramSelectConfig: Record<ParameterSelectDataType, SelectPropsConfig> = { | |||
| dataset: { | |||
| getOptions: async () => { | |||
| const res = await getDatasetList({ | |||
| @@ -72,14 +82,44 @@ export const paramSelectConfig: Record<string, SelectPropsConfig> = { | |||
| resource: { | |||
| fieldNames: resourceFieldNames, | |||
| filterOption: filterResourceStandard as SelectProps['filterOption'], | |||
| // 不会用到 | |||
| getValue: () => { | |||
| return ''; | |||
| isObjectValue: false, | |||
| }, | |||
| 'remote-resource-type': { | |||
| optionFilterProp: 'label', | |||
| isObjectValue: false, | |||
| getValue: (value: JCCResourceType) => { | |||
| return value.value; | |||
| }, | |||
| // 不会用的 | |||
| getLabel: () => { | |||
| return ''; | |||
| getLabel: (value: JCCResourceType) => { | |||
| return value.label; | |||
| }, | |||
| isObjectValue: false, | |||
| }, | |||
| 'remote-image': { | |||
| optionFilterProp: 'label', | |||
| getValue: (value: JCCResourceImage) => { | |||
| return value.imageID; | |||
| }, | |||
| getLabel: (value: JCCResourceImage) => { | |||
| return value.name; | |||
| }, | |||
| isObjectValue: true, | |||
| }, | |||
| 'remote-resource': { | |||
| optionFilterProp: 'label', | |||
| getValue: (value: JCCResourceStandard) => { | |||
| return value.id; | |||
| }, | |||
| getLabel: (value: JCCResourceStandard) => { | |||
| const cpu = value.baseResourceSpecs.find((v) => v.type === 'CPU'); | |||
| const ram = value.baseResourceSpecs.find((v) => v.type === 'MEMORY' && v.name === 'RAM'); | |||
| const vram = value.baseResourceSpecs.find((v) => v.type === 'MEMORY' && v.name === 'VRAM'); | |||
| const cpuText = cpu ? `CPU:${cpu.availableValue}, ` : ''; | |||
| const ramText = ram ? `内存: ${ram.availableValue}${ram.availableUnit?.toUpperCase()}` : ''; | |||
| const vramText = vram | |||
| ? `(显存${vram.availableValue}${vram.availableUnit?.toUpperCase()})` | |||
| : ''; | |||
| return `${value.type}: ${value.availableCount}*${value.name}${vramText}, ${cpuText}${ramText}`; | |||
| }, | |||
| isObjectValue: true, | |||
| }, | |||
| }; | |||
| @@ -4,19 +4,25 @@ | |||
| * @Description: 参数下拉选择组件,支持资源规格、数据集、模型、服务 | |||
| */ | |||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||
| import jccResourceState from '@/state/jcdResource'; | |||
| import systemResourceState, { getSystemResources } from '@/state/systemResource'; | |||
| import { to } from '@/utils/promise'; | |||
| import { useSnapshot } from '@umijs/max'; | |||
| import { Select, type SelectProps } from 'antd'; | |||
| import { useEffect, useMemo, useState } from 'react'; | |||
| import { useCallback, useEffect, useMemo, useState } from 'react'; | |||
| import FormInfo from '../FormInfo'; | |||
| import { paramSelectConfig } from './config'; | |||
| import { paramSelectConfig, type ParameterSelectDataType } from './config'; | |||
| export { type ParameterSelectDataType }; | |||
| export type ParameterSelectObject = { | |||
| value: any; | |||
| [key: string]: any; | |||
| }; | |||
| export type ParameterSelectDataType = 'dataset' | 'model' | 'service' | 'resource'; | |||
| type SelectOptions = SelectProps['options']; | |||
| const identityFunc = (value: any) => value; | |||
| export interface ParameterSelectProps extends SelectProps { | |||
| /** 类型 */ | |||
| @@ -25,8 +31,6 @@ export interface ParameterSelectProps extends SelectProps { | |||
| display?: boolean; | |||
| /** 值,支持对象,对象必须包含 value */ | |||
| value?: string | ParameterSelectObject; | |||
| /** 用于流水线, 流水线资源规格要求 id 为字符串 */ | |||
| isPipeline?: boolean; | |||
| /** 修改后回调 */ | |||
| onChange?: (value: string | ParameterSelectObject) => void; | |||
| } | |||
| @@ -36,15 +40,14 @@ function ParameterSelect({ | |||
| dataType, | |||
| display = false, | |||
| value, | |||
| // isPipeline = false, | |||
| onChange, | |||
| ...rest | |||
| }: ParameterSelectProps) { | |||
| const [options, setOptions] = useState<SelectProps['options']>([]); | |||
| const [options, setOptions] = useState<SelectOptions>([]); | |||
| const propsConfig = paramSelectConfig[dataType]; | |||
| const { | |||
| getLabel, | |||
| getValue, | |||
| getLabel = identityFunc, | |||
| getValue = identityFunc, | |||
| getOptions, | |||
| filterOption, | |||
| fieldNames, | |||
| @@ -55,29 +58,45 @@ function ParameterSelect({ | |||
| // 数据集、模型、服务,对象转换成 json 字符串 | |||
| const valueText = | |||
| typeof selectValue === 'object' && selectValue !== null ? getValue(selectValue) : selectValue; | |||
| const [resourceStandardList] = useComputingResource(); | |||
| // const computingResource = isPipeline | |||
| // ? resourceStandardList.map((v) => ({ | |||
| // ...v, | |||
| // id: String(v.id), | |||
| // })) | |||
| // : resourceStandardList; | |||
| const jccResourceSnap = useSnapshot(jccResourceState); | |||
| const { getResourceTypes } = jccResourceSnap; | |||
| const systemResourceSnap = useSnapshot(systemResourceState); | |||
| const objectOptions = useMemo(() => { | |||
| return options?.map((v) => ({ | |||
| label: getLabel(v), | |||
| value: getValue(v), | |||
| })); | |||
| }, [getLabel, getValue, options]); | |||
| return dataType === 'remote-resource-type' | |||
| ? jccResourceSnap.types | |||
| : dataType === 'remote-image' | |||
| ? jccResourceSnap.images | |||
| : dataType === 'remote-resource' | |||
| ? jccResourceSnap.resources | |||
| : options; | |||
| }, [dataType, options, jccResourceSnap.types, jccResourceSnap.images, jccResourceSnap.resources]); | |||
| // 将对象类型转换为 Select Options | |||
| const converObjectToOptions = useCallback( | |||
| (v: any) => { | |||
| return { | |||
| label: getLabel(v), | |||
| value: getValue(v), | |||
| }; | |||
| }, | |||
| [getLabel, getValue], | |||
| ); | |||
| // 数据集、模型、服务获取数据后,进行转换 | |||
| const objectSelectOptions = useMemo(() => { | |||
| return objectOptions?.map(converObjectToOptions); | |||
| }, [converObjectToOptions, objectOptions]); | |||
| // 快速得到选中的对象 | |||
| const valueMap = useMemo(() => { | |||
| const map = new Map<string | number, any>(); | |||
| options?.forEach((v) => { | |||
| objectOptions?.forEach((v) => { | |||
| map.set(getValue(v), v); | |||
| }); | |||
| return map; | |||
| }, [options, getValue]); | |||
| }, [objectOptions, getValue]); | |||
| useEffect(() => { | |||
| // 获取下拉数据 | |||
| @@ -87,13 +106,18 @@ function ParameterSelect({ | |||
| if (res) { | |||
| setOptions(res); | |||
| } | |||
| } else if (dataType === 'remote-resource-type') { | |||
| getResourceTypes(); | |||
| } else if (dataType === 'resource') { | |||
| getSystemResources(); | |||
| } | |||
| }; | |||
| getSelectOptions(); | |||
| }, [getOptions]); | |||
| }, [getOptions, dataType, getResourceTypes]); | |||
| const selectOptions = dataType === 'resource' ? resourceStandardList : objectOptions; | |||
| const selectOptions = ( | |||
| dataType === 'resource' ? systemResourceSnap.resources : objectSelectOptions | |||
| ) as SelectOptions; | |||
| const handleChange = (text: string) => { | |||
| // 数据集、模型、服务,转换成对象 | |||
| @@ -4,66 +4,90 @@ | |||
| * @Description: 资源规格 hook | |||
| */ | |||
| import { getComputingResourceReq } from '@/services/pipeline'; | |||
| import { ComputingResource } from '@/types'; | |||
| import { to } from '@/utils/promise'; | |||
| import { type SelectProps } from 'antd'; | |||
| import { useCallback, useEffect, useState } from 'react'; | |||
| // import { getComputingResourceReq } from '@/services/pipeline'; | |||
| // import { ComputingResource } from '@/types'; | |||
| // import { to } from '@/utils/promise'; | |||
| // import { type SelectProps } from 'antd'; | |||
| // import { useCallback, useEffect, useState } from 'react'; | |||
| const computingResource: ComputingResource[] = []; | |||
| // const computingResource: ComputingResource[] = []; | |||
| /** 过滤资源规格 */ | |||
| export const filterResourceStandard: SelectProps<string, ComputingResource>['filterOption'] = ( | |||
| input: string, | |||
| option?: ComputingResource, | |||
| ) => { | |||
| return ( | |||
| option?.computing_resource?.toLocaleLowerCase()?.includes(input.toLocaleLowerCase()) ?? false | |||
| ); | |||
| }; | |||
| // /** 过滤资源规格 */ | |||
| // export const filterResourceStandard: SelectProps<string, ComputingResource>['filterOption'] = ( | |||
| // input: string, | |||
| // option?: ComputingResource, | |||
| // ) => { | |||
| // return ( | |||
| // option?.computing_resource?.toLocaleLowerCase()?.includes(input.toLocaleLowerCase()) ?? false | |||
| // ); | |||
| // }; | |||
| /** 资源规格字段 */ | |||
| export const resourceFieldNames = { | |||
| label: 'description', | |||
| value: 'id', | |||
| }; | |||
| // /** 资源规格字段 */ | |||
| // export const resourceFieldNames = { | |||
| // label: 'description', | |||
| // value: 'id', | |||
| // }; | |||
| /** 获取资源规格 */ | |||
| export function useComputingResource() { | |||
| const [resourceStandardList, setResourceStandardList] = useState<ComputingResource[]>([]); | |||
| // /** 获取资源规格 */ | |||
| // export function useComputingResource() { | |||
| // const [resourceStandardList, setResourceStandardList] = useState<ComputingResource[]>([]); | |||
| useEffect(() => { | |||
| // 获取资源规格列表数据 | |||
| const getComputingResource = async () => { | |||
| const params = { | |||
| page: 0, | |||
| size: 1000, | |||
| resource_type: '', | |||
| }; | |||
| const [res] = await to(getComputingResourceReq(params)); | |||
| if (res && res.data && Array.isArray(res.data.content)) { | |||
| setResourceStandardList(res.data.content); | |||
| computingResource.splice(0, computingResource.length, ...res.data.content); | |||
| } | |||
| }; | |||
| // useEffect(() => { | |||
| // // 获取资源规格列表数据 | |||
| // const getComputingResource = async () => { | |||
| // const params = { | |||
| // page: 0, | |||
| // size: 1000, | |||
| // resource_type: '', | |||
| // }; | |||
| // const [res] = await to(getComputingResourceReq(params)); | |||
| // if (res && res.data && Array.isArray(res.data.content)) { | |||
| // setResourceStandardList(res.data.content); | |||
| // computingResource.splice(0, computingResource.length, ...res.data.content); | |||
| // } | |||
| // }; | |||
| // if (computingResource.length > 0) { | |||
| // setResourceStandardList(computingResource); | |||
| // } else { | |||
| // getComputingResource(); | |||
| // } | |||
| // }, []); | |||
| if (computingResource.length > 0) { | |||
| setResourceStandardList(computingResource); | |||
| } else { | |||
| getComputingResource(); | |||
| } | |||
| // // 根据 standard 获取 description | |||
| // const getDescription = useCallback( | |||
| // (id?: string | number) => { | |||
| // if (!id) { | |||
| // return undefined; | |||
| // } | |||
| // return resourceStandardList.find((item) => Number(item.id) === Number(id))?.description; | |||
| // }, | |||
| // [resourceStandardList], | |||
| // ); | |||
| // return [resourceStandardList, getDescription] as const; | |||
| // } | |||
| import state, { getSystemResources } from '@/state/systemResource'; | |||
| import { useSnapshot } from '@umijs/max'; | |||
| import { useCallback, useEffect } from 'react'; | |||
| export const useSystemResource = () => { | |||
| useEffect(() => { | |||
| getSystemResources(); | |||
| }, []); | |||
| // 根据 standard 获取 description | |||
| const snap = useSnapshot(state); | |||
| /* 根据 standard 获取 description */ | |||
| const getDescription = useCallback( | |||
| (id?: string | number) => { | |||
| if (!id) { | |||
| return undefined; | |||
| } | |||
| return resourceStandardList.find((item) => Number(item.id) === Number(id))?.description; | |||
| return snap.resources.find((item) => Number(item.id) === Number(id))?.description; | |||
| }, | |||
| [resourceStandardList], | |||
| [snap.resources], | |||
| ); | |||
| return [resourceStandardList, getDescription] as const; | |||
| } | |||
| return getDescription; | |||
| }; | |||
| @@ -1,6 +1,6 @@ | |||
| import ConfigInfo, { type BasicInfoData } from '@/components/ConfigInfo'; | |||
| import { AutoMLTaskType, autoMLTaskTypeOptions, ExperimentStatus } from '@/enums'; | |||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||
| import { useSystemResource } from '@/hooks/useComputingResource'; | |||
| import { | |||
| classifierAlgorithms, | |||
| FrameworkType, | |||
| @@ -39,7 +39,7 @@ function BasicInfo({ | |||
| instanceStatus, | |||
| isInstance = false, | |||
| }: BasicInfoProps) { | |||
| const getResourceDescription = useComputingResource()[1]; | |||
| const getResourceDescription = useSystemResource(); | |||
| const basicDatas: BasicInfoData[] = useMemo(() => { | |||
| if (!info) { | |||
| return []; | |||
| @@ -6,7 +6,7 @@ import { | |||
| autoMLEnsembleClassOptions, | |||
| autoMLTaskTypeOptions, | |||
| } from '@/enums'; | |||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||
| import { useSystemResource } from '@/hooks/useComputingResource'; | |||
| import { | |||
| classificationAlgorithms, | |||
| featureAlgorithms, | |||
| @@ -76,7 +76,7 @@ function AutoMLBasic({ | |||
| instanceStatus, | |||
| isInstance = false, | |||
| }: AutoMLBasicProps) { | |||
| const getResourceDescription = useComputingResource()[1]; | |||
| const getResourceDescription = useSystemResource(); | |||
| const basicDatas: BasicInfoData[] = useMemo(() => { | |||
| if (!info) { | |||
| return []; | |||
| @@ -1,30 +1,8 @@ | |||
| import { getAccessToken } from '@/access'; | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import KFModal from '@/components/KFModal'; | |||
| import { CategoryData, DataSource, ResourceType, resourceConfig } from '@/pages/Dataset/config'; | |||
| import { CategoryData, DataSource } from '@/pages/Dataset/config'; | |||
| import { addDataset } from '@/services/dataset/index.js'; | |||
| import { to } from '@/utils/promise'; | |||
| import { | |||
| getFileListFromEvent, | |||
| limitUploadFileType, | |||
| removeUploadedFile, | |||
| validateUploadFiles, | |||
| } from '@/utils/ui'; | |||
| import { | |||
| Button, | |||
| Form, | |||
| Input, | |||
| Radio, | |||
| Select, | |||
| Upload, | |||
| UploadFile, | |||
| message, | |||
| type ModalProps, | |||
| type UploadProps, | |||
| } from 'antd'; | |||
| import { omit } from 'lodash'; | |||
| import { useState } from 'react'; | |||
| import styles from './index.less'; | |||
| import { Form, Input, Radio, Select, message, type ModalProps } from 'antd'; | |||
| interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> { | |||
| typeList: CategoryData[]; | |||
| @@ -33,20 +11,6 @@ interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> { | |||
| } | |||
| function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalProps) { | |||
| const [uuid] = useState(Date.now()); | |||
| // 上传组件参数 | |||
| const uploadProps: UploadProps = { | |||
| action: resourceConfig[ResourceType.Dataset].uploadAction, | |||
| headers: { | |||
| Authorization: getAccessToken() || '', | |||
| }, | |||
| defaultFileList: [], | |||
| accept: '.zip,.tgz', | |||
| beforeUpload: limitUploadFileType('zip,tgz'), | |||
| onRemove: removeUploadedFile, | |||
| }; | |||
| // 上传请求 | |||
| const createDataset = async (params: any) => { | |||
| const [res] = await to(addDataset(params)); | |||
| @@ -58,22 +22,11 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||
| // 提交 | |||
| const onFinish = (formData: any) => { | |||
| const fileList: UploadFile[] = formData['fileList'] ?? []; | |||
| if (validateUploadFiles(fileList)) { | |||
| const params = { | |||
| ...omit(formData, ['fileList']), | |||
| dataset_source: DataSource.Create, | |||
| dataset_version_vos: fileList.map((item) => { | |||
| const data = item.response?.data?.[0] ?? {}; | |||
| return { | |||
| file_name: data.fileName, | |||
| file_size: data.fileSize, | |||
| url: data.url, | |||
| }; | |||
| }), | |||
| }; | |||
| createDataset(params); | |||
| } | |||
| const params = { | |||
| ...formData, | |||
| dataset_source: DataSource.Create, | |||
| }; | |||
| createDataset(params); | |||
| }; | |||
| return ( | |||
| @@ -108,32 +61,6 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||
| > | |||
| <Input placeholder="请输入数据名称" showCount allowClear maxLength={40} /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="数据集版本" | |||
| name="version" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入数据集版本', | |||
| }, | |||
| { | |||
| pattern: /^[a-zA-Z0-9._-]+$/, | |||
| message: '数据集版本只支持字母、数字、点(.)、下划线(_)、中横线(-)', | |||
| }, | |||
| { | |||
| validator: (_rule, value) => { | |||
| if (value === 'master') { | |||
| return Promise.reject(`数据集版本不能为 master`); | |||
| } else if (value === 'origin') { | |||
| return Promise.reject(`数据集版本不能为 origin`); | |||
| } | |||
| return Promise.resolve(); | |||
| }, | |||
| }, | |||
| ]} | |||
| > | |||
| <Input placeholder="请输入数据集版本" showCount allowClear maxLength={64} /> | |||
| </Form.Item> | |||
| <Form.Item label="数据集分类" name="data_type"> | |||
| <Select | |||
| allowClear | |||
| @@ -172,24 +99,6 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||
| allowClear | |||
| /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="版本描述" | |||
| name="version_desc" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入版本描述', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input.TextArea | |||
| placeholder="请输入版本描述" | |||
| autoSize={{ minRows: 2, maxRows: 6 }} | |||
| maxLength={200} | |||
| showCount | |||
| allowClear | |||
| /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="可见性" | |||
| name="is_public" | |||
| @@ -200,29 +109,6 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||
| <Radio value={true}>公开</Radio> | |||
| </Radio.Group> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="数据集文件" | |||
| name="fileList" | |||
| valuePropName="fileList" | |||
| getValueFromEvent={getFileListFromEvent} | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请上传数据集文件', | |||
| }, | |||
| ]} | |||
| > | |||
| <Upload {...uploadProps} data={{ uuid: uuid }}> | |||
| <Button | |||
| className={styles['upload-button']} | |||
| type="default" | |||
| icon={<KFIcon type="icon-shangchuan" />} | |||
| > | |||
| 上传文件 | |||
| </Button> | |||
| <div className={styles['upload-tip']}>只允许上传 .zip 和 .tgz 格式文件</div> | |||
| </Upload> | |||
| </Form.Item> | |||
| </Form> | |||
| </KFModal> | |||
| ); | |||
| @@ -1,25 +1,8 @@ | |||
| import { getAccessToken } from '@/access'; | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import KFModal from '@/components/KFModal'; | |||
| import { CategoryData, DataSource, ResourceType, resourceConfig } from '@/pages/Dataset/config'; | |||
| import { CategoryData, DataSource } from '@/pages/Dataset/config'; | |||
| import { addModel } from '@/services/dataset/index.js'; | |||
| import { to } from '@/utils/promise'; | |||
| import { getFileListFromEvent, removeUploadedFile, validateUploadFiles } from '@/utils/ui'; | |||
| import { | |||
| Button, | |||
| Form, | |||
| Input, | |||
| Radio, | |||
| Select, | |||
| Upload, | |||
| UploadFile, | |||
| message, | |||
| type ModalProps, | |||
| type UploadProps, | |||
| } from 'antd'; | |||
| import { omit } from 'lodash'; | |||
| import { useState } from 'react'; | |||
| import styles from '../AddDatasetModal/index.less'; | |||
| import { Form, Input, Radio, Select, message, type ModalProps } from 'antd'; | |||
| interface AddModelModalProps extends Omit<ModalProps, 'onOk'> { | |||
| typeList: CategoryData[]; | |||
| @@ -28,18 +11,6 @@ interface AddModelModalProps extends Omit<ModalProps, 'onOk'> { | |||
| } | |||
| function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) { | |||
| const [uuid] = useState(Date.now()); | |||
| // 上传组件参数 | |||
| const uploadProps: UploadProps = { | |||
| action: resourceConfig[ResourceType.Model].uploadAction, | |||
| headers: { | |||
| Authorization: getAccessToken() || '', | |||
| }, | |||
| defaultFileList: [], | |||
| onRemove: removeUploadedFile, | |||
| }; | |||
| // 上传请求 | |||
| const createModel = async (params: any) => { | |||
| const [res] = await to(addModel(params)); | |||
| @@ -51,22 +22,11 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||
| // 提交 | |||
| const onFinish = (formData: any) => { | |||
| const fileList: UploadFile[] = formData['fileList'] ?? []; | |||
| if (validateUploadFiles(fileList)) { | |||
| const params = { | |||
| ...omit(formData, ['fileList']), | |||
| model_source: DataSource.Create, | |||
| model_version_vos: fileList.map((item) => { | |||
| const data = item.response?.data?.[0] ?? {}; | |||
| return { | |||
| file_name: data.fileName, | |||
| file_size: data.fileSize, | |||
| url: data.url, | |||
| }; | |||
| }), | |||
| }; | |||
| createModel(params); | |||
| } | |||
| const params = { | |||
| ...formData, | |||
| model_source: DataSource.Create, | |||
| }; | |||
| createModel(params); | |||
| }; | |||
| return ( | |||
| @@ -99,32 +59,6 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||
| > | |||
| <Input placeholder="请输入模型名称" showCount allowClear maxLength={40} /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="模型版本" | |||
| name="version" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入模型版本', | |||
| }, | |||
| { | |||
| pattern: /^[a-zA-Z0-9._-]+$/, | |||
| message: '模型版本只支持字母、数字、点(.)、下划线(_)、中横线(-)', | |||
| }, | |||
| { | |||
| validator: (_rule, value) => { | |||
| if (value === 'master') { | |||
| return Promise.reject(`模型版本不能为 master`); | |||
| } else if (value === 'origin') { | |||
| return Promise.reject(`模型版本不能为 origin`); | |||
| } | |||
| return Promise.resolve(); | |||
| }, | |||
| }, | |||
| ]} | |||
| > | |||
| <Input placeholder="请输入模型版本" showCount allowClear maxLength={64} /> | |||
| </Form.Item> | |||
| <Form.Item label="模型框架" name="model_type"> | |||
| <Select | |||
| allowClear | |||
| @@ -163,24 +97,6 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||
| allowClear | |||
| /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="版本描述" | |||
| name="version_desc" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入版本描述', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input.TextArea | |||
| placeholder="请输入版本描述" | |||
| autoSize={{ minRows: 2, maxRows: 6 }} | |||
| maxLength={200} | |||
| showCount | |||
| allowClear | |||
| /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="可见性" | |||
| name="is_public" | |||
| @@ -191,28 +107,6 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||
| <Radio value={true}>公开</Radio> | |||
| </Radio.Group> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="模型文件" | |||
| name="fileList" | |||
| valuePropName="fileList" | |||
| getValueFromEvent={getFileListFromEvent} | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请上传模型文件', | |||
| }, | |||
| ]} | |||
| > | |||
| <Upload {...uploadProps} data={{ uuid: uuid }}> | |||
| <Button | |||
| className={styles['upload-button']} | |||
| type="default" | |||
| icon={<KFIcon type="icon-shangchuan" />} | |||
| > | |||
| 上传文件 | |||
| </Button> | |||
| </Upload> | |||
| </Form.Item> | |||
| </Form> | |||
| </KFModal> | |||
| ); | |||
| @@ -15,7 +15,7 @@ import { | |||
| type UploadProps, | |||
| } from 'antd'; | |||
| import { omit } from 'lodash'; | |||
| import { useState } from 'react'; | |||
| import { useEffect, useState } from 'react'; | |||
| import styles from '../AddDatasetModal/index.less'; | |||
| interface AddVersionModalProps extends Omit<ModalProps, 'onOk'> { | |||
| @@ -40,6 +40,23 @@ function AddVersionModal({ | |||
| }: AddVersionModalProps) { | |||
| const [uuid] = useState(Date.now()); | |||
| const config = resourceConfig[resourceType]; | |||
| const [form] = Form.useForm(); | |||
| useEffect(() => { | |||
| const getNextVersion = async () => { | |||
| const request = config.getNextVersion; | |||
| const params = { | |||
| identifier, | |||
| owner, | |||
| }; | |||
| const [res] = await to(request(params)); | |||
| if (res && res.data) { | |||
| const nextVersion = res.data; | |||
| form.setFieldValue('version', nextVersion); | |||
| } | |||
| }; | |||
| getNextVersion(); | |||
| }, [identifier, owner, config, form]); | |||
| // 上传组件参数 | |||
| const uploadProps: UploadProps = { | |||
| @@ -109,6 +126,7 @@ function AddVersionModal({ | |||
| }} | |||
| onFinish={onFinish} | |||
| autoComplete="off" | |||
| form={form} | |||
| > | |||
| <Form.Item | |||
| label={`${name}名称`} | |||
| @@ -146,7 +164,7 @@ function AddVersionModal({ | |||
| }, | |||
| ]} | |||
| > | |||
| <Input placeholder={`请输入${name}版本`} maxLength={64} showCount allowClear /> | |||
| <Input placeholder={`请输入${name}版本`} maxLength={64} showCount allowClear disabled /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="版本描述" | |||
| @@ -28,8 +28,15 @@ | |||
| border-radius: 4px; | |||
| } | |||
| &__desc { | |||
| margin-bottom: 0 !important; | |||
| color: @text-color; | |||
| font-size: @font-size; | |||
| } | |||
| &__praise { | |||
| display: flex; | |||
| flex: none; | |||
| align-items: center; | |||
| justify-content: center; | |||
| width: 70px; | |||
| @@ -4,6 +4,7 @@ | |||
| * @Description: 数据集、模型详情 | |||
| */ | |||
| import KFEmpty, { EmptyType } from '@/components/KFEmpty'; | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { | |||
| ResourceData, | |||
| @@ -19,7 +20,7 @@ import { openAntdModal } from '@/utils/modal'; | |||
| import { to } from '@/utils/promise'; | |||
| import { modalConfirm } from '@/utils/ui'; | |||
| import { useParams, useSearchParams } from '@umijs/max'; | |||
| import { App, Button, Flex, Select, Tabs } from 'antd'; | |||
| import { App, Button, Flex, Select, Tabs, Typography } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import { useCallback, useEffect, useState } from 'react'; | |||
| import AddVersionModal from '../AddVersionModal'; | |||
| @@ -62,21 +63,24 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { | |||
| const { message } = App.useApp(); | |||
| // 获取详情 | |||
| const getResourceDetail = useCallback(async () => { | |||
| const params = { | |||
| id: resourceId, | |||
| owner, | |||
| name, | |||
| identifier, | |||
| version, | |||
| is_public, | |||
| }; | |||
| const request = config.getInfo; | |||
| const [res] = await to(request(params)); | |||
| if (res && res.data) { | |||
| setInfo(res.data); | |||
| } | |||
| }, [config, resourceId, owner, name, identifier, version, is_public]); | |||
| const getResourceDetail = useCallback( | |||
| async (version: string | undefined) => { | |||
| const params = { | |||
| id: resourceId, | |||
| owner, | |||
| name, | |||
| identifier, | |||
| version, | |||
| is_public, | |||
| }; | |||
| const request = config.getInfo; | |||
| const [res] = await to(request(params)); | |||
| if (res && res.data) { | |||
| setInfo(res.data); | |||
| } | |||
| }, | |||
| [config, resourceId, owner, name, identifier, is_public], | |||
| ); | |||
| // 获取版本列表 | |||
| const getVersionList = useCallback( | |||
| @@ -101,14 +105,15 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { | |||
| } | |||
| } else { | |||
| setVersion(undefined); | |||
| getResourceDetail(undefined); | |||
| } | |||
| }, | |||
| [config, owner, identifier, versionParam], | |||
| [config, owner, identifier, versionParam, getResourceDetail], | |||
| ); | |||
| useEffect(() => { | |||
| if (version) { | |||
| getResourceDetail(); | |||
| getResourceDetail(version); | |||
| } | |||
| }, [version, getResourceDetail]); | |||
| @@ -117,7 +122,7 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { | |||
| }, [getVersionList]); | |||
| // 新建版本 | |||
| const showModal = () => { | |||
| const showAddVersionModal = () => { | |||
| const { close } = openAntdModal(AddVersionModal, { | |||
| resourceType: resourceType, | |||
| resourceId: resourceId, | |||
| @@ -291,52 +296,70 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { | |||
| <span>{info.praises_count}</span> | |||
| </div> | |||
| </Flex> | |||
| <Flex align="center"> | |||
| <span style={{ marginRight: '10px' }}>版本号:</span> | |||
| <Select | |||
| placeholder="请选择版本号" | |||
| style={{ width: '160px', marginRight: '20px' }} | |||
| value={version} | |||
| onChange={handleVersionChange} | |||
| fieldNames={{ label: 'name', value: 'name' }} | |||
| options={versionList} | |||
| /> | |||
| <Button type="default" onClick={showModal} icon={<KFIcon type="icon-xinjian2" />}> | |||
| 创建新版本 | |||
| </Button> | |||
| <Button | |||
| type="default" | |||
| style={{ marginLeft: '20px' }} | |||
| icon={<KFIcon type="icon-bianji" />} | |||
| onClick={showEditVersionModal} | |||
| > | |||
| 版本编辑 | |||
| </Button> | |||
| <Button | |||
| type="default" | |||
| style={{ marginLeft: '20px' }} | |||
| icon={<KFIcon type="icon-banbenduibi" />} | |||
| onClick={showVersionSelector} | |||
| > | |||
| 版本对比 | |||
| </Button> | |||
| <Button | |||
| type="default" | |||
| style={{ marginLeft: '20px' }} | |||
| onClick={handleDelete} | |||
| icon={<KFIcon type="icon-shanchu" />} | |||
| disabled={!version} | |||
| danger | |||
| {version ? ( | |||
| <Flex align="center"> | |||
| <span style={{ marginRight: '10px' }}>版本号:</span> | |||
| <Select | |||
| placeholder="请选择版本号" | |||
| style={{ width: '160px', marginRight: '20px' }} | |||
| value={version} | |||
| onChange={handleVersionChange} | |||
| fieldNames={{ label: 'name', value: 'name' }} | |||
| options={versionList} | |||
| /> | |||
| <Button | |||
| type="default" | |||
| onClick={showAddVersionModal} | |||
| icon={<KFIcon type="icon-xinjian2" />} | |||
| > | |||
| 创建新版本 | |||
| </Button> | |||
| <Button | |||
| type="default" | |||
| style={{ marginLeft: '20px' }} | |||
| icon={<KFIcon type="icon-banbenduibi" />} | |||
| onClick={showVersionSelector} | |||
| > | |||
| 版本对比 | |||
| </Button> | |||
| <Button | |||
| type="default" | |||
| style={{ marginLeft: '20px' }} | |||
| onClick={handleDelete} | |||
| icon={<KFIcon type="icon-shanchu" />} | |||
| danger | |||
| > | |||
| 删除版本 | |||
| </Button> | |||
| </Flex> | |||
| ) : ( | |||
| <Typography.Paragraph | |||
| className={styles['resource-info__top__desc']} | |||
| ellipsis={{ tooltip: info.description }} | |||
| > | |||
| 删除版本 | |||
| </Button> | |||
| </Flex> | |||
| {info.description ?? '暂无描述'} | |||
| </Typography.Paragraph> | |||
| )} | |||
| </div> | |||
| <div className={styles['resource-info__bottom']}> | |||
| <Tabs activeKey={activeTab} items={items} onChange={(key) => setActiveTab(key)}></Tabs> | |||
| <div className={styles['resource-info__bottom__legend']}> | |||
| {activeTab === ResourceInfoTabKeys.Evolution && <GraphLegend />} | |||
| </div> | |||
| {version ? ( | |||
| <> | |||
| <Tabs activeKey={activeTab} items={items} onChange={(key) => setActiveTab(key)}></Tabs> | |||
| <div className={styles['resource-info__bottom__legend']}> | |||
| {activeTab === ResourceInfoTabKeys.Evolution && <GraphLegend />} | |||
| </div> | |||
| </> | |||
| ) : ( | |||
| <KFEmpty | |||
| style={{ height: '100%' }} | |||
| type={EmptyType.NoData} | |||
| title="暂无版本" | |||
| content={`请创建${config.name}版本`} | |||
| hasFooter={true} | |||
| buttonTitle="创建版本" | |||
| onButtonClick={showAddVersionModal} | |||
| /> | |||
| )} | |||
| </div> | |||
| </div> | |||
| ); | |||
| @@ -13,9 +13,11 @@ import { | |||
| editModelVersion, | |||
| getDatasetInfo, | |||
| getDatasetList, | |||
| getDatasetNextVersionReq, | |||
| getDatasetVersionList, | |||
| getModelInfo, | |||
| getModelList, | |||
| getModelNextVersionReq, | |||
| getModelVersionList, | |||
| } from '@/services/dataset/index.js'; | |||
| import { limitUploadFileType } from '@/utils/ui'; | |||
| @@ -42,6 +44,7 @@ type ResourceTypeInfo = { | |||
| deleteVersion: (params: any) => Promise<any>; // 删除版本 | |||
| getInfo: (params: any) => Promise<any>; // 获取详情 | |||
| compareVersion: (params: any) => Promise<any>; // 版本对比 | |||
| getNextVersion: (params: any) => Promise<any>; // 获取下一个版本 | |||
| name: string; // 名称 | |||
| typeParamKey: 'data_type' | 'model_type'; // 类型参数名称,获取资源列表接口使用 | |||
| tagParamKey: 'data_tag' | 'model_tag'; // 标签参数名称,获取资源列表接口使用 | |||
| @@ -72,6 +75,7 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | |||
| deleteVersion: deleteDatasetVersion, | |||
| getInfo: getDatasetInfo, | |||
| compareVersion: compareDatasetVersion, | |||
| getNextVersion: getDatasetNextVersionReq, | |||
| name: '数据集', | |||
| typeParamKey: 'data_type', | |||
| tagParamKey: 'data_tag', | |||
| @@ -111,6 +115,7 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | |||
| deleteVersion: deleteModelVersion, | |||
| getInfo: getModelInfo, | |||
| compareVersion: compareModelVersion, | |||
| getNextVersion: getModelNextVersionReq, | |||
| name: '模型', | |||
| typeParamKey: 'model_type', | |||
| tagParamKey: 'model_tag', | |||
| @@ -8,7 +8,7 @@ import { CodeConfigData } from '@/components/CodeSelectorModal'; | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { DevEditorStatus } from '@/enums'; | |||
| import { useCacheState } from '@/hooks/useCacheState'; | |||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||
| import { useSystemResource } from '@/hooks/useComputingResource'; | |||
| import { DatasetData, ModelData } from '@/pages/Dataset/config'; | |||
| import { | |||
| deleteEditorReq, | |||
| @@ -66,7 +66,7 @@ function EditorList() { | |||
| pageSize: 10, | |||
| }, | |||
| ); | |||
| const getResourceDescription = useComputingResource()[1]; | |||
| const getResourceDescription = useSystemResource(); | |||
| // 获取编辑器列表 | |||
| const getEditorList = useCallback(async () => { | |||
| @@ -3,12 +3,10 @@ import KFModal from '@/components/KFModal'; | |||
| import { | |||
| DataSource, | |||
| ResourceType, | |||
| ResourceVersionData, | |||
| resourceConfig, | |||
| type ResourceData, | |||
| } from '@/pages/Dataset/config'; | |||
| import { to } from '@/utils/promise'; | |||
| import { InfoCircleOutlined } from '@ant-design/icons'; | |||
| import { Form, Input, ModalProps, Select } from 'antd'; | |||
| import { pick } from 'lodash'; | |||
| import { useEffect, useState } from 'react'; | |||
| @@ -44,7 +42,6 @@ function ExportModelModal({ | |||
| }: ExportModelModalProps) { | |||
| const [form] = Form.useForm(); | |||
| const [resources, setResources] = useState<ResourceData[]>([]); | |||
| const [versions, setVersions] = useState<ResourceVersionData[]>([]); | |||
| const config = resourceConfig[resourceType]; | |||
| const layout = { | |||
| @@ -77,35 +74,24 @@ function ExportModelModal({ | |||
| return undefined; | |||
| }; | |||
| // 版本 tooltip | |||
| const getTooltip = () => { | |||
| const id = form.getFieldValue('id'); | |||
| const resource = getSelectedResource(id); | |||
| const name = resource?.name ?? ''; | |||
| const versionNames = versions.map((item: ResourceVersionData) => item.name).join('、'); | |||
| const tooltip = | |||
| versions.length > 0 ? `${name}有以下版本:\n${versionNames}\n注意不能重复` : undefined; | |||
| return tooltip; | |||
| }; | |||
| // 处理数据集、模型选择变化 | |||
| const handleResourceChange = (id: number | undefined) => { | |||
| if (id) { | |||
| getRecourceVersions(id); | |||
| getRecourceNextVersion(id); | |||
| } else { | |||
| setVersions([]); | |||
| form.setFieldValue('version', ''); | |||
| } | |||
| }; | |||
| // 获取数据集、模型版本列表 | |||
| const getRecourceVersions = async (id: number) => { | |||
| // 获取数据集、模型下一个版本 | |||
| const getRecourceNextVersion = async (id: number) => { | |||
| const resource = getSelectedResource(id); | |||
| if (!resource) { | |||
| return; | |||
| } | |||
| const [res] = await to(config.getVersions(pick(resource, ['identifier', 'owner']))); | |||
| const [res] = await to(config.getNextVersion(pick(resource, ['identifier', 'owner']))); | |||
| if (res && res.data) { | |||
| setVersions(res.data); | |||
| form.setFieldValue('version', res.data); | |||
| } | |||
| }; | |||
| @@ -184,15 +170,6 @@ function ExportModelModal({ | |||
| <Form.Item | |||
| label={`${config.name}版本`} | |||
| name="version" | |||
| tooltip={ | |||
| getTooltip() | |||
| ? { | |||
| overlayClassName: styles['export-model-modal__tooltip'], | |||
| title: getTooltip(), | |||
| icon: <InfoCircleOutlined />, | |||
| } | |||
| : undefined | |||
| } | |||
| rules={[ | |||
| { required: true, message: `请输入${config.name}版本` }, | |||
| { | |||
| @@ -205,8 +182,6 @@ function ExportModelModal({ | |||
| return Promise.reject(`${config.name}版本不能为 master`); | |||
| } else if (value === 'origin') { | |||
| return Promise.reject(`${config.name}版本不能为 origin`); | |||
| } else if (value && versions.map((item) => item.name).includes(value)) { | |||
| return Promise.reject(`${config.name}版本已存在`); | |||
| } else { | |||
| return Promise.resolve(); | |||
| } | |||
| @@ -214,7 +189,13 @@ function ExportModelModal({ | |||
| }, | |||
| ]} | |||
| > | |||
| <Input placeholder={`请输入${config.name}版本`} maxLength={64} showCount allowClear /> | |||
| <Input | |||
| placeholder={`请输入${config.name}版本`} | |||
| maxLength={64} | |||
| showCount | |||
| allowClear | |||
| disabled | |||
| /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="版本描述" | |||
| @@ -1,6 +1,6 @@ | |||
| import ConfigInfo, { type BasicInfoData } from '@/components/ConfigInfo'; | |||
| import { ExperimentStatus, hyperParameterOptimizedMode } from '@/enums'; | |||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||
| import { useSystemResource } from '@/hooks/useComputingResource'; | |||
| import ExperimentRunBasic from '@/pages/AutoML/components/ExperimentRunBasic'; | |||
| import { | |||
| schedulerAlgorithms, | |||
| @@ -41,7 +41,7 @@ function HyperParameterBasic({ | |||
| instanceStatus, | |||
| isInstance = false, | |||
| }: HyperParameterBasicProps) { | |||
| const getResourceDescription = useComputingResource()[1]; | |||
| const getResourceDescription = useSystemResource(); | |||
| const basicDatas: BasicInfoData[] = useMemo(() => { | |||
| if (!info) { | |||
| @@ -9,7 +9,7 @@ import PageTitle from '@/components/PageTitle'; | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { ServiceRunStatus, serviceStatusOptions } from '@/enums'; | |||
| import { useCacheState } from '@/hooks/useCacheState'; | |||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||
| import { useSystemResource } from '@/hooks/useComputingResource'; | |||
| import { ModelData } from '@/pages/Dataset/config'; | |||
| import { | |||
| deleteServiceVersionReq, | |||
| @@ -89,7 +89,7 @@ function ServiceInfo() { | |||
| format: formatDate, | |||
| }, | |||
| ]; | |||
| const getResourceDescription = useComputingResource()[1]; | |||
| const getResourceDescription = useSystemResource(); | |||
| // 获取服务详情 | |||
| const getServiceInfo = useCallback(async () => { | |||
| @@ -1,6 +1,6 @@ | |||
| import BasicInfo, { type BasicInfoData } from '@/components/BasicInfo'; | |||
| import { ServiceRunStatus } from '@/enums'; | |||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||
| import { useSystemResource } from '@/hooks/useComputingResource'; | |||
| import { ServiceVersionData } from '@/pages/ModelDeployment/types'; | |||
| import { formatDate } from '@/utils/date'; | |||
| import { formatMirror, formatModel } from '@/utils/format'; | |||
| @@ -36,7 +36,7 @@ const formatEnvText = (env?: Record<string, string>) => { | |||
| }; | |||
| function VersionBasicInfo({ info }: BasicInfoProps) { | |||
| const getResourceDescription = useComputingResource()[1]; | |||
| const getResourceDescription = useSystemResource(); | |||
| const datas: BasicInfoData[] = [ | |||
| { | |||
| @@ -1,6 +1,6 @@ | |||
| import KFModal from '@/components/KFModal'; | |||
| import { ServiceRunStatus } from '@/enums'; | |||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||
| import { useSystemResource } from '@/hooks/useComputingResource'; | |||
| import { type ServiceVersionData } from '@/pages/ModelDeployment/types'; | |||
| import { getServiceVersionCompareReq } from '@/services/modelDeployment'; | |||
| import { isEmpty } from '@/utils'; | |||
| @@ -42,7 +42,7 @@ const formatEnvText = (env: Record<string, string>) => { | |||
| function VersionCompareModal({ version1, version2, ...rest }: VersionCompareModalProps) { | |||
| const [compareData, setCompareData] = useState<CompareData | undefined>(undefined); | |||
| const getResourceDescription = useComputingResource()[1]; | |||
| const getResourceDescription = useSystemResource(); | |||
| const fields: FiledType[] = useMemo( | |||
| () => [ | |||
| @@ -70,15 +70,6 @@ export function createMenuItems( | |||
| } | |||
| } | |||
| export function getInParameterComponent( | |||
| parameter: PipelineNodeModelParameter, | |||
| ): React.ReactNode | null { | |||
| if (parameter.value) { | |||
| } | |||
| return null; | |||
| } | |||
| // 判断是否允许输入 | |||
| export function canInput(parameter: PipelineNodeModelParameter) { | |||
| const { type, item_type } = parameter; | |||
| @@ -87,6 +78,9 @@ export function canInput(parameter: PipelineNodeModelParameter) { | |||
| (item_type === 'dataset' || | |||
| item_type === 'model' || | |||
| item_type === 'image' || | |||
| item_type === 'code') | |||
| item_type === 'code' || | |||
| item_type === 'remote-dataset' || | |||
| item_type === 'remote-model' || | |||
| item_type === 'remote-code') | |||
| ); | |||
| } | |||
| @@ -1,7 +1,10 @@ | |||
| import CodeSelectorModal, { CodeConfigData } from '@/components/CodeSelectorModal'; | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import ParameterInput, { requiredValidator } from '@/components/ParameterInput'; | |||
| import ParameterSelect, { type ParameterSelectDataType } from '@/components/ParameterSelect'; | |||
| import ParameterSelect, { | |||
| type ParameterSelectDataType, | |||
| type ParameterSelectObject, | |||
| } from '@/components/ParameterSelect'; | |||
| import ResourceSelectorModal, { | |||
| ResourceSelectorType, | |||
| selectorTypeConfig, | |||
| @@ -9,6 +12,7 @@ import ResourceSelectorModal, { | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { CommonTabKeys, ComponentType } from '@/enums'; | |||
| import { canInput, createMenuItems } from '@/pages/Pipeline/Info/utils'; | |||
| import state from '@/state/jcdResource'; | |||
| import { | |||
| PipelineGlobalParam, | |||
| PipelineNodeModel, | |||
| @@ -20,6 +24,7 @@ import { to } from '@/utils/promise'; | |||
| import { removeFormListItem } from '@/utils/ui'; | |||
| import { MinusCircleOutlined, PlusCircleOutlined, PlusOutlined } from '@ant-design/icons'; | |||
| import { INode } from '@antv/g6'; | |||
| import { useSnapshot } from '@umijs/max'; | |||
| import { Button, Drawer, Flex, Form, Input, MenuProps } from 'antd'; | |||
| import { RuleObject } from 'antd/es/form'; | |||
| import { NamePath } from 'antd/es/form/interface'; | |||
| @@ -38,6 +43,17 @@ type PipelineNodeParameterProps = { | |||
| onFormChange: (data: PipelineNodeModelSerialize) => void; | |||
| }; | |||
| // 自定义的下拉组件类型 | |||
| const parameterSelectList = [ | |||
| 'dataset', | |||
| 'model', | |||
| 'service', | |||
| 'resource', | |||
| 'remote-resource-type', | |||
| 'remote-image', | |||
| 'remote-resource', | |||
| ]; | |||
| const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParameterProps, ref) => { | |||
| const [form] = Form.useForm(); | |||
| const [stagingItem, setStagingItem] = useState<PipelineNodeModelSerialize>( | |||
| @@ -45,6 +61,8 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||
| ); | |||
| const [open, setOpen] = useState(false); | |||
| const [menuItems, setMenuItems] = useState<MenuProps['items']>([]); | |||
| const snap = useSnapshot(state); | |||
| const { setCurrentType } = snap; | |||
| const afterOpenChange = async () => { | |||
| if (!open) { | |||
| @@ -119,6 +137,12 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||
| // 参数下拉菜单 | |||
| setMenuItems(createMenuItems(params, parentNodes)); | |||
| // 云际组件,设置 store 当前资源类型 | |||
| if (model.id.startsWith('remote-task')) { | |||
| const resourceType = model.in_parameters['--resource_type'].value; | |||
| setCurrentType(resourceType); | |||
| } | |||
| }, | |||
| close: () => { | |||
| onClose(); | |||
| @@ -136,7 +160,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||
| } | |||
| }, | |||
| }), | |||
| [form, open], | |||
| [form, open, setCurrentType], | |||
| ); | |||
| // ref 类型选择 | |||
| @@ -144,7 +168,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||
| formItemName: NamePath, | |||
| item: PipelineNodeModelParameter | Pick<PipelineNodeModelParameter, 'item_type'>, | |||
| ) => { | |||
| if (item.item_type === 'code') { | |||
| if (item.item_type === 'code' || item.item_type === 'remote-code') { | |||
| selectCodeConfig(formItemName, item); | |||
| } else { | |||
| selectResource(formItemName, item); | |||
| @@ -183,9 +207,11 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||
| let type: ResourceSelectorType; | |||
| switch (item.item_type) { | |||
| case 'dataset': | |||
| case 'remote-dataset': | |||
| type = ResourceSelectorType.Dataset; | |||
| break; | |||
| case 'model': | |||
| case 'remote-model': | |||
| type = ResourceSelectorType.Model; | |||
| break; | |||
| default: | |||
| @@ -249,14 +275,14 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||
| // 获取选择数据集、模型后面按钮 icon | |||
| const getSelectBtnIcon = (item: { item_type: string }) => { | |||
| const type = item.item_type; | |||
| if (type === 'code') { | |||
| if (type === 'code' || type === 'remote-code') { | |||
| return <KFIcon type="icon-xuanzedaimapeizhi" />; | |||
| } | |||
| let selectorType: ResourceSelectorType; | |||
| if (type === 'dataset') { | |||
| if (type === 'dataset' || type === 'remote-dataset') { | |||
| selectorType = ResourceSelectorType.Dataset; | |||
| } else if (type === 'model') { | |||
| } else if (type === 'model' || type === 'remote-model') { | |||
| selectorType = ResourceSelectorType.Model; | |||
| } else { | |||
| selectorType = ResourceSelectorType.Mirror; | |||
| @@ -331,6 +357,21 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||
| return rules; | |||
| }; | |||
| // 云际组件,选择类型后,重置镜像和资源,获取镜像、资源列表 | |||
| const handleParameterSelect = ( | |||
| value: ParameterSelectObject, | |||
| itemType: string, | |||
| parentName: string, | |||
| ) => { | |||
| if (itemType === 'remote-resource-type') { | |||
| snap.setCurrentType(value.value); | |||
| const remoteImage = form.getFieldValue([parentName, '--image']); | |||
| form.setFieldValue([parentName, '--image'], { ...remoteImage, value: undefined }); | |||
| const remoteResource = form.getFieldValue([parentName, '--resource']); | |||
| form.setFieldValue([parentName, '--resource'], { ...remoteResource, value: undefined }); | |||
| } | |||
| }; | |||
| // 表单组件 | |||
| const getFormComponent = ( | |||
| item: { key: string; value: PipelineNodeModelParameter }, | |||
| @@ -361,12 +402,18 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||
| </Flex> | |||
| )} | |||
| {item.value.type === ComponentType.Select && | |||
| (['dataset', 'model', 'service', 'resource'].includes(item.value.item_type) ? ( | |||
| (parameterSelectList.includes(item.value.item_type) ? ( | |||
| <Form.Item name={[parentName, item.key]} rules={getFormRules(item)} noStyle> | |||
| <ParameterSelect | |||
| isPipeline | |||
| dataType={item.value.item_type as ParameterSelectDataType} | |||
| placeholder={item.value.placeholder} | |||
| onChange={(value) => | |||
| handleParameterSelect( | |||
| value as ParameterSelectObject, | |||
| item.value.item_type, | |||
| parentName, | |||
| ) | |||
| } | |||
| /> | |||
| </Form.Item> | |||
| ) : null)} | |||
| @@ -90,6 +90,15 @@ export function compareDatasetVersion(data) { | |||
| }); | |||
| } | |||
| // 获取数据集下个版本号 | |||
| export function getDatasetNextVersionReq(data) { | |||
| return request(`/api/mmp/newdataset/queryNextVersion`, { | |||
| method: 'POST', | |||
| data, | |||
| }); | |||
| } | |||
| // ----------------------------模型--------------------------------- | |||
| // 分页查询模型列表 | |||
| @@ -157,6 +166,14 @@ export function deleteModelVersion(params) { | |||
| }); | |||
| } | |||
| // 获取模型下个版本号 | |||
| export function getModelNextVersionReq(data) { | |||
| return request(`/api/mmp/newmodel/queryNextVersion`, { | |||
| method: 'POST', | |||
| data, | |||
| }); | |||
| } | |||
| // 获取模型依赖 | |||
| export function getModelAtlasReq(params) { | |||
| return request(`/api/mmp/newmodel/getModelDependencyTree`, { | |||
| @@ -218,4 +235,5 @@ export function unpraiseResourceReq(id) { | |||
| return request(`/api/mmp/newmodel/unpraise/${id}`, { | |||
| method: 'DELETE', | |||
| }); | |||
| } | |||
| } | |||
| @@ -0,0 +1,87 @@ | |||
| // 外部系统 | |||
| import { request } from '@umijs/max'; | |||
| // 云际系统登录 | |||
| export function jccLoginReq() { | |||
| return request(`http://119.45.255.234:30180/jcc-admin/admin/login`, { | |||
| method: 'POST', | |||
| data: { | |||
| username: 'iflytek', | |||
| password: 'iflytek@123', | |||
| }, | |||
| headers: { | |||
| isToken: false, | |||
| }, | |||
| skipLoading: true, | |||
| skipValidating: true, | |||
| }); | |||
| } | |||
| // 云际系统获取资源类型 | |||
| export function jccGetResourceTypesReq(token: string, userId: number) { | |||
| return request(`http://119.45.255.234:30180/jsm/jobSet/resourceRange`, { | |||
| method: 'POST', | |||
| data: { | |||
| userID: userId, | |||
| }, | |||
| headers: { | |||
| authorization: `${token}`, | |||
| isToken: false, | |||
| }, | |||
| skipLoading: true, | |||
| skipValidating: true, | |||
| }); | |||
| } | |||
| // 云际系统获取资源镜像 | |||
| export function jccGetImagesReq(token: string, cardTypes: string[]) { | |||
| return request(`http://119.45.255.234:30180/jsm/jobSet/queryImages`, { | |||
| method: 'POST', | |||
| data: { | |||
| cardTypes: cardTypes, | |||
| }, | |||
| headers: { | |||
| authorization: `${token}`, | |||
| isToken: false, | |||
| }, | |||
| skipLoading: true, | |||
| skipValidating: true, | |||
| }); | |||
| } | |||
| // 云际系统获取资源列表 | |||
| export function jccGetResourcesReq(token: string, cardType: string) { | |||
| return request(`http://119.45.255.234:30180/jsm/jobSet/queryResource`, { | |||
| method: 'POST', | |||
| data: { | |||
| queryResource: { | |||
| cpu: { | |||
| min: 0, | |||
| max: 0, | |||
| }, | |||
| memory: { | |||
| min: 0, | |||
| max: 0, | |||
| }, | |||
| gpu: { | |||
| min: 0, | |||
| max: 0, | |||
| }, | |||
| storage: { | |||
| min: 0, | |||
| max: 0, | |||
| }, | |||
| type: cardType, | |||
| }, | |||
| resourceType: 'Train', | |||
| clusterIDs: ['1865927992266461184', ''], | |||
| }, | |||
| headers: { | |||
| authorization: `${token}`, | |||
| isToken: false, | |||
| }, | |||
| skipLoading: true, | |||
| skipValidating: true, | |||
| }); | |||
| } | |||
| @@ -0,0 +1,140 @@ | |||
| import { | |||
| jccGetImagesReq, | |||
| jccGetResourcesReq, | |||
| jccGetResourceTypesReq, | |||
| jccLoginReq, | |||
| } from '@/services/external'; | |||
| import { to } from '@/utils/promise'; | |||
| import { proxy } from '@umijs/max'; | |||
| export type JCCResourceRange = { | |||
| type: string; | |||
| }; | |||
| export type JCCResourceType = { | |||
| label: string; | |||
| value: string; | |||
| }; | |||
| export interface JCCResourceImage { | |||
| imageID: number; | |||
| name: string; | |||
| createTime: Date; | |||
| clusterImages: JCCClusterImage[]; | |||
| } | |||
| export interface JCCClusterImage { | |||
| imageID: number; | |||
| clusterID: string; | |||
| originImageType: string; | |||
| originImageID: string; | |||
| originImageName: string; | |||
| cards: JCCCard[]; | |||
| } | |||
| export interface JCCCard { | |||
| originImageID: string; | |||
| card: string; | |||
| } | |||
| export interface JCCResourceStandard { | |||
| id: number; | |||
| sourceKey: string; | |||
| type: string; | |||
| name: string; | |||
| totalCount: number; | |||
| availableCount: number; | |||
| changeType: number; | |||
| status: number; | |||
| region: string; | |||
| clusterId: string; | |||
| costPerUnit: number; | |||
| costType: string; | |||
| tag: string; | |||
| userId: number; | |||
| createTime: Date; | |||
| updateTime: Date; | |||
| baseResourceSpecs: JCCBaseResourceSpec[]; | |||
| } | |||
| export interface JCCBaseResourceSpec { | |||
| id: number; | |||
| resourceSpecId: number; | |||
| type: string; | |||
| name: string; | |||
| totalValue: number; | |||
| totalUnit: string; | |||
| availableValue: number; | |||
| availableUnit: string; | |||
| userId: number; | |||
| createTime: Date; | |||
| updateTime: Date; | |||
| } | |||
| type JCCResourceTypeStore = { | |||
| token: string; | |||
| types: JCCResourceType[]; | |||
| images: JCCResourceImage[]; | |||
| resources: JCCResourceStandard[]; | |||
| currentType: string | undefined | null; | |||
| jccLogin: () => void; | |||
| getResourceTypes: () => void; | |||
| getImages: (cardTypes: string[]) => void; | |||
| getResources: (cardType: string) => void; | |||
| setCurrentType: (cardType: string) => void; | |||
| }; | |||
| const state = proxy<JCCResourceTypeStore>({ | |||
| token: '', | |||
| types: [], | |||
| images: [], | |||
| resources: [], | |||
| currentType: undefined, | |||
| jccLogin: async () => { | |||
| const [res] = await to(jccLoginReq()); | |||
| if (res && res.code === 200 && res.data) { | |||
| const { tokenHead, token } = res.data; | |||
| state.token = tokenHead + token; | |||
| } | |||
| }, | |||
| getResourceTypes: async () => { | |||
| const [loginRes] = await to(jccLoginReq()); | |||
| if (loginRes && loginRes.code === 200 && loginRes.data) { | |||
| const { tokenHead, token, jsmUserInfo } = loginRes.data; | |||
| state.token = tokenHead + token; | |||
| const userID = jsmUserInfo?.data?.userID; | |||
| const [res] = await to(jccGetResourceTypesReq(tokenHead + token, userID)); | |||
| if (res && res.code === 'OK' && res.data) { | |||
| state.types = res.data.resourceRanges?.map((v: JCCResourceRange) => ({ | |||
| label: v.type, | |||
| value: v.type, | |||
| })); | |||
| } | |||
| } | |||
| }, | |||
| getImages: async (cardTypes: string[]) => { | |||
| const [res] = await to(jccGetImagesReq(state.token, cardTypes)); | |||
| if (res && res.code === 'OK' && res.data) { | |||
| state.images = res.data.images; | |||
| } | |||
| }, | |||
| getResources: async (cardType: string) => { | |||
| const [res] = await to(jccGetResourcesReq(state.token, cardType)); | |||
| if (res && res.code === 'OK' && res.data) { | |||
| state.resources = res.data.resource; | |||
| } | |||
| }, | |||
| setCurrentType: (cardType: string | undefined | null) => { | |||
| if (state.currentType !== cardType) { | |||
| state.currentType = cardType; | |||
| state.images = []; | |||
| state.resources = []; | |||
| if (cardType) { | |||
| state.getImages([cardType]); | |||
| state.getResources(cardType); | |||
| } | |||
| } | |||
| }, | |||
| }); | |||
| export default state; | |||
| @@ -0,0 +1,53 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-10-10 08:51:41 | |||
| * @Description: 资源规格 | |||
| */ | |||
| import { getComputingResourceReq } from '@/services/pipeline'; | |||
| import { ComputingResource } from '@/types'; | |||
| import { to } from '@/utils/promise'; | |||
| import { proxy } from '@umijs/max'; | |||
| import { type SelectProps } from 'antd'; | |||
| /** 过滤资源规格 */ | |||
| export const filterResourceStandard: SelectProps<string, ComputingResource>['filterOption'] = ( | |||
| input: string, | |||
| option?: ComputingResource, | |||
| ) => { | |||
| return ( | |||
| option?.computing_resource?.toLocaleLowerCase()?.includes(input.toLocaleLowerCase()) ?? false | |||
| ); | |||
| }; | |||
| /** 资源规格字段 */ | |||
| export const resourceFieldNames = { | |||
| label: 'description', | |||
| value: 'id', | |||
| }; | |||
| export interface SystemResourceStore { | |||
| resources: ComputingResource[]; | |||
| } | |||
| const state = proxy<SystemResourceStore>({ | |||
| resources: [], | |||
| }); | |||
| /** 获取资源列表 */ | |||
| export const getSystemResources = async () => { | |||
| if (state.resources.length > 0) { | |||
| return; | |||
| } | |||
| const params = { | |||
| page: 0, | |||
| size: 1000, | |||
| resource_type: '', | |||
| }; | |||
| const [res] = await to(getComputingResourceReq(params)); | |||
| if (res && res.data && Array.isArray(res.data.content)) { | |||
| state.resources = res.data.content; | |||
| } | |||
| }; | |||
| export default state; | |||
| @@ -13,6 +13,8 @@ export default class SessionStorage { | |||
| static readonly aimUrlKey = 'aim-url'; | |||
| /** tensorBoard url */ | |||
| static readonly tensorBoardUrlKey = 'tensor-board-url'; | |||
| // /** 云际系统 Token */ | |||
| // static readonly jccTokenKey = 'jcc-token'; | |||
| /** | |||
| * 获取 SessionStorage 值 | |||