| @@ -3,6 +3,7 @@ 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 { 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, | |||
| }, | |||
| }; | |||
| @@ -5,18 +5,22 @@ | |||
| */ | |||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||
| import state from '@/state/jcdResource'; | |||
| 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'; | |||
| const identityFunc = (value: any) => value; | |||
| export interface ParameterSelectProps extends SelectProps { | |||
| /** 类型 */ | |||
| @@ -25,8 +29,6 @@ export interface ParameterSelectProps extends SelectProps { | |||
| display?: boolean; | |||
| /** 值,支持对象,对象必须包含 value */ | |||
| value?: string | ParameterSelectObject; | |||
| /** 用于流水线, 流水线资源规格要求 id 为字符串 */ | |||
| isPipeline?: boolean; | |||
| /** 修改后回调 */ | |||
| onChange?: (value: string | ParameterSelectObject) => void; | |||
| } | |||
| @@ -36,15 +38,14 @@ function ParameterSelect({ | |||
| dataType, | |||
| display = false, | |||
| value, | |||
| // isPipeline = false, | |||
| onChange, | |||
| ...rest | |||
| }: ParameterSelectProps) { | |||
| const [options, setOptions] = useState<SelectProps['options']>([]); | |||
| const propsConfig = paramSelectConfig[dataType]; | |||
| const { | |||
| getLabel, | |||
| getValue, | |||
| getLabel = identityFunc, | |||
| getValue = identityFunc, | |||
| getOptions, | |||
| filterOption, | |||
| fieldNames, | |||
| @@ -56,28 +57,43 @@ function ParameterSelect({ | |||
| 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 objectOptions = useMemo(() => { | |||
| return options?.map((v) => ({ | |||
| label: getLabel(v), | |||
| value: getValue(v), | |||
| })); | |||
| }, [getLabel, getValue, options]); | |||
| const snap = useSnapshot(state); | |||
| const { getResourceTypes } = snap; | |||
| const objectOptions = | |||
| dataType === 'remote-resource-type' | |||
| ? snap.types | |||
| : dataType === 'remote-image' | |||
| ? snap.images | |||
| : dataType === 'remote-resource' | |||
| ? snap.resources | |||
| : options; | |||
| // 将对象类型转换为 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 +103,14 @@ function ParameterSelect({ | |||
| if (res) { | |||
| setOptions(res); | |||
| } | |||
| } else if (dataType === 'remote-resource-type') { | |||
| getResourceTypes(); | |||
| } | |||
| }; | |||
| getSelectOptions(); | |||
| }, [getOptions]); | |||
| }, [getOptions, dataType, getResourceTypes]); | |||
| const selectOptions = dataType === 'resource' ? resourceStandardList : objectOptions; | |||
| const selectOptions = dataType === 'resource' ? resourceStandardList : objectSelectOptions; | |||
| const handleChange = (text: string) => { | |||
| // 数据集、模型、服务,转换成对象 | |||
| @@ -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'; | |||
| @@ -45,6 +50,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||
| ); | |||
| const [open, setOpen] = useState(false); | |||
| const [menuItems, setMenuItems] = useState<MenuProps['items']>([]); | |||
| const snap = useSnapshot(state); | |||
| const afterOpenChange = async () => { | |||
| if (!open) { | |||
| @@ -144,7 +150,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 +189,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 +257,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 +339,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 +384,26 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||
| </Flex> | |||
| )} | |||
| {item.value.type === ComponentType.Select && | |||
| (['dataset', 'model', 'service', 'resource'].includes(item.value.item_type) ? ( | |||
| ([ | |||
| 'dataset', | |||
| 'model', | |||
| 'service', | |||
| 'resource', | |||
| 'remote-resource-type', | |||
| 'remote-image', | |||
| 'remote-resource', | |||
| ].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)} | |||
| @@ -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,128 @@ | |||
| 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; | |||
| getResourceTypes: () => void; | |||
| getImages: (cardTypes: string[]) => void; | |||
| getResources: (cardType: string) => void; | |||
| setCurrentType: (cardType: string) => void; | |||
| }; | |||
| const state = proxy<JCCResourceTypeStore>({ | |||
| token: '', | |||
| types: [], | |||
| images: [], | |||
| resources: [], | |||
| currentType: undefined, | |||
| 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) => { | |||
| state.currentType = cardType; | |||
| if (cardType) { | |||
| state.getImages([cardType]); | |||
| state.getResources(cardType); | |||
| } | |||
| }, | |||
| }); | |||
| 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 值 | |||