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 0e502c59..200bbd20 100644 --- a/react-ui/config/routes.ts +++ b/react-ui/config/routes.ts @@ -67,24 +67,36 @@ export default [ path: '/pipeline', routes: [ { - name: '流水线', - path: '/pipeline/pipelineText', - component: './Pipeline/index', - }, - { - name: '训练', - path: '/pipeline/pytorchtext/:id/:name', - component: './Pipeline/editPipeline/index', + name: '流水线模板', + path: 'template', + routes: [ + { + name: '流水线模板', + path: '', + component: './Pipeline/index', + }, + { + name: '流水线详情', + path: ':id/:name', + component: './Pipeline/editPipeline/index', + }, + ], }, { name: '实验', - path: '/pipeline/experimentText', - component: './Experiment/index', - }, - { - name: '实验训练', - path: '/pipeline/experimentPytorchtext/:workflowId/:id', - component: './Experiment/experimentText/index', + path: 'experiment', + routes: [ + { + name: '实验', + path: '', + component: './Experiment/index', + }, + { + name: '实验训练', + path: ':workflowId/:id', + component: './Experiment/training/index', + }, + ], }, ], }, @@ -158,17 +170,17 @@ export default [ { name: '镜像列表', path: '', - component: './Mirror/list', + component: './Mirror/List', }, { name: '镜像详情', path: ':id', - component: './Mirror/info', + component: './Mirror/Info', }, { name: '创建镜像', path: 'create', - component: './Mirror/create', + component: './Mirror/Create', }, ], }, @@ -194,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/CommonTableCell/index.tsx b/react-ui/src/components/CommonTableCell/index.tsx index 6c2f35b0..afa5d8d3 100644 --- a/react-ui/src/components/CommonTableCell/index.tsx +++ b/react-ui/src/components/CommonTableCell/index.tsx @@ -12,7 +12,11 @@ function renderCell(text?: string | null) { function CommonTableCell(ellipsis: boolean = false) { if (ellipsis) { - return (text?: string | null) => {renderCell(text)}; + return (text?: string | null) => ( + + {renderCell(text)} + + ); } else { return renderCell; } 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 new file mode 100644 index 00000000..2d4f0489 --- /dev/null +++ b/react-ui/src/components/ParameterInput/index.less @@ -0,0 +1,64 @@ +.parameter-input { + width: 100%; + min-width: 0; + padding: 4px 11px; + border: 1px solid #d9d9d9; + border-radius: 6px; + + &:hover { + border-color: @primary-color; + } + + &__content { + display: flex; + align-items: center; + width: fit-content; + max-width: 100%; + min-height: 22px; + padding: 0 8px; + color: .addAlpha(@text-color, 0.8) []; + background-color: rgba(0, 0, 0, 0.06); + border-radius: 4px; + + &__value { + .singleLine(); + margin-right: 8px; + font-size: @font-size-input; + line-height: 1.5714285714285714; + } + + &__close-icon { + font-size: 10px; + + &:hover { + color: #000; + } + } + } + + &__placeholder { + min-height: 22px; + color: rgba(0, 0, 0, 0.25); + font-size: @font-size-input; + line-height: 1.5714285714285714; + } +} + +.parameter-input.parameter-input--large { + padding: 10px 11px; + font-size: @font-size-input-lg; + + .parameter-input__placeholder { + font-size: @font-size-input-lg; + line-height: 1.5; + } + + .parameter-input__content__value { + font-size: @font-size-input-lg; + line-height: 1.5; + } + + .parameter-input__content__close-icon { + font-size: 12px; + } +} diff --git a/react-ui/src/components/ParameterInput/index.tsx b/react-ui/src/components/ParameterInput/index.tsx new file mode 100644 index 00000000..27ba2856 --- /dev/null +++ b/react-ui/src/components/ParameterInput/index.tsx @@ -0,0 +1,106 @@ +import { CloseOutlined } from '@ant-design/icons'; +import { Input } from 'antd'; +import classNames from 'classnames'; +import './index.less'; + +type ParameterInputData = { + value?: any; + showValue?: any; + fromSelect?: boolean; +} & Record; + +interface ParameterInputProps { + value?: ParameterInputData; + onChange?: (value: ParameterInputData) => void; + onClick?: () => void; + canInput?: boolean; + textArea?: boolean; + placeholder?: string; + allowClear?: boolean; + className?: string; + style?: React.CSSProperties; + size?: 'middle' | 'small' | 'large'; + disabled?: boolean; +} + +function ParameterInput({ + value, + onChange, + onClick, + canInput = true, + textArea = false, + placeholder, + allowClear, + className, + style, + size = 'middle', + disabled = false, + ...rest +}: ParameterInputProps) { + // console.log('ParameterInput', value); + + const valueObj = + typeof value === 'string' ? { value: value, fromSelect: false, showValue: value } : value; + if (valueObj && !valueObj.showValue) { + valueObj.showValue = valueObj.value; + } + const isSelect = valueObj?.fromSelect; + const InputComponent = textArea ? Input.TextArea : Input; + + return ( + <> + {(isSelect || !canInput) && !disabled ? ( +
+ {valueObj?.showValue ? ( +
+ {valueObj?.showValue} + { + e.stopPropagation(); + onChange?.({ + ...valueObj, + fromSelect: false, + value: undefined, + showValue: undefined, + }); + }} + /> +
+ ) : ( +
{placeholder}
+ )} +
+ ) : ( + + onChange?.({ + ...valueObj, + fromSelect: false, + value: e.target.value, + showValue: e.target.value, + }) + } + /> + )} + + ); +} + +export default ParameterInput; 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/components/ExperimentParameter/index.tsx b/react-ui/src/pages/Experiment/components/ExperimentParameter/index.tsx index 06fa87e9..f3528037 100644 --- a/react-ui/src/pages/Experiment/components/ExperimentParameter/index.tsx +++ b/react-ui/src/pages/Experiment/components/ExperimentParameter/index.tsx @@ -1,6 +1,9 @@ import SubAreaTitle from '@/components/SubAreaTitle'; +import { getComputingResourceReq } from '@/services/pipeline'; import { PipelineNodeModelSerialize } from '@/types'; -import { Form, Input, type FormProps } from 'antd'; +import { to } from '@/utils/promise'; +import { Form, Input, Select, type FormProps } from 'antd'; +import { useEffect, useState } from 'react'; import styles from './index.less'; const { TextArea } = Input; @@ -10,6 +13,25 @@ type ExperimentParameterProps = { }; function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) { + const [resourceStandardList, setResourceStandardList] = useState([]); // 资源规模列表 + + useEffect(() => { + getComputingResource(); + }, []); + + // 获取资源规格列表数据 + const getComputingResource = async () => { + const params = { + page: 0, + size: 1000, + resource_type: '', + }; + const [res] = await to(getComputingResourceReq(params)); + if (res && res.data && res.data.content) { + setResourceStandardList(res.data.content); + } + }; + // 控制策略 const controlStrategyList = Object.entries(nodeData.control_strategy ?? {}).map( ([key, value]) => ({ key, value }), @@ -103,7 +125,14 @@ function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) { }, ]} > - + @@ -117,7 +146,7 @@ function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) { name={['control_strategy', item.key]} label={item.value.label} getValueProps={(e) => { - return { value: e.value }; + return { value: e.showValue || e.value }; }} > @@ -133,7 +162,7 @@ function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) { label={item.value.label + '(' + item.key + ')'} rules={[{ required: item.value.require ? true : false }]} getValueProps={(e) => { - return { value: e.value }; + return { value: e.showValue || e.value }; }} > @@ -149,7 +178,7 @@ function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) { label={item.value.label + '(' + item.key + ')'} rules={[{ required: item.value.require ? true : false }]} getValueProps={(e) => { - return { value: e.value }; + return { value: e.showValue || e.value }; }} > diff --git a/react-ui/src/pages/Experiment/components/LogGroup/index.tsx b/react-ui/src/pages/Experiment/components/LogGroup/index.tsx index 0a89050c..f87d1d09 100644 --- a/react-ui/src/pages/Experiment/components/LogGroup/index.tsx +++ b/react-ui/src/pages/Experiment/components/LogGroup/index.tsx @@ -5,8 +5,8 @@ */ import { useStateRef } from '@/hooks'; -import { ExperimentLog } from '@/pages/Experiment/experimentText/props'; import { ExperimentStatus } from '@/pages/Experiment/status'; +import { ExperimentLog } from '@/pages/Experiment/training/props'; import { getExperimentPodsLog } from '@/services/experiment/index.js'; import { DoubleRightOutlined, DownOutlined, UpOutlined } from '@ant-design/icons'; import { Button } from 'antd'; diff --git a/react-ui/src/pages/Experiment/components/LogList/index.tsx b/react-ui/src/pages/Experiment/components/LogList/index.tsx index fcad0527..69ac89b2 100644 --- a/react-ui/src/pages/Experiment/components/LogList/index.tsx +++ b/react-ui/src/pages/Experiment/components/LogList/index.tsx @@ -1,5 +1,5 @@ -import { ExperimentLog } from '@/pages/Experiment/experimentText/props'; import { ExperimentStatus } from '@/pages/Experiment/status'; +import { ExperimentLog } from '@/pages/Experiment/training/props'; import LogGroup from '../LogGroup'; import styles from './index.less'; diff --git a/react-ui/src/pages/Experiment/index.jsx b/react-ui/src/pages/Experiment/index.jsx index 56548d6e..014a7c5b 100644 --- a/react-ui/src/pages/Experiment/index.jsx +++ b/react-ui/src/pages/Experiment/index.jsx @@ -1,3 +1,4 @@ +import CommonTableCell from '@/components/CommonTableCell'; import KFIcon from '@/components/KFIcon'; import { deleteExperimentById, @@ -198,7 +199,7 @@ function Experiment() { }; const routeToEdit = (e, record) => { e.stopPropagation(); - navgite({ pathname: `/pipeline/pytorchtext/${record.workflow_id}/${record.workflow_name}` }); + navgite({ pathname: `/pipeline/template/${record.workflow_id}/${record.workflow_name}` }); }; // 创建或者编辑实验接口请求 const handleAddExperiment = async (values) => { @@ -255,7 +256,7 @@ function Experiment() { }; const routerToText = (e, item, record) => { e.stopPropagation(); - navgite({ pathname: `/pipeline/experimentPytorchtext/${record.workflow_id}/${item.id}` }); + navgite({ pathname: `/pipeline/experiment/${record.workflow_id}/${item.id}` }); }; const handleTensorboard = async (experimentIn) => { @@ -290,6 +291,8 @@ function Experiment() { title: '实验描述', dataIndex: 'description', key: 'description', + render: CommonTableCell(true), + ellipsis: { showTitle: false }, }, { title: '最近五次运行状态', 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/Experiment/experimentText/index.jsx b/react-ui/src/pages/Experiment/training/index.jsx similarity index 100% rename from react-ui/src/pages/Experiment/experimentText/index.jsx rename to react-ui/src/pages/Experiment/training/index.jsx diff --git a/react-ui/src/pages/Experiment/experimentText/index.less b/react-ui/src/pages/Experiment/training/index.less similarity index 85% rename from react-ui/src/pages/Experiment/experimentText/index.less rename to react-ui/src/pages/Experiment/training/index.less index 8c341aa5..f2f5510d 100644 --- a/react-ui/src/pages/Experiment/experimentText/index.less +++ b/react-ui/src/pages/Experiment/training/index.less @@ -27,5 +27,7 @@ width: 100%; height: calc(100% - 56px); background-color: @background-color; + background-image: url(/assets/images/pipeline-canvas-back.png); + background-size: 100% 100%; } } diff --git a/react-ui/src/pages/Experiment/experimentText/props.less b/react-ui/src/pages/Experiment/training/props.less similarity index 100% rename from react-ui/src/pages/Experiment/experimentText/props.less rename to react-ui/src/pages/Experiment/training/props.less diff --git a/react-ui/src/pages/Experiment/experimentText/props.tsx b/react-ui/src/pages/Experiment/training/props.tsx similarity index 100% rename from react-ui/src/pages/Experiment/experimentText/props.tsx rename to react-ui/src/pages/Experiment/training/props.tsx diff --git a/react-ui/src/pages/Mirror/create.less b/react-ui/src/pages/Mirror/Create/index.less similarity index 100% rename from react-ui/src/pages/Mirror/create.less rename to react-ui/src/pages/Mirror/Create/index.less diff --git a/react-ui/src/pages/Mirror/create.tsx b/react-ui/src/pages/Mirror/Create/index.tsx similarity index 97% rename from react-ui/src/pages/Mirror/create.tsx rename to react-ui/src/pages/Mirror/Create/index.tsx index f2d1f86f..ed709772 100644 --- a/react-ui/src/pages/Mirror/create.tsx +++ b/react-ui/src/pages/Mirror/Create/index.tsx @@ -11,13 +11,17 @@ 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'; import { omit } from 'lodash'; import { useEffect, useState } from 'react'; -import styles from './create.less'; +import styles from './index.less'; type FormData = { name: string; @@ -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/info.less b/react-ui/src/pages/Mirror/Info/index.less similarity index 100% rename from react-ui/src/pages/Mirror/info.less rename to react-ui/src/pages/Mirror/Info/index.less diff --git a/react-ui/src/pages/Mirror/info.tsx b/react-ui/src/pages/Mirror/Info/index.tsx similarity index 98% rename from react-ui/src/pages/Mirror/info.tsx rename to react-ui/src/pages/Mirror/Info/index.tsx index 4fd37c6a..18c21a6e 100644 --- a/react-ui/src/pages/Mirror/info.tsx +++ b/react-ui/src/pages/Mirror/Info/index.tsx @@ -34,8 +34,8 @@ import { } from 'antd'; import classNames from 'classnames'; import { useEffect, useState } from 'react'; -import MirrorStatusCell from './components/MirrorStatusCell'; -import styles from './info.less'; +import MirrorStatusCell from '../components/MirrorStatusCell'; +import styles from './index.less'; type MirrorInfoData = { name?: string; diff --git a/react-ui/src/pages/Mirror/list.less b/react-ui/src/pages/Mirror/List/index.less similarity index 83% rename from react-ui/src/pages/Mirror/list.less rename to react-ui/src/pages/Mirror/List/index.less index 9f2905e9..6acc2b14 100644 --- a/react-ui/src/pages/Mirror/list.less +++ b/react-ui/src/pages/Mirror/List/index.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/index.tsx similarity index 97% rename from react-ui/src/pages/Mirror/list.tsx rename to react-ui/src/pages/Mirror/List/index.tsx index 16bfeb1a..70a5ff10 100644 --- a/react-ui/src/pages/Mirror/list.tsx +++ b/react-ui/src/pages/Mirror/List/index.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 { @@ -27,7 +28,7 @@ import { import { type SearchProps } from 'antd/es/input'; import classNames from 'classnames'; import { useEffect, useState } from 'react'; -import styles from './list.less'; +import styles from './index.less'; const mirrorTabItems = [ { @@ -145,6 +146,7 @@ function MirrorList() { // 创建镜像 const createMirror = () => { navigate(`/dataset/mirror/create`); + setSessionStorageItem(mirrorNameKey, ''); setCacheState({ activeTab, pagination, 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/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 913419c0..cd10e0d8 100644 --- a/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.less +++ b/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.less @@ -1,20 +1,29 @@ -.form_item_block { +.form-item { position: relative; padding-top: 40px; border-bottom: 1px dashed rgba(20, 49, 179, 0.12); + + &__delete-button { + position: absolute; + top: 5px; + right: 24px; + } + + :global { + .anticon.anticon-question-circle { + margin-top: -14px; + } + } } -.delete_button { - position: absolute; - top: 5px; - right: 0; -} -.add_button_form_item { + +.form-item-add { margin-top: 15px; &:first-child { margin-top: 0; } -} -.add_button_form_item .add_button { - padding: 0; + + &__add-button { + padding: 0; + } } diff --git a/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx b/react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.tsx index e61423fd..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'; @@ -55,14 +56,14 @@ const GlobalParamsDrawer = forwardRef( getContainer={false} onClose={onClose} open={open} - width={420} + width={520} >
@@ -70,7 +71,7 @@ const GlobalParamsDrawer = forwardRef( {(fields, { add, remove }) => ( <> {fields.map(({ key, name, ...restField }) => ( -
+
))} - + + e.preventDefault()}>参数
); 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..45cf6a1e 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 }; -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/editPipeline.less b/react-ui/src/pages/Pipeline/editPipeline/editPipeline.less deleted file mode 100644 index 382cc587..00000000 --- a/react-ui/src/pages/Pipeline/editPipeline/editPipeline.less +++ /dev/null @@ -1,80 +0,0 @@ -#graph { - position: relative; - width: 100%; - height: 100%; -} -.editPipelineProps { - :global { - label { - width: 100%; - - &::after { - display: none; - } - } - } -} -.editPipelinePropsContent { - display: flex; - align-items: center; - width: 100%; - height: 43px; - margin-bottom: 20px; - padding: 0 24px; - color: #1d1d20; - font-size: 15px; - font-family: 'Alibaba'; - background: #f8fbff; -} -.centerContainer { - display: flex; - flex: 1; - flex-direction: column; -} -.buttonList { - display: flex; - align-items: center; - justify-content: end; - width: 100%; - height: 45px; - padding: 0 30px; - background: #ffffff; - box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); -} -.rightmenu { - position: absolute; - top: 0px; - left: 0px; - width: 120px; - height: 146px; - overflow-y: auto; - color: #333333; - font-size: 12px; - - background-color: #ffffff; -} - -.rightmenuItem { - padding: 10px 20px; - cursor: pointer; -} -.rightmenuItem:hover { - color: #ffffff; - background-color: rgba(24, 144, 255, 0.3); -} - -.ref-row { - display: flex; - align-items: center; - - .select-button { - display: flex; - flex: none; - align-items: center; - justify-content: flex-start; - width: 100px; - margin-left: 10px; - padding-right: 0; - padding-left: 0; - } -} diff --git a/react-ui/src/pages/Pipeline/editPipeline/index.jsx b/react-ui/src/pages/Pipeline/editPipeline/index.jsx index fe421514..b44a8f6e 100644 --- a/react-ui/src/pages/Pipeline/editPipeline/index.jsx +++ b/react-ui/src/pages/Pipeline/editPipeline/index.jsx @@ -2,14 +2,13 @@ import KFIcon from '@/components/KFIcon'; import { useStateRef, useVisible } from '@/hooks'; import { getWorkflowById, saveWorkflow } from '@/services/pipeline/index.js'; import { to } from '@/utils/promise'; -import { useEmotionCss } from '@ant-design/use-emotion-css'; import G6 from '@antv/g6'; import { Button, message } from 'antd'; import { useEffect, useRef } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { s8 } from '../../../utils'; import GlobalParamsDrawer from '../components/GlobalParamsDrawer'; -import Styles from './editPipeline.less'; +import styles from './index.less'; import ModelMenus from './modelMenus'; import Props from './props'; import { findAllParentNodes, findFirstDuplicate } from './utils'; @@ -20,42 +19,6 @@ const EditPipeline = () => { const navgite = useNavigate(); let contextMenu = {}; const locationParams = useParams(); //新版本获取路由参数接口 - - const pipelineContainer = useEmotionCss(() => { - return { - display: 'flex', - backgroundColor: '#fff', - height: '98vh', - position: 'relative', - }; - }); - const rightmenu = useEmotionCss(() => { - return { - position: 'absolute', - width: '120px', - height: '146px', - left: '0px', - top: '0px', - - color: '#333333', - overflowY: 'auto', - }; - }); - const rightmenuItem = useEmotionCss(() => { - return { - padding: '10px 20px', - cursor: 'pointer', - fontSize: '12px', - }; - }); - const graphStyle = useEmotionCss(() => { - return { - width: '100%', - backgroundSize: '100% 100%', - backgroundImage: 'url(/assets/images/pipeline-canvas-back.png)', - flex: 1, - }; - }); const graphRef = useRef(); const paramsDrawerRef = useRef(); const propsRef = useRef(); @@ -65,7 +28,7 @@ const EditPipeline = () => { let sourceAnchorIdx, targetAnchorIdx; const onDragEnd = (val) => { - console.log(val, 'eee'); + console.log(val); const _x = val.x; const _y = val.y; const point = graph.getPointByClient(_x, _y); @@ -78,10 +41,8 @@ const EditPipeline = () => { id: val.component_name + '-' + s8(), isCluster: false, }; - console.log(graph, model); - + console.log('model', model); graph.addItem('node', model, true); - console.log(graph); }; const formChange = (val) => { if (graph) { @@ -110,7 +71,6 @@ const EditPipeline = () => { } const [propsRes, propsError] = await to(propsRef.current.getFieldsValue()); - console.log(await to(propsRef.current.getFieldsValue())); if (propsError) { message.error('基本信息必填项需配置'); return; @@ -147,7 +107,6 @@ const EditPipeline = () => { } }; const getGraphData = (data) => { - console.log('graph', graph); if (graph) { console.log(data); graph.data(data); @@ -230,6 +189,7 @@ const EditPipeline = () => { } } + // eslint-disable-next-line for (const key in edgeMap) { const arcEdges = edgeMap[key]; const { length } = arcEdges; @@ -472,7 +432,7 @@ const EditPipeline = () => { height: graphRef.current.clientHeight || '100%', animate: false, groupByTypes: false, - fitView: true, + fitView: false, plugins: [contextMenu], enabledStack: true, modes: { @@ -730,10 +690,10 @@ const EditPipeline = () => { }; }; return ( -
+
-
-
+
+
-
+
{ }; const { Panel } = Collapse; return ( -
+
组件库
{modelMenusList && modelMenusList.length > 0 ? ( .ant-collapse-item > .ant-collapse-header { margin-bottom: 5px; + padding: 20px 16px 15px 16px; background-color: #fff; border-color: transparent; - padding: 20px 16px 15px 16px; } .ant-collapse > .ant-collapse-item { margin: 0 10px; - border-bottom:0.5px dashed rgba(20, 49, 179, 0.12); + border-bottom: 0.5px dashed rgba(20, 49, 179, 0.12); border-radius: 0px; } .ant-collapse .ant-collapse-content { @@ -41,10 +30,23 @@ } } } -.modelMenusTitle{ - padding: 12px 25px; +.collapseItem { + display: flex; + align-items: center; + height: 40px; + padding: 0 16px; + color: #575757; + font-size: 14px; + border-radius: 4px; + cursor: pointer; +} +.collapseItem:hover { + color: #1664ff; + background: rgba(22, 100, 255, 0.08); +} +.modelMenusTitle { margin-bottom: 10px; - color:#111111; -font-size:16px; -font-family: 'Alibaba'; -} \ No newline at end of file + padding: 12px 25px; + color: #111111; + font-size: 16px; +} diff --git a/react-ui/src/pages/Pipeline/editPipeline/props.jsx b/react-ui/src/pages/Pipeline/editPipeline/props.jsx index 179cd152..7436c865 100644 --- a/react-ui/src/pages/Pipeline/editPipeline/props.jsx +++ b/react-ui/src/pages/Pipeline/editPipeline/props.jsx @@ -1,4 +1,5 @@ import KFIcon from '@/components/KFIcon'; +import ParameterInput from '@/components/ParameterInput'; import SubAreaTitle from '@/components/SubAreaTitle'; import { getComputingResourceReq } from '@/services/pipeline'; import { openAntdModal } from '@/utils/modal'; @@ -8,8 +9,8 @@ import { pick } from 'lodash'; import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; import PropsLabel from '../components/PropsLabel'; import ResourceSelectorModal, { ResourceSelectorType } from '../components/ResourceSelectorModal'; -import styles from './editPipeline.less'; -import { createMenuItems } from './utils'; +import styles from './props.less'; +import { canInput, createMenuItems } from './utils'; const { TextArea } = Input; const Props = forwardRef(({ onParentChange }, ref) => { @@ -40,7 +41,7 @@ const Props = forwardRef(({ onParentChange }, ref) => { const afterOpenChange = () => { if (!open) { - console.log('zzzz', form.getFieldsValue()); + console.log('zzzzz', form.getFieldsValue()); const control_strategy = form.getFieldValue('control_strategy'); const in_parameters = form.getFieldValue('in_parameters'); const out_parameters = form.getFieldValue('out_parameters'); @@ -77,6 +78,7 @@ const Props = forwardRef(({ onParentChange }, ref) => { out_parameters: JSON.parse(model.out_parameters), control_strategy: JSON.parse(model.control_strategy), }; + console.log('model', nodeData); setStagingItem({ ...nodeData, }); @@ -95,11 +97,11 @@ const Props = forwardRef(({ onParentChange }, ref) => { } }, propClose: () => { - close(); + onClose(); }, })); - // 选择数据集、模型 + // 选择数据集、模型、镜像 const selectResource = (name, item) => { let type; let resource; @@ -128,19 +130,20 @@ const Props = forwardRef(({ onParentChange }, ref) => { } else { const jsonObj = pick(res, ['id', 'version', 'path']); const value = JSON.stringify(jsonObj); - form.setFieldValue(name, { ...item, value }); - } + const showValue = `${res.name}:${res.version}`; + form.setFieldValue(name, { ...item, value, showValue, fromSelect: true }); - if (type === ResourceSelectorType.Dataset) { - setSelectedDataset(res); - } else if (type === ResourceSelectorType.Model) { - setSelectedModel(res); + if (type === ResourceSelectorType.Dataset) { + setSelectedDataset(res); + } else if (type === ResourceSelectorType.Model) { + setSelectedModel(res); + } } } else { if (type === ResourceSelectorType.Dataset) { - setSelectedDataset(null); + setSelectedDataset(undefined); } else if (type === ResourceSelectorType.Model) { - setSelectedModel(null); + setSelectedModel(undefined); } form.setFieldValue(name, ''); } @@ -188,275 +191,276 @@ const Props = forwardRef(({ onParentChange }, ref) => { ); return ( - <> - + - + +
+ -
- -
- - - - - - -
- + + + + + +
+ +
+ +
+ + + + + +
- -
- - - - - - -
-
- { - handleParameterClick('working_directory', value); - }} - /> - } - > - - +
+ { + handleParameterClick('working_directory', value); + }} + /> + } + > + + + { + handleParameterClick('command', value); + }} + /> + } + > +