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 (
+
+ );
+}
+
+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) => (
+
+ }
+ onClick={() => createModelDeployment(ModelDeploymentOperationType.Update, record)}
+ >
+ 更新
+
+ {(record.status === ModelDeploymentStatus.Failed ||
+ record.status === ModelDeploymentStatus.Stopped) && (
+ }
+ onClick={() => createModelDeployment(ModelDeploymentOperationType.Restart, record)}
+ >
+ 重启
+
+ )}
+ {(record.status === ModelDeploymentStatus.Running ||
+ record.status === ModelDeploymentStatus.Init) && (
+ }
+ onClick={() => handleModelDeployStop(record)}
+ >
+ 停止
+
+ )}
+
+ }
+ onClick={() => handleModelDeployDelete(record)}
+ >
+ 删除
+
+
+
+ ),
+ },
+ ];
+
+ 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) => (
-
- }
- onClick={() => toDetail(record)}
- >
- 编辑
-
- }
- onClick={() => toDetail(record)}
- >
- 启动
-
- }
- onClick={() => toDetail(record)}
- >
- 停止
-
-
- }
- onClick={() => handleMirrorDelete(record)}
- >
- 删除
-
-
-
- ),
- },
- ];
-
- 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);