Browse Source

feat:完成服务部署

pull/134/head
cp3hnu 1 year ago
parent
commit
89bf402ec3
30 changed files with 1091 additions and 507 deletions
  1. +8
    -8
      react-ui/config/routes.ts
  2. BIN
      react-ui/src/assets/img/service-version.png
  3. +1
    -1
      react-ui/src/components/BasicInfo/index.tsx
  4. +11
    -0
      react-ui/src/components/CodeSelect/index.less
  5. +71
    -0
      react-ui/src/components/CodeSelect/index.tsx
  6. +6
    -0
      react-ui/src/components/ParameterInput/index.tsx
  7. +6
    -0
      react-ui/src/components/ParameterSelect/index.tsx
  8. +9
    -1
      react-ui/src/components/ResourceSelect/index.tsx
  9. +23
    -10
      react-ui/src/enums/index.ts
  10. +1
    -1
      react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx
  11. +0
    -0
      react-ui/src/pages/ModelDeployment/CreateService/index.less
  12. +35
    -65
      react-ui/src/pages/ModelDeployment/CreateService/index.tsx
  13. +1
    -1
      react-ui/src/pages/ModelDeployment/CreateVersion/index.less
  14. +184
    -66
      react-ui/src/pages/ModelDeployment/CreateVersion/index.tsx
  15. +67
    -119
      react-ui/src/pages/ModelDeployment/List/index.tsx
  16. +0
    -38
      react-ui/src/pages/ModelDeployment/ServerInfo/index.less
  17. +0
    -76
      react-ui/src/pages/ModelDeployment/ServerInfo/index.tsx
  18. +24
    -0
      react-ui/src/pages/ModelDeployment/ServiceInfo/index.less
  19. +430
    -0
      react-ui/src/pages/ModelDeployment/ServiceInfo/index.tsx
  20. +1
    -1
      react-ui/src/pages/ModelDeployment/VersionInfo/index.less
  21. +30
    -19
      react-ui/src/pages/ModelDeployment/VersionInfo/index.tsx
  22. +47
    -21
      react-ui/src/pages/ModelDeployment/components/BasicInfo/index.tsx
  23. +10
    -10
      react-ui/src/pages/ModelDeployment/components/ModelDeployStatusCell/index.tsx
  24. +5
    -6
      react-ui/src/pages/ModelDeployment/components/ServerLog/index.tsx
  25. +4
    -6
      react-ui/src/pages/ModelDeployment/components/UserGuide/index.tsx
  26. +44
    -17
      react-ui/src/pages/ModelDeployment/types.ts
  27. +2
    -0
      react-ui/src/pages/Pipeline/components/CodeSelectorModal/index.tsx
  28. +0
    -1
      react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx
  29. +67
    -38
      react-ui/src/services/modelDeployment/index.ts
  30. +4
    -2
      react-ui/src/utils/sessionStorage.ts

+ 8
- 8
react-ui/config/routes.ts View File

@@ -222,23 +222,23 @@ export default [
}, },
{ {
name: '服务详情', name: '服务详情',
path: 'serverInfo/:id',
component: './ModelDeployment/Info',
path: 'serviceInfo/:id',
component: './ModelDeployment/ServiceInfo',
}, },
{ {
name: '模型部署详情',
path: 'info/:id',
component: './ModelDeployment/Info',
name: '服务版本详情',
path: 'versionInfo/:id',
component: './ModelDeployment/VersionInfo',
}, },
{ {
name: '创建推理服务', name: '创建推理服务',
path: 'create',
component: './ModelDeployment/CreateServer',
path: 'createService',
component: './ModelDeployment/CreateService',
}, },
{ {
name: '新增服务版本', name: '新增服务版本',
path: 'addVersion/:id', path: 'addVersion/:id',
component: './ModelDeployment/Create',
component: './ModelDeployment/CreateVersion',
}, },
], ],
}, },


BIN
react-ui/src/assets/img/service-version.png View File

Before After
Width: 45  |  Height: 45  |  Size: 852 B

+ 1
- 1
react-ui/src/components/BasicInfo/index.tsx View File

@@ -90,7 +90,7 @@ function BasicInfoItemValue({ value, link, url }: BasicInfoItemValueProps) {
); );
} else { } else {
return ( return (
<div className="kf-basic-info-item__value kf-basic-info-item__text">{value || '--'}</div>
<div className="kf-basic-info-item__value kf-basic-info-item__text">{value ?? '--'}</div>
); );
} }
} }


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

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

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

+ 71
- 0
react-ui/src/components/CodeSelect/index.tsx View File

@@ -0,0 +1,71 @@
/*
* @Author: 赵伟
* @Date: 2024-10-08 15:36:08
* @Description: 代码配置选择表单组件
*/

import KFIcon from '@/components/KFIcon';
import CodeSelectorModal from '@/pages/Pipeline/components/CodeSelectorModal';
import { openAntdModal } from '@/utils/modal';
import { Button } from 'antd';
import ParameterInput, { type ParameterInputProps } from '../ParameterInput';
import './index.less';

export { requiredValidator, type ParameterInputObject } from '../ParameterInput';

type CodeSelectProps = ParameterInputProps;

function CodeSelect({ value, onChange, disabled, ...rest }: CodeSelectProps) {
const selectResource = () => {
const { close } = openAntdModal(CodeSelectorModal, {
onOk: (res) => {
if (res) {
const { git_url, git_branch, code_repo_name } = res;
const jsonObj = {
code_path: git_url,
branch: git_branch,
};
const jsonObjStr = JSON.stringify(jsonObj);
const showValue = code_repo_name;
onChange?.({
value: jsonObjStr,
showValue,
fromSelect: true,
...jsonObj,
});
} else {
onChange?.({
value: undefined,
showValue: undefined,
fromSelect: false,
});
}
close();
},
});
};

return (
<div className="kf-code-select">
<ParameterInput
{...rest}
disabled={disabled}
value={value}
onChange={onChange}
onClick={selectResource}
></ParameterInput>
<Button
className="kf-code-select__button"
size="large"
type="link"
icon={<KFIcon type="icon-xuanzedaimapeizhi" font={16} />}
disabled={disabled}
onClick={selectResource}
>
选择代码配置
</Button>
</div>
);
}

export default CodeSelect;

+ 6
- 0
react-ui/src/components/ParameterInput/index.tsx View File

@@ -1,3 +1,9 @@
/*
* @Author: 赵伟
* @Date: 2024-04-16 08:42:57
* @Description: 参数输入组件
*/

import { CloseOutlined } from '@ant-design/icons'; import { CloseOutlined } from '@ant-design/icons';
import { Form, Input } from 'antd'; import { Form, Input } from 'antd';
import { RuleObject } from 'antd/es/form'; import { RuleObject } from 'antd/es/form';


+ 6
- 0
react-ui/src/components/ParameterSelect/index.tsx View File

@@ -1,3 +1,9 @@
/*
* @Author: 赵伟
* @Date: 2024-04-16 08:42:57
* @Description: 参数选择组件
*/

import { PipelineNodeModelParameter } from '@/types'; import { PipelineNodeModelParameter } from '@/types';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import { Select } from 'antd'; import { Select } from 'antd';


+ 9
- 1
react-ui/src/components/ResourceSelect/index.tsx View File

@@ -1,3 +1,9 @@
/*
* @Author: 赵伟
* @Date: 2024-04-16 13:58:08
* @Description: 数据集、模型、镜像选择表单组件
*/

import KFIcon from '@/components/KFIcon'; import KFIcon from '@/components/KFIcon';
import ResourceSelectorModal, { import ResourceSelectorModal, {
ResourceSelectorResponse, ResourceSelectorResponse,
@@ -22,7 +28,7 @@ const getSelectBtnIcon = (type: ResourceSelectorType) => {
return <KFIcon type={selectorTypeConfig[type].buttonIcon} font={16} />; return <KFIcon type={selectorTypeConfig[type].buttonIcon} font={16} />;
}; };


function ResourceSelect({ type, value, onChange, ...rest }: ResourceSelectProps) {
function ResourceSelect({ type, value, onChange, disabled, ...rest }: ResourceSelectProps) {
const [selectedResource, setSelectedResource] = useState<ResourceSelectorResponse | undefined>( const [selectedResource, setSelectedResource] = useState<ResourceSelectorResponse | undefined>(
undefined, undefined,
); );
@@ -87,6 +93,7 @@ function ResourceSelect({ type, value, onChange, ...rest }: ResourceSelectProps)
<div className="kf-resource-select"> <div className="kf-resource-select">
<ParameterInput <ParameterInput
{...rest} {...rest}
disabled={disabled}
value={value} value={value}
onChange={onChange} onChange={onChange}
onRemove={() => setSelectedResource(undefined)} onRemove={() => setSelectedResource(undefined)}
@@ -97,6 +104,7 @@ function ResourceSelect({ type, value, onChange, ...rest }: ResourceSelectProps)
size="large" size="large"
type="link" type="link"
icon={getSelectBtnIcon(type)} icon={getSelectBtnIcon(type)}
disabled={disabled}
onClick={selectResource} onClick={selectResource}
> >
{selectorTypeConfig[type].buttontTitle} {selectorTypeConfig[type].buttontTitle}


+ 23
- 10
react-ui/src/enums/index.ts View File

@@ -44,8 +44,8 @@ export enum MirrorVersionStatus {
Failed = 'failed', // 构建中 Failed = 'failed', // 构建中
} }


// 模型部署状态
export enum ModelDeploymentStatus {
// 服务运行状态
export enum ServiceRunStatus {
Init = 'Init', // 启动中 Init = 'Init', // 启动中
Running = 'Running', // 运行中 Running = 'Running', // 运行中
Stopped = 'Stopped', // 已停止 Stopped = 'Stopped', // 已停止
@@ -53,14 +53,13 @@ export enum ModelDeploymentStatus {
Pending = 'Pending', // 挂起中 Pending = 'Pending', // 挂起中
} }


// 模型部署状态选项列表
export const modelDeploymentStatusOptions = [
{ label: '全部', value: '' },
{ label: '启动中', value: ModelDeploymentStatus.Init },
{ label: '运行中', value: ModelDeploymentStatus.Running },
{ label: '已停止', value: ModelDeploymentStatus.Stopped },
{ label: '失败', value: ModelDeploymentStatus.Failed },
{ label: '挂起中', value: ModelDeploymentStatus.Pending },
// 服务运行状态选项列表
export const serviceStatusOptions = [
{ label: '启动中', value: ServiceRunStatus.Init },
{ label: '运行中', value: ServiceRunStatus.Running },
{ label: '已停止', value: ServiceRunStatus.Stopped },
{ label: '失败', value: ServiceRunStatus.Failed },
{ label: '挂起中', value: ServiceRunStatus.Pending },
]; ];


// 开发环境编辑器状态 // 开发环境编辑器状态
@@ -71,3 +70,17 @@ export enum DevEditorStatus {
Failed = 'Failed', // 失败 Failed = 'Failed', // 失败
Unknown = 'Unknown', // 未启动 Unknown = 'Unknown', // 未启动
} }

export enum ServiceType {
Video = 'video',
Image = 'image',
Audio = 'audio',
Text = 'text',
}

export const serviceTypeOptions = [
{ label: '视频', value: ServiceType.Video },
{ label: '图像', value: ServiceType.Image },
{ label: '音频', value: ServiceType.Audio },
{ label: '文本', value: ServiceType.Text },
];

+ 1
- 1
react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx View File

@@ -80,7 +80,7 @@ const ResourceInfo = ({ resourceType }: ResourceInfoProps) => {
}) => { }) => {
const request = config.getInfo; const request = config.getInfo;
const [res] = await to(request(params)); const [res] = await to(request(params));
if (res) {
if (res && res.data) {
setInfo(res.data); setInfo(res.data);
} }
}; };


react-ui/src/pages/ModelDeployment/Create/index.less → react-ui/src/pages/ModelDeployment/CreateService/index.less View File


react-ui/src/pages/ModelDeployment/CreateServer/index.tsx → react-ui/src/pages/ModelDeployment/CreateService/index.tsx View File

@@ -1,82 +1,63 @@
/* /*
* @Author: 赵伟 * @Author: 赵伟
* @Date: 2024-04-16 13:58:08 * @Date: 2024-04-16 13:58:08
* @Description: 创建模型部署
* @Description: 创建推理服务
*/ */

import PageTitle from '@/components/PageTitle'; import PageTitle from '@/components/PageTitle';
import SubAreaTitle from '@/components/SubAreaTitle'; import SubAreaTitle from '@/components/SubAreaTitle';
import { CommonTabKeys } from '@/enums';
import {
createModelDeploymentReq,
restartModelDeploymentReq,
updateModelDeploymentReq,
} from '@/services/modelDeployment';
import { camelCaseToUnderscore, underscoreToCamelCase } from '@/utils';
import { CommonTabKeys, serviceTypeOptions } from '@/enums';
import { createServiceReq, updateServiceReq } from '@/services/modelDeployment';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import { import {
getSessionStorageItem, getSessionStorageItem,
modelDeploymentInfoKey,
removeSessionStorageItem, removeSessionStorageItem,
serviceInfoKey,
} from '@/utils/sessionStorage'; } from '@/utils/sessionStorage';
import { useNavigate } from '@umijs/max'; import { useNavigate } from '@umijs/max';
import { App, Button, Col, Form, Input, Row } from 'antd';
import { App, Button, Col, Form, Input, Row, Select } from 'antd';
import { pick } from 'lodash'; import { pick } from 'lodash';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { ModelDeploymentData, ModelDeploymentOperationType } from '../types';
import { ServiceData, ServiceOperationType } from '../types';
import styles from './index.less'; import styles from './index.less';


// 表单数据 // 表单数据
export type FormData = { export type FormData = {
serviceName: string; // 服务名称
serviceVersion: string; // 服务版本
service_name: string; // 服务名称
service_type: string; // 服务类型
description: string; // 描述 description: string; // 描述
}; };


function ModelDeploymentCreate() {
function CreateService() {
const navigate = useNavigate(); const navigate = useNavigate();
const [form] = Form.useForm(); const [form] = Form.useForm();
const [operationType, setOperationType] = useState(ModelDeploymentOperationType.Create);
const [modelDeploymentInfo, setModelDeploymentInfo] = useState<ModelDeploymentData | undefined>(
undefined,
);
const [operationType, setOperationType] = useState(ServiceOperationType.Create);
const [serviceInfo, setServiceInfo] = useState<ServiceData | undefined>(undefined);
const { message } = App.useApp(); const { message } = App.useApp();


useEffect(() => { useEffect(() => {
const res = getSessionStorageItem(modelDeploymentInfoKey, true);
const res = getSessionStorageItem(serviceInfoKey, true);
if (res) { if (res) {
setOperationType(res.operationType); setOperationType(res.operationType);
setModelDeploymentInfo(res);
const formData = underscoreToCamelCase(res) as FormData;
form.setFieldsValue(formData);
setServiceInfo(res);
form.setFieldsValue(pick(res, ['service_name', 'service_type', 'description']));
} }
return () => { return () => {
removeSessionStorageItem(modelDeploymentInfoKey);
removeSessionStorageItem(serviceInfoKey);
}; };
}, []); }, []);


// 创建
const createModelDeployment = async (formData: FormData) => {
// 根据后台要求,修改表单数据
const object = camelCaseToUnderscore({
...formData,
});

// 创建、更新服务
const createService = async (formData: FormData) => {
const request =
operationType === ServiceOperationType.Create ? createServiceReq : updateServiceReq;
const params = const params =
operationType === ModelDeploymentOperationType.Create
? object
operationType === ServiceOperationType.Create
? formData
: { : {
...pick(modelDeploymentInfo, ['service_id', 'service_ins_id']),
update_model: {
...pick(object, ['description', 'env', 'replicas', 'resource', 'image']),
},
id: serviceInfo?.id,
...formData,
}; };

let request = createModelDeploymentReq;
if (operationType === ModelDeploymentOperationType.Restart) {
request = restartModelDeploymentReq;
} else if (operationType === ModelDeploymentOperationType.Update) {
request = updateModelDeploymentReq;
}
const [res] = await to(request(params)); const [res] = await to(request(params));
if (res) { if (res) {
message.success('操作成功'); message.success('操作成功');
@@ -86,7 +67,7 @@ function ModelDeploymentCreate() {


// 提交 // 提交
const handleSubmit = (values: FormData) => { const handleSubmit = (values: FormData) => {
createModelDeployment(values);
createService(values);
}; };


// 取消 // 取消
@@ -94,17 +75,12 @@ function ModelDeploymentCreate() {
navigate(-1); navigate(-1);
}; };


const disabled = operationType !== ModelDeploymentOperationType.Create;
let buttonText = '新建';
if (operationType === ModelDeploymentOperationType.Update) {
buttonText = '更新';
} else if (operationType === ModelDeploymentOperationType.Restart) {
buttonText = '重启';
}
const disabled = operationType !== ServiceOperationType.Create;
const title = operationType === ServiceOperationType.Create ? '创建推理服务' : '更新推理服务';


return ( return (
<div className={styles['model-deployment-create']}> <div className={styles['model-deployment-create']}>
<PageTitle title="创建推理服务"></PageTitle>
<PageTitle title={title}></PageTitle>
<div className={styles['model-deployment-create__content']}> <div className={styles['model-deployment-create__content']}>
<div> <div>
<Form <Form
@@ -126,7 +102,7 @@ function ModelDeploymentCreate() {
<Col span={10}> <Col span={10}>
<Form.Item <Form.Item
label="服务名称" label="服务名称"
name="serviceName"
name="service_name"
rules={[ rules={[
{ {
required: true, required: true,
@@ -147,22 +123,16 @@ function ModelDeploymentCreate() {
<Row gutter={8}> <Row gutter={8}>
<Col span={10}> <Col span={10}>
<Form.Item <Form.Item
label="服务版本"
name="serviceVersion"
label="服务类型"
name="service_type"
rules={[ rules={[
{ {
required: true, required: true,
message: '请输入服务版本',
message: '请选择服务类型',
}, },
]} ]}
> >
<Input
placeholder="请输入服务版本"
disabled={disabled}
maxLength={30}
showCount
allowClear
/>
<Select placeholder="请选择服务类型" options={serviceTypeOptions} allowClear />
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
@@ -191,7 +161,7 @@ function ModelDeploymentCreate() {


<Form.Item wrapperCol={{ offset: 0, span: 16 }}> <Form.Item wrapperCol={{ offset: 0, span: 16 }}>
<Button type="primary" htmlType="submit"> <Button type="primary" htmlType="submit">
{buttonText}
确定
</Button> </Button>
<Button <Button
type="default" type="default"
@@ -209,4 +179,4 @@ function ModelDeploymentCreate() {
); );
} }


export default ModelDeploymentCreate;
export default CreateService;

react-ui/src/pages/ModelDeployment/CreateServer/index.less → react-ui/src/pages/ModelDeployment/CreateVersion/index.less View File

@@ -1,4 +1,4 @@
.model-deployment-create {
.create-service-version {
height: 100%; height: 100%;


&__content { &__content {

react-ui/src/pages/ModelDeployment/Create/index.tsx → react-ui/src/pages/ModelDeployment/CreateVersion/index.tsx View File

@@ -1,8 +1,9 @@
/* /*
* @Author: 赵伟 * @Author: 赵伟
* @Date: 2024-04-16 13:58:08 * @Date: 2024-04-16 13:58:08
* @Description: 创建模型部署
* @Description: 创建服务版本
*/ */
import CodeSelect from '@/components/CodeSelect';
import KFIcon from '@/components/KFIcon'; import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle'; import PageTitle from '@/components/PageTitle';
import ResourceSelect, { import ResourceSelect, {
@@ -11,98 +12,147 @@ import ResourceSelect, {
type ParameterInputObject, type ParameterInputObject,
} from '@/components/ResourceSelect'; } from '@/components/ResourceSelect';
import SubAreaTitle from '@/components/SubAreaTitle'; import SubAreaTitle from '@/components/SubAreaTitle';
import { CommonTabKeys } from '@/enums';
import { useComputingResource } from '@/hooks/resource'; import { useComputingResource } from '@/hooks/resource';
import { import {
createModelDeploymentReq,
restartModelDeploymentReq,
updateModelDeploymentReq,
createServiceVersionReq,
getServiceInfoReq,
updateServiceVersionReq,
} from '@/services/modelDeployment'; } from '@/services/modelDeployment';
import { camelCaseToUnderscore, underscoreToCamelCase } from '@/utils';
import { changePropertyName } from '@/utils';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import { import {
getSessionStorageItem, getSessionStorageItem,
modelDeploymentInfoKey,
removeSessionStorageItem, removeSessionStorageItem,
serviceVersionInfoKey,
} from '@/utils/sessionStorage'; } from '@/utils/sessionStorage';
import { modalConfirm } from '@/utils/ui'; import { modalConfirm } from '@/utils/ui';
import { useNavigate } from '@umijs/max';
import { PlusOutlined } from '@ant-design/icons';
import { useNavigate, useParams } from '@umijs/max';
import { App, Button, Col, Flex, Form, Input, Row, Select } from 'antd'; import { App, Button, Col, Flex, Form, Input, Row, Select } from 'antd';
import { omit, pick } from 'lodash'; import { omit, pick } from 'lodash';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { ModelDeploymentData, ModelDeploymentOperationType } from '../types';
import { ServiceData, ServiceOperationType, ServiceVersionData } from '../types';
import styles from './index.less'; import styles from './index.less';


// 表单数据 // 表单数据
export type FormData = { export type FormData = {
serviceName: string; // 服务名称
service_name: string; // 服务名称
version: string; // 服务版本
description: string; // 描述 description: string; // 描述
model: ParameterInputObject; // 模型 model: ParameterInputObject; // 模型
image: ParameterInputObject; // 镜像 image: ParameterInputObject; // 镜像
code_config: ParameterInputObject; // 代码
resource: string; // 资源规格 resource: string; // 资源规格
replicas: string; // 副本数量 replicas: string; // 副本数量
modelPath: string; // 模型路径
env: { key: string; value: string }[]; // 环境变量
mount_path: string; // 模型路径
env_variables: { key: string; value: string }[]; // 环境变量
}; };


function ModelDeploymentCreate() {
function CreateServiceVersion() {
const navigate = useNavigate(); const navigate = useNavigate();
const [form] = Form.useForm(); const [form] = Form.useForm();
const [resourceStandardList, filterResourceStandard] = useComputingResource(); const [resourceStandardList, filterResourceStandard] = useComputingResource();
const [operationType, setOperationType] = useState(ModelDeploymentOperationType.Create);
const [modelDeploymentInfo, setModelDeploymentInfo] = useState<ModelDeploymentData | undefined>(
undefined,
);
const [operationType, setOperationType] = useState(ServiceOperationType.Create);
const { message } = App.useApp(); const { message } = App.useApp();
const [serviceInfo, setServiceInfo] = useState<ServiceData | undefined>(undefined);
const [versionInfo, setVersionInfo] = useState<ServiceVersionData | undefined>(undefined);
const params = useParams();
const id = params.id;


useEffect(() => { useEffect(() => {
const res = getSessionStorageItem(modelDeploymentInfoKey, true);
const res: (ServiceVersionData & { operationType: ServiceOperationType }) | undefined =
getSessionStorageItem(serviceVersionInfoKey, true);
if (res) { if (res) {
setOperationType(res.operationType); setOperationType(res.operationType);
setModelDeploymentInfo(res);
const formData = underscoreToCamelCase(res) as FormData;
setVersionInfo(res);
let model, codeConfig, envVariables;
if (res.model && typeof res.model === 'object') {
model = changePropertyName(res.model, { show_value: 'showValue' });
// 接口返回是数据没有 value 值,但是 form 需要 value
model.value = model.showValue;
}
if (res.code_config && typeof res.code_config === 'object') {
codeConfig = changePropertyName(res.code_config, { show_value: 'showValue' });
// 接口返回是数据没有 value 值,但是 form 需要 value
codeConfig.value = codeConfig.showValue;
}
if (res.env_variables && typeof res.env_variables === 'object') {
envVariables = Object.entries(res.env_variables).map(([key, value]) => ({
key,
value,
}));
}

const formData = {
...omit(res, 'model', 'code_config', 'env_variables'),
model: model,
code_config: codeConfig,
env_variables: envVariables,
};
form.setFieldsValue(formData); form.setFieldsValue(formData);
} }
return () => { return () => {
removeSessionStorageItem(modelDeploymentInfoKey);
removeSessionStorageItem(serviceVersionInfoKey);
}; };
}, []); }, []);


// 创建
const createModelDeployment = async (formData: FormData) => {
const envList = formData['env'] ?? [];
useEffect(() => {
getServiceInfo();
}, []);

// 获取服务详情
const getServiceInfo = async () => {
const [res] = await to(getServiceInfoReq(id));
if (res && res.data) {
setServiceInfo(res.data);
form.setFieldsValue({
service_name: res.data.service_name,
});
}
};

// 创建版本
const createServiceVersion = async (formData: FormData) => {
const envList = formData['env_variables'] ?? [];
const image = formData['image']; const image = formData['image'];
const model = formData['model']; const model = formData['model'];
const env = envList.reduce((acc, cur) => {
const codeConfig = formData['code_config'];
const envVariables = envList.reduce((acc, cur) => {
acc[cur.key] = cur.value; acc[cur.key] = cur.value;
return acc; return acc;
}, {} as Record<string, string>); }, {} as Record<string, string>);


// 根据后台要求,修改表单数据 // 根据后台要求,修改表单数据
const object = camelCaseToUnderscore({
...omit(formData, ['replicas', 'env', 'image', 'model']),
const object = {
...omit(formData, ['replicas', 'env_variables', 'image', 'model', 'code_config']),
replicas: Number(formData.replicas), replicas: Number(formData.replicas),
env,
env_variables: envVariables,
image: image.value, image: image.value,
model: pick(model, ['id', 'version', 'path', 'showValue']),
});
model: changePropertyName(
pick(model, ['id', 'name', 'version', 'path', 'identifier', 'owner', 'showValue']),
{ showValue: 'show_value' },
),
code_config: changePropertyName(pick(codeConfig, ['code_path', 'branch', 'showValue']), {
showValue: 'show_value',
}),
service_id: serviceInfo?.id,
};


const params = const params =
operationType === ModelDeploymentOperationType.Create
operationType === ServiceOperationType.Create
? object ? object
: { : {
...pick(modelDeploymentInfo, ['service_id', 'service_ins_id']),
update_model: {
...pick(object, ['description', 'env', 'replicas', 'resource', 'image']),
},
id: versionInfo?.id,
rerun: operationType === ServiceOperationType.Restart ? true : false,
deployment_name: versionInfo?.deployment_name,
...object,
}; };


let request = createModelDeploymentReq;
if (operationType === ModelDeploymentOperationType.Restart) {
request = restartModelDeploymentReq;
} else if (operationType === ModelDeploymentOperationType.Update) {
request = updateModelDeploymentReq;
}
const request =
operationType === ServiceOperationType.Create
? createServiceVersionReq
: updateServiceVersionReq;

const [res] = await to(request(params)); const [res] = await to(request(params));
if (res) { if (res) {
message.success('操作成功'); message.success('操作成功');
@@ -112,7 +162,7 @@ function ModelDeploymentCreate() {


// 提交 // 提交
const handleSubmit = (values: FormData) => { const handleSubmit = (values: FormData) => {
createModelDeployment(values);
createServiceVersion(values);
}; };


// 取消 // 取消
@@ -120,25 +170,27 @@ function ModelDeploymentCreate() {
navigate(-1); navigate(-1);
}; };


const disabled = operationType !== ModelDeploymentOperationType.Create;
const disabled = operationType !== ServiceOperationType.Create;
let buttonText = '新建'; let buttonText = '新建';
if (operationType === ModelDeploymentOperationType.Update) {
let title = '新增服务版本';
if (operationType === ServiceOperationType.Update) {
title = '更新服务版本';
buttonText = '更新'; buttonText = '更新';
} else if (operationType === ModelDeploymentOperationType.Restart) {
} else if (operationType === ServiceOperationType.Restart) {
title = '重启服务版本';
buttonText = '重启'; buttonText = '重启';
} }


return ( return (
<div className={styles['model-deployment-create']}>
<PageTitle title="创建推理服务"></PageTitle>
<div className={styles['model-deployment-create__content']}>
<div className={styles['create-service-version']}>
<PageTitle title={title}></PageTitle>
<div className={styles['create-service-version__content']}>
<div> <div>
<Form <Form
name="model-deployment-create"
name="create-service-version"
labelCol={{ flex: '100px' }} labelCol={{ flex: '100px' }}
labelAlign="left" labelAlign="left"
form={form} form={form}
initialValues={{ upload_type: CommonTabKeys.Public }}
onFinish={handleSubmit} onFinish={handleSubmit}
size="large" size="large"
autoComplete="off" autoComplete="off"
@@ -152,7 +204,7 @@ function ModelDeploymentCreate() {
<Col span={10}> <Col span={10}>
<Form.Item <Form.Item
label="服务名称" label="服务名称"
name="serviceName"
name="service_name"
rules={[ rules={[
{ {
required: true, required: true,
@@ -162,8 +214,34 @@ function ModelDeploymentCreate() {
> >
<Input <Input
placeholder="请输入服务名称" placeholder="请输入服务名称"
disabled={disabled}
maxLength={30} maxLength={30}
disabled
showCount
allowClear
/>
</Form.Item>
</Col>
</Row>
<Row gutter={8}>
<Col span={10}>
<Form.Item
label="服务版本"
name="version"
rules={[
{
required: true,
message: '请输入服务版本',
},
{
pattern: /^[a-zA-Z0-9._-]+$/,
message: '版本只支持字母、数字、下划线、点、横杠',
},
]}
>
<Input
placeholder="请输入服务版本"
maxLength={30}
disabled={disabled}
showCount showCount
allowClear allowClear
/> />
@@ -173,18 +251,18 @@ function ModelDeploymentCreate() {
<Row gutter={8}> <Row gutter={8}>
<Col span={20}> <Col span={20}>
<Form.Item <Form.Item
label="描  述"
label="版本描述"
name="description" name="description"
rules={[ rules={[
{ {
required: true, required: true,
message: '请输入描述',
message: '请输入版本描述',
}, },
]} ]}
> >
<Input.TextArea <Input.TextArea
autoSize={{ minRows: 2, maxRows: 6 }} autoSize={{ minRows: 2, maxRows: 6 }}
placeholder="请输入描述,最长128字符"
placeholder="请输入版本描述,最长128字符"
maxLength={128} maxLength={128}
showCount showCount
allowClear allowClear
@@ -238,6 +316,29 @@ function ModelDeploymentCreate() {
placeholder="请选择镜像" placeholder="请选择镜像"
canInput={false} canInput={false}
size="large" size="large"
disabled={disabled}
/>
</Form.Item>
</Col>
</Row>
<Row gutter={8}>
<Col span={10}>
<Form.Item
label="代码配置"
name="code_config"
rules={[
{
validator: requiredValidator,
message: '请选择代码配置',
},
]}
required
>
<CodeSelect
placeholder="请选择代码配置"
canInput={false}
size="large"
disabled={disabled}
/> />
</Form.Item> </Form.Item>
</Col> </Col>
@@ -292,12 +393,16 @@ function ModelDeploymentCreate() {
<Col span={10}> <Col span={10}>
<Form.Item <Form.Item
label="挂载路径" label="挂载路径"
name="modelPath"
name="mount_path"
rules={[ rules={[
{ {
required: true, required: true,
message: '请输入模型挂载路径', message: '请输入模型挂载路径',
}, },
{
pattern: /^\/[a-zA-Z0-9._/-]+$/,
message: '请输入正确的挂载绝对路径',
},
]} ]}
> >
<Input <Input
@@ -311,16 +416,12 @@ function ModelDeploymentCreate() {
</Col> </Col>
</Row> </Row>


<Form.List name="env">
<Form.List name="env_variables">
{(fields, { add, remove }) => ( {(fields, { add, remove }) => (
<> <>
<Row gutter={8}> <Row gutter={8}>
<Col span={10}> <Col span={10}>
<Form.Item label="环境变量">
<Button type="link" style={{ padding: '0' }} onClick={() => add()}>
添加环境变量
</Button>
</Form.Item>
<Form.Item label="环境变量"></Form.Item>
</Col> </Col>
</Row> </Row>
{fields.map(({ key, name, ...restField }) => ( {fields.map(({ key, name, ...restField }) => (
@@ -329,9 +430,16 @@ function ModelDeploymentCreate() {
{...restField} {...restField}
name={[name, 'key']} name={[name, 'key']}
style={{ flex: 1 }} style={{ flex: 1 }}
rules={[{ required: true, message: '请输入变量名' }]}
rules={[
{ required: true, message: '请输入变量名' },
{
pattern: /^[a-zA-Z_][a-zA-Z0-9_-]*$/,
message:
'变量名只支持字母、数字、下划线、中横线且开头必须是字母或下划线',
},
]}
> >
<Input placeholder="请输入变量名" />
<Input placeholder="请输入变量名" disabled={disabled} />
</Form.Item> </Form.Item>
<span style={{ marginBottom: '24px' }}>=</span> <span style={{ marginBottom: '24px' }}>=</span>
<Form.Item <Form.Item
@@ -340,12 +448,13 @@ function ModelDeploymentCreate() {
style={{ flex: 1 }} style={{ flex: 1 }}
rules={[{ required: true, message: '请输入变量值' }]} rules={[{ required: true, message: '请输入变量值' }]}
> >
<Input placeholder="请输入变量值" />
<Input placeholder="请输入变量值" disabled={disabled} />
</Form.Item> </Form.Item>
<Button <Button
type="link" type="link"
style={{ marginBottom: '24px' }} style={{ marginBottom: '24px' }}
icon={<KFIcon type="icon-shanchu" font={16} />} icon={<KFIcon type="icon-shanchu" font={16} />}
disabled={disabled}
onClick={() => { onClick={() => {
modalConfirm({ modalConfirm({
content: '是否确认删除?', content: '是否确认删除?',
@@ -357,6 +466,15 @@ function ModelDeploymentCreate() {
></Button> ></Button>
</Flex> </Flex>
))} ))}
<Button
type="link"
style={{ padding: '0', margin: '-24px 0 24px' }}
onClick={() => add()}
icon={<PlusOutlined />}
disabled={disabled}
>
环境变量
</Button>
</> </>
)} )}
</Form.List> </Form.List>
@@ -381,4 +499,4 @@ function ModelDeploymentCreate() {
); );
} }


export default ModelDeploymentCreate;
export default CreateServiceVersion;

+ 67
- 119
react-ui/src/pages/ModelDeployment/List/index.tsx View File

@@ -1,22 +1,18 @@
/* /*
* @Author: 赵伟 * @Author: 赵伟
* @Date: 2024-04-16 13:58:08 * @Date: 2024-04-16 13:58:08
* @Description: 模型部署列表
* @Description: 模型部署服务列表
*/ */
import CommonTableCell from '@/components/CommonTableCell'; import CommonTableCell from '@/components/CommonTableCell';
import DateTableCell from '@/components/DateTableCell'; import DateTableCell from '@/components/DateTableCell';
import KFIcon from '@/components/KFIcon'; import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle'; import PageTitle from '@/components/PageTitle';
import { ModelDeploymentStatus, modelDeploymentStatusOptions } from '@/enums';
import { serviceTypeOptions } from '@/enums';
import { useCacheState } from '@/hooks/pageCacheState'; import { useCacheState } from '@/hooks/pageCacheState';
import {
deleteModelDeploymentReq,
getModelDeploymentListReq,
stopModelDeploymentReq,
} from '@/services/modelDeployment';
import { deleteServiceReq, getServiceListReq } from '@/services/modelDeployment';
import themes from '@/styles/theme.less'; import themes from '@/styles/theme.less';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import { modelDeploymentInfoKey, setSessionStorageItem } from '@/utils/sessionStorage';
import { serviceInfoKey, setSessionStorageItem } from '@/utils/sessionStorage';
import { modalConfirm } from '@/utils/ui'; import { modalConfirm } from '@/utils/ui';
import { useNavigate } from '@umijs/max'; import { useNavigate } from '@umijs/max';
import { import {
@@ -31,20 +27,20 @@ import {
} from 'antd'; } from 'antd';
import { type SearchProps } from 'antd/es/input'; import { type SearchProps } from 'antd/es/input';
import classNames from 'classnames'; import classNames from 'classnames';
import { pick } from 'lodash';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import ModelDeploymentStatusCell from '../components/ModelDeployStatusCell';
import { ModelDeploymentData, ModelDeploymentOperationType } from '../types';
import { ServiceData, ServiceOperationType } from '../types';
import styles from './index.less'; import styles from './index.less';


const allServiceTypeOptions = [{ label: '全部', value: '' }, ...serviceTypeOptions];

function ModelDeployment() { function ModelDeployment() {
const navigate = useNavigate(); const navigate = useNavigate();
const { message } = App.useApp(); const { message } = App.useApp();
const [cacheState, setCacheState] = useCacheState(); const [cacheState, setCacheState] = useCacheState();
const [searchStatus, setSearchStatus] = useState(cacheState?.searchStatus ?? '');
const [serviceType, setServiceType] = useState(cacheState?.serviceType ?? '');
const [searchText, setSearchText] = useState(cacheState?.searchText); const [searchText, setSearchText] = useState(cacheState?.searchText);
const [inputText, setInputText] = useState(cacheState?.searchText); const [inputText, setInputText] = useState(cacheState?.searchText);
const [tableData, setTableData] = useState<ModelDeploymentData[]>([]);
const [tableData, setTableData] = useState<ServiceData[]>([]);
const [total, setTotal] = useState(0); const [total, setTotal] = useState(0);
const [pagination, setPagination] = useState<TablePaginationConfig>( const [pagination, setPagination] = useState<TablePaginationConfig>(
cacheState?.pagination ?? { cacheState?.pagination ?? {
@@ -54,29 +50,28 @@ function ModelDeployment() {
); );


useEffect(() => { useEffect(() => {
getModelDeploymentList();
}, [pagination, searchText, searchStatus]);
getServiceList();
}, [pagination, searchText, serviceType]);


// 获取模型部署列表
const getModelDeploymentList = async () => {
// 获取模型部署服务列表
const getServiceList = async () => {
const params: Record<string, any> = { const params: Record<string, any> = {
page: pagination.current!,
page: pagination.current! - 1,
size: pagination.pageSize, size: pagination.pageSize,
service_name: searchText, service_name: searchText,
status: searchStatus,
service_type: serviceType,
}; };
const [res] = await to(getModelDeploymentListReq(params));
const [res] = await to(getServiceListReq(params));
if (res && res.data) { if (res && res.data) {
const { service_list = [], total = 0 } = res.data;
setTableData(service_list);
setTotal(total);
const { content = [], totalElements = 0 } = res.data;
setTableData(content);
setTotal(totalElements);
} }
}; };


// 删除模型部署 // 删除模型部署
const deleteModelDeploy = async (record: ModelDeploymentData) => {
const params = pick(record, ['service_id', 'service_ins_id']);
const [res] = await to(deleteModelDeploymentReq(params));
const deleteService = async (record: ServiceData) => {
const [res] = await to(deleteServiceReq(record.id));
if (res) { if (res) {
message.success('删除成功'); message.success('删除成功');
// 如果是一页的唯一数据,删除时,请求第一页的数据 // 如果是一页的唯一数据,删除时,请求第一页的数据
@@ -88,54 +83,31 @@ function ModelDeployment() {
current: 1, current: 1,
})); }));
} else { } else {
getModelDeploymentList();
getServiceList();
} }
} }
}; };


// 停止模型部署
const stopModelDeploy = async (record: ModelDeploymentData) => {
const params = pick(record, ['service_id', 'service_ins_id']);
const [res] = await to(stopModelDeploymentReq(params));
if (res) {
message.success('操作成功');
getModelDeploymentList();
}
};

// 搜索 // 搜索
const onSearch: SearchProps['onSearch'] = (value) => { const onSearch: SearchProps['onSearch'] = (value) => {
setSearchText(value); setSearchText(value);
}; };


// 处理删除 // 处理删除
const handleModelDeployDelete = (record: ModelDeploymentData) => {
const handleServiceDelete = (record: ServiceData) => {
modalConfirm({ modalConfirm({
title: '删除后,该模型部署将不可恢复',
title: '删除后,该服务将不可恢复',
content: '是否确认删除?', content: '是否确认删除?',
onOk: () => { onOk: () => {
deleteModelDeploy(record);
deleteService(record);
}, },
}); });
}; };


// 处理停止
const handleModelDeployStop = async (record: ModelDeploymentData) => {
modalConfirm({
content: '是否确认停止?',
onOk: () => {
stopModelDeploy(record);
},
});
};

// 创建、更新、重启模型部署
const createModelDeployment = (
type: ModelDeploymentOperationType,
record?: ModelDeploymentData,
) => {
// 创建、更新服务
const createService = (type: ServiceOperationType, record?: ServiceData) => {
setSessionStorageItem( setSessionStorageItem(
modelDeploymentInfoKey,
serviceInfoKey,
{ {
...record, ...record,
operationType: type, operationType: type,
@@ -146,23 +118,23 @@ function ModelDeployment() {
setCacheState({ setCacheState({
pagination, pagination,
searchText, searchText,
searchStatus,
serviceType: serviceType,
}); });


navigate(`/modelDeployment/create`);
navigate(`/modelDeployment/createService`);
}; };


// 查看详情 // 查看详情
const toDetail = (record: ModelDeploymentData) => {
setSessionStorageItem(modelDeploymentInfoKey, record, true);
const toDetail = (record: ServiceData) => {
setSessionStorageItem(serviceInfoKey, record, true);


setCacheState({ setCacheState({
pagination, pagination,
searchText, searchText,
searchStatus,
serviceType: serviceType,
}); });


navigate(`/modelDeployment/info/${record.service_id}`);
navigate(`/modelDeployment/serviceInfo/${record.id}`);
}; };


// 分页切换 // 分页切换
@@ -173,7 +145,7 @@ function ModelDeployment() {
// console.log(pagination, filters, sorter, action); // console.log(pagination, filters, sorter, action);
}; };


const columns: TableProps<ModelDeploymentData>['columns'] = [
const columns: TableProps<ServiceData>['columns'] = [
{ {
title: '序号', title: '序号',
dataIndex: 'index', dataIndex: 'index',
@@ -197,23 +169,23 @@ function ModelDeployment() {
}, },
}, },
{ {
title: '型',
dataIndex: ['model', 'show_value'],
key: 'model',
title: '服务类型',
dataIndex: 'service_type_name',
key: 'service_type_name',
width: '20%', width: '20%',
render: CommonTableCell(), render: CommonTableCell(),
}, },
{ {
title: '状态',
dataIndex: 'status',
key: 'status',
title: '版本数量',
dataIndex: 'version_count',
key: 'version_count',
width: '20%', width: '20%',
render: ModelDeploymentStatusCell,
render: CommonTableCell(),
}, },
{ {
title: '创建人',
dataIndex: 'created_by',
key: 'created_by',
title: '服务描述',
dataIndex: 'description',
key: 'description',
render: CommonTableCell(), render: CommonTableCell(),
width: '20%', width: '20%',
}, },
@@ -227,44 +199,28 @@ function ModelDeployment() {
{ {
title: '操作', title: '操作',
dataIndex: 'operation', dataIndex: 'operation',
width: 250,
width: 300,
key: 'operation', key: 'operation',
render: (_: any, record: ModelDeploymentData) => (
render: (_: any, record: ServiceData) => (
<div> <div>
<Button <Button
type="link" type="link"
size="small" size="small"
key="edit" key="edit"
icon={<KFIcon type="icon-bianji" />} icon={<KFIcon type="icon-bianji" />}
onClick={() => createModelDeployment(ModelDeploymentOperationType.Update, record)}
onClick={() => createService(ServiceOperationType.Update, record)}
> >
更新
编辑
</Button>
<Button
type="link"
size="small"
key="run"
icon={<KFIcon type="icon-xiangqing" />}
onClick={() => toDetail(record)}
>
查看详情
</Button> </Button>
{(record.status === ModelDeploymentStatus.Failed ||
record.status === ModelDeploymentStatus.Stopped) && (
<Button
type="link"
size="small"
key="run"
icon={<KFIcon type="icon-yunhang" />}
onClick={() => createModelDeployment(ModelDeploymentOperationType.Restart, record)}
>
重启
</Button>
)}
{(record.status === ModelDeploymentStatus.Running ||
record.status === ModelDeploymentStatus.Init ||
record.status === ModelDeploymentStatus.Pending) && (
<Button
type="link"
size="small"
key="stop"
icon={<KFIcon type="icon-tingzhi" />}
onClick={() => handleModelDeployStop(record)}
>
停止
</Button>
)}
<ConfigProvider <ConfigProvider
theme={{ theme={{
token: { token: {
@@ -277,7 +233,7 @@ function ModelDeployment() {
size="small" size="small"
key="remove" key="remove"
icon={<KFIcon type="icon-shanchu" />} icon={<KFIcon type="icon-shanchu" />}
onClick={() => handleModelDeployDelete(record)}
onClick={() => handleServiceDelete(record)}
> >
删除 删除
</Button> </Button>
@@ -289,11 +245,11 @@ function ModelDeployment() {


return ( return (
<div className={styles['model-deployment']}> <div className={styles['model-deployment']}>
<PageTitle title="模型列表"></PageTitle>
<PageTitle title="服务列表"></PageTitle>
<div className={styles['model-deployment__content']}> <div className={styles['model-deployment__content']}>
<div className={styles['model-deployment__content__filter']}> <div className={styles['model-deployment__content__filter']}>
<Input.Search <Input.Search
placeholder="按模型服务名称筛选"
placeholder="按服务名称筛选"
onSearch={onSearch} onSearch={onSearch}
onChange={(e) => setInputText(e.target.value)} onChange={(e) => setInputText(e.target.value)}
style={{ width: 300 }} style={{ width: 300 }}
@@ -303,27 +259,19 @@ function ModelDeployment() {
<Select <Select
style={{ width: 100, marginLeft: '20px' }} style={{ width: 100, marginLeft: '20px' }}
placeholder="请选择" placeholder="请选择"
onChange={(value) => setSearchStatus(value)}
options={modelDeploymentStatusOptions}
value={searchStatus}
onChange={(value) => setServiceType(value ?? '')}
options={allServiceTypeOptions}
value={serviceType}
allowClear allowClear
></Select> ></Select>
<Button <Button
style={{ marginLeft: '20px' }}
style={{ marginLeft: 'auto', marginRight: '20px' }}
type="default" type="default"
onClick={() => createModelDeployment(ModelDeploymentOperationType.Create)}
onClick={() => createService(ServiceOperationType.Create)}
icon={<KFIcon type="icon-xinjian2" />} icon={<KFIcon type="icon-xinjian2" />}
> >
创建推理服务 创建推理服务
</Button> </Button>
<Button
style={{ marginRight: 0, marginLeft: 'auto' }}
type="default"
onClick={getModelDeploymentList}
icon={<KFIcon type="icon-shuaxin" />}
>
刷新
</Button>
</div> </div>
<div <div
className={classNames( className={classNames(
@@ -343,7 +291,7 @@ function ModelDeployment() {
showTotal: () => `共${total}条`, showTotal: () => `共${total}条`,
}} }}
onChange={handleTableChange} onChange={handleTableChange}
rowKey="service_id"
rowKey="id"
/> />
</div> </div>
</div> </div>


+ 0
- 38
react-ui/src/pages/ModelDeployment/ServerInfo/index.less View File

@@ -1,38 +0,0 @@
.model-deployment-info {
height: 100%;

&__content {
display: flex;
flex-direction: column;
height: calc(100% - 60px);
margin-top: 10px;
padding: 30px 30px 0;
background-color: white;
border-radius: 10px;

&__tabs {
flex: 1;
min-height: 0;
margin-top: 20px;
padding-bottom: 10px;

:global {
.ant-tabs {
height: 100%;

.ant-tabs-nav {
margin-bottom: 10px;
}

.ant-tabs-content {
height: 100%;

.ant-tabs-tabpane {
height: 100%;
}
}
}
}
}
}
}

+ 0
- 76
react-ui/src/pages/ModelDeployment/ServerInfo/index.tsx View File

@@ -1,76 +0,0 @@
/*
* @Author: 赵伟
* @Date: 2024-04-16 13:58:08
* @Description: 镜像详情
*/
import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle';
import SubAreaTitle from '@/components/SubAreaTitle';
import { useSessionStorage } from '@/hooks/sessionStorage';
import { modelDeploymentInfoKey } from '@/utils/sessionStorage';
import { Tabs, type TabsProps } from 'antd';
import { useState } from 'react';
import BasicInfo from '../components/BasicInfo';
import ServerLog from '../components/ServerLog';
import UserGuide from '../components/UserGuide';
import { ModelDeploymentData } from '../types';
import styles from './index.less';

export enum ModelDeploymentTabKey {
Predict = 'Predict', // 预测
Guide = 'Guide', // 调用指南
Log = 'Log', // 服务日志
}

function ModelDeploymentInfo() {
const [activeTab, setActiveTab] = useState<string>(ModelDeploymentTabKey.Predict);
const [modelDeployementInfo] = useSessionStorage<ModelDeploymentData | undefined>(
modelDeploymentInfoKey,
true,
undefined,
);

const tabItems = [
{
key: ModelDeploymentTabKey.Predict,
label: '预测',
icon: <KFIcon type="icon-yuce" />,
},
{
key: ModelDeploymentTabKey.Guide,
label: '调用指南',
icon: <KFIcon type="icon-tiaoyongzhinan" />,
children: <UserGuide info={modelDeployementInfo}></UserGuide>,
},
{
key: ModelDeploymentTabKey.Log,
label: '服务日志',
icon: <KFIcon type="icon-fuwurizhi" />,
children: <ServerLog info={modelDeployementInfo}></ServerLog>,
},
];

// 切换 Tab,重置数据
const hanleTabChange: TabsProps['onChange'] = (value) => {
setActiveTab(value);
};

return (
<div className={styles['model-deployment-info']}>
<PageTitle title="服务详情"></PageTitle>
<div className={styles['model-deployment-info__content']}>
<SubAreaTitle
title="基本信息"
image={require('@/assets/img/mirror-basic.png')}
style={{ marginBottom: '26px' }}
></SubAreaTitle>
<BasicInfo info={modelDeployementInfo} />
<div className={styles['model-deployment-info__content__tabs']}>
<Tabs activeKey={activeTab} items={tabItems} onChange={hanleTabChange} />
</div>
</div>
</div>
);
}

export default ModelDeploymentInfo;

+ 24
- 0
react-ui/src/pages/ModelDeployment/ServiceInfo/index.less View File

@@ -0,0 +1,24 @@
.service-info {
height: 100%;
&__content {
display: flex;
flex-direction: column;
height: calc(100% - 60px);
margin-top: 10px;
padding: 20px 30px 0;
background-color: white;
border-radius: 10px;

&__filter {
display: flex;
flex: none;
align-items: center;
justify-content: space-between;
}

&__table {
flex: 1;
margin-top: 24px;
}
}
}

+ 430
- 0
react-ui/src/pages/ModelDeployment/ServiceInfo/index.tsx View File

@@ -0,0 +1,430 @@
/*
* @Author: 赵伟
* @Date: 2024-04-16 13:58:08
* @Description: 模型部署列表
*/
import BasicInfo from '@/components/BasicInfo';
import CommonTableCell from '@/components/CommonTableCell';
import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle';
import SubAreaTitle from '@/components/SubAreaTitle';
import { ServiceRunStatus, serviceStatusOptions } from '@/enums';
import { useCacheState } from '@/hooks/pageCacheState';
import { useComputingResource } from '@/hooks/resource';
import {
deleteServiceVersionReq,
getServiceInfoReq,
getServiceVersionsReq,
stopServiceVersionReq,
} from '@/services/modelDeployment';
import themes from '@/styles/theme.less';
import { formatDate } from '@/utils/date';
import { to } from '@/utils/promise';
import { serviceVersionInfoKey, setSessionStorageItem } from '@/utils/sessionStorage';
import { modalConfirm } from '@/utils/ui';
import { useNavigate, useParams } from '@umijs/max';
import {
App,
Button,
ConfigProvider,
Input,
Select,
Table,
Tooltip,
type TablePaginationConfig,
type TableProps,
} from 'antd';
import { type SearchProps } from 'antd/es/input';
import classNames from 'classnames';
import { useEffect, useState } from 'react';
import ServiceRunStatusCell from '../components/ModelDeployStatusCell';
import { ServiceData, ServiceOperationType, ServiceVersionData } from '../types';
import styles from './index.less';

const allServiceStatusOptions = [{ label: '全部', value: '' }, ...serviceStatusOptions];

function ServiceInfo() {
const navigate = useNavigate();
const { message } = App.useApp();
const [cacheState, setCacheState] = useCacheState();
const [serviceStatus, setServiceStatus] = useState(cacheState?.serviceStatus ?? '');
const [searchText, setSearchText] = useState(cacheState?.searchText);
const [inputText, setInputText] = useState(cacheState?.searchText);
const [tableData, setTableData] = useState<ServiceVersionData[]>([]);
const [total, setTotal] = useState(0);
const [pagination, setPagination] = useState<TablePaginationConfig>(
cacheState?.pagination ?? {
current: 1,
pageSize: 10,
},
);
const params = useParams();
const id = params.id;
const [serviceInfo, setServiceInfo] = useState<ServiceData | undefined>(undefined);
const basicInfo = [
{
label: '服务名称',
value: serviceInfo?.service_name,
},
{
label: '服务描述',
value: serviceInfo?.description,
},
{
label: '版本数量',
value: serviceInfo?.version_count,
},
{
label: '创建时间',
value: serviceInfo?.create_time,
format: formatDate,
},
];
const getResourceDescription = useComputingResource()[2];

useEffect(() => {
getServiceInfo();
}, []);

useEffect(() => {
getServiceVersions();
}, [pagination, searchText, serviceStatus]);

// 获取服务详情
const getServiceInfo = async () => {
const [res] = await to(getServiceInfoReq(id));
if (res && res.data) {
setServiceInfo(res.data);
}
};

// 获取服务版本列表
const getServiceVersions = async () => {
const params: Record<string, any> = {
page: pagination.current! - 1,
size: pagination.pageSize,
version: searchText,
run_state: serviceStatus,
service_id: id,
};
const [res] = await to(getServiceVersionsReq(params));
if (res && res.data) {
const { content = [], totalElements = 0 } = res.data;
setTableData(content);
setTotal(totalElements);
}
};

// 删除模型部署
const deleteServiceVersion = async (record: ServiceVersionData) => {
const [res] = await to(deleteServiceVersionReq(record.id));
if (res) {
message.success('删除成功');
// 如果是一页的唯一数据,删除时,请求第一页的数据
// 否则直接刷新这一页的数据
// 避免回到第一页
if (tableData.length > 1) {
setPagination((prev) => ({
...prev,
current: 1,
}));
} else {
getServiceInfo();
getServiceVersions();
}
}
};

// 停止模型部署
const stopServiceVersion = async (record: ServiceVersionData) => {
const [res] = await to(stopServiceVersionReq(record.id));
if (res) {
message.success('操作成功');
getServiceVersions();
}
};

// 搜索
const onSearch: SearchProps['onSearch'] = (value) => {
setSearchText(value);
};

// 处理删除
const handleServiceVersionDelete = (record: ServiceVersionData) => {
modalConfirm({
title: '删除后,该服务版本将不可恢复',
content: '是否确认删除?',
onOk: () => {
deleteServiceVersion(record);
},
});
};

// 处理停止
const handleServiceVersionStop = async (record: ServiceVersionData) => {
modalConfirm({
content: '是否确认停止?',
onOk: () => {
stopServiceVersion(record);
},
});
};

// 创建、更新、重启模型部署
const createServiceVersion = (type: ServiceOperationType, record?: ServiceVersionData) => {
setSessionStorageItem(
serviceVersionInfoKey,
{
...record,
operationType: type,
},
true,
);

setCacheState({
pagination,
searchText,
serviceStatus: serviceStatus,
});

navigate(`/modelDeployment/addVersion/${id}`);
};

// 查看详情
const toDetail = (record: ServiceVersionData) => {
setSessionStorageItem(serviceVersionInfoKey, record, true);

setCacheState({
pagination,
searchText,
serviceStatus: serviceStatus,
});

navigate(`/modelDeployment/versionInfo/${record.id}`);
};

// 分页切换
const handleTableChange: TableProps['onChange'] = (pagination, _filters, _sorter, { action }) => {
if (action === 'paginate') {
setPagination(pagination);
}
// console.log(pagination, filters, sorter, action);
};

const columns: TableProps<ServiceVersionData>['columns'] = [
{
title: '序号',
dataIndex: 'index',
key: 'index',
width: '20%',
render(_text, _record, index) {
return <span>{(pagination.current! - 1) * pagination.pageSize! + index + 1}</span>;
},
},
{
title: '服务版本',
dataIndex: 'version',
key: 'version',
width: '20%',
render: CommonTableCell(),
},
{
title: '模型版本',
dataIndex: 'model',
key: 'model',
width: '20%',
render: (_text: string, record: ServiceVersionData) => (
<Tooltip
title={record.model.show_value}
placement="topLeft"
overlayStyle={{ maxWidth: '400px' }}
>
<span>{record.model.show_value}</span>
</Tooltip>
),
ellipsis: { showTitle: false },
},
{
title: '状态',
dataIndex: 'run_state',
key: 'run_state',
width: '20%',
render: ServiceRunStatusCell,
},
{
title: '版本镜像',
dataIndex: 'image',
key: 'image',
width: '20%',
render: CommonTableCell(true),
ellipsis: { showTitle: false },
},
{
title: '副本数量',
dataIndex: 'replicas',
key: 'replicas',
render: CommonTableCell(),
width: '20%',
},
{
title: '资源规格',
dataIndex: 'resource',
key: 'resource',
width: '20%',
render: (resource: string) => (
<Tooltip
title={getResourceDescription(resource)}
placement="topLeft"
overlayStyle={{ maxWidth: '400px' }}
>
<span>{resource ? getResourceDescription(resource) : '--'}</span>
</Tooltip>
),
ellipsis: { showTitle: false },
},
{
title: '操作',
dataIndex: 'operation',
width: 320,
key: 'operation',
render: (_: any, record: ServiceVersionData) => (
<div>
<Button
type="link"
size="small"
key="info"
icon={<KFIcon type="icon-xiangqing" />}
onClick={() => toDetail(record)}
>
详情
</Button>
<Button
type="link"
size="small"
key="edit"
icon={<KFIcon type="icon-bianji" />}
onClick={() => createServiceVersion(ServiceOperationType.Update, record)}
>
更新
</Button>
{(record.run_state === ServiceRunStatus.Failed ||
record.run_state === ServiceRunStatus.Stopped) && (
<Button
type="link"
size="small"
key="run"
icon={<KFIcon type="icon-yunhang" />}
onClick={() => createServiceVersion(ServiceOperationType.Restart, record)}
>
重启
</Button>
)}
{(record.run_state === ServiceRunStatus.Running ||
record.run_state === ServiceRunStatus.Init ||
record.run_state === ServiceRunStatus.Pending) && (
<Button
type="link"
size="small"
key="stop"
icon={<KFIcon type="icon-tingzhi" />}
onClick={() => handleServiceVersionStop(record)}
>
停止
</Button>
)}

<ConfigProvider
theme={{
token: {
colorLink: themes['warningColor'],
},
}}
>
<Button
type="link"
size="small"
key="remove"
icon={<KFIcon type="icon-shanchu" />}
onClick={() => handleServiceVersionDelete(record)}
>
删除
</Button>
</ConfigProvider>
</div>
),
},
];

return (
<div className={styles['service-info']}>
<PageTitle title="服务详情"></PageTitle>
<div className={styles['service-info__content']}>
<SubAreaTitle
title="基本信息"
image={require('@/assets/img/mirror-basic.png')}
style={{ marginBottom: '26px', flex: 'none' }}
></SubAreaTitle>
<BasicInfo datas={basicInfo} labelWidth={66} style={{ flex: 'none' }}></BasicInfo>
<SubAreaTitle
title="服务版本"
image={require('@/assets/img/service-version.png')}
style={{ margin: '40px 0 26px', flex: 'none' }}
></SubAreaTitle>
<div className={styles['service-info__content__filter']}>
<Input.Search
placeholder="按版本号筛选"
onSearch={onSearch}
onChange={(e) => setInputText(e.target.value)}
style={{ width: 300 }}
value={inputText}
allowClear
/>
<Select
style={{ width: 100, marginLeft: '20px' }}
placeholder="请选择"
onChange={(value) => setServiceStatus(value)}
options={allServiceStatusOptions}
value={serviceStatus}
allowClear
></Select>
<Button
style={{ marginRight: '20px', marginLeft: 'auto' }}
type="default"
onClick={() => createServiceVersion(ServiceOperationType.Create)}
icon={<KFIcon type="icon-xinjian2" />}
>
新增版本
</Button>
<Button
style={{ marginRight: 0 }}
type="default"
onClick={getServiceVersions}
icon={<KFIcon type="icon-shuaxin" />}
>
刷新
</Button>
</div>
<div
className={classNames('vertical-scroll-table', styles['service-info__content__table'])}
>
<Table
dataSource={tableData}
columns={columns}
scroll={{ y: 'calc(100% - 55px)' }}
pagination={{
...pagination,
total: total,
showSizeChanger: true,
showQuickJumper: true,
showTotal: () => `共${total}条`,
}}
onChange={handleTableChange}
rowKey="id"
/>
</div>
</div>
</div>
);
}

export default ServiceInfo;

react-ui/src/pages/ModelDeployment/Info/index.less → react-ui/src/pages/ModelDeployment/VersionInfo/index.less View File

@@ -1,4 +1,4 @@
.model-deployment-info {
.service-version-info {
height: 100%; height: 100%;


&__content { &__content {

react-ui/src/pages/ModelDeployment/Info/index.tsx → react-ui/src/pages/ModelDeployment/VersionInfo/index.tsx View File

@@ -1,19 +1,20 @@
/* /*
* @Author: 赵伟 * @Author: 赵伟
* @Date: 2024-04-16 13:58:08 * @Date: 2024-04-16 13:58:08
* @Description: 镜像详情
* @Description: 服务版本详情
*/ */
import KFIcon from '@/components/KFIcon'; import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle'; import PageTitle from '@/components/PageTitle';
import SubAreaTitle from '@/components/SubAreaTitle'; import SubAreaTitle from '@/components/SubAreaTitle';
import { useSessionStorage } from '@/hooks/sessionStorage';
import { modelDeploymentInfoKey } from '@/utils/sessionStorage';
import { getServiceVersionInfoReq } from '@/services/modelDeployment';
import { to } from '@/utils/promise';
import { useParams } from '@umijs/max';
import { Tabs, type TabsProps } from 'antd'; import { Tabs, type TabsProps } from 'antd';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import BasicInfo from '../components/BasicInfo'; import BasicInfo from '../components/BasicInfo';
import ServerLog from '../components/ServerLog'; import ServerLog from '../components/ServerLog';
import UserGuide from '../components/UserGuide'; import UserGuide from '../components/UserGuide';
import { ModelDeploymentData } from '../types';
import { ServiceVersionData } from '../types';
import styles from './index.less'; import styles from './index.less';


export enum ModelDeploymentTabKey { export enum ModelDeploymentTabKey {
@@ -22,13 +23,23 @@ export enum ModelDeploymentTabKey {
Log = 'Log', // 服务日志 Log = 'Log', // 服务日志
} }


function ModelDeploymentInfo() {
function ServiceVersionInfo() {
const [activeTab, setActiveTab] = useState<string>(ModelDeploymentTabKey.Predict); const [activeTab, setActiveTab] = useState<string>(ModelDeploymentTabKey.Predict);
const [modelDeployementInfo] = useSessionStorage<ModelDeploymentData | undefined>(
modelDeploymentInfoKey,
true,
undefined,
);
const [versionInfo, setVersionInfo] = useState<ServiceVersionData | undefined>(undefined);
const params = useParams();
const id = params.id;

useEffect(() => {
getServiceVersionInfo();
}, []);

// 获取服务版本详情
const getServiceVersionInfo = async () => {
const [res] = await to(getServiceVersionInfoReq(id));
if (res && res.data) {
setVersionInfo(res.data);
}
};


const tabItems = [ const tabItems = [
{ {
@@ -40,13 +51,13 @@ function ModelDeploymentInfo() {
key: ModelDeploymentTabKey.Guide, key: ModelDeploymentTabKey.Guide,
label: '调用指南', label: '调用指南',
icon: <KFIcon type="icon-tiaoyongzhinan" />, icon: <KFIcon type="icon-tiaoyongzhinan" />,
children: <UserGuide info={modelDeployementInfo}></UserGuide>,
children: <UserGuide info={versionInfo}></UserGuide>,
}, },
{ {
key: ModelDeploymentTabKey.Log, key: ModelDeploymentTabKey.Log,
label: '服务日志', label: '服务日志',
icon: <KFIcon type="icon-fuwurizhi" />, icon: <KFIcon type="icon-fuwurizhi" />,
children: <ServerLog info={modelDeployementInfo}></ServerLog>,
children: <ServerLog info={versionInfo}></ServerLog>,
}, },
]; ];


@@ -56,16 +67,16 @@ function ModelDeploymentInfo() {
}; };


return ( return (
<div className={styles['model-deployment-info']}>
<PageTitle title="服务详情"></PageTitle>
<div className={styles['model-deployment-info__content']}>
<div className={styles['service-version-info']}>
<PageTitle title="服务版本详情"></PageTitle>
<div className={styles['service-version-info__content']}>
<SubAreaTitle <SubAreaTitle
title="基本信息" title="基本信息"
image={require('@/assets/img/mirror-basic.png')} image={require('@/assets/img/mirror-basic.png')}
style={{ marginBottom: '26px' }} style={{ marginBottom: '26px' }}
></SubAreaTitle> ></SubAreaTitle>
<BasicInfo info={modelDeployementInfo} />
<div className={styles['model-deployment-info__content__tabs']}>
<BasicInfo info={versionInfo} />
<div className={styles['service-version-info__content__tabs']}>
<Tabs activeKey={activeTab} items={tabItems} onChange={hanleTabChange} /> <Tabs activeKey={activeTab} items={tabItems} onChange={hanleTabChange} />
</div> </div>
</div> </div>
@@ -73,4 +84,4 @@ function ModelDeploymentInfo() {
); );
} }


export default ModelDeploymentInfo;
export default ServiceVersionInfo;

+ 47
- 21
react-ui/src/pages/ModelDeployment/components/BasicInfo/index.tsx View File

@@ -1,12 +1,13 @@
import LabelValue from '@/components/LabelValue'; import LabelValue from '@/components/LabelValue';
import { useComputingResource } from '@/hooks/resource'; import { useComputingResource } from '@/hooks/resource';
import { ModelDeploymentData } from '@/pages/ModelDeployment/types';
import { ServiceVersionData } from '@/pages/ModelDeployment/types';
import { formatDate } from '@/utils/date'; import { formatDate } from '@/utils/date';
import { Link } from '@umijs/max';
import { Col, Row } from 'antd'; import { Col, Row } from 'antd';
import ModelDeploymentStatusCell from '../ModelDeployStatusCell';
import ServiceRunStatusCell from '../ModelDeployStatusCell';


type BasicInfoProps = { type BasicInfoProps = {
info?: ModelDeploymentData;
info?: ServiceVersionData;
}; };


function BasicInfo({ info }: BasicInfoProps) { function BasicInfo({ info }: BasicInfoProps) {
@@ -14,42 +15,75 @@ function BasicInfo({ info }: BasicInfoProps) {


// 格式化环境变量 // 格式化环境变量
const formatEnvText = () => { const formatEnvText = () => {
if (!info?.env) {
if (!info?.env_variables) {
return '--'; return '--';
} }
const env = info.env;
const env = info.env_variables;
return Object.entries(env) return Object.entries(env)
.map(([key, value]) => `${key}: ${value}`) .map(([key, value]) => `${key}: ${value}`)
.join('\n'); .join('\n');
}; };


const formatCodeConfig = () => {
if (info && info.code_config) {
const url = `${info.code_config.code_path}/tree/${info.code_config.branch}`;
return (
<a href={url} target="_blank" rel="noreferrer">
{info?.code_config?.show_value}
</a>
);
}
return undefined;
};

const formatResource = () => {
if (info && info.resource) {
return getResourceDescription(info.resource);
}
return undefined;
};

const formatModel = () => {
if (info && info.model) {
const model = info.model;
const path = `/dataset/model/info/${model.id}?version=${model.version}&name=${model.name}&owner=${model.owner}&identifier=${model.identifier}`;
return <Link to={path}>{info?.model?.show_value}</Link>;
}
return undefined;
};

return ( return (
<div> <div>
<Row gutter={40} style={{ marginBottom: '20px' }}> <Row gutter={40} style={{ marginBottom: '20px' }}>
<Col span={10}> <Col span={10}>
<LabelValue label="服务名称:" value={info?.service_name}></LabelValue> <LabelValue label="服务名称:" value={info?.service_name}></LabelValue>
</Col> </Col>
<Col span={10}>
<LabelValue label="版本名称:" value={info?.version}></LabelValue>
</Col>
</Row>
<Row gutter={40} style={{ marginBottom: '20px' }}>
<Col span={10}>
<LabelValue label="代码配置" value={formatCodeConfig()}></LabelValue>
</Col>
<Col span={10}> <Col span={10}>
<LabelValue label="镜  像:" value={info?.image}></LabelValue> <LabelValue label="镜  像:" value={info?.image}></LabelValue>
</Col> </Col>
</Row> </Row>
<Row gutter={40} style={{ marginBottom: '20px' }}> <Row gutter={40} style={{ marginBottom: '20px' }}>
<Col span={10}> <Col span={10}>
<LabelValue
label="状  态:"
value={ModelDeploymentStatusCell(info?.status)}
></LabelValue>
<LabelValue label="状  态:" value={ServiceRunStatusCell(info?.run_state)}></LabelValue>
</Col> </Col>
<Col span={10}> <Col span={10}>
<LabelValue label="模  型:" value={info?.model?.show_value}></LabelValue>
<LabelValue label="模  型:" value={formatModel()}></LabelValue>
</Col> </Col>
</Row> </Row>
<Row gutter={40} style={{ marginBottom: '20px' }}> <Row gutter={40} style={{ marginBottom: '20px' }}>
<Col span={10}> <Col span={10}>
<LabelValue label="创建人:" value={info?.created_by}></LabelValue>
<LabelValue label="资源规格:" value={formatResource()}></LabelValue>
</Col> </Col>
<Col span={10}> <Col span={10}>
<LabelValue label="挂载路径:" value={info?.model_path}></LabelValue>
<LabelValue label="挂载路径:" value={info?.mount_path}></LabelValue>
</Col> </Col>
</Row> </Row>
<Row gutter={40} style={{ marginBottom: '20px' }}> <Row gutter={40} style={{ marginBottom: '20px' }}>
@@ -68,19 +102,11 @@ function BasicInfo({ info }: BasicInfoProps) {
<LabelValue label="更新时间:" value={formatDate(info?.update_time)}></LabelValue> <LabelValue label="更新时间:" value={formatDate(info?.update_time)}></LabelValue>
</Col> </Col>
</Row> </Row>
<Row gutter={40} style={{ marginBottom: '20px' }}>
<Row gutter={40}>
<Col span={10}> <Col span={10}>
<LabelValue label="环境变量:" value={formatEnvText()}></LabelValue> <LabelValue label="环境变量:" value={formatEnvText()}></LabelValue>
</Col> </Col>
<Col span={10}> <Col span={10}>
<LabelValue
label="资源规格:"
value={info?.resource ? getResourceDescription(info.resource) : '--'}
></LabelValue>
</Col>
</Row>
<Row gutter={40}>
<Col span={18}>
<LabelValue label="描  述:" value={info?.description}></LabelValue> <LabelValue label="描  述:" value={info?.description}></LabelValue>
</Col> </Col>
</Row> </Row>


+ 10
- 10
react-ui/src/pages/ModelDeployment/components/ModelDeployStatusCell/index.tsx View File

@@ -3,42 +3,42 @@
* @Date: 2024-04-18 18:35:41 * @Date: 2024-04-18 18:35:41
* @Description: 模型部署状态 * @Description: 模型部署状态
*/ */
import { ModelDeploymentStatus } from '@/enums';
import { ServiceRunStatus } from '@/enums';
import styles from './index.less'; import styles from './index.less';


export type ModelDeploymentStatusInfo = {
export type ServiceRunStatusInfo = {
text: string; text: string;
classname: string; classname: string;
}; };


export const statusInfo: Record<ModelDeploymentStatus, ModelDeploymentStatusInfo> = {
[ModelDeploymentStatus.Init]: {
export const statusInfo: Record<ServiceRunStatus, ServiceRunStatusInfo> = {
[ServiceRunStatus.Init]: {
text: '启动中', text: '启动中',
classname: styles['model-deployment-status-cell'], classname: styles['model-deployment-status-cell'],
}, },
[ModelDeploymentStatus.Running]: {
[ServiceRunStatus.Running]: {
classname: styles['model-deployment-status-cell--running'], classname: styles['model-deployment-status-cell--running'],
text: '运行中', text: '运行中',
}, },
[ModelDeploymentStatus.Stopped]: {
[ServiceRunStatus.Stopped]: {
classname: styles['model-deployment-status-cell--stopped'], classname: styles['model-deployment-status-cell--stopped'],
text: '已停止', text: '已停止',
}, },
[ModelDeploymentStatus.Failed]: {
[ServiceRunStatus.Failed]: {
classname: styles['model-deployment-status-cell--error'], classname: styles['model-deployment-status-cell--error'],
text: '失败', text: '失败',
}, },
[ModelDeploymentStatus.Pending]: {
[ServiceRunStatus.Pending]: {
classname: styles['model-deployment-status-cell--pending'], classname: styles['model-deployment-status-cell--pending'],
text: '挂起中', text: '挂起中',
}, },
}; };


function ModelDeploymentStatusCell(status?: ModelDeploymentStatus | null) {
function ServiceRunStatusCell(status?: ServiceRunStatus | null) {
if (status === null || status === undefined || !statusInfo[status]) { if (status === null || status === undefined || !statusInfo[status]) {
return <span>--</span>; return <span>--</span>;
} }
return <span className={statusInfo[status].classname}>{statusInfo[status].text}</span>; return <span className={statusInfo[status].classname}>{statusInfo[status].text}</span>;
} }


export default ModelDeploymentStatusCell;
export default ServiceRunStatusCell;

+ 5
- 6
react-ui/src/pages/ModelDeployment/components/ServerLog/index.tsx View File

@@ -1,10 +1,9 @@
import { ModelDeploymentData } from '@/pages/ModelDeployment/types';
import { getModelDeploymentLogReq } from '@/services/modelDeployment';
import { ServiceVersionData } from '@/pages/ModelDeployment/types';
import { getServiceVersionLogReq } from '@/services/modelDeployment';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import { DoubleRightOutlined } from '@ant-design/icons'; import { DoubleRightOutlined } from '@ant-design/icons';
import { Button, DatePicker, type TimeRangePickerProps } from 'antd'; import { Button, DatePicker, type TimeRangePickerProps } from 'antd';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { pick } from 'lodash';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import styles from './index.less'; import styles from './index.less';
const { RangePicker } = DatePicker; const { RangePicker } = DatePicker;
@@ -28,7 +27,7 @@ type LogData = {
}; };


type ServerLogProps = { type ServerLogProps = {
info?: ModelDeploymentData;
info?: ServiceVersionData;
}; };


function ServerLog({ info }: ServerLogProps) { function ServerLog({ info }: ServerLogProps) {
@@ -64,9 +63,9 @@ function ServerLog({ info }: ServerLogProps) {
const params = { const params = {
start_time: logTime[0], start_time: logTime[0],
end_time: logTime[1], end_time: logTime[1],
...pick(info, ['service_id', 'service_ins_id']),
id: info.id,
}; };
const [res] = await to(getModelDeploymentLogReq(params));
const [res] = await to(getServiceVersionLogReq(params));
if (res && res.data) { if (res && res.data) {
setLogData((prev) => [...prev, res.data]); setLogData((prev) => [...prev, res.data]);
setHasMore(!!res.data.log_content); setHasMore(!!res.data.log_content);


+ 4
- 6
react-ui/src/pages/ModelDeployment/components/UserGuide/index.tsx View File

@@ -1,12 +1,11 @@
import { ModelDeploymentData } from '@/pages/ModelDeployment/types';
import { getModelDeploymentDocsReq } from '@/services/modelDeployment';
import { ServiceVersionData } from '@/pages/ModelDeployment/types';
import { getServiceVersionDocsReq } from '@/services/modelDeployment';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import { pick } from 'lodash';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import styles from './index.less'; import styles from './index.less';


type UserGuideProps = { type UserGuideProps = {
info?: ModelDeploymentData;
info?: ServiceVersionData;
}; };


function UserGuide({ info }: UserGuideProps) { function UserGuide({ info }: UserGuideProps) {
@@ -18,8 +17,7 @@ function UserGuide({ info }: UserGuideProps) {
// 获取模型部署文档 // 获取模型部署文档
const getModelDeploymentDocs = async () => { const getModelDeploymentDocs = async () => {
if (info) { if (info) {
const params = pick(info, ['service_id', 'service_ins_id']);
const [res] = await to(getModelDeploymentDocsReq(params));
const [res] = await to(getServiceVersionDocsReq(info.id));
if (res && res.data && res.data.docs) { if (res && res.data && res.data.docs) {
setDocs(JSON.stringify(res.data.docs, null, 2)); setDocs(JSON.stringify(res.data.docs, null, 2));
} }


+ 44
- 17
react-ui/src/pages/ModelDeployment/types.ts View File

@@ -1,31 +1,58 @@
import { ModelDeploymentStatus } from '@/enums';
import { ServiceRunStatus } from '@/enums';


// 模型部署列表数据类型
export type ModelDeploymentData = {
service_id: number;
service_ins_id: number;
service_name: string;
description: string;
status: ModelDeploymentStatus;
update_time: string;
create_time: string;
// 服务列表数据类型
export type ServiceData = {
id: number; // 服务id
service_name: string; // 服务名称
service_type: string; // 服务类型
service_type_name: string; // 服务类型中文
description: string; // 描述
version_count: number; // 版本数量
created_by: string; created_by: string;
model_path: string;
url: string;
image: string;
replicas: number;
resource: string;
create_time: string;
update_by: string;
update_time: string;
};

// 服务版本数据类型
export type ServiceVersionData = {
id: number; // 版本id
service_id: number; // 服务id
service_name: string; // 服务名称
description: string; // 版本描述
version: string; // 版本
run_state: ServiceRunStatus; // 运行状态
image: string; // 镜像
replicas: number; // 副本数
resource: string; // 资源
mount_path: string; // 挂载路径
model: { model: {
// 模型
id: number; id: number;
name: string;
version: string; version: string;
path: string; path: string;
identifier: string;
owner: string;
show_value: string; show_value: string;
}; };
env: Record<string, string>;
code_config: {
// 代码配置
code_path: string;
branch: string;
show_value: string;
};
env_variables: Record<string, string>; // 环境变量
url: string; // API URL
deployment_name: string;
update_by: string;
update_time: string;
create_time: string;
created_by: string;
}; };


// 操作类型 // 操作类型
export enum ModelDeploymentOperationType {
export enum ServiceOperationType {
Create = 'Create', // 创建 Create = 'Create', // 创建
Update = 'Update', // 更新 Update = 'Update', // 更新
Restart = 'Restart', // 重启 Restart = 'Restart', // 重启


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

@@ -15,6 +15,8 @@ import { useEffect, useState } from 'react';
import CodeConfigItem from '../CodeConfigItem'; import CodeConfigItem from '../CodeConfigItem';
import styles from './index.less'; import styles from './index.less';


export { type CodeConfigData };

export interface CodeSelectorModalProps extends Omit<ModalProps, 'onOk'> { export interface CodeSelectorModalProps extends Omit<ModalProps, 'onOk'> {
onOk?: (params: CodeConfigData | undefined) => void; onOk?: (params: CodeConfigData | undefined) => void;
} }


+ 0
- 1
react-ui/src/pages/Pipeline/components/PipelineNodeDrawer/index.tsx View File

@@ -136,7 +136,6 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete
const { close } = openAntdModal(CodeSelectorModal, { const { close } = openAntdModal(CodeSelectorModal, {
onOk: (res) => { onOk: (res) => {
if (res) { if (res) {
console.log('res', res);
const value = JSON.stringify({ const value = JSON.stringify({
id: res.id, id: res.id,
name: res.code_repo_name, name: res.code_repo_name,


+ 67
- 38
react-ui/src/services/modelDeployment/index.ts View File

@@ -5,73 +5,102 @@
*/ */
import { request } from '@umijs/max'; import { request } from '@umijs/max';


// 分页查询模型部署列表
export function getModelDeploymentListReq(data: any) {
return request(`/api/v1/model/get`, {
// 分页查询服务列表
export function getServiceListReq(data: any) {
return request(`/api/mmp/service`, {
method: 'GET',
params: data,
});
}

// 创建推理服务
export function createServiceReq(data: any) {
return request(`/api/mmp/service`, {
method: 'POST', method: 'POST',
data, data,
}); });
} }


// 查询模型部署详情
export function getModelDeploymentInfoReq(id: number) {
return request(`/api/mmp/image/${id}`, {
// 编辑推理服务
export function updateServiceReq(data: any) {
return request(`/api/mmp/service`, {
method: 'PUT',
data,
});
}

// 删除推理服务
export function deleteServiceReq(id: any) {
return request(`/api/mmp/service/${id}`, {
method: 'DELETE',
});
}

// 获取服务详情
export function getServiceInfoReq(id: any) {
return request(`/api/mmp/service/serviceDetail/${id}`, {
method: 'GET', method: 'GET',
}); });
} }


// 创建模型部署
export function createModelDeploymentReq(data: any) {
return request(`/api/v1/model/create`, {
method: 'POST',
data,
// ------------------------------- 服务版本部分 ---------------------------------------------

// 获取服务版本列表
export function getServiceVersionsReq(data: any) {
return request(`/api/mmp/service/serviceVersion`, {
method: 'GET',
params: data,
}); });
} }


// 删除模型部署
export function deleteModelDeploymentReq(data: any) {
return request(`/api/v1/model/delete`, {
// 创建服务版本
export function createServiceVersionReq(data: any) {
return request(`/api/mmp/service/serviceVersion`, {
method: 'POST', method: 'POST',
data, data,
}); });
} }


// 重启模型部署
export function restartModelDeploymentReq(data: any) {
return request(`/api/v1/model/restart`, {
method: 'POST',
// 更新服务版本
export function updateServiceVersionReq(data: any) {
return request(`/api/mmp/service/serviceVersion`, {
method: 'PUT',
data, data,
}); });
} }


// 停止模型部署
export function stopModelDeploymentReq(data: any) {
return request(`/api/v1/model/stop`, {
method: 'POST',
data,
// 删除服务版本
export function deleteServiceVersionReq(id: any) {
return request(`/api/mmp/service/serviceVersion/${id}`, {
method: 'DELETE',
}); });
} }


// 更新模型部署
export function updateModelDeploymentReq(data: any) {
return request(`/api/v1/model/update`, {
method: 'POST',
data,
// 获取服务版本详情
export function getServiceVersionInfoReq(id: any) {
return request(`/api/mmp/service/serviceVersionDetail/${id}`, {
method: 'GET',
}); });
} }


// 获取模型部署操作指南
export function getModelDeploymentDocsReq(data: any) {
return request(`/api/v1/model/getDocs`, {
method: 'POST',
data,
// 停止服务版本
export function stopServiceVersionReq(id: any) {
return request(`/api/mmp/service/stopServiceVersion/${id}`, {
method: 'DELETE',
}); });
} }


// 获取模型部署日志
export function getModelDeploymentLogReq(data: any) {
return request(`/api/v1/model/getAppLog`, {
method: 'POST',
data,
// 获取服务版本操作指南
export function getServiceVersionDocsReq(id: any) {
return request(`/api/mmp/service/getServiceVersionDocs/${id}`, {
method: 'GET',
});
}

// 获取服务版本日志
export function getServiceVersionLogReq(params: any) {
return request(`/api/mmp/service/getServiceVersionLog`, {
method: 'GET',
params,
}); });
} }

+ 4
- 2
react-ui/src/utils/sessionStorage.ts View File

@@ -1,7 +1,9 @@
// 用于新建镜像 // 用于新建镜像
export const mirrorNameKey = 'mirror-name'; export const mirrorNameKey = 'mirror-name';
// 模型部署
export const modelDeploymentInfoKey = 'model-deployment-info';
// 模型部署服务
export const serviceInfoKey = 'service-info';
// 模型部署服务版本
export const serviceVersionInfoKey = 'service-version-info';
// 编辑器 url // 编辑器 url
export const editorUrlKey = 'editor-url'; export const editorUrlKey = 'editor-url';
// 数据集、模型资源 // 数据集、模型资源


Loading…
Cancel
Save