Browse Source

feat: 添加资源选择控件

pull/99/head
cp3hnu 1 year ago
parent
commit
5d65155fda
7 changed files with 175 additions and 237 deletions
  1. +11
    -0
      react-ui/src/components/ResourceSelect/index.less
  2. +104
    -0
      react-ui/src/components/ResourceSelect/index.tsx
  3. +36
    -126
      react-ui/src/pages/DevelopmentEnvironment/Create/index.tsx
  4. +1
    -1
      react-ui/src/pages/DevelopmentEnvironment/List/index.tsx
  5. +17
    -108
      react-ui/src/pages/ModelDeployment/Create/index.tsx
  6. +4
    -0
      react-ui/src/pages/Pipeline/components/ResourceSelectorModal/config.tsx
  7. +2
    -2
      react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.tsx

+ 11
- 0
react-ui/src/components/ResourceSelect/index.less View File

@@ -0,0 +1,11 @@
.resource-select {
position: relative;
display: flex;
align-items: center;

&__button {
position: absolute;
top: 0;
left: calc(100% + 10px);
}
}

+ 104
- 0
react-ui/src/components/ResourceSelect/index.tsx View File

@@ -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;

+ 36
- 126
react-ui/src/pages/DevelopmentEnvironment/Create/index.tsx View File

@@ -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
- 1
react-ui/src/pages/DevelopmentEnvironment/List/index.tsx View File

@@ -1,7 +1,7 @@
/*
* @Author: 赵伟
* @Date: 2024-04-16 13:58:08
* @Description: 开发环境
* @Description: 开发环境列表
*/
import CommonTableCell from '@/components/CommonTableCell';
import DateTableCell from '@/components/DateTableCell';


+ 17
- 108
react-ui/src/pages/ModelDeployment/Create/index.tsx View File

@@ -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}>


+ 4
- 0
react-ui/src/pages/Pipeline/components/ResourceSelectorModal/config.tsx View File

@@ -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: '选择镜像',
},
};

+ 2
- 2
react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.tsx View File

@@ -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);
}
};



Loading…
Cancel
Save