| @@ -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', | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| @@ -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> | |||||
| ); | ); | ||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,11 @@ | |||||
| .kf-code-select { | |||||
| position: relative; | |||||
| display: flex; | |||||
| align-items: center; | |||||
| &__button { | |||||
| position: absolute; | |||||
| top: 0; | |||||
| left: calc(100% + 10px); | |||||
| } | |||||
| } | |||||
| @@ -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; | |||||
| @@ -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'; | ||||
| @@ -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'; | ||||
| @@ -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} | ||||
| @@ -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 }, | |||||
| ]; | |||||
| @@ -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); | ||||
| } | } | ||||
| }; | }; | ||||
| @@ -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; | |||||
| @@ -1,4 +1,4 @@ | |||||
| .model-deployment-create { | |||||
| .create-service-version { | |||||
| height: 100%; | height: 100%; | ||||
| &__content { | &__content { | ||||
| @@ -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; | |||||
| @@ -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> | ||||
| @@ -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%; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -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; | |||||
| @@ -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; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -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; | |||||
| @@ -1,4 +1,4 @@ | |||||
| .model-deployment-info { | |||||
| .service-version-info { | |||||
| height: 100%; | height: 100%; | ||||
| &__content { | &__content { | ||||
| @@ -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; | |||||
| @@ -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> | ||||
| @@ -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; | |||||
| @@ -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); | ||||
| @@ -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)); | ||||
| } | } | ||||
| @@ -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', // 重启 | ||||
| @@ -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; | ||||
| } | } | ||||
| @@ -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, | ||||
| @@ -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, | |||||
| }); | }); | ||||
| } | } | ||||
| @@ -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'; | ||||
| // 数据集、模型资源 | // 数据集、模型资源 | ||||