| @@ -0,0 +1,11 @@ | |||
| .resource-select { | |||
| position: relative; | |||
| display: flex; | |||
| align-items: center; | |||
| &__button { | |||
| position: absolute; | |||
| top: 0; | |||
| left: calc(100% + 10px); | |||
| } | |||
| } | |||
| @@ -0,0 +1,104 @@ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import ResourceSelectorModal, { | |||
| ResourceSelectorResponse, | |||
| ResourceSelectorType, | |||
| selectorTypeConfig, | |||
| } from '@/pages/Pipeline/components/ResourceSelectorModal'; | |||
| import { openAntdModal } from '@/utils/modal'; | |||
| import { Button } from 'antd'; | |||
| import { useState } from 'react'; | |||
| import ParameterInput, { type ParameterInputProps } from '../ParameterInput'; | |||
| import styles from './index.less'; | |||
| export { requiredValidator, type ParameterInputObject } from '../ParameterInput'; | |||
| type ResourceSelectProps = { | |||
| type: ResourceSelectorType; | |||
| } & ParameterInputProps; | |||
| // 获取选择数据集、模型后面按钮 icon | |||
| const getSelectBtnIcon = (type: ResourceSelectorType) => { | |||
| return <KFIcon type={selectorTypeConfig[type].buttonIcon} font={16} />; | |||
| }; | |||
| function ResourceSelect({ type, value, onChange, ...rest }: ResourceSelectProps) { | |||
| const [selectedResource, setSelectedResource] = useState<ResourceSelectorResponse | undefined>( | |||
| undefined, | |||
| ); | |||
| const selectResource = () => { | |||
| const resource = selectedResource; | |||
| const { close } = openAntdModal(ResourceSelectorModal, { | |||
| type, | |||
| defaultExpandedKeys: resource ? [resource.id] : [], | |||
| defaultCheckedKeys: resource ? [`${resource.id}-${resource.version}`] : [], | |||
| defaultActiveTab: resource?.activeTab, | |||
| onOk: (res) => { | |||
| setSelectedResource(res); | |||
| if (res) { | |||
| const { activeTab, id, name, version, path } = res; | |||
| if (type === ResourceSelectorType.Mirror) { | |||
| onChange?.({ | |||
| value: path, | |||
| showValue: path, | |||
| fromSelect: true, | |||
| activeTab, | |||
| expandedKeys: [`${id}`], | |||
| checkedKeys: [`${id}-${version}`], | |||
| }); | |||
| } else { | |||
| const jsonObj = { | |||
| id, | |||
| version, | |||
| path, | |||
| }; | |||
| const jsonObjStr = JSON.stringify(jsonObj); | |||
| const showValue = `${name}:${version}`; | |||
| onChange?.({ | |||
| value: jsonObjStr, | |||
| showValue, | |||
| fromSelect: true, | |||
| activeTab, | |||
| expandedKeys: [`${id}`], | |||
| checkedKeys: [`${id}-${version}`], | |||
| ...jsonObj, | |||
| }); | |||
| } | |||
| } else { | |||
| onChange?.({ | |||
| value: undefined, | |||
| showValue: undefined, | |||
| fromSelect: false, | |||
| activeTab: undefined, | |||
| expandedKeys: [], | |||
| checkedKeys: [], | |||
| }); | |||
| } | |||
| close(); | |||
| }, | |||
| }); | |||
| }; | |||
| return ( | |||
| <div className={styles['resource-select']}> | |||
| <ParameterInput | |||
| {...rest} | |||
| value={value} | |||
| onChange={onChange} | |||
| onRemove={() => setSelectedResource(undefined)} | |||
| onClick={selectResource} | |||
| ></ParameterInput> | |||
| <Button | |||
| className={styles['resource-select__button']} | |||
| size="large" | |||
| type="link" | |||
| icon={getSelectBtnIcon(type)} | |||
| onClick={selectResource} | |||
| > | |||
| {selectorTypeConfig[type].buttontTitle} | |||
| </Button> | |||
| </div> | |||
| ); | |||
| } | |||
| export default ResourceSelect; | |||
| @@ -1,35 +1,32 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-16 13:58:08 | |||
| * @Description: 创建镜像 | |||
| * @Description: 创建开发环境 | |||
| */ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import KFRadio, { type KFRadioItem } from '@/components/KFRadio'; | |||
| import PageTitle from '@/components/PageTitle'; | |||
| import ParameterInput from '@/components/ParameterInput'; | |||
| import ResourceSelect, { | |||
| requiredValidator, | |||
| type ParameterInputObject, | |||
| } from '@/components/ResourceSelect'; | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { useComputingResource } from '@/hooks/resource'; | |||
| import ResourceSelectorModal, { | |||
| ResourceSelectorResponse, | |||
| ResourceSelectorType, | |||
| selectorTypeConfig, | |||
| } from '@/pages/Pipeline/components/ResourceSelectorModal'; | |||
| import { ResourceSelectorType } from '@/pages/Pipeline/components/ResourceSelectorModal'; | |||
| import { createEditorReq } from '@/services/developmentEnvironment'; | |||
| import { openAntdModal } from '@/utils/modal'; | |||
| import { to } from '@/utils/promise'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import { App, Button, Col, Form, Input, Row, Select } from 'antd'; | |||
| import { pick } from 'lodash'; | |||
| import { useState } from 'react'; | |||
| import { omit, pick } from 'lodash'; | |||
| import styles from './index.less'; | |||
| type FormData = { | |||
| name: string; | |||
| computing_resource: string; | |||
| standard: string; | |||
| image: string; | |||
| model: ResourceSelectorResponse; | |||
| dataset: ResourceSelectorResponse; | |||
| image: ParameterInputObject; | |||
| model: ParameterInputObject; | |||
| dataset: ParameterInputObject; | |||
| }; | |||
| enum ComputingResourceType { | |||
| @@ -55,25 +52,20 @@ function EditorCreate() { | |||
| const [form] = Form.useForm(); | |||
| const { message } = App.useApp(); | |||
| const [resourceStandardList, filterResourceStandard] = useComputingResource(); | |||
| const [selectedModel, setSelectedModel] = useState<ResourceSelectorResponse | undefined>( | |||
| undefined, | |||
| ); // 选择的模型,为了再次打开时恢复原来的选择 | |||
| const [selectedDataset, setSelectedDataset] = useState<ResourceSelectorResponse | undefined>( | |||
| undefined, | |||
| ); // 选择的数据集,为了再次打开时恢复原来的选择 | |||
| const [selectedMirror, setSelectedMirror] = useState<ResourceSelectorResponse | undefined>( | |||
| undefined, | |||
| ); // 选择的镜像,为了再次打开时恢复原来的选择 | |||
| // 创建编辑器 | |||
| const createEditor = async (formData: FormData) => { | |||
| // const { model, dataset } = formData; | |||
| // const params = { | |||
| // ...formData, | |||
| // model: JSON.stringify(omit(model, ['showValue'])), | |||
| // dataset: JSON.stringify(dataset, ['showValue']), | |||
| // }; | |||
| const [res] = await to(createEditorReq(formData)); | |||
| // 根据后台要求,修改表单数据 | |||
| const image = formData['image']; | |||
| const model = formData['model']; | |||
| const dataset = formData['dataset']; | |||
| const params = { | |||
| ...omit(formData, ['image', 'model', 'dataset']), | |||
| image: image.value, | |||
| model: pick(model, ['id', 'version', 'path', 'showValue']), | |||
| dataset: pick(dataset, ['id', 'version', 'path', 'showValue']), | |||
| }; | |||
| const [res] = await to(createEditorReq(params)); | |||
| if (res) { | |||
| message.success('创建成功'); | |||
| navgite(-1); | |||
| @@ -89,61 +81,6 @@ function EditorCreate() { | |||
| const cancel = () => { | |||
| navgite(-1); | |||
| }; | |||
| // 获取选择数据集、模型后面按钮 icon | |||
| const getSelectBtnIcon = (type: ResourceSelectorType) => { | |||
| return <KFIcon type={selectorTypeConfig[type].buttonIcon} font={16} />; | |||
| }; | |||
| // 选择模型、镜像、数据集 | |||
| const selectResource = (name: string, type: ResourceSelectorType) => { | |||
| let resource: ResourceSelectorResponse | undefined; | |||
| switch (type) { | |||
| case ResourceSelectorType.Model: | |||
| resource = selectedModel; | |||
| break; | |||
| case ResourceSelectorType.Dataset: | |||
| resource = selectedDataset; | |||
| break; | |||
| default: | |||
| resource = selectedMirror; | |||
| break; | |||
| } | |||
| const { close } = openAntdModal(ResourceSelectorModal, { | |||
| type, | |||
| defaultExpandedKeys: resource ? [resource.id] : [], | |||
| defaultCheckedKeys: resource ? [`${resource.id}-${resource.version}`] : [], | |||
| defaultActiveTab: resource?.activeTab, | |||
| onOk: (res) => { | |||
| if (res) { | |||
| if (type === ResourceSelectorType.Mirror) { | |||
| form.setFieldValue(name, res.path); | |||
| setSelectedMirror(res); | |||
| } else { | |||
| const showValue = `${res.name}:${res.version}`; | |||
| form.setFieldValue(name, { | |||
| ...pick(res, ['id', 'version', 'path']), | |||
| showValue, | |||
| }); | |||
| if (type === ResourceSelectorType.Model) { | |||
| setSelectedModel(res); | |||
| } else if (type === ResourceSelectorType.Dataset) { | |||
| setSelectedDataset(res); | |||
| } | |||
| } | |||
| } else { | |||
| if (type === ResourceSelectorType.Model) { | |||
| setSelectedModel(undefined); | |||
| } else if (type === ResourceSelectorType.Dataset) { | |||
| setSelectedDataset(undefined); | |||
| } else if (type === ResourceSelectorType.Mirror) { | |||
| setSelectedMirror(undefined); | |||
| } | |||
| form.setFieldValue(name, ''); | |||
| } | |||
| close(); | |||
| }, | |||
| }); | |||
| }; | |||
| return ( | |||
| <div className={styles['editor-create']}> | |||
| @@ -230,64 +167,46 @@ function EditorCreate() { | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="镜像" | |||
| label="镜 像" | |||
| name="image" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入镜像', | |||
| validator: requiredValidator, | |||
| message: '请选择镜像', | |||
| }, | |||
| ]} | |||
| required | |||
| > | |||
| <ParameterInput | |||
| <ResourceSelect | |||
| type={ResourceSelectorType.Mirror} | |||
| placeholder="请选择镜像" | |||
| canInput={false} | |||
| size="large" | |||
| onClick={() => selectResource('image', ResourceSelectorType.Mirror)} | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| <Col span={10}> | |||
| <Button | |||
| size="large" | |||
| type="link" | |||
| icon={getSelectBtnIcon(ResourceSelectorType.Mirror)} | |||
| onClick={() => selectResource('image', ResourceSelectorType.Mirror)} | |||
| > | |||
| 选择镜像 | |||
| </Button> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="模型" | |||
| label="模 型" | |||
| name="model" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| validator: requiredValidator, | |||
| message: '请选择模型', | |||
| }, | |||
| ]} | |||
| required | |||
| > | |||
| <ParameterInput | |||
| <ResourceSelect | |||
| type={ResourceSelectorType.Model} | |||
| placeholder="请选择模型" | |||
| canInput={false} | |||
| size="large" | |||
| onClick={() => selectResource('model', ResourceSelectorType.Model)} | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| <Col span={10}> | |||
| <Button | |||
| size="large" | |||
| type="link" | |||
| icon={getSelectBtnIcon(ResourceSelectorType.Model)} | |||
| onClick={() => selectResource('model', ResourceSelectorType.Model)} | |||
| > | |||
| 选择模型 | |||
| </Button> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| @@ -296,29 +215,20 @@ function EditorCreate() { | |||
| name="dataset" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| validator: requiredValidator, | |||
| message: '请选择数据集', | |||
| }, | |||
| ]} | |||
| required | |||
| > | |||
| <ParameterInput | |||
| <ResourceSelect | |||
| type={ResourceSelectorType.Dataset} | |||
| placeholder="请选择数据集" | |||
| canInput={false} | |||
| size="large" | |||
| onClick={() => selectResource('dataset', ResourceSelectorType.Dataset)} | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| <Col span={10}> | |||
| <Button | |||
| size="large" | |||
| type="link" | |||
| icon={getSelectBtnIcon(ResourceSelectorType.Dataset)} | |||
| onClick={() => selectResource('dataset', ResourceSelectorType.Dataset)} | |||
| > | |||
| 选择数据集 | |||
| </Button> | |||
| </Col> | |||
| </Row> | |||
| <Form.Item wrapperCol={{ offset: 0, span: 16 }}> | |||
| @@ -1,7 +1,7 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-16 13:58:08 | |||
| * @Description: 开发环境 | |||
| * @Description: 开发环境列表 | |||
| */ | |||
| import CommonTableCell from '@/components/CommonTableCell'; | |||
| import DateTableCell from '@/components/DateTableCell'; | |||
| @@ -5,22 +5,20 @@ | |||
| */ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import PageTitle from '@/components/PageTitle'; | |||
| import ParameterInput, { requiredValidator } from '@/components/ParameterInput'; | |||
| import ResourceSelect, { | |||
| requiredValidator, | |||
| type ParameterInputObject, | |||
| } from '@/components/ResourceSelect'; | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { CommonTabKeys } from '@/enums'; | |||
| import { useComputingResource } from '@/hooks/resource'; | |||
| import ResourceSelectorModal, { | |||
| ResourceSelectorResponse, | |||
| ResourceSelectorType, | |||
| selectorTypeConfig, | |||
| } from '@/pages/Pipeline/components/ResourceSelectorModal'; | |||
| import { ResourceSelectorType } from '@/pages/Pipeline/components/ResourceSelectorModal'; | |||
| import { | |||
| createModelDeploymentReq, | |||
| restartModelDeploymentReq, | |||
| updateModelDeploymentReq, | |||
| } from '@/services/modelDeployment'; | |||
| import { camelCaseToUnderscore, underscoreToCamelCase } from '@/utils'; | |||
| import { openAntdModal } from '@/utils/modal'; | |||
| import { to } from '@/utils/promise'; | |||
| import { | |||
| getSessionStorageItem, | |||
| @@ -39,13 +37,8 @@ import styles from './index.less'; | |||
| export type FormData = { | |||
| serviceName: string; // 服务名称 | |||
| description: string; // 描述 | |||
| model: { | |||
| id: number; | |||
| version: string; | |||
| value: string; | |||
| showValue: string; | |||
| }; // 模型 | |||
| image: string; // 镜像 | |||
| model: ParameterInputObject; // 模型 | |||
| image: ParameterInputObject; // 镜像 | |||
| resource: string; // 资源规格 | |||
| replicas: string; // 副本数量 | |||
| modelPath: string; // 模型路径 | |||
| @@ -56,16 +49,10 @@ function ModelDeploymentCreate() { | |||
| const navgite = useNavigate(); | |||
| const [form] = Form.useForm(); | |||
| const [resourceStandardList, filterResourceStandard] = useComputingResource(); | |||
| const [selectedModel, setSelectedModel] = useState<ResourceSelectorResponse | undefined>( | |||
| undefined, | |||
| ); // 选择的模型,为了再次打开时恢复原来的选择 | |||
| const [operationType, setOperationType] = useState(ModelDeploymentOperationType.Create); | |||
| const [modelDeploymentInfo, setModelDeploymentInfo] = useState<ModelDeploymentData | undefined>( | |||
| undefined, | |||
| ); | |||
| const [selectedMirror, setSelectedMirror] = useState<ResourceSelectorResponse | undefined>( | |||
| undefined, | |||
| ); // 选择的镜像,为了再次打开时恢复原来的选择 | |||
| const { message } = App.useApp(); | |||
| useEffect(() => { | |||
| @@ -81,78 +68,23 @@ function ModelDeploymentCreate() { | |||
| }; | |||
| }, []); | |||
| // 获取选择数据集、模型后面按钮 icon | |||
| const getSelectBtnIcon = (type: ResourceSelectorType) => { | |||
| return <KFIcon type={selectorTypeConfig[type].buttonIcon} font={16} />; | |||
| }; | |||
| // 选择模型、镜像 | |||
| const selectResource = (formItemName: string, type: ResourceSelectorType) => { | |||
| let resource: ResourceSelectorResponse | undefined; | |||
| switch (type) { | |||
| case ResourceSelectorType.Model: | |||
| resource = selectedModel; | |||
| break; | |||
| default: | |||
| resource = selectedMirror; | |||
| break; | |||
| } | |||
| const { close } = openAntdModal(ResourceSelectorModal, { | |||
| type, | |||
| defaultExpandedKeys: resource ? [resource.id] : [], | |||
| defaultCheckedKeys: resource ? [`${resource.id}-${resource.version}`] : [], | |||
| defaultActiveTab: resource?.activeTab, | |||
| onOk: (res) => { | |||
| if (res) { | |||
| if (type === ResourceSelectorType.Mirror) { | |||
| form.setFieldValue(formItemName, res.path); | |||
| setSelectedMirror(res); | |||
| } else { | |||
| const { activeTab, id, name, version, path } = res; | |||
| const jsonObj = { | |||
| id, | |||
| version, | |||
| path, | |||
| }; | |||
| const value = JSON.stringify(jsonObj); | |||
| const showValue = `${name}:${version}`; | |||
| form.setFieldValue(formItemName, { | |||
| value, | |||
| showValue, | |||
| fromSelect: true, | |||
| activeTab, | |||
| expandedKeys: [id], | |||
| checkedKeys: [`${id}-${version}`], | |||
| ...jsonObj, | |||
| }); | |||
| setSelectedModel(res); | |||
| } | |||
| } else { | |||
| if (type === ResourceSelectorType.Model) { | |||
| setSelectedModel(undefined); | |||
| } else { | |||
| setSelectedMirror(undefined); | |||
| } | |||
| form.setFieldValue(formItemName, ''); | |||
| } | |||
| form.validateFields([formItemName]); | |||
| close(); | |||
| }, | |||
| }); | |||
| }; | |||
| // 创建 | |||
| const createModelDeployment = async (formData: FormData) => { | |||
| const envList = formData['env'] ?? []; | |||
| const image = formData['image']; | |||
| const model = formData['model']; | |||
| const env = envList.reduce((acc, cur) => { | |||
| acc[cur.key] = cur.value; | |||
| return acc; | |||
| }, {} as Record<string, string>); | |||
| // 根据后台要求,修改表单数据 | |||
| const object = camelCaseToUnderscore({ | |||
| ...omit(formData, ['replicas', 'env']), | |||
| ...omit(formData, ['replicas', 'env', 'image', 'model']), | |||
| replicas: Number(formData.replicas), | |||
| env, | |||
| image: image.value, | |||
| model: pick(model, ['id', 'version', 'path', 'showValue']), | |||
| }); | |||
| const params = | |||
| @@ -277,27 +209,15 @@ function ModelDeploymentCreate() { | |||
| ]} | |||
| required | |||
| > | |||
| <ParameterInput | |||
| <ResourceSelect | |||
| type={ResourceSelectorType.Model} | |||
| placeholder="请选择模型" | |||
| disabled={disabled} | |||
| canInput={false} | |||
| size="large" | |||
| onClick={() => selectResource('model', ResourceSelectorType.Model)} | |||
| onChange={() => setSelectedModel(undefined)} | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| <Col span={10}> | |||
| <Button | |||
| disabled={disabled} | |||
| size="large" | |||
| type="link" | |||
| icon={getSelectBtnIcon(ResourceSelectorType.Model)} | |||
| onClick={() => selectResource('model', ResourceSelectorType.Model)} | |||
| > | |||
| 选择模型 | |||
| </Button> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| @@ -312,25 +232,14 @@ function ModelDeploymentCreate() { | |||
| ]} | |||
| required | |||
| > | |||
| <ParameterInput | |||
| <ResourceSelect | |||
| type={ResourceSelectorType.Mirror} | |||
| placeholder="请选择镜像" | |||
| canInput={false} | |||
| size="large" | |||
| onClick={() => selectResource('image', ResourceSelectorType.Mirror)} | |||
| onChange={() => setSelectedMirror(undefined)} | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| <Col span={10}> | |||
| <Button | |||
| size="large" | |||
| type="link" | |||
| icon={getSelectBtnIcon(ResourceSelectorType.Mirror)} | |||
| onClick={() => selectResource('image', ResourceSelectorType.Mirror)} | |||
| > | |||
| 选择镜像 | |||
| </Button> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| @@ -37,6 +37,7 @@ export type SelectorTypeInfo = { | |||
| litReqParamKey: 'available_range' | 'image_type'; // 表示是公开还是私有的参数名称,获取资源列表接口使用 | |||
| fileReqParamKey: 'models_id' | 'dataset_id'; // 文件请求参数名称,获取文件列表接口使用 | |||
| tabItems: TabsProps['items']; // tab 列表 | |||
| buttontTitle: string; // 按钮 title | |||
| }; | |||
| // 获取镜像文件列表,为了兼容数据集和模型 | |||
| @@ -77,6 +78,7 @@ export const selectorTypeConfig: Record<ResourceSelectorType, SelectorTypeInfo> | |||
| label: '公开模型', | |||
| }, | |||
| ], | |||
| buttontTitle: '选择模型', | |||
| }, | |||
| [ResourceSelectorType.Dataset]: { | |||
| getList: getDatasetList, | |||
| @@ -98,6 +100,7 @@ export const selectorTypeConfig: Record<ResourceSelectorType, SelectorTypeInfo> | |||
| label: '公开数据集', | |||
| }, | |||
| ], | |||
| buttontTitle: '选择数据集', | |||
| }, | |||
| [ResourceSelectorType.Mirror]: { | |||
| getList: getMirrorListReq, | |||
| @@ -121,5 +124,6 @@ export const selectorTypeConfig: Record<ResourceSelectorType, SelectorTypeInfo> | |||
| label: '公开镜像', | |||
| }, | |||
| ], | |||
| buttontTitle: '选择镜像', | |||
| }, | |||
| }; | |||
| @@ -39,7 +39,7 @@ export interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> { | |||
| defaultExpandedKeys?: React.Key[]; | |||
| defaultCheckedKeys?: React.Key[]; | |||
| defaultActiveTab?: CommonTabKeys; | |||
| onOk?: (params: ResourceSelectorResponse | null) => void; | |||
| onOk?: (params: ResourceSelectorResponse | undefined) => void; | |||
| } | |||
| type TreeRef = GetRef<typeof Tree<TreeDataNode>>; | |||
| @@ -279,7 +279,7 @@ function ResourceSelectorModal({ | |||
| }; | |||
| onOk?.(res); | |||
| } else { | |||
| onOk?.(null); | |||
| onOk?.(undefined); | |||
| } | |||
| }; | |||