| @@ -19,7 +19,6 @@ const Settings: ProLayoutProps & { | |||
| title: '智能软件开发平台', | |||
| pwa: true, | |||
| logo: '/assets/images/left-top-logo.png', | |||
| iconfontUrl: '//at.alicdn.com/t/c/font_4511326_a182r7rksx5.js', | |||
| token: { | |||
| // 参见ts声明,demo 见文档,通过token 修改样式 | |||
| //https://procomponents.ant.design/components/layout#%E9%80%9A%E8%BF%87-token-%E4%BF%AE%E6%94%B9%E6%A0%B7%E5%BC%8F | |||
| @@ -206,17 +206,17 @@ export default [ | |||
| { | |||
| name: '模型列表', | |||
| path: '', | |||
| component: './ModelDeployment/list', | |||
| component: './ModelDeployment/List', | |||
| }, | |||
| { | |||
| name: '镜像详情', | |||
| path: ':id', | |||
| component: './ModelDeployment/info', | |||
| component: './ModelDeployment/Info', | |||
| }, | |||
| { | |||
| name: '创建镜像', | |||
| path: 'create', | |||
| component: './ModelDeployment/create', | |||
| component: './ModelDeployment/Create', | |||
| }, | |||
| ], | |||
| }, | |||
| @@ -224,6 +224,9 @@ export const antd: RuntimeAntdConfig = (memo) => { | |||
| inputFontSizeLG: parseInt(themes['fontSizeInputLg']), | |||
| paddingBlockLG: 10, | |||
| }; | |||
| memo.theme.components.Select = { | |||
| singleItemHeightLG: 46, | |||
| }; | |||
| memo.theme.components.Table = { | |||
| headerBg: 'rgba(242, 244, 247, 0.36)', | |||
| headerBorderRadius: 4, | |||
| @@ -3,6 +3,7 @@ | |||
| * @Date: 2024-04-17 12:53:06 | |||
| * @Description: | |||
| */ | |||
| import '@/iconfont/iconfont-menu.js'; | |||
| import '@/iconfont/iconfont.js'; | |||
| import { createFromIconfontCN } from '@ant-design/icons'; | |||
| @@ -4,4 +4,7 @@ | |||
| height: 50px; | |||
| padding-left: 30px; | |||
| background-image: url(@/assets/img/page-title-bg.png); | |||
| background-repeat: no-repeat; | |||
| background-position: top center; | |||
| background-size: 100%; | |||
| } | |||
| @@ -1,8 +1,7 @@ | |||
| .parameter-input { | |||
| flex: 1 1 auto; | |||
| width: 100%; | |||
| min-width: 0; | |||
| height: 32px; | |||
| padding: 3px 11px; | |||
| padding: 4px 11px; | |||
| border: 1px solid #d9d9d9; | |||
| border-radius: 6px; | |||
| @@ -15,7 +14,7 @@ | |||
| align-items: center; | |||
| width: fit-content; | |||
| max-width: 100%; | |||
| height: 24px; | |||
| min-height: 22px; | |||
| padding: 0 8px; | |||
| color: .addAlpha(@text-color, 0.8) []; | |||
| background-color: rgba(0, 0, 0, 0.06); | |||
| @@ -25,6 +24,7 @@ | |||
| .singleLine(); | |||
| margin-right: 8px; | |||
| font-size: @font-size-input; | |||
| line-height: 1.5714285714285714; | |||
| } | |||
| &__close-icon { | |||
| @@ -37,7 +37,28 @@ | |||
| } | |||
| &__placeholder { | |||
| min-height: 22px; | |||
| color: rgba(0, 0, 0, 0.25); | |||
| font-size: @font-size-input; | |||
| line-height: 1.5714285714285714; | |||
| } | |||
| } | |||
| .parameter-input.parameter-input--large { | |||
| padding: 10px 11px; | |||
| font-size: @font-size-input-lg; | |||
| .parameter-input__placeholder { | |||
| font-size: @font-size-input-lg; | |||
| line-height: 1.5; | |||
| } | |||
| .parameter-input__content__value { | |||
| font-size: @font-size-input-lg; | |||
| line-height: 1.5; | |||
| } | |||
| .parameter-input__content__close-icon { | |||
| font-size: 12px; | |||
| } | |||
| } | |||
| @@ -1,6 +1,7 @@ | |||
| import { CloseOutlined } from '@ant-design/icons'; | |||
| import { Input } from 'antd'; | |||
| import styles from './index.less'; | |||
| import classNames from 'classnames'; | |||
| import './index.less'; | |||
| type ParameterInputData = { | |||
| value?: any; | |||
| @@ -16,6 +17,10 @@ interface ParameterInputProps { | |||
| textArea?: boolean; | |||
| placeholder?: string; | |||
| allowClear?: boolean; | |||
| className?: string; | |||
| style?: React.CSSProperties; | |||
| size?: 'middle' | 'small' | 'large'; | |||
| disabled?: boolean; | |||
| } | |||
| function ParameterInput({ | |||
| @@ -26,6 +31,10 @@ function ParameterInput({ | |||
| textArea = false, | |||
| placeholder, | |||
| allowClear, | |||
| className, | |||
| style, | |||
| size = 'middle', | |||
| disabled = false, | |||
| ...rest | |||
| }: ParameterInputProps) { | |||
| // console.log('ParameterInput', value); | |||
| @@ -40,15 +49,21 @@ function ParameterInput({ | |||
| return ( | |||
| <> | |||
| {isSelect || !canInput ? ( | |||
| <div className={styles['parameter-input']} onClick={onClick}> | |||
| {(isSelect || !canInput) && !disabled ? ( | |||
| <div | |||
| className={classNames( | |||
| 'parameter-input', | |||
| { 'parameter-input--large': size === 'large' }, | |||
| className, | |||
| )} | |||
| style={style} | |||
| onClick={onClick} | |||
| > | |||
| {valueObj?.showValue ? ( | |||
| <div className={styles['parameter-input__content']}> | |||
| <span className={styles['parameter-input__content__value']}> | |||
| {valueObj?.showValue} | |||
| </span> | |||
| <div className="parameter-input__content"> | |||
| <span className="parameter-input__content__value">{valueObj?.showValue}</span> | |||
| <CloseOutlined | |||
| className={styles['parameter-input__content__close-icon']} | |||
| className="parameter-input__content__close-icon" | |||
| onClick={() => | |||
| onChange?.({ | |||
| ...valueObj, | |||
| @@ -60,15 +75,19 @@ function ParameterInput({ | |||
| /> | |||
| </div> | |||
| ) : ( | |||
| <div className={styles['parameter-input__placeholder']}>{placeholder}</div> | |||
| <div className="parameter-input__placeholder">{placeholder}</div> | |||
| )} | |||
| </div> | |||
| ) : ( | |||
| <InputComponent | |||
| {...rest} | |||
| size={size} | |||
| className={className} | |||
| style={style} | |||
| placeholder={placeholder} | |||
| allowClear={allowClear} | |||
| value={valueObj?.showValue} | |||
| disabled={disabled} | |||
| onChange={(e) => | |||
| onChange?.({ | |||
| ...valueObj, | |||
| @@ -4,9 +4,25 @@ export enum CommonTabKeys { | |||
| Public = 'Public', // 公开 | |||
| } | |||
| // 镜像状态 | |||
| // 镜像版本状态 | |||
| export enum MirrorVersionStatus { | |||
| Available = 'available', // 可用 | |||
| Building = 'building', // 构建中 | |||
| Failed = 'failed', // 构建中 | |||
| } | |||
| // 模型部署状态 | |||
| export enum ModelDeploymentStatus { | |||
| Init = 'Init', // 启动中 | |||
| Running = 'Running', // 运行中 | |||
| Stopped = 'Stopped', // 已停止 | |||
| Failed = 'Failed', // 失败 | |||
| } | |||
| export const modelDeploymentStatusOptions = [ | |||
| { label: '全部', value: '' }, | |||
| { label: '启动中', value: ModelDeploymentStatus.Init }, | |||
| { label: '运行中', value: ModelDeploymentStatus.Running }, | |||
| { label: '已停止', value: ModelDeploymentStatus.Stopped }, | |||
| { label: '失败', value: ModelDeploymentStatus.Failed }, | |||
| ]; | |||
| @@ -0,0 +1,45 @@ | |||
| import { getComputingResourceReq } from '@/services/pipeline'; | |||
| import { ComputingResource } from '@/types'; | |||
| import { to } from '@/utils/promise'; | |||
| import { type SelectProps } from 'antd'; | |||
| import { useCallback, useEffect, useState } from 'react'; | |||
| export function useComputingResource() { | |||
| const [resourceStandardList, setResourceStandardList] = useState<ComputingResource[]>([]); | |||
| useEffect(() => { | |||
| getComputingResource(); | |||
| }, []); | |||
| // 获取资源规格列表数据 | |||
| const getComputingResource = useCallback(async () => { | |||
| const params = { | |||
| page: 0, | |||
| size: 1000, | |||
| resource_type: '', | |||
| }; | |||
| const [res] = await to(getComputingResourceReq(params)); | |||
| if (res && res.data && res.data.content) { | |||
| setResourceStandardList(res.data.content); | |||
| } | |||
| }, []); | |||
| // 过滤资源规格 | |||
| const filterResourceStandard: SelectProps<string, ComputingResource>['filterOption'] = | |||
| useCallback((input: string, option?: ComputingResource) => { | |||
| return ( | |||
| option?.computing_resource?.toLocaleLowerCase()?.includes(input.toLocaleLowerCase()) ?? | |||
| false | |||
| ); | |||
| }, []); | |||
| // 根据 standard 获取 description | |||
| const getDescription = useCallback( | |||
| (standard: string) => { | |||
| return resourceStandardList.find((item) => item.standard === standard)?.description; | |||
| }, | |||
| [resourceStandardList], | |||
| ); | |||
| return [resourceStandardList, filterResourceStandard, getDescription] as const; | |||
| } | |||
| @@ -0,0 +1,18 @@ | |||
| import { getSessionStorageItem, removeSessionStorageItem } from '@/utils/sessionStorage'; | |||
| import { useEffect, useState } from 'react'; | |||
| export function useSessionStorage<T>(key: string, isObject: boolean, initialValue: T) { | |||
| const [storage, setStorage] = useState<T>(initialValue); | |||
| useEffect(() => { | |||
| const res = getSessionStorageItem(key, isObject); | |||
| if (res) { | |||
| setStorage(res); | |||
| } | |||
| return () => { | |||
| removeSessionStorageItem(key); | |||
| }; | |||
| }, []); | |||
| return [storage]; | |||
| } | |||
| @@ -20,7 +20,7 @@ import { | |||
| } from 'antd'; | |||
| import { omit } from 'lodash'; | |||
| import { useEffect, useState } from 'react'; | |||
| import { CategoryData } from '../../type'; | |||
| import { CategoryData } from '../../types'; | |||
| import styles from './index.less'; | |||
| interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> { | |||
| @@ -1,7 +1,7 @@ | |||
| import { getAccessToken } from '@/access'; | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import KFModal from '@/components/KFModal'; | |||
| import { CategoryData } from '@/pages/Dataset/type'; | |||
| import { CategoryData } from '@/pages/Dataset/types'; | |||
| import { addModel } from '@/services/dataset/index.js'; | |||
| import { to } from '@/utils/promise'; | |||
| import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | |||
| @@ -1,7 +1,7 @@ | |||
| import { getAccessToken } from '@/access'; | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import KFModal from '@/components/KFModal'; | |||
| import { ResourceType, resourceConfig } from '@/pages/Dataset/type'; | |||
| import { ResourceType, resourceConfig } from '@/pages/Dataset/types'; | |||
| import { to } from '@/utils/promise'; | |||
| import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | |||
| import { | |||
| @@ -1,5 +1,5 @@ | |||
| import classNames from 'classnames'; | |||
| import { CategoryData, ResourceType, resourceConfig } from '../../type'; | |||
| import { CategoryData, ResourceType, resourceConfig } from '../../types'; | |||
| import styles from './index.less'; | |||
| type CategoryItemProps = { | |||
| @@ -1,5 +1,5 @@ | |||
| import { Flex, Input } from 'antd'; | |||
| import { CategoryData, ResourceType, resourceConfig } from '../../type'; | |||
| import { CategoryData, ResourceType, resourceConfig } from '../../types'; | |||
| import CategoryItem from '../CategoryItem'; | |||
| import styles from './index.less'; | |||
| @@ -7,7 +7,7 @@ import { modalConfirm } from '@/utils/ui'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import { Button, Input, Pagination, PaginationProps, message } from 'antd'; | |||
| import { Ref, forwardRef, useEffect, useImperativeHandle, useState } from 'react'; | |||
| import { CategoryData, ResourceData, ResourceType, resourceConfig } from '../../type'; | |||
| import { CategoryData, ResourceData, ResourceType, resourceConfig } from '../../types'; | |||
| import AddDatasetModal from '../AddDatasetModal'; | |||
| import ResourceItem from '../Resourcetem'; | |||
| import styles from './index.less'; | |||
| @@ -4,5 +4,8 @@ | |||
| height: 50px; | |||
| padding-left: 27px; | |||
| background-image: url(@/assets/img/page-title-bg.png); | |||
| background-repeat: no-repeat; | |||
| background-position: top center; | |||
| background-size: 100% 100%; | |||
| } | |||
| } | |||
| @@ -4,7 +4,7 @@ import { getAssetIcon } from '@/services/dataset/index.js'; | |||
| import { to } from '@/utils/promise'; | |||
| import { Flex, Tabs, type TabsProps } from 'antd'; | |||
| import { useEffect, useRef, useState } from 'react'; | |||
| import { CategoryData, ResourceType, resourceConfig } from '../../type'; | |||
| import { CategoryData, ResourceType, resourceConfig } from '../../types'; | |||
| import CategoryList from '../CategoryList'; | |||
| import ResourceList, { ResourceListRef } from '../ResourceList'; | |||
| import styles from './index.less'; | |||
| @@ -3,7 +3,7 @@ import creatByImg from '@/assets/img/creatBy.png'; | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { formatDate } from '@/utils/date'; | |||
| import { Button, Flex, Typography } from 'antd'; | |||
| import { ResourceData } from '../../type'; | |||
| import { ResourceData } from '../../types'; | |||
| import styles from './index.less'; | |||
| type ResourceItemProps = { | |||
| @@ -1,5 +1,5 @@ | |||
| import ResourcePage from './components/ResourcePage'; | |||
| import { ResourceType } from './type'; | |||
| import { ResourceType } from './types'; | |||
| const DatasetPage = () => { | |||
| return <ResourcePage resourceType={ResourceType.Dataset} />; | |||
| @@ -1,5 +1,5 @@ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { ResourceType } from '@/pages/Dataset/type'; | |||
| import { ResourceType } from '@/pages/Dataset/types'; | |||
| import { | |||
| deleteDatasetVersion, | |||
| getDatasetById, | |||
| @@ -7,7 +7,10 @@ | |||
| margin-bottom: 10px; | |||
| padding: 25px 30px; | |||
| background-image: url(/assets/images/dataset-back.png); | |||
| background-repeat: no-repeat; | |||
| background-position: top center; | |||
| background-size: 100% 100%; | |||
| .smallTagBox { | |||
| display: flex; | |||
| align-items: center; | |||
| @@ -19,9 +19,6 @@ export enum ResourceType { | |||
| Dataset = 'Dataset', // 数据集 | |||
| } | |||
| type ResourceTypeKeys = keyof typeof ResourceType; | |||
| export type ResourceTypeValues = (typeof ResourceType)[ResourceTypeKeys]; | |||
| type ResourceTypeInfo = { | |||
| getList: (params: any) => Promise<any>; | |||
| getVersions: (params: any) => Promise<any>; | |||
| @@ -45,7 +42,7 @@ type ResourceTypeInfo = { | |||
| uploadAccept?: string; | |||
| }; | |||
| export const resourceConfig: Record<ResourceTypeValues, ResourceTypeInfo> = { | |||
| export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | |||
| [ResourceType.Dataset]: { | |||
| getList: getDatasetList, | |||
| getVersions: getDatasetVersionsById, | |||
| @@ -16,7 +16,7 @@ function DatasetAnnotation() { | |||
| }; | |||
| return ( | |||
| <div className={styles.container}> | |||
| {iframeUrl && <iframe src={iframeUrl} className={styles.frame}></iframe>} | |||
| <iframe src="http://172.20.32.181:31213/label-studio" className={styles.frame}></iframe> | |||
| </div> | |||
| ); | |||
| } | |||
| @@ -6,6 +6,8 @@ | |||
| height: 49px; | |||
| padding-right: 30px; | |||
| background-image: url(/assets/images/pipeline-back.png); | |||
| background-repeat: no-repeat; | |||
| background-position: top center; | |||
| background-size: 100% 100%; | |||
| } | |||
| .pipelineTopBox { | |||
| @@ -17,6 +19,8 @@ | |||
| margin-bottom: 10px; | |||
| padding-right: 30px; | |||
| background-image: url(/assets/images/pipeline-back.png); | |||
| background-repeat: no-repeat; | |||
| background-position: top center; | |||
| background-size: 100% 100%; | |||
| } | |||
| .tableExpandBox { | |||
| @@ -15,10 +15,7 @@ export enum ExperimentStatus { | |||
| Omitted = 'Omitted', | |||
| } | |||
| type ExperimentStatusKeys = keyof typeof ExperimentStatus; | |||
| export type ExperimentStatusValues = (typeof ExperimentStatus)[ExperimentStatusKeys]; | |||
| export const experimentStatusInfo: Record<ExperimentStatusValues, StatusInfo | undefined> = { | |||
| export const experimentStatusInfo: Record<ExperimentStatus, StatusInfo | undefined> = { | |||
| Running: { | |||
| label: '运行中', | |||
| color: '#165bff', | |||
| @@ -6,15 +6,12 @@ | |||
| import { MirrorVersionStatus } from '@/enums'; | |||
| import styles from './index.less'; | |||
| type MirrorVersionStatusKeys = keyof typeof MirrorVersionStatus; | |||
| type MirrorVersionStatusValues = (typeof MirrorVersionStatus)[MirrorVersionStatusKeys]; | |||
| export type MirrorVersionStatusInfo = { | |||
| text: string; | |||
| classname: string; | |||
| }; | |||
| const statusInfo: Record<MirrorVersionStatusValues, MirrorVersionStatusInfo> = { | |||
| const statusInfo: Record<MirrorVersionStatus, MirrorVersionStatusInfo> = { | |||
| [MirrorVersionStatus.Building]: { | |||
| text: '构建中', | |||
| classname: styles['mirror-status-cell'], | |||
| @@ -11,7 +11,11 @@ import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { CommonTabKeys } from '@/enums'; | |||
| import { createMirrorReq } from '@/services/mirror'; | |||
| import { to } from '@/utils/promise'; | |||
| import { getSessionItemThenRemove, mirrorNameKey } from '@/utils/sessionStorage'; | |||
| import { | |||
| getSessionStorageItem, | |||
| mirrorNameKey, | |||
| removeSessionStorageItem, | |||
| } from '@/utils/sessionStorage'; | |||
| import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import { Button, Col, Form, Input, Row, Upload, UploadFile, message, type UploadProps } from 'antd'; | |||
| @@ -56,11 +60,14 @@ function MirrorCreate() { | |||
| }; | |||
| useEffect(() => { | |||
| const name = getSessionItemThenRemove(mirrorNameKey); | |||
| const name = getSessionStorageItem(mirrorNameKey); | |||
| if (name) { | |||
| form.setFieldValue('name', name); | |||
| setNameDisabled(true); | |||
| } | |||
| return () => { | |||
| removeSessionStorageItem(mirrorNameKey); | |||
| }; | |||
| }, []); | |||
| // 创建公网、本地镜像 | |||
| @@ -4,6 +4,9 @@ | |||
| height: 50px; | |||
| padding-left: 27px; | |||
| background-image: url(@/assets/img/page-title-bg.png); | |||
| background-repeat: no-repeat; | |||
| background-position: top center; | |||
| background-size: 100% 100%; | |||
| } | |||
| &__content { | |||
| @@ -11,6 +11,7 @@ import { useCacheState } from '@/hooks/pageCacheState'; | |||
| import { deleteMirrorReq, getMirrorListReq } from '@/services/mirror'; | |||
| import themes from '@/styles/theme.less'; | |||
| import { to } from '@/utils/promise'; | |||
| import { mirrorNameKey, setSessionStorageItem } from '@/utils/sessionStorage'; | |||
| import { modalConfirm } from '@/utils/ui'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import { | |||
| @@ -145,6 +146,7 @@ function MirrorList() { | |||
| // 创建镜像 | |||
| const createMirror = () => { | |||
| navigate(`/dataset/mirror/create`); | |||
| setSessionStorageItem(mirrorNameKey, ''); | |||
| setCacheState({ | |||
| activeTab, | |||
| pagination, | |||
| @@ -1,5 +1,5 @@ | |||
| import ResourcePage from '@/pages/Dataset/components/ResourcePage'; | |||
| import { ResourceType } from '@/pages/Dataset/type'; | |||
| import { ResourceType } from '@/pages/Dataset/types'; | |||
| const ModelPage = () => { | |||
| return <ResourcePage resourceType={ResourceType.Model} />; | |||
| @@ -1,6 +1,6 @@ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import AddVersionModal from '@/pages/Dataset/components/AddVersionModal'; | |||
| import { ResourceType } from '@/pages/Dataset/type'; | |||
| import { ResourceType } from '@/pages/Dataset/types'; | |||
| import { | |||
| deleteModelVersion, | |||
| getModelById, | |||
| @@ -7,8 +7,10 @@ | |||
| margin-bottom: 10px; | |||
| padding: 25px 30px; | |||
| background-image: url(/assets/images/dataset-back.png); | |||
| background-repeat: no-repeat; | |||
| background-position: top center; | |||
| background-size: 100% 100%; | |||
| .smallTagBox { | |||
| display: flex; | |||
| align-items: center; | |||
| @@ -6,6 +6,8 @@ | |||
| margin-top: 10px; | |||
| padding: 30px 30px 10px; | |||
| overflow: auto; | |||
| color: @text-color; | |||
| font-size: @font-size-content; | |||
| background-color: white; | |||
| border-radius: 10px; | |||
| @@ -0,0 +1,449 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-16 13:58:08 | |||
| * @Description: 创建模型部署 | |||
| */ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import PageTitle from '@/components/PageTitle'; | |||
| import ParameterInput from '@/components/ParameterInput'; | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { CommonTabKeys } from '@/enums'; | |||
| import { useComputingResource } from '@/hooks/resource'; | |||
| import ResourceSelectorModal, { | |||
| ResourceSelectorResponse, | |||
| ResourceSelectorType, | |||
| selectorTypeConfig, | |||
| } from '@/pages/Pipeline/components/ResourceSelectorModal'; | |||
| import { | |||
| createModelDeploymentReq, | |||
| restartModelDeploymentReq, | |||
| updateModelDeploymentReq, | |||
| } from '@/services/modelDeployment'; | |||
| import { camelCaseToUnderscore, underscoreToCamelCase } from '@/utils'; | |||
| import { openAntdModal } from '@/utils/modal'; | |||
| import { to } from '@/utils/promise'; | |||
| import { | |||
| getSessionStorageItem, | |||
| modelDeploymentInfoKey, | |||
| removeSessionStorageItem, | |||
| } from '@/utils/sessionStorage'; | |||
| import { modalConfirm } from '@/utils/ui'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import { App, Button, Col, Flex, Form, Input, Row, Select } from 'antd'; | |||
| import { omit, pick } from 'lodash'; | |||
| import { useEffect, useState } from 'react'; | |||
| import { ModelDeploymentData, ModelDeploymentOperationType } from '../types'; | |||
| import styles from './index.less'; | |||
| // 表单数据 | |||
| export type FormData = { | |||
| serviceName: string; // 服务名称 | |||
| description: string; // 描述 | |||
| model: { | |||
| id: number; | |||
| version: string; | |||
| value: string; | |||
| showValue: string; | |||
| }; // 模型 | |||
| image: string; // 镜像 | |||
| resource: string; // 资源规格 | |||
| replicas: string; // 副本数量 | |||
| modelPath: string; // 模型路径 | |||
| env: { key: string; value: string }[]; // 环境变量 | |||
| }; | |||
| function ModelDeploymentCreate() { | |||
| const navgite = useNavigate(); | |||
| const [form] = Form.useForm(); | |||
| const [resourceStandardList, filterResourceStandard] = useComputingResource(); | |||
| const [selectedModel, setSelectedModel] = useState<ResourceSelectorResponse | undefined>( | |||
| undefined, | |||
| ); // 选择的模型,为了再次打开时恢复原来的选择 | |||
| const [operationType, setOperationType] = useState(ModelDeploymentOperationType.Create); | |||
| const [modelDeploymentInfo, setModelDeploymentInfo] = useState<ModelDeploymentData | undefined>( | |||
| undefined, | |||
| ); | |||
| const { message } = App.useApp(); | |||
| useEffect(() => { | |||
| const res = getSessionStorageItem(modelDeploymentInfoKey, true); | |||
| if (res) { | |||
| setOperationType(res.operationType); | |||
| setModelDeploymentInfo(res); | |||
| const formData = underscoreToCamelCase(res) as FormData; | |||
| form.setFieldsValue(formData); | |||
| } | |||
| return () => { | |||
| removeSessionStorageItem(modelDeploymentInfoKey); | |||
| }; | |||
| }, []); | |||
| // 获取选择数据集、模型后面按钮 icon | |||
| const getSelectBtnIcon = (type: ResourceSelectorType) => { | |||
| return <KFIcon type={selectorTypeConfig[type].buttonIcon} font={16} />; | |||
| }; | |||
| // 选择模型、镜像 | |||
| const selectResource = (name: string, selectType: string) => { | |||
| let type; | |||
| let resource: ResourceSelectorResponse | undefined; | |||
| switch (selectType) { | |||
| case 'model': | |||
| type = ResourceSelectorType.Model; | |||
| resource = selectedModel; | |||
| break; | |||
| default: | |||
| type = ResourceSelectorType.Mirror; | |||
| break; | |||
| } | |||
| const { close } = openAntdModal(ResourceSelectorModal, { | |||
| type, | |||
| defaultExpandedKeys: resource ? [resource.id] : [], | |||
| defaultCheckedKeys: resource ? [`${resource.id}-${resource.version}`] : [], | |||
| defaultActiveTab: resource?.activeTab, | |||
| onOk: (res) => { | |||
| if (res) { | |||
| if (type === ResourceSelectorType.Mirror) { | |||
| form.setFieldValue(name, res); | |||
| } else { | |||
| const response = res as ResourceSelectorResponse; | |||
| const showValue = `${response.name}:${response.version}`; | |||
| form.setFieldValue(name, { | |||
| ...pick(response, ['id', 'version', 'path']), | |||
| showValue, | |||
| }); | |||
| setSelectedModel(response); | |||
| } | |||
| } else { | |||
| if (type === ResourceSelectorType.Model) { | |||
| setSelectedModel(undefined); | |||
| } | |||
| form.setFieldValue(name, ''); | |||
| } | |||
| close(); | |||
| }, | |||
| }); | |||
| }; | |||
| // 创建 | |||
| const createModelDeployment = async (formData: FormData) => { | |||
| const envList = formData['env'] ?? []; | |||
| const env = envList.reduce((acc, cur) => { | |||
| acc[cur.key] = cur.value; | |||
| return acc; | |||
| }, {} as Record<string, string>); | |||
| const object = camelCaseToUnderscore({ | |||
| ...omit(formData, ['replicas', 'env']), | |||
| replicas: Number(formData.replicas), | |||
| env, | |||
| }); | |||
| const params = | |||
| operationType === ModelDeploymentOperationType.Create | |||
| ? object | |||
| : { | |||
| ...pick(modelDeploymentInfo, ['service_id', 'service_ins_id']), | |||
| update_model: { | |||
| ...pick(object, ['description', 'env', 'replicas', 'resource', 'image']), | |||
| }, | |||
| }; | |||
| let request = createModelDeploymentReq; | |||
| if (operationType === ModelDeploymentOperationType.Restart) { | |||
| request = restartModelDeploymentReq; | |||
| } else if (operationType === ModelDeploymentOperationType.Update) { | |||
| request = updateModelDeploymentReq; | |||
| } | |||
| const [res] = await to(request(params)); | |||
| if (res) { | |||
| message.success('操作成功'); | |||
| navgite(-1); | |||
| } | |||
| }; | |||
| // 提交 | |||
| const handleSubmit = (values: FormData) => { | |||
| createModelDeployment(values); | |||
| }; | |||
| // 取消 | |||
| const cancel = () => { | |||
| navgite(-1); | |||
| }; | |||
| const disabled = operationType !== ModelDeploymentOperationType.Create; | |||
| let buttonText = '新建'; | |||
| if (operationType === ModelDeploymentOperationType.Update) { | |||
| buttonText = '更新'; | |||
| } else if (operationType === ModelDeploymentOperationType.Restart) { | |||
| buttonText = '重启'; | |||
| } | |||
| return ( | |||
| <div className={styles['model-deployment-create']}> | |||
| <PageTitle title="创建推理服务"></PageTitle> | |||
| <div className={styles['model-deployment-create__content']}> | |||
| <div> | |||
| <Form | |||
| name="model-deployment-create" | |||
| labelCol={{ flex: '100px' }} | |||
| labelAlign="left" | |||
| form={form} | |||
| initialValues={{ upload_type: CommonTabKeys.Public }} | |||
| onFinish={handleSubmit} | |||
| size="large" | |||
| > | |||
| <SubAreaTitle | |||
| title="基本信息" | |||
| image={require('@/assets/img/mirror-basic.png')} | |||
| style={{ marginBottom: '26px' }} | |||
| ></SubAreaTitle> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="服务名称" | |||
| name="serviceName" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入服务名称', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input | |||
| placeholder="请输入服务名称" | |||
| disabled={disabled} | |||
| maxLength={30} | |||
| showCount | |||
| allowClear | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={20}> | |||
| <Form.Item | |||
| label="描 述" | |||
| name="description" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入描述', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input.TextArea | |||
| autoSize={{ minRows: 2, maxRows: 6 }} | |||
| placeholder="请输入描述,最长128字符" | |||
| maxLength={128} | |||
| showCount | |||
| allowClear | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <SubAreaTitle | |||
| title="部署构建" | |||
| image={require('@/assets/img/model-deployment.png')} | |||
| style={{ marginTop: '20px', marginBottom: '24px' }} | |||
| ></SubAreaTitle> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="选择模型" | |||
| name="model" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请选择模型', | |||
| }, | |||
| ]} | |||
| > | |||
| <ParameterInput | |||
| placeholder="请选择模型" | |||
| disabled={disabled} | |||
| canInput={false} | |||
| size="large" | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| <Col span={10}> | |||
| <Button | |||
| disabled={disabled} | |||
| size="large" | |||
| type="link" | |||
| icon={getSelectBtnIcon(ResourceSelectorType.Model)} | |||
| onClick={() => selectResource('model', 'model')} | |||
| > | |||
| 选择模型 | |||
| </Button> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="选择镜像" | |||
| name="image" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入镜像', | |||
| }, | |||
| ]} | |||
| > | |||
| <ParameterInput placeholder="请选择镜像" canInput={false} size="large" /> | |||
| </Form.Item> | |||
| </Col> | |||
| <Col span={10}> | |||
| <Button | |||
| size="large" | |||
| type="link" | |||
| icon={getSelectBtnIcon(ResourceSelectorType.Mirror)} | |||
| onClick={() => selectResource('image', 'image')} | |||
| > | |||
| 选择镜像 | |||
| </Button> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="资源规格" | |||
| name="resource" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请选择资源规格', | |||
| }, | |||
| ]} | |||
| > | |||
| <Select | |||
| showSearch | |||
| placeholder="请选择资源规格" | |||
| filterOption={filterResourceStandard} | |||
| options={resourceStandardList} | |||
| fieldNames={{ | |||
| label: 'description', | |||
| value: 'standard', | |||
| }} | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="副本数量" | |||
| name="replicas" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入副本数量', | |||
| }, | |||
| { | |||
| pattern: /^-?\d+(\.\d+)?$/, | |||
| message: '副本数量必须是数字', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input placeholder="请输入副本数量" allowClear /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="挂载路径" | |||
| name="modelPath" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入模型挂载路径', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input | |||
| placeholder="请输入模型挂载路径" | |||
| disabled={disabled} | |||
| maxLength={64} | |||
| showCount | |||
| allowClear | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Form.List name="env"> | |||
| {(fields, { add, remove }) => ( | |||
| <> | |||
| <Row gutter={8}> | |||
| <Col span={10}> | |||
| <Form.Item label="环境变量"> | |||
| <Button type="link" style={{ padding: '0' }} onClick={() => add()}> | |||
| 添加环境变量 | |||
| </Button> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| {fields.map(({ key, name, ...restField }) => ( | |||
| <Flex key={key} align="center" gap="0 8px" style={{ width: '50%' }}> | |||
| <Form.Item | |||
| {...restField} | |||
| name={[name, 'key']} | |||
| style={{ flex: 1 }} | |||
| rules={[{ required: true, message: '请输入变量名' }]} | |||
| > | |||
| <Input placeholder="请输入变量名" /> | |||
| </Form.Item> | |||
| <span style={{ marginBottom: '24px' }}>=</span> | |||
| <Form.Item | |||
| {...restField} | |||
| name={[name, 'value']} | |||
| style={{ flex: 1 }} | |||
| rules={[{ required: true, message: '请输入变量值' }]} | |||
| > | |||
| <Input placeholder="请输入变量值" /> | |||
| </Form.Item> | |||
| <Button | |||
| type="link" | |||
| style={{ marginBottom: '24px' }} | |||
| icon={<KFIcon type="icon-shanchu" font={16} />} | |||
| onClick={() => { | |||
| modalConfirm({ | |||
| content: '是否确认删除?', | |||
| onOk: () => { | |||
| remove(name); | |||
| }, | |||
| }); | |||
| }} | |||
| ></Button> | |||
| </Flex> | |||
| ))} | |||
| </> | |||
| )} | |||
| </Form.List> | |||
| <Form.Item wrapperCol={{ offset: 0, span: 16 }}> | |||
| <Button type="primary" htmlType="submit"> | |||
| {buttonText} | |||
| </Button> | |||
| <Button | |||
| type="default" | |||
| htmlType="button" | |||
| onClick={cancel} | |||
| style={{ marginLeft: '20px' }} | |||
| > | |||
| 取消 | |||
| </Button> | |||
| </Form.Item> | |||
| </Form> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| export default ModelDeploymentCreate; | |||
| @@ -9,6 +9,7 @@ | |||
| line-height: 1.6; | |||
| .label { | |||
| flex: none; | |||
| width: 80px; | |||
| color: @text-color-secondary; | |||
| } | |||
| @@ -16,6 +17,8 @@ | |||
| .value { | |||
| flex: 1; | |||
| color: @text-color; | |||
| white-space: pre-line; | |||
| word-break: break-all; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,194 @@ | |||
| /* | |||
| * @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 { useComputingResource } from '@/hooks/resource'; | |||
| import { useSessionStorage } from '@/hooks/sessionStorage'; | |||
| import { formatDate } from '@/utils/date'; | |||
| import { modelDeploymentInfoKey } from '@/utils/sessionStorage'; | |||
| import { Col, Row, Tabs, type TabsProps } from 'antd'; | |||
| import { useEffect, useState } from 'react'; | |||
| import ModelDeploymentStatusCell from '../components/ModelDeployStatusCell'; | |||
| import { ModelDeploymentData } from '../types'; | |||
| import styles from './index.less'; | |||
| const tabItems = [ | |||
| { | |||
| key: '1', | |||
| label: '预测', | |||
| icon: <KFIcon type="icon-yuce" />, | |||
| }, | |||
| { | |||
| key: '2', | |||
| label: '调用指南', | |||
| icon: <KFIcon type="icon-tiaoyongzhinan" />, | |||
| }, | |||
| { | |||
| key: '3', | |||
| label: '服务日志', | |||
| icon: <KFIcon type="icon-fuwurizhi" />, | |||
| }, | |||
| ]; | |||
| function ModelDeploymentInfo() { | |||
| const [activeTab, setActiveTab] = useState<string>('1'); | |||
| const [modelDeployementInfo] = useSessionStorage<ModelDeploymentData | undefined>( | |||
| modelDeploymentInfoKey, | |||
| true, | |||
| undefined, | |||
| ); | |||
| const getResourceDescription = useComputingResource()[2]; | |||
| useEffect(() => {}, []); | |||
| // 切换 Tab,重置数据 | |||
| const hanleTabChange: TabsProps['onChange'] = (value) => { | |||
| setActiveTab(value); | |||
| }; | |||
| const formatEnvText = () => { | |||
| if (!modelDeployementInfo?.env) { | |||
| return '--'; | |||
| } | |||
| const env = modelDeployementInfo.env; | |||
| return Object.entries(env) | |||
| .map(([key, value]) => `${key}: ${value}`) | |||
| .join('\n'); | |||
| }; | |||
| return ( | |||
| <div className={styles['model-deployment-info']}> | |||
| <PageTitle title="服务详情"></PageTitle> | |||
| <div className={styles['model-deployment-info__content']}> | |||
| <div> | |||
| <SubAreaTitle | |||
| title="基本信息" | |||
| image={require('@/assets/img/mirror-basic.png')} | |||
| style={{ marginBottom: '26px' }} | |||
| ></SubAreaTitle> | |||
| <div className={styles['model-deployment-info__basic']}> | |||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||
| <Col span={10}> | |||
| <div className={styles['model-deployment-info__basic__item']}> | |||
| <div className={styles['label']}>服务名称:</div> | |||
| <div className={styles['value']}> | |||
| {modelDeployementInfo?.service_name ?? '--'} | |||
| </div> | |||
| </div> | |||
| </Col> | |||
| <Col span={10}> | |||
| <div className={styles['model-deployment-info__basic__item']}> | |||
| <div className={styles['label']}>镜 像:</div> | |||
| <div className={styles['value']}>{modelDeployementInfo?.image ?? '--'}</div> | |||
| </div> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||
| <Col span={10}> | |||
| <div className={styles['model-deployment-info__basic__item']}> | |||
| <div className={styles['label']}>状 态:</div> | |||
| <div className={styles['value']}> | |||
| {ModelDeploymentStatusCell(modelDeployementInfo?.status)} | |||
| </div> | |||
| </div> | |||
| </Col> | |||
| <Col span={10}> | |||
| <div className={styles['model-deployment-info__basic__item']}> | |||
| <div className={styles['label']}>模 型:</div> | |||
| <div className={styles['value']}> | |||
| {modelDeployementInfo?.model?.show_value ?? '--'} | |||
| </div> | |||
| </div> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||
| <Col span={10}> | |||
| <div className={styles['model-deployment-info__basic__item']}> | |||
| <div className={styles['label']}>创建人:</div> | |||
| <div className={styles['value']}>{modelDeployementInfo?.created_by ?? '--'}</div> | |||
| </div> | |||
| </Col> | |||
| <Col span={10}> | |||
| <div className={styles['model-deployment-info__basic__item']}> | |||
| <div className={styles['label']}>挂载路径:</div> | |||
| <div className={styles['value']}>{modelDeployementInfo?.model_path ?? '--'}</div> | |||
| </div> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||
| <Col span={10}> | |||
| <div className={styles['model-deployment-info__basic__item']}> | |||
| <div className={styles['label']}>API URL:</div> | |||
| <div className={styles['value']}>{modelDeployementInfo?.url ?? '--'}</div> | |||
| </div> | |||
| </Col> | |||
| <Col span={10}> | |||
| <div className={styles['model-deployment-info__basic__item']}> | |||
| <div className={styles['label']}>副本数量:</div> | |||
| <div className={styles['value']}>{modelDeployementInfo?.replicas ?? '--'}</div> | |||
| </div> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||
| <Col span={10}> | |||
| <div className={styles['model-deployment-info__basic__item']}> | |||
| <div className={styles['label']}>创建时间:</div> | |||
| <div className={styles['value']}> | |||
| {modelDeployementInfo?.create_time | |||
| ? formatDate(modelDeployementInfo.create_time) | |||
| : '--'} | |||
| </div> | |||
| </div> | |||
| </Col> | |||
| <Col span={10}> | |||
| <div className={styles['model-deployment-info__basic__item']}> | |||
| <div className={styles['label']}>更新时间:</div> | |||
| <div className={styles['value']}> | |||
| {modelDeployementInfo?.update_time | |||
| ? formatDate(modelDeployementInfo.update_time) | |||
| : '--'} | |||
| </div> | |||
| </div> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||
| <Col span={10}> | |||
| <div className={styles['model-deployment-info__basic__item']}> | |||
| <div className={styles['label']}>环境变量:</div> | |||
| <div className={styles['value']}>{formatEnvText()}</div> | |||
| </div> | |||
| </Col> | |||
| <Col span={10}> | |||
| <div className={styles['model-deployment-info__basic__item']}> | |||
| <div className={styles['label']}>资源规格</div> | |||
| <div className={styles['value']}> | |||
| {modelDeployementInfo?.resource | |||
| ? getResourceDescription(modelDeployementInfo.resource) | |||
| : '--'} | |||
| </div> | |||
| </div> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={40}> | |||
| <Col span={24}> | |||
| <div className={styles['model-deployment-info__basic__item']}> | |||
| <div className={styles['label']}>描 述:</div> | |||
| <div className={styles['value']}>{modelDeployementInfo?.description ?? '--'}</div> | |||
| </div> | |||
| </Col> | |||
| </Row> | |||
| </div> | |||
| <div style={{ marginTop: '20px' }}> | |||
| <Tabs activeKey={activeTab} items={tabItems} onChange={hanleTabChange} /> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| export default ModelDeploymentInfo; | |||
| @@ -0,0 +1,348 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-16 13:58:08 | |||
| * @Description: 模型部署列表 | |||
| */ | |||
| import CommonTableCell from '@/components/CommonTableCell'; | |||
| import DateTableCell from '@/components/DateTableCell'; | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import PageTitle from '@/components/PageTitle'; | |||
| import { ModelDeploymentStatus, modelDeploymentStatusOptions } from '@/enums'; | |||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||
| import { | |||
| deleteModelDeploymentReq, | |||
| getModelDeploymentListReq, | |||
| stopModelDeploymentReq, | |||
| } from '@/services/modelDeployment'; | |||
| import themes from '@/styles/theme.less'; | |||
| import { to } from '@/utils/promise'; | |||
| import { modelDeploymentInfoKey, setSessionStorageItem } from '@/utils/sessionStorage'; | |||
| import { modalConfirm } from '@/utils/ui'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import { | |||
| App, | |||
| Button, | |||
| ConfigProvider, | |||
| Input, | |||
| Select, | |||
| Table, | |||
| type TablePaginationConfig, | |||
| type TableProps, | |||
| } from 'antd'; | |||
| import { type SearchProps } from 'antd/es/input'; | |||
| import classNames from 'classnames'; | |||
| import { pick } from 'lodash'; | |||
| import { useEffect, useState } from 'react'; | |||
| import ModelDeploymentStatusCell from '../components/ModelDeployStatusCell'; | |||
| import { ModelDeploymentData, ModelDeploymentOperationType } from '../types'; | |||
| import styles from './index.less'; | |||
| function ModelDeployment() { | |||
| const navigate = useNavigate(); | |||
| const { message } = App.useApp(); | |||
| const [cacheState, setCacheState] = useCacheState(); | |||
| const [searchStatus, setSearchStatus] = useState(cacheState?.searchStatus ?? ''); | |||
| const [searchText, setSearchText] = useState(cacheState?.searchText); | |||
| const [inputText, setInputText] = useState(cacheState?.searchText); | |||
| const [tableData, setTableData] = useState<ModelDeploymentData[]>([]); | |||
| const [total, setTotal] = useState(0); | |||
| const [pagination, setPagination] = useState<TablePaginationConfig>( | |||
| cacheState?.pagination ?? { | |||
| current: 1, | |||
| pageSize: 10, | |||
| }, | |||
| ); | |||
| useEffect(() => { | |||
| getModelDeploymentList(); | |||
| }, [pagination, searchText, searchStatus]); | |||
| // 获取模型部署列表 | |||
| const getModelDeploymentList = async () => { | |||
| const params: Record<string, any> = { | |||
| page: pagination.current!, | |||
| size: pagination.pageSize, | |||
| service_name: searchText, | |||
| status: searchStatus, | |||
| }; | |||
| const [res] = await to(getModelDeploymentListReq(params)); | |||
| if (res && res.data) { | |||
| const { service_list = [], total = 0 } = res.data; | |||
| setTableData(service_list); | |||
| setTotal(total); | |||
| } | |||
| }; | |||
| // 删除模型部署 | |||
| const deleteModelDeploy = async (record: ModelDeploymentData) => { | |||
| const params = pick(record, ['service_id', 'service_ins_id']); | |||
| const [res] = await to(deleteModelDeploymentReq(params)); | |||
| if (res) { | |||
| message.success('删除成功'); | |||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||
| // 否则直接刷新这一页的数据 | |||
| // 避免回到第一页 | |||
| if (tableData.length > 1) { | |||
| setPagination((prev) => ({ | |||
| ...prev, | |||
| current: 1, | |||
| })); | |||
| } else { | |||
| getModelDeploymentList(); | |||
| } | |||
| } | |||
| }; | |||
| // 停止模型部署 | |||
| 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) => { | |||
| setSearchText(value); | |||
| }; | |||
| // 处理删除 | |||
| const handleModelDeployDelete = (record: ModelDeploymentData) => { | |||
| modalConfirm({ | |||
| title: '删除后,该模型部署将不可恢复', | |||
| content: '是否确认删除?', | |||
| onOk: () => { | |||
| deleteModelDeploy(record); | |||
| }, | |||
| }); | |||
| }; | |||
| // 处理停止 | |||
| const handleModelDeployStop = async (record: ModelDeploymentData) => { | |||
| modalConfirm({ | |||
| content: '是否确认停止?', | |||
| onOk: () => { | |||
| stopModelDeploy(record); | |||
| }, | |||
| }); | |||
| }; | |||
| // 创建、更新、重启模型部署 | |||
| const createModelDeployment = ( | |||
| type: ModelDeploymentOperationType, | |||
| record?: ModelDeploymentData, | |||
| ) => { | |||
| setSessionStorageItem( | |||
| modelDeploymentInfoKey, | |||
| { | |||
| ...record, | |||
| operationType: type, | |||
| }, | |||
| true, | |||
| ); | |||
| setCacheState({ | |||
| pagination, | |||
| searchText, | |||
| searchStatus, | |||
| }); | |||
| navigate(`/modelDeployment/create`); | |||
| }; | |||
| // 查看详情 | |||
| const toDetail = (record: ModelDeploymentData) => { | |||
| setSessionStorageItem(modelDeploymentInfoKey, record, true); | |||
| setCacheState({ | |||
| pagination, | |||
| searchText, | |||
| searchStatus, | |||
| }); | |||
| navigate(`/modelDeployment/${record.service_id}`); | |||
| }; | |||
| // 分页切换 | |||
| const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => { | |||
| if (action === 'paginate') { | |||
| setPagination(pagination); | |||
| } | |||
| // console.log(pagination, filters, sorter, action); | |||
| }; | |||
| const columns: TableProps<ModelDeploymentData>['columns'] = [ | |||
| { | |||
| title: '序号', | |||
| dataIndex: 'index', | |||
| key: 'index', | |||
| width: '20%', | |||
| render(text, record, index) { | |||
| return <span>{(pagination.current! - 1) * pagination.pageSize! + index + 1}</span>; | |||
| }, | |||
| }, | |||
| { | |||
| title: '服务名称', | |||
| dataIndex: 'service_name', | |||
| key: 'service_name', | |||
| width: '20%', | |||
| render: (text, record) => { | |||
| return <a onClick={() => toDetail(record)}>{text}</a>; | |||
| }, | |||
| }, | |||
| { | |||
| title: '模型', | |||
| dataIndex: ['model', 'show_value'], | |||
| key: 'model', | |||
| width: '20%', | |||
| render: CommonTableCell(), | |||
| }, | |||
| { | |||
| title: '状态', | |||
| dataIndex: 'status', | |||
| key: 'status', | |||
| width: '20%', | |||
| render: ModelDeploymentStatusCell, | |||
| }, | |||
| { | |||
| title: '创建人', | |||
| dataIndex: 'created_by', | |||
| key: 'created_by', | |||
| render: CommonTableCell(), | |||
| width: '20%', | |||
| }, | |||
| { | |||
| title: '更新时间', | |||
| dataIndex: 'update_time', | |||
| key: 'update_time', | |||
| width: '20%', | |||
| render: DateTableCell, | |||
| }, | |||
| { | |||
| title: '操作', | |||
| dataIndex: 'operation', | |||
| width: 350, | |||
| key: 'operation', | |||
| render: (_: any, record: ModelDeploymentData) => ( | |||
| <div> | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="edit" | |||
| icon={<KFIcon type="icon-bianji" />} | |||
| onClick={() => createModelDeployment(ModelDeploymentOperationType.Update, record)} | |||
| > | |||
| 更新 | |||
| </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) && ( | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="stop" | |||
| icon={<KFIcon type="icon-tingzhi" />} | |||
| onClick={() => handleModelDeployStop(record)} | |||
| > | |||
| 停止 | |||
| </Button> | |||
| )} | |||
| <ConfigProvider | |||
| theme={{ | |||
| token: { | |||
| colorLink: themes['warningColor'], | |||
| }, | |||
| }} | |||
| > | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="remove" | |||
| icon={<KFIcon type="icon-shanchu" />} | |||
| onClick={() => handleModelDeployDelete(record)} | |||
| > | |||
| 删除 | |||
| </Button> | |||
| </ConfigProvider> | |||
| </div> | |||
| ), | |||
| }, | |||
| ]; | |||
| return ( | |||
| <div className={styles['model-deployment']}> | |||
| <PageTitle title="模型列表"></PageTitle> | |||
| <div className={styles['model-deployment__content']}> | |||
| <div className={styles['model-deployment__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) => setSearchStatus(value)} | |||
| options={modelDeploymentStatusOptions} | |||
| value={searchStatus} | |||
| allowClear | |||
| ></Select> | |||
| <Button | |||
| style={{ marginLeft: '20px' }} | |||
| type="default" | |||
| onClick={() => createModelDeployment(ModelDeploymentOperationType.Create)} | |||
| icon={<KFIcon type="icon-xinjian2" />} | |||
| > | |||
| 创建推理服务 | |||
| </Button> | |||
| <Button | |||
| style={{ marginRight: 0, marginLeft: 'auto' }} | |||
| type="default" | |||
| onClick={getModelDeploymentList} | |||
| icon={<KFIcon type="icon-shuaxin" />} | |||
| > | |||
| 刷新 | |||
| </Button> | |||
| </div> | |||
| <div | |||
| className={classNames( | |||
| 'vertical-scroll-table', | |||
| styles['model-deployment__content__table'], | |||
| )} | |||
| > | |||
| <Table | |||
| dataSource={tableData} | |||
| columns={columns} | |||
| scroll={{ y: 'calc(100% - 55px)' }} | |||
| pagination={{ | |||
| ...pagination, | |||
| total: total, | |||
| showSizeChanger: true, | |||
| showQuickJumper: true, | |||
| }} | |||
| onChange={handleTableChange} | |||
| rowKey="service_id" | |||
| /> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| export default ModelDeployment; | |||
| @@ -1,11 +0,0 @@ | |||
| .mirror-status-cell { | |||
| color: @text-color; | |||
| &--success { | |||
| color: @success-color; | |||
| } | |||
| &--error { | |||
| color: @error-color; | |||
| } | |||
| } | |||
| @@ -1,39 +0,0 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-18 18:35:41 | |||
| * @Description: | |||
| */ | |||
| import { MirrorVersionStatus } from '@/enums'; | |||
| import styles from './index.less'; | |||
| type MirrorVersionStatusKeys = keyof typeof MirrorVersionStatus; | |||
| type MirrorVersionStatusValues = (typeof MirrorVersionStatus)[MirrorVersionStatusKeys]; | |||
| export type MirrorVersionStatusInfo = { | |||
| text: string; | |||
| classname: string; | |||
| }; | |||
| const statusInfo: Record<MirrorVersionStatusValues, MirrorVersionStatusInfo> = { | |||
| [MirrorVersionStatus.Building]: { | |||
| text: '构建中', | |||
| classname: styles['mirror-status-cell'], | |||
| }, | |||
| [MirrorVersionStatus.Available]: { | |||
| classname: styles['mirror-status-cell--success'], | |||
| text: '可用', | |||
| }, | |||
| [MirrorVersionStatus.Failed]: { | |||
| classname: styles['mirror-status-cell--error'], | |||
| text: '构建失败', | |||
| }, | |||
| }; | |||
| function MirrorStatusCell(status: MirrorVersionStatus) { | |||
| if (status === null || status === undefined || !statusInfo[status]) { | |||
| return <span>--</span>; | |||
| } | |||
| return <span className={statusInfo[status].classname}>{statusInfo[status].text}</span>; | |||
| } | |||
| export default MirrorStatusCell; | |||
| @@ -0,0 +1,15 @@ | |||
| .model-deployment-status-cell { | |||
| color: @text-color; | |||
| &--running { | |||
| color: @primary-color; | |||
| } | |||
| &--stopped { | |||
| color: @warning-color; | |||
| } | |||
| &--error { | |||
| color: @error-color; | |||
| } | |||
| } | |||
| @@ -0,0 +1,40 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-18 18:35:41 | |||
| * @Description: 模型部署状态 | |||
| */ | |||
| import { ModelDeploymentStatus } from '@/enums'; | |||
| import styles from './index.less'; | |||
| export type ModelDeploymentStatusInfo = { | |||
| text: string; | |||
| classname: string; | |||
| }; | |||
| export const statusInfo: Record<ModelDeploymentStatus, ModelDeploymentStatusInfo> = { | |||
| [ModelDeploymentStatus.Init]: { | |||
| text: '启动中', | |||
| classname: styles['model-deployment-status-cell'], | |||
| }, | |||
| [ModelDeploymentStatus.Running]: { | |||
| classname: styles['model-deployment-status-cell--running'], | |||
| text: '运行中', | |||
| }, | |||
| [ModelDeploymentStatus.Stopped]: { | |||
| classname: styles['model-deployment-status-cell--stopped'], | |||
| text: '已停止', | |||
| }, | |||
| [ModelDeploymentStatus.Failed]: { | |||
| classname: styles['model-deployment-status-cell--error'], | |||
| text: '失败', | |||
| }, | |||
| }; | |||
| function ModelDeploymentStatusCell(status: ModelDeploymentStatus | undefined) { | |||
| if (status === null || status === undefined || !statusInfo[status]) { | |||
| return <span>--</span>; | |||
| } | |||
| return <span className={statusInfo[status].classname}>{statusInfo[status].text}</span>; | |||
| } | |||
| export default ModelDeploymentStatusCell; | |||
| @@ -1,297 +0,0 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-16 13:58:08 | |||
| * @Description: 创建模型部署 | |||
| */ | |||
| import PageTitle from '@/components/PageTitle'; | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { CommonTabKeys } from '@/enums'; | |||
| import { createMirrorReq } from '@/services/mirror'; | |||
| import { getComputingResourceReq } from '@/services/pipeline'; | |||
| import { to } from '@/utils/promise'; | |||
| import { getSessionItemThenRemove, mirrorNameKey } from '@/utils/sessionStorage'; | |||
| import { validateUploadFiles } from '@/utils/ui'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import { Button, Col, Form, Input, Row, Select, UploadFile, message, type SelectProps } from 'antd'; | |||
| import { omit } from 'lodash'; | |||
| import { useEffect, useState } from 'react'; | |||
| import styles from './create.less'; | |||
| type FormData = { | |||
| name: string; | |||
| tag: string; | |||
| description: string; | |||
| path?: string; | |||
| upload_type: string; | |||
| fileList?: UploadFile[]; | |||
| }; | |||
| function ModelDeploymentCreate() { | |||
| const navgite = useNavigate(); | |||
| const [form] = Form.useForm(); | |||
| const [nameDisabled, setNameDisabled] = useState(false); | |||
| const [resourceStandardList, setResourceStandardList] = useState([]); | |||
| useEffect(() => { | |||
| const name = getSessionItemThenRemove(mirrorNameKey); | |||
| if (name) { | |||
| form.setFieldValue('name', name); | |||
| setNameDisabled(true); | |||
| } | |||
| getComputingResource(); | |||
| }, []); | |||
| const getComputingResource = async () => { | |||
| const params = { | |||
| page: 0, | |||
| size: 1000, | |||
| resource_type: '', | |||
| }; | |||
| const [res] = await to(getComputingResourceReq(params)); | |||
| if (res && res.data && res.data.content) { | |||
| setResourceStandardList(res.data.content); | |||
| } | |||
| }; | |||
| const filterResourceStandard: SelectProps['filterOption'] = ( | |||
| input: string, | |||
| { computing_resource = '' }, | |||
| ) => { | |||
| return computing_resource.toLocaleLowerCase().includes(input.toLocaleLowerCase()); | |||
| }; | |||
| // 创建公网、本地镜像 | |||
| const createPublicMirror = async (formData: FormData) => { | |||
| const upload_type = formData['upload_type']; | |||
| let params; | |||
| if (upload_type === CommonTabKeys.Public) { | |||
| params = { | |||
| ...omit(formData, ['upload_type']), | |||
| upload_type: 0, | |||
| image_type: 0, | |||
| }; | |||
| } else { | |||
| const fileList = formData['fileList'] ?? []; | |||
| if (validateUploadFiles(fileList)) { | |||
| const file = fileList[0]; | |||
| params = { | |||
| ...omit(formData, ['fileList', 'upload_type']), | |||
| path: file.response.data.url, | |||
| file_size: file.response.data.fileSize, | |||
| upload_type: 1, | |||
| image_type: 0, | |||
| }; | |||
| } | |||
| } | |||
| const [res] = await to(createMirrorReq(params)); | |||
| if (res) { | |||
| message.success('创建成功'); | |||
| navgite(-1); | |||
| } | |||
| }; | |||
| // 提交 | |||
| const handleSubmit = (values: FormData) => { | |||
| createPublicMirror(values); | |||
| }; | |||
| // 取消 | |||
| const cancel = () => { | |||
| navgite(-1); | |||
| }; | |||
| return ( | |||
| <div className={styles['model-deployment-create']}> | |||
| <PageTitle title="创建推理服务"></PageTitle> | |||
| <div className={styles['model-deployment-create__content']}> | |||
| <div> | |||
| <Form | |||
| name="model-deployment-create" | |||
| labelCol={{ flex: '130px' }} | |||
| wrapperCol={{ flex: 1 }} | |||
| labelAlign="left" | |||
| form={form} | |||
| initialValues={{ upload_type: CommonTabKeys.Public }} | |||
| onFinish={handleSubmit} | |||
| size="large" | |||
| > | |||
| <SubAreaTitle | |||
| title="基本信息" | |||
| image={require('@/assets/img/mirror-basic.png')} | |||
| style={{ marginBottom: '26px' }} | |||
| ></SubAreaTitle> | |||
| <Row gutter={10}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="服务名称" | |||
| name="name" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入服务名称', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input | |||
| placeholder="请输入服务名称" | |||
| maxLength={64} | |||
| disabled={nameDisabled} | |||
| showCount | |||
| allowClear | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={10}> | |||
| <Col span={20}> | |||
| <Form.Item | |||
| label="描 述" | |||
| name="description" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入描述', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input.TextArea | |||
| autoSize={{ minRows: 2, maxRows: 6 }} | |||
| placeholder="请输入描述,最长128字符" | |||
| maxLength={128} | |||
| showCount | |||
| allowClear | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <SubAreaTitle | |||
| title="部署构建" | |||
| image={require('@/assets/img/mirror-version.png')} | |||
| style={{ marginTop: '20px', marginBottom: '24px' }} | |||
| ></SubAreaTitle> | |||
| <Row gutter={10}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="选择模型" | |||
| name="name" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入模型', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input | |||
| placeholder="请输入模型" | |||
| maxLength={64} | |||
| disabled={nameDisabled} | |||
| showCount | |||
| allowClear | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={10}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="选择镜像" | |||
| name="name" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入镜像', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input | |||
| placeholder="请输入镜像" | |||
| maxLength={64} | |||
| disabled={nameDisabled} | |||
| showCount | |||
| allowClear | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={10}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="资源规格" | |||
| name="name" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请选择资源规格', | |||
| }, | |||
| ]} | |||
| > | |||
| <Select | |||
| showSearch | |||
| placeholder="请选择资源规格" | |||
| filterOption={filterResourceStandard} | |||
| options={resourceStandardList} | |||
| fieldNames={{ | |||
| label: 'description', | |||
| value: 'standard', | |||
| }} | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={10}> | |||
| <Col span={10}> | |||
| <Form.Item | |||
| label="副本数量" | |||
| name="name" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入副本数量', | |||
| }, | |||
| ]} | |||
| > | |||
| <Input | |||
| placeholder="请输入副本数量" | |||
| maxLength={64} | |||
| disabled={nameDisabled} | |||
| showCount | |||
| allowClear | |||
| /> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={10}> | |||
| <Col span={10}> | |||
| <Form.Item label="环境变量" name="name"> | |||
| <Button type="link" style={{ padding: '0' }}> | |||
| 添加环境变量 | |||
| </Button> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| <Form.Item wrapperCol={{ offset: 0, span: 16 }}> | |||
| <Button type="primary" htmlType="submit"> | |||
| 确定 | |||
| </Button> | |||
| <Button | |||
| type="default" | |||
| htmlType="button" | |||
| onClick={cancel} | |||
| style={{ marginLeft: '20px' }} | |||
| > | |||
| 取消 | |||
| </Button> | |||
| </Form.Item> | |||
| </Form> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| export default ModelDeploymentCreate; | |||
| @@ -1,148 +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 { getMirrorInfoReq } from '@/services/mirror'; | |||
| import { formatDate } from '@/utils/date'; | |||
| import { to } from '@/utils/promise'; | |||
| import { useNavigate, useParams } from '@umijs/max'; | |||
| import { Col, Row, Tabs, type TabsProps } from 'antd'; | |||
| import { useEffect, useState } from 'react'; | |||
| import styles from './info.less'; | |||
| type MirrorInfoData = { | |||
| name?: string; | |||
| description?: string; | |||
| version_count?: string; | |||
| create_time?: string; | |||
| }; | |||
| type MirrorVersionData = { | |||
| id: number; | |||
| version: string; | |||
| url: string; | |||
| status: string; | |||
| file_size: string; | |||
| create_time: string; | |||
| }; | |||
| const tabItems = [ | |||
| { | |||
| key: '1', | |||
| label: '预测', | |||
| icon: <KFIcon type="icon-yuce" />, | |||
| }, | |||
| { | |||
| key: '2', | |||
| label: '调用指南', | |||
| icon: <KFIcon type="icon-tiaoyongzhinan" />, | |||
| }, | |||
| { | |||
| key: '3', | |||
| label: '服务日志', | |||
| icon: <KFIcon type="icon-fuwurizhi" />, | |||
| }, | |||
| ]; | |||
| function ModelDeploymentInfo() { | |||
| const navigate = useNavigate(); | |||
| const urlParams = useParams(); | |||
| const [mirrorInfo, setMirrorInfo] = useState<MirrorInfoData>({}); | |||
| const [activeTab, setActiveTab] = useState<string>('1'); | |||
| useEffect(() => { | |||
| getMirrorInfo(); | |||
| }, []); | |||
| // 获取镜像详情 | |||
| const getMirrorInfo = async () => { | |||
| const id = Number(urlParams.id); | |||
| const [res] = await to(getMirrorInfoReq(id)); | |||
| if (res && res.data) { | |||
| const { name = '', description = '', version_count = '', create_time: time } = res.data; | |||
| const create_time = formatDate(time); | |||
| setMirrorInfo({ | |||
| name, | |||
| description, | |||
| version_count, | |||
| create_time, | |||
| }); | |||
| } | |||
| }; | |||
| // 切换 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']}> | |||
| <div> | |||
| <SubAreaTitle | |||
| title="基本信息" | |||
| image={require('@/assets/img/mirror-basic.png')} | |||
| style={{ marginBottom: '26px' }} | |||
| ></SubAreaTitle> | |||
| <div className={styles['model-deployment-info__basic']}> | |||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||
| <Col span={10}> | |||
| <div className={styles['model-deployment-info__basic__item']}> | |||
| <div className={styles['label']}>服务名称:</div> | |||
| <div className={styles['value']}>{mirrorInfo.name}</div> | |||
| </div> | |||
| </Col> | |||
| <Col span={10}> | |||
| <div className={styles['model-deployment-info__basic__item']}> | |||
| <div className={styles['label']}>镜像:</div> | |||
| <div className={styles['value']}>{mirrorInfo.version_count ?? '--'}</div> | |||
| </div> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||
| <Col span={10}> | |||
| <div className={styles['model-deployment-info__basic__item']}> | |||
| <div className={styles['label']}>状态:</div> | |||
| <div className={styles['value']}>{mirrorInfo.name}</div> | |||
| </div> | |||
| </Col> | |||
| <Col span={10}> | |||
| <div className={styles['model-deployment-info__basic__item']}> | |||
| <div className={styles['label']}>模型:</div> | |||
| <div className={styles['value']}>{mirrorInfo.version_count ?? '--'}</div> | |||
| </div> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={40} style={{ marginBottom: '20px' }}> | |||
| <Col span={10}> | |||
| <div className={styles['model-deployment-info__basic__item']}> | |||
| <div className={styles['label']}>环境变量:</div> | |||
| <div className={styles['value']}>{mirrorInfo.name}</div> | |||
| </div> | |||
| </Col> | |||
| </Row> | |||
| <Row gutter={40}> | |||
| <Col span={24}> | |||
| <div className={styles['model-deployment-info__basic__item']}> | |||
| <div className={styles['label']}>描述:</div> | |||
| <div className={styles['value']}>{mirrorInfo.description}</div> | |||
| </div> | |||
| </Col> | |||
| </Row> | |||
| </div> | |||
| <div> | |||
| <Tabs activeKey={activeTab} items={tabItems} onChange={hanleTabChange} /> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| export default ModelDeploymentInfo; | |||
| @@ -1,283 +0,0 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-16 13:58:08 | |||
| * @Description: 模型部署列表 | |||
| */ | |||
| import CommonTableCell from '@/components/CommonTableCell'; | |||
| import DateTableCell from '@/components/DateTableCell'; | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import PageTitle from '@/components/PageTitle'; | |||
| import { useCacheState } from '@/hooks/pageCacheState'; | |||
| import { deleteMirrorReq, getMirrorListReq } from '@/services/mirror'; | |||
| import themes from '@/styles/theme.less'; | |||
| import { to } from '@/utils/promise'; | |||
| import { modalConfirm } from '@/utils/ui'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import { | |||
| App, | |||
| Button, | |||
| ConfigProvider, | |||
| Input, | |||
| Table, | |||
| type TablePaginationConfig, | |||
| type TableProps, | |||
| } from 'antd'; | |||
| import { type SearchProps } from 'antd/es/input'; | |||
| import classNames from 'classnames'; | |||
| import { useEffect, useState } from 'react'; | |||
| import styles from './list.less'; | |||
| export type MirrorData = { | |||
| id: number; | |||
| name: string; | |||
| description: string; | |||
| create_time: string; | |||
| }; | |||
| function ModelDeployment() { | |||
| const navigate = useNavigate(); | |||
| const { message } = App.useApp(); | |||
| const [cacheState, setCacheState] = useCacheState(); | |||
| const [searchText, setSearchText] = useState(cacheState?.searchText); | |||
| const [inputText, setInputText] = useState(cacheState?.searchText); | |||
| const [tableData, setTableData] = useState<MirrorData[]>([]); | |||
| const [total, setTotal] = useState(0); | |||
| const [pagination, setPagination] = useState<Required<TablePaginationConfig>>( | |||
| cacheState?.pagination ?? { | |||
| current: 1, | |||
| pageSize: 10, | |||
| }, | |||
| ); | |||
| useEffect(() => { | |||
| getMirrorList(); | |||
| }, [pagination, searchText]); | |||
| // 获取镜像列表 | |||
| const getMirrorList = async () => { | |||
| const params: Record<string, any> = { | |||
| page: pagination.current - 1, | |||
| size: pagination.pageSize, | |||
| name: searchText, | |||
| image_type: 1, | |||
| }; | |||
| const [res] = await to(getMirrorListReq(params)); | |||
| if (res && res.data) { | |||
| const { content = [], totalElements = 0 } = res.data; | |||
| setTableData(content); | |||
| setTotal(totalElements); | |||
| } | |||
| }; | |||
| // 删除镜像 | |||
| const deleteMirror = async (id: number) => { | |||
| const [res] = await to(deleteMirrorReq(id)); | |||
| if (res) { | |||
| message.success('删除成功'); | |||
| // 如果是一页的唯一数据,删除时,请求第一页的数据 | |||
| // 否则直接刷新这一页的数据 | |||
| // 避免回到第一页 | |||
| if (tableData.length > 1) { | |||
| setPagination((prev) => ({ | |||
| ...prev, | |||
| current: 1, | |||
| })); | |||
| } else { | |||
| getMirrorList(); | |||
| } | |||
| } | |||
| }; | |||
| // 搜索 | |||
| const onSearch: SearchProps['onSearch'] = (value) => { | |||
| setSearchText(value); | |||
| }; | |||
| // 查看详情 | |||
| const toDetail = (record: MirrorData) => { | |||
| navigate(`/modelDeployment/${record.id}`); | |||
| setCacheState({ | |||
| pagination, | |||
| searchText, | |||
| }); | |||
| }; | |||
| // 处理删除 | |||
| const handleMirrorDelete = (record: MirrorData) => { | |||
| modalConfirm({ | |||
| title: '删除后,该镜像将不可恢复', | |||
| content: '是否确认删除?', | |||
| onOk: () => { | |||
| deleteMirror(record.id); | |||
| }, | |||
| }); | |||
| }; | |||
| // 创建镜像 | |||
| const createMirror = () => { | |||
| navigate(`/modelDeployment/create`); | |||
| setCacheState({ | |||
| pagination, | |||
| searchText, | |||
| }); | |||
| }; | |||
| // 分页切换 | |||
| const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => { | |||
| if (action === 'paginate') { | |||
| setPagination(pagination); | |||
| } | |||
| // console.log(pagination, filters, sorter, action); | |||
| }; | |||
| const columns: TableProps<MirrorData>['columns'] = [ | |||
| { | |||
| title: '序号', | |||
| dataIndex: 'index', | |||
| key: 'index', | |||
| width: 100, | |||
| align: 'center', | |||
| render(text, record, index) { | |||
| return <span>{(pagination.current - 1) * pagination.pageSize + index + 1}</span>; | |||
| }, | |||
| }, | |||
| { | |||
| title: '服务名称', | |||
| dataIndex: 'name', | |||
| key: 'name', | |||
| width: '30%', | |||
| render: CommonTableCell(), | |||
| }, | |||
| { | |||
| title: '模型', | |||
| dataIndex: 'version_count', | |||
| key: 'version_count', | |||
| width: '20%', | |||
| render: CommonTableCell(), | |||
| }, | |||
| { | |||
| title: '状态', | |||
| dataIndex: 'version_count', | |||
| key: 'version_count', | |||
| width: '10%', | |||
| render: CommonTableCell(), | |||
| }, | |||
| { | |||
| title: '创建人', | |||
| dataIndex: 'description', | |||
| key: 'description', | |||
| render: CommonTableCell(true), | |||
| width: '20%', | |||
| ellipsis: { showTitle: false }, | |||
| }, | |||
| { | |||
| title: '更新时间', | |||
| dataIndex: 'create_time', | |||
| key: 'create_time', | |||
| width: '20%', | |||
| render: DateTableCell, | |||
| }, | |||
| { | |||
| title: '操作', | |||
| dataIndex: 'operation', | |||
| width: 350, | |||
| key: 'operation', | |||
| render: (_: any, record: MirrorData) => ( | |||
| <div> | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="edit" | |||
| icon={<KFIcon type="icon-bianji" />} | |||
| onClick={() => toDetail(record)} | |||
| > | |||
| 编辑 | |||
| </Button> | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="run" | |||
| icon={<KFIcon type="icon-yunhang" />} | |||
| onClick={() => toDetail(record)} | |||
| > | |||
| 启动 | |||
| </Button> | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="stop" | |||
| icon={<KFIcon type="icon-tingzhi" />} | |||
| onClick={() => toDetail(record)} | |||
| > | |||
| 停止 | |||
| </Button> | |||
| <ConfigProvider | |||
| theme={{ | |||
| token: { | |||
| colorLink: themes['warningColor'], | |||
| }, | |||
| }} | |||
| > | |||
| <Button | |||
| type="link" | |||
| size="small" | |||
| key="remove" | |||
| icon={<KFIcon type="icon-shanchu" />} | |||
| onClick={() => handleMirrorDelete(record)} | |||
| > | |||
| 删除 | |||
| </Button> | |||
| </ConfigProvider> | |||
| </div> | |||
| ), | |||
| }, | |||
| ]; | |||
| return ( | |||
| <div className={styles['model-deployment']}> | |||
| <PageTitle title="模型列表"></PageTitle> | |||
| <div className={styles['model-deployment__content']}> | |||
| <div className={styles['model-deployment__filter']}> | |||
| <Input.Search | |||
| placeholder="按数据集名称筛选" | |||
| allowClear | |||
| onSearch={onSearch} | |||
| onChange={(e) => setInputText(e.target.value)} | |||
| style={{ width: 300 }} | |||
| value={inputText} | |||
| /> | |||
| <Button | |||
| style={{ marginLeft: '20px' }} | |||
| type="default" | |||
| onClick={createMirror} | |||
| icon={<KFIcon type="icon-xinjian2" />} | |||
| > | |||
| 创建推理服务 | |||
| </Button> | |||
| </div> | |||
| <div | |||
| className={classNames( | |||
| 'vertical-scroll-table', | |||
| styles['model-deployment__content__table'], | |||
| )} | |||
| > | |||
| <Table | |||
| dataSource={tableData} | |||
| columns={columns} | |||
| scroll={{ y: 'calc(100% - 55px)' }} | |||
| pagination={{ | |||
| ...pagination, | |||
| total: total, | |||
| showSizeChanger: true, | |||
| showQuickJumper: true, | |||
| }} | |||
| onChange={handleTableChange} | |||
| rowKey="id" | |||
| /> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| ); | |||
| } | |||
| export default ModelDeployment; | |||
| @@ -0,0 +1,38 @@ | |||
| import { ModelDeploymentStatus } 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; | |||
| created_by: string; | |||
| model_path: string; | |||
| url: string; | |||
| image: string; | |||
| replicas: number; | |||
| resource: string; | |||
| model: { | |||
| id: number; | |||
| version: string; | |||
| path: string; | |||
| show_value: string; | |||
| }; | |||
| env: Record<string, string>; | |||
| }; | |||
| // 操作类型 | |||
| export enum ModelDeploymentOperationType { | |||
| Create = 'create', | |||
| Update = 'update', | |||
| Restart = 'restart', | |||
| } | |||
| // 状态 | |||
| export type ModelDeploymentStatusInfo = { | |||
| text: string; | |||
| classname: string; | |||
| }; | |||
| @@ -6,7 +6,7 @@ | |||
| &__delete-button { | |||
| position: absolute; | |||
| top: 5px; | |||
| right: 0; | |||
| right: 24px; | |||
| } | |||
| :global { | |||
| @@ -1,8 +1,9 @@ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { getParamComponent, getParamRules } from '@/pages/Experiment/components/AddExperimentModal'; | |||
| import { type PipelineGlobalParam } from '@/types'; | |||
| import { to } from '@/utils/promise'; | |||
| import { modalConfirm } from '@/utils/ui'; | |||
| import { DeleteOutlined, PlusOutlined } from '@ant-design/icons'; | |||
| import { PlusOutlined } from '@ant-design/icons'; | |||
| import { Button, Drawer, Form, Input, Radio, Tooltip } from 'antd'; | |||
| import { NamePath } from 'antd/es/form/interface'; | |||
| import { forwardRef, useImperativeHandle } from 'react'; | |||
| @@ -143,7 +144,7 @@ const GlobalParamsDrawer = forwardRef( | |||
| className={styles['form-item__delete-button']} | |||
| type="link" | |||
| onClick={() => removeParameter(name, remove)} | |||
| icon={<DeleteOutlined />} | |||
| icon={<KFIcon type="icon-shanchu" />} | |||
| ></Button> | |||
| </Tooltip> | |||
| </div> | |||
| @@ -0,0 +1,124 @@ | |||
| import datasetImg from '@/assets/img/modal-select-dataset.png'; | |||
| import mirrorImg from '@/assets/img/modal-select-mirror.png'; | |||
| import modelImg from '@/assets/img/modal-select-model.png'; | |||
| import { CommonTabKeys, MirrorVersionStatus } from '@/enums'; | |||
| import { | |||
| getDatasetList, | |||
| getDatasetVersionIdList, | |||
| getDatasetVersionsById, | |||
| getModelList, | |||
| getModelVersionIdList, | |||
| getModelVersionsById, | |||
| } from '@/services/dataset/index.js'; | |||
| import { getMirrorListReq, getMirrorVersionListReq } from '@/services/mirror'; | |||
| import type { TabsProps } from 'antd'; | |||
| export enum ResourceSelectorType { | |||
| Model = 'Model', // 模型 | |||
| Dataset = 'Dataset', // 数据集 | |||
| Mirror = 'Mirror', //镜像 | |||
| } | |||
| export type MirrorVersion = { | |||
| id: number; // 镜像版本id | |||
| status: MirrorVersionStatus; // 镜像版本状态 | |||
| tag_name: string; // 镜像版本 | |||
| url: string; // 镜像版本路径 | |||
| }; | |||
| export type SelectorTypeInfo = { | |||
| getList: (params: any) => Promise<any>; | |||
| getVersions: (params: any) => Promise<any>; | |||
| getFiles: (params: any) => Promise<any>; | |||
| handleVersionResponse: (res: any) => any[]; | |||
| modalIcon: string; | |||
| buttonIcon: string; | |||
| name: string; | |||
| litReqParamKey: 'available_range' | 'image_type'; | |||
| fileReqParamKey: 'models_id' | 'dataset_id'; | |||
| tabItems: TabsProps['items']; | |||
| }; | |||
| // 获取镜像列表,为了兼容数据集和模型 | |||
| const getMirrorFilesReq = ({ id, version }: { id: number; version: string }): Promise<any> => { | |||
| const index = version.indexOf('-'); | |||
| const url = version.slice(index + 1); | |||
| return Promise.resolve({ | |||
| data: { | |||
| content: [ | |||
| { | |||
| id: `${id}-${version}`, | |||
| file_name: `${url}`, | |||
| }, | |||
| ], | |||
| }, | |||
| }); | |||
| }; | |||
| export const selectorTypeConfig: Record<ResourceSelectorType, SelectorTypeInfo> = { | |||
| [ResourceSelectorType.Model]: { | |||
| getList: getModelList, | |||
| getVersions: getModelVersionsById, | |||
| getFiles: getModelVersionIdList, | |||
| handleVersionResponse: (res) => res.data || [], | |||
| name: '模型', | |||
| modalIcon: modelImg, | |||
| buttonIcon: 'icon-xuanzemoxing', | |||
| litReqParamKey: 'available_range', | |||
| fileReqParamKey: 'models_id', | |||
| tabItems: [ | |||
| { | |||
| key: CommonTabKeys.Private, | |||
| label: '我的模型', | |||
| }, | |||
| { | |||
| key: CommonTabKeys.Public, | |||
| label: '公开模型', | |||
| }, | |||
| ], | |||
| }, | |||
| [ResourceSelectorType.Dataset]: { | |||
| getList: getDatasetList, | |||
| getVersions: getDatasetVersionsById, | |||
| getFiles: getDatasetVersionIdList, | |||
| handleVersionResponse: (res) => res.data || [], | |||
| name: '数据集', | |||
| modalIcon: datasetImg, | |||
| buttonIcon: 'icon-xuanzeshujuji', | |||
| litReqParamKey: 'available_range', | |||
| fileReqParamKey: 'dataset_id', | |||
| tabItems: [ | |||
| { | |||
| key: CommonTabKeys.Private, | |||
| label: '我的数据集', | |||
| }, | |||
| { | |||
| key: CommonTabKeys.Public, | |||
| label: '公开数据集', | |||
| }, | |||
| ], | |||
| }, | |||
| [ResourceSelectorType.Mirror]: { | |||
| getList: getMirrorListReq, | |||
| getVersions: (id: number) => getMirrorVersionListReq({ image_id: id, page: 0, size: 200 }), | |||
| getFiles: getMirrorFilesReq, | |||
| handleVersionResponse: (res) => | |||
| res.data?.content?.filter((v: MirrorVersion) => v.status === MirrorVersionStatus.Available) || | |||
| [], | |||
| name: '镜像', | |||
| modalIcon: mirrorImg, | |||
| buttonIcon: 'icon-xuanzejingxiang', | |||
| litReqParamKey: 'image_type', | |||
| fileReqParamKey: 'dataset_id', | |||
| tabItems: [ | |||
| { | |||
| key: CommonTabKeys.Private, | |||
| label: '我的镜像', | |||
| }, | |||
| { | |||
| key: CommonTabKeys.Public, | |||
| label: '公开镜像', | |||
| }, | |||
| ], | |||
| }, | |||
| }; | |||
| @@ -1,133 +1,22 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-11 16:31:18 | |||
| * @Description: 选择数据集和模型 | |||
| * @Description: 选择数据集、模型、镜像 | |||
| */ | |||
| import datasetImg from '@/assets/img/modal-select-dataset.png'; | |||
| import mirrorImg from '@/assets/img/modal-select-mirror.png'; | |||
| import modelImg from '@/assets/img/modal-select-model.png'; | |||
| import KFModal from '@/components/KFModal'; | |||
| import { CommonTabKeys, MirrorVersionStatus } from '@/enums'; | |||
| import { | |||
| getDatasetList, | |||
| getDatasetVersionIdList, | |||
| getDatasetVersionsById, | |||
| getModelList, | |||
| getModelVersionIdList, | |||
| getModelVersionsById, | |||
| } from '@/services/dataset/index.js'; | |||
| import { getMirrorListReq, getMirrorVersionListReq } from '@/services/mirror'; | |||
| import { CommonTabKeys } from '@/enums'; | |||
| import { to } from '@/utils/promise'; | |||
| import { Icon } from '@umijs/max'; | |||
| import type { GetRef, ModalProps, TabsProps, TreeDataNode, TreeProps } from 'antd'; | |||
| import type { GetRef, ModalProps, TreeDataNode, TreeProps } from 'antd'; | |||
| import { Input, Tabs, Tree } from 'antd'; | |||
| import React, { useEffect, useMemo, useRef, useState } from 'react'; | |||
| import { MirrorVersion, ResourceSelectorType, selectorTypeConfig } from './config'; | |||
| import styles from './index.less'; | |||
| export { ResourceSelectorType, selectorTypeConfig } from './config'; | |||
| export enum ResourceSelectorType { | |||
| Model = 'Model', // 模型 | |||
| Dataset = 'Dataset', // 数据集 | |||
| Mirror = 'Mirror', //镜像 | |||
| } | |||
| type ResourceSelectorTypeKeys = keyof typeof ResourceSelectorType; | |||
| type ResourceSelectorTypeValues = (typeof ResourceSelectorType)[ResourceSelectorTypeKeys]; | |||
| export type SelectorTypeInfo = { | |||
| getList: (params: any) => Promise<any>; | |||
| getVersions: (params: any) => Promise<any>; | |||
| getFiles: (params: any) => Promise<any>; | |||
| handleVersionResponse: (res: any) => any[]; | |||
| modalIcon: string; | |||
| name: string; | |||
| litReqParamKey: 'available_range' | 'image_type'; | |||
| fileReqParamKey: 'models_id' | 'dataset_id'; | |||
| tabItems: TabsProps['items']; | |||
| }; | |||
| // 获取镜像列表,为了兼容之前的结构 | |||
| const getMirrorFilesReq = ({ id, version }: { id: number; version: string }): Promise<any> => { | |||
| const index = version.indexOf('-'); | |||
| const url = version.slice(index + 1); | |||
| return Promise.resolve({ | |||
| data: { | |||
| content: [ | |||
| { | |||
| id: `${id}-${version}`, | |||
| file_name: `${url}`, | |||
| }, | |||
| ], | |||
| }, | |||
| }); | |||
| }; | |||
| export const selectorTypeData: Record<ResourceSelectorTypeValues, SelectorTypeInfo> = { | |||
| [ResourceSelectorType.Model]: { | |||
| getList: getModelList, | |||
| getVersions: getModelVersionsById, | |||
| getFiles: getModelVersionIdList, | |||
| handleVersionResponse: (res) => res.data || [], | |||
| name: '模型', | |||
| modalIcon: modelImg, | |||
| litReqParamKey: 'available_range', | |||
| fileReqParamKey: 'models_id', | |||
| tabItems: [ | |||
| { | |||
| key: CommonTabKeys.Private, | |||
| label: '我的模型', | |||
| }, | |||
| { | |||
| key: CommonTabKeys.Public, | |||
| label: '公开模型', | |||
| }, | |||
| ], | |||
| }, | |||
| [ResourceSelectorType.Dataset]: { | |||
| getList: getDatasetList, | |||
| getVersions: getDatasetVersionsById, | |||
| getFiles: getDatasetVersionIdList, | |||
| handleVersionResponse: (res) => res.data || [], | |||
| name: '数据集', | |||
| modalIcon: datasetImg, | |||
| litReqParamKey: 'available_range', | |||
| fileReqParamKey: 'dataset_id', | |||
| tabItems: [ | |||
| { | |||
| key: CommonTabKeys.Private, | |||
| label: '我的数据集', | |||
| }, | |||
| { | |||
| key: CommonTabKeys.Public, | |||
| label: '公开数据集', | |||
| }, | |||
| ], | |||
| }, | |||
| [ResourceSelectorType.Mirror]: { | |||
| getList: getMirrorListReq, | |||
| getVersions: (id: number) => getMirrorVersionListReq({ image_id: id, page: 0, size: 200 }), | |||
| getFiles: getMirrorFilesReq, | |||
| handleVersionResponse: (res) => | |||
| res.data?.content?.filter((v: MirrorVersion) => v.status === MirrorVersionStatus.Available) || | |||
| [], | |||
| name: '镜像', | |||
| modalIcon: mirrorImg, | |||
| litReqParamKey: 'image_type', | |||
| fileReqParamKey: 'dataset_id', | |||
| tabItems: [ | |||
| { | |||
| key: CommonTabKeys.Private, | |||
| label: '我的镜像', | |||
| }, | |||
| { | |||
| key: CommonTabKeys.Public, | |||
| label: '公开镜像', | |||
| }, | |||
| ], | |||
| }, | |||
| }; | |||
| type ResourceSelectorResponse = { | |||
| // 选择数据集和模型的返回类型 | |||
| export type ResourceSelectorResponse = { | |||
| id: number; // 数据集或者模型 id | |||
| name: string; // 数据集或者模型 name | |||
| version: string; // 数据集或者模型版本 | |||
| @@ -135,11 +24,11 @@ type ResourceSelectorResponse = { | |||
| activeTab: CommonTabKeys; // 是我的还是公开的 | |||
| }; | |||
| interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> { | |||
| export interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> { | |||
| type: ResourceSelectorType; // 模型 | 数据集 | |||
| defaultExpandedKeys: React.Key[]; | |||
| defaultCheckedKeys: React.Key[]; | |||
| defaultActiveTab: CommonTabKeys; | |||
| defaultExpandedKeys?: React.Key[]; | |||
| defaultCheckedKeys?: React.Key[]; | |||
| defaultActiveTab?: CommonTabKeys; | |||
| onOk?: (params: ResourceSelectorResponse | string | null) => void; | |||
| } | |||
| @@ -148,13 +37,6 @@ type ResourceGroup = { | |||
| name: string; // 数据集或者模型 id | |||
| }; | |||
| type MirrorVersion = { | |||
| id: number; // 镜像版本id | |||
| status: MirrorVersionStatus; // 镜像版本状态 | |||
| tag_name: string; // 镜像版本 | |||
| url: string; // 镜像版本路径 | |||
| }; | |||
| type ResourceFile = { | |||
| id: number; // 文件 id | |||
| file_name: string; // 文件 name | |||
| @@ -261,9 +143,9 @@ function ResourceSelectorModal({ | |||
| const params = { | |||
| page: 0, | |||
| size: 200, | |||
| [selectorTypeData[type].litReqParamKey]: available_range, | |||
| [selectorTypeConfig[type].litReqParamKey]: available_range, | |||
| }; | |||
| const getListReq = selectorTypeData[type].getList; | |||
| const getListReq = selectorTypeConfig[type].getList; | |||
| const [res] = await to(getListReq(params)); | |||
| if (res) { | |||
| const list = res.data?.content || []; | |||
| @@ -279,10 +161,10 @@ function ResourceSelectorModal({ | |||
| // 获取数据集或模型版本列表 | |||
| const getVersions = async (parentId: number) => { | |||
| const getVersionsReq = selectorTypeData[type].getVersions; | |||
| const getVersionsReq = selectorTypeConfig[type].getVersions; | |||
| const [res, error] = await to(getVersionsReq(parentId)); | |||
| if (res) { | |||
| const list = selectorTypeData[type].handleVersionResponse(res); | |||
| const list = selectorTypeConfig[type].handleVersionResponse(res); | |||
| const children = list.map(convertVersionToTreeData(parentId)); | |||
| // 更新 treeData children | |||
| setOriginTreeData((prev) => prev.map(updateChildren(parentId, children))); | |||
| @@ -301,8 +183,8 @@ function ResourceSelectorModal({ | |||
| // 获取版本下的文件 | |||
| const getFiles = async (id: number, version: string) => { | |||
| const getFilesReq = selectorTypeData[type].getFiles; | |||
| const paramsKey = selectorTypeData[type].fileReqParamKey; | |||
| const getFilesReq = selectorTypeConfig[type].getFiles; | |||
| const paramsKey = selectorTypeConfig[type].fileReqParamKey; | |||
| const params = { version: version, [paramsKey]: id }; | |||
| const [res] = await to(getFilesReq(params)); | |||
| if (res) { | |||
| @@ -404,14 +286,14 @@ function ResourceSelectorModal({ | |||
| } | |||
| }; | |||
| const title = `选择${selectorTypeData[type].name}`; | |||
| const palceholder = `请输入${selectorTypeData[type].name}名称`; | |||
| const title = `选择${selectorTypeConfig[type].name}`; | |||
| const palceholder = `请输入${selectorTypeConfig[type].name}名称`; | |||
| const fileTitle = | |||
| type === ResourceSelectorType.Mirror | |||
| ? '已选镜像' | |||
| : `已选${selectorTypeData[type].name}文件(${files.length})`; | |||
| const tabItems = selectorTypeData[type].tabItems; | |||
| const titleImg = selectorTypeData[type].modalIcon; | |||
| : `已选${selectorTypeConfig[type].name}文件(${files.length})`; | |||
| const tabItems = selectorTypeConfig[type].tabItems; | |||
| const titleImg = selectorTypeConfig[type].modalIcon; | |||
| return ( | |||
| <KFModal {...rest} title={title} image={titleImg} onOk={handleOk} width={920} destroyOnClose> | |||
| @@ -101,7 +101,7 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||
| }, | |||
| })); | |||
| // 选择数据集、模型 | |||
| // 选择数据集、模型、镜像 | |||
| const selectResource = (name, item) => { | |||
| let type; | |||
| let resource; | |||
| @@ -130,20 +130,20 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||
| } else { | |||
| const jsonObj = pick(res, ['id', 'version', 'path']); | |||
| const value = JSON.stringify(jsonObj); | |||
| const showValue = `${res.name}:${res.version}`; | |||
| const showValue = `${res.name}:${res.version}`; | |||
| form.setFieldValue(name, { ...item, value, showValue, fromSelect: true }); | |||
| } | |||
| if (type === ResourceSelectorType.Dataset) { | |||
| setSelectedDataset(res); | |||
| } else if (type === ResourceSelectorType.Model) { | |||
| setSelectedModel(res); | |||
| if (type === ResourceSelectorType.Dataset) { | |||
| setSelectedDataset(res); | |||
| } else if (type === ResourceSelectorType.Model) { | |||
| setSelectedModel(res); | |||
| } | |||
| } | |||
| } else { | |||
| if (type === ResourceSelectorType.Dataset) { | |||
| setSelectedDataset(null); | |||
| setSelectedDataset(undefined); | |||
| } else if (type === ResourceSelectorType.Model) { | |||
| setSelectedModel(null); | |||
| setSelectedModel(undefined); | |||
| } | |||
| form.setFieldValue(name, ''); | |||
| } | |||
| @@ -7,6 +7,8 @@ | |||
| margin-bottom: 10px; | |||
| padding-right: 30px; | |||
| background-image: url(/assets/images/pipeline-back.png); | |||
| background-repeat: no-repeat; | |||
| background-position: top left; | |||
| background-size: 100% 100%; | |||
| } | |||
| @@ -43,7 +43,7 @@ export function deleteMirrorReq(id: number) { | |||
| }); | |||
| } | |||
| // 删除镜像 | |||
| // 删除镜像版本 | |||
| export function deleteMirrorVersionReq(id: number) { | |||
| return request(`/api/mmp/imageVersion/${id}`, { | |||
| method: 'DELETE', | |||
| @@ -0,0 +1,61 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-16 14:29:44 | |||
| * @Description: 模型部署接口 | |||
| */ | |||
| import { request } from '@umijs/max'; | |||
| // 分页查询模型部署列表 | |||
| export function getModelDeploymentListReq(data: any) { | |||
| return request(`/api/v1/model/get`, { | |||
| method: 'POST', | |||
| data, | |||
| }); | |||
| } | |||
| // 查询模型部署详情 | |||
| export function getModelDeploymentInfoReq(id: number) { | |||
| return request(`/api/mmp/image/${id}`, { | |||
| method: 'GET', | |||
| }); | |||
| } | |||
| // 创建模型部署 | |||
| export function createModelDeploymentReq(data: any) { | |||
| return request(`/api/v1/model/create`, { | |||
| method: 'POST', | |||
| data, | |||
| }); | |||
| } | |||
| // 删除模型部署 | |||
| export function deleteModelDeploymentReq(data: any) { | |||
| return request(`/api/v1/model/delete`, { | |||
| method: 'POST', | |||
| data, | |||
| }); | |||
| } | |||
| // 重启模型部署 | |||
| export function restartModelDeploymentReq(data: any) { | |||
| return request(`/api/v1/model/restart`, { | |||
| method: 'POST', | |||
| data, | |||
| }); | |||
| } | |||
| // 停止模型部署 | |||
| export function stopModelDeploymentReq(data: any) { | |||
| return request(`/api/v1/model/stop`, { | |||
| method: 'POST', | |||
| data, | |||
| }); | |||
| } | |||
| // 更新模型部署 | |||
| export function updateModelDeploymentReq(data: any) { | |||
| return request(`/api/v1/model/update`, { | |||
| method: 'POST', | |||
| data, | |||
| }); | |||
| } | |||
| @@ -68,3 +68,12 @@ export type PipelineNodeModelSerialize = Omit< | |||
| in_parameters: Record<string, PipelineNodeModelParameter>; | |||
| out_parameters: Record<string, PipelineNodeModelParameter>; | |||
| }; | |||
| // 资源规格 | |||
| export type ComputingResource = { | |||
| id: number; | |||
| computing_resource: string; | |||
| description: string; | |||
| standard: string; | |||
| create_by: string; | |||
| }; | |||
| @@ -28,3 +28,36 @@ export function parseJsonText(text?: string | null): any | null { | |||
| return null; | |||
| } | |||
| } | |||
| // Underscore-to-camelCase | |||
| export function underscoreToCamelCase(obj: Record<string, any>) { | |||
| const newObj: Record<string, any> = {}; | |||
| for (const key in obj) { | |||
| if (obj.hasOwnProperty(key)) { | |||
| const newKey = key.replace(/([-_][a-z])/gi, function ($1) { | |||
| return $1.toUpperCase().replace('[-_]', '').replace('_', ''); | |||
| }); | |||
| let value = obj[key]; | |||
| if (typeof value === 'object' && value !== null) { | |||
| value = underscoreToCamelCase(value); | |||
| } | |||
| newObj[newKey] = value; | |||
| } | |||
| } | |||
| return newObj; | |||
| } | |||
| export function camelCaseToUnderscore(obj: Record<string, any>) { | |||
| const newObj: Record<string, any> = {}; | |||
| for (const key in obj) { | |||
| if (obj.hasOwnProperty(key)) { | |||
| const newKey = key.replace(/([A-Z])/g, '_$1').toLowerCase(); | |||
| let value = obj[key]; | |||
| if (typeof value === 'object' && value !== null) { | |||
| value = camelCaseToUnderscore(value); | |||
| } | |||
| newObj[newKey] = value; | |||
| } | |||
| } | |||
| return newObj; | |||
| } | |||
| @@ -16,7 +16,8 @@ import { createRoot } from 'react-dom/client'; | |||
| * @param modalProps - The modal properties. | |||
| * @return An object with a destroy method to close the modal. | |||
| */ | |||
| export const openAntdModal = <T extends ModalProps>( | |||
| export const openAntdModal = <T extends Omit<ModalProps, 'onOk'>>( | |||
| modal: (props: T) => React.ReactNode, | |||
| modalProps: T, | |||
| ) => { | |||
| @@ -1,5 +1,7 @@ | |||
| // 用于新建镜像 | |||
| export const mirrorNameKey = 'mirror-name'; | |||
| // 模型部署 | |||
| export const modelDeploymentInfoKey = 'model-deployment-info'; | |||
| export const getSessionStorageItem = (key: string, isObject: boolean = false) => { | |||
| const jsonStr = sessionStorage.getItem(key); | |||
| @@ -22,6 +24,10 @@ export const setSessionStorageItem = (key: string, state?: any, isObject: boolea | |||
| } | |||
| }; | |||
| export const removeSessionStorageItem = (key: string) => { | |||
| sessionStorage.removeItem(key); | |||
| }; | |||
| // 获取之后就删除,多用于上一个页面传递数据到下一个页面 | |||
| export const getSessionItemThenRemove = (key: string, isObject: boolean = false) => { | |||
| const res = getSessionStorageItem(key, isObject); | |||