| @@ -19,7 +19,6 @@ const Settings: ProLayoutProps & { | |||||
| title: '智能软件开发平台', | title: '智能软件开发平台', | ||||
| pwa: true, | pwa: true, | ||||
| logo: '/assets/images/left-top-logo.png', | logo: '/assets/images/left-top-logo.png', | ||||
| iconfontUrl: '//at.alicdn.com/t/c/font_4511326_a182r7rksx5.js', | |||||
| token: { | token: { | ||||
| // 参见ts声明,demo 见文档,通过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 | //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: '模型列表', | name: '模型列表', | ||||
| path: '', | path: '', | ||||
| component: './ModelDeployment/list', | |||||
| component: './ModelDeployment/List', | |||||
| }, | }, | ||||
| { | { | ||||
| name: '镜像详情', | name: '镜像详情', | ||||
| path: ':id', | path: ':id', | ||||
| component: './ModelDeployment/info', | |||||
| component: './ModelDeployment/Info', | |||||
| }, | }, | ||||
| { | { | ||||
| name: '创建镜像', | name: '创建镜像', | ||||
| path: 'create', | path: 'create', | ||||
| component: './ModelDeployment/create', | |||||
| component: './ModelDeployment/Create', | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| @@ -224,6 +224,9 @@ export const antd: RuntimeAntdConfig = (memo) => { | |||||
| inputFontSizeLG: parseInt(themes['fontSizeInputLg']), | inputFontSizeLG: parseInt(themes['fontSizeInputLg']), | ||||
| paddingBlockLG: 10, | paddingBlockLG: 10, | ||||
| }; | }; | ||||
| memo.theme.components.Select = { | |||||
| singleItemHeightLG: 46, | |||||
| }; | |||||
| memo.theme.components.Table = { | memo.theme.components.Table = { | ||||
| headerBg: 'rgba(242, 244, 247, 0.36)', | headerBg: 'rgba(242, 244, 247, 0.36)', | ||||
| headerBorderRadius: 4, | headerBorderRadius: 4, | ||||
| @@ -3,6 +3,7 @@ | |||||
| * @Date: 2024-04-17 12:53:06 | * @Date: 2024-04-17 12:53:06 | ||||
| * @Description: | * @Description: | ||||
| */ | */ | ||||
| import '@/iconfont/iconfont-menu.js'; | |||||
| import '@/iconfont/iconfont.js'; | import '@/iconfont/iconfont.js'; | ||||
| import { createFromIconfontCN } from '@ant-design/icons'; | import { createFromIconfontCN } from '@ant-design/icons'; | ||||
| @@ -4,4 +4,7 @@ | |||||
| height: 50px; | height: 50px; | ||||
| padding-left: 30px; | padding-left: 30px; | ||||
| background-image: url(@/assets/img/page-title-bg.png); | 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 { | .parameter-input { | ||||
| flex: 1 1 auto; | |||||
| width: 100%; | |||||
| min-width: 0; | min-width: 0; | ||||
| height: 32px; | |||||
| padding: 3px 11px; | |||||
| padding: 4px 11px; | |||||
| border: 1px solid #d9d9d9; | border: 1px solid #d9d9d9; | ||||
| border-radius: 6px; | border-radius: 6px; | ||||
| @@ -15,7 +14,7 @@ | |||||
| align-items: center; | align-items: center; | ||||
| width: fit-content; | width: fit-content; | ||||
| max-width: 100%; | max-width: 100%; | ||||
| height: 24px; | |||||
| min-height: 22px; | |||||
| padding: 0 8px; | padding: 0 8px; | ||||
| color: .addAlpha(@text-color, 0.8) []; | color: .addAlpha(@text-color, 0.8) []; | ||||
| background-color: rgba(0, 0, 0, 0.06); | background-color: rgba(0, 0, 0, 0.06); | ||||
| @@ -25,6 +24,7 @@ | |||||
| .singleLine(); | .singleLine(); | ||||
| margin-right: 8px; | margin-right: 8px; | ||||
| font-size: @font-size-input; | font-size: @font-size-input; | ||||
| line-height: 1.5714285714285714; | |||||
| } | } | ||||
| &__close-icon { | &__close-icon { | ||||
| @@ -37,7 +37,28 @@ | |||||
| } | } | ||||
| &__placeholder { | &__placeholder { | ||||
| min-height: 22px; | |||||
| color: rgba(0, 0, 0, 0.25); | color: rgba(0, 0, 0, 0.25); | ||||
| font-size: @font-size-input; | 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 { CloseOutlined } from '@ant-design/icons'; | ||||
| import { Input } from 'antd'; | import { Input } from 'antd'; | ||||
| import styles from './index.less'; | |||||
| import classNames from 'classnames'; | |||||
| import './index.less'; | |||||
| type ParameterInputData = { | type ParameterInputData = { | ||||
| value?: any; | value?: any; | ||||
| @@ -16,6 +17,10 @@ interface ParameterInputProps { | |||||
| textArea?: boolean; | textArea?: boolean; | ||||
| placeholder?: string; | placeholder?: string; | ||||
| allowClear?: boolean; | allowClear?: boolean; | ||||
| className?: string; | |||||
| style?: React.CSSProperties; | |||||
| size?: 'middle' | 'small' | 'large'; | |||||
| disabled?: boolean; | |||||
| } | } | ||||
| function ParameterInput({ | function ParameterInput({ | ||||
| @@ -26,6 +31,10 @@ function ParameterInput({ | |||||
| textArea = false, | textArea = false, | ||||
| placeholder, | placeholder, | ||||
| allowClear, | allowClear, | ||||
| className, | |||||
| style, | |||||
| size = 'middle', | |||||
| disabled = false, | |||||
| ...rest | ...rest | ||||
| }: ParameterInputProps) { | }: ParameterInputProps) { | ||||
| // console.log('ParameterInput', value); | // console.log('ParameterInput', value); | ||||
| @@ -40,15 +49,21 @@ function ParameterInput({ | |||||
| return ( | 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 ? ( | {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 | <CloseOutlined | ||||
| className={styles['parameter-input__content__close-icon']} | |||||
| className="parameter-input__content__close-icon" | |||||
| onClick={() => | onClick={() => | ||||
| onChange?.({ | onChange?.({ | ||||
| ...valueObj, | ...valueObj, | ||||
| @@ -60,15 +75,19 @@ function ParameterInput({ | |||||
| /> | /> | ||||
| </div> | </div> | ||||
| ) : ( | ) : ( | ||||
| <div className={styles['parameter-input__placeholder']}>{placeholder}</div> | |||||
| <div className="parameter-input__placeholder">{placeholder}</div> | |||||
| )} | )} | ||||
| </div> | </div> | ||||
| ) : ( | ) : ( | ||||
| <InputComponent | <InputComponent | ||||
| {...rest} | {...rest} | ||||
| size={size} | |||||
| className={className} | |||||
| style={style} | |||||
| placeholder={placeholder} | placeholder={placeholder} | ||||
| allowClear={allowClear} | allowClear={allowClear} | ||||
| value={valueObj?.showValue} | value={valueObj?.showValue} | ||||
| disabled={disabled} | |||||
| onChange={(e) => | onChange={(e) => | ||||
| onChange?.({ | onChange?.({ | ||||
| ...valueObj, | ...valueObj, | ||||
| @@ -4,9 +4,25 @@ export enum CommonTabKeys { | |||||
| Public = 'Public', // 公开 | Public = 'Public', // 公开 | ||||
| } | } | ||||
| // 镜像状态 | |||||
| // 镜像版本状态 | |||||
| export enum MirrorVersionStatus { | export enum MirrorVersionStatus { | ||||
| Available = 'available', // 可用 | Available = 'available', // 可用 | ||||
| Building = 'building', // 构建中 | Building = 'building', // 构建中 | ||||
| Failed = 'failed', // 构建中 | 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'; | } from 'antd'; | ||||
| import { omit } from 'lodash'; | import { omit } from 'lodash'; | ||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import { CategoryData } from '../../type'; | |||||
| import { CategoryData } from '../../types'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> { | interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> { | ||||
| @@ -1,7 +1,7 @@ | |||||
| import { getAccessToken } from '@/access'; | import { getAccessToken } from '@/access'; | ||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import KFModal from '@/components/KFModal'; | 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 { addModel } from '@/services/dataset/index.js'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | ||||
| @@ -1,7 +1,7 @@ | |||||
| import { getAccessToken } from '@/access'; | import { getAccessToken } from '@/access'; | ||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import KFModal from '@/components/KFModal'; | 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 { to } from '@/utils/promise'; | ||||
| import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | ||||
| import { | import { | ||||
| @@ -1,5 +1,5 @@ | |||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { CategoryData, ResourceType, resourceConfig } from '../../type'; | |||||
| import { CategoryData, ResourceType, resourceConfig } from '../../types'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type CategoryItemProps = { | type CategoryItemProps = { | ||||
| @@ -1,5 +1,5 @@ | |||||
| import { Flex, Input } from 'antd'; | import { Flex, Input } from 'antd'; | ||||
| import { CategoryData, ResourceType, resourceConfig } from '../../type'; | |||||
| import { CategoryData, ResourceType, resourceConfig } from '../../types'; | |||||
| import CategoryItem from '../CategoryItem'; | import CategoryItem from '../CategoryItem'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -7,7 +7,7 @@ import { modalConfirm } from '@/utils/ui'; | |||||
| import { useNavigate } from '@umijs/max'; | import { useNavigate } from '@umijs/max'; | ||||
| import { Button, Input, Pagination, PaginationProps, message } from 'antd'; | import { Button, Input, Pagination, PaginationProps, message } from 'antd'; | ||||
| import { Ref, forwardRef, useEffect, useImperativeHandle, useState } from 'react'; | 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 AddDatasetModal from '../AddDatasetModal'; | ||||
| import ResourceItem from '../Resourcetem'; | import ResourceItem from '../Resourcetem'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -4,5 +4,8 @@ | |||||
| height: 50px; | height: 50px; | ||||
| padding-left: 27px; | padding-left: 27px; | ||||
| background-image: url(@/assets/img/page-title-bg.png); | 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 { to } from '@/utils/promise'; | ||||
| import { Flex, Tabs, type TabsProps } from 'antd'; | import { Flex, Tabs, type TabsProps } from 'antd'; | ||||
| import { useEffect, useRef, useState } from 'react'; | import { useEffect, useRef, useState } from 'react'; | ||||
| import { CategoryData, ResourceType, resourceConfig } from '../../type'; | |||||
| import { CategoryData, ResourceType, resourceConfig } from '../../types'; | |||||
| import CategoryList from '../CategoryList'; | import CategoryList from '../CategoryList'; | ||||
| import ResourceList, { ResourceListRef } from '../ResourceList'; | import ResourceList, { ResourceListRef } from '../ResourceList'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -3,7 +3,7 @@ import creatByImg from '@/assets/img/creatBy.png'; | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { formatDate } from '@/utils/date'; | import { formatDate } from '@/utils/date'; | ||||
| import { Button, Flex, Typography } from 'antd'; | import { Button, Flex, Typography } from 'antd'; | ||||
| import { ResourceData } from '../../type'; | |||||
| import { ResourceData } from '../../types'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type ResourceItemProps = { | type ResourceItemProps = { | ||||
| @@ -1,5 +1,5 @@ | |||||
| import ResourcePage from './components/ResourcePage'; | import ResourcePage from './components/ResourcePage'; | ||||
| import { ResourceType } from './type'; | |||||
| import { ResourceType } from './types'; | |||||
| const DatasetPage = () => { | const DatasetPage = () => { | ||||
| return <ResourcePage resourceType={ResourceType.Dataset} />; | return <ResourcePage resourceType={ResourceType.Dataset} />; | ||||
| @@ -1,5 +1,5 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import { ResourceType } from '@/pages/Dataset/type'; | |||||
| import { ResourceType } from '@/pages/Dataset/types'; | |||||
| import { | import { | ||||
| deleteDatasetVersion, | deleteDatasetVersion, | ||||
| getDatasetById, | getDatasetById, | ||||
| @@ -7,7 +7,10 @@ | |||||
| margin-bottom: 10px; | margin-bottom: 10px; | ||||
| padding: 25px 30px; | padding: 25px 30px; | ||||
| background-image: url(/assets/images/dataset-back.png); | background-image: url(/assets/images/dataset-back.png); | ||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | |||||
| background-size: 100% 100%; | background-size: 100% 100%; | ||||
| .smallTagBox { | .smallTagBox { | ||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| @@ -19,9 +19,6 @@ export enum ResourceType { | |||||
| Dataset = 'Dataset', // 数据集 | Dataset = 'Dataset', // 数据集 | ||||
| } | } | ||||
| type ResourceTypeKeys = keyof typeof ResourceType; | |||||
| export type ResourceTypeValues = (typeof ResourceType)[ResourceTypeKeys]; | |||||
| type ResourceTypeInfo = { | type ResourceTypeInfo = { | ||||
| getList: (params: any) => Promise<any>; | getList: (params: any) => Promise<any>; | ||||
| getVersions: (params: any) => Promise<any>; | getVersions: (params: any) => Promise<any>; | ||||
| @@ -45,7 +42,7 @@ type ResourceTypeInfo = { | |||||
| uploadAccept?: string; | uploadAccept?: string; | ||||
| }; | }; | ||||
| export const resourceConfig: Record<ResourceTypeValues, ResourceTypeInfo> = { | |||||
| export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | |||||
| [ResourceType.Dataset]: { | [ResourceType.Dataset]: { | ||||
| getList: getDatasetList, | getList: getDatasetList, | ||||
| getVersions: getDatasetVersionsById, | getVersions: getDatasetVersionsById, | ||||
| @@ -16,7 +16,7 @@ function DatasetAnnotation() { | |||||
| }; | }; | ||||
| return ( | return ( | ||||
| <div className={styles.container}> | <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> | </div> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -6,6 +6,8 @@ | |||||
| height: 49px; | height: 49px; | ||||
| padding-right: 30px; | padding-right: 30px; | ||||
| background-image: url(/assets/images/pipeline-back.png); | background-image: url(/assets/images/pipeline-back.png); | ||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | |||||
| background-size: 100% 100%; | background-size: 100% 100%; | ||||
| } | } | ||||
| .pipelineTopBox { | .pipelineTopBox { | ||||
| @@ -17,6 +19,8 @@ | |||||
| margin-bottom: 10px; | margin-bottom: 10px; | ||||
| padding-right: 30px; | padding-right: 30px; | ||||
| background-image: url(/assets/images/pipeline-back.png); | background-image: url(/assets/images/pipeline-back.png); | ||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | |||||
| background-size: 100% 100%; | background-size: 100% 100%; | ||||
| } | } | ||||
| .tableExpandBox { | .tableExpandBox { | ||||
| @@ -15,10 +15,7 @@ export enum ExperimentStatus { | |||||
| Omitted = 'Omitted', | 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: { | Running: { | ||||
| label: '运行中', | label: '运行中', | ||||
| color: '#165bff', | color: '#165bff', | ||||
| @@ -6,15 +6,12 @@ | |||||
| import { MirrorVersionStatus } from '@/enums'; | import { MirrorVersionStatus } from '@/enums'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| type MirrorVersionStatusKeys = keyof typeof MirrorVersionStatus; | |||||
| type MirrorVersionStatusValues = (typeof MirrorVersionStatus)[MirrorVersionStatusKeys]; | |||||
| export type MirrorVersionStatusInfo = { | export type MirrorVersionStatusInfo = { | ||||
| text: string; | text: string; | ||||
| classname: string; | classname: string; | ||||
| }; | }; | ||||
| const statusInfo: Record<MirrorVersionStatusValues, MirrorVersionStatusInfo> = { | |||||
| const statusInfo: Record<MirrorVersionStatus, MirrorVersionStatusInfo> = { | |||||
| [MirrorVersionStatus.Building]: { | [MirrorVersionStatus.Building]: { | ||||
| text: '构建中', | text: '构建中', | ||||
| classname: styles['mirror-status-cell'], | classname: styles['mirror-status-cell'], | ||||
| @@ -11,7 +11,11 @@ import SubAreaTitle from '@/components/SubAreaTitle'; | |||||
| import { CommonTabKeys } from '@/enums'; | import { CommonTabKeys } from '@/enums'; | ||||
| import { createMirrorReq } from '@/services/mirror'; | import { createMirrorReq } from '@/services/mirror'; | ||||
| import { to } from '@/utils/promise'; | 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 { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; | ||||
| import { useNavigate } from '@umijs/max'; | import { useNavigate } from '@umijs/max'; | ||||
| import { Button, Col, Form, Input, Row, Upload, UploadFile, message, type UploadProps } from 'antd'; | import { Button, Col, Form, Input, Row, Upload, UploadFile, message, type UploadProps } from 'antd'; | ||||
| @@ -56,11 +60,14 @@ function MirrorCreate() { | |||||
| }; | }; | ||||
| useEffect(() => { | useEffect(() => { | ||||
| const name = getSessionItemThenRemove(mirrorNameKey); | |||||
| const name = getSessionStorageItem(mirrorNameKey); | |||||
| if (name) { | if (name) { | ||||
| form.setFieldValue('name', name); | form.setFieldValue('name', name); | ||||
| setNameDisabled(true); | setNameDisabled(true); | ||||
| } | } | ||||
| return () => { | |||||
| removeSessionStorageItem(mirrorNameKey); | |||||
| }; | |||||
| }, []); | }, []); | ||||
| // 创建公网、本地镜像 | // 创建公网、本地镜像 | ||||
| @@ -4,6 +4,9 @@ | |||||
| height: 50px; | height: 50px; | ||||
| padding-left: 27px; | padding-left: 27px; | ||||
| background-image: url(@/assets/img/page-title-bg.png); | background-image: url(@/assets/img/page-title-bg.png); | ||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | |||||
| background-size: 100% 100%; | |||||
| } | } | ||||
| &__content { | &__content { | ||||
| @@ -11,6 +11,7 @@ import { useCacheState } from '@/hooks/pageCacheState'; | |||||
| import { deleteMirrorReq, getMirrorListReq } from '@/services/mirror'; | import { deleteMirrorReq, getMirrorListReq } from '@/services/mirror'; | ||||
| import themes from '@/styles/theme.less'; | import themes from '@/styles/theme.less'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { mirrorNameKey, setSessionStorageItem } from '@/utils/sessionStorage'; | |||||
| import { modalConfirm } from '@/utils/ui'; | import { modalConfirm } from '@/utils/ui'; | ||||
| import { useNavigate } from '@umijs/max'; | import { useNavigate } from '@umijs/max'; | ||||
| import { | import { | ||||
| @@ -145,6 +146,7 @@ function MirrorList() { | |||||
| // 创建镜像 | // 创建镜像 | ||||
| const createMirror = () => { | const createMirror = () => { | ||||
| navigate(`/dataset/mirror/create`); | navigate(`/dataset/mirror/create`); | ||||
| setSessionStorageItem(mirrorNameKey, ''); | |||||
| setCacheState({ | setCacheState({ | ||||
| activeTab, | activeTab, | ||||
| pagination, | pagination, | ||||
| @@ -1,5 +1,5 @@ | |||||
| import ResourcePage from '@/pages/Dataset/components/ResourcePage'; | import ResourcePage from '@/pages/Dataset/components/ResourcePage'; | ||||
| import { ResourceType } from '@/pages/Dataset/type'; | |||||
| import { ResourceType } from '@/pages/Dataset/types'; | |||||
| const ModelPage = () => { | const ModelPage = () => { | ||||
| return <ResourcePage resourceType={ResourceType.Model} />; | return <ResourcePage resourceType={ResourceType.Model} />; | ||||
| @@ -1,6 +1,6 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import AddVersionModal from '@/pages/Dataset/components/AddVersionModal'; | import AddVersionModal from '@/pages/Dataset/components/AddVersionModal'; | ||||
| import { ResourceType } from '@/pages/Dataset/type'; | |||||
| import { ResourceType } from '@/pages/Dataset/types'; | |||||
| import { | import { | ||||
| deleteModelVersion, | deleteModelVersion, | ||||
| getModelById, | getModelById, | ||||
| @@ -7,8 +7,10 @@ | |||||
| margin-bottom: 10px; | margin-bottom: 10px; | ||||
| padding: 25px 30px; | padding: 25px 30px; | ||||
| background-image: url(/assets/images/dataset-back.png); | background-image: url(/assets/images/dataset-back.png); | ||||
| background-repeat: no-repeat; | |||||
| background-position: top center; | |||||
| background-size: 100% 100%; | background-size: 100% 100%; | ||||
| .smallTagBox { | .smallTagBox { | ||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| @@ -6,6 +6,8 @@ | |||||
| margin-top: 10px; | margin-top: 10px; | ||||
| padding: 30px 30px 10px; | padding: 30px 30px 10px; | ||||
| overflow: auto; | overflow: auto; | ||||
| color: @text-color; | |||||
| font-size: @font-size-content; | |||||
| background-color: white; | background-color: white; | ||||
| border-radius: 10px; | 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; | line-height: 1.6; | ||||
| .label { | .label { | ||||
| flex: none; | |||||
| width: 80px; | width: 80px; | ||||
| color: @text-color-secondary; | color: @text-color-secondary; | ||||
| } | } | ||||
| @@ -16,6 +17,8 @@ | |||||
| .value { | .value { | ||||
| flex: 1; | flex: 1; | ||||
| color: @text-color; | 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 { | &__delete-button { | ||||
| position: absolute; | position: absolute; | ||||
| top: 5px; | top: 5px; | ||||
| right: 0; | |||||
| right: 24px; | |||||
| } | } | ||||
| :global { | :global { | ||||
| @@ -1,8 +1,9 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | |||||
| import { getParamComponent, getParamRules } from '@/pages/Experiment/components/AddExperimentModal'; | import { getParamComponent, getParamRules } from '@/pages/Experiment/components/AddExperimentModal'; | ||||
| import { type PipelineGlobalParam } from '@/types'; | import { type PipelineGlobalParam } from '@/types'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { modalConfirm } from '@/utils/ui'; | 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 { Button, Drawer, Form, Input, Radio, Tooltip } from 'antd'; | ||||
| import { NamePath } from 'antd/es/form/interface'; | import { NamePath } from 'antd/es/form/interface'; | ||||
| import { forwardRef, useImperativeHandle } from 'react'; | import { forwardRef, useImperativeHandle } from 'react'; | ||||
| @@ -143,7 +144,7 @@ const GlobalParamsDrawer = forwardRef( | |||||
| className={styles['form-item__delete-button']} | className={styles['form-item__delete-button']} | ||||
| type="link" | type="link" | ||||
| onClick={() => removeParameter(name, remove)} | onClick={() => removeParameter(name, remove)} | ||||
| icon={<DeleteOutlined />} | |||||
| icon={<KFIcon type="icon-shanchu" />} | |||||
| ></Button> | ></Button> | ||||
| </Tooltip> | </Tooltip> | ||||
| </div> | </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: 赵伟 | * @Author: 赵伟 | ||||
| * @Date: 2024-04-11 16:31:18 | * @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 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 { to } from '@/utils/promise'; | ||||
| import { Icon } from '@umijs/max'; | 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 { Input, Tabs, Tree } from 'antd'; | ||||
| import React, { useEffect, useMemo, useRef, useState } from 'react'; | import React, { useEffect, useMemo, useRef, useState } from 'react'; | ||||
| import { MirrorVersion, ResourceSelectorType, selectorTypeConfig } from './config'; | |||||
| import styles from './index.less'; | 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 | id: number; // 数据集或者模型 id | ||||
| name: string; // 数据集或者模型 name | name: string; // 数据集或者模型 name | ||||
| version: string; // 数据集或者模型版本 | version: string; // 数据集或者模型版本 | ||||
| @@ -135,11 +24,11 @@ type ResourceSelectorResponse = { | |||||
| activeTab: CommonTabKeys; // 是我的还是公开的 | activeTab: CommonTabKeys; // 是我的还是公开的 | ||||
| }; | }; | ||||
| interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| export interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> { | |||||
| type: ResourceSelectorType; // 模型 | 数据集 | type: ResourceSelectorType; // 模型 | 数据集 | ||||
| defaultExpandedKeys: React.Key[]; | |||||
| defaultCheckedKeys: React.Key[]; | |||||
| defaultActiveTab: CommonTabKeys; | |||||
| defaultExpandedKeys?: React.Key[]; | |||||
| defaultCheckedKeys?: React.Key[]; | |||||
| defaultActiveTab?: CommonTabKeys; | |||||
| onOk?: (params: ResourceSelectorResponse | string | null) => void; | onOk?: (params: ResourceSelectorResponse | string | null) => void; | ||||
| } | } | ||||
| @@ -148,13 +37,6 @@ type ResourceGroup = { | |||||
| name: string; // 数据集或者模型 id | name: string; // 数据集或者模型 id | ||||
| }; | }; | ||||
| type MirrorVersion = { | |||||
| id: number; // 镜像版本id | |||||
| status: MirrorVersionStatus; // 镜像版本状态 | |||||
| tag_name: string; // 镜像版本 | |||||
| url: string; // 镜像版本路径 | |||||
| }; | |||||
| type ResourceFile = { | type ResourceFile = { | ||||
| id: number; // 文件 id | id: number; // 文件 id | ||||
| file_name: string; // 文件 name | file_name: string; // 文件 name | ||||
| @@ -261,9 +143,9 @@ function ResourceSelectorModal({ | |||||
| const params = { | const params = { | ||||
| page: 0, | page: 0, | ||||
| size: 200, | 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)); | const [res] = await to(getListReq(params)); | ||||
| if (res) { | if (res) { | ||||
| const list = res.data?.content || []; | const list = res.data?.content || []; | ||||
| @@ -279,10 +161,10 @@ function ResourceSelectorModal({ | |||||
| // 获取数据集或模型版本列表 | // 获取数据集或模型版本列表 | ||||
| const getVersions = async (parentId: number) => { | const getVersions = async (parentId: number) => { | ||||
| const getVersionsReq = selectorTypeData[type].getVersions; | |||||
| const getVersionsReq = selectorTypeConfig[type].getVersions; | |||||
| const [res, error] = await to(getVersionsReq(parentId)); | const [res, error] = await to(getVersionsReq(parentId)); | ||||
| if (res) { | if (res) { | ||||
| const list = selectorTypeData[type].handleVersionResponse(res); | |||||
| const list = selectorTypeConfig[type].handleVersionResponse(res); | |||||
| const children = list.map(convertVersionToTreeData(parentId)); | const children = list.map(convertVersionToTreeData(parentId)); | ||||
| // 更新 treeData children | // 更新 treeData children | ||||
| setOriginTreeData((prev) => prev.map(updateChildren(parentId, children))); | setOriginTreeData((prev) => prev.map(updateChildren(parentId, children))); | ||||
| @@ -301,8 +183,8 @@ function ResourceSelectorModal({ | |||||
| // 获取版本下的文件 | // 获取版本下的文件 | ||||
| const getFiles = async (id: number, version: string) => { | 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 params = { version: version, [paramsKey]: id }; | ||||
| const [res] = await to(getFilesReq(params)); | const [res] = await to(getFilesReq(params)); | ||||
| if (res) { | 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 = | const fileTitle = | ||||
| type === ResourceSelectorType.Mirror | 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 ( | return ( | ||||
| <KFModal {...rest} title={title} image={titleImg} onOk={handleOk} width={920} destroyOnClose> | <KFModal {...rest} title={title} image={titleImg} onOk={handleOk} width={920} destroyOnClose> | ||||
| @@ -101,7 +101,7 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| }, | }, | ||||
| })); | })); | ||||
| // 选择数据集、模型 | |||||
| // 选择数据集、模型、镜像 | |||||
| const selectResource = (name, item) => { | const selectResource = (name, item) => { | ||||
| let type; | let type; | ||||
| let resource; | let resource; | ||||
| @@ -130,20 +130,20 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| } else { | } else { | ||||
| const jsonObj = pick(res, ['id', 'version', 'path']); | const jsonObj = pick(res, ['id', 'version', 'path']); | ||||
| const value = JSON.stringify(jsonObj); | 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 }); | 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 { | } else { | ||||
| if (type === ResourceSelectorType.Dataset) { | if (type === ResourceSelectorType.Dataset) { | ||||
| setSelectedDataset(null); | |||||
| setSelectedDataset(undefined); | |||||
| } else if (type === ResourceSelectorType.Model) { | } else if (type === ResourceSelectorType.Model) { | ||||
| setSelectedModel(null); | |||||
| setSelectedModel(undefined); | |||||
| } | } | ||||
| form.setFieldValue(name, ''); | form.setFieldValue(name, ''); | ||||
| } | } | ||||
| @@ -7,6 +7,8 @@ | |||||
| margin-bottom: 10px; | margin-bottom: 10px; | ||||
| padding-right: 30px; | padding-right: 30px; | ||||
| background-image: url(/assets/images/pipeline-back.png); | background-image: url(/assets/images/pipeline-back.png); | ||||
| background-repeat: no-repeat; | |||||
| background-position: top left; | |||||
| background-size: 100% 100%; | background-size: 100% 100%; | ||||
| } | } | ||||
| @@ -43,7 +43,7 @@ export function deleteMirrorReq(id: number) { | |||||
| }); | }); | ||||
| } | } | ||||
| // 删除镜像 | |||||
| // 删除镜像版本 | |||||
| export function deleteMirrorVersionReq(id: number) { | export function deleteMirrorVersionReq(id: number) { | ||||
| return request(`/api/mmp/imageVersion/${id}`, { | return request(`/api/mmp/imageVersion/${id}`, { | ||||
| method: 'DELETE', | 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>; | in_parameters: Record<string, PipelineNodeModelParameter>; | ||||
| out_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; | 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. | * @param modalProps - The modal properties. | ||||
| * @return An object with a destroy method to close the modal. | * @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, | modal: (props: T) => React.ReactNode, | ||||
| modalProps: T, | modalProps: T, | ||||
| ) => { | ) => { | ||||
| @@ -1,5 +1,7 @@ | |||||
| // 用于新建镜像 | // 用于新建镜像 | ||||
| export const mirrorNameKey = 'mirror-name'; | export const mirrorNameKey = 'mirror-name'; | ||||
| // 模型部署 | |||||
| export const modelDeploymentInfoKey = 'model-deployment-info'; | |||||
| export const getSessionStorageItem = (key: string, isObject: boolean = false) => { | export const getSessionStorageItem = (key: string, isObject: boolean = false) => { | ||||
| const jsonStr = sessionStorage.getItem(key); | 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) => { | export const getSessionItemThenRemove = (key: string, isObject: boolean = false) => { | ||||
| const res = getSessionStorageItem(key, isObject); | const res = getSessionStorageItem(key, isObject); | ||||