|
- /*
- * @Author: 赵伟
- * @Date: 2024-04-16 13:58:08
- * @Description: 创建模型部署
- */
- import KFIcon from '@/components/KFIcon';
- import PageTitle from '@/components/PageTitle';
- import ParameterInput from '@/components/ParameterInput';
- 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 {
- createModelDeploymentReq,
- restartModelDeploymentReq,
- updateModelDeploymentReq,
- } from '@/services/modelDeployment';
- import { camelCaseToUnderscore, underscoreToCamelCase } from '@/utils';
- import { openAntdModal } from '@/utils/modal';
- import { to } from '@/utils/promise';
- import {
- getSessionStorageItem,
- modelDeploymentInfoKey,
- removeSessionStorageItem,
- } from '@/utils/sessionStorage';
- import { modalConfirm } from '@/utils/ui';
- import { useNavigate } from '@umijs/max';
- import { App, Button, Col, Flex, Form, Input, Row, Select } from 'antd';
- import { omit, pick } from 'lodash';
- import { useEffect, useState } from 'react';
- import { ModelDeploymentData, ModelDeploymentOperationType } from '../types';
- import styles from './index.less';
-
- // 表单数据
- export type FormData = {
- serviceName: string; // 服务名称
- description: string; // 描述
- model: {
- id: number;
- version: string;
- value: string;
- showValue: string;
- }; // 模型
- image: string; // 镜像
- resource: string; // 资源规格
- replicas: string; // 副本数量
- modelPath: string; // 模型路径
- env: { key: string; value: string }[]; // 环境变量
- };
-
- 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 { message } = App.useApp();
-
- useEffect(() => {
- const res = getSessionStorageItem(modelDeploymentInfoKey, true);
- if (res) {
- setOperationType(res.operationType);
- setModelDeploymentInfo(res);
- const formData = underscoreToCamelCase(res) as FormData;
- form.setFieldsValue(formData);
- }
- return () => {
- removeSessionStorageItem(modelDeploymentInfoKey);
- };
- }, []);
-
- // 获取选择数据集、模型后面按钮 icon
- const getSelectBtnIcon = (type: ResourceSelectorType) => {
- return <KFIcon type={selectorTypeConfig[type].buttonIcon} font={16} />;
- };
-
- // 选择模型、镜像
- const selectResource = (name: string, selectType: string) => {
- let type;
- let resource: ResourceSelectorResponse | undefined;
- switch (selectType) {
- case 'model':
- type = ResourceSelectorType.Model;
- resource = selectedModel;
- break;
- default:
- type = ResourceSelectorType.Mirror;
- 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);
- } else {
- const response = res as ResourceSelectorResponse;
- const showValue = `${response.name}:${response.version}`;
- form.setFieldValue(name, {
- ...pick(response, ['id', 'version', 'path']),
- showValue,
- });
- setSelectedModel(response);
- }
- } else {
- if (type === ResourceSelectorType.Model) {
- setSelectedModel(undefined);
- }
- form.setFieldValue(name, '');
- }
- close();
- },
- });
- };
-
- // 创建
- const createModelDeployment = async (formData: FormData) => {
- const envList = formData['env'] ?? [];
- const env = envList.reduce((acc, cur) => {
- acc[cur.key] = cur.value;
- return acc;
- }, {} as Record<string, string>);
-
- const object = camelCaseToUnderscore({
- ...omit(formData, ['replicas', 'env']),
- replicas: Number(formData.replicas),
- env,
- });
-
- const params =
- operationType === ModelDeploymentOperationType.Create
- ? object
- : {
- ...pick(modelDeploymentInfo, ['service_id', 'service_ins_id']),
- update_model: {
- ...pick(object, ['description', 'env', 'replicas', 'resource', 'image']),
- },
- };
-
- let request = createModelDeploymentReq;
- if (operationType === ModelDeploymentOperationType.Restart) {
- request = restartModelDeploymentReq;
- } else if (operationType === ModelDeploymentOperationType.Update) {
- request = updateModelDeploymentReq;
- }
- const [res] = await to(request(params));
- if (res) {
- message.success('操作成功');
- navgite(-1);
- }
- };
-
- // 提交
- const handleSubmit = (values: FormData) => {
- createModelDeployment(values);
- };
-
- // 取消
- const cancel = () => {
- navgite(-1);
- };
-
- const disabled = operationType !== ModelDeploymentOperationType.Create;
- let buttonText = '新建';
- if (operationType === ModelDeploymentOperationType.Update) {
- buttonText = '更新';
- } else if (operationType === ModelDeploymentOperationType.Restart) {
- buttonText = '重启';
- }
-
- return (
- <div className={styles['model-deployment-create']}>
- <PageTitle title="创建推理服务"></PageTitle>
- <div className={styles['model-deployment-create__content']}>
- <div>
- <Form
- name="model-deployment-create"
- labelCol={{ flex: '100px' }}
- labelAlign="left"
- form={form}
- initialValues={{ upload_type: CommonTabKeys.Public }}
- onFinish={handleSubmit}
- size="large"
- >
- <SubAreaTitle
- title="基本信息"
- image={require('@/assets/img/mirror-basic.png')}
- style={{ marginBottom: '26px' }}
- ></SubAreaTitle>
- <Row gutter={8}>
- <Col span={10}>
- <Form.Item
- label="服务名称"
- name="serviceName"
- rules={[
- {
- required: true,
- message: '请输入服务名称',
- },
- ]}
- >
- <Input
- placeholder="请输入服务名称"
- disabled={disabled}
- maxLength={30}
- showCount
- allowClear
- />
- </Form.Item>
- </Col>
- </Row>
- <Row gutter={8}>
- <Col span={20}>
- <Form.Item
- label="描 述"
- name="description"
- rules={[
- {
- required: true,
- message: '请输入描述',
- },
- ]}
- >
- <Input.TextArea
- autoSize={{ minRows: 2, maxRows: 6 }}
- placeholder="请输入描述,最长128字符"
- maxLength={128}
- showCount
- allowClear
- />
- </Form.Item>
- </Col>
- </Row>
- <SubAreaTitle
- title="部署构建"
- image={require('@/assets/img/model-deployment.png')}
- style={{ marginTop: '20px', marginBottom: '24px' }}
- ></SubAreaTitle>
-
- <Row gutter={8}>
- <Col span={10}>
- <Form.Item
- label="选择模型"
- name="model"
- rules={[
- {
- required: true,
- message: '请选择模型',
- },
- ]}
- >
- <ParameterInput
- placeholder="请选择模型"
- disabled={disabled}
- canInput={false}
- size="large"
- />
- </Form.Item>
- </Col>
- <Col span={10}>
- <Button
- disabled={disabled}
- size="large"
- type="link"
- icon={getSelectBtnIcon(ResourceSelectorType.Model)}
- onClick={() => selectResource('model', 'model')}
- >
- 选择模型
- </Button>
- </Col>
- </Row>
- <Row gutter={8}>
- <Col span={10}>
- <Form.Item
- label="选择镜像"
- name="image"
- rules={[
- {
- required: true,
- message: '请输入镜像',
- },
- ]}
- >
- <ParameterInput placeholder="请选择镜像" canInput={false} size="large" />
- </Form.Item>
- </Col>
- <Col span={10}>
- <Button
- size="large"
- type="link"
- icon={getSelectBtnIcon(ResourceSelectorType.Mirror)}
- onClick={() => selectResource('image', 'image')}
- >
- 选择镜像
- </Button>
- </Col>
- </Row>
- <Row gutter={8}>
- <Col span={10}>
- <Form.Item
- label="资源规格"
- name="resource"
- rules={[
- {
- required: true,
- message: '请选择资源规格',
- },
- ]}
- >
- <Select
- showSearch
- placeholder="请选择资源规格"
- filterOption={filterResourceStandard}
- options={resourceStandardList}
- fieldNames={{
- label: 'description',
- value: 'standard',
- }}
- />
- </Form.Item>
- </Col>
- </Row>
- <Row gutter={8}>
- <Col span={10}>
- <Form.Item
- label="副本数量"
- name="replicas"
- rules={[
- {
- required: true,
- message: '请输入副本数量',
- },
- {
- pattern: /^-?\d+(\.\d+)?$/,
- message: '副本数量必须是数字',
- },
- ]}
- >
- <Input placeholder="请输入副本数量" allowClear />
- </Form.Item>
- </Col>
- </Row>
-
- <Row gutter={8}>
- <Col span={10}>
- <Form.Item
- label="挂载路径"
- name="modelPath"
- rules={[
- {
- required: true,
- message: '请输入模型挂载路径',
- },
- ]}
- >
- <Input
- placeholder="请输入模型挂载路径"
- disabled={disabled}
- maxLength={64}
- showCount
- allowClear
- />
- </Form.Item>
- </Col>
- </Row>
-
- <Form.List name="env">
- {(fields, { add, remove }) => (
- <>
- <Row gutter={8}>
- <Col span={10}>
- <Form.Item label="环境变量">
- <Button type="link" style={{ padding: '0' }} onClick={() => add()}>
- 添加环境变量
- </Button>
- </Form.Item>
- </Col>
- </Row>
- {fields.map(({ key, name, ...restField }) => (
- <Flex key={key} align="center" gap="0 8px" style={{ width: '50%' }}>
- <Form.Item
- {...restField}
- name={[name, 'key']}
- style={{ flex: 1 }}
- rules={[{ required: true, message: '请输入变量名' }]}
- >
- <Input placeholder="请输入变量名" />
- </Form.Item>
- <span style={{ marginBottom: '24px' }}>=</span>
- <Form.Item
- {...restField}
- name={[name, 'value']}
- style={{ flex: 1 }}
- rules={[{ required: true, message: '请输入变量值' }]}
- >
- <Input placeholder="请输入变量值" />
- </Form.Item>
- <Button
- type="link"
- style={{ marginBottom: '24px' }}
- icon={<KFIcon type="icon-shanchu" font={16} />}
- onClick={() => {
- modalConfirm({
- content: '是否确认删除?',
- onOk: () => {
- remove(name);
- },
- });
- }}
- ></Button>
- </Flex>
- ))}
- </>
- )}
- </Form.List>
-
- <Form.Item wrapperCol={{ offset: 0, span: 16 }}>
- <Button type="primary" htmlType="submit">
- {buttonText}
- </Button>
- <Button
- type="default"
- htmlType="button"
- onClick={cancel}
- style={{ marginLeft: '20px' }}
- >
- 取消
- </Button>
- </Form.Item>
- </Form>
- </div>
- </div>
- </div>
- );
- }
-
- export default ModelDeploymentCreate;
|