| @@ -65,3 +65,4 @@ mvnw | |||||
| /react-ui/types/tsconfig.tsbuildinfo | /react-ui/types/tsconfig.tsbuildinfo | ||||
| /react-ui/storybook-static | /react-ui/storybook-static | ||||
| /react-ui/.storybook/scripts | /react-ui/.storybook/scripts | ||||
| /react-ui/dist.zip | |||||
| @@ -23,7 +23,11 @@ export default { | |||||
| target: 'http://172.20.32.197:31213', // 开发环境 | target: 'http://172.20.32.197:31213', // 开发环境 | ||||
| // target: 'http://172.20.32.235:31213', // 测试环境 | // target: 'http://172.20.32.235:31213', // 测试环境 | ||||
| // target: 'http://172.20.32.44:8082', | // target: 'http://172.20.32.44:8082', | ||||
| <<<<<<< HEAD | |||||
| // target: 'http://172.20.32.164:8082', | // target: 'http://172.20.32.164:8082', | ||||
| ======= | |||||
| target: 'http://172.20.32.164:8082', | |||||
| >>>>>>> dev-zw | |||||
| // 配置了这个可以从 http 代理到 https | // 配置了这个可以从 http 代理到 https | ||||
| // 依赖 origin 的功能可能需要这个,比如 cookie | // 依赖 origin 的功能可能需要这个,比如 cookie | ||||
| changeOrigin: true, | changeOrigin: true, | ||||
| @@ -8,7 +8,7 @@ | |||||
| "build": "max build", | "build": "max build", | ||||
| "deploy": "npm run build && npm run gh-pages", | "deploy": "npm run build && npm run gh-pages", | ||||
| "dev": "npm run start:dev", | "dev": "npm run start:dev", | ||||
| "dev-no-sso": "cross-env NO_SSO=true npm run start:dev", | |||||
| "dev-no-sso": "cross-env NO_SSO=true npm run start:mock", | |||||
| "docker-hub:build": "docker build -f Dockerfile.hub -t ant-design-pro ./", | "docker-hub:build": "docker build -f Dockerfile.hub -t ant-design-pro ./", | ||||
| "docker-prod:build": "docker-compose -f ./docker/docker-compose.yml build", | "docker-prod:build": "docker-compose -f ./docker/docker-compose.yml build", | ||||
| "docker-prod:dev": "docker-compose -f ./docker/docker-compose.yml up", | "docker-prod:dev": "docker-compose -f ./docker/docker-compose.yml up", | ||||
| @@ -4,7 +4,7 @@ | |||||
| * @Description: 流水线选择代码配置表单 | * @Description: 流水线选择代码配置表单 | ||||
| */ | */ | ||||
| import CodeSelectorModal from '@/components/CodeSelectorModal'; | |||||
| import CodeSelectorModal, { CodeConfigData } from '@/components/CodeSelectorModal'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { openAntdModal } from '@/utils/modal'; | import { openAntdModal } from '@/utils/modal'; | ||||
| import { Button } from 'antd'; | import { Button } from 'antd'; | ||||
| @@ -18,19 +18,9 @@ export { | |||||
| type ParameterInputValue, | type ParameterInputValue, | ||||
| } from '../ParameterInput'; | } from '../ParameterInput'; | ||||
| export type CodeSelectProps = ParameterInputProps; | |||||
| // 服务的需要的代码配置数据格式 | |||||
| export type ServerCodeData = { | |||||
| id: number; | |||||
| name: string; | |||||
| code_path: string; | |||||
| branch: string; | |||||
| username: string; | |||||
| password: string; | |||||
| ssh_private_key: string; | |||||
| is_public: boolean; | |||||
| }; | |||||
| export interface CodeSelectProps extends ParameterInputProps { | |||||
| value?: CodeConfigData; | |||||
| } | |||||
| /** 代码配置选择表单组件 */ | /** 代码配置选择表单组件 */ | ||||
| function CodeSelect({ | function CodeSelect({ | ||||
| @@ -44,50 +34,18 @@ function CodeSelect({ | |||||
| }: CodeSelectProps) { | }: CodeSelectProps) { | ||||
| // 选择代码配置 | // 选择代码配置 | ||||
| const selectResource = () => { | const selectResource = () => { | ||||
| const codeData = value as ServerCodeData; | |||||
| const defaultSelected = | |||||
| value && typeof value === 'object' | |||||
| ? { | |||||
| id: codeData.id, | |||||
| code_repo_name: codeData.name, | |||||
| git_url: codeData.code_path, | |||||
| git_branch: codeData.branch, | |||||
| git_user_name: codeData.username, | |||||
| git_password: codeData.password, | |||||
| ssh_key: codeData.ssh_private_key, | |||||
| is_public: codeData.is_public, | |||||
| } | |||||
| : undefined; | |||||
| const defaultSelected: CodeConfigData | undefined = | |||||
| value && typeof value === 'object' ? value : undefined; | |||||
| const { close } = openAntdModal(CodeSelectorModal, { | const { close } = openAntdModal(CodeSelectorModal, { | ||||
| defaultSelected: defaultSelected, | defaultSelected: defaultSelected, | ||||
| onOk: (res) => { | onOk: (res) => { | ||||
| if (res) { | if (res) { | ||||
| const { | |||||
| id, | |||||
| code_repo_name, | |||||
| git_url, | |||||
| git_branch, | |||||
| git_user_name, | |||||
| git_password, | |||||
| ssh_key, | |||||
| is_public, | |||||
| } = res; | |||||
| const jsonObj = { | |||||
| id, | |||||
| name: code_repo_name, | |||||
| code_path: git_url, | |||||
| branch: git_branch, | |||||
| username: git_user_name, | |||||
| password: git_password, | |||||
| ssh_private_key: ssh_key, | |||||
| is_public, | |||||
| }; | |||||
| const jsonObjStr = JSON.stringify(jsonObj); | |||||
| const { code_repo_name } = res; | |||||
| onChange?.({ | onChange?.({ | ||||
| value: jsonObjStr, | |||||
| ...res, | |||||
| value: code_repo_name, | |||||
| showValue: code_repo_name, | showValue: code_repo_name, | ||||
| fromSelect: true, | fromSelect: true, | ||||
| ...jsonObj, | |||||
| }); | }); | ||||
| } else { | } else { | ||||
| onChange?.(undefined); | onChange?.(undefined); | ||||
| @@ -1,3 +1,4 @@ | |||||
| import { PipelineGlobalParamType, type PipelineGlobalParam } from '@/types'; | |||||
| import { formatEnum } from '@/utils/format'; | import { formatEnum } from '@/utils/format'; | ||||
| import { Typography, type SelectProps } from 'antd'; | import { Typography, type SelectProps } from 'antd'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| @@ -16,6 +17,8 @@ type FormInfoProps = { | |||||
| options?: SelectProps['options']; | options?: SelectProps['options']; | ||||
| /** 自定义节点 label、value 的字段 */ | /** 自定义节点 label、value 的字段 */ | ||||
| fieldNames?: SelectProps['fieldNames']; | fieldNames?: SelectProps['fieldNames']; | ||||
| /** 全局参数 */ | |||||
| globalParams?: PipelineGlobalParam[] | null; | |||||
| /** 自定义类名 */ | /** 自定义类名 */ | ||||
| className?: string; | className?: string; | ||||
| /** 自定义样式 */ | /** 自定义样式 */ | ||||
| @@ -32,12 +35,29 @@ function FormInfo({ | |||||
| select = false, | select = false, | ||||
| options, | options, | ||||
| fieldNames, | fieldNames, | ||||
| globalParams, | |||||
| className, | className, | ||||
| style, | style, | ||||
| }: FormInfoProps) { | }: FormInfoProps) { | ||||
| let showValue = value; | let showValue = value; | ||||
| if (value && typeof value === 'object' && valuePropName) { | if (value && typeof value === 'object' && valuePropName) { | ||||
| showValue = value[valuePropName]; | showValue = value[valuePropName]; | ||||
| const reg = /^\$\{(.*)\}$/; | |||||
| if (value.fromSelect && Array.isArray(globalParams) && globalParams.length > 0) { | |||||
| const match = reg.exec(showValue); | |||||
| if (match) { | |||||
| const paramName = match[1]; | |||||
| const foundParam = globalParams.find((v) => v.param_name === paramName); | |||||
| if (foundParam) { | |||||
| showValue = | |||||
| foundParam.param_type === PipelineGlobalParamType.Boolean // 布尔类型转换 | |||||
| ? foundParam.param_value | |||||
| ? 'true' | |||||
| : 'false' | |||||
| : foundParam.param_value; | |||||
| } | |||||
| } | |||||
| } | |||||
| } else if (select === true && options) { | } else if (select === true && options) { | ||||
| let _options: SelectProps['options'] = options; | let _options: SelectProps['options'] = options; | ||||
| if (fieldNames) { | if (fieldNames) { | ||||
| @@ -9,6 +9,7 @@ import { CloseOutlined } from '@ant-design/icons'; | |||||
| import { ConfigProvider, Form, Input, Typography } from 'antd'; | import { ConfigProvider, Form, Input, Typography } from 'antd'; | ||||
| import { RuleObject } from 'antd/es/form'; | import { RuleObject } from 'antd/es/form'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { ReactNode } from 'react'; | |||||
| import './index.less'; | import './index.less'; | ||||
| // 如果值是对象时的类型 | // 如果值是对象时的类型 | ||||
| @@ -55,6 +56,8 @@ export interface ParameterInputProps { | |||||
| disabled?: boolean; | disabled?: boolean; | ||||
| /** 元素 id */ | /** 元素 id */ | ||||
| id?: string; | id?: string; | ||||
| /** 带标签的 input,设置后置标签 */ | |||||
| addonAfter?: ReactNode; | |||||
| } | } | ||||
| function ParameterInput({ | function ParameterInput({ | ||||
| @@ -75,7 +78,7 @@ function ParameterInput({ | |||||
| const valueObj = | const valueObj = | ||||
| typeof value === 'string' ? { value: value, fromSelect: false, showValue: value } : value; | typeof value === 'string' ? { value: value, fromSelect: false, showValue: value } : value; | ||||
| if (valueObj && !valueObj.showValue) { | if (valueObj && !valueObj.showValue) { | ||||
| valueObj.showValue = valueObj.value; | |||||
| valueObj.showValue = typeof valueObj.value === 'string' ? valueObj.value : ''; | |||||
| } | } | ||||
| const isSelect = valueObj?.fromSelect; | const isSelect = valueObj?.fromSelect; | ||||
| const placeholder = valueObj?.placeholder || rest?.placeholder; | const placeholder = valueObj?.placeholder || rest?.placeholder; | ||||
| @@ -1,29 +1,34 @@ | |||||
| import { filterResourceStandard, resourceFieldNames } from '@/hooks/useComputingResource'; | |||||
| import { DatasetData, ModelData } from '@/pages/Dataset/config'; | |||||
| import { ServiceData } from '@/pages/ModelDeployment/types'; | import { ServiceData } from '@/pages/ModelDeployment/types'; | ||||
| import { getDatasetList, getModelList } from '@/services/dataset/index.js'; | import { getDatasetList, getModelList } from '@/services/dataset/index.js'; | ||||
| import { getServiceListReq } from '@/services/modelDeployment'; | import { getServiceListReq } from '@/services/modelDeployment'; | ||||
| import type { JCCResourceImage, JCCResourceStandard, JCCResourceType } from '@/state/jcdResource'; | |||||
| import { filterResourceStandard, resourceFieldNames } from '@/state/systemResource'; | |||||
| import { type SelectProps } from 'antd'; | import { type SelectProps } from 'antd'; | ||||
| import { pick } from 'lodash'; | |||||
| // id 从 number 转换为 string | |||||
| const convertId = (item: any) => ({ | |||||
| ...item, | |||||
| id: JSON.stringify({ | |||||
| id: `${item.id}`, | |||||
| name: item.name, | |||||
| identifier: item.identifier, | |||||
| owner: item.owner, | |||||
| }), | |||||
| }); | |||||
| export type SelectPropsConfig = { | export type SelectPropsConfig = { | ||||
| getOptions: () => Promise<any>; // 获取下拉数据 | |||||
| getOptions?: () => Promise<any>; // 获取下拉数据 | |||||
| fieldNames?: SelectProps['fieldNames']; // 下拉数据字段 | fieldNames?: SelectProps['fieldNames']; // 下拉数据字段 | ||||
| optionFilterProp?: SelectProps['optionFilterProp']; // 过滤字段名 | optionFilterProp?: SelectProps['optionFilterProp']; // 过滤字段名 | ||||
| filterOption?: SelectProps['filterOption']; // 过滤函数 | filterOption?: SelectProps['filterOption']; // 过滤函数 | ||||
| isObjectValue: boolean; // value 是对象 | |||||
| getValue?: (value: any) => string | number; // 对象类型时,获取其值 | |||||
| getLabel?: (value: any) => string; // 对象类型时,获取其 label | |||||
| }; | }; | ||||
| export const paramSelectConfig: Record<string, SelectPropsConfig> = { | |||||
| export const ParameterSelectTypeList = [ | |||||
| 'dataset', | |||||
| 'model', | |||||
| 'service', | |||||
| 'resource', | |||||
| 'remote-image', | |||||
| 'remote-resource-type', | |||||
| 'remote-resource', | |||||
| ] as const; | |||||
| export type ParameterSelectDataType = (typeof ParameterSelectTypeList)[number]; | |||||
| export const paramSelectConfig: Record<ParameterSelectDataType, SelectPropsConfig> = { | |||||
| dataset: { | dataset: { | ||||
| getOptions: async () => { | getOptions: async () => { | ||||
| const res = await getDatasetList({ | const res = await getDatasetList({ | ||||
| @@ -31,13 +36,16 @@ export const paramSelectConfig: Record<string, SelectPropsConfig> = { | |||||
| size: 1000, | size: 1000, | ||||
| is_public: false, | is_public: false, | ||||
| }); | }); | ||||
| return res?.data?.content?.map(convertId) ?? []; | |||||
| return res?.data?.content ?? []; | |||||
| }, | }, | ||||
| fieldNames: { | |||||
| label: 'name', | |||||
| value: 'id', | |||||
| optionFilterProp: 'label', | |||||
| getValue: (value: DatasetData) => { | |||||
| return value.id; | |||||
| }, | |||||
| getLabel: (value: DatasetData) => { | |||||
| return value.name; | |||||
| }, | }, | ||||
| optionFilterProp: 'name', | |||||
| isObjectValue: true, | |||||
| }, | }, | ||||
| model: { | model: { | ||||
| getOptions: async () => { | getOptions: async () => { | ||||
| @@ -46,13 +54,16 @@ export const paramSelectConfig: Record<string, SelectPropsConfig> = { | |||||
| size: 1000, | size: 1000, | ||||
| is_public: false, | is_public: false, | ||||
| }); | }); | ||||
| return res?.data?.content?.map(convertId) ?? []; | |||||
| return res?.data?.content ?? []; | |||||
| }, | |||||
| optionFilterProp: 'label', | |||||
| getValue: (value: ModelData) => { | |||||
| return value.id; | |||||
| }, | }, | ||||
| fieldNames: { | |||||
| label: 'name', | |||||
| value: 'id', | |||||
| getLabel: (value: ModelData) => { | |||||
| return value.name; | |||||
| }, | }, | ||||
| optionFilterProp: 'name', | |||||
| isObjectValue: true, | |||||
| }, | }, | ||||
| service: { | service: { | ||||
| getOptions: async () => { | getOptions: async () => { | ||||
| @@ -60,25 +71,58 @@ export const paramSelectConfig: Record<string, SelectPropsConfig> = { | |||||
| page: 0, | page: 0, | ||||
| size: 1000, | size: 1000, | ||||
| }); | }); | ||||
| return ( | |||||
| res?.data?.content?.map((item: ServiceData) => ({ | |||||
| label: item.service_name, | |||||
| value: JSON.stringify(pick(item, ['id', 'service_name'])), | |||||
| })) ?? [] | |||||
| ); | |||||
| }, | |||||
| fieldNames: { | |||||
| label: 'label', | |||||
| value: 'value', | |||||
| return res?.data?.content ?? []; | |||||
| }, | }, | ||||
| optionFilterProp: 'label', | optionFilterProp: 'label', | ||||
| getValue: (value: ServiceData) => { | |||||
| return value.id; | |||||
| }, | |||||
| getLabel: (value: ServiceData) => { | |||||
| return value.service_name; | |||||
| }, | |||||
| isObjectValue: true, | |||||
| }, | }, | ||||
| resource: { | resource: { | ||||
| getOptions: async () => { | |||||
| // 不需要这个函数 | |||||
| return []; | |||||
| }, | |||||
| fieldNames: resourceFieldNames, | fieldNames: resourceFieldNames, | ||||
| filterOption: filterResourceStandard as SelectProps['filterOption'], | filterOption: filterResourceStandard as SelectProps['filterOption'], | ||||
| isObjectValue: false, | |||||
| }, | |||||
| 'remote-resource-type': { | |||||
| optionFilterProp: 'label', | |||||
| isObjectValue: false, | |||||
| getValue: (value: JCCResourceType) => { | |||||
| return value.value; | |||||
| }, | |||||
| getLabel: (value: JCCResourceType) => { | |||||
| return value.label; | |||||
| }, | |||||
| }, | |||||
| '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: 参数下拉选择组件,支持资源规格、数据集、模型、服务 | * @Description: 参数下拉选择组件,支持资源规格、数据集、模型、服务 | ||||
| */ | */ | ||||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||||
| import jccResourceState from '@/state/jcdResource'; | |||||
| import systemResourceState, { getSystemResources } from '@/state/systemResource'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { useSnapshot } from '@umijs/max'; | |||||
| import { Select, type SelectProps } from 'antd'; | import { Select, type SelectProps } from 'antd'; | ||||
| import { useEffect, useState } from 'react'; | |||||
| import { useCallback, useEffect, useMemo, useState } from 'react'; | |||||
| import FormInfo from '../FormInfo'; | import FormInfo from '../FormInfo'; | ||||
| import { paramSelectConfig } from './config'; | |||||
| import { paramSelectConfig, type ParameterSelectDataType } from './config'; | |||||
| export { ParameterSelectTypeList, type ParameterSelectDataType } from './config'; | |||||
| export type ParameterSelectObject = { | export type ParameterSelectObject = { | ||||
| value: any; | value: any; | ||||
| [key: string]: 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 { | export interface ParameterSelectProps extends SelectProps { | ||||
| /** 类型 */ | /** 类型 */ | ||||
| @@ -25,8 +31,6 @@ export interface ParameterSelectProps extends SelectProps { | |||||
| display?: boolean; | display?: boolean; | ||||
| /** 值,支持对象,对象必须包含 value */ | /** 值,支持对象,对象必须包含 value */ | ||||
| value?: string | ParameterSelectObject; | value?: string | ParameterSelectObject; | ||||
| /** 用于流水线, 流水线资源规格要求 id 为字符串 */ | |||||
| isPipeline?: boolean; | |||||
| /** 修改后回调 */ | /** 修改后回调 */ | ||||
| onChange?: (value: string | ParameterSelectObject) => void; | onChange?: (value: string | ParameterSelectObject) => void; | ||||
| } | } | ||||
| @@ -36,69 +40,126 @@ function ParameterSelect({ | |||||
| dataType, | dataType, | ||||
| display = false, | display = false, | ||||
| value, | value, | ||||
| isPipeline = false, | |||||
| onChange, | onChange, | ||||
| ...rest | ...rest | ||||
| }: ParameterSelectProps) { | }: ParameterSelectProps) { | ||||
| const [options, setOptions] = useState<SelectProps['options']>([]); | |||||
| const [options, setOptions] = useState<SelectOptions>([]); | |||||
| const propsConfig = paramSelectConfig[dataType]; | const propsConfig = paramSelectConfig[dataType]; | ||||
| const valueText = typeof value === 'object' && value !== null ? value.value : value; | |||||
| const [resourceStandardList] = useComputingResource(); | |||||
| const computingResource = isPipeline | |||||
| ? resourceStandardList.map((v) => ({ | |||||
| ...v, | |||||
| id: String(v.id), | |||||
| })) | |||||
| : resourceStandardList; | |||||
| const { | |||||
| getLabel = identityFunc, | |||||
| getValue = identityFunc, | |||||
| getOptions, | |||||
| filterOption, | |||||
| fieldNames, | |||||
| optionFilterProp, | |||||
| isObjectValue, | |||||
| } = propsConfig; | |||||
| const selectValue = typeof value === 'object' && value !== null ? value.value : value; | |||||
| // 数据集、模型、服务,对象转换成 json 字符串 | |||||
| const valueText = | |||||
| typeof selectValue === 'object' && selectValue !== null ? getValue(selectValue) : selectValue; | |||||
| const jccResourceSnap = useSnapshot(jccResourceState); | |||||
| const { getResourceTypes } = jccResourceSnap; | |||||
| const systemResourceSnap = useSnapshot(systemResourceState); | |||||
| const objectOptions = useMemo(() => { | |||||
| 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>(); | |||||
| objectOptions?.forEach((v) => { | |||||
| map.set(getValue(v), v); | |||||
| }); | |||||
| return map; | |||||
| }, [objectOptions, getValue]); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| // 获取下拉数据 | // 获取下拉数据 | ||||
| const getSelectOptions = async () => { | const getSelectOptions = async () => { | ||||
| if (!propsConfig) { | |||||
| return; | |||||
| } | |||||
| const getOptions = propsConfig.getOptions; | |||||
| const [res] = await to(getOptions()); | |||||
| if (res) { | |||||
| setOptions(res); | |||||
| if (getOptions) { | |||||
| const [res] = await to(getOptions()); | |||||
| if (res) { | |||||
| setOptions(res); | |||||
| } | |||||
| } else if (dataType === 'remote-resource-type') { | |||||
| getResourceTypes(); | |||||
| } else if (dataType === 'resource') { | |||||
| getSystemResources(); | |||||
| } | } | ||||
| }; | }; | ||||
| getSelectOptions(); | getSelectOptions(); | ||||
| }, [propsConfig]); | |||||
| }, [getOptions, dataType, getResourceTypes]); | |||||
| const selectOptions = dataType === 'resource' ? computingResource : options; | |||||
| const selectOptions = ( | |||||
| dataType === 'resource' ? systemResourceSnap.resources : objectSelectOptions | |||||
| ) as SelectOptions; | |||||
| const handleChange = (text: string) => { | const handleChange = (text: string) => { | ||||
| if (typeof value === 'object' && value !== null) { | |||||
| onChange?.({ | |||||
| ...value, | |||||
| value: text, | |||||
| }); | |||||
| // 数据集、模型、服务,转换成对象 | |||||
| if (isObjectValue) { | |||||
| // 设置为 null 是因为 antv g6 的 bug | |||||
| // 如果值为 undefined 时, graph.changeData(data) 会保留前面的值 | |||||
| const selectValue = text ? valueMap.get(text) : null; | |||||
| if (typeof value === 'object' && value !== null) { | |||||
| onChange?.({ | |||||
| ...value, | |||||
| value: selectValue, | |||||
| }); | |||||
| } else { | |||||
| onChange?.(selectValue); | |||||
| } | |||||
| } else { | } else { | ||||
| onChange?.(text); | |||||
| const selectValue = text ? text : ''; | |||||
| if (typeof value === 'object' && value !== null) { | |||||
| onChange?.({ | |||||
| ...value, | |||||
| value: selectValue, | |||||
| }); | |||||
| } else { | |||||
| onChange?.(selectValue); | |||||
| } | |||||
| } | } | ||||
| }; | }; | ||||
| // 只用于展示,FormInfo 组件带有 Tooltip | // 只用于展示,FormInfo 组件带有 Tooltip | ||||
| if (display) { | if (display) { | ||||
| return ( | return ( | ||||
| <FormInfo | |||||
| select | |||||
| value={valueText} | |||||
| options={selectOptions} | |||||
| fieldNames={propsConfig?.fieldNames} | |||||
| ></FormInfo> | |||||
| <FormInfo select value={valueText} options={selectOptions} fieldNames={fieldNames}></FormInfo> | |||||
| ); | ); | ||||
| } | } | ||||
| return ( | return ( | ||||
| <Select | <Select | ||||
| {...rest} | {...rest} | ||||
| filterOption={propsConfig?.filterOption} | |||||
| options={selectOptions} | options={selectOptions} | ||||
| fieldNames={propsConfig?.fieldNames} | |||||
| optionFilterProp={propsConfig?.optionFilterProp} | |||||
| fieldNames={fieldNames} | |||||
| optionFilterProp={optionFilterProp} | |||||
| filterOption={filterOption} | |||||
| value={valueText} | value={valueText} | ||||
| onChange={handleChange} | onChange={handleChange} | ||||
| showSearch | showSearch | ||||
| @@ -10,10 +10,10 @@ import ResourceSelectorModal, { | |||||
| ResourceSelectorType, | ResourceSelectorType, | ||||
| selectorTypeConfig, | selectorTypeConfig, | ||||
| } from '@/components/ResourceSelectorModal'; | } from '@/components/ResourceSelectorModal'; | ||||
| import { CommonTabKeys } from '@/enums'; | |||||
| import { openAntdModal } from '@/utils/modal'; | import { openAntdModal } from '@/utils/modal'; | ||||
| import { Button, ConfigProvider } from 'antd'; | import { Button, ConfigProvider } from 'antd'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { pick } from 'lodash'; | |||||
| import ParameterInput, { type ParameterInputProps } from '../ParameterInput'; | import ParameterInput, { type ParameterInputProps } from '../ParameterInput'; | ||||
| import './index.less'; | import './index.less'; | ||||
| @@ -27,6 +27,8 @@ export { ResourceSelectorType, selectorTypeConfig, type ResourceSelectorResponse | |||||
| interface ResourceSelectProps extends ParameterInputProps { | interface ResourceSelectProps extends ParameterInputProps { | ||||
| /** 类型,数据集、模型、镜像 */ | /** 类型,数据集、模型、镜像 */ | ||||
| type: ResourceSelectorType; | type: ResourceSelectorType; | ||||
| /** 值 */ | |||||
| value?: ResourceSelectorResponse; | |||||
| } | } | ||||
| // 获取选择数据集、模型、镜像后面按钮 icon | // 获取选择数据集、模型、镜像后面按钮 icon | ||||
| @@ -47,68 +49,51 @@ function ResourceSelect({ | |||||
| }: ResourceSelectProps) { | }: ResourceSelectProps) { | ||||
| const { componentSize } = ConfigProvider.useConfig(); | const { componentSize } = ConfigProvider.useConfig(); | ||||
| const mySize = size || componentSize; | const mySize = size || componentSize; | ||||
| let selectedResource: ResourceSelectorResponse | undefined = undefined; | |||||
| if ( | |||||
| value && | |||||
| typeof value === 'object' && | |||||
| value.activeTab && | |||||
| value.id && | |||||
| value.name && | |||||
| value.version && | |||||
| value.path && | |||||
| (type === ResourceSelectorType.Mirror || (value.identifier && value.owner)) | |||||
| ) { | |||||
| selectedResource = pick(value, [ | |||||
| 'activeTab', | |||||
| 'id', | |||||
| 'identifier', | |||||
| 'name', | |||||
| 'owner', | |||||
| 'version', | |||||
| 'path', | |||||
| ]) as ResourceSelectorResponse; | |||||
| let defaultActiveTab: CommonTabKeys | undefined, | |||||
| defaultExpandedKeys: string[] = [], | |||||
| defaultCheckedKeys: string[] = []; | |||||
| if (value && typeof value === 'object') { | |||||
| defaultActiveTab = value.activeTab; | |||||
| if (type === ResourceSelectorType.Mirror) { | |||||
| if (value.image_id && value.id) { | |||||
| defaultExpandedKeys = [`${value.image_id}`]; | |||||
| defaultCheckedKeys = [`${value.image_id}-${value.id}`]; | |||||
| } | |||||
| } else if (value.id && value.version) { | |||||
| defaultExpandedKeys = [value.id]; | |||||
| defaultCheckedKeys = [`${value.id}-${value.version}`]; | |||||
| } | |||||
| } | } | ||||
| // 选择数据集、模型、镜像 | // 选择数据集、模型、镜像 | ||||
| const selectResource = () => { | const selectResource = () => { | ||||
| const { close } = openAntdModal(ResourceSelectorModal, { | const { close } = openAntdModal(ResourceSelectorModal, { | ||||
| type, | type, | ||||
| defaultExpandedKeys: selectedResource ? [selectedResource.id] : [], | |||||
| defaultCheckedKeys: selectedResource | |||||
| ? [`${selectedResource.id}-${selectedResource.version}`] | |||||
| : [], | |||||
| defaultActiveTab: selectedResource?.activeTab, | |||||
| defaultExpandedKeys: defaultExpandedKeys, | |||||
| defaultCheckedKeys: defaultCheckedKeys, | |||||
| defaultActiveTab: defaultActiveTab, | |||||
| onOk: (res) => { | onOk: (res) => { | ||||
| if (res) { | if (res) { | ||||
| const { activeTab, id, name, version, path, identifier, owner } = res; | |||||
| if (type === ResourceSelectorType.Mirror) { | if (type === ResourceSelectorType.Mirror) { | ||||
| const { activeTab, ...rest } = res; | |||||
| const { url } = rest; | |||||
| onChange?.({ | onChange?.({ | ||||
| value: path, | |||||
| showValue: path, | |||||
| ...rest, | |||||
| value: url, | |||||
| showValue: url, | |||||
| fromSelect: true, | fromSelect: true, | ||||
| activeTab, | activeTab, | ||||
| id, | |||||
| name, | |||||
| version, | |||||
| path, | |||||
| }); | }); | ||||
| } else { | } else { | ||||
| const jsonObj = { | |||||
| id, | |||||
| name, | |||||
| version, | |||||
| path, | |||||
| identifier, | |||||
| owner, | |||||
| }; | |||||
| const jsonObjStr = JSON.stringify(jsonObj); | |||||
| const { activeTab, ...rest } = res; | |||||
| const { name, version } = rest; | |||||
| const showValue = `${name}:${version}`; | const showValue = `${name}:${version}`; | ||||
| onChange?.({ | onChange?.({ | ||||
| value: jsonObjStr, | |||||
| ...rest, | |||||
| value: showValue, | |||||
| showValue, | showValue, | ||||
| fromSelect: true, | fromSelect: true, | ||||
| activeTab, | activeTab, | ||||
| ...jsonObj, | |||||
| }); | }); | ||||
| } | } | ||||
| } else { | } else { | ||||
| @@ -52,9 +52,9 @@ const convertResourceVersionToTreeData = ( | |||||
| ): TreeDataNode[] => { | ): TreeDataNode[] => { | ||||
| return list.map((item: ResourceVersionData) => ({ | return list.map((item: ResourceVersionData) => ({ | ||||
| ...pick(info, ['id', 'name', 'owner', 'identifier', 'is_public']), | ...pick(info, ['id', 'name', 'owner', 'identifier', 'is_public']), | ||||
| version: item.name, | |||||
| title: item.name, | |||||
| key: `${parentId}-${item.name}`, | key: `${parentId}-${item.name}`, | ||||
| title: item.name, | |||||
| version: item.name, | |||||
| isLeaf: true, | isLeaf: true, | ||||
| checkable: true, | checkable: true, | ||||
| })); | })); | ||||
| @@ -66,12 +66,11 @@ const convertMirrorVersionToTreeData = ( | |||||
| list: MirrorVersionData[], | list: MirrorVersionData[], | ||||
| ): TreeDataNode[] => { | ): TreeDataNode[] => { | ||||
| return list.map((item: MirrorVersionData) => ({ | return list.map((item: MirrorVersionData) => ({ | ||||
| url: item.url, | |||||
| title: item.tag_name, | |||||
| ...item, | |||||
| key: `${parentId}-${item.id}`, | key: `${parentId}-${item.id}`, | ||||
| title: item.tag_name, | |||||
| isLeaf: true, | isLeaf: true, | ||||
| checkable: true, | checkable: true, | ||||
| description: item.description, | |||||
| })); | })); | ||||
| }; | }; | ||||
| @@ -8,6 +8,7 @@ import KFIcon from '@/components/KFIcon'; | |||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { CommonTabKeys } from '@/enums'; | import { CommonTabKeys } from '@/enums'; | ||||
| import { ResourceFileData } from '@/pages/Dataset/config'; | import { ResourceFileData } from '@/pages/Dataset/config'; | ||||
| import { type MirrorVersionData } from '@/pages/Mirror/Info'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import type { GetRef, ModalProps, TreeDataNode, TreeProps } from 'antd'; | import type { GetRef, ModalProps, TreeDataNode, TreeProps } from 'antd'; | ||||
| import { Input, Tabs, Tree } from 'antd'; | import { Input, Tabs, Tree } from 'antd'; | ||||
| @@ -19,13 +20,13 @@ export { ResourceSelectorType, selectorTypeConfig }; | |||||
| // 选择数据集、模型、镜像的返回类型 | // 选择数据集、模型、镜像的返回类型 | ||||
| export type ResourceSelectorResponse = { | export type ResourceSelectorResponse = { | ||||
| activeTab: CommonTabKeys; // 是我的还是公开的 | activeTab: CommonTabKeys; // 是我的还是公开的 | ||||
| id: string; // 数据集\模型\镜像 id | |||||
| name: string; // 数据集\模型\镜像 name | |||||
| version: string; // 数据集\模型\镜像版本 | |||||
| path: string; // 数据集\模型\镜像版本路径 | |||||
| identifier: string; // 数据集\模型 identifier,镜像这个字段为空 | |||||
| owner: string; // 数据集\模型 owner,镜像这个字段为空 | |||||
| }; | |||||
| id: string; // 数据集\模型 id | |||||
| name: string; // 数据集\模型 name | |||||
| identifier: string; // 数据集\模型 identifier | |||||
| owner: string; // 数据集\模型 owner | |||||
| version: string; // 数据集\模型 version | |||||
| path: string; // 数据集\模型 版本路径 | |||||
| } & MirrorVersionData; | |||||
| export interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> { | export interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> { | ||||
| /** 类型,数据集、模型、镜像 */ | /** 类型,数据集、模型、镜像 */ | ||||
| @@ -241,15 +242,22 @@ function ResourceSelectorModal({ | |||||
| const name = (treeNode?.title ?? '') as string; | const name = (treeNode?.title ?? '') as string; | ||||
| const identifier = (treeNode?.identifier ?? '') as string; | const identifier = (treeNode?.identifier ?? '') as string; | ||||
| const owner = (treeNode?.owner ?? '') as string; | const owner = (treeNode?.owner ?? '') as string; | ||||
| const res = { | |||||
| id, | |||||
| name, | |||||
| path: versionPath, | |||||
| version, | |||||
| identifier, | |||||
| owner, | |||||
| activeTab: activeTab, | |||||
| }; | |||||
| const childNode = treeNode.children.filter((v: TreeDataNode) => v.key === last)[0]; | |||||
| const res = | |||||
| type === ResourceSelectorType.Mirror | |||||
| ? { | |||||
| activeTab: activeTab, | |||||
| ...childNode, | |||||
| } | |||||
| : { | |||||
| activeTab: activeTab, | |||||
| id: Number(id), | |||||
| name, | |||||
| path: versionPath, | |||||
| version, | |||||
| identifier, | |||||
| owner, | |||||
| }; | |||||
| onOk?.(res); | onOk?.(res); | ||||
| } else { | } else { | ||||
| onOk?.(undefined); | onOk?.(undefined); | ||||
| @@ -163,3 +163,11 @@ export enum AutoMLTrailStatus { | |||||
| CANCELLED = 'CANCELLED', // 取消 | CANCELLED = 'CANCELLED', // 取消 | ||||
| MEMOUT = 'MEMOUT', // 内存溢出 | MEMOUT = 'MEMOUT', // 内存溢出 | ||||
| } | } | ||||
| // 流水线组件类型 | |||||
| export enum ComponentType { | |||||
| Ref = 'ref', | |||||
| Select = 'select', | |||||
| Map = 'map', | |||||
| Str = 'str', | |||||
| } | |||||
| @@ -4,66 +4,90 @@ | |||||
| * @Description: 资源规格 hook | * @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( | const getDescription = useCallback( | ||||
| (id?: string | number) => { | (id?: string | number) => { | ||||
| if (!id) { | if (!id) { | ||||
| return undefined; | 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 ConfigInfo, { type BasicInfoData } from '@/components/ConfigInfo'; | ||||
| import { AutoMLTaskType, autoMLTaskTypeOptions, ExperimentStatus } from '@/enums'; | import { AutoMLTaskType, autoMLTaskTypeOptions, ExperimentStatus } from '@/enums'; | ||||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||||
| import { useSystemResource } from '@/hooks/useComputingResource'; | |||||
| import { | import { | ||||
| classifierAlgorithms, | classifierAlgorithms, | ||||
| FrameworkType, | FrameworkType, | ||||
| @@ -39,7 +39,7 @@ function BasicInfo({ | |||||
| instanceStatus, | instanceStatus, | ||||
| isInstance = false, | isInstance = false, | ||||
| }: BasicInfoProps) { | }: BasicInfoProps) { | ||||
| const getResourceDescription = useComputingResource()[1]; | |||||
| const getResourceDescription = useSystemResource(); | |||||
| const basicDatas: BasicInfoData[] = useMemo(() => { | const basicDatas: BasicInfoData[] = useMemo(() => { | ||||
| if (!info) { | if (!info) { | ||||
| return []; | return []; | ||||
| @@ -6,7 +6,7 @@ import { | |||||
| autoMLEnsembleClassOptions, | autoMLEnsembleClassOptions, | ||||
| autoMLTaskTypeOptions, | autoMLTaskTypeOptions, | ||||
| } from '@/enums'; | } from '@/enums'; | ||||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||||
| import { useSystemResource } from '@/hooks/useComputingResource'; | |||||
| import { | import { | ||||
| classificationAlgorithms, | classificationAlgorithms, | ||||
| featureAlgorithms, | featureAlgorithms, | ||||
| @@ -76,7 +76,7 @@ function AutoMLBasic({ | |||||
| instanceStatus, | instanceStatus, | ||||
| isInstance = false, | isInstance = false, | ||||
| }: AutoMLBasicProps) { | }: AutoMLBasicProps) { | ||||
| const getResourceDescription = useComputingResource()[1]; | |||||
| const getResourceDescription = useSystemResource(); | |||||
| const basicDatas: BasicInfoData[] = useMemo(() => { | const basicDatas: BasicInfoData[] = useMemo(() => { | ||||
| if (!info) { | if (!info) { | ||||
| return []; | return []; | ||||
| @@ -42,37 +42,44 @@ export const regressorAlgorithms = [ | |||||
| // 特征预处理算法 | // 特征预处理算法 | ||||
| export const featureAlgorithms = [ | export const featureAlgorithms = [ | ||||
| { label: 'densifier (数据增稠)', value: 'densifier' }, | |||||
| { label: 'densifier (特征变换-数据增稠)', value: 'densifier' }, | |||||
| { | { | ||||
| label: 'extra_trees_preproc_for_classification (分类任务极端随机树)', | |||||
| label: 'extra_trees_preproc_for_classification (特征选择-分类任务极端随机树)', | |||||
| value: 'extra_trees_preproc_for_classification', | value: 'extra_trees_preproc_for_classification', | ||||
| }, | }, | ||||
| { | { | ||||
| label: 'extra_trees_preproc_for_regression (回归任务极端随机树)', | |||||
| label: 'extra_trees_preproc_for_regression (特征选择-回归任务极端随机树)', | |||||
| value: 'extra_trees_preproc_for_regression', | value: 'extra_trees_preproc_for_regression', | ||||
| }, | }, | ||||
| { label: 'fast_ica (快速独立成分分析)', value: 'fast_ica' }, | |||||
| { label: 'feature_agglomeration (特征聚合)', value: 'feature_agglomeration' }, | |||||
| { label: 'kernel_pca (核主成分分析)', value: 'kernel_pca' }, | |||||
| { label: 'kitchen_sinks (随机特征映射)', value: 'kitchen_sinks' }, | |||||
| { label: 'liblinear_svc_preprocessor (线性svc预处理器)', value: 'liblinear_svc_preprocessor' }, | |||||
| { label: 'fast_ica (特征选择-快速独立成分分析)', value: 'fast_ica' }, | |||||
| { label: 'feature_agglomeration (特征变换-特征聚合)', value: 'feature_agglomeration' }, | |||||
| { label: 'kernel_pca (特征选择-核主成分分析)', value: 'kernel_pca' }, | |||||
| { label: 'kitchen_sinks (特征变换-随机特征映射)', value: 'kitchen_sinks' }, | |||||
| { | |||||
| label: 'liblinear_svc_preprocessor (特征选择-线性svc预处理器)', | |||||
| value: 'liblinear_svc_preprocessor', | |||||
| }, | |||||
| { label: 'miss_value_impute (缺失值填充)', value: 'miss_value_impute' }, | |||||
| { label: 'no_preprocessing (无预处理)', value: 'no_preprocessing' }, | { label: 'no_preprocessing (无预处理)', value: 'no_preprocessing' }, | ||||
| { label: 'nystroem_sampler (尼斯特罗姆采样器)', value: 'nystroem_sampler' }, | |||||
| { label: 'pca (主成分分析)', value: 'pca' }, | |||||
| { label: 'polynomial (多项式特征扩展)', value: 'polynomial' }, | |||||
| { label: 'random_trees_embedding (随机森林特征嵌入)', value: 'random_trees_embedding' }, | |||||
| { label: 'nystroem_sampler (特征变换-尼斯特罗姆采样器)', value: 'nystroem_sampler' }, | |||||
| { label: 'pca (特征选择-主成分分析)', value: 'pca' }, | |||||
| { label: 'polynomial (特征变换-多项式特征扩展)', value: 'polynomial' }, | |||||
| { label: 'random_trees_embedding (特征变换-随机森林特征嵌入)', value: 'random_trees_embedding' }, | |||||
| { | { | ||||
| label: 'select_percentile_classification (基于百分位的分类特征选择)', | |||||
| label: 'select_percentile_classification 特征选择-基于百分位的分类特征选择)', | |||||
| value: 'select_percentile_classification', | value: 'select_percentile_classification', | ||||
| }, | }, | ||||
| { | { | ||||
| label: 'select_percentile_regression (基于百分位的回归特征选择)', | |||||
| label: 'select_percentile_regression (特征选择-基于百分位的回归特征选择)', | |||||
| value: 'select_percentile_regression', | value: 'select_percentile_regression', | ||||
| }, | }, | ||||
| { | { | ||||
| label: 'select_rates_classification (基于比率的分类特征选择)', | |||||
| label: 'select_rates_classification (特征选择-基于比率的分类特征选择)', | |||||
| value: 'select_rates_classification', | value: 'select_rates_classification', | ||||
| }, | }, | ||||
| { label: 'select_rates_regression (基于比率的回归特征选择)', value: 'select_rates_regression' }, | |||||
| { label: 'truncatedSVD (截断奇异值分解)', value: 'truncatedSVD' }, | |||||
| { | |||||
| label: 'select_rates_regression (特征选择-基于比率的回归特征选择)', | |||||
| value: 'select_rates_regression', | |||||
| }, | |||||
| { label: 'truncatedSVD (特征变换-截断奇异值分解)', value: 'truncatedSVD' }, | |||||
| ]; | ]; | ||||
| @@ -169,16 +169,16 @@ function ExperimentList({ type }: ExperimentListProps) { | |||||
| const handleMessage = (e: MessageEvent) => { | const handleMessage = (e: MessageEvent) => { | ||||
| const { type, payload } = e.data; | const { type, payload } = e.data; | ||||
| if (type === ExperimentCompleted) { | if (type === ExperimentCompleted) { | ||||
| const { experimentId, experimentInsId, status, finishTime } = payload; | |||||
| const { experimentId, experimentInsId, status /*finishTime*/ } = payload; | |||||
| const currentIns = experimentInsList.find((v) => v.id === experimentInsId); | const currentIns = experimentInsList.find((v) => v.id === experimentInsId); | ||||
| console.log( | |||||
| '实验实例状态变化', | |||||
| currentIns?.status, | |||||
| status, | |||||
| experimentId, | |||||
| experimentInsId, | |||||
| finishTime, | |||||
| ); | |||||
| // console.log( | |||||
| // '实验实例状态变化', | |||||
| // currentIns?.status, | |||||
| // status, | |||||
| // experimentId, | |||||
| // experimentInsId, | |||||
| // finishTime, | |||||
| // ); | |||||
| if ( | if ( | ||||
| !currentIns || | !currentIns || | ||||
| @@ -1,30 +1,8 @@ | |||||
| import { getAccessToken } from '@/access'; | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import KFModal from '@/components/KFModal'; | 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 { addDataset } from '@/services/dataset/index.js'; | ||||
| import { to } from '@/utils/promise'; | 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'> { | interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> { | ||||
| typeList: CategoryData[]; | typeList: CategoryData[]; | ||||
| @@ -33,20 +11,6 @@ interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| } | } | ||||
| function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalProps) { | 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 createDataset = async (params: any) => { | ||||
| const [res] = await to(addDataset(params)); | const [res] = await to(addDataset(params)); | ||||
| @@ -58,22 +22,11 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||||
| // 提交 | // 提交 | ||||
| const onFinish = (formData: any) => { | 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 ( | return ( | ||||
| @@ -108,32 +61,6 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||||
| > | > | ||||
| <Input placeholder="请输入数据名称" showCount allowClear maxLength={40} /> | <Input placeholder="请输入数据名称" showCount allowClear maxLength={40} /> | ||||
| </Form.Item> | </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"> | <Form.Item label="数据集分类" name="data_type"> | ||||
| <Select | <Select | ||||
| allowClear | allowClear | ||||
| @@ -172,24 +99,6 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||||
| allowClear | allowClear | ||||
| /> | /> | ||||
| </Form.Item> | </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 | <Form.Item | ||||
| label="可见性" | label="可见性" | ||||
| name="is_public" | name="is_public" | ||||
| @@ -200,29 +109,6 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||||
| <Radio value={true}>公开</Radio> | <Radio value={true}>公开</Radio> | ||||
| </Radio.Group> | </Radio.Group> | ||||
| </Form.Item> | </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> | </Form> | ||||
| </KFModal> | </KFModal> | ||||
| ); | ); | ||||
| @@ -1,25 +1,8 @@ | |||||
| import { getAccessToken } from '@/access'; | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import KFModal from '@/components/KFModal'; | 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 { addModel } from '@/services/dataset/index.js'; | ||||
| import { to } from '@/utils/promise'; | 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'> { | interface AddModelModalProps extends Omit<ModalProps, 'onOk'> { | ||||
| typeList: CategoryData[]; | typeList: CategoryData[]; | ||||
| @@ -28,18 +11,6 @@ interface AddModelModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| } | } | ||||
| function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) { | 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 createModel = async (params: any) => { | ||||
| const [res] = await to(addModel(params)); | const [res] = await to(addModel(params)); | ||||
| @@ -51,22 +22,11 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||||
| // 提交 | // 提交 | ||||
| const onFinish = (formData: any) => { | 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 ( | return ( | ||||
| @@ -99,32 +59,6 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||||
| > | > | ||||
| <Input placeholder="请输入模型名称" showCount allowClear maxLength={40} /> | <Input placeholder="请输入模型名称" showCount allowClear maxLength={40} /> | ||||
| </Form.Item> | </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"> | <Form.Item label="模型框架" name="model_type"> | ||||
| <Select | <Select | ||||
| allowClear | allowClear | ||||
| @@ -163,24 +97,6 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||||
| allowClear | allowClear | ||||
| /> | /> | ||||
| </Form.Item> | </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 | <Form.Item | ||||
| label="可见性" | label="可见性" | ||||
| name="is_public" | name="is_public" | ||||
| @@ -191,28 +107,6 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) | |||||
| <Radio value={true}>公开</Radio> | <Radio value={true}>公开</Radio> | ||||
| </Radio.Group> | </Radio.Group> | ||||
| </Form.Item> | </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> | </Form> | ||||
| </KFModal> | </KFModal> | ||||
| ); | ); | ||||
| @@ -15,7 +15,7 @@ import { | |||||
| type UploadProps, | type UploadProps, | ||||
| } from 'antd'; | } from 'antd'; | ||||
| import { omit } from 'lodash'; | import { omit } from 'lodash'; | ||||
| import { useState } from 'react'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import styles from '../AddDatasetModal/index.less'; | import styles from '../AddDatasetModal/index.less'; | ||||
| interface AddVersionModalProps extends Omit<ModalProps, 'onOk'> { | interface AddVersionModalProps extends Omit<ModalProps, 'onOk'> { | ||||
| @@ -40,6 +40,23 @@ function AddVersionModal({ | |||||
| }: AddVersionModalProps) { | }: AddVersionModalProps) { | ||||
| const [uuid] = useState(Date.now()); | const [uuid] = useState(Date.now()); | ||||
| const config = resourceConfig[resourceType]; | 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 = { | const uploadProps: UploadProps = { | ||||
| @@ -109,6 +126,7 @@ function AddVersionModal({ | |||||
| }} | }} | ||||
| onFinish={onFinish} | onFinish={onFinish} | ||||
| autoComplete="off" | autoComplete="off" | ||||
| form={form} | |||||
| > | > | ||||
| <Form.Item | <Form.Item | ||||
| label={`${name}名称`} | 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> | ||||
| <Form.Item | <Form.Item | ||||
| label="版本描述" | label="版本描述" | ||||
| @@ -0,0 +1,121 @@ | |||||
| import KFModal from '@/components/KFModal'; | |||||
| import { DataSource, ResourceData, ResourceType, resourceConfig } from '@/pages/Dataset/config'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { Form, Input, message, type ModalProps } from 'antd'; | |||||
| interface EditVersionModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| resourceType: ResourceType; | |||||
| resourceVersion: ResourceData; | |||||
| onOk: () => void; | |||||
| } | |||||
| function EditVersionModal({ resourceType, resourceVersion, onOk, ...rest }: EditVersionModalProps) { | |||||
| const config = resourceConfig[resourceType]; | |||||
| const { name: resoureName, version, version_desc } = resourceVersion; | |||||
| // 修改请求 | |||||
| const editDatasetVersion = async (params: any) => { | |||||
| const request = config.editVersion; | |||||
| const [res] = await to(request(params)); | |||||
| if (res) { | |||||
| message.success('编辑成功'); | |||||
| onOk?.(); | |||||
| } | |||||
| }; | |||||
| // 提交 | |||||
| const onFinish = (formData: any) => { | |||||
| const params = { | |||||
| ...resourceVersion, | |||||
| ...formData, | |||||
| [config.sourceParamKey]: DataSource.Create, | |||||
| }; | |||||
| editDatasetVersion(params); | |||||
| }; | |||||
| const name = config.name; | |||||
| return ( | |||||
| <KFModal | |||||
| {...rest} | |||||
| title="编辑版本" | |||||
| image={require('@/assets/img/create-experiment.png')} | |||||
| width={825} | |||||
| okButtonProps={{ | |||||
| htmlType: 'submit', | |||||
| form: 'form', | |||||
| }} | |||||
| > | |||||
| <Form | |||||
| name="form" | |||||
| layout="vertical" | |||||
| initialValues={{ | |||||
| name: resoureName, | |||||
| version: version, | |||||
| version_desc: version_desc, | |||||
| }} | |||||
| onFinish={onFinish} | |||||
| autoComplete="off" | |||||
| > | |||||
| <Form.Item | |||||
| label={`${name}名称`} | |||||
| name="name" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: `请输入${name}名称`, | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input disabled placeholder={`请输入${name}名称`} /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label={`${name}版本`} | |||||
| name="version" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: `请输入${name}版本`, | |||||
| }, | |||||
| { | |||||
| pattern: /^[a-zA-Z0-9._-]+$/, | |||||
| message: `${name}版本只支持字母、数字、点(.)、下划线(_)、中横线(-)`, | |||||
| }, | |||||
| { | |||||
| validator: (_rule, value) => { | |||||
| if (value === 'master') { | |||||
| return Promise.reject(`${name}版本不能为 master`); | |||||
| } else if (value === 'origin') { | |||||
| return Promise.reject(`${name}版本不能为 origin`); | |||||
| } | |||||
| return Promise.resolve(); | |||||
| }, | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input placeholder={`请输入${name}版本`} maxLength={64} showCount allowClear disabled /> | |||||
| </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> | |||||
| </KFModal> | |||||
| ); | |||||
| } | |||||
| export default EditVersionModal; | |||||
| @@ -28,8 +28,15 @@ | |||||
| border-radius: 4px; | border-radius: 4px; | ||||
| } | } | ||||
| &__desc { | |||||
| margin-bottom: 0 !important; | |||||
| color: @text-color; | |||||
| font-size: @font-size; | |||||
| } | |||||
| &__praise { | &__praise { | ||||
| display: flex; | display: flex; | ||||
| flex: none; | |||||
| align-items: center; | align-items: center; | ||||
| justify-content: center; | justify-content: center; | ||||
| width: 70px; | width: 70px; | ||||
| @@ -4,6 +4,7 @@ | |||||
| * @Description: 数据集、模型详情 | * @Description: 数据集、模型详情 | ||||
| */ | */ | ||||
| import KFEmpty, { EmptyType } from '@/components/KFEmpty'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { | import { | ||||
| ResourceData, | ResourceData, | ||||
| @@ -19,10 +20,11 @@ import { openAntdModal } from '@/utils/modal'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| import { useParams, useSearchParams } from '@umijs/max'; | 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 classNames from 'classnames'; | ||||
| import { useCallback, useEffect, useState } from 'react'; | import { useCallback, useEffect, useState } from 'react'; | ||||
| import AddVersionModal from '../AddVersionModal'; | import AddVersionModal from '../AddVersionModal'; | ||||
| import EditVersionModal from '../EditVersionModal'; | |||||
| import ResourceIntro from '../ResourceIntro'; | import ResourceIntro from '../ResourceIntro'; | ||||
| import ResourceVersion from '../ResourceVersion'; | import ResourceVersion from '../ResourceVersion'; | ||||
| import VersionCompareModal from '../VersionCompareModal'; | import VersionCompareModal from '../VersionCompareModal'; | ||||
| @@ -61,21 +63,24 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { | |||||
| const { message } = App.useApp(); | 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( | const getVersionList = useCallback( | ||||
| @@ -100,14 +105,15 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { | |||||
| } | } | ||||
| } else { | } else { | ||||
| setVersion(undefined); | setVersion(undefined); | ||||
| getResourceDetail(undefined); | |||||
| } | } | ||||
| }, | }, | ||||
| [config, owner, identifier, versionParam], | |||||
| [config, owner, identifier, versionParam, getResourceDetail], | |||||
| ); | ); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| if (version) { | if (version) { | ||||
| getResourceDetail(); | |||||
| getResourceDetail(version); | |||||
| } | } | ||||
| }, [version, getResourceDetail]); | }, [version, getResourceDetail]); | ||||
| @@ -116,7 +122,7 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { | |||||
| }, [getVersionList]); | }, [getVersionList]); | ||||
| // 新建版本 | // 新建版本 | ||||
| const showModal = () => { | |||||
| const showAddVersionModal = () => { | |||||
| const { close } = openAntdModal(AddVersionModal, { | const { close } = openAntdModal(AddVersionModal, { | ||||
| resourceType: resourceType, | resourceType: resourceType, | ||||
| resourceId: resourceId, | resourceId: resourceId, | ||||
| @@ -132,6 +138,18 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { | |||||
| }); | }); | ||||
| }; | }; | ||||
| // 版本编辑 | |||||
| const showEditVersionModal = () => { | |||||
| const { close } = openAntdModal(EditVersionModal, { | |||||
| resourceType: resourceType, | |||||
| resourceVersion: info, | |||||
| onOk: () => { | |||||
| getResourceDetail(); | |||||
| close(); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| // 选择版本 | // 选择版本 | ||||
| const showVersionSelector = () => { | const showVersionSelector = () => { | ||||
| const { close } = openAntdModal(VersionSelectorModal, { | const { close } = openAntdModal(VersionSelectorModal, { | ||||
| @@ -278,44 +296,70 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { | |||||
| <span>{info.praises_count}</span> | <span>{info.praises_count}</span> | ||||
| </div> | </div> | ||||
| </Flex> | </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-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> | ||||
| <div className={styles['resource-info__bottom']}> | <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> | ||||
| </div> | </div> | ||||
| ); | ); | ||||
| @@ -9,11 +9,15 @@ import { | |||||
| deleteDatasetVersion, | deleteDatasetVersion, | ||||
| deleteModel, | deleteModel, | ||||
| deleteModelVersion, | deleteModelVersion, | ||||
| editDatasetVersion, | |||||
| editModelVersion, | |||||
| getDatasetInfo, | getDatasetInfo, | ||||
| getDatasetList, | getDatasetList, | ||||
| getDatasetNextVersionReq, | |||||
| getDatasetVersionList, | getDatasetVersionList, | ||||
| getModelInfo, | getModelInfo, | ||||
| getModelList, | getModelList, | ||||
| getModelNextVersionReq, | |||||
| getModelVersionList, | getModelVersionList, | ||||
| } from '@/services/dataset/index.js'; | } from '@/services/dataset/index.js'; | ||||
| import { limitUploadFileType } from '@/utils/ui'; | import { limitUploadFileType } from '@/utils/ui'; | ||||
| @@ -36,9 +40,11 @@ type ResourceTypeInfo = { | |||||
| getVersions: (params: any) => Promise<any>; // 获取版本列表 | getVersions: (params: any) => Promise<any>; // 获取版本列表 | ||||
| deleteRecord: (params: any) => Promise<any>; // 删除 | deleteRecord: (params: any) => Promise<any>; // 删除 | ||||
| addVersion: (params: any) => Promise<any>; // 新增版本 | addVersion: (params: any) => Promise<any>; // 新增版本 | ||||
| editVersion: (params: any) => Promise<any>; // 编辑版本 | |||||
| deleteVersion: (params: any) => Promise<any>; // 删除版本 | deleteVersion: (params: any) => Promise<any>; // 删除版本 | ||||
| getInfo: (params: any) => Promise<any>; // 获取详情 | getInfo: (params: any) => Promise<any>; // 获取详情 | ||||
| compareVersion: (params: any) => Promise<any>; // 版本对比 | compareVersion: (params: any) => Promise<any>; // 版本对比 | ||||
| getNextVersion: (params: any) => Promise<any>; // 获取下一个版本 | |||||
| name: string; // 名称 | name: string; // 名称 | ||||
| typeParamKey: 'data_type' | 'model_type'; // 类型参数名称,获取资源列表接口使用 | typeParamKey: 'data_type' | 'model_type'; // 类型参数名称,获取资源列表接口使用 | ||||
| tagParamKey: 'data_tag' | 'model_tag'; // 标签参数名称,获取资源列表接口使用 | tagParamKey: 'data_tag' | 'model_tag'; // 标签参数名称,获取资源列表接口使用 | ||||
| @@ -65,9 +71,11 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | |||||
| getVersions: getDatasetVersionList, | getVersions: getDatasetVersionList, | ||||
| deleteRecord: deleteDataset, | deleteRecord: deleteDataset, | ||||
| addVersion: addDatasetVersion, | addVersion: addDatasetVersion, | ||||
| editVersion: editDatasetVersion, | |||||
| deleteVersion: deleteDatasetVersion, | deleteVersion: deleteDatasetVersion, | ||||
| getInfo: getDatasetInfo, | getInfo: getDatasetInfo, | ||||
| compareVersion: compareDatasetVersion, | compareVersion: compareDatasetVersion, | ||||
| getNextVersion: getDatasetNextVersionReq, | |||||
| name: '数据集', | name: '数据集', | ||||
| typeParamKey: 'data_type', | typeParamKey: 'data_type', | ||||
| tagParamKey: 'data_tag', | tagParamKey: 'data_tag', | ||||
| @@ -103,9 +111,11 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | |||||
| getVersions: getModelVersionList, | getVersions: getModelVersionList, | ||||
| deleteRecord: deleteModel, | deleteRecord: deleteModel, | ||||
| addVersion: addModelVersion, | addVersion: addModelVersion, | ||||
| editVersion: editModelVersion, | |||||
| deleteVersion: deleteModelVersion, | deleteVersion: deleteModelVersion, | ||||
| getInfo: getModelInfo, | getInfo: getModelInfo, | ||||
| compareVersion: compareModelVersion, | compareVersion: compareModelVersion, | ||||
| getNextVersion: getModelNextVersionReq, | |||||
| name: '模型', | name: '模型', | ||||
| typeParamKey: 'model_type', | typeParamKey: 'model_type', | ||||
| tagParamKey: 'model_tag', | tagParamKey: 'model_tag', | ||||
| @@ -4,10 +4,11 @@ | |||||
| * @Description: 开发环境列表 | * @Description: 开发环境列表 | ||||
| */ | */ | ||||
| import { CodeConfigData } from '@/components/CodeSelectorModal'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { DevEditorStatus } from '@/enums'; | import { DevEditorStatus } from '@/enums'; | ||||
| import { useCacheState } from '@/hooks/useCacheState'; | import { useCacheState } from '@/hooks/useCacheState'; | ||||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||||
| import { useSystemResource } from '@/hooks/useComputingResource'; | |||||
| import { DatasetData, ModelData } from '@/pages/Dataset/config'; | import { DatasetData, ModelData } from '@/pages/Dataset/config'; | ||||
| import { | import { | ||||
| deleteEditorReq, | deleteEditorReq, | ||||
| @@ -17,12 +18,7 @@ import { | |||||
| } from '@/services/developmentEnvironment'; | } from '@/services/developmentEnvironment'; | ||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { parseJsonText } from '@/utils'; | import { parseJsonText } from '@/utils'; | ||||
| import { | |||||
| formatCodeConfig, | |||||
| formatDataset, | |||||
| formatModel, | |||||
| type SelectedCodeConfig, | |||||
| } from '@/utils/format'; | |||||
| import { formatCodeConfig, formatDataset, formatModel } from '@/utils/format'; | |||||
| import { openAntdModal } from '@/utils/modal'; | import { openAntdModal } from '@/utils/modal'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import SessionStorage from '@/utils/sessionStorage'; | import SessionStorage from '@/utils/sessionStorage'; | ||||
| @@ -55,7 +51,7 @@ export type EditorData = { | |||||
| dataset?: string | DatasetData; | dataset?: string | DatasetData; | ||||
| model?: string | ModelData; | model?: string | ModelData; | ||||
| image?: string; | image?: string; | ||||
| code_config?: string | SelectedCodeConfig; | |||||
| code_config?: string | CodeConfigData; | |||||
| }; | }; | ||||
| function EditorList() { | function EditorList() { | ||||
| @@ -70,7 +66,7 @@ function EditorList() { | |||||
| pageSize: 10, | pageSize: 10, | ||||
| }, | }, | ||||
| ); | ); | ||||
| const getResourceDescription = useComputingResource()[1]; | |||||
| const getResourceDescription = useSystemResource(); | |||||
| // 获取编辑器列表 | // 获取编辑器列表 | ||||
| const getEditorList = useCallback(async () => { | const getEditorList = useCallback(async () => { | ||||
| @@ -211,7 +207,7 @@ function EditorList() { | |||||
| const gotoCodeConfig = (record: EditorData, e: React.MouseEvent) => { | const gotoCodeConfig = (record: EditorData, e: React.MouseEvent) => { | ||||
| e.stopPropagation(); | e.stopPropagation(); | ||||
| const codeConfig = record.code_config as SelectedCodeConfig; | |||||
| const codeConfig = record.code_config as CodeConfigData; | |||||
| const url = formatCodeConfig(codeConfig)?.url; | const url = formatCodeConfig(codeConfig)?.url; | ||||
| if (url) { | if (url) { | ||||
| window.open(url, '_blank'); | window.open(url, '_blank'); | ||||
| @@ -95,16 +95,13 @@ function ExperimentText() { | |||||
| return; | return; | ||||
| } | } | ||||
| const workflow = parseJsonText(dag); | |||||
| const workflow = dag; | |||||
| const experimentStatusObjs = parseJsonText(nodes_status); | const experimentStatusObjs = parseJsonText(nodes_status); | ||||
| if (!workflow || !workflow.nodes) { | if (!workflow || !workflow.nodes) { | ||||
| return; | return; | ||||
| } | } | ||||
| workflow.nodes.forEach((item) => { | workflow.nodes.forEach((item) => { | ||||
| item.in_parameters = parseJsonText(item.in_parameters); | |||||
| item.out_parameters = parseJsonText(item.out_parameters); | |||||
| item.control_strategy = parseJsonText(item.control_strategy); | |||||
| item.imgName = item.img.slice(0, item.img.length - 4); | item.imgName = item.img.slice(0, item.img.length - 4); | ||||
| }); | }); | ||||
| workflowRef.current = workflow; | workflowRef.current = workflow; | ||||
| @@ -140,8 +137,11 @@ function ExperimentText() { | |||||
| } else if (status === ExperimentStatus.Running) { | } else if (status === ExperimentStatus.Running) { | ||||
| // 如果状态是 Running,打开第一个 Running 或者 pending 的节点,如果没有,则打开第一个节点 | // 如果状态是 Running,打开第一个 Running 或者 pending 的节点,如果没有,则打开第一个节点 | ||||
| const node = | const node = | ||||
| workflow.nodes.find((item) => item.experimentStatus === ExperimentStatus.Running || item.experimentStatus === ExperimentStatus.Pending) ?? | |||||
| workflow.nodes[0]; | |||||
| workflow.nodes.find( | |||||
| (item) => | |||||
| item.experimentStatus === ExperimentStatus.Running || | |||||
| item.experimentStatus === ExperimentStatus.Pending, | |||||
| ) ?? workflow.nodes[0]; | |||||
| if (node) { | if (node) { | ||||
| setExperimentNodeData(node); | setExperimentNodeData(node); | ||||
| openPropsDrawer(); | openPropsDrawer(); | ||||
| @@ -567,12 +567,13 @@ function ExperimentText() { | |||||
| instanceNodeStatus={experimentNodeData.experimentStatus} | instanceNodeStatus={experimentNodeData.experimentStatus} | ||||
| instanceNodeStartTime={experimentNodeData.experimentStartTime} | instanceNodeStartTime={experimentNodeData.experimentStartTime} | ||||
| instanceNodeEndTime={experimentNodeData.experimentEndTime} | instanceNodeEndTime={experimentNodeData.experimentEndTime} | ||||
| globalParams={experimentIns?.global_param} | |||||
| ></ExperimentDrawer> | ></ExperimentDrawer> | ||||
| ) : null} | ) : null} | ||||
| <ParamsModal | <ParamsModal | ||||
| open={paramsModalOpen} | open={paramsModalOpen} | ||||
| onCancel={closeParamsModal} | onCancel={closeParamsModal} | ||||
| globalParam={experimentIns?.global_param} | |||||
| globalParams={experimentIns?.global_param} | |||||
| ></ParamsModal> | ></ParamsModal> | ||||
| </div> | </div> | ||||
| ); | ); | ||||
| @@ -6,4 +6,10 @@ | |||||
| border: 1px solid #e6e6e6; | border: 1px solid #e6e6e6; | ||||
| border-radius: 6px; | border-radius: 6px; | ||||
| } | } | ||||
| :global { | |||||
| .ant-form-item-row { | |||||
| align-items: center; | |||||
| } | |||||
| } | |||||
| } | } | ||||
| @@ -1,7 +1,7 @@ | |||||
| import createExperimentIcon from '@/assets/img/create-experiment.png'; | import createExperimentIcon from '@/assets/img/create-experiment.png'; | ||||
| import editExperimentIcon from '@/assets/img/edit-experiment.png'; | import editExperimentIcon from '@/assets/img/edit-experiment.png'; | ||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { type PipelineGlobalParam } from '@/types'; | |||||
| import { PipelineGlobalParamType, type PipelineGlobalParam } from '@/types'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { Button, Form, Input, Radio, Select, Typography, type FormRule } from 'antd'; | import { Button, Form, Input, Radio, Select, Typography, type FormRule } from 'antd'; | ||||
| import { useState } from 'react'; | import { useState } from 'react'; | ||||
| @@ -32,7 +32,7 @@ interface Workflow { | |||||
| // 根据参数设置输入组件 | // 根据参数设置输入组件 | ||||
| export const getParamComponent = (paramType: number): JSX.Element => { | export const getParamComponent = (paramType: number): JSX.Element => { | ||||
| // 防止后台返回不是 number 类型 | // 防止后台返回不是 number 类型 | ||||
| if (Number(paramType) === 3) { | |||||
| if (Number(paramType) === PipelineGlobalParamType.Boolean) { | |||||
| return ( | return ( | ||||
| <Radio.Group> | <Radio.Group> | ||||
| <Radio value={1}>是</Radio> | <Radio value={1}>是</Radio> | ||||
| @@ -50,7 +50,7 @@ export const getParamComponent = (paramType: number): JSX.Element => { | |||||
| export const getParamRules = (paramType: number, required: boolean = false): FormRule[] => { | export const getParamRules = (paramType: number, required: boolean = false): FormRule[] => { | ||||
| const rules = []; | const rules = []; | ||||
| // 防止后台返回不是 number 类型 | // 防止后台返回不是 number 类型 | ||||
| if (Number(paramType) === 2) { | |||||
| if (Number(paramType) === PipelineGlobalParamType.Number) { | |||||
| rules.push({ | rules.push({ | ||||
| pattern: /^-?((0(\.0*[1-9]\d*)?)|([1-9]\d*(\.\d+)?))$/, | pattern: /^-?((0(\.0*[1-9]\d*)?)|([1-9]\d*(\.\d+)?))$/, | ||||
| message: '整型必须是数字', | message: '整型必须是数字', | ||||
| @@ -64,10 +64,10 @@ export const getParamRules = (paramType: number, required: boolean = false): For | |||||
| // 根据参数设置 label | // 根据参数设置 label | ||||
| export const getParamLabel = (param: PipelineGlobalParam): React.ReactNode => { | export const getParamLabel = (param: PipelineGlobalParam): React.ReactNode => { | ||||
| const paramTypes: Readonly<Record<number, string>> = { | |||||
| 1: '字符串', | |||||
| 2: '整型', | |||||
| 3: '布尔类型', | |||||
| const paramTypes: Readonly<Record<PipelineGlobalParamType, string>> = { | |||||
| [PipelineGlobalParamType.String]: '字符串', | |||||
| [PipelineGlobalParamType.Number]: '整型', | |||||
| [PipelineGlobalParamType.Boolean]: '布尔类型', | |||||
| }; | }; | ||||
| const label = param.param_name + `(${paramTypes[param.param_type]})`; | const label = param.param_name + `(${paramTypes[param.param_type]})`; | ||||
| return <Typography.Text ellipsis={{ tooltip: label }}>{label}</Typography.Text>; | return <Typography.Text ellipsis={{ tooltip: label }}>{label}</Typography.Text>; | ||||
| @@ -1,7 +1,7 @@ | |||||
| import RunDuration from '@/components/RunDuration'; | import RunDuration from '@/components/RunDuration'; | ||||
| import { ExperimentStatus } from '@/enums'; | import { ExperimentStatus } from '@/enums'; | ||||
| import { experimentStatusInfo } from '@/pages/Experiment/status'; | import { experimentStatusInfo } from '@/pages/Experiment/status'; | ||||
| import { PipelineNodeModelSerialize } from '@/types'; | |||||
| import { PipelineNodeModelSerialize, type PipelineGlobalParam } from '@/types'; | |||||
| import { formatDate } from '@/utils/date'; | import { formatDate } from '@/utils/date'; | ||||
| import { CloseOutlined, DatabaseOutlined, ProfileOutlined } from '@ant-design/icons'; | import { CloseOutlined, DatabaseOutlined, ProfileOutlined } from '@ant-design/icons'; | ||||
| import { Drawer, Tabs, Typography } from 'antd'; | import { Drawer, Tabs, Typography } from 'antd'; | ||||
| @@ -25,6 +25,7 @@ type ExperimentDrawerProps = { | |||||
| instanceNodeStatus?: ExperimentStatus; // 实例节点状态 | instanceNodeStatus?: ExperimentStatus; // 实例节点状态 | ||||
| instanceNodeStartTime?: string; // 开始时间 | instanceNodeStartTime?: string; // 开始时间 | ||||
| instanceNodeEndTime?: string; // 在定时刷新实验实例状态中,会经常变化 | instanceNodeEndTime?: string; // 在定时刷新实验实例状态中,会经常变化 | ||||
| globalParams?: PipelineGlobalParam[] | null; // 全局参数 | |||||
| }; | }; | ||||
| const ExperimentDrawer = ({ | const ExperimentDrawer = ({ | ||||
| @@ -41,6 +42,7 @@ const ExperimentDrawer = ({ | |||||
| instanceNodeStatus, | instanceNodeStatus, | ||||
| instanceNodeStartTime, | instanceNodeStartTime, | ||||
| instanceNodeEndTime, | instanceNodeEndTime, | ||||
| globalParams, | |||||
| }: ExperimentDrawerProps) => { | }: ExperimentDrawerProps) => { | ||||
| // 如果性能有问题,可以进一步拆解 | // 如果性能有问题,可以进一步拆解 | ||||
| const items = useMemo( | const items = useMemo( | ||||
| @@ -66,7 +68,7 @@ const ExperimentDrawer = ({ | |||||
| key: '2', | key: '2', | ||||
| label: '配置参数', | label: '配置参数', | ||||
| icon: <DatabaseOutlined />, | icon: <DatabaseOutlined />, | ||||
| children: <ExperimentParameter nodeData={instanceNodeData} />, | |||||
| children: <ExperimentParameter nodeData={instanceNodeData} globalParams={globalParams} />, | |||||
| }, | }, | ||||
| { | { | ||||
| key: '3', | key: '3', | ||||
| @@ -94,6 +96,7 @@ const ExperimentDrawer = ({ | |||||
| experimentName, | experimentName, | ||||
| experimentId, | experimentId, | ||||
| pipelineId, | pipelineId, | ||||
| globalParams, | |||||
| ], | ], | ||||
| ); | ); | ||||
| @@ -15,4 +15,24 @@ | |||||
| font-size: @font-size; | font-size: @font-size; | ||||
| background: #f8fbff; | background: #f8fbff; | ||||
| } | } | ||||
| &__form-list { | |||||
| :global { | |||||
| .ant-row { | |||||
| padding: 0 !important; | |||||
| } | |||||
| } | |||||
| &:last-child { | |||||
| :global { | |||||
| .ant-form-item { | |||||
| margin-bottom: 0 !important; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| &__list-empty { | |||||
| color: @text-color-tertiary; | |||||
| } | |||||
| } | } | ||||
| @@ -1,25 +1,92 @@ | |||||
| import FormInfo from '@/components/FormInfo'; | import FormInfo from '@/components/FormInfo'; | ||||
| import ParameterSelect from '@/components/ParameterSelect'; | |||||
| import ParameterSelect, { type ParameterSelectDataType } from '@/components/ParameterSelect'; | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import { PipelineNodeModelSerialize } from '@/types'; | |||||
| import { Form } from 'antd'; | |||||
| import { ComponentType } from '@/enums'; | |||||
| import type { | |||||
| PipelineGlobalParam, | |||||
| PipelineNodeModelParameter, | |||||
| PipelineNodeModelSerialize, | |||||
| } from '@/types'; | |||||
| import { Flex, Form } from 'antd'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type ExperimentParameterProps = { | type ExperimentParameterProps = { | ||||
| nodeData: PipelineNodeModelSerialize; | nodeData: PipelineNodeModelSerialize; | ||||
| globalParams?: PipelineGlobalParam[] | null; // 全局参数 | |||||
| }; | }; | ||||
| function ExperimentParameter({ nodeData }: ExperimentParameterProps) { | |||||
| function ExperimentParameter({ nodeData, globalParams }: ExperimentParameterProps) { | |||||
| // 表单组件 | |||||
| const getFormComponent = ( | |||||
| item: { key: string; value: PipelineNodeModelParameter }, | |||||
| parentName: string, | |||||
| ) => { | |||||
| return ( | |||||
| <Form.Item | |||||
| key={item.key} | |||||
| name={[parentName, item.key]} | |||||
| label={item.value.label + '(' + item.key + ')'} | |||||
| rules={[{ required: item.value.require ? true : false }]} | |||||
| > | |||||
| {item.value.type === ComponentType.Map && ( | |||||
| <Form.List name={[parentName, item.key, 'value']}> | |||||
| {(fields) => ( | |||||
| <> | |||||
| {fields.length > 0 ? ( | |||||
| fields.map(({ key, name, ...restField }) => ( | |||||
| <Flex | |||||
| key={key} | |||||
| gap="0 8px" | |||||
| style={{ width: '100%' }} | |||||
| className={styles['experiment-parameter__form-list']} | |||||
| > | |||||
| <Form.Item | |||||
| {...restField} | |||||
| name={[name, 'name']} | |||||
| style={{ flex: 1, minWidth: 0 }} | |||||
| > | |||||
| <FormInfo /> | |||||
| </Form.Item> | |||||
| <span style={{ lineHeight: '32px' }}>=</span> | |||||
| <Form.Item | |||||
| {...restField} | |||||
| name={[name, 'value']} | |||||
| style={{ flex: 1, minWidth: 0 }} | |||||
| > | |||||
| <FormInfo valuePropName="showValue" globalParams={globalParams} /> | |||||
| </Form.Item> | |||||
| </Flex> | |||||
| )) | |||||
| ) : ( | |||||
| <div className={styles['experiment-parameter__list-empty']}>无</div> | |||||
| )} | |||||
| </> | |||||
| )} | |||||
| </Form.List> | |||||
| )} | |||||
| {item.value.type === ComponentType.Select && | |||||
| (['dataset', 'model', 'service', 'resource'].includes(item.value.item_type) ? ( | |||||
| <ParameterSelect dataType={item.value.item_type as ParameterSelectDataType} display /> | |||||
| ) : null)} | |||||
| {item.value.type !== ComponentType.Map && item.value.type !== ComponentType.Select && ( | |||||
| <FormInfo valuePropName="showValue" globalParams={globalParams} /> | |||||
| )} | |||||
| </Form.Item> | |||||
| ); | |||||
| }; | |||||
| // 基本参数 | |||||
| const basicParametersList = Object.entries(nodeData.task_info ?? {}) | |||||
| .map(([key, value]) => ({ | |||||
| key, | |||||
| value, | |||||
| })) | |||||
| .filter((v) => v.value.visible === true); | |||||
| // 控制策略 | // 控制策略 | ||||
| // const controlStrategyList = Object.entries(nodeData.control_strategy ?? {}).map( | |||||
| // ([key, value]) => ({ key, value }), | |||||
| // ); | |||||
| const nodeId = nodeData.id; | |||||
| const hasTaskInfo = | |||||
| nodeId && | |||||
| !nodeId.startsWith('git-clone') && | |||||
| !nodeId.startsWith('dataset-export') && | |||||
| !nodeId.startsWith('model-export'); | |||||
| const controlStrategyList = Object.entries(nodeData.control_strategy ?? {}) | |||||
| .map(([key, value]) => ({ key, value })) | |||||
| .filter((v) => v.value.visible === true); | |||||
| // 输入参数 | // 输入参数 | ||||
| const inParametersList = Object.entries(nodeData.in_parameters ?? {}).map(([key, value]) => ({ | const inParametersList = Object.entries(nodeData.in_parameters ?? {}).map(([key, value]) => ({ | ||||
| @@ -80,96 +147,56 @@ function ExperimentParameter({ nodeData }: ExperimentParameterProps) { | |||||
| > | > | ||||
| <FormInfo /> | <FormInfo /> | ||||
| </Form.Item> | </Form.Item> | ||||
| {hasTaskInfo && ( | |||||
| {basicParametersList.length + controlStrategyList.length > 0 && ( | |||||
| <div className={styles['experiment-parameter__title']}> | |||||
| <SubAreaTitle | |||||
| image={require('@/assets/img/duty-message.png')} | |||||
| title="任务信息" | |||||
| ></SubAreaTitle> | |||||
| </div> | |||||
| )} | |||||
| {/* 基本参数 */} | |||||
| {basicParametersList.map((item) => getFormComponent(item, 'task_info'))} | |||||
| {/* 控制参数 */} | |||||
| {controlStrategyList.map((item) => getFormComponent(item, 'control_strategy'))} | |||||
| {/* 输入参数 */} | |||||
| {inParametersList.length > 0 && ( | |||||
| <> | <> | ||||
| <div className={styles['experiment-parameter__title']}> | <div className={styles['experiment-parameter__title']}> | ||||
| <SubAreaTitle | <SubAreaTitle | ||||
| image={require('@/assets/img/duty-message.png')} | image={require('@/assets/img/duty-message.png')} | ||||
| title="任务信息" | |||||
| title="输入参数" | |||||
| ></SubAreaTitle> | ></SubAreaTitle> | ||||
| </div> | </div> | ||||
| <Form.Item | |||||
| label="镜像" | |||||
| name="image" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入镜像', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <FormInfo /> | |||||
| </Form.Item> | |||||
| <Form.Item label="工作目录" name="working_directory"> | |||||
| <FormInfo /> | |||||
| </Form.Item> | |||||
| {inParametersList.map((item) => getFormComponent(item, 'in_parameters'))} | |||||
| </> | |||||
| )} | |||||
| <Form.Item label="启动命令" name="command"> | |||||
| <FormInfo textArea /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="资源规格" | |||||
| name="resources_standard" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入资源规格', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <ParameterSelect dataType="resource" placeholder="请选择资源规格" display /> | |||||
| </Form.Item> | |||||
| {/* <Form.Item label="挂载路径" name="mount_path"> | |||||
| <FormInfo /> | |||||
| </Form.Item> */} | |||||
| <Form.Item label="环境变量" name="env_variables"> | |||||
| <FormInfo textArea /> | |||||
| </Form.Item> | |||||
| {/* {controlStrategyList.map((item) => ( | |||||
| <Form.Item key={item.key} name={['control_strategy', item.key]} label={item.value.label}> | |||||
| <FormInfo valuePropName="showValue" /> | |||||
| </Form.Item> | |||||
| ))} */} | |||||
| {/* 输出参数 */} | |||||
| {outParametersList.length > 0 && ( | |||||
| <> | |||||
| <div className={styles['experiment-parameter__title']}> | |||||
| <SubAreaTitle | |||||
| image={require('@/assets/img/duty-message.png')} | |||||
| title="输出参数" | |||||
| ></SubAreaTitle> | |||||
| </div> | |||||
| {outParametersList.map((item) => ( | |||||
| <Form.Item | |||||
| key={item.key} | |||||
| name={['out_parameters', item.key]} | |||||
| label={item.value.label + '(' + item.key + ')'} | |||||
| rules={[{ required: item.value.require ? true : false }]} | |||||
| > | |||||
| <FormInfo valuePropName="showValue" /> | |||||
| </Form.Item> | |||||
| ))} | |||||
| </> | </> | ||||
| )} | )} | ||||
| <div className={styles['experiment-parameter__title']}> | |||||
| <SubAreaTitle | |||||
| image={require('@/assets/img/duty-message.png')} | |||||
| title="输入参数" | |||||
| ></SubAreaTitle> | |||||
| </div> | |||||
| {inParametersList.map((item) => ( | |||||
| <Form.Item | |||||
| key={item.key} | |||||
| name={['in_parameters', item.key]} | |||||
| label={item.value.label + '(' + item.key + ')'} | |||||
| rules={[{ required: item.value.require ? true : false }]} | |||||
| > | |||||
| {item.value.type === 'select' ? ( | |||||
| ['dataset', 'model', 'service', 'resource'].includes(item.value.item_type) ? ( | |||||
| <ParameterSelect dataType={item.value.item_type as any} display /> | |||||
| ) : null | |||||
| ) : ( | |||||
| <FormInfo valuePropName="showValue" /> | |||||
| )} | |||||
| </Form.Item> | |||||
| ))} | |||||
| <div className={styles['experiment-parameter__title']}> | |||||
| <SubAreaTitle | |||||
| image={require('@/assets/img/duty-message.png')} | |||||
| title="输出参数" | |||||
| ></SubAreaTitle> | |||||
| </div> | |||||
| {outParametersList.map((item) => ( | |||||
| <Form.Item | |||||
| key={item.key} | |||||
| name={['out_parameters', item.key]} | |||||
| label={item.value.label + '(' + item.key + ')'} | |||||
| rules={[{ required: item.value.require ? true : false }]} | |||||
| > | |||||
| <FormInfo valuePropName="showValue" /> | |||||
| </Form.Item> | |||||
| ))} | |||||
| </Form> | </Form> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -3,12 +3,10 @@ import KFModal from '@/components/KFModal'; | |||||
| import { | import { | ||||
| DataSource, | DataSource, | ||||
| ResourceType, | ResourceType, | ||||
| ResourceVersionData, | |||||
| resourceConfig, | resourceConfig, | ||||
| type ResourceData, | type ResourceData, | ||||
| } from '@/pages/Dataset/config'; | } from '@/pages/Dataset/config'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { InfoCircleOutlined } from '@ant-design/icons'; | |||||
| import { Form, Input, ModalProps, Select } from 'antd'; | import { Form, Input, ModalProps, Select } from 'antd'; | ||||
| import { pick } from 'lodash'; | import { pick } from 'lodash'; | ||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| @@ -44,7 +42,6 @@ function ExportModelModal({ | |||||
| }: ExportModelModalProps) { | }: ExportModelModalProps) { | ||||
| const [form] = Form.useForm(); | const [form] = Form.useForm(); | ||||
| const [resources, setResources] = useState<ResourceData[]>([]); | const [resources, setResources] = useState<ResourceData[]>([]); | ||||
| const [versions, setVersions] = useState<ResourceVersionData[]>([]); | |||||
| const config = resourceConfig[resourceType]; | const config = resourceConfig[resourceType]; | ||||
| const layout = { | const layout = { | ||||
| @@ -77,35 +74,24 @@ function ExportModelModal({ | |||||
| return undefined; | 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) => { | const handleResourceChange = (id: number | undefined) => { | ||||
| if (id) { | if (id) { | ||||
| getRecourceVersions(id); | |||||
| getRecourceNextVersion(id); | |||||
| } else { | } else { | ||||
| setVersions([]); | |||||
| form.setFieldValue('version', ''); | |||||
| } | } | ||||
| }; | }; | ||||
| // 获取数据集、模型版本列表 | |||||
| const getRecourceVersions = async (id: number) => { | |||||
| // 获取数据集、模型下一个版本 | |||||
| const getRecourceNextVersion = async (id: number) => { | |||||
| const resource = getSelectedResource(id); | const resource = getSelectedResource(id); | ||||
| if (!resource) { | if (!resource) { | ||||
| return; | 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) { | if (res && res.data) { | ||||
| setVersions(res.data); | |||||
| form.setFieldValue('version', res.data); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -184,15 +170,6 @@ function ExportModelModal({ | |||||
| <Form.Item | <Form.Item | ||||
| label={`${config.name}版本`} | label={`${config.name}版本`} | ||||
| name="version" | name="version" | ||||
| tooltip={ | |||||
| getTooltip() | |||||
| ? { | |||||
| overlayClassName: styles['export-model-modal__tooltip'], | |||||
| title: getTooltip(), | |||||
| icon: <InfoCircleOutlined />, | |||||
| } | |||||
| : undefined | |||||
| } | |||||
| rules={[ | rules={[ | ||||
| { required: true, message: `请输入${config.name}版本` }, | { required: true, message: `请输入${config.name}版本` }, | ||||
| { | { | ||||
| @@ -205,8 +182,6 @@ function ExportModelModal({ | |||||
| return Promise.reject(`${config.name}版本不能为 master`); | return Promise.reject(`${config.name}版本不能为 master`); | ||||
| } else if (value === 'origin') { | } else if (value === 'origin') { | ||||
| return Promise.reject(`${config.name}版本不能为 origin`); | return Promise.reject(`${config.name}版本不能为 origin`); | ||||
| } else if (value && versions.map((item) => item.name).includes(value)) { | |||||
| return Promise.reject(`${config.name}版本已存在`); | |||||
| } else { | } else { | ||||
| return Promise.resolve(); | 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> | ||||
| <Form.Item | <Form.Item | ||||
| label="版本描述" | label="版本描述" | ||||
| @@ -4,6 +4,12 @@ | |||||
| overflow-y: auto; | overflow-y: auto; | ||||
| border: 1px solid #e6e6e6; | border: 1px solid #e6e6e6; | ||||
| border-radius: 8px; | border-radius: 8px; | ||||
| :global { | |||||
| .ant-form-item-row { | |||||
| align-items: center; | |||||
| } | |||||
| } | |||||
| } | } | ||||
| .params-empty { | .params-empty { | ||||
| :global { | :global { | ||||
| @@ -14,10 +14,10 @@ import styles from './index.less'; | |||||
| type ParamsModalProps = { | type ParamsModalProps = { | ||||
| open: boolean; | open: boolean; | ||||
| onCancel: () => void; | onCancel: () => void; | ||||
| globalParam?: PipelineGlobalParam[] | null; | |||||
| globalParams?: PipelineGlobalParam[] | null; | |||||
| }; | }; | ||||
| function ParamsModal({ open, onCancel, globalParam = [] }: ParamsModalProps) { | |||||
| function ParamsModal({ open, onCancel, globalParams = [] }: ParamsModalProps) { | |||||
| return ( | return ( | ||||
| <KFModal | <KFModal | ||||
| title="执行参数" | title="执行参数" | ||||
| @@ -28,13 +28,13 @@ function ParamsModal({ open, onCancel, globalParam = [] }: ParamsModalProps) { | |||||
| cancelButtonProps={{ style: { display: 'none' } }} | cancelButtonProps={{ style: { display: 'none' } }} | ||||
| width={825} | width={825} | ||||
| > | > | ||||
| {Array.isArray(globalParam) && globalParam.length > 0 ? ( | |||||
| {Array.isArray(globalParams) && globalParams.length > 0 ? ( | |||||
| <div className={styles['params-container']}> | <div className={styles['params-container']}> | ||||
| <Form | <Form | ||||
| name="view_params_form" | name="view_params_form" | ||||
| labelCol={{ span: 6 }} | labelCol={{ span: 6 }} | ||||
| wrapperCol={{ span: 18 }} | wrapperCol={{ span: 18 }} | ||||
| initialValues={{ global_param: globalParam }} | |||||
| initialValues={{ global_param: globalParams }} | |||||
| labelAlign="left" | labelAlign="left" | ||||
| disabled | disabled | ||||
| > | > | ||||
| @@ -45,9 +45,9 @@ function ParamsModal({ open, onCancel, globalParam = [] }: ParamsModalProps) { | |||||
| {...restField} | {...restField} | ||||
| key={key} | key={key} | ||||
| name={[name, 'param_value']} | name={[name, 'param_value']} | ||||
| label={getParamLabel(globalParam[name])} | |||||
| label={getParamLabel(globalParams[name])} | |||||
| > | > | ||||
| {getParamComponent(globalParam[name]['param_type'])} | |||||
| {getParamComponent(globalParams[name]['param_type'])} | |||||
| </Form.Item> | </Form.Item> | ||||
| )) | )) | ||||
| } | } | ||||
| @@ -226,14 +226,14 @@ function Experiment() { | |||||
| if (type === ExperimentCompleted) { | if (type === ExperimentCompleted) { | ||||
| const { experimentId, experimentInsId, status, finishTime } = payload; | const { experimentId, experimentInsId, status, finishTime } = payload; | ||||
| const currentIns = experimentInsList.find((v) => v.id === experimentInsId); | const currentIns = experimentInsList.find((v) => v.id === experimentInsId); | ||||
| console.log( | |||||
| '实验实例状态变化', | |||||
| currentIns?.status, | |||||
| status, | |||||
| experimentId, | |||||
| experimentInsId, | |||||
| finishTime, | |||||
| ); | |||||
| // console.log( | |||||
| // '实验实例状态变化', | |||||
| // currentIns?.status, | |||||
| // status, | |||||
| // experimentId, | |||||
| // experimentInsId, | |||||
| // finishTime, | |||||
| // ); | |||||
| if ( | if ( | ||||
| !currentIns || | !currentIns || | ||||
| @@ -323,6 +323,7 @@ function ExecuteConfig() { | |||||
| className={styles['hyper-parameter__body__name']} | className={styles['hyper-parameter__body__name']} | ||||
| {...restField} | {...restField} | ||||
| name={[name, 'name']} | name={[name, 'name']} | ||||
| dependencies={fields.map((_, i) => ['parameters', i, 'name'])} | |||||
| required | required | ||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| @@ -1,6 +1,6 @@ | |||||
| import ConfigInfo, { type BasicInfoData } from '@/components/ConfigInfo'; | import ConfigInfo, { type BasicInfoData } from '@/components/ConfigInfo'; | ||||
| import { ExperimentStatus, hyperParameterOptimizedMode } from '@/enums'; | import { ExperimentStatus, hyperParameterOptimizedMode } from '@/enums'; | ||||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||||
| import { useSystemResource } from '@/hooks/useComputingResource'; | |||||
| import ExperimentRunBasic from '@/pages/AutoML/components/ExperimentRunBasic'; | import ExperimentRunBasic from '@/pages/AutoML/components/ExperimentRunBasic'; | ||||
| import { | import { | ||||
| schedulerAlgorithms, | schedulerAlgorithms, | ||||
| @@ -41,7 +41,7 @@ function HyperParameterBasic({ | |||||
| instanceStatus, | instanceStatus, | ||||
| isInstance = false, | isInstance = false, | ||||
| }: HyperParameterBasicProps) { | }: HyperParameterBasicProps) { | ||||
| const getResourceDescription = useComputingResource()[1]; | |||||
| const getResourceDescription = useSystemResource(); | |||||
| const basicDatas: BasicInfoData[] = useMemo(() => { | const basicDatas: BasicInfoData[] = useMemo(() => { | ||||
| if (!info) { | if (!info) { | ||||
| @@ -46,6 +46,7 @@ export type MirrorInfoData = { | |||||
| }; | }; | ||||
| export type MirrorVersionData = { | export type MirrorVersionData = { | ||||
| image_id: number; | |||||
| id: number; | id: number; | ||||
| version: string; | version: string; | ||||
| url: string; | url: string; | ||||
| @@ -23,7 +23,7 @@ import { removeFormListItem } from '@/utils/ui'; | |||||
| import { MinusCircleOutlined, PlusCircleOutlined, PlusOutlined } from '@ant-design/icons'; | import { MinusCircleOutlined, PlusCircleOutlined, PlusOutlined } from '@ant-design/icons'; | ||||
| import { useNavigate, useParams } from '@umijs/max'; | import { useNavigate, useParams } from '@umijs/max'; | ||||
| import { App, Button, Col, Flex, Form, Input, InputNumber, Row } from 'antd'; | import { App, Button, Col, Flex, Form, Input, InputNumber, Row } from 'antd'; | ||||
| import { omit, pick } from 'lodash'; | |||||
| import { omit } from 'lodash'; | |||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import { CreateServiceVersionFrom, ServiceOperationType, ServiceVersionData } from '../types'; | import { CreateServiceVersionFrom, ServiceOperationType, ServiceVersionData } from '../types'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -79,7 +79,7 @@ function CreateServiceVersion() { | |||||
| if (res.model && typeof res.model === 'object') { | if (res.model && typeof res.model === 'object') { | ||||
| model = changePropertyName(res.model, { show_value: 'showValue' }); | model = changePropertyName(res.model, { show_value: 'showValue' }); | ||||
| // 接口返回是数据没有 value 值,但是 form 需要 value | // 接口返回是数据没有 value 值,但是 form 需要 value | ||||
| model.value = model.showValue; | |||||
| // model.value = model.showValue; | |||||
| } | } | ||||
| // 环境变量 | // 环境变量 | ||||
| if (res.env_variables && typeof res.env_variables === 'object') { | if (res.env_variables && typeof res.env_variables === 'object') { | ||||
| @@ -117,7 +117,6 @@ function CreateServiceVersion() { | |||||
| // 创建版本 | // 创建版本 | ||||
| const createServiceVersion = async (formData: FormData) => { | const createServiceVersion = async (formData: FormData) => { | ||||
| const envList = formData['env_variables']; | const envList = formData['env_variables']; | ||||
| const model = formData['model']; | |||||
| const envVariables = envList?.reduce((acc, cur) => { | const envVariables = envList?.reduce((acc, cur) => { | ||||
| acc[cur.key] = cur.value; | acc[cur.key] = cur.value; | ||||
| return acc; | return acc; | ||||
| @@ -125,13 +124,9 @@ function CreateServiceVersion() { | |||||
| // 根据后台要求,修改表单数据 | // 根据后台要求,修改表单数据 | ||||
| const object = { | const object = { | ||||
| ...omit(formData, ['replicas', 'env_variables', 'model']), | |||||
| ...omit(formData, ['replicas', 'env_variables']), | |||||
| replicas: Number(formData.replicas), | replicas: Number(formData.replicas), | ||||
| env_variables: envVariables, | env_variables: envVariables, | ||||
| model: changePropertyName( | |||||
| pick(model, ['id', 'name', 'version', 'path', 'identifier', 'owner', 'showValue']), | |||||
| { showValue: 'show_value' }, | |||||
| ), | |||||
| service_id: serviceId, | service_id: serviceId, | ||||
| }; | }; | ||||
| @@ -427,6 +422,7 @@ function CreateServiceVersion() { | |||||
| {...restField} | {...restField} | ||||
| name={[name, 'key']} | name={[name, 'key']} | ||||
| style={{ flex: 1 }} | style={{ flex: 1 }} | ||||
| dependencies={fields.map((_, i) => ['env_variables', i, 'key'])} | |||||
| rules={[ | rules={[ | ||||
| { | { | ||||
| validator: (_, value) => { | validator: (_, value) => { | ||||
| @@ -9,7 +9,8 @@ import PageTitle from '@/components/PageTitle'; | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import { ServiceRunStatus, serviceStatusOptions } from '@/enums'; | import { ServiceRunStatus, serviceStatusOptions } from '@/enums'; | ||||
| import { useCacheState } from '@/hooks/useCacheState'; | import { useCacheState } from '@/hooks/useCacheState'; | ||||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||||
| import { useSystemResource } from '@/hooks/useComputingResource'; | |||||
| import { ModelData } from '@/pages/Dataset/config'; | |||||
| import { | import { | ||||
| deleteServiceVersionReq, | deleteServiceVersionReq, | ||||
| getServiceInfoReq, | getServiceInfoReq, | ||||
| @@ -18,6 +19,7 @@ import { | |||||
| } from '@/services/modelDeployment'; | } from '@/services/modelDeployment'; | ||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { formatDate } from '@/utils/date'; | import { formatDate } from '@/utils/date'; | ||||
| import { formatModel } from '@/utils/format'; | |||||
| import { openAntdModal } from '@/utils/modal'; | import { openAntdModal } from '@/utils/modal'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import SessionStorage from '@/utils/sessionStorage'; | import SessionStorage from '@/utils/sessionStorage'; | ||||
| @@ -87,7 +89,7 @@ function ServiceInfo() { | |||||
| format: formatDate, | format: formatDate, | ||||
| }, | }, | ||||
| ]; | ]; | ||||
| const getResourceDescription = useComputingResource()[1]; | |||||
| const getResourceDescription = useSystemResource(); | |||||
| // 获取服务详情 | // 获取服务详情 | ||||
| const getServiceInfo = useCallback(async () => { | const getServiceInfo = useCallback(async () => { | ||||
| @@ -110,8 +112,8 @@ function ServiceInfo() { | |||||
| if (res && res.data) { | if (res && res.data) { | ||||
| const { content = [], totalElements = 0 } = res.data; | const { content = [], totalElements = 0 } = res.data; | ||||
| content.forEach((item: ServiceVersionData) => { | content.forEach((item: ServiceVersionData) => { | ||||
| if (item.model && !item.model.show_value) { | |||||
| item.model.show_value = `${item.model.name}:${item.model.version}`; | |||||
| if (item.model && !item.model.showValue) { | |||||
| item.model.showValue = `${item.model.name}:${item.model.version}`; | |||||
| } | } | ||||
| }); | }); | ||||
| setTableData(content); | setTableData(content); | ||||
| @@ -258,6 +260,20 @@ function ServiceInfo() { | |||||
| }, | }, | ||||
| }; | }; | ||||
| // 去模型 | |||||
| const gotoModel = (record: ServiceVersionData, e: React.MouseEvent) => { | |||||
| e.stopPropagation(); | |||||
| const model = record.model as any as ModelData; | |||||
| const link = formatModel(model)?.link; | |||||
| if (link) { | |||||
| setCacheState({ | |||||
| pagination, | |||||
| }); | |||||
| navigate(link); | |||||
| } | |||||
| }; | |||||
| const columns: TableProps<ServiceVersionData>['columns'] = [ | const columns: TableProps<ServiceVersionData>['columns'] = [ | ||||
| { | { | ||||
| title: '序号', | title: '序号', | ||||
| @@ -278,10 +294,12 @@ function ServiceInfo() { | |||||
| }, | }, | ||||
| { | { | ||||
| title: '模型版本', | title: '模型版本', | ||||
| dataIndex: ['model', 'show_value'], | |||||
| dataIndex: ['model', 'showValue'], | |||||
| key: 'model', | key: 'model', | ||||
| width: '20%', | width: '20%', | ||||
| render: tableCellRender(true), | |||||
| render: tableCellRender(true, TableCellValueType.Link, { | |||||
| onClick: gotoModel, | |||||
| }), | |||||
| }, | }, | ||||
| { | { | ||||
| title: '镜像版本', | title: '镜像版本', | ||||
| @@ -1,6 +1,6 @@ | |||||
| import BasicInfo, { type BasicInfoData } from '@/components/BasicInfo'; | import BasicInfo, { type BasicInfoData } from '@/components/BasicInfo'; | ||||
| import { ServiceRunStatus } from '@/enums'; | import { ServiceRunStatus } from '@/enums'; | ||||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||||
| import { useSystemResource } from '@/hooks/useComputingResource'; | |||||
| import { ServiceVersionData } from '@/pages/ModelDeployment/types'; | import { ServiceVersionData } from '@/pages/ModelDeployment/types'; | ||||
| import { formatDate } from '@/utils/date'; | import { formatDate } from '@/utils/date'; | ||||
| import { formatMirror, formatModel } from '@/utils/format'; | import { formatMirror, formatModel } from '@/utils/format'; | ||||
| @@ -36,7 +36,7 @@ const formatEnvText = (env?: Record<string, string>) => { | |||||
| }; | }; | ||||
| function VersionBasicInfo({ info }: BasicInfoProps) { | function VersionBasicInfo({ info }: BasicInfoProps) { | ||||
| const getResourceDescription = useComputingResource()[1]; | |||||
| const getResourceDescription = useSystemResource(); | |||||
| const datas: BasicInfoData[] = [ | const datas: BasicInfoData[] = [ | ||||
| { | { | ||||
| @@ -1,6 +1,6 @@ | |||||
| import KFModal from '@/components/KFModal'; | import KFModal from '@/components/KFModal'; | ||||
| import { ServiceRunStatus } from '@/enums'; | import { ServiceRunStatus } from '@/enums'; | ||||
| import { useComputingResource } from '@/hooks/useComputingResource'; | |||||
| import { useSystemResource } from '@/hooks/useComputingResource'; | |||||
| import { type ServiceVersionData } from '@/pages/ModelDeployment/types'; | import { type ServiceVersionData } from '@/pages/ModelDeployment/types'; | ||||
| import { getServiceVersionCompareReq } from '@/services/modelDeployment'; | import { getServiceVersionCompareReq } from '@/services/modelDeployment'; | ||||
| import { isEmpty } from '@/utils'; | import { isEmpty } from '@/utils'; | ||||
| @@ -42,7 +42,7 @@ const formatEnvText = (env: Record<string, string>) => { | |||||
| function VersionCompareModal({ version1, version2, ...rest }: VersionCompareModalProps) { | function VersionCompareModal({ version1, version2, ...rest }: VersionCompareModalProps) { | ||||
| const [compareData, setCompareData] = useState<CompareData | undefined>(undefined); | const [compareData, setCompareData] = useState<CompareData | undefined>(undefined); | ||||
| const getResourceDescription = useComputingResource()[1]; | |||||
| const getResourceDescription = useSystemResource(); | |||||
| const fields: FiledType[] = useMemo( | const fields: FiledType[] = useMemo( | ||||
| () => [ | () => [ | ||||
| @@ -34,7 +34,7 @@ export type ServiceVersionData = { | |||||
| path: string; | path: string; | ||||
| identifier: string; | identifier: string; | ||||
| owner: string; | owner: string; | ||||
| show_value: string; | |||||
| showValue: string; | |||||
| }; | }; | ||||
| code_config: { | code_config: { | ||||
| // 代码配置 | // 代码配置 | ||||
| @@ -3,7 +3,7 @@ import { useStateRef } from '@/hooks/useStateRef'; | |||||
| import { useVisible } from '@/hooks/useVisible'; | import { useVisible } from '@/hooks/useVisible'; | ||||
| import { getWorkflowById, saveWorkflow } from '@/services/pipeline/index.js'; | import { getWorkflowById, saveWorkflow } from '@/services/pipeline/index.js'; | ||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { fittingString, parseJsonText, s8 } from '@/utils'; | |||||
| import { fittingString, s8 } from '@/utils'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import G6 from '@antv/g6'; | import G6 from '@antv/g6'; | ||||
| import { useNavigate, useParams } from '@umijs/max'; | import { useNavigate, useParams } from '@umijs/max'; | ||||
| @@ -54,11 +54,20 @@ const EditPipeline = () => { | |||||
| const onDragEnd = (val) => { | const onDragEnd = (val) => { | ||||
| const { x, y } = val; | const { x, y } = val; | ||||
| const point = graph.getPointByClient(x, y); | const point = graph.getPointByClient(x, y); | ||||
| let label = val.label; | |||||
| const data = graph.save(); | |||||
| const nodeLabels = data.nodes.map((v) => v.label); | |||||
| if (nodeLabels.includes(label)) { | |||||
| label += '-' + s8(); | |||||
| } | |||||
| // 元模型 | // 元模型 | ||||
| const model = { | const model = { | ||||
| ...val, | ...val, | ||||
| x: point.x, | x: point.x, | ||||
| y: point.y, | y: point.y, | ||||
| label, | |||||
| id: val.component_name + '-' + s8(), | id: val.component_name + '-' + s8(), | ||||
| isCluster: false, | isCluster: false, | ||||
| formError: true, | formError: true, | ||||
| @@ -90,24 +99,29 @@ const EditPipeline = () => { | |||||
| // 保存 | // 保存 | ||||
| const savePipeline = async (isBack) => { | const savePipeline = async (isBack) => { | ||||
| const [globalParamRes, globalParamError] = await to(paramsDrawerRef.current.validateFields()); | |||||
| if (globalParamError) { | |||||
| message.error('全局参数配置有误'); | |||||
| openParamsDrawer(); | |||||
| return; | |||||
| } | |||||
| closeParamsDrawer(); | |||||
| // 验证全局参数 | |||||
| // 现在改为关闭的时候就验证了 | |||||
| // const [globalParamRes, globalParamError] = await to(paramsDrawerRef.current.validateFields()); | |||||
| // if (globalParamError) { | |||||
| // message.error('全局参数配置有误'); | |||||
| // openParamsDrawer(); | |||||
| // return; | |||||
| // } | |||||
| // closeParamsDrawer(); | |||||
| const [propsRes, propsError] = await to(propsRef.current.validateFields()); | |||||
| if (propsError) { | |||||
| message.error('节点必填项必须配置'); | |||||
| return; | |||||
| } | |||||
| propsRef.current.close(); | |||||
| // 以前没有遮挡【保存】按钮时有用 | |||||
| // 验证节点必填参数 | |||||
| // const [propsRes, propsError] = await to(propsRef.current.validateFields()); | |||||
| // if (propsError) { | |||||
| // message.error('节点必填项必须配置'); | |||||
| // return; | |||||
| // } | |||||
| // propsRef.current.close(); | |||||
| setTimeout(() => { | setTimeout(() => { | ||||
| const data = graph.save(); | const data = graph.save(); | ||||
| // console.log(data); | // console.log(data); | ||||
| // 验证节点必填参数 | |||||
| const errorNode = data.nodes.find((item) => item.formError === true); | const errorNode = data.nodes.find((item) => item.formError === true); | ||||
| if (errorNode) { | if (errorNode) { | ||||
| message.error(`【${errorNode.label}】节点配置验证失败`); | message.error(`【${errorNode.label}】节点配置验证失败`); | ||||
| @@ -117,11 +131,25 @@ const EditPipeline = () => { | |||||
| } | } | ||||
| return; | return; | ||||
| } | } | ||||
| // 验证节点名称是否有重命名 | |||||
| const nodeLabels = data.nodes.map((v) => v.label); | |||||
| for (let i = 0; i < nodeLabels.length; i++) { | |||||
| const current = nodeLabels[i]; | |||||
| for (let j = i + 1; j < nodeLabels.length; j++) { | |||||
| const next = nodeLabels[j]; | |||||
| if (current === next) { | |||||
| message.error(`存在重名的【${current}】节点`); | |||||
| return; | |||||
| } | |||||
| } | |||||
| } | |||||
| const params = { | const params = { | ||||
| ...locationParams, | ...locationParams, | ||||
| name: workflowInfo?.name, | name: workflowInfo?.name, | ||||
| dag: JSON.stringify(data), | |||||
| global_param: JSON.stringify(globalParamRes.global_param), | |||||
| dag: data, | |||||
| global_param: globalParam, | |||||
| }; | }; | ||||
| saveWorkflow(params).then((ret) => { | saveWorkflow(params).then((ret) => { | ||||
| message.success('保存成功'); | message.success('保存成功'); | ||||
| @@ -290,7 +318,7 @@ const EditPipeline = () => { | |||||
| const { global_param, dag } = res.data; | const { global_param, dag } = res.data; | ||||
| setGlobalParam(global_param || []); | setGlobalParam(global_param || []); | ||||
| if (dag) { | if (dag) { | ||||
| getGraphData(parseJsonText(dag)); | |||||
| getGraphData(dag); | |||||
| } | } | ||||
| } | } | ||||
| }; | }; | ||||
| @@ -299,13 +327,19 @@ const EditPipeline = () => { | |||||
| const openNodeDrawer = (node, validate = false) => { | const openNodeDrawer = (node, validate = false) => { | ||||
| // 获取所有的上游节点 | // 获取所有的上游节点 | ||||
| const parentNodes = findAllParentNodes(graph, node); | const parentNodes = findAllParentNodes(graph, node); | ||||
| // 如果没有打开过全局参数抽屉,获取不到全局参数 | |||||
| const globalParams = | |||||
| paramsDrawerRef.current.getFieldsValue().global_param || globalParamRef.current; | |||||
| // q全局参数 | |||||
| const globalParams = globalParamRef.current; | |||||
| // 打开节点编辑抽屉 | // 打开节点编辑抽屉 | ||||
| propsRef.current.showDrawer(node.getModel(), globalParams, parentNodes, validate); | propsRef.current.showDrawer(node.getModel(), globalParams, parentNodes, validate); | ||||
| }; | }; | ||||
| // 关闭全局参数节点,获取全局参数 | |||||
| const closeGlobalParamsDrawer = () => { | |||||
| const { global_param } = paramsDrawerRef.current.getFieldsValue(); | |||||
| setGlobalParam(global_param); | |||||
| closeParamsDrawer(); | |||||
| }; | |||||
| // 初始化图 | // 初始化图 | ||||
| const initGraph = () => { | const initGraph = () => { | ||||
| const contextMenu = initMenu(); | const contextMenu = initMenu(); | ||||
| @@ -730,7 +764,7 @@ const EditPipeline = () => { | |||||
| ref={paramsDrawerRef} | ref={paramsDrawerRef} | ||||
| open={paramsDrawerOpen} | open={paramsDrawerOpen} | ||||
| globalParam={globalParam} | globalParam={globalParam} | ||||
| onClose={closeParamsDrawer} | |||||
| onClose={closeGlobalParamsDrawer} | |||||
| ></GlobalParamsDrawer> | ></GlobalParamsDrawer> | ||||
| </div> | </div> | ||||
| ); | ); | ||||
| @@ -1,5 +1,4 @@ | |||||
| import { PipelineGlobalParam, PipelineNodeModelParameter } from '@/types'; | import { PipelineGlobalParam, PipelineNodeModelParameter } from '@/types'; | ||||
| import { parseJsonText } from '@/utils'; | |||||
| import { Graph, INode } from '@antv/g6'; | import { Graph, INode } from '@antv/g6'; | ||||
| import { type MenuProps } from 'antd'; | import { type MenuProps } from 'antd'; | ||||
| @@ -42,8 +41,7 @@ export function createMenuItems( | |||||
| ): MenuProps['items'] { | ): MenuProps['items'] { | ||||
| const nodes: MenuProps['items'] = parentNodes.map((item) => { | const nodes: MenuProps['items'] = parentNodes.map((item) => { | ||||
| const model = item.getModel(); | const model = item.getModel(); | ||||
| const out_parameters = model.out_parameters as string | undefined | null; | |||||
| const out_parametersObj = parseJsonText(out_parameters); | |||||
| const out_parametersObj = model.out_parameters as Record<string, PipelineNodeModelParameter>; | |||||
| const outParametersList = Object.keys(out_parametersObj ?? {}); | const outParametersList = Object.keys(out_parametersObj ?? {}); | ||||
| return { | return { | ||||
| key: model.id as string, | key: model.id as string, | ||||
| @@ -72,15 +70,6 @@ export function createMenuItems( | |||||
| } | } | ||||
| } | } | ||||
| export function getInParameterComponent( | |||||
| parameter: PipelineNodeModelParameter, | |||||
| ): React.ReactNode | null { | |||||
| if (parameter.value) { | |||||
| } | |||||
| return null; | |||||
| } | |||||
| // 判断是否允许输入 | // 判断是否允许输入 | ||||
| export function canInput(parameter: PipelineNodeModelParameter) { | export function canInput(parameter: PipelineNodeModelParameter) { | ||||
| const { type, item_type } = parameter; | const { type, item_type } = parameter; | ||||
| @@ -89,6 +78,9 @@ export function canInput(parameter: PipelineNodeModelParameter) { | |||||
| (item_type === 'dataset' || | (item_type === 'dataset' || | ||||
| item_type === 'model' || | item_type === 'model' || | ||||
| item_type === 'image' || | item_type === 'image' || | ||||
| item_type === 'code') | |||||
| item_type === 'code' || | |||||
| item_type === 'remote-dataset' || | |||||
| item_type === 'remote-model' || | |||||
| item_type === 'remote-code') | |||||
| ); | ); | ||||
| } | } | ||||
| @@ -1,6 +1,6 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { getParamComponent, getParamRules } from '@/pages/Experiment/components/AddExperimentModal'; | import { getParamComponent, getParamRules } from '@/pages/Experiment/components/AddExperimentModal'; | ||||
| import { type PipelineGlobalParam } from '@/types'; | |||||
| import { type PipelineGlobalParam, PipelineGlobalParamType } from '@/types'; | |||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| import { PlusOutlined } from '@ant-design/icons'; | import { PlusOutlined } from '@ant-design/icons'; | ||||
| @@ -42,6 +42,7 @@ const GlobalParamsDrawer = forwardRef( | |||||
| form.setFieldValue(name, null); | form.setFieldValue(name, null); | ||||
| }; | }; | ||||
| // 处理删除 | |||||
| const removeParameter = (name: number, remove: (param: number) => void) => { | const removeParameter = (name: number, remove: (param: number) => void) => { | ||||
| modalConfirm({ | modalConfirm({ | ||||
| title: '删除后,该全局参数将不可恢复', | title: '删除后,该全局参数将不可恢复', | ||||
| @@ -52,6 +53,16 @@ const GlobalParamsDrawer = forwardRef( | |||||
| }); | }); | ||||
| }; | }; | ||||
| // 处理关闭 | |||||
| const handleClose = async () => { | |||||
| try { | |||||
| await form.validateFields(); | |||||
| onClose(); | |||||
| } catch { | |||||
| return false; | |||||
| } | |||||
| }; | |||||
| return ( | return ( | ||||
| <Drawer | <Drawer | ||||
| rootStyle={{ marginTop: '55px' }} | rootStyle={{ marginTop: '55px' }} | ||||
| @@ -59,7 +70,7 @@ const GlobalParamsDrawer = forwardRef( | |||||
| placement="right" | placement="right" | ||||
| closeIcon={false} | closeIcon={false} | ||||
| getContainer={false} | getContainer={false} | ||||
| onClose={onClose} | |||||
| onClose={handleClose} | |||||
| open={open} | open={open} | ||||
| width={520} | width={520} | ||||
| > | > | ||||
| @@ -81,7 +92,7 @@ const GlobalParamsDrawer = forwardRef( | |||||
| {...restField} | {...restField} | ||||
| name={[name, 'param_name']} | name={[name, 'param_name']} | ||||
| label="参数名称" | label="参数名称" | ||||
| validateTrigger={[]} | |||||
| dependencies={fields.map((_, i) => ['global_param', i, 'param_name'])} | |||||
| rules={[ | rules={[ | ||||
| { required: true, message: '请输入参数名称' }, | { required: true, message: '请输入参数名称' }, | ||||
| { | { | ||||
| @@ -97,11 +108,7 @@ const GlobalParamsDrawer = forwardRef( | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input | |||||
| placeholder="请输入参数名称" | |||||
| allowClear | |||||
| onBlur={() => form.validateFields()} | |||||
| /> | |||||
| <Input placeholder="请输入参数名称" allowClear /> | |||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | <Form.Item | ||||
| {...restField} | {...restField} | ||||
| @@ -124,9 +131,9 @@ const GlobalParamsDrawer = forwardRef( | |||||
| <Radio.Group | <Radio.Group | ||||
| onChange={() => handleTypeChange(['global_param', name, 'param_value'])} | onChange={() => handleTypeChange(['global_param', name, 'param_value'])} | ||||
| > | > | ||||
| <Radio value={1}>字符串</Radio> | |||||
| <Radio value={2}>整型</Radio> | |||||
| <Radio value={3}>布尔类型</Radio> | |||||
| <Radio value={PipelineGlobalParamType.String}>字符串</Radio> | |||||
| <Radio value={PipelineGlobalParamType.Number}>整型</Radio> | |||||
| <Radio value={PipelineGlobalParamType.Boolean}>布尔类型</Radio> | |||||
| </Radio.Group> | </Radio.Group> | ||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item | <Form.Item | ||||
| @@ -21,10 +21,7 @@ | |||||
| background: #f8fbff; | background: #f8fbff; | ||||
| } | } | ||||
| &__ref-row { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| &__component { | |||||
| &__select-button { | &__select-button { | ||||
| display: flex; | display: flex; | ||||
| flex: none; | flex: none; | ||||
| @@ -34,5 +31,29 @@ | |||||
| padding-right: 0; | padding-right: 0; | ||||
| padding-left: 0; | padding-left: 0; | ||||
| } | } | ||||
| &__list-row { | |||||
| :global { | |||||
| .ant-row { | |||||
| padding: 0 !important; | |||||
| } | |||||
| } | |||||
| &:last-child { | |||||
| :global { | |||||
| .ant-form-item { | |||||
| margin-bottom: 0 !important; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| &__add-button { | |||||
| border-color: .addAlpha(@primary-color, 0.5) []; | |||||
| box-shadow: none !important; | |||||
| &:hover { | |||||
| border-style: solid; | |||||
| } | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,32 +1,44 @@ | |||||
| import { type ServerCodeData } from '@/components/CodeSelect'; | |||||
| import CodeSelectorModal from '@/components/CodeSelectorModal'; | |||||
| import CodeSelectorModal, { CodeConfigData } from '@/components/CodeSelectorModal'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import ParameterInput, { requiredValidator } from '@/components/ParameterInput'; | import ParameterInput, { requiredValidator } from '@/components/ParameterInput'; | ||||
| import ParameterSelect, { type ParameterSelectDataType } from '@/components/ParameterSelect'; | |||||
| import ParameterSelect, { | |||||
| type ParameterSelectDataType, | |||||
| type ParameterSelectObject, | |||||
| ParameterSelectTypeList, | |||||
| } from '@/components/ParameterSelect'; | |||||
| import ResourceSelectorModal, { | import ResourceSelectorModal, { | ||||
| ResourceSelectorType, | ResourceSelectorType, | ||||
| selectorTypeConfig, | selectorTypeConfig, | ||||
| } from '@/components/ResourceSelectorModal'; | } from '@/components/ResourceSelectorModal'; | ||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import { CommonTabKeys } from '@/enums'; | |||||
| import { CommonTabKeys, ComponentType } from '@/enums'; | |||||
| import { canInput, createMenuItems } from '@/pages/Pipeline/Info/utils'; | import { canInput, createMenuItems } from '@/pages/Pipeline/Info/utils'; | ||||
| import state from '@/state/jcdResource'; | |||||
| import { | import { | ||||
| PipelineGlobalParam, | PipelineGlobalParam, | ||||
| PipelineNodeModel, | PipelineNodeModel, | ||||
| PipelineNodeModelParameter, | PipelineNodeModelParameter, | ||||
| PipelineNodeModelSerialize, | PipelineNodeModelSerialize, | ||||
| } from '@/types'; | } from '@/types'; | ||||
| import { parseJsonText } from '@/utils'; | |||||
| import { openAntdModal } from '@/utils/modal'; | import { openAntdModal } from '@/utils/modal'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { removeFormListItem } from '@/utils/ui'; | |||||
| import { MinusCircleOutlined, PlusCircleOutlined, PlusOutlined } from '@ant-design/icons'; | |||||
| import { INode } from '@antv/g6'; | import { INode } from '@antv/g6'; | ||||
| import { Button, Drawer, Form, Input, MenuProps } from 'antd'; | |||||
| import { useSnapshot } from '@umijs/max'; | |||||
| import { Button, Drawer, Flex, Form, Input, MenuProps } from 'antd'; | |||||
| import { RuleObject } from 'antd/es/form'; | import { RuleObject } from 'antd/es/form'; | ||||
| import { NamePath } from 'antd/es/form/interface'; | import { NamePath } from 'antd/es/form/interface'; | ||||
| import { omit } from 'lodash'; | |||||
| import { forwardRef, useImperativeHandle, useState } from 'react'; | import { forwardRef, useImperativeHandle, useState } from 'react'; | ||||
| import PropsLabel from '../PropsLabel'; | import PropsLabel from '../PropsLabel'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| const { TextArea } = Input; | |||||
| // 表单列表数据 | |||||
| export type FormListVariable = { | |||||
| name: string; // 参数名 | |||||
| value: string; // 参数值 | |||||
| }; | |||||
| type PipelineNodeParameterProps = { | type PipelineNodeParameterProps = { | ||||
| onFormChange: (data: PipelineNodeModelSerialize) => void; | onFormChange: (data: PipelineNodeModelSerialize) => void; | ||||
| @@ -37,14 +49,10 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| const [stagingItem, setStagingItem] = useState<PipelineNodeModelSerialize>( | const [stagingItem, setStagingItem] = useState<PipelineNodeModelSerialize>( | ||||
| {} as PipelineNodeModelSerialize, | {} as PipelineNodeModelSerialize, | ||||
| ); | ); | ||||
| const nodeId = Form.useWatch('id', form) as string; | |||||
| const hasTaskInfo = | |||||
| nodeId && | |||||
| !nodeId.startsWith('git-clone') && | |||||
| !nodeId.startsWith('dataset-export') && | |||||
| !nodeId.startsWith('model-export'); | |||||
| const [open, setOpen] = useState(false); | const [open, setOpen] = useState(false); | ||||
| const [menuItems, setMenuItems] = useState<MenuProps['items']>([]); | const [menuItems, setMenuItems] = useState<MenuProps['items']>([]); | ||||
| const snap = useSnapshot(state); | |||||
| const { setCurrentType } = snap; | |||||
| const afterOpenChange = async () => { | const afterOpenChange = async () => { | ||||
| if (!open) { | if (!open) { | ||||
| @@ -53,11 +61,16 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| // 不管是否验证成功,都需要获取表单数据 | // 不管是否验证成功,都需要获取表单数据 | ||||
| const fields = form.getFieldsValue(); | const fields = form.getFieldsValue(); | ||||
| // 保存字段顺序 | |||||
| // const control_strategy = { | |||||
| // ...stagingItem.control_strategy, | |||||
| // ...fields.control_strategy, | |||||
| // }; | |||||
| // 保持原有字段和顺序 | |||||
| const task_info = { | |||||
| ...stagingItem.task_info, | |||||
| ...fields.task_info, | |||||
| }; | |||||
| const control_strategy = { | |||||
| ...stagingItem.control_strategy, | |||||
| ...fields.control_strategy, | |||||
| }; | |||||
| const in_parameters = { | const in_parameters = { | ||||
| ...stagingItem.in_parameters, | ...stagingItem.in_parameters, | ||||
| @@ -68,17 +81,18 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| ...fields.out_parameters, | ...fields.out_parameters, | ||||
| }; | }; | ||||
| // console.log('getFieldsValue', fields); | |||||
| console.log('getFieldsValue', fields); | |||||
| const res = { | const res = { | ||||
| ...stagingItem, | |||||
| ...fields, | |||||
| // control_strategy: JSON.stringify(control_strategy), | |||||
| in_parameters: JSON.stringify(in_parameters), | |||||
| out_parameters: JSON.stringify(out_parameters), | |||||
| ...omit(stagingItem, ['control_strategy', 'task_info', 'in_parameters', 'out_parameters']), | |||||
| ...omit(fields, ['control_strategy', 'task_info', 'in_parameters', 'out_parameters']), | |||||
| task_info: task_info, | |||||
| control_strategy: control_strategy, | |||||
| in_parameters: in_parameters, | |||||
| out_parameters: out_parameters, | |||||
| formError: !!error, | formError: !!error, | ||||
| }; | }; | ||||
| // console.log('res', res); | |||||
| console.log('res', res); | |||||
| onFormChange(res); | onFormChange(res); | ||||
| } | } | ||||
| }; | }; | ||||
| @@ -96,19 +110,12 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| validate: boolean = false, | validate: boolean = false, | ||||
| ) { | ) { | ||||
| try { | try { | ||||
| const nodeData: PipelineNodeModelSerialize = { | |||||
| ...model, | |||||
| in_parameters: JSON.parse(model.in_parameters), | |||||
| out_parameters: JSON.parse(model.out_parameters), | |||||
| // control_strategy: JSON.parse(model.control_strategy), | |||||
| }; | |||||
| // console.log('model', nodeData); | |||||
| setStagingItem({ | setStagingItem({ | ||||
| ...nodeData, | |||||
| ...model, | |||||
| }); | }); | ||||
| form.resetFields(); | form.resetFields(); | ||||
| form.setFieldsValue({ | form.setFieldsValue({ | ||||
| ...nodeData, | |||||
| ...model, | |||||
| }); | }); | ||||
| if (validate) { | if (validate) { | ||||
| form.validateFields(); | form.validateFields(); | ||||
| @@ -120,6 +127,12 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| // 参数下拉菜单 | // 参数下拉菜单 | ||||
| setMenuItems(createMenuItems(params, parentNodes)); | setMenuItems(createMenuItems(params, parentNodes)); | ||||
| // 云际组件,设置 store 当前资源类型 | |||||
| if (model.id.startsWith('remote-task')) { | |||||
| const resourceType = model.in_parameters['--resource_type'].value; | |||||
| setCurrentType(resourceType); | |||||
| } | |||||
| }, | }, | ||||
| close: () => { | close: () => { | ||||
| onClose(); | onClose(); | ||||
| @@ -137,7 +150,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| } | } | ||||
| }, | }, | ||||
| }), | }), | ||||
| [form, open], | |||||
| [form, open, setCurrentType], | |||||
| ); | ); | ||||
| // ref 类型选择 | // ref 类型选择 | ||||
| @@ -145,7 +158,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| formItemName: NamePath, | formItemName: NamePath, | ||||
| item: PipelineNodeModelParameter | Pick<PipelineNodeModelParameter, 'item_type'>, | item: PipelineNodeModelParameter | Pick<PipelineNodeModelParameter, 'item_type'>, | ||||
| ) => { | ) => { | ||||
| if (item.item_type === 'code') { | |||||
| if (item.item_type === 'code' || item.item_type === 'remote-code') { | |||||
| selectCodeConfig(formItemName, item); | selectCodeConfig(formItemName, item); | ||||
| } else { | } else { | ||||
| selectResource(formItemName, item); | selectResource(formItemName, item); | ||||
| @@ -157,47 +170,15 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| formItemName: NamePath, | formItemName: NamePath, | ||||
| item: PipelineNodeModelParameter | Pick<PipelineNodeModelParameter, 'item_type'>, | item: PipelineNodeModelParameter | Pick<PipelineNodeModelParameter, 'item_type'>, | ||||
| ) => { | ) => { | ||||
| const jsonValue = form.getFieldValue(formItemName)?.value; | |||||
| const fieldValue = parseJsonText(jsonValue) as ServerCodeData; | |||||
| const defaultSelected = fieldValue | |||||
| ? { | |||||
| id: fieldValue.id, | |||||
| code_repo_name: fieldValue.name, | |||||
| git_url: fieldValue.code_path, | |||||
| git_branch: fieldValue.branch, | |||||
| git_user_name: fieldValue.username, | |||||
| git_password: fieldValue.password, | |||||
| ssh_key: fieldValue.ssh_private_key, | |||||
| is_public: fieldValue.is_public, | |||||
| } | |||||
| : undefined; | |||||
| const defaultSelected = form.getFieldValue(formItemName)?.value as CodeConfigData; | |||||
| const { close } = openAntdModal(CodeSelectorModal, { | const { close } = openAntdModal(CodeSelectorModal, { | ||||
| defaultSelected, | defaultSelected, | ||||
| onOk: (res) => { | onOk: (res) => { | ||||
| if (res) { | if (res) { | ||||
| const { | |||||
| id, | |||||
| code_repo_name, | |||||
| git_url, | |||||
| git_branch, | |||||
| git_user_name, | |||||
| git_password, | |||||
| ssh_key, | |||||
| is_public, | |||||
| } = res; | |||||
| const value = JSON.stringify({ | |||||
| id, | |||||
| name: code_repo_name, | |||||
| code_path: git_url, | |||||
| branch: git_branch, | |||||
| username: git_user_name, | |||||
| password: git_password, | |||||
| ssh_private_key: ssh_key, | |||||
| is_public, | |||||
| }); | |||||
| const { code_repo_name } = res; | |||||
| form.setFieldValue(formItemName, { | form.setFieldValue(formItemName, { | ||||
| ...item, | ...item, | ||||
| value, | |||||
| value: res, | |||||
| showValue: code_repo_name, | showValue: code_repo_name, | ||||
| fromSelect: true, | fromSelect: true, | ||||
| }); | }); | ||||
| @@ -216,9 +197,11 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| let type: ResourceSelectorType; | let type: ResourceSelectorType; | ||||
| switch (item.item_type) { | switch (item.item_type) { | ||||
| case 'dataset': | case 'dataset': | ||||
| case 'remote-dataset': | |||||
| type = ResourceSelectorType.Dataset; | type = ResourceSelectorType.Dataset; | ||||
| break; | break; | ||||
| case 'model': | case 'model': | ||||
| case 'remote-model': | |||||
| type = ResourceSelectorType.Model; | type = ResourceSelectorType.Model; | ||||
| break; | break; | ||||
| default: | default: | ||||
| @@ -237,36 +220,24 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| onOk: (res) => { | onOk: (res) => { | ||||
| if (res) { | if (res) { | ||||
| if (type === ResourceSelectorType.Mirror) { | if (type === ResourceSelectorType.Mirror) { | ||||
| const { activeTab, id, version, path } = res; | |||||
| if (formItemName === 'image') { | |||||
| // 单独的选择镜像 | |||||
| form.setFieldValue(formItemName, path); | |||||
| } else { | |||||
| // 输入参数选择镜像 | |||||
| form.setFieldValue(formItemName, { | |||||
| ...item, | |||||
| value: path, | |||||
| showValue: path, | |||||
| fromSelect: true, | |||||
| activeTab, | |||||
| expandedKeys: [id], | |||||
| checkedKeys: [`${id}-${version}`], | |||||
| }); | |||||
| } | |||||
| } else { | |||||
| const { activeTab, id, name, version, path, identifier, owner } = res; | |||||
| const value = JSON.stringify({ | |||||
| id, | |||||
| name, | |||||
| version, | |||||
| path, | |||||
| identifier, | |||||
| owner, | |||||
| const { activeTab, ...rest } = res; | |||||
| const { url, id, image_id } = rest; | |||||
| form.setFieldValue(formItemName, { | |||||
| ...item, | |||||
| value: rest, | |||||
| showValue: url, | |||||
| fromSelect: true, | |||||
| activeTab, | |||||
| expandedKeys: [`${image_id}`], | |||||
| checkedKeys: [`${image_id}-${id}`], | |||||
| }); | }); | ||||
| } else { | |||||
| const { activeTab, ...rest } = res; | |||||
| const { id, name, version } = rest; | |||||
| const showValue = `${name}:${version}`; | const showValue = `${name}:${version}`; | ||||
| form.setFieldValue(formItemName, { | form.setFieldValue(formItemName, { | ||||
| ...item, | ...item, | ||||
| value, | |||||
| value: rest, | |||||
| showValue, | showValue, | ||||
| fromSelect: true, | fromSelect: true, | ||||
| activeTab, | activeTab, | ||||
| @@ -275,19 +246,15 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| }); | }); | ||||
| } | } | ||||
| } else { | } else { | ||||
| if (type === ResourceSelectorType.Mirror && formItemName === 'image') { | |||||
| form.setFieldValue(formItemName, undefined); | |||||
| } else { | |||||
| form.setFieldValue(formItemName, { | |||||
| ...item, | |||||
| value: undefined, | |||||
| showValue: undefined, | |||||
| fromSelect: false, | |||||
| activeTab: undefined, | |||||
| expandedKeys: [], | |||||
| checkedKeys: [], | |||||
| }); | |||||
| } | |||||
| form.setFieldValue(formItemName, { | |||||
| ...item, | |||||
| value: undefined, | |||||
| showValue: undefined, | |||||
| fromSelect: false, | |||||
| activeTab: undefined, | |||||
| expandedKeys: [], | |||||
| checkedKeys: [], | |||||
| }); | |||||
| } | } | ||||
| form.validateFields([formItemName]); | form.validateFields([formItemName]); | ||||
| close(); | close(); | ||||
| @@ -298,14 +265,14 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| // 获取选择数据集、模型后面按钮 icon | // 获取选择数据集、模型后面按钮 icon | ||||
| const getSelectBtnIcon = (item: { item_type: string }) => { | const getSelectBtnIcon = (item: { item_type: string }) => { | ||||
| const type = item.item_type; | const type = item.item_type; | ||||
| if (type === 'code') { | |||||
| if (type === 'code' || type === 'remote-code') { | |||||
| return <KFIcon type="icon-xuanzedaimapeizhi" />; | return <KFIcon type="icon-xuanzedaimapeizhi" />; | ||||
| } | } | ||||
| let selectorType: ResourceSelectorType; | let selectorType: ResourceSelectorType; | ||||
| if (type === 'dataset') { | |||||
| if (type === 'dataset' || type === 'remote-dataset') { | |||||
| selectorType = ResourceSelectorType.Dataset; | selectorType = ResourceSelectorType.Dataset; | ||||
| } else if (type === 'model') { | |||||
| } else if (type === 'model' || type === 'remote-model') { | |||||
| selectorType = ResourceSelectorType.Model; | selectorType = ResourceSelectorType.Model; | ||||
| } else { | } else { | ||||
| selectorType = ResourceSelectorType.Mirror; | selectorType = ResourceSelectorType.Mirror; | ||||
| @@ -323,16 +290,16 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| // form item label | // form item label | ||||
| const getLabel = ( | const getLabel = ( | ||||
| item: { key: string; value: PipelineNodeModelParameter }, | item: { key: string; value: PipelineNodeModelParameter }, | ||||
| namePrefix: string, | |||||
| parentName: string, | |||||
| ) => { | ) => { | ||||
| return item.value.type === 'select' ? ( | |||||
| return item.value.type === ComponentType.Select || item.value.type === ComponentType.Map ? ( | |||||
| item.value.label + '(' + item.key + ')' | item.value.label + '(' + item.key + ')' | ||||
| ) : ( | ) : ( | ||||
| <PropsLabel | <PropsLabel | ||||
| menuItems={menuItems} | menuItems={menuItems} | ||||
| title={item.value.label + '(' + item.key + ')'} | title={item.value.label + '(' + item.key + ')'} | ||||
| onClick={(value) => { | onClick={(value) => { | ||||
| handleParameterClick([namePrefix, item.key], { | |||||
| handleParameterClick([parentName, item.key], { | |||||
| ...item.value, | ...item.value, | ||||
| value, | value, | ||||
| fromSelect: true, | fromSelect: true, | ||||
| @@ -380,21 +347,249 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| return rules; | 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 }, | |||||
| parentName: string, | |||||
| ) => { | |||||
| return ( | |||||
| <> | |||||
| {item.value.type === ComponentType.Ref && ( | |||||
| <Flex align="center"> | |||||
| <Form.Item name={[parentName, item.key]} rules={getFormRules(item)} noStyle> | |||||
| <ParameterInput | |||||
| canInput={canInput(item.value)} | |||||
| placeholder={item.value.placeholder} | |||||
| allowClear | |||||
| ></ParameterInput> | |||||
| </Form.Item> | |||||
| <Form.Item noStyle> | |||||
| <Button | |||||
| size="small" | |||||
| type="link" | |||||
| icon={getSelectBtnIcon(item.value)} | |||||
| onClick={() => selectRefData([parentName, item.key], item.value)} | |||||
| className={styles['pipeline-drawer__component__select-button']} | |||||
| > | |||||
| {item.value.label} | |||||
| </Button> | |||||
| </Form.Item> | |||||
| </Flex> | |||||
| )} | |||||
| {item.value.type === ComponentType.Select && | |||||
| (ParameterSelectTypeList.includes(item.value.item_type as ParameterSelectDataType) ? ( | |||||
| <Form.Item name={[parentName, item.key]} rules={getFormRules(item)} noStyle> | |||||
| <ParameterSelect | |||||
| 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)} | |||||
| {item.value.type === ComponentType.Map && ( | |||||
| <Form.Item name={[parentName, item.key]} rules={getFormRules(item)} noStyle> | |||||
| <Form.List name={[parentName, item.key, 'value']}> | |||||
| {(fields, { add, remove }) => ( | |||||
| <> | |||||
| {fields.map(({ key, name, ...restField }, index) => ( | |||||
| <Flex | |||||
| key={key} | |||||
| gap="0 8px" | |||||
| style={{ width: '100%' }} | |||||
| className={styles['pipeline-drawer__component__list-row']} | |||||
| > | |||||
| <Form.Item | |||||
| {...restField} | |||||
| name={[name, 'name']} | |||||
| style={{ flex: 1, minWidth: 0 }} | |||||
| dependencies={fields.map((_, i) => [ | |||||
| parentName, | |||||
| item.key, | |||||
| 'value', | |||||
| i, | |||||
| 'name', | |||||
| ])} | |||||
| rules={[ | |||||
| { | |||||
| validator: (_, value) => { | |||||
| if (!value) { | |||||
| return Promise.reject(new Error('请输入变量名')); | |||||
| } | |||||
| if (!/^[a-zA-Z_][a-zA-Z0-9_-]*$/.test(value)) { | |||||
| return Promise.reject( | |||||
| new Error( | |||||
| '变量名只支持字母、数字、下划线、中横线并且必须以字母或下划线开头', | |||||
| ), | |||||
| ); | |||||
| } | |||||
| // 判断不能重名 | |||||
| const list = form | |||||
| .getFieldValue([parentName, item.key, 'value']) | |||||
| .filter( | |||||
| (item: FormListVariable | undefined) => | |||||
| item !== undefined && item !== null, | |||||
| ); | |||||
| const names = list.map((item: FormListVariable) => item.name); | |||||
| if (new Set(names).size !== names.length) { | |||||
| return Promise.reject(new Error('变量名不能重复')); | |||||
| } | |||||
| return Promise.resolve(); | |||||
| }, | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input placeholder="请输入变量名" allowClear /> | |||||
| </Form.Item> | |||||
| <span style={{ lineHeight: '32px' }}>=</span> | |||||
| <Form.Item | |||||
| {...restField} | |||||
| name={[name, 'value']} | |||||
| style={{ flex: 1, minWidth: 0 }} | |||||
| rules={[ | |||||
| { | |||||
| validator: requiredValidator, | |||||
| message: '请输入变量值', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| {/* <Input placeholder="请输入变量值" allowClear /> */} | |||||
| <ParameterInput | |||||
| placeholder="请输入变量值" | |||||
| allowClear | |||||
| addonAfter={ | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title="" | |||||
| onClick={(value) => { | |||||
| handleParameterClick( | |||||
| [parentName, item.key, 'value', name, 'value'], | |||||
| { | |||||
| ...item.value, | |||||
| value, | |||||
| fromSelect: true, | |||||
| showValue: value, | |||||
| }, | |||||
| ); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| ></ParameterInput> | |||||
| </Form.Item> | |||||
| <Flex | |||||
| style={{ | |||||
| width: '76px', | |||||
| height: '32px', | |||||
| }} | |||||
| align="center" | |||||
| > | |||||
| <Button | |||||
| style={{ | |||||
| marginRight: '3px', | |||||
| }} | |||||
| shape="circle" | |||||
| size="middle" | |||||
| type="text" | |||||
| icon={<MinusCircleOutlined />} | |||||
| onClick={() => { | |||||
| removeFormListItem( | |||||
| form, | |||||
| 'env_variables', | |||||
| name, | |||||
| remove, | |||||
| ['key', 'value'], | |||||
| '删除后,该变量将不可恢复', | |||||
| ); | |||||
| }} | |||||
| ></Button> | |||||
| {index === fields.length - 1 && ( | |||||
| <Button | |||||
| shape="circle" | |||||
| size="middle" | |||||
| type="text" | |||||
| icon={<PlusCircleOutlined />} | |||||
| onClick={() => add()} | |||||
| ></Button> | |||||
| )} | |||||
| </Flex> | |||||
| </Flex> | |||||
| ))} | |||||
| {fields.length === 0 && ( | |||||
| <Button | |||||
| className={styles['pipeline-drawer__component__add-button']} | |||||
| color="primary" | |||||
| variant="dashed" | |||||
| icon={<PlusOutlined />} | |||||
| block | |||||
| onClick={() => add()} | |||||
| > | |||||
| 新增 | |||||
| </Button> | |||||
| )} | |||||
| </> | |||||
| )} | |||||
| </Form.List> | |||||
| </Form.Item> | |||||
| )} | |||||
| {item.value.type === ComponentType.Str && ( | |||||
| <Form.Item name={[parentName, item.key]} rules={getFormRules(item)} noStyle> | |||||
| <ParameterInput | |||||
| canInput={canInput(item.value)} | |||||
| placeholder={item.value.placeholder} | |||||
| allowClear | |||||
| ></ParameterInput> | |||||
| </Form.Item> | |||||
| )} | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| // 基本参数 | |||||
| const basicParametersList = Object.entries(stagingItem.task_info ?? {}) | |||||
| .map(([key, value]) => ({ | |||||
| key, | |||||
| value, | |||||
| })) | |||||
| .filter((v) => v.value.visible === true); | |||||
| // 控制策略 | // 控制策略 | ||||
| // const controlStrategyList = Object.entries(stagingItem.control_strategy ?? {}).map( | |||||
| // ([key, value]) => ({ key, value }), | |||||
| // ); | |||||
| const controlStrategyList = Object.entries(stagingItem.control_strategy ?? {}) | |||||
| .map(([key, value]) => ({ key, value })) | |||||
| .filter((v) => v.value.visible === true); | |||||
| // 输入参数 | // 输入参数 | ||||
| const inParametersList = Object.entries(stagingItem.in_parameters ?? {}).map(([key, value]) => ({ | |||||
| key, | |||||
| value, | |||||
| })); | |||||
| const inParametersList = Object.entries(stagingItem.in_parameters ?? {}) | |||||
| .map(([key, value]) => ({ | |||||
| key, | |||||
| value, | |||||
| })) | |||||
| .filter((v) => v.value.visible === true); | |||||
| // 输出参数 | // 输出参数 | ||||
| const outParametersList = Object.entries(stagingItem.out_parameters ?? {}).map( | |||||
| ([key, value]) => ({ key, value }), | |||||
| ); | |||||
| const outParametersList = Object.entries(stagingItem.out_parameters ?? {}) | |||||
| .map(([key, value]) => ({ key, value })) | |||||
| .filter((v) => v.value.visible === true); | |||||
| return ( | return ( | ||||
| <Drawer | <Drawer | ||||
| @@ -406,7 +601,7 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| onClose={onClose} | onClose={onClose} | ||||
| afterOpenChange={afterOpenChange} | afterOpenChange={afterOpenChange} | ||||
| open={open} | open={open} | ||||
| width={520} | |||||
| width={620} | |||||
| className={styles['pipeline-drawer']} | className={styles['pipeline-drawer']} | ||||
| > | > | ||||
| <Form | <Form | ||||
| @@ -455,114 +650,37 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| > | > | ||||
| <Input disabled /> | <Input disabled /> | ||||
| </Form.Item> | </Form.Item> | ||||
| {hasTaskInfo && ( | |||||
| <> | |||||
| <div className={styles['pipeline-drawer__title']}> | |||||
| <SubAreaTitle | |||||
| image={require('@/assets/img/duty-message.png')} | |||||
| title="任务信息" | |||||
| ></SubAreaTitle> | |||||
| </div> | |||||
| <Form.Item label="镜像" required> | |||||
| <div className={styles['pipeline-drawer__ref-row']}> | |||||
| <Form.Item name="image" noStyle rules={[{ required: true, message: '请输入镜像' }]}> | |||||
| <Input placeholder="请输入或选择镜像" allowClear /> | |||||
| </Form.Item> | |||||
| <Form.Item noStyle> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| icon={getSelectBtnIcon({ item_type: 'image' })} | |||||
| onClick={() => selectResource('image', { item_type: 'image' })} | |||||
| className={styles['pipeline-drawer__ref-row__select-button']} | |||||
| > | |||||
| 选择镜像 | |||||
| </Button> | |||||
| </Form.Item> | |||||
| </div> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| name="working_directory" | |||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title="工作目录" | |||||
| onClick={(value) => { | |||||
| handleParameterClick('working_directory', value); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| > | |||||
| <Input placeholder="请输入工作目录" allowClear /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| name="command" | |||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title="启动命令" | |||||
| onClick={(value) => { | |||||
| handleParameterClick('command', value); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| > | |||||
| <TextArea placeholder="请输入启动命令" allowClear /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="资源规格" | |||||
| name="resources_standard" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请选择资源规格', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <ParameterSelect dataType="resource" placeholder="请选择资源规格" /> | |||||
| </Form.Item> | |||||
| {/* <Form.Item | |||||
| name="mount_path" | |||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title="挂载路径" | |||||
| onClick={(value) => { | |||||
| handleParameterClick('mount_path', value); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| > | |||||
| <Input placeholder="请输入挂载路径" allowClear /> | |||||
| </Form.Item> */} | |||||
| <Form.Item | |||||
| name="env_variables" | |||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title="环境变量" | |||||
| onClick={(value) => { | |||||
| handleParameterClick('env_variables', value); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| > | |||||
| <TextArea placeholder="请输入环境变量" allowClear /> | |||||
| </Form.Item> | |||||
| {/* 控制参数 */} | |||||
| {/* {controlStrategyList.map((item) => ( | |||||
| {basicParametersList.length + controlStrategyList.length > 0 && ( | |||||
| <div className={styles['pipeline-drawer__title']}> | |||||
| <SubAreaTitle | |||||
| image={require('@/assets/img/duty-message.png')} | |||||
| title="任务信息" | |||||
| ></SubAreaTitle> | |||||
| </div> | |||||
| )} | |||||
| {/* 基本参数 */} | |||||
| {basicParametersList.map((item) => ( | |||||
| <Form.Item | <Form.Item | ||||
| key={item.key} | key={item.key} | ||||
| name={['control_strategy', item.key]} | |||||
| label={getLabel(item, 'task_info')} | |||||
| required={item.value.require ? true : false} | required={item.value.require ? true : false} | ||||
| > | |||||
| {getFormComponent(item, 'task_info')} | |||||
| </Form.Item> | |||||
| ))} | |||||
| {/* 控制参数 */} | |||||
| {controlStrategyList.map((item) => ( | |||||
| <Form.Item | |||||
| key={item.key} | |||||
| label={getLabel(item, 'control_strategy')} | label={getLabel(item, 'control_strategy')} | ||||
| rules={getFormRules(item)} | |||||
| required={item.value.require ? true : false} | |||||
| > | > | ||||
| <ParameterInput placeholder={item.value.placeholder} allowClear></ParameterInput> | |||||
| {getFormComponent(item, 'control_strategy')} | |||||
| </Form.Item> | </Form.Item> | ||||
| ))} */} | |||||
| </> | |||||
| )} | |||||
| ))} | |||||
| {/* 输入参数 */} | {/* 输入参数 */} | ||||
| {inParametersList.length > 0 && ( | {inParametersList.length > 0 && ( | ||||
| @@ -579,42 +697,12 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||||
| label={getLabel(item, 'in_parameters')} | label={getLabel(item, 'in_parameters')} | ||||
| required={item.value.require ? true : false} | required={item.value.require ? true : false} | ||||
| > | > | ||||
| <div className={styles['pipeline-drawer__ref-row']}> | |||||
| <Form.Item name={['in_parameters', item.key]} rules={getFormRules(item)} noStyle> | |||||
| {item.value.type === 'select' ? ( | |||||
| ['dataset', 'model', 'service', 'resource'].includes(item.value.item_type) ? ( | |||||
| <ParameterSelect | |||||
| isPipeline | |||||
| dataType={item.value.item_type as ParameterSelectDataType} | |||||
| placeholder={item.value.placeholder} | |||||
| /> | |||||
| ) : null | |||||
| ) : ( | |||||
| <ParameterInput | |||||
| canInput={canInput(item.value)} | |||||
| placeholder={item.value.placeholder} | |||||
| allowClear | |||||
| ></ParameterInput> | |||||
| )} | |||||
| </Form.Item> | |||||
| {item.value.type === 'ref' && ( | |||||
| <Form.Item noStyle> | |||||
| <Button | |||||
| size="small" | |||||
| type="link" | |||||
| icon={getSelectBtnIcon(item.value)} | |||||
| onClick={() => selectRefData(['in_parameters', item.key], item.value)} | |||||
| className={styles['pipeline-drawer__ref-row__select-button']} | |||||
| > | |||||
| {item.value.label} | |||||
| </Button> | |||||
| </Form.Item> | |||||
| )} | |||||
| </div> | |||||
| {getFormComponent(item, 'in_parameters')} | |||||
| </Form.Item> | </Form.Item> | ||||
| ))} | ))} | ||||
| </> | </> | ||||
| )} | )} | ||||
| {/* 输出参数 */} | {/* 输出参数 */} | ||||
| {outParametersList.length > 0 && ( | {outParametersList.length > 0 && ( | ||||
| <> | <> | ||||
| @@ -24,7 +24,7 @@ const { TextArea } = Input; | |||||
| const Pipeline = () => { | const Pipeline = () => { | ||||
| const [form] = Form.useForm(); | const [form] = Form.useForm(); | ||||
| const navigate = useNavigate(); | const navigate = useNavigate(); | ||||
| const [formId, setFormId] = useState(null); | |||||
| const [editRecord, setEditRecord] = useState(null); | |||||
| const [dialogTitle, setDialogTitle] = useState('新建流水线'); | const [dialogTitle, setDialogTitle] = useState('新建流水线'); | ||||
| const [pipeList, setPipeList] = useState([]); | const [pipeList, setPipeList] = useState([]); | ||||
| const [total, setTotal] = useState(0); | const [total, setTotal] = useState(0); | ||||
| @@ -75,7 +75,7 @@ const Pipeline = () => { | |||||
| if (ret.code === 200) { | if (ret.code === 200) { | ||||
| form.resetFields(); | form.resetFields(); | ||||
| form.setFieldsValue({ ...ret.data }); | form.setFieldsValue({ ...ret.data }); | ||||
| setFormId(ret.data.id); | |||||
| setEditRecord(ret.data); | |||||
| setDialogTitle('编辑流水线'); | setDialogTitle('编辑流水线'); | ||||
| setIsModalOpen(true); | setIsModalOpen(true); | ||||
| } | } | ||||
| @@ -99,7 +99,7 @@ const Pipeline = () => { | |||||
| // 显示 modal | // 显示 modal | ||||
| const showModal = () => { | const showModal = () => { | ||||
| form.resetFields(); | form.resetFields(); | ||||
| setFormId(null); | |||||
| setEditRecord(null); | |||||
| setDialogTitle('新建流水线'); | setDialogTitle('新建流水线'); | ||||
| setIsModalOpen(true); | setIsModalOpen(true); | ||||
| }; | }; | ||||
| @@ -111,8 +111,8 @@ const Pipeline = () => { | |||||
| // 表单提交 | // 表单提交 | ||||
| const onFinish = (values) => { | const onFinish = (values) => { | ||||
| if (formId) { | |||||
| editWorkflow({ ...values, id: formId }).then((ret) => { | |||||
| if (editRecord) { | |||||
| editWorkflow({ ...editRecord, ...values }).then((ret) => { | |||||
| setIsModalOpen(false); | setIsModalOpen(false); | ||||
| message.success('编辑成功'); | message.success('编辑成功'); | ||||
| getList(); | getList(); | ||||
| @@ -58,6 +58,14 @@ export function addDatasetVersion(data) { | |||||
| }); | }); | ||||
| } | } | ||||
| // 编辑数据集版本 | |||||
| export function editDatasetVersion(data) { | |||||
| return request(`/api/mmp/newdataset/updateVersionDesc`, { | |||||
| method: 'PUT', | |||||
| data, | |||||
| }); | |||||
| } | |||||
| // 下载数据集所有文件 | // 下载数据集所有文件 | ||||
| export function downloadAllFiles(params) { | export function downloadAllFiles(params) { | ||||
| return request(`/api/mmp/newdataset/downloadAllFiles`, { | return request(`/api/mmp/newdataset/downloadAllFiles`, { | ||||
| @@ -82,6 +90,15 @@ export function compareDatasetVersion(data) { | |||||
| }); | }); | ||||
| } | } | ||||
| // 获取数据集下个版本号 | |||||
| export function getDatasetNextVersionReq(data) { | |||||
| return request(`/api/mmp/newdataset/queryNextVersion`, { | |||||
| method: 'POST', | |||||
| data, | |||||
| }); | |||||
| } | |||||
| // ----------------------------模型--------------------------------- | // ----------------------------模型--------------------------------- | ||||
| // 分页查询模型列表 | // 分页查询模型列表 | ||||
| @@ -132,6 +149,15 @@ export function addModelVersion(data) { | |||||
| }); | }); | ||||
| } | } | ||||
| // 编辑模型版本 | |||||
| export function editModelVersion(data) { | |||||
| return request(`/api/mmp/newmodel/updateVersionDesc`, { | |||||
| method: 'PUT', | |||||
| data, | |||||
| }); | |||||
| } | |||||
| // 删除模型版本 | // 删除模型版本 | ||||
| export function deleteModelVersion(params) { | export function deleteModelVersion(params) { | ||||
| return request(`/api/mmp/newmodel/deleteVersion`, { | return request(`/api/mmp/newmodel/deleteVersion`, { | ||||
| @@ -140,6 +166,14 @@ export function deleteModelVersion(params) { | |||||
| }); | }); | ||||
| } | } | ||||
| // 获取模型下个版本号 | |||||
| export function getModelNextVersionReq(data) { | |||||
| return request(`/api/mmp/newmodel/queryNextVersion`, { | |||||
| method: 'POST', | |||||
| data, | |||||
| }); | |||||
| } | |||||
| // 获取模型依赖 | // 获取模型依赖 | ||||
| export function getModelAtlasReq(params) { | export function getModelAtlasReq(params) { | ||||
| return request(`/api/mmp/newmodel/getModelDependencyTree`, { | return request(`/api/mmp/newmodel/getModelDependencyTree`, { | ||||
| @@ -201,4 +235,5 @@ export function unpraiseResourceReq(id) { | |||||
| return request(`/api/mmp/newmodel/unpraise/${id}`, { | return request(`/api/mmp/newmodel/unpraise/${id}`, { | ||||
| method: 'DELETE', | method: 'DELETE', | ||||
| }); | }); | ||||
| } | |||||
| } | |||||
| @@ -0,0 +1,89 @@ | |||||
| // 外部系统 | |||||
| import { request } from '@umijs/max'; | |||||
| const jccBaseUrl = 'https://jcc.jointcloud.net'; | |||||
| // 云际系统登录 | |||||
| export function jccLoginReq() { | |||||
| return request(`${jccBaseUrl}/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(`${jccBaseUrl}/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(`${jccBaseUrl}/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(`${jccBaseUrl}/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; | |||||
| @@ -29,11 +29,17 @@ export type GlobalInitialState = { | |||||
| clientInfo?: ClientInfo; | clientInfo?: ClientInfo; | ||||
| }; | }; | ||||
| export enum PipelineGlobalParamType { | |||||
| String = 1, | |||||
| Number = 2, | |||||
| Boolean = 3, | |||||
| } | |||||
| // 流水线全局参数 | // 流水线全局参数 | ||||
| export type PipelineGlobalParam = { | export type PipelineGlobalParam = { | ||||
| param_name: string; | param_name: string; | ||||
| description: string; | description: string; | ||||
| param_type: number; | |||||
| param_type: PipelineGlobalParamType; | |||||
| param_value: number | string | boolean; | param_value: number | string | boolean; | ||||
| is_sensitive: number; | is_sensitive: number; | ||||
| }; | }; | ||||
| @@ -66,9 +72,10 @@ export type PipelineNodeModel = { | |||||
| label: string; | label: string; | ||||
| experimentStartTime: string; | experimentStartTime: string; | ||||
| experimentEndTime?: string | null; | experimentEndTime?: string | null; | ||||
| control_strategy: string; | |||||
| in_parameters: string; | |||||
| out_parameters: string; | |||||
| control_strategy: Record<string, PipelineNodeModelParameter>; | |||||
| in_parameters: Record<string, PipelineNodeModelParameter>; | |||||
| task_info: Record<string, PipelineNodeModelParameter>; | |||||
| out_parameters: Record<string, PipelineNodeModelParameter>; | |||||
| component_label: string; | component_label: string; | ||||
| icon_path: string; | icon_path: string; | ||||
| workflowId?: string; | workflowId?: string; | ||||
| @@ -82,11 +89,12 @@ export type PipelineNodeModelParameter = { | |||||
| label: string; | label: string; | ||||
| value: any; | value: any; | ||||
| require?: number; | require?: number; | ||||
| visible: boolean; | |||||
| placeholder?: string; | placeholder?: string; | ||||
| describe?: string; | describe?: string; | ||||
| fromSelect?: boolean; | |||||
| showValue?: any; | |||||
| editable?: number; | |||||
| editable?: boolean; | |||||
| showValue?: any; // 前端显示用 | |||||
| fromSelect?: boolean; // 前端显示用 | |||||
| activeTab?: string; // ResourceSelectorModal tab | activeTab?: string; // ResourceSelectorModal tab | ||||
| expandedKeys?: string[]; // ResourceSelectorModal expandedKeys | expandedKeys?: string[]; // ResourceSelectorModal expandedKeys | ||||
| checkedKeys?: string[]; // ResourceSelectorModal checkedKeys | checkedKeys?: string[]; // ResourceSelectorModal checkedKeys | ||||
| @@ -110,14 +118,7 @@ export type KeysToCamelCase<T> = { | |||||
| }; | }; | ||||
| // 序列化后的流水线节点 | // 序列化后的流水线节点 | ||||
| export type PipelineNodeModelSerialize = Omit< | |||||
| PipelineNodeModel, | |||||
| 'in_parameters' | 'out_parameters' | |||||
| > & { | |||||
| // control_strategy: Record<string, PipelineNodeModelParameter>; | |||||
| in_parameters: Record<string, PipelineNodeModelParameter>; | |||||
| out_parameters: Record<string, PipelineNodeModelParameter>; | |||||
| }; | |||||
| export type PipelineNodeModelSerialize = PipelineNodeModel; | |||||
| // 资源规格 | // 资源规格 | ||||
| export type ComputingResource = { | export type ComputingResource = { | ||||
| @@ -1,4 +1,5 @@ | |||||
| import { BasicInfoLink } from '@/components/BasicInfo/types'; | import { BasicInfoLink } from '@/components/BasicInfo/types'; | ||||
| import { CodeConfigData } from '@/components/CodeSelectorModal'; | |||||
| import { ResourceSelectorResponse } from '@/components/ResourceSelectorModal'; | import { ResourceSelectorResponse } from '@/components/ResourceSelectorModal'; | ||||
| import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceInfo'; | import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceInfo'; | ||||
| import { | import { | ||||
| @@ -12,13 +13,6 @@ import { getGitUrl } from '@/utils'; | |||||
| // 格式化日期 | // 格式化日期 | ||||
| export { formatDate } from '@/utils/date'; | export { formatDate } from '@/utils/date'; | ||||
| export type SelectedCodeConfig = { | |||||
| code_path: string; | |||||
| branch: string; | |||||
| showValue?: string; // 前端使用的 | |||||
| show_value?: string; // 后端使用的 | |||||
| }; | |||||
| /** | /** | ||||
| * 格式化数据集数组 | * 格式化数据集数组 | ||||
| * | * | ||||
| @@ -77,7 +71,7 @@ export const formatMirror = (mirror: ResourceSelectorResponse): string | undefin | |||||
| if (!mirror) { | if (!mirror) { | ||||
| return undefined; | return undefined; | ||||
| } | } | ||||
| return mirror.path; | |||||
| return mirror.url; | |||||
| }; | }; | ||||
| /** | /** | ||||
| @@ -87,20 +81,20 @@ export const formatMirror = (mirror: ResourceSelectorResponse): string | undefin | |||||
| * @return 基本信息链接对象 | * @return 基本信息链接对象 | ||||
| */ | */ | ||||
| export const formatCodeConfig = ( | export const formatCodeConfig = ( | ||||
| project?: ProjectDependency | SelectedCodeConfig, | |||||
| project?: ProjectDependency | CodeConfigData, | |||||
| ): BasicInfoLink | undefined => { | ): BasicInfoLink | undefined => { | ||||
| if (!project) { | if (!project) { | ||||
| return undefined; | return undefined; | ||||
| } | } | ||||
| // 创建表单,CodeSelect 组件返回,目前有流水线、模型部署、超参数自动寻优创建时选择了代码配置 | |||||
| if ('code_path' in project) { | |||||
| const { showValue, show_value, code_path, branch } = project; | |||||
| // 创建表单,CodeSelect 组件返回,目前有流水线、超参数自动寻优、主动学习、开发环境创建时选择了代码配置 | |||||
| if ('code_repo_name' in project) { | |||||
| const { code_repo_name, git_url, git_branch } = project; | |||||
| return { | return { | ||||
| value: showValue || show_value, | |||||
| url: getGitUrl(code_path, branch), | |||||
| value: code_repo_name, | |||||
| url: getGitUrl(git_url, git_branch), | |||||
| }; | }; | ||||
| } else { | } else { | ||||
| // 数据集和模型的代码配置 | |||||
| // 数据集和模型详情的代码配置 | |||||
| const { url, branch, name } = project; | const { url, branch, name } = project; | ||||
| return { | return { | ||||
| value: name, | value: name, | ||||
| @@ -13,8 +13,13 @@ export default class SessionStorage { | |||||
| static readonly aimUrlKey = 'aim-url'; | static readonly aimUrlKey = 'aim-url'; | ||||
| /** tensorBoard url */ | /** tensorBoard url */ | ||||
| static readonly tensorBoardUrlKey = 'tensor-board-url'; | static readonly tensorBoardUrlKey = 'tensor-board-url'; | ||||
| <<<<<<< HEAD | |||||
| /** 登录之前的地址 */ | /** 登录之前的地址 */ | ||||
| static readonly redirectUrl = 'redirect-url'; | static readonly redirectUrl = 'redirect-url'; | ||||
| ======= | |||||
| // /** 云际系统 Token */ | |||||
| // static readonly jccTokenKey = 'jcc-token'; | |||||
| >>>>>>> dev-zw | |||||
| /** | /** | ||||
| * 获取 SessionStorage 值 | * 获取 SessionStorage 值 | ||||