| @@ -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 | ||||
| @@ -67,24 +67,36 @@ export default [ | |||||
| path: '/pipeline', | path: '/pipeline', | ||||
| routes: [ | routes: [ | ||||
| { | { | ||||
| name: '流水线', | |||||
| path: '/pipeline/pipelineText', | |||||
| component: './Pipeline/index', | |||||
| }, | |||||
| { | |||||
| name: '训练', | |||||
| path: '/pipeline/pytorchtext/:id/:name', | |||||
| component: './Pipeline/editPipeline/index', | |||||
| name: '流水线模板', | |||||
| path: 'template', | |||||
| routes: [ | |||||
| { | |||||
| name: '流水线模板', | |||||
| path: '', | |||||
| component: './Pipeline/index', | |||||
| }, | |||||
| { | |||||
| name: '流水线详情', | |||||
| path: ':id/:name', | |||||
| component: './Pipeline/editPipeline/index', | |||||
| }, | |||||
| ], | |||||
| }, | }, | ||||
| { | { | ||||
| name: '实验', | name: '实验', | ||||
| path: '/pipeline/experimentText', | |||||
| component: './Experiment/index', | |||||
| }, | |||||
| { | |||||
| name: '实验训练', | |||||
| path: '/pipeline/experimentPytorchtext/:workflowId/:id', | |||||
| component: './Experiment/experimentText/index', | |||||
| path: 'experiment', | |||||
| routes: [ | |||||
| { | |||||
| name: '实验', | |||||
| path: '', | |||||
| component: './Experiment/index', | |||||
| }, | |||||
| { | |||||
| name: '实验训练', | |||||
| path: ':workflowId/:id', | |||||
| component: './Experiment/training/index', | |||||
| }, | |||||
| ], | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| @@ -158,17 +170,17 @@ export default [ | |||||
| { | { | ||||
| name: '镜像列表', | name: '镜像列表', | ||||
| path: '', | path: '', | ||||
| component: './Mirror/list', | |||||
| component: './Mirror/List', | |||||
| }, | }, | ||||
| { | { | ||||
| name: '镜像详情', | name: '镜像详情', | ||||
| path: ':id', | path: ':id', | ||||
| component: './Mirror/info', | |||||
| component: './Mirror/Info', | |||||
| }, | }, | ||||
| { | { | ||||
| name: '创建镜像', | name: '创建镜像', | ||||
| path: 'create', | path: 'create', | ||||
| component: './Mirror/create', | |||||
| component: './Mirror/Create', | |||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| @@ -194,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%; | |||||
| } | } | ||||
| @@ -0,0 +1,64 @@ | |||||
| .parameter-input { | |||||
| width: 100%; | |||||
| min-width: 0; | |||||
| padding: 4px 11px; | |||||
| border: 1px solid #d9d9d9; | |||||
| border-radius: 6px; | |||||
| &:hover { | |||||
| border-color: @primary-color; | |||||
| } | |||||
| &__content { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| width: fit-content; | |||||
| max-width: 100%; | |||||
| min-height: 22px; | |||||
| padding: 0 8px; | |||||
| color: .addAlpha(@text-color, 0.8) []; | |||||
| background-color: rgba(0, 0, 0, 0.06); | |||||
| border-radius: 4px; | |||||
| &__value { | |||||
| .singleLine(); | |||||
| margin-right: 8px; | |||||
| font-size: @font-size-input; | |||||
| line-height: 1.5714285714285714; | |||||
| } | |||||
| &__close-icon { | |||||
| font-size: 10px; | |||||
| &:hover { | |||||
| color: #000; | |||||
| } | |||||
| } | |||||
| } | |||||
| &__placeholder { | |||||
| min-height: 22px; | |||||
| color: rgba(0, 0, 0, 0.25); | |||||
| font-size: @font-size-input; | |||||
| line-height: 1.5714285714285714; | |||||
| } | |||||
| } | |||||
| .parameter-input.parameter-input--large { | |||||
| padding: 10px 11px; | |||||
| font-size: @font-size-input-lg; | |||||
| .parameter-input__placeholder { | |||||
| font-size: @font-size-input-lg; | |||||
| line-height: 1.5; | |||||
| } | |||||
| .parameter-input__content__value { | |||||
| font-size: @font-size-input-lg; | |||||
| line-height: 1.5; | |||||
| } | |||||
| .parameter-input__content__close-icon { | |||||
| font-size: 12px; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,105 @@ | |||||
| import { CloseOutlined } from '@ant-design/icons'; | |||||
| import { Input } from 'antd'; | |||||
| import classNames from 'classnames'; | |||||
| import './index.less'; | |||||
| type ParameterInputData = { | |||||
| value?: any; | |||||
| showValue?: any; | |||||
| fromSelect?: boolean; | |||||
| } & Record<string, any>; | |||||
| interface ParameterInputProps { | |||||
| value?: ParameterInputData; | |||||
| onChange?: (value: ParameterInputData) => void; | |||||
| onClick?: () => void; | |||||
| canInput?: boolean; | |||||
| textArea?: boolean; | |||||
| placeholder?: string; | |||||
| allowClear?: boolean; | |||||
| className?: string; | |||||
| style?: React.CSSProperties; | |||||
| size?: 'middle' | 'small' | 'large'; | |||||
| disabled?: boolean; | |||||
| } | |||||
| function ParameterInput({ | |||||
| value, | |||||
| onChange, | |||||
| onClick, | |||||
| canInput = true, | |||||
| textArea = false, | |||||
| placeholder, | |||||
| allowClear, | |||||
| className, | |||||
| style, | |||||
| size = 'middle', | |||||
| disabled = false, | |||||
| ...rest | |||||
| }: ParameterInputProps) { | |||||
| // console.log('ParameterInput', value); | |||||
| const valueObj = | |||||
| typeof value === 'string' ? { value: value, fromSelect: false, showValue: value } : value; | |||||
| if (valueObj && !valueObj.showValue) { | |||||
| valueObj.showValue = valueObj.value; | |||||
| } | |||||
| const isSelect = valueObj?.fromSelect; | |||||
| const InputComponent = textArea ? Input.TextArea : Input; | |||||
| return ( | |||||
| <> | |||||
| {(isSelect || !canInput) && !disabled ? ( | |||||
| <div | |||||
| className={classNames( | |||||
| 'parameter-input', | |||||
| { 'parameter-input--large': size === 'large' }, | |||||
| className, | |||||
| )} | |||||
| style={style} | |||||
| onClick={onClick} | |||||
| > | |||||
| {valueObj?.showValue ? ( | |||||
| <div className="parameter-input__content"> | |||||
| <span className="parameter-input__content__value">{valueObj?.showValue}</span> | |||||
| <CloseOutlined | |||||
| className="parameter-input__content__close-icon" | |||||
| onClick={() => | |||||
| onChange?.({ | |||||
| ...valueObj, | |||||
| fromSelect: false, | |||||
| value: undefined, | |||||
| showValue: undefined, | |||||
| }) | |||||
| } | |||||
| /> | |||||
| </div> | |||||
| ) : ( | |||||
| <div className="parameter-input__placeholder">{placeholder}</div> | |||||
| )} | |||||
| </div> | |||||
| ) : ( | |||||
| <InputComponent | |||||
| {...rest} | |||||
| size={size} | |||||
| className={className} | |||||
| style={style} | |||||
| placeholder={placeholder} | |||||
| allowClear={allowClear} | |||||
| value={valueObj?.showValue} | |||||
| disabled={disabled} | |||||
| onChange={(e) => | |||||
| onChange?.({ | |||||
| ...valueObj, | |||||
| fromSelect: false, | |||||
| value: e.target.value, | |||||
| showValue: e.target.value, | |||||
| }) | |||||
| } | |||||
| /> | |||||
| )} | |||||
| </> | |||||
| ); | |||||
| } | |||||
| export default ParameterInput; | |||||
| @@ -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> | ||||
| ); | ); | ||||
| } | } | ||||
| @@ -1,6 +1,9 @@ | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import { getComputingResourceReq } from '@/services/pipeline'; | |||||
| import { PipelineNodeModelSerialize } from '@/types'; | import { PipelineNodeModelSerialize } from '@/types'; | ||||
| import { Form, Input, type FormProps } from 'antd'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { Form, Input, Select, type FormProps } from 'antd'; | |||||
| import { useEffect, useState } from 'react'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| const { TextArea } = Input; | const { TextArea } = Input; | ||||
| @@ -10,6 +13,25 @@ type ExperimentParameterProps = { | |||||
| }; | }; | ||||
| function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) { | function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) { | ||||
| const [resourceStandardList, setResourceStandardList] = useState([]); // 资源规模列表 | |||||
| useEffect(() => { | |||||
| 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 controlStrategyList = Object.entries(nodeData.control_strategy ?? {}).map( | const controlStrategyList = Object.entries(nodeData.control_strategy ?? {}).map( | ||||
| ([key, value]) => ({ key, value }), | ([key, value]) => ({ key, value }), | ||||
| @@ -103,7 +125,14 @@ function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) { | |||||
| }, | }, | ||||
| ]} | ]} | ||||
| > | > | ||||
| <Input disabled /> | |||||
| <Select | |||||
| options={resourceStandardList} | |||||
| disabled | |||||
| fieldNames={{ | |||||
| label: 'description', | |||||
| value: 'standard', | |||||
| }} | |||||
| /> | |||||
| </Form.Item> | </Form.Item> | ||||
| <Form.Item label="挂载路径" name="mount_path"> | <Form.Item label="挂载路径" name="mount_path"> | ||||
| <Input disabled /> | <Input disabled /> | ||||
| @@ -117,7 +146,7 @@ function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) { | |||||
| name={['control_strategy', item.key]} | name={['control_strategy', item.key]} | ||||
| label={item.value.label} | label={item.value.label} | ||||
| getValueProps={(e) => { | getValueProps={(e) => { | ||||
| return { value: e.value }; | |||||
| return { value: e.showValue || e.value }; | |||||
| }} | }} | ||||
| > | > | ||||
| <Input disabled /> | <Input disabled /> | ||||
| @@ -133,7 +162,7 @@ function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) { | |||||
| label={item.value.label + '(' + item.key + ')'} | label={item.value.label + '(' + item.key + ')'} | ||||
| rules={[{ required: item.value.require ? true : false }]} | rules={[{ required: item.value.require ? true : false }]} | ||||
| getValueProps={(e) => { | getValueProps={(e) => { | ||||
| return { value: e.value }; | |||||
| return { value: e.showValue || e.value }; | |||||
| }} | }} | ||||
| > | > | ||||
| <Input disabled /> | <Input disabled /> | ||||
| @@ -149,7 +178,7 @@ function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) { | |||||
| label={item.value.label + '(' + item.key + ')'} | label={item.value.label + '(' + item.key + ')'} | ||||
| rules={[{ required: item.value.require ? true : false }]} | rules={[{ required: item.value.require ? true : false }]} | ||||
| getValueProps={(e) => { | getValueProps={(e) => { | ||||
| return { value: e.value }; | |||||
| return { value: e.showValue || e.value }; | |||||
| }} | }} | ||||
| > | > | ||||
| <Input disabled /> | <Input disabled /> | ||||
| @@ -5,8 +5,8 @@ | |||||
| */ | */ | ||||
| import { useStateRef } from '@/hooks'; | import { useStateRef } from '@/hooks'; | ||||
| import { ExperimentLog } from '@/pages/Experiment/experimentText/props'; | |||||
| import { ExperimentStatus } from '@/pages/Experiment/status'; | import { ExperimentStatus } from '@/pages/Experiment/status'; | ||||
| import { ExperimentLog } from '@/pages/Experiment/training/props'; | |||||
| import { getExperimentPodsLog } from '@/services/experiment/index.js'; | import { getExperimentPodsLog } from '@/services/experiment/index.js'; | ||||
| import { DoubleRightOutlined, DownOutlined, UpOutlined } from '@ant-design/icons'; | import { DoubleRightOutlined, DownOutlined, UpOutlined } from '@ant-design/icons'; | ||||
| import { Button } from 'antd'; | import { Button } from 'antd'; | ||||
| @@ -1,5 +1,5 @@ | |||||
| import { ExperimentLog } from '@/pages/Experiment/experimentText/props'; | |||||
| import { ExperimentStatus } from '@/pages/Experiment/status'; | import { ExperimentStatus } from '@/pages/Experiment/status'; | ||||
| import { ExperimentLog } from '@/pages/Experiment/training/props'; | |||||
| import LogGroup from '../LogGroup'; | import LogGroup from '../LogGroup'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -198,7 +198,7 @@ function Experiment() { | |||||
| }; | }; | ||||
| const routeToEdit = (e, record) => { | const routeToEdit = (e, record) => { | ||||
| e.stopPropagation(); | e.stopPropagation(); | ||||
| navgite({ pathname: `/pipeline/pytorchtext/${record.workflow_id}/${record.workflow_name}` }); | |||||
| navgite({ pathname: `/pipeline/template/${record.workflow_id}/${record.workflow_name}` }); | |||||
| }; | }; | ||||
| // 创建或者编辑实验接口请求 | // 创建或者编辑实验接口请求 | ||||
| const handleAddExperiment = async (values) => { | const handleAddExperiment = async (values) => { | ||||
| @@ -255,7 +255,7 @@ function Experiment() { | |||||
| }; | }; | ||||
| const routerToText = (e, item, record) => { | const routerToText = (e, item, record) => { | ||||
| e.stopPropagation(); | e.stopPropagation(); | ||||
| navgite({ pathname: `/pipeline/experimentPytorchtext/${record.workflow_id}/${item.id}` }); | |||||
| navgite({ pathname: `/pipeline/experiment/${record.workflow_id}/${item.id}` }); | |||||
| }; | }; | ||||
| const handleTensorboard = async (experimentIn) => { | const handleTensorboard = async (experimentIn) => { | ||||
| @@ -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', | ||||
| @@ -27,5 +27,7 @@ | |||||
| width: 100%; | width: 100%; | ||||
| height: calc(100% - 56px); | height: calc(100% - 56px); | ||||
| background-color: @background-color; | background-color: @background-color; | ||||
| background-image: url(/assets/images/pipeline-canvas-back.png); | |||||
| background-size: 100% 100%; | |||||
| } | } | ||||
| } | } | ||||
| @@ -11,13 +11,17 @@ 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'; | ||||
| import { omit } from 'lodash'; | import { omit } from 'lodash'; | ||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import styles from './create.less'; | |||||
| import styles from './index.less'; | |||||
| type FormData = { | type FormData = { | ||||
| name: string; | name: string; | ||||
| @@ -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); | |||||
| }; | |||||
| }, []); | }, []); | ||||
| // 创建公网、本地镜像 | // 创建公网、本地镜像 | ||||
| @@ -34,8 +34,8 @@ import { | |||||
| } from 'antd'; | } from 'antd'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import MirrorStatusCell from './components/MirrorStatusCell'; | |||||
| import styles from './info.less'; | |||||
| import MirrorStatusCell from '../components/MirrorStatusCell'; | |||||
| import styles from './index.less'; | |||||
| type MirrorInfoData = { | type MirrorInfoData = { | ||||
| name?: string; | name?: string; | ||||
| @@ -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 { | ||||
| @@ -27,7 +28,7 @@ import { | |||||
| import { type SearchProps } from 'antd/es/input'; | import { type SearchProps } from 'antd/es/input'; | ||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||
| import styles from './list.less'; | |||||
| import styles from './index.less'; | |||||
| const mirrorTabItems = [ | const mirrorTabItems = [ | ||||
| { | { | ||||
| @@ -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, | ||||
| @@ -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'], | ||||
| @@ -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; | |||||
| }; | |||||
| @@ -1,20 +1,29 @@ | |||||
| .form_item_block { | |||||
| .form-item { | |||||
| position: relative; | position: relative; | ||||
| padding-top: 40px; | padding-top: 40px; | ||||
| border-bottom: 1px dashed rgba(20, 49, 179, 0.12); | border-bottom: 1px dashed rgba(20, 49, 179, 0.12); | ||||
| &__delete-button { | |||||
| position: absolute; | |||||
| top: 5px; | |||||
| right: 24px; | |||||
| } | |||||
| :global { | |||||
| .anticon.anticon-question-circle { | |||||
| margin-top: -14px; | |||||
| } | |||||
| } | |||||
| } | } | ||||
| .delete_button { | |||||
| position: absolute; | |||||
| top: 5px; | |||||
| right: 0; | |||||
| } | |||||
| .add_button_form_item { | |||||
| .form-item-add { | |||||
| margin-top: 15px; | margin-top: 15px; | ||||
| &:first-child { | &:first-child { | ||||
| margin-top: 0; | margin-top: 0; | ||||
| } | } | ||||
| } | |||||
| .add_button_form_item .add_button { | |||||
| padding: 0; | |||||
| &__add-button { | |||||
| padding: 0; | |||||
| } | |||||
| } | } | ||||
| @@ -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'; | ||||
| @@ -55,14 +56,14 @@ const GlobalParamsDrawer = forwardRef( | |||||
| getContainer={false} | getContainer={false} | ||||
| onClose={onClose} | onClose={onClose} | ||||
| open={open} | open={open} | ||||
| width={420} | |||||
| width={520} | |||||
| > | > | ||||
| <Form | <Form | ||||
| name="global_params_form" | name="global_params_form" | ||||
| autoComplete="off" | autoComplete="off" | ||||
| form={form} | form={form} | ||||
| labelCol={{ span: 7 }} | |||||
| wrapperCol={{ span: 17 }} | |||||
| labelCol={{ span: 5 }} | |||||
| wrapperCol={{ span: 19 }} | |||||
| initialValues={{ global_param: globalParam }} | initialValues={{ global_param: globalParam }} | ||||
| labelAlign="left" | labelAlign="left" | ||||
| > | > | ||||
| @@ -70,7 +71,7 @@ const GlobalParamsDrawer = forwardRef( | |||||
| {(fields, { add, remove }) => ( | {(fields, { add, remove }) => ( | ||||
| <> | <> | ||||
| {fields.map(({ key, name, ...restField }) => ( | {fields.map(({ key, name, ...restField }) => ( | ||||
| <div key={key} className={styles.form_item_block}> | |||||
| <div key={key} className={styles['form-item']}> | |||||
| <Form.Item | <Form.Item | ||||
| {...restField} | {...restField} | ||||
| name={[name, 'param_name']} | name={[name, 'param_name']} | ||||
| @@ -140,17 +141,17 @@ const GlobalParamsDrawer = forwardRef( | |||||
| </Form.Item> | </Form.Item> | ||||
| <Tooltip title="删除参数"> | <Tooltip title="删除参数"> | ||||
| <Button | <Button | ||||
| className={styles.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> | ||||
| ))} | ))} | ||||
| <Form.Item className={styles.add_button_form_item}> | |||||
| <Form.Item className={styles['form-item-add']}> | |||||
| <Button | <Button | ||||
| className={styles.add_button} | |||||
| className={styles['form-item-add__add-button']} | |||||
| type="link" | type="link" | ||||
| onClick={() => add()} | onClick={() => add()} | ||||
| icon={<PlusOutlined />} | icon={<PlusOutlined />} | ||||
| @@ -1,4 +1,4 @@ | |||||
| import { Button, Dropdown, type MenuProps } from 'antd'; | |||||
| import { Dropdown, type MenuProps } from 'antd'; | |||||
| import { useEffect } from 'react'; | import { useEffect } from 'react'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| @@ -22,20 +22,18 @@ function PropsLabel({ title, menuItems, onClick }: PropsLabelProps) { | |||||
| return ( | return ( | ||||
| <div className={styles['props-label']}> | <div className={styles['props-label']}> | ||||
| <span>{title}</span> | |||||
| <div>{title}</div> | |||||
| <Dropdown | <Dropdown | ||||
| menu={{ | menu={{ | ||||
| items: menuItems, | items: menuItems, | ||||
| onClick: handleItemClick, | onClick: handleItemClick, | ||||
| triggerSubMenuAction: 'click', | |||||
| triggerSubMenuAction: 'hover', | |||||
| }} | }} | ||||
| trigger={['click']} | trigger={['click']} | ||||
| placement="topRight" | placement="topRight" | ||||
| arrow | arrow | ||||
| > | > | ||||
| <Button size="small" type="link"> | |||||
| 参数 | |||||
| </Button> | |||||
| <a onClick={(e) => e.preventDefault()}>参数</a> | |||||
| </Dropdown> | </Dropdown> | ||||
| </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> | ||||
| @@ -1,80 +0,0 @@ | |||||
| #graph { | |||||
| position: relative; | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| } | |||||
| .editPipelineProps { | |||||
| :global { | |||||
| label { | |||||
| width: 100%; | |||||
| &::after { | |||||
| display: none; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| .editPipelinePropsContent { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| width: 100%; | |||||
| height: 43px; | |||||
| margin-bottom: 20px; | |||||
| padding: 0 24px; | |||||
| color: #1d1d20; | |||||
| font-size: 15px; | |||||
| font-family: 'Alibaba'; | |||||
| background: #f8fbff; | |||||
| } | |||||
| .centerContainer { | |||||
| display: flex; | |||||
| flex: 1; | |||||
| flex-direction: column; | |||||
| } | |||||
| .buttonList { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: end; | |||||
| width: 100%; | |||||
| height: 45px; | |||||
| padding: 0 30px; | |||||
| background: #ffffff; | |||||
| box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); | |||||
| } | |||||
| .rightmenu { | |||||
| position: absolute; | |||||
| top: 0px; | |||||
| left: 0px; | |||||
| width: 120px; | |||||
| height: 146px; | |||||
| overflow-y: auto; | |||||
| color: #333333; | |||||
| font-size: 12px; | |||||
| background-color: #ffffff; | |||||
| } | |||||
| .rightmenuItem { | |||||
| padding: 10px 20px; | |||||
| cursor: pointer; | |||||
| } | |||||
| .rightmenuItem:hover { | |||||
| color: #ffffff; | |||||
| background-color: rgba(24, 144, 255, 0.3); | |||||
| } | |||||
| .ref-row { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| .select-button { | |||||
| display: flex; | |||||
| flex: none; | |||||
| align-items: center; | |||||
| justify-content: flex-start; | |||||
| width: 100px; | |||||
| margin-left: 10px; | |||||
| padding-right: 0; | |||||
| padding-left: 0; | |||||
| } | |||||
| } | |||||
| @@ -2,14 +2,13 @@ import KFIcon from '@/components/KFIcon'; | |||||
| import { useStateRef, useVisible } from '@/hooks'; | import { useStateRef, useVisible } from '@/hooks'; | ||||
| import { getWorkflowById, saveWorkflow } from '@/services/pipeline/index.js'; | import { getWorkflowById, saveWorkflow } from '@/services/pipeline/index.js'; | ||||
| import { to } from '@/utils/promise'; | import { to } from '@/utils/promise'; | ||||
| import { useEmotionCss } from '@ant-design/use-emotion-css'; | |||||
| import G6 from '@antv/g6'; | import G6 from '@antv/g6'; | ||||
| import { Button, message } from 'antd'; | import { Button, message } from 'antd'; | ||||
| import { useEffect, useRef } from 'react'; | import { useEffect, useRef } from 'react'; | ||||
| import { useNavigate, useParams } from 'react-router-dom'; | import { useNavigate, useParams } from 'react-router-dom'; | ||||
| import { s8 } from '../../../utils'; | import { s8 } from '../../../utils'; | ||||
| import GlobalParamsDrawer from '../components/GlobalParamsDrawer'; | import GlobalParamsDrawer from '../components/GlobalParamsDrawer'; | ||||
| import Styles from './editPipeline.less'; | |||||
| import styles from './index.less'; | |||||
| import ModelMenus from './modelMenus'; | import ModelMenus from './modelMenus'; | ||||
| import Props from './props'; | import Props from './props'; | ||||
| import { findAllParentNodes, findFirstDuplicate } from './utils'; | import { findAllParentNodes, findFirstDuplicate } from './utils'; | ||||
| @@ -20,42 +19,6 @@ const EditPipeline = () => { | |||||
| const navgite = useNavigate(); | const navgite = useNavigate(); | ||||
| let contextMenu = {}; | let contextMenu = {}; | ||||
| const locationParams = useParams(); //新版本获取路由参数接口 | const locationParams = useParams(); //新版本获取路由参数接口 | ||||
| const pipelineContainer = useEmotionCss(() => { | |||||
| return { | |||||
| display: 'flex', | |||||
| backgroundColor: '#fff', | |||||
| height: '98vh', | |||||
| position: 'relative', | |||||
| }; | |||||
| }); | |||||
| const rightmenu = useEmotionCss(() => { | |||||
| return { | |||||
| position: 'absolute', | |||||
| width: '120px', | |||||
| height: '146px', | |||||
| left: '0px', | |||||
| top: '0px', | |||||
| color: '#333333', | |||||
| overflowY: 'auto', | |||||
| }; | |||||
| }); | |||||
| const rightmenuItem = useEmotionCss(() => { | |||||
| return { | |||||
| padding: '10px 20px', | |||||
| cursor: 'pointer', | |||||
| fontSize: '12px', | |||||
| }; | |||||
| }); | |||||
| const graphStyle = useEmotionCss(() => { | |||||
| return { | |||||
| width: '100%', | |||||
| backgroundSize: '100% 100%', | |||||
| backgroundImage: 'url(/assets/images/pipeline-canvas-back.png)', | |||||
| flex: 1, | |||||
| }; | |||||
| }); | |||||
| const graphRef = useRef(); | const graphRef = useRef(); | ||||
| const paramsDrawerRef = useRef(); | const paramsDrawerRef = useRef(); | ||||
| const propsRef = useRef(); | const propsRef = useRef(); | ||||
| @@ -65,7 +28,7 @@ const EditPipeline = () => { | |||||
| let sourceAnchorIdx, targetAnchorIdx; | let sourceAnchorIdx, targetAnchorIdx; | ||||
| const onDragEnd = (val) => { | const onDragEnd = (val) => { | ||||
| console.log(val, 'eee'); | |||||
| console.log(val); | |||||
| const _x = val.x; | const _x = val.x; | ||||
| const _y = val.y; | const _y = val.y; | ||||
| const point = graph.getPointByClient(_x, _y); | const point = graph.getPointByClient(_x, _y); | ||||
| @@ -78,10 +41,8 @@ const EditPipeline = () => { | |||||
| id: val.component_name + '-' + s8(), | id: val.component_name + '-' + s8(), | ||||
| isCluster: false, | isCluster: false, | ||||
| }; | }; | ||||
| console.log(graph, model); | |||||
| console.log('model', model); | |||||
| graph.addItem('node', model, true); | graph.addItem('node', model, true); | ||||
| console.log(graph); | |||||
| }; | }; | ||||
| const formChange = (val) => { | const formChange = (val) => { | ||||
| if (graph) { | if (graph) { | ||||
| @@ -110,7 +71,6 @@ const EditPipeline = () => { | |||||
| } | } | ||||
| const [propsRes, propsError] = await to(propsRef.current.getFieldsValue()); | const [propsRes, propsError] = await to(propsRef.current.getFieldsValue()); | ||||
| console.log(await to(propsRef.current.getFieldsValue())); | |||||
| if (propsError) { | if (propsError) { | ||||
| message.error('基本信息必填项需配置'); | message.error('基本信息必填项需配置'); | ||||
| return; | return; | ||||
| @@ -147,7 +107,6 @@ const EditPipeline = () => { | |||||
| } | } | ||||
| }; | }; | ||||
| const getGraphData = (data) => { | const getGraphData = (data) => { | ||||
| console.log('graph', graph); | |||||
| if (graph) { | if (graph) { | ||||
| console.log(data); | console.log(data); | ||||
| graph.data(data); | graph.data(data); | ||||
| @@ -230,6 +189,7 @@ const EditPipeline = () => { | |||||
| } | } | ||||
| } | } | ||||
| // eslint-disable-next-line | |||||
| for (const key in edgeMap) { | for (const key in edgeMap) { | ||||
| const arcEdges = edgeMap[key]; | const arcEdges = edgeMap[key]; | ||||
| const { length } = arcEdges; | const { length } = arcEdges; | ||||
| @@ -472,7 +432,7 @@ const EditPipeline = () => { | |||||
| height: graphRef.current.clientHeight || '100%', | height: graphRef.current.clientHeight || '100%', | ||||
| animate: false, | animate: false, | ||||
| groupByTypes: false, | groupByTypes: false, | ||||
| fitView: true, | |||||
| fitView: false, | |||||
| plugins: [contextMenu], | plugins: [contextMenu], | ||||
| enabledStack: true, | enabledStack: true, | ||||
| modes: { | modes: { | ||||
| @@ -730,10 +690,10 @@ const EditPipeline = () => { | |||||
| }; | }; | ||||
| }; | }; | ||||
| return ( | return ( | ||||
| <div className={pipelineContainer}> | |||||
| <div className={styles['pipeline-container']}> | |||||
| <ModelMenus onParDragEnd={onDragEnd}></ModelMenus> | <ModelMenus onParDragEnd={onDragEnd}></ModelMenus> | ||||
| <div className={Styles.centerContainer}> | |||||
| <div className={Styles.buttonList}> | |||||
| <div className={styles['pipeline-container__workflow']}> | |||||
| <div className={styles['pipeline-container__workflow__top']}> | |||||
| <Button | <Button | ||||
| type="default" | type="default" | ||||
| icon={<KFIcon type="icon-quanjucanshu" />} | icon={<KFIcon type="icon-quanjucanshu" />} | ||||
| @@ -768,7 +728,7 @@ const EditPipeline = () => { | |||||
| 保存并返回 | 保存并返回 | ||||
| </Button> | </Button> | ||||
| </div> | </div> | ||||
| <div className={graphStyle} ref={graphRef} id={Styles.graphStyle}></div> | |||||
| <div className={styles['pipeline-container__workflow__graph']} ref={graphRef}></div> | |||||
| </div> | </div> | ||||
| <Props ref={propsRef} onParentChange={formChange}></Props> | <Props ref={propsRef} onParentChange={formChange}></Props> | ||||
| <GlobalParamsDrawer | <GlobalParamsDrawer | ||||
| @@ -0,0 +1,29 @@ | |||||
| .pipeline-container { | |||||
| display: flex; | |||||
| height: 100%; | |||||
| background-color: #fff; | |||||
| &__workflow { | |||||
| flex: 1; | |||||
| height: 100%; | |||||
| &__top { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| justify-content: end; | |||||
| width: 100%; | |||||
| height: 45px; | |||||
| padding: 0 30px; | |||||
| background: #ffffff; | |||||
| box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); | |||||
| } | |||||
| &__graph { | |||||
| width: 100%; | |||||
| height: calc(100% - 45px); | |||||
| background-color: @background-color; | |||||
| background-image: url(/assets/images/pipeline-canvas-back.png); | |||||
| background-size: 100% 100%; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -24,7 +24,7 @@ const ModelMenus = ({ onParDragEnd }) => { | |||||
| }; | }; | ||||
| const { Panel } = Collapse; | const { Panel } = Collapse; | ||||
| return ( | return ( | ||||
| <div style={{ width: '250px', height: '99%' }} className={Styles.collapse}> | |||||
| <div className={Styles.collapse}> | |||||
| <div className={Styles.modelMenusTitle}>组件库</div> | <div className={Styles.modelMenusTitle}>组件库</div> | ||||
| {modelMenusList && modelMenusList.length > 0 ? ( | {modelMenusList && modelMenusList.length > 0 ? ( | ||||
| <Collapse | <Collapse | ||||
| @@ -1,35 +1,24 @@ | |||||
| .collapseList { | |||||
| } | |||||
| .collapseItem { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| height: 40px; | |||||
| padding: 0 16px; | |||||
| color: #575757; | |||||
| font-size: 14px; | |||||
| border-radius: 4px; | |||||
| cursor: pointer; | |||||
| } | |||||
| .collapseItem:hover { | |||||
| background: rgba(22, 100, 255, 0.08); | |||||
| color:#1664ff; | |||||
| } | |||||
| .collapse { | .collapse { | ||||
| width: 250px; | |||||
| height: 100%; | |||||
| :global { | :global { | ||||
| .ant-collapse { | .ant-collapse { | ||||
| height: calc(100% - 60px); | |||||
| overflow-y: auto; | |||||
| background-color: #fff; | background-color: #fff; | ||||
| border-color: transparent !important; | border-color: transparent !important; | ||||
| } | } | ||||
| .ant-collapse > .ant-collapse-item > .ant-collapse-header { | .ant-collapse > .ant-collapse-item > .ant-collapse-header { | ||||
| margin-bottom: 5px; | margin-bottom: 5px; | ||||
| padding: 20px 16px 15px 16px; | |||||
| background-color: #fff; | background-color: #fff; | ||||
| border-color: transparent; | border-color: transparent; | ||||
| padding: 20px 16px 15px 16px; | |||||
| } | } | ||||
| .ant-collapse > .ant-collapse-item { | .ant-collapse > .ant-collapse-item { | ||||
| margin: 0 10px; | margin: 0 10px; | ||||
| border-bottom:0.5px dashed rgba(20, 49, 179, 0.12); | |||||
| border-bottom: 0.5px dashed rgba(20, 49, 179, 0.12); | |||||
| border-radius: 0px; | border-radius: 0px; | ||||
| } | } | ||||
| .ant-collapse .ant-collapse-content { | .ant-collapse .ant-collapse-content { | ||||
| @@ -41,10 +30,23 @@ | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| .modelMenusTitle{ | |||||
| padding: 12px 25px; | |||||
| .collapseItem { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| height: 40px; | |||||
| padding: 0 16px; | |||||
| color: #575757; | |||||
| font-size: 14px; | |||||
| border-radius: 4px; | |||||
| cursor: pointer; | |||||
| } | |||||
| .collapseItem:hover { | |||||
| color: #1664ff; | |||||
| background: rgba(22, 100, 255, 0.08); | |||||
| } | |||||
| .modelMenusTitle { | |||||
| margin-bottom: 10px; | margin-bottom: 10px; | ||||
| color:#111111; | |||||
| font-size:16px; | |||||
| font-family: 'Alibaba'; | |||||
| } | |||||
| padding: 12px 25px; | |||||
| color: #111111; | |||||
| font-size: 16px; | |||||
| } | |||||
| @@ -1,4 +1,5 @@ | |||||
| import KFIcon from '@/components/KFIcon'; | import KFIcon from '@/components/KFIcon'; | ||||
| import ParameterInput from '@/components/ParameterInput'; | |||||
| import SubAreaTitle from '@/components/SubAreaTitle'; | import SubAreaTitle from '@/components/SubAreaTitle'; | ||||
| import { getComputingResourceReq } from '@/services/pipeline'; | import { getComputingResourceReq } from '@/services/pipeline'; | ||||
| import { openAntdModal } from '@/utils/modal'; | import { openAntdModal } from '@/utils/modal'; | ||||
| @@ -8,8 +9,8 @@ import { pick } from 'lodash'; | |||||
| import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; | import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; | ||||
| import PropsLabel from '../components/PropsLabel'; | import PropsLabel from '../components/PropsLabel'; | ||||
| import ResourceSelectorModal, { ResourceSelectorType } from '../components/ResourceSelectorModal'; | import ResourceSelectorModal, { ResourceSelectorType } from '../components/ResourceSelectorModal'; | ||||
| import styles from './editPipeline.less'; | |||||
| import { createMenuItems } from './utils'; | |||||
| import styles from './props.less'; | |||||
| import { canInput, createMenuItems } from './utils'; | |||||
| const { TextArea } = Input; | const { TextArea } = Input; | ||||
| const Props = forwardRef(({ onParentChange }, ref) => { | const Props = forwardRef(({ onParentChange }, ref) => { | ||||
| @@ -40,7 +41,7 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| const afterOpenChange = () => { | const afterOpenChange = () => { | ||||
| if (!open) { | if (!open) { | ||||
| console.log('zzzz', form.getFieldsValue()); | |||||
| console.log('zzzzz', form.getFieldsValue()); | |||||
| const control_strategy = form.getFieldValue('control_strategy'); | const control_strategy = form.getFieldValue('control_strategy'); | ||||
| const in_parameters = form.getFieldValue('in_parameters'); | const in_parameters = form.getFieldValue('in_parameters'); | ||||
| const out_parameters = form.getFieldValue('out_parameters'); | const out_parameters = form.getFieldValue('out_parameters'); | ||||
| @@ -77,6 +78,7 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| out_parameters: JSON.parse(model.out_parameters), | out_parameters: JSON.parse(model.out_parameters), | ||||
| control_strategy: JSON.parse(model.control_strategy), | control_strategy: JSON.parse(model.control_strategy), | ||||
| }; | }; | ||||
| console.log('model', nodeData); | |||||
| setStagingItem({ | setStagingItem({ | ||||
| ...nodeData, | ...nodeData, | ||||
| }); | }); | ||||
| @@ -95,11 +97,11 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| } | } | ||||
| }, | }, | ||||
| propClose: () => { | propClose: () => { | ||||
| close(); | |||||
| onClose(); | |||||
| }, | }, | ||||
| })); | })); | ||||
| // 选择数据集、模型 | |||||
| // 选择数据集、模型、镜像 | |||||
| const selectResource = (name, item) => { | const selectResource = (name, item) => { | ||||
| let type; | let type; | ||||
| let resource; | let resource; | ||||
| @@ -128,19 +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); | ||||
| form.setFieldValue(name, { ...item, value }); | |||||
| } | |||||
| const showValue = `${res.name}:${res.version}`; | |||||
| form.setFieldValue(name, { ...item, value, showValue, fromSelect: true }); | |||||
| if (type === ResourceSelectorType.Dataset) { | |||||
| setSelectedDataset(res); | |||||
| } else if (type === ResourceSelectorType.Model) { | |||||
| setSelectedModel(res); | |||||
| if (type === ResourceSelectorType.Dataset) { | |||||
| setSelectedDataset(res); | |||||
| } else if (type === ResourceSelectorType.Model) { | |||||
| setSelectedModel(res); | |||||
| } | |||||
| } | } | ||||
| } else { | } 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, ''); | ||||
| } | } | ||||
| @@ -188,275 +191,276 @@ const Props = forwardRef(({ onParentChange }, ref) => { | |||||
| ); | ); | ||||
| return ( | return ( | ||||
| <> | |||||
| <Drawer | |||||
| title="编辑任务" | |||||
| placement="right" | |||||
| rootStyle={{ marginTop: '45px' }} | |||||
| getContainer={false} | |||||
| closeIcon={false} | |||||
| onClose={onClose} | |||||
| afterOpenChange={afterOpenChange} | |||||
| open={open} | |||||
| width={520} | |||||
| className={styles.editPipelineProps} | |||||
| <Drawer | |||||
| title="编辑任务" | |||||
| placement="right" | |||||
| rootStyle={{ marginTop: '45px' }} | |||||
| getContainer={false} | |||||
| closeIcon={false} | |||||
| onClose={onClose} | |||||
| afterOpenChange={afterOpenChange} | |||||
| open={open} | |||||
| width={520} | |||||
| className={styles['pipeline-drawer']} | |||||
| > | |||||
| <Form | |||||
| name="form" | |||||
| form={form} | |||||
| layout="vertical" | |||||
| labelCol={{ | |||||
| span: 24, | |||||
| }} | |||||
| wrapperCol={{ | |||||
| span: 24, | |||||
| }} | |||||
| style={{ | |||||
| maxWidth: 600, | |||||
| }} | |||||
| autoComplete="off" | |||||
| > | > | ||||
| <Form | |||||
| name="form" | |||||
| form={form} | |||||
| layout="vertical" | |||||
| labelCol={{ | |||||
| span: 24, | |||||
| }} | |||||
| wrapperCol={{ | |||||
| span: 24, | |||||
| }} | |||||
| style={{ | |||||
| maxWidth: 600, | |||||
| }} | |||||
| autoComplete="off" | |||||
| <div className={styles['pipeline-drawer__title']}> | |||||
| <SubAreaTitle image="/assets/images/static-message.png" title="基本信息"></SubAreaTitle> | |||||
| </div> | |||||
| <Form.Item | |||||
| label="任务名称" | |||||
| name="label" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入任务名称', | |||||
| }, | |||||
| ]} | |||||
| > | > | ||||
| <div className={styles.editPipelinePropsContent}> | |||||
| <SubAreaTitle image="/assets/images/static-message.png" title="基本信息"></SubAreaTitle> | |||||
| </div> | |||||
| <Form.Item | |||||
| label="任务名称" | |||||
| name="label" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入任务名称', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input placeholder="请输入任务名称" allowClear /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="任务ID" | |||||
| name="id" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入任务id', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input disabled /> | |||||
| </Form.Item> | |||||
| <div className={styles.editPipelinePropsContent}> | |||||
| <SubAreaTitle image="/assets/images/duty-message.png" title="任务信息"></SubAreaTitle> | |||||
| <Input placeholder="请输入任务名称" allowClear /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="任务ID" | |||||
| name="id" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请输入任务id', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Input disabled /> | |||||
| </Form.Item> | |||||
| <div className={styles['pipeline-drawer__title']}> | |||||
| <SubAreaTitle image="/assets/images/duty-message.png" title="任务信息"></SubAreaTitle> | |||||
| </div> | |||||
| <Form.Item label="镜像" required> | |||||
| <div className={styles['pipeline-drawer__ref-row']}> | |||||
| <Form.Item name="image" noStyle rules={[{ required: true, message: '请输入镜像' }]}> | |||||
| <Input placeholder="请输入或选择镜像" allowClear /> | |||||
| </Form.Item> | |||||
| <Form.Item noStyle> | |||||
| <Button | |||||
| type="link" | |||||
| size="small" | |||||
| icon={getSelectBtnIcon({ item_type: 'image' })} | |||||
| onClick={() => selectResource('image', { item_type: 'image' })} | |||||
| className={styles['pipeline-drawer__ref-row__select-button']} | |||||
| > | |||||
| 选择镜像 | |||||
| </Button> | |||||
| </Form.Item> | |||||
| </div> | </div> | ||||
| <Form.Item label="镜像" required> | |||||
| <div className={styles['ref-row']}> | |||||
| <Form.Item name="image" noStyle rules={[{ required: true, message: '请输入镜像' }]}> | |||||
| <Input placeholder="请输入或选择镜像" allowClear /> | |||||
| </Form.Item> | |||||
| <Form.Item noStyle> | |||||
| <Button | |||||
| type="link" | |||||
| icon={getSelectBtnIcon({ item_type: 'image' })} | |||||
| onClick={() => selectResource('image', { item_type: 'image' })} | |||||
| className={styles['select-button']} | |||||
| > | |||||
| 选择镜像 | |||||
| </Button> | |||||
| </Form.Item> | |||||
| </div> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| name="working_directory" | |||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title="工作目录" | |||||
| onClick={(value) => { | |||||
| handleParameterClick('working_directory', value); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| > | |||||
| <Input placeholder="请输入工作目录" allowClear /> | |||||
| </Form.Item> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| name="working_directory" | |||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title="工作目录" | |||||
| onClick={(value) => { | |||||
| handleParameterClick('working_directory', value); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| > | |||||
| <Input placeholder="请输入工作目录" allowClear /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| name="command" | |||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title="启动命令" | |||||
| onClick={(value) => { | |||||
| handleParameterClick('command', value); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| > | |||||
| <TextArea placeholder="请输入启动命令" allowClear /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="资源规格" | |||||
| name="resources_standard" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请选择资源规格', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Select | |||||
| showSearch | |||||
| placeholder="请选择资源规格" | |||||
| filterOption={filterResourceStandard} | |||||
| options={resourceStandardList} | |||||
| fieldNames={{ | |||||
| label: 'description', | |||||
| value: 'standard', | |||||
| }} | |||||
| /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| name="mount_path" | |||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title="挂载路径" | |||||
| onClick={(value) => { | |||||
| handleParameterClick('mount_path', value); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| > | |||||
| <Input placeholder="请输入挂载路径" allowClear /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| name="env_variables" | |||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title="环境变量" | |||||
| onClick={(value) => { | |||||
| handleParameterClick('env_variables', value); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| > | |||||
| <TextArea placeholder="请输入环境变量" allowClear /> | |||||
| </Form.Item> | |||||
| {controlStrategyList.map((item) => ( | |||||
| <Form.Item | <Form.Item | ||||
| name="command" | |||||
| key={item.key} | |||||
| name={['control_strategy', item.key]} | |||||
| label={ | label={ | ||||
| <PropsLabel | <PropsLabel | ||||
| menuItems={menuItems} | menuItems={menuItems} | ||||
| title="启动命令" | |||||
| title={item.value.label} | |||||
| onClick={(value) => { | onClick={(value) => { | ||||
| handleParameterClick('command', value); | |||||
| handleParameterClick(['control_strategy', item.key], { | |||||
| ...item.value, | |||||
| value, | |||||
| fromSelect: true, | |||||
| showValue: value, | |||||
| }); | |||||
| }} | }} | ||||
| /> | /> | ||||
| } | } | ||||
| // getValueProps={(e) => { | |||||
| // return { value: e.value }; | |||||
| // }} | |||||
| // getValueFromEvent={(e) => { | |||||
| // return { | |||||
| // ...item.value, | |||||
| // value: e.target.value, | |||||
| // }; | |||||
| // }} | |||||
| > | > | ||||
| <TextArea placeholder="请输入启动命令" allowClear /> | |||||
| <ParameterInput placeholder={item.value.placeholder} allowClear></ParameterInput> | |||||
| </Form.Item> | </Form.Item> | ||||
| ))} | |||||
| <div className={styles['pipeline-drawer__title']}> | |||||
| <SubAreaTitle image="/assets/images/duty-message.png" title="输入参数"></SubAreaTitle> | |||||
| </div> | |||||
| {inParametersList.map((item) => ( | |||||
| <Form.Item | <Form.Item | ||||
| label="资源规格" | |||||
| name="resources_standard" | |||||
| rules={[ | |||||
| { | |||||
| required: true, | |||||
| message: '请选择资源规格', | |||||
| }, | |||||
| ]} | |||||
| > | |||||
| <Select | |||||
| showSearch | |||||
| placeholder="请选择资源规格" | |||||
| filterOption={filterResourceStandard} | |||||
| options={resourceStandardList} | |||||
| fieldNames={{ | |||||
| label: 'description', | |||||
| value: 'standard', | |||||
| }} | |||||
| /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| name="mount_path" | |||||
| key={item.key} | |||||
| label={ | label={ | ||||
| <PropsLabel | <PropsLabel | ||||
| menuItems={menuItems} | menuItems={menuItems} | ||||
| title="挂载路径" | |||||
| title={item.value.label + '(' + item.key + ')'} | |||||
| onClick={(value) => { | onClick={(value) => { | ||||
| handleParameterClick('mount_path', value); | |||||
| handleParameterClick(['in_parameters', item.key], { | |||||
| ...item.value, | |||||
| value, | |||||
| fromSelect: true, | |||||
| showValue: value, | |||||
| }); | |||||
| }} | }} | ||||
| /> | /> | ||||
| } | } | ||||
| required={item.value.require ? true : false} | |||||
| > | > | ||||
| <Input placeholder="请输入挂载路径" allowClear /> | |||||
| <div className={styles['pipeline-drawer__ref-row']}> | |||||
| <Form.Item | |||||
| name={['in_parameters', item.key]} | |||||
| noStyle | |||||
| rules={[{ required: item.value.require ? true : false }]} | |||||
| > | |||||
| <ParameterInput | |||||
| placeholder={item.value.placeholder} | |||||
| canInput={canInput(item.value)} | |||||
| allowClear | |||||
| ></ParameterInput> | |||||
| </Form.Item> | |||||
| {item.value.type === 'ref' && ( | |||||
| <Form.Item noStyle> | |||||
| <Button | |||||
| size="small" | |||||
| type="link" | |||||
| icon={getSelectBtnIcon(item.value)} | |||||
| onClick={() => selectResource(['in_parameters', item.key], item.value)} | |||||
| className={styles['pipeline-drawer__ref-row__select-button']} | |||||
| > | |||||
| {item.value.label} | |||||
| </Button> | |||||
| </Form.Item> | |||||
| )} | |||||
| </div> | |||||
| </Form.Item> | </Form.Item> | ||||
| ))} | |||||
| <div className={styles['pipeline-drawer__title']}> | |||||
| <SubAreaTitle image="/assets/images/duty-message.png" title="输出参数"></SubAreaTitle> | |||||
| </div> | |||||
| {outParametersList.map((item) => ( | |||||
| <Form.Item | <Form.Item | ||||
| name="env_variables" | |||||
| key={item.key} | |||||
| name={['out_parameters', item.key]} | |||||
| label={ | label={ | ||||
| <PropsLabel | <PropsLabel | ||||
| menuItems={menuItems} | menuItems={menuItems} | ||||
| title="环境变量" | |||||
| title={item.value.label + '(' + item.key + ')'} | |||||
| onClick={(value) => { | onClick={(value) => { | ||||
| handleParameterClick('env_variables', value); | |||||
| handleParameterClick(['out_parameters', item.key], { | |||||
| ...item.value, | |||||
| value, | |||||
| fromSelect: true, | |||||
| showValue: value, | |||||
| }); | |||||
| }} | }} | ||||
| /> | /> | ||||
| } | } | ||||
| rules={[{ required: item.value.require ? true : false }]} | |||||
| // getValueProps={(e) => { | |||||
| // return { value: e.value }; | |||||
| // }} | |||||
| // getValueFromEvent={(e) => { | |||||
| // return { | |||||
| // ...item.value, | |||||
| // value: e.target.value, | |||||
| // }; | |||||
| // }} | |||||
| > | > | ||||
| <TextArea placeholder="请输入环境变量" allowClear /> | |||||
| <ParameterInput placeholder={item.value.placeholder} allowClear></ParameterInput> | |||||
| </Form.Item> | </Form.Item> | ||||
| {controlStrategyList.map((item) => ( | |||||
| <Form.Item | |||||
| key={item.key} | |||||
| name={['control_strategy', item.key]} | |||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title={item.value.label} | |||||
| onClick={(value) => { | |||||
| handleParameterClick(['control_strategy', item.key], { | |||||
| ...item.value, | |||||
| value, | |||||
| }); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| getValueProps={(e) => { | |||||
| return { value: e.value }; | |||||
| }} | |||||
| getValueFromEvent={(e) => { | |||||
| return { | |||||
| ...item.value, | |||||
| value: e.target.value, | |||||
| }; | |||||
| }} | |||||
| > | |||||
| <Input placeholder={item.value.label} allowClear /> | |||||
| </Form.Item> | |||||
| ))} | |||||
| <div className={styles.editPipelinePropsContent}> | |||||
| <SubAreaTitle image="/assets/images/duty-message.png" title="输入参数"></SubAreaTitle> | |||||
| </div> | |||||
| {inParametersList.map((item) => ( | |||||
| <Form.Item | |||||
| key={item.key} | |||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title={item.value.label + '(' + item.key + ')'} | |||||
| onClick={(value) => { | |||||
| handleParameterClick(['in_parameters', item.key], { | |||||
| ...item.value, | |||||
| value, | |||||
| }); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| required={item.value.require ? true : false} | |||||
| > | |||||
| <div className={styles['ref-row']}> | |||||
| <Form.Item | |||||
| name={['in_parameters', item.key]} | |||||
| noStyle | |||||
| rules={[{ required: item.value.require ? true : false }]} | |||||
| getValueProps={(e) => { | |||||
| return { value: e.value }; | |||||
| }} | |||||
| getValueFromEvent={(e) => { | |||||
| return { | |||||
| ...item.value, | |||||
| value: e.target.value, | |||||
| }; | |||||
| }} | |||||
| > | |||||
| <Input placeholder={item.value.label} allowClear /> | |||||
| </Form.Item> | |||||
| {item.value.type === 'ref' && ( | |||||
| <Form.Item noStyle> | |||||
| <Button | |||||
| type="link" | |||||
| icon={getSelectBtnIcon(item.value)} | |||||
| onClick={() => selectResource(['in_parameters', item.key], item.value)} | |||||
| className={styles['select-button']} | |||||
| > | |||||
| {item.value.label} | |||||
| </Button> | |||||
| </Form.Item> | |||||
| )} | |||||
| </div> | |||||
| </Form.Item> | |||||
| ))} | |||||
| <div className={styles.editPipelinePropsContent}> | |||||
| <SubAreaTitle image="/assets/images/duty-message.png" title="输出参数"></SubAreaTitle> | |||||
| </div> | |||||
| {outParametersList.map((item) => ( | |||||
| <Form.Item | |||||
| key={item.key} | |||||
| name={['out_parameters', item.key]} | |||||
| label={ | |||||
| <PropsLabel | |||||
| menuItems={menuItems} | |||||
| title={item.value.label + '(' + item.key + ')'} | |||||
| onClick={(value) => { | |||||
| handleParameterClick(['out_parameters', item.key], { | |||||
| ...item.value, | |||||
| value, | |||||
| }); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| rules={[{ required: item.value.require ? true : false }]} | |||||
| getValueProps={(e) => { | |||||
| return { value: e.value }; | |||||
| }} | |||||
| getValueFromEvent={(e) => { | |||||
| return { | |||||
| ...item.value, | |||||
| value: e.target.value, | |||||
| }; | |||||
| }} | |||||
| > | |||||
| <Input placeholder={item.value.label} allowClear /> | |||||
| </Form.Item> | |||||
| ))} | |||||
| </Form> | |||||
| </Drawer> | |||||
| </> | |||||
| ))} | |||||
| </Form> | |||||
| </Drawer> | |||||
| ); | ); | ||||
| }); | }); | ||||
| @@ -0,0 +1,38 @@ | |||||
| .pipeline-drawer { | |||||
| :global { | |||||
| label { | |||||
| width: 100%; | |||||
| &::after { | |||||
| display: none; | |||||
| } | |||||
| } | |||||
| } | |||||
| &__title { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| width: 100%; | |||||
| height: 43px; | |||||
| margin-bottom: 20px; | |||||
| padding: 0 24px; | |||||
| color: @text-color; | |||||
| font-size: @font-size; | |||||
| background: #f8fbff; | |||||
| } | |||||
| &__ref-row { | |||||
| display: flex; | |||||
| align-items: center; | |||||
| &__select-button { | |||||
| display: flex; | |||||
| flex: none; | |||||
| align-items: center; | |||||
| justify-content: flex-start; | |||||
| margin-left: 10px; | |||||
| padding-right: 0; | |||||
| padding-left: 0; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -1,4 +1,5 @@ | |||||
| import { PipelineGlobalParam } from '@/types'; | |||||
| import { PipelineGlobalParam, PipelineNodeModelParameter } from '@/types'; | |||||
| import { parseJsonText } from '@/utils'; | |||||
| import { Graph, INode } from '@antv/g6'; | import { Graph, INode } from '@antv/g6'; | ||||
| import { type MenuProps } from 'antd'; | import { type MenuProps } from 'antd'; | ||||
| @@ -67,13 +68,19 @@ export function createMenuItems( | |||||
| ]; | ]; | ||||
| } | } | ||||
| function parseJsonText(text?: string | null): any | null { | |||||
| if (!text) { | |||||
| return null; | |||||
| } | |||||
| try { | |||||
| return JSON.parse(text); | |||||
| } catch (error) { | |||||
| return null; | |||||
| export function getInParameterComponent( | |||||
| parameter: PipelineNodeModelParameter, | |||||
| ): React.ReactNode | null { | |||||
| if (parameter.value) { | |||||
| } | } | ||||
| return null; | |||||
| } | |||||
| export function canInput(parameter: PipelineNodeModelParameter) { | |||||
| const { type, item_type } = parameter; | |||||
| return !( | |||||
| type === 'ref' && | |||||
| (item_type === 'dataset' || item_type === 'model' || item_type === 'image') | |||||
| ); | |||||
| } | } | ||||
| @@ -41,7 +41,7 @@ const Pipeline = () => { | |||||
| }; | }; | ||||
| const routeToEdit = (e, record) => { | const routeToEdit = (e, record) => { | ||||
| e.stopPropagation(); | e.stopPropagation(); | ||||
| navgite({ pathname: `/pipeline/pytorchtext/${record.id}/${record.name}` }); | |||||
| navgite({ pathname: `/pipeline/template/${record.id}/${record.name}` }); | |||||
| }; | }; | ||||
| const showModal = () => { | const showModal = () => { | ||||
| form.resetFields(); | form.resetFields(); | ||||
| @@ -66,7 +66,7 @@ const Pipeline = () => { | |||||
| addWorkflow(values).then((ret) => { | addWorkflow(values).then((ret) => { | ||||
| console.log(ret); | console.log(ret); | ||||
| if (ret.code == 200) { | if (ret.code == 200) { | ||||
| navgite({ pathname: `/pipeline/pytorchtext/${ret.data.id}/${ret.data.name}` }); | |||||
| navgite({ pathname: `/pipeline/template/${ret.data.id}/${ret.data.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%; | ||||
| } | } | ||||
| @@ -13,7 +13,7 @@ type ExperimentTableProps = { | |||||
| function ExperimentTable({ tableData = [], style }: ExperimentTableProps) { | function ExperimentTable({ tableData = [], style }: ExperimentTableProps) { | ||||
| const navgite = useNavigate(); | const navgite = useNavigate(); | ||||
| const gotoExperiment = (record: ExperimentInstance) => { | const gotoExperiment = (record: ExperimentInstance) => { | ||||
| navgite(`/pipeline/experimentPytorchtext/${record.workflow_id}/${record.id}`); | |||||
| navgite(`/pipeline/experiment/${record.workflow_id}/${record.id}`); | |||||
| }; | }; | ||||
| return ( | return ( | ||||
| @@ -75,7 +75,7 @@ function QuickStart() { | |||||
| buttonTop={20} | buttonTop={20} | ||||
| x={left + 2 * (192 + space)} | x={left + 2 * (192 + space)} | ||||
| y={276} | y={276} | ||||
| onClick={() => navgite('/pipeline/pipelineText')} | |||||
| onClick={() => navgite('/pipeline/template')} | |||||
| /> | /> | ||||
| <WorkFlow | <WorkFlow | ||||
| content="开发者可以在这里运行流水线模板,产生实验实例,对比实验训练过程与产生的实验训练数据" | content="开发者可以在这里运行流水线模板,产生实验实例,对比实验训练过程与产生的实验训练数据" | ||||
| @@ -83,7 +83,7 @@ function QuickStart() { | |||||
| buttonTop={40} | buttonTop={40} | ||||
| x={left + 3 * (192 + space)} | x={left + 3 * (192 + space)} | ||||
| y={295} | y={295} | ||||
| onClick={() => navgite('/pipeline/experimentText')} | |||||
| onClick={() => navgite('/pipeline/experiment')} | |||||
| /> | /> | ||||
| <WorkFlow | <WorkFlow | ||||
| content="支持异构硬件(CPU/GPU)的模型加载,高吞吐,低延迟;支持大规模复杂模型的一键部署,实时弹性扩缩容;提供完整的运维监控体系。" | content="支持异构硬件(CPU/GPU)的模型加载,高吞吐,低延迟;支持大规模复杂模型的一键部署,实时弹性扩缩容;提供完整的运维监控体系。" | ||||
| @@ -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, | |||||
| }); | |||||
| } | |||||
| @@ -49,10 +49,17 @@ export type PipelineNodeModelParameter = { | |||||
| value: any; | value: any; | ||||
| require: number; | require: number; | ||||
| type: string; | type: string; | ||||
| item_type: string; | |||||
| placeholder?: string; | placeholder?: string; | ||||
| describe?: string; | describe?: string; | ||||
| fromSelect?: boolean; | |||||
| showValue?: any; | |||||
| editable: number; | |||||
| }; | }; | ||||
| // type ChangePropertyType<T, K extends keyof T, NewType> = Omit<T, K> & { [P in K]: NewType } | |||||
| // 序列化后的流水线节点 | |||||
| export type PipelineNodeModelSerialize = Omit< | export type PipelineNodeModelSerialize = Omit< | ||||
| PipelineNodeModel, | PipelineNodeModel, | ||||
| 'control_strategy' | 'in_parameters' | 'out_parameters' | 'control_strategy' | 'in_parameters' | 'out_parameters' | ||||
| @@ -61,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; | |||||
| }; | |||||
| @@ -3,6 +3,8 @@ | |||||
| * @Date: 2024-03-25 13:52:54 | * @Date: 2024-03-25 13:52:54 | ||||
| * @Description: 工具类 | * @Description: 工具类 | ||||
| */ | */ | ||||
| // 生成 8 位随机数 | |||||
| export function s8() { | export function s8() { | ||||
| return (((1 + Math.random()) * 0x100000000) | 0).toString(16).substring(1); | return (((1 + Math.random()) * 0x100000000) | 0).toString(16).substring(1); | ||||
| } | } | ||||
| @@ -14,3 +16,48 @@ export function getNameByCode(list: any[], code: any) { | |||||
| }); | }); | ||||
| return name; | return name; | ||||
| } | } | ||||
| // 解析 json 字符串 | |||||
| export function parseJsonText(text?: string | null): any | null { | |||||
| if (!text) { | |||||
| return null; | |||||
| } | |||||
| try { | |||||
| return JSON.parse(text); | |||||
| } catch (error) { | |||||
| 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); | ||||