diff --git a/react-ui/config/defaultSettings.ts b/react-ui/config/defaultSettings.ts index 65b0fcf5..97a26343 100644 --- a/react-ui/config/defaultSettings.ts +++ b/react-ui/config/defaultSettings.ts @@ -19,7 +19,6 @@ const Settings: ProLayoutProps & { title: '智能软件开发平台', pwa: true, logo: '/assets/images/left-top-logo.png', - iconfontUrl: '//at.alicdn.com/t/c/font_4511326_a182r7rksx5.js', token: { // 参见ts声明,demo 见文档,通过token 修改样式 //https://procomponents.ant.design/components/layout#%E9%80%9A%E8%BF%87-token-%E4%BF%AE%E6%94%B9%E6%A0%B7%E5%BC%8F diff --git a/react-ui/config/routes.ts b/react-ui/config/routes.ts index 26b79145..b3143e97 100644 --- a/react-ui/config/routes.ts +++ b/react-ui/config/routes.ts @@ -206,17 +206,17 @@ export default [ { name: '模型列表', path: '', - component: './ModelDeployment/list', + component: './ModelDeployment/List', }, { name: '镜像详情', path: ':id', - component: './ModelDeployment/info', + component: './ModelDeployment/Info', }, { name: '创建镜像', path: 'create', - component: './ModelDeployment/create', + component: './ModelDeployment/Create', }, ], }, diff --git a/react-ui/src/app.tsx b/react-ui/src/app.tsx index becc1abc..7d928861 100644 --- a/react-ui/src/app.tsx +++ b/react-ui/src/app.tsx @@ -224,6 +224,9 @@ export const antd: RuntimeAntdConfig = (memo) => { inputFontSizeLG: parseInt(themes['fontSizeInputLg']), paddingBlockLG: 10, }; + memo.theme.components.Select = { + singleItemHeightLG: 46, + }; memo.theme.components.Table = { headerBg: 'rgba(242, 244, 247, 0.36)', headerBorderRadius: 4, diff --git a/react-ui/src/assets/img/model-deployment.zip b/react-ui/src/assets/img/model-deployment.png similarity index 91% rename from react-ui/src/assets/img/model-deployment.zip rename to react-ui/src/assets/img/model-deployment.png index 1bd161a1..bf8511c8 100644 Binary files a/react-ui/src/assets/img/model-deployment.zip and b/react-ui/src/assets/img/model-deployment.png differ diff --git a/react-ui/src/components/KFIcon/index.tsx b/react-ui/src/components/KFIcon/index.tsx index e3951928..65239957 100644 --- a/react-ui/src/components/KFIcon/index.tsx +++ b/react-ui/src/components/KFIcon/index.tsx @@ -3,6 +3,7 @@ * @Date: 2024-04-17 12:53:06 * @Description: */ +import '@/iconfont/iconfont-menu.js'; import '@/iconfont/iconfont.js'; import { createFromIconfontCN } from '@ant-design/icons'; diff --git a/react-ui/src/components/PageTitle/index.less b/react-ui/src/components/PageTitle/index.less index 8ab36193..d120009b 100644 --- a/react-ui/src/components/PageTitle/index.less +++ b/react-ui/src/components/PageTitle/index.less @@ -4,4 +4,7 @@ height: 50px; padding-left: 30px; background-image: url(@/assets/img/page-title-bg.png); + background-repeat: no-repeat; + background-position: top center; + background-size: 100%; } diff --git a/react-ui/src/components/ParameterInput/index.less b/react-ui/src/components/ParameterInput/index.less index 738402a0..2d4f0489 100644 --- a/react-ui/src/components/ParameterInput/index.less +++ b/react-ui/src/components/ParameterInput/index.less @@ -1,8 +1,7 @@ .parameter-input { - flex: 1 1 auto; + width: 100%; min-width: 0; - height: 32px; - padding: 3px 11px; + padding: 4px 11px; border: 1px solid #d9d9d9; border-radius: 6px; @@ -15,7 +14,7 @@ align-items: center; width: fit-content; max-width: 100%; - height: 24px; + min-height: 22px; padding: 0 8px; color: .addAlpha(@text-color, 0.8) []; background-color: rgba(0, 0, 0, 0.06); @@ -25,6 +24,7 @@ .singleLine(); margin-right: 8px; font-size: @font-size-input; + line-height: 1.5714285714285714; } &__close-icon { @@ -37,7 +37,28 @@ } &__placeholder { + min-height: 22px; color: rgba(0, 0, 0, 0.25); font-size: @font-size-input; + line-height: 1.5714285714285714; + } +} + +.parameter-input.parameter-input--large { + padding: 10px 11px; + font-size: @font-size-input-lg; + + .parameter-input__placeholder { + font-size: @font-size-input-lg; + line-height: 1.5; + } + + .parameter-input__content__value { + font-size: @font-size-input-lg; + line-height: 1.5; + } + + .parameter-input__content__close-icon { + font-size: 12px; } } diff --git a/react-ui/src/components/ParameterInput/index.tsx b/react-ui/src/components/ParameterInput/index.tsx index 3fbcf364..9d023047 100644 --- a/react-ui/src/components/ParameterInput/index.tsx +++ b/react-ui/src/components/ParameterInput/index.tsx @@ -1,6 +1,7 @@ import { CloseOutlined } from '@ant-design/icons'; import { Input } from 'antd'; -import styles from './index.less'; +import classNames from 'classnames'; +import './index.less'; type ParameterInputData = { value?: any; @@ -16,6 +17,10 @@ interface ParameterInputProps { textArea?: boolean; placeholder?: string; allowClear?: boolean; + className?: string; + style?: React.CSSProperties; + size?: 'middle' | 'small' | 'large'; + disabled?: boolean; } function ParameterInput({ @@ -26,6 +31,10 @@ function ParameterInput({ textArea = false, placeholder, allowClear, + className, + style, + size = 'middle', + disabled = false, ...rest }: ParameterInputProps) { // console.log('ParameterInput', value); @@ -40,15 +49,21 @@ function ParameterInput({ return ( <> - {isSelect || !canInput ? ( -
+ {(isSelect || !canInput) && !disabled ? ( +
{valueObj?.showValue ? ( -
- - {valueObj?.showValue} - +
+ {valueObj?.showValue} onChange?.({ ...valueObj, @@ -60,15 +75,19 @@ function ParameterInput({ />
) : ( -
{placeholder}
+
{placeholder}
)}
) : ( onChange?.({ ...valueObj, diff --git a/react-ui/src/enums/index.ts b/react-ui/src/enums/index.ts index 0c0c7b81..f652445d 100644 --- a/react-ui/src/enums/index.ts +++ b/react-ui/src/enums/index.ts @@ -4,9 +4,25 @@ export enum CommonTabKeys { Public = 'Public', // 公开 } -// 镜像状态 +// 镜像版本状态 export enum MirrorVersionStatus { Available = 'available', // 可用 Building = 'building', // 构建中 Failed = 'failed', // 构建中 } + +// 模型部署状态 +export enum ModelDeploymentStatus { + Init = 'Init', // 启动中 + Running = 'Running', // 运行中 + Stopped = 'Stopped', // 已停止 + Failed = 'Failed', // 失败 +} + +export const modelDeploymentStatusOptions = [ + { label: '全部', value: '' }, + { label: '启动中', value: ModelDeploymentStatus.Init }, + { label: '运行中', value: ModelDeploymentStatus.Running }, + { label: '已停止', value: ModelDeploymentStatus.Stopped }, + { label: '失败', value: ModelDeploymentStatus.Failed }, +]; diff --git a/react-ui/src/hooks/resource.ts b/react-ui/src/hooks/resource.ts new file mode 100644 index 00000000..b3bd76cb --- /dev/null +++ b/react-ui/src/hooks/resource.ts @@ -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([]); + + 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['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; +} diff --git a/react-ui/src/hooks/sessionStorage.ts b/react-ui/src/hooks/sessionStorage.ts new file mode 100644 index 00000000..6a2c53e1 --- /dev/null +++ b/react-ui/src/hooks/sessionStorage.ts @@ -0,0 +1,18 @@ +import { getSessionStorageItem, removeSessionStorageItem } from '@/utils/sessionStorage'; +import { useEffect, useState } from 'react'; + +export function useSessionStorage(key: string, isObject: boolean, initialValue: T) { + const [storage, setStorage] = useState(initialValue); + + useEffect(() => { + const res = getSessionStorageItem(key, isObject); + if (res) { + setStorage(res); + } + return () => { + removeSessionStorageItem(key); + }; + }, []); + + return [storage]; +} diff --git a/react-ui/src/iconfont/iconfont-menu.js b/react-ui/src/iconfont/iconfont-menu.js new file mode 100644 index 00000000..211a58a7 --- /dev/null +++ b/react-ui/src/iconfont/iconfont-menu.js @@ -0,0 +1 @@ +window._iconfont_svg_string_4511326='',function(t){var a=(a=document.getElementsByTagName("script"))[a.length-1],l=a.getAttribute("data-injectcss"),a=a.getAttribute("data-disable-injectsvg");if(!a){var h,i,o,c,e,m=function(a,l){l.parentNode.insertBefore(a,l)};if(l&&!t.__iconfont__svg__cssinject__){t.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}h=function(){var a,l=document.createElement("div");l.innerHTML=t._iconfont_svg_string_4511326,(l=l.getElementsByTagName("svg")[0])&&(l.setAttribute("aria-hidden","true"),l.style.position="absolute",l.style.width=0,l.style.height=0,l.style.overflow="hidden",l=l,(a=document.body).firstChild?m(l,a.firstChild):a.appendChild(l))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(h,0):(i=function(){document.removeEventListener("DOMContentLoaded",i,!1),h()},document.addEventListener("DOMContentLoaded",i,!1)):document.attachEvent&&(o=h,c=t.document,e=!1,n(),c.onreadystatechange=function(){"complete"==c.readyState&&(c.onreadystatechange=null,p())})}function p(){e||(e=!0,o())}function n(){try{c.documentElement.doScroll("left")}catch(a){return void setTimeout(n,50)}p()}}(window); \ No newline at end of file diff --git a/react-ui/src/iconfont/iconfont.js b/react-ui/src/iconfont/iconfont.js index 80008ba1..e135846d 100644 --- a/react-ui/src/iconfont/iconfont.js +++ b/react-ui/src/iconfont/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_4511447='',function(t){var a=(a=document.getElementsByTagName("script"))[a.length-1],h=a.getAttribute("data-injectcss"),a=a.getAttribute("data-disable-injectsvg");if(!a){var v,l,i,z,o,m=function(a,h){h.parentNode.insertBefore(a,h)};if(h&&!t.__iconfont__svg__cssinject__){t.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}v=function(){var a,h=document.createElement("div");h.innerHTML=t._iconfont_svg_string_4511447,(h=h.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",h=h,(a=document.body).firstChild?m(h,a.firstChild):a.appendChild(h))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(v,0):(l=function(){document.removeEventListener("DOMContentLoaded",l,!1),v()},document.addEventListener("DOMContentLoaded",l,!1)):document.attachEvent&&(i=v,z=t.document,o=!1,n(),z.onreadystatechange=function(){"complete"==z.readyState&&(z.onreadystatechange=null,p())})}function p(){o||(o=!0,i())}function n(){try{z.documentElement.doScroll("left")}catch(a){return void setTimeout(n,50)}p()}}(window); \ No newline at end of file +window._iconfont_svg_string_4511447='',function(t){var a=(a=document.getElementsByTagName("script"))[a.length-1],h=a.getAttribute("data-injectcss"),a=a.getAttribute("data-disable-injectsvg");if(!a){var l,v,z,i,o,m=function(a,h){h.parentNode.insertBefore(a,h)};if(h&&!t.__iconfont__svg__cssinject__){t.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(a){console&&console.log(a)}}l=function(){var a,h=document.createElement("div");h.innerHTML=t._iconfont_svg_string_4511447,(h=h.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",h=h,(a=document.body).firstChild?m(h,a.firstChild):a.appendChild(h))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(l,0):(v=function(){document.removeEventListener("DOMContentLoaded",v,!1),l()},document.addEventListener("DOMContentLoaded",v,!1)):document.attachEvent&&(z=l,i=t.document,o=!1,d(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,p())})}function p(){o||(o=!0,z())}function d(){try{i.documentElement.doScroll("left")}catch(a){return void setTimeout(d,50)}p()}}(window); \ No newline at end of file diff --git a/react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx b/react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx index dbb8fc84..b55aa08c 100644 --- a/react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx +++ b/react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx @@ -20,7 +20,7 @@ import { } from 'antd'; import { omit } from 'lodash'; import { useEffect, useState } from 'react'; -import { CategoryData } from '../../type'; +import { CategoryData } from '../../types'; import styles from './index.less'; interface AddDatasetModalProps extends Omit { diff --git a/react-ui/src/pages/Dataset/components/AddModelModal/index.tsx b/react-ui/src/pages/Dataset/components/AddModelModal/index.tsx index 21e76faa..5d4125de 100644 --- a/react-ui/src/pages/Dataset/components/AddModelModal/index.tsx +++ b/react-ui/src/pages/Dataset/components/AddModelModal/index.tsx @@ -1,7 +1,7 @@ import { getAccessToken } from '@/access'; import KFIcon from '@/components/KFIcon'; import KFModal from '@/components/KFModal'; -import { CategoryData } from '@/pages/Dataset/type'; +import { CategoryData } from '@/pages/Dataset/types'; import { addModel } from '@/services/dataset/index.js'; import { to } from '@/utils/promise'; import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; diff --git a/react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx b/react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx index 83a6a269..839c8e20 100644 --- a/react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx +++ b/react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx @@ -1,7 +1,7 @@ import { getAccessToken } from '@/access'; import KFIcon from '@/components/KFIcon'; import KFModal from '@/components/KFModal'; -import { ResourceType, resourceConfig } from '@/pages/Dataset/type'; +import { ResourceType, resourceConfig } from '@/pages/Dataset/types'; import { to } from '@/utils/promise'; import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; import { diff --git a/react-ui/src/pages/Dataset/components/CategoryItem/index.tsx b/react-ui/src/pages/Dataset/components/CategoryItem/index.tsx index 6b0aa607..23f8ce5f 100644 --- a/react-ui/src/pages/Dataset/components/CategoryItem/index.tsx +++ b/react-ui/src/pages/Dataset/components/CategoryItem/index.tsx @@ -1,5 +1,5 @@ import classNames from 'classnames'; -import { CategoryData, ResourceType, resourceConfig } from '../../type'; +import { CategoryData, ResourceType, resourceConfig } from '../../types'; import styles from './index.less'; type CategoryItemProps = { diff --git a/react-ui/src/pages/Dataset/components/CategoryList/index.tsx b/react-ui/src/pages/Dataset/components/CategoryList/index.tsx index cb37f435..28a8de66 100644 --- a/react-ui/src/pages/Dataset/components/CategoryList/index.tsx +++ b/react-ui/src/pages/Dataset/components/CategoryList/index.tsx @@ -1,5 +1,5 @@ import { Flex, Input } from 'antd'; -import { CategoryData, ResourceType, resourceConfig } from '../../type'; +import { CategoryData, ResourceType, resourceConfig } from '../../types'; import CategoryItem from '../CategoryItem'; import styles from './index.less'; diff --git a/react-ui/src/pages/Dataset/components/ResourceList/index.tsx b/react-ui/src/pages/Dataset/components/ResourceList/index.tsx index 59ff87f6..9797c190 100644 --- a/react-ui/src/pages/Dataset/components/ResourceList/index.tsx +++ b/react-ui/src/pages/Dataset/components/ResourceList/index.tsx @@ -7,7 +7,7 @@ import { modalConfirm } from '@/utils/ui'; import { useNavigate } from '@umijs/max'; import { Button, Input, Pagination, PaginationProps, message } from 'antd'; import { Ref, forwardRef, useEffect, useImperativeHandle, useState } from 'react'; -import { CategoryData, ResourceData, ResourceType, resourceConfig } from '../../type'; +import { CategoryData, ResourceData, ResourceType, resourceConfig } from '../../types'; import AddDatasetModal from '../AddDatasetModal'; import ResourceItem from '../Resourcetem'; import styles from './index.less'; diff --git a/react-ui/src/pages/Dataset/components/ResourcePage/index.less b/react-ui/src/pages/Dataset/components/ResourcePage/index.less index 2288dbe6..000c6132 100644 --- a/react-ui/src/pages/Dataset/components/ResourcePage/index.less +++ b/react-ui/src/pages/Dataset/components/ResourcePage/index.less @@ -4,5 +4,8 @@ height: 50px; padding-left: 27px; background-image: url(@/assets/img/page-title-bg.png); + background-repeat: no-repeat; + background-position: top center; + background-size: 100% 100%; } } diff --git a/react-ui/src/pages/Dataset/components/ResourcePage/index.tsx b/react-ui/src/pages/Dataset/components/ResourcePage/index.tsx index c956e4be..9e4dff88 100644 --- a/react-ui/src/pages/Dataset/components/ResourcePage/index.tsx +++ b/react-ui/src/pages/Dataset/components/ResourcePage/index.tsx @@ -4,7 +4,7 @@ import { getAssetIcon } from '@/services/dataset/index.js'; import { to } from '@/utils/promise'; import { Flex, Tabs, type TabsProps } from 'antd'; import { useEffect, useRef, useState } from 'react'; -import { CategoryData, ResourceType, resourceConfig } from '../../type'; +import { CategoryData, ResourceType, resourceConfig } from '../../types'; import CategoryList from '../CategoryList'; import ResourceList, { ResourceListRef } from '../ResourceList'; import styles from './index.less'; diff --git a/react-ui/src/pages/Dataset/components/Resourcetem/index.tsx b/react-ui/src/pages/Dataset/components/Resourcetem/index.tsx index 3a261a48..b8ad9750 100644 --- a/react-ui/src/pages/Dataset/components/Resourcetem/index.tsx +++ b/react-ui/src/pages/Dataset/components/Resourcetem/index.tsx @@ -3,7 +3,7 @@ import creatByImg from '@/assets/img/creatBy.png'; import KFIcon from '@/components/KFIcon'; import { formatDate } from '@/utils/date'; import { Button, Flex, Typography } from 'antd'; -import { ResourceData } from '../../type'; +import { ResourceData } from '../../types'; import styles from './index.less'; type ResourceItemProps = { diff --git a/react-ui/src/pages/Dataset/index.jsx b/react-ui/src/pages/Dataset/index.jsx index d567cb5c..33d2844d 100644 --- a/react-ui/src/pages/Dataset/index.jsx +++ b/react-ui/src/pages/Dataset/index.jsx @@ -1,5 +1,5 @@ import ResourcePage from './components/ResourcePage'; -import { ResourceType } from './type'; +import { ResourceType } from './types'; const DatasetPage = () => { return ; diff --git a/react-ui/src/pages/Dataset/intro.jsx b/react-ui/src/pages/Dataset/intro.jsx index 85539c9a..e1218b1f 100644 --- a/react-ui/src/pages/Dataset/intro.jsx +++ b/react-ui/src/pages/Dataset/intro.jsx @@ -1,5 +1,5 @@ import KFIcon from '@/components/KFIcon'; -import { ResourceType } from '@/pages/Dataset/type'; +import { ResourceType } from '@/pages/Dataset/types'; import { deleteDatasetVersion, getDatasetById, diff --git a/react-ui/src/pages/Dataset/intro.less b/react-ui/src/pages/Dataset/intro.less index 0f712402..b36af2cf 100644 --- a/react-ui/src/pages/Dataset/intro.less +++ b/react-ui/src/pages/Dataset/intro.less @@ -7,7 +7,10 @@ margin-bottom: 10px; padding: 25px 30px; background-image: url(/assets/images/dataset-back.png); + background-repeat: no-repeat; + background-position: top center; background-size: 100% 100%; + .smallTagBox { display: flex; align-items: center; diff --git a/react-ui/src/pages/Dataset/type.tsx b/react-ui/src/pages/Dataset/types.tsx similarity index 94% rename from react-ui/src/pages/Dataset/type.tsx rename to react-ui/src/pages/Dataset/types.tsx index 3bc7f2fe..5f3c5f1e 100644 --- a/react-ui/src/pages/Dataset/type.tsx +++ b/react-ui/src/pages/Dataset/types.tsx @@ -19,9 +19,6 @@ export enum ResourceType { Dataset = 'Dataset', // 数据集 } -type ResourceTypeKeys = keyof typeof ResourceType; -export type ResourceTypeValues = (typeof ResourceType)[ResourceTypeKeys]; - type ResourceTypeInfo = { getList: (params: any) => Promise; getVersions: (params: any) => Promise; @@ -45,7 +42,7 @@ type ResourceTypeInfo = { uploadAccept?: string; }; -export const resourceConfig: Record = { +export const resourceConfig: Record = { [ResourceType.Dataset]: { getList: getDatasetList, getVersions: getDatasetVersionsById, diff --git a/react-ui/src/pages/DatasetPreparation/DatasetAnnotation/index.tsx b/react-ui/src/pages/DatasetPreparation/DatasetAnnotation/index.tsx index a1f017e6..385be971 100644 --- a/react-ui/src/pages/DatasetPreparation/DatasetAnnotation/index.tsx +++ b/react-ui/src/pages/DatasetPreparation/DatasetAnnotation/index.tsx @@ -16,7 +16,7 @@ function DatasetAnnotation() { }; return (
- {iframeUrl && } +
); } diff --git a/react-ui/src/pages/Experiment/index.less b/react-ui/src/pages/Experiment/index.less index 50ec2305..84bce093 100644 --- a/react-ui/src/pages/Experiment/index.less +++ b/react-ui/src/pages/Experiment/index.less @@ -6,6 +6,8 @@ height: 49px; padding-right: 30px; background-image: url(/assets/images/pipeline-back.png); + background-repeat: no-repeat; + background-position: top center; background-size: 100% 100%; } .pipelineTopBox { @@ -17,6 +19,8 @@ margin-bottom: 10px; padding-right: 30px; background-image: url(/assets/images/pipeline-back.png); + background-repeat: no-repeat; + background-position: top center; background-size: 100% 100%; } .tableExpandBox { diff --git a/react-ui/src/pages/Experiment/status.ts b/react-ui/src/pages/Experiment/status.ts index 9f568795..1b13649e 100644 --- a/react-ui/src/pages/Experiment/status.ts +++ b/react-ui/src/pages/Experiment/status.ts @@ -15,10 +15,7 @@ export enum ExperimentStatus { Omitted = 'Omitted', } -type ExperimentStatusKeys = keyof typeof ExperimentStatus; -export type ExperimentStatusValues = (typeof ExperimentStatus)[ExperimentStatusKeys]; - -export const experimentStatusInfo: Record = { +export const experimentStatusInfo: Record = { Running: { label: '运行中', color: '#165bff', diff --git a/react-ui/src/pages/Mirror/components/MirrorStatusCell/index.tsx b/react-ui/src/pages/Mirror/components/MirrorStatusCell/index.tsx index 3702825f..93e64ce3 100644 --- a/react-ui/src/pages/Mirror/components/MirrorStatusCell/index.tsx +++ b/react-ui/src/pages/Mirror/components/MirrorStatusCell/index.tsx @@ -6,15 +6,12 @@ import { MirrorVersionStatus } from '@/enums'; import styles from './index.less'; -type MirrorVersionStatusKeys = keyof typeof MirrorVersionStatus; -type MirrorVersionStatusValues = (typeof MirrorVersionStatus)[MirrorVersionStatusKeys]; - export type MirrorVersionStatusInfo = { text: string; classname: string; }; -const statusInfo: Record = { +const statusInfo: Record = { [MirrorVersionStatus.Building]: { text: '构建中', classname: styles['mirror-status-cell'], diff --git a/react-ui/src/pages/Mirror/create.tsx b/react-ui/src/pages/Mirror/create.tsx index f2d1f86f..0116f479 100644 --- a/react-ui/src/pages/Mirror/create.tsx +++ b/react-ui/src/pages/Mirror/create.tsx @@ -11,7 +11,11 @@ import SubAreaTitle from '@/components/SubAreaTitle'; import { CommonTabKeys } from '@/enums'; import { createMirrorReq } from '@/services/mirror'; import { to } from '@/utils/promise'; -import { getSessionItemThenRemove, mirrorNameKey } from '@/utils/sessionStorage'; +import { + getSessionStorageItem, + mirrorNameKey, + removeSessionStorageItem, +} from '@/utils/sessionStorage'; import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; import { useNavigate } from '@umijs/max'; import { Button, Col, Form, Input, Row, Upload, UploadFile, message, type UploadProps } from 'antd'; @@ -56,11 +60,14 @@ function MirrorCreate() { }; useEffect(() => { - const name = getSessionItemThenRemove(mirrorNameKey); + const name = getSessionStorageItem(mirrorNameKey); if (name) { form.setFieldValue('name', name); setNameDisabled(true); } + return () => { + removeSessionStorageItem(mirrorNameKey); + }; }, []); // 创建公网、本地镜像 diff --git a/react-ui/src/pages/Mirror/list.less b/react-ui/src/pages/Mirror/list.less index 9f2905e9..6acc2b14 100644 --- a/react-ui/src/pages/Mirror/list.less +++ b/react-ui/src/pages/Mirror/list.less @@ -4,6 +4,9 @@ height: 50px; padding-left: 27px; background-image: url(@/assets/img/page-title-bg.png); + background-repeat: no-repeat; + background-position: top center; + background-size: 100% 100%; } &__content { diff --git a/react-ui/src/pages/Mirror/list.tsx b/react-ui/src/pages/Mirror/list.tsx index 16bfeb1a..eba17cb6 100644 --- a/react-ui/src/pages/Mirror/list.tsx +++ b/react-ui/src/pages/Mirror/list.tsx @@ -11,6 +11,7 @@ import { useCacheState } from '@/hooks/pageCacheState'; import { deleteMirrorReq, getMirrorListReq } from '@/services/mirror'; import themes from '@/styles/theme.less'; import { to } from '@/utils/promise'; +import { mirrorNameKey, setSessionStorageItem } from '@/utils/sessionStorage'; import { modalConfirm } from '@/utils/ui'; import { useNavigate } from '@umijs/max'; import { @@ -145,6 +146,7 @@ function MirrorList() { // 创建镜像 const createMirror = () => { navigate(`/dataset/mirror/create`); + setSessionStorageItem(mirrorNameKey, ''); setCacheState({ activeTab, pagination, diff --git a/react-ui/src/pages/Model/index.jsx b/react-ui/src/pages/Model/index.jsx index f8add51f..06ddafd4 100644 --- a/react-ui/src/pages/Model/index.jsx +++ b/react-ui/src/pages/Model/index.jsx @@ -1,5 +1,5 @@ import ResourcePage from '@/pages/Dataset/components/ResourcePage'; -import { ResourceType } from '@/pages/Dataset/type'; +import { ResourceType } from '@/pages/Dataset/types'; const ModelPage = () => { return ; diff --git a/react-ui/src/pages/Model/intro.jsx b/react-ui/src/pages/Model/intro.jsx index 261e29d8..f044e465 100644 --- a/react-ui/src/pages/Model/intro.jsx +++ b/react-ui/src/pages/Model/intro.jsx @@ -1,6 +1,6 @@ import KFIcon from '@/components/KFIcon'; import AddVersionModal from '@/pages/Dataset/components/AddVersionModal'; -import { ResourceType } from '@/pages/Dataset/type'; +import { ResourceType } from '@/pages/Dataset/types'; import { deleteModelVersion, getModelById, diff --git a/react-ui/src/pages/Model/intro.less b/react-ui/src/pages/Model/intro.less index 596c64d7..b40d4a2b 100644 --- a/react-ui/src/pages/Model/intro.less +++ b/react-ui/src/pages/Model/intro.less @@ -7,8 +7,10 @@ margin-bottom: 10px; padding: 25px 30px; background-image: url(/assets/images/dataset-back.png); - + background-repeat: no-repeat; + background-position: top center; background-size: 100% 100%; + .smallTagBox { display: flex; align-items: center; diff --git a/react-ui/src/pages/ModelDeployment/create.less b/react-ui/src/pages/ModelDeployment/Create/index.less similarity index 83% rename from react-ui/src/pages/ModelDeployment/create.less rename to react-ui/src/pages/ModelDeployment/Create/index.less index 63c00764..f098861f 100644 --- a/react-ui/src/pages/ModelDeployment/create.less +++ b/react-ui/src/pages/ModelDeployment/Create/index.less @@ -6,6 +6,8 @@ margin-top: 10px; padding: 30px 30px 10px; overflow: auto; + color: @text-color; + font-size: @font-size-content; background-color: white; border-radius: 10px; diff --git a/react-ui/src/pages/ModelDeployment/Create/index.tsx b/react-ui/src/pages/ModelDeployment/Create/index.tsx new file mode 100644 index 00000000..1c9d25b8 --- /dev/null +++ b/react-ui/src/pages/ModelDeployment/Create/index.tsx @@ -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( + undefined, + ); // 选择的模型,为了再次打开时恢复原来的选择 + const [operationType, setOperationType] = useState(ModelDeploymentOperationType.Create); + const [modelDeploymentInfo, setModelDeploymentInfo] = useState( + 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 ; + }; + + // 选择模型、镜像 + 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); + + 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 ( +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {(fields, { add, remove }) => ( + <> + + + + + + + + {fields.map(({ key, name, ...restField }) => ( + + + + + = + + + + + + ))} + + )} + + + + + + +
+
+
+
+ ); +} + +export default ModelDeploymentCreate; diff --git a/react-ui/src/pages/ModelDeployment/info.less b/react-ui/src/pages/ModelDeployment/Info/index.less similarity index 91% rename from react-ui/src/pages/ModelDeployment/info.less rename to react-ui/src/pages/ModelDeployment/Info/index.less index c77a7070..f1a0416c 100644 --- a/react-ui/src/pages/ModelDeployment/info.less +++ b/react-ui/src/pages/ModelDeployment/Info/index.less @@ -9,6 +9,7 @@ line-height: 1.6; .label { + flex: none; width: 80px; color: @text-color-secondary; } @@ -16,6 +17,8 @@ .value { flex: 1; color: @text-color; + white-space: pre-line; + word-break: break-all; } } } diff --git a/react-ui/src/pages/ModelDeployment/Info/index.tsx b/react-ui/src/pages/ModelDeployment/Info/index.tsx new file mode 100644 index 00000000..c4163c1d --- /dev/null +++ b/react-ui/src/pages/ModelDeployment/Info/index.tsx @@ -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: , + }, + { + key: '2', + label: '调用指南', + icon: , + }, + { + key: '3', + label: '服务日志', + icon: , + }, +]; + +function ModelDeploymentInfo() { + const [activeTab, setActiveTab] = useState('1'); + const [modelDeployementInfo] = useSessionStorage( + 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 ( +
+ +
+
+ +
+ + +
+
服务名称:
+
+ {modelDeployementInfo?.service_name ?? '--'} +
+
+ + +
+
镜  像:
+
{modelDeployementInfo?.image ?? '--'}
+
+ +
+ + +
+
状  态:
+
+ {ModelDeploymentStatusCell(modelDeployementInfo?.status)} +
+
+ + +
+
模  型:
+
+ {modelDeployementInfo?.model?.show_value ?? '--'} +
+
+ +
+ + +
+
创建人:
+
{modelDeployementInfo?.created_by ?? '--'}
+
+ + +
+
挂载路径:
+
{modelDeployementInfo?.model_path ?? '--'}
+
+ +
+ + +
+
API URL:
+
{modelDeployementInfo?.url ?? '--'}
+
+ + +
+
副本数量:
+
{modelDeployementInfo?.replicas ?? '--'}
+
+ +
+ + +
+
创建时间:
+
+ {modelDeployementInfo?.create_time + ? formatDate(modelDeployementInfo.create_time) + : '--'} +
+
+ + +
+
更新时间:
+
+ {modelDeployementInfo?.update_time + ? formatDate(modelDeployementInfo.update_time) + : '--'} +
+
+ +
+ + +
+
环境变量:
+
{formatEnvText()}
+
+ + +
+
资源规格
+
+ {modelDeployementInfo?.resource + ? getResourceDescription(modelDeployementInfo.resource) + : '--'} +
+
+ +
+ + +
+
描  述:
+
{modelDeployementInfo?.description ?? '--'}
+
+ +
+
+
+ +
+
+
+
+ ); +} + +export default ModelDeploymentInfo; diff --git a/react-ui/src/pages/ModelDeployment/list.less b/react-ui/src/pages/ModelDeployment/List/index.less similarity index 100% rename from react-ui/src/pages/ModelDeployment/list.less rename to react-ui/src/pages/ModelDeployment/List/index.less diff --git a/react-ui/src/pages/ModelDeployment/List/index.tsx b/react-ui/src/pages/ModelDeployment/List/index.tsx new file mode 100644 index 00000000..ce9cbaf7 --- /dev/null +++ b/react-ui/src/pages/ModelDeployment/List/index.tsx @@ -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([]); + const [total, setTotal] = useState(0); + const [pagination, setPagination] = useState( + cacheState?.pagination ?? { + current: 1, + pageSize: 10, + }, + ); + + useEffect(() => { + getModelDeploymentList(); + }, [pagination, searchText, searchStatus]); + + // 获取模型部署列表 + const getModelDeploymentList = async () => { + const params: Record = { + 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['columns'] = [ + { + title: '序号', + dataIndex: 'index', + key: 'index', + width: '20%', + render(text, record, index) { + return {(pagination.current! - 1) * pagination.pageSize! + index + 1}; + }, + }, + { + title: '服务名称', + dataIndex: 'service_name', + key: 'service_name', + width: '20%', + render: (text, record) => { + return toDetail(record)}>{text}; + }, + }, + { + 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) => ( +
+ + {(record.status === ModelDeploymentStatus.Failed || + record.status === ModelDeploymentStatus.Stopped) && ( + + )} + {(record.status === ModelDeploymentStatus.Running || + record.status === ModelDeploymentStatus.Init) && ( + + )} + + + +
+ ), + }, + ]; + + return ( +
+ +
+
+ setInputText(e.target.value)} + style={{ width: 300 }} + value={inputText} + allowClear + /> + + + +
+
+ + + + + ); +} + +export default ModelDeployment; diff --git a/react-ui/src/pages/ModelDeployment/components/MirrorStatusCell/index.less b/react-ui/src/pages/ModelDeployment/components/MirrorStatusCell/index.less deleted file mode 100644 index 043bf411..00000000 --- a/react-ui/src/pages/ModelDeployment/components/MirrorStatusCell/index.less +++ /dev/null @@ -1,11 +0,0 @@ -.mirror-status-cell { - color: @text-color; - - &--success { - color: @success-color; - } - - &--error { - color: @error-color; - } -} diff --git a/react-ui/src/pages/ModelDeployment/components/MirrorStatusCell/index.tsx b/react-ui/src/pages/ModelDeployment/components/MirrorStatusCell/index.tsx deleted file mode 100644 index 3702825f..00000000 --- a/react-ui/src/pages/ModelDeployment/components/MirrorStatusCell/index.tsx +++ /dev/null @@ -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 = { - [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 --; - } - return {statusInfo[status].text}; -} - -export default MirrorStatusCell; diff --git a/react-ui/src/pages/ModelDeployment/components/ModelDeployStatusCell/index.less b/react-ui/src/pages/ModelDeployment/components/ModelDeployStatusCell/index.less new file mode 100644 index 00000000..9da49f8d --- /dev/null +++ b/react-ui/src/pages/ModelDeployment/components/ModelDeployStatusCell/index.less @@ -0,0 +1,15 @@ +.model-deployment-status-cell { + color: @text-color; + + &--running { + color: @primary-color; + } + + &--stopped { + color: @warning-color; + } + + &--error { + color: @error-color; + } +} diff --git a/react-ui/src/pages/ModelDeployment/components/ModelDeployStatusCell/index.tsx b/react-ui/src/pages/ModelDeployment/components/ModelDeployStatusCell/index.tsx new file mode 100644 index 00000000..a1773e43 --- /dev/null +++ b/react-ui/src/pages/ModelDeployment/components/ModelDeployStatusCell/index.tsx @@ -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.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 --; + } + return {statusInfo[status].text}; +} + +export default ModelDeploymentStatusCell; diff --git a/react-ui/src/pages/ModelDeployment/create.tsx b/react-ui/src/pages/ModelDeployment/create.tsx deleted file mode 100644 index cc2c43ff..00000000 --- a/react-ui/src/pages/ModelDeployment/create.tsx +++ /dev/null @@ -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 ( -
- -
-
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -} - -export default ModelDeploymentCreate; diff --git a/react-ui/src/pages/ModelDeployment/info.tsx b/react-ui/src/pages/ModelDeployment/info.tsx deleted file mode 100644 index 3e4e8a81..00000000 --- a/react-ui/src/pages/ModelDeployment/info.tsx +++ /dev/null @@ -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: , - }, - { - key: '2', - label: '调用指南', - icon: , - }, - { - key: '3', - label: '服务日志', - icon: , - }, -]; - -function ModelDeploymentInfo() { - const navigate = useNavigate(); - const urlParams = useParams(); - - const [mirrorInfo, setMirrorInfo] = useState({}); - - const [activeTab, setActiveTab] = useState('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 ( -
- -
-
- -
- -
-
-
服务名称:
-
{mirrorInfo.name}
-
- - -
-
镜像:
-
{mirrorInfo.version_count ?? '--'}
-
- - - - -
-
状态:
-
{mirrorInfo.name}
-
- - -
-
模型:
-
{mirrorInfo.version_count ?? '--'}
-
- - - - -
-
环境变量:
-
{mirrorInfo.name}
-
- - - - -
-
描述:
-
{mirrorInfo.description}
-
- - - -
- -
- - - - ); -} - -export default ModelDeploymentInfo; diff --git a/react-ui/src/pages/ModelDeployment/list.tsx b/react-ui/src/pages/ModelDeployment/list.tsx deleted file mode 100644 index bfad5a22..00000000 --- a/react-ui/src/pages/ModelDeployment/list.tsx +++ /dev/null @@ -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([]); - const [total, setTotal] = useState(0); - const [pagination, setPagination] = useState>( - cacheState?.pagination ?? { - current: 1, - pageSize: 10, - }, - ); - - useEffect(() => { - getMirrorList(); - }, [pagination, searchText]); - - // 获取镜像列表 - const getMirrorList = async () => { - const params: Record = { - 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['columns'] = [ - { - title: '序号', - dataIndex: 'index', - key: 'index', - width: 100, - align: 'center', - render(text, record, index) { - return {(pagination.current - 1) * pagination.pageSize + index + 1}; - }, - }, - { - 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) => ( -
- - - - - - -
- ), - }, - ]; - - return ( -
- -
-
- setInputText(e.target.value)} - style={{ width: 300 }} - value={inputText} - /> - -
-
-
- - - - ); -} - -export default ModelDeployment; diff --git a/react-ui/src/pages/ModelDeployment/types.ts b/react-ui/src/pages/ModelDeployment/types.ts new file mode 100644 index 00000000..8c9b7fc4 --- /dev/null +++ b/react-ui/src/pages/ModelDeployment/types.ts @@ -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; +}; + +// 操作类型 +export enum ModelDeploymentOperationType { + Create = 'create', + Update = 'update', + Restart = 'restart', +} + +// 状态 +export type ModelDeploymentStatusInfo = { + text: string; + classname: string; +}; diff --git a/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.less b/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.less index b1681d74..cd10e0d8 100644 --- a/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.less +++ b/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.less @@ -6,7 +6,7 @@ &__delete-button { position: absolute; top: 5px; - right: 0; + right: 24px; } :global { diff --git a/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx b/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx index 4966f265..7336b333 100644 --- a/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx +++ b/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx @@ -1,8 +1,9 @@ +import KFIcon from '@/components/KFIcon'; import { getParamComponent, getParamRules } from '@/pages/Experiment/components/AddExperimentModal'; import { type PipelineGlobalParam } from '@/types'; import { to } from '@/utils/promise'; import { modalConfirm } from '@/utils/ui'; -import { DeleteOutlined, PlusOutlined } from '@ant-design/icons'; +import { PlusOutlined } from '@ant-design/icons'; import { Button, Drawer, Form, Input, Radio, Tooltip } from 'antd'; import { NamePath } from 'antd/es/form/interface'; import { forwardRef, useImperativeHandle } from 'react'; @@ -143,7 +144,7 @@ const GlobalParamsDrawer = forwardRef( className={styles['form-item__delete-button']} type="link" onClick={() => removeParameter(name, remove)} - icon={} + icon={} > diff --git a/react-ui/src/pages/Pipeline/components/ResourceSelectorModal/config.tsx b/react-ui/src/pages/Pipeline/components/ResourceSelectorModal/config.tsx new file mode 100644 index 00000000..112586d6 --- /dev/null +++ b/react-ui/src/pages/Pipeline/components/ResourceSelectorModal/config.tsx @@ -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; + getVersions: (params: any) => Promise; + getFiles: (params: any) => Promise; + 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 => { + 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.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: '公开镜像', + }, + ], + }, +}; diff --git a/react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.tsx b/react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.tsx index 20c265f2..bddf2718 100644 --- a/react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.tsx +++ b/react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.tsx @@ -1,133 +1,22 @@ /* * @Author: 赵伟 * @Date: 2024-04-11 16:31:18 - * @Description: 选择数据集和模型 + * @Description: 选择数据集、模型、镜像 */ -import datasetImg from '@/assets/img/modal-select-dataset.png'; -import mirrorImg from '@/assets/img/modal-select-mirror.png'; -import modelImg from '@/assets/img/modal-select-model.png'; import KFModal from '@/components/KFModal'; -import { CommonTabKeys, MirrorVersionStatus } from '@/enums'; -import { - getDatasetList, - getDatasetVersionIdList, - getDatasetVersionsById, - getModelList, - getModelVersionIdList, - getModelVersionsById, -} from '@/services/dataset/index.js'; -import { getMirrorListReq, getMirrorVersionListReq } from '@/services/mirror'; +import { CommonTabKeys } from '@/enums'; import { to } from '@/utils/promise'; import { Icon } from '@umijs/max'; -import type { GetRef, ModalProps, TabsProps, TreeDataNode, TreeProps } from 'antd'; +import type { GetRef, ModalProps, TreeDataNode, TreeProps } from 'antd'; import { Input, Tabs, Tree } from 'antd'; import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { MirrorVersion, ResourceSelectorType, selectorTypeConfig } from './config'; import styles from './index.less'; +export { ResourceSelectorType, selectorTypeConfig } from './config'; -export enum ResourceSelectorType { - Model = 'Model', // 模型 - Dataset = 'Dataset', // 数据集 - Mirror = 'Mirror', //镜像 -} - -type ResourceSelectorTypeKeys = keyof typeof ResourceSelectorType; -type ResourceSelectorTypeValues = (typeof ResourceSelectorType)[ResourceSelectorTypeKeys]; - -export type SelectorTypeInfo = { - getList: (params: any) => Promise; - getVersions: (params: any) => Promise; - getFiles: (params: any) => Promise; - 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 => { - 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 = { - [ResourceSelectorType.Model]: { - getList: getModelList, - getVersions: getModelVersionsById, - getFiles: getModelVersionIdList, - handleVersionResponse: (res) => res.data || [], - name: '模型', - modalIcon: modelImg, - litReqParamKey: 'available_range', - fileReqParamKey: 'models_id', - tabItems: [ - { - key: CommonTabKeys.Private, - label: '我的模型', - }, - { - key: CommonTabKeys.Public, - label: '公开模型', - }, - ], - }, - [ResourceSelectorType.Dataset]: { - getList: getDatasetList, - getVersions: getDatasetVersionsById, - getFiles: getDatasetVersionIdList, - handleVersionResponse: (res) => res.data || [], - name: '数据集', - modalIcon: datasetImg, - litReqParamKey: 'available_range', - fileReqParamKey: 'dataset_id', - tabItems: [ - { - key: CommonTabKeys.Private, - label: '我的数据集', - }, - { - key: CommonTabKeys.Public, - label: '公开数据集', - }, - ], - }, - [ResourceSelectorType.Mirror]: { - getList: getMirrorListReq, - getVersions: (id: number) => getMirrorVersionListReq({ image_id: id, page: 0, size: 200 }), - getFiles: getMirrorFilesReq, - handleVersionResponse: (res) => - res.data?.content?.filter((v: MirrorVersion) => v.status === MirrorVersionStatus.Available) || - [], - name: '镜像', - modalIcon: mirrorImg, - litReqParamKey: 'image_type', - fileReqParamKey: 'dataset_id', - tabItems: [ - { - key: CommonTabKeys.Private, - label: '我的镜像', - }, - { - key: CommonTabKeys.Public, - label: '公开镜像', - }, - ], - }, -}; - -type ResourceSelectorResponse = { +// 选择数据集和模型的返回类型 +export type ResourceSelectorResponse = { id: number; // 数据集或者模型 id name: string; // 数据集或者模型 name version: string; // 数据集或者模型版本 @@ -135,11 +24,11 @@ type ResourceSelectorResponse = { activeTab: CommonTabKeys; // 是我的还是公开的 }; -interface ResourceSelectorModalProps extends Omit { +export interface ResourceSelectorModalProps extends Omit { type: ResourceSelectorType; // 模型 | 数据集 - defaultExpandedKeys: React.Key[]; - defaultCheckedKeys: React.Key[]; - defaultActiveTab: CommonTabKeys; + defaultExpandedKeys?: React.Key[]; + defaultCheckedKeys?: React.Key[]; + defaultActiveTab?: CommonTabKeys; onOk?: (params: ResourceSelectorResponse | string | null) => void; } @@ -148,13 +37,6 @@ type ResourceGroup = { name: string; // 数据集或者模型 id }; -type MirrorVersion = { - id: number; // 镜像版本id - status: MirrorVersionStatus; // 镜像版本状态 - tag_name: string; // 镜像版本 - url: string; // 镜像版本路径 -}; - type ResourceFile = { id: number; // 文件 id file_name: string; // 文件 name @@ -261,9 +143,9 @@ function ResourceSelectorModal({ const params = { page: 0, size: 200, - [selectorTypeData[type].litReqParamKey]: available_range, + [selectorTypeConfig[type].litReqParamKey]: available_range, }; - const getListReq = selectorTypeData[type].getList; + const getListReq = selectorTypeConfig[type].getList; const [res] = await to(getListReq(params)); if (res) { const list = res.data?.content || []; @@ -279,10 +161,10 @@ function ResourceSelectorModal({ // 获取数据集或模型版本列表 const getVersions = async (parentId: number) => { - const getVersionsReq = selectorTypeData[type].getVersions; + const getVersionsReq = selectorTypeConfig[type].getVersions; const [res, error] = await to(getVersionsReq(parentId)); if (res) { - const list = selectorTypeData[type].handleVersionResponse(res); + const list = selectorTypeConfig[type].handleVersionResponse(res); const children = list.map(convertVersionToTreeData(parentId)); // 更新 treeData children setOriginTreeData((prev) => prev.map(updateChildren(parentId, children))); @@ -301,8 +183,8 @@ function ResourceSelectorModal({ // 获取版本下的文件 const getFiles = async (id: number, version: string) => { - const getFilesReq = selectorTypeData[type].getFiles; - const paramsKey = selectorTypeData[type].fileReqParamKey; + const getFilesReq = selectorTypeConfig[type].getFiles; + const paramsKey = selectorTypeConfig[type].fileReqParamKey; const params = { version: version, [paramsKey]: id }; const [res] = await to(getFilesReq(params)); if (res) { @@ -404,14 +286,14 @@ function ResourceSelectorModal({ } }; - const title = `选择${selectorTypeData[type].name}`; - const palceholder = `请输入${selectorTypeData[type].name}名称`; + const title = `选择${selectorTypeConfig[type].name}`; + const palceholder = `请输入${selectorTypeConfig[type].name}名称`; const fileTitle = type === ResourceSelectorType.Mirror ? '已选镜像' - : `已选${selectorTypeData[type].name}文件(${files.length})`; - const tabItems = selectorTypeData[type].tabItems; - const titleImg = selectorTypeData[type].modalIcon; + : `已选${selectorTypeConfig[type].name}文件(${files.length})`; + const tabItems = selectorTypeConfig[type].tabItems; + const titleImg = selectorTypeConfig[type].modalIcon; return ( diff --git a/react-ui/src/pages/Pipeline/editPipeline/props.jsx b/react-ui/src/pages/Pipeline/editPipeline/props.jsx index f2d71963..7436c865 100644 --- a/react-ui/src/pages/Pipeline/editPipeline/props.jsx +++ b/react-ui/src/pages/Pipeline/editPipeline/props.jsx @@ -101,7 +101,7 @@ const Props = forwardRef(({ onParentChange }, ref) => { }, })); - // 选择数据集、模型 + // 选择数据集、模型、镜像 const selectResource = (name, item) => { let type; let resource; @@ -130,20 +130,20 @@ const Props = forwardRef(({ onParentChange }, ref) => { } else { const jsonObj = pick(res, ['id', 'version', 'path']); const value = JSON.stringify(jsonObj); - const showValue = `${res.name}:${res.version}`; + const showValue = `${res.name}:${res.version}`; form.setFieldValue(name, { ...item, value, showValue, fromSelect: true }); - } - if (type === ResourceSelectorType.Dataset) { - setSelectedDataset(res); - } else if (type === ResourceSelectorType.Model) { - setSelectedModel(res); + if (type === ResourceSelectorType.Dataset) { + setSelectedDataset(res); + } else if (type === ResourceSelectorType.Model) { + setSelectedModel(res); + } } } else { if (type === ResourceSelectorType.Dataset) { - setSelectedDataset(null); + setSelectedDataset(undefined); } else if (type === ResourceSelectorType.Model) { - setSelectedModel(null); + setSelectedModel(undefined); } form.setFieldValue(name, ''); } diff --git a/react-ui/src/pages/Pipeline/index.less b/react-ui/src/pages/Pipeline/index.less index 102a37ef..e1808690 100644 --- a/react-ui/src/pages/Pipeline/index.less +++ b/react-ui/src/pages/Pipeline/index.less @@ -7,6 +7,8 @@ margin-bottom: 10px; padding-right: 30px; background-image: url(/assets/images/pipeline-back.png); + background-repeat: no-repeat; + background-position: top left; background-size: 100% 100%; } diff --git a/react-ui/src/services/mirror/index.ts b/react-ui/src/services/mirror/index.ts index 5820f631..216ce32c 100644 --- a/react-ui/src/services/mirror/index.ts +++ b/react-ui/src/services/mirror/index.ts @@ -43,7 +43,7 @@ export function deleteMirrorReq(id: number) { }); } -// 删除镜像 +// 删除镜像版本 export function deleteMirrorVersionReq(id: number) { return request(`/api/mmp/imageVersion/${id}`, { method: 'DELETE', diff --git a/react-ui/src/services/modelDeployment/index.ts b/react-ui/src/services/modelDeployment/index.ts new file mode 100644 index 00000000..7416eeef --- /dev/null +++ b/react-ui/src/services/modelDeployment/index.ts @@ -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, + }); +} diff --git a/react-ui/src/types.ts b/react-ui/src/types.ts index 287535ee..855584c6 100644 --- a/react-ui/src/types.ts +++ b/react-ui/src/types.ts @@ -68,3 +68,12 @@ export type PipelineNodeModelSerialize = Omit< in_parameters: Record; out_parameters: Record; }; + +// 资源规格 +export type ComputingResource = { + id: number; + computing_resource: string; + description: string; + standard: string; + create_by: string; +}; diff --git a/react-ui/src/utils/index.ts b/react-ui/src/utils/index.ts index a2042b0b..08fef8cc 100644 --- a/react-ui/src/utils/index.ts +++ b/react-ui/src/utils/index.ts @@ -28,3 +28,36 @@ export function parseJsonText(text?: string | null): any | null { return null; } } + +// Underscore-to-camelCase +export function underscoreToCamelCase(obj: Record) { + const newObj: Record = {}; + 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) { + const newObj: Record = {}; + 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; +} diff --git a/react-ui/src/utils/modal.tsx b/react-ui/src/utils/modal.tsx index 577c1ec3..4aff7b77 100644 --- a/react-ui/src/utils/modal.tsx +++ b/react-ui/src/utils/modal.tsx @@ -16,7 +16,8 @@ import { createRoot } from 'react-dom/client'; * @param modalProps - The modal properties. * @return An object with a destroy method to close the modal. */ -export const openAntdModal = ( + +export const openAntdModal = >( modal: (props: T) => React.ReactNode, modalProps: T, ) => { diff --git a/react-ui/src/utils/sessionStorage.ts b/react-ui/src/utils/sessionStorage.ts index fd006c7a..6018dbe7 100644 --- a/react-ui/src/utils/sessionStorage.ts +++ b/react-ui/src/utils/sessionStorage.ts @@ -1,5 +1,7 @@ // 用于新建镜像 export const mirrorNameKey = 'mirror-name'; +// 模型部署 +export const modelDeploymentInfoKey = 'model-deployment-info'; export const getSessionStorageItem = (key: string, isObject: boolean = false) => { const jsonStr = sessionStorage.getItem(key); @@ -22,6 +24,10 @@ export const setSessionStorageItem = (key: string, state?: any, isObject: boolea } }; +export const removeSessionStorageItem = (key: string) => { + sessionStorage.removeItem(key); +}; + // 获取之后就删除,多用于上一个页面传递数据到下一个页面 export const getSessionItemThenRemove = (key: string, isObject: boolean = false) => { const res = getSessionStorageItem(key, isObject);