From 877816cb14e196ceddeb72a268fc9a224221bf6b Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Wed, 4 Sep 2024 10:27:26 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=E4=BF=AE=E6=94=B9=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E7=AE=A1=E7=90=86-=E8=A7=92=E8=89=B2=E5=88=86?= =?UTF-8?q?=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- react-ui/src/components/IFramePage/index.tsx | 2 +- react-ui/src/pages/System/Role/authUser.tsx | 2 +- .../src/pages/System/Role/components/DataScope.tsx | 10 ++++------ .../System/Role/components/UserSelectorModal.less | 12 ++++++++++++ .../System/Role/components/UserSelectorModal.tsx | 4 +++- .../src/pages/System/User/components/AuthRole.tsx | 10 +++++----- .../src/pages/System/User/components/ResetPwd.tsx | 8 ++++++-- 7 files changed, 32 insertions(+), 16 deletions(-) create mode 100644 react-ui/src/pages/System/Role/components/UserSelectorModal.less diff --git a/react-ui/src/components/IFramePage/index.tsx b/react-ui/src/components/IFramePage/index.tsx index 4db0c7ba..f76420a6 100644 --- a/react-ui/src/components/IFramePage/index.tsx +++ b/react-ui/src/components/IFramePage/index.tsx @@ -61,7 +61,7 @@ function IframePage({ type, className, style }: IframePageProps) { return (
- {loading && } + {loading && }
); diff --git a/react-ui/src/pages/System/Role/authUser.tsx b/react-ui/src/pages/System/Role/authUser.tsx index 2ba3f3a1..44678525 100644 --- a/react-ui/src/pages/System/Role/authUser.tsx +++ b/react-ui/src/pages/System/Role/authUser.tsx @@ -129,7 +129,7 @@ const AuthUserTableList: React.FC = () => { { title: , dataIndex: 'option', - width: '60px', + width: '160px', valueType: 'option', render: (_, record) => [ + )} + + )} + + ); +} + +export default KFEmpty; diff --git a/react-ui/src/components/KFSpin/index.less b/react-ui/src/components/KFSpin/index.less index 931e7ea0..56ff13a1 100644 --- a/react-ui/src/components/KFSpin/index.less +++ b/react-ui/src/components/KFSpin/index.less @@ -4,7 +4,7 @@ right: 0; bottom: 0; left: 0; - z-index: 1000; + z-index: 1001; display: flex; flex-direction: column; align-items: center; diff --git a/react-ui/src/components/ResourceSelect/index.tsx b/react-ui/src/components/ResourceSelect/index.tsx index 96f5e96e..a7ca6514 100644 --- a/react-ui/src/components/ResourceSelect/index.tsx +++ b/react-ui/src/components/ResourceSelect/index.tsx @@ -11,6 +11,7 @@ import ParameterInput, { type ParameterInputProps } from '../ParameterInput'; import './index.less'; export { requiredValidator, type ParameterInputObject } from '../ParameterInput'; +export { ResourceSelectorType, selectorTypeConfig, type ResourceSelectorResponse }; type ResourceSelectProps = { type: ResourceSelectorType; diff --git a/react-ui/src/iconfont/iconfont.js b/react-ui/src/iconfont/iconfont.js index 5326fd3a..c9da6580 100644 --- a/react-ui/src/iconfont/iconfont.js +++ b/react-ui/src/iconfont/iconfont.js @@ -1 +1 @@ -window._iconfont_svg_string_4511447='',(t=>{var a=(h=(h=document.getElementsByTagName("script"))[h.length-1]).getAttribute("data-injectcss"),h=h.getAttribute("data-disable-injectsvg");if(!h){var l,v,z,i,o,m=function(a,h){h.parentNode.insertBefore(a,h)};if(a&&!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 +window._iconfont_svg_string_4511447='',(t=>{var a=(h=(h=document.getElementsByTagName("script"))[h.length-1]).getAttribute("data-injectcss"),h=h.getAttribute("data-disable-injectsvg");if(!h){var l,v,z,i,o,m=function(a,h){h.parentNode.insertBefore(a,h)};if(a&&!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/overrides.less b/react-ui/src/overrides.less index e20ad0e4..af9591fe 100644 --- a/react-ui/src/overrides.less +++ b/react-ui/src/overrides.less @@ -116,6 +116,10 @@ } } + .ant-input.ant-input-disabled { + height: 46px; + } + // 选择框高度为46px .ant-select-single { height: 46px; diff --git a/react-ui/src/pages/404.tsx b/react-ui/src/pages/404.tsx index 0263687e..23242d56 100644 --- a/react-ui/src/pages/404.tsx +++ b/react-ui/src/pages/404.tsx @@ -1,18 +1,20 @@ -import { history } from '@umijs/max'; -import { Button, Result } from 'antd'; -import React from 'react'; +import KFEmpty, { EmptyType } from '@/components/KFEmpty'; +import { useNavigate } from '@umijs/max'; -const NoFoundPage: React.FC = () => ( - history.push('/')}> - Back Home - - } - /> -); +const NoFoundPage = () => { + const navigate = useNavigate(); + + return ( + navigate('/')} + > + ); +}; export default NoFoundPage; diff --git a/react-ui/src/pages/CodeConfig/List/index.tsx b/react-ui/src/pages/CodeConfig/List/index.tsx index 316f5925..1a307751 100644 --- a/react-ui/src/pages/CodeConfig/List/index.tsx +++ b/react-ui/src/pages/CodeConfig/List/index.tsx @@ -1,9 +1,10 @@ +import KFEmpty, { EmptyType } from '@/components/KFEmpty'; import KFIcon from '@/components/KFIcon'; import { deleteCodeConfigReq, getCodeConfigListReq } from '@/services/codeConfig'; import { openAntdModal } from '@/utils/modal'; import { to } from '@/utils/promise'; import { modalConfirm } from '@/utils/ui'; -import { App, Button, Empty, Input, Pagination, PaginationProps } from 'antd'; +import { App, Button, Input, Pagination, PaginationProps } from 'antd'; import { useEffect, useState } from 'react'; import AddCodeConfigModal, { OperationType } from '../components/AddCodeConfigModal'; import CodeConfigItem from '../components/CodeConfigItem'; @@ -31,7 +32,7 @@ export type ResourceListRef = { }; function CodeConfigList() { - const [dataList, setDataList] = useState([]); + const [dataList, setDataList] = useState(undefined); const [total, setTotal] = useState(0); const [pagination, setPagination] = useState({ current: 1, @@ -56,6 +57,9 @@ function CodeConfigList() { if (res && res.data && res.data.content) { setDataList(res.data.content); setTotal(res.data.totalElements); + } else { + setDataList([]); + setTotal(0); } }; @@ -117,7 +121,7 @@ function CodeConfigList() { return (
- 数据总数:{total}个 + 数据总数:{total} 个
- {dataList?.length !== 0 ? ( + {dataList && dataList.length !== 0 && ( <>
- {dataList?.map((item) => ( + {dataList.map((item) => ( - ) : ( -
- -
+ )} + {dataList && dataList.length === 0 && ( + )}
); diff --git a/react-ui/src/pages/Dataset/components/AddDatasetModal/index.less b/react-ui/src/pages/Dataset/components/AddDatasetModal/index.less index 66022fdd..428395bd 100644 --- a/react-ui/src/pages/Dataset/components/AddDatasetModal/index.less +++ b/react-ui/src/pages/Dataset/components/AddDatasetModal/index.less @@ -1,6 +1,7 @@ .upload-tip { margin-top: 5px; - color: @error-color; + color: @text-color-secondary; + font-size: 14px; } .upload-button { diff --git a/react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx b/react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx index 97b93fdf..bf3dc0d1 100644 --- a/react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx +++ b/react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx @@ -1,9 +1,7 @@ import { getAccessToken } from '@/access'; -import { DictValueEnumObj } from '@/components/DictTag'; import KFIcon from '@/components/KFIcon'; import KFModal from '@/components/KFModal'; -import { addDatesetAndVesion } from '@/services/dataset/index.js'; -import { getDictSelectOption } from '@/services/system/dict'; +import { addDateset } from '@/services/dataset/index.js'; import { to } from '@/utils/promise'; import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; import { @@ -19,7 +17,7 @@ import { type UploadProps, } from 'antd'; import { omit } from 'lodash'; -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import { CategoryData } from '../../config'; import styles from './index.less'; @@ -31,15 +29,15 @@ interface AddDatasetModalProps extends Omit { function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalProps) { const [uuid] = useState(Date.now()); - const [clusterOptions, setClusterOptions] = useState([]); + // const [clusterOptions, setClusterOptions] = useState([]); - useEffect(() => { - getClusterOptions(); - }, []); + // useEffect(() => { + // getClusterOptions(); + // }, []); // 上传组件参数 const uploadProps: UploadProps = { - action: '/api/mmp/dataset/upload', + action: '/api/mmp/newdataset/upload', headers: { Authorization: getAccessToken() || '', }, @@ -47,16 +45,16 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr }; // 获取集群版本数据 - const getClusterOptions = async () => { - const [res] = await to(getDictSelectOption('available_cluster')); - if (res) { - setClusterOptions(res); - } - }; + // const getClusterOptions = async () => { + // const [res] = await to(getDictSelectOption('available_cluster')); + // if (res) { + // setClusterOptions(res); + // } + // }; // 上传请求 const createDataset = async (params: any) => { - const [res] = await to(addDatesetAndVesion(params)); + const [res] = await to(addDateset(params)); if (res) { message.success('创建成功'); onOk?.(); @@ -94,7 +92,13 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr }} destroyOnClose > -
+ - + @@ -135,14 +139,14 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr allowClear placeholder="请选择研究方向/应用领域" options={tagList} - fieldNames={{ label: 'name', value: 'id' }} + fieldNames={{ label: 'name', value: 'name' }} optionFilterProp="name" showSearch /> - + {/* {resourceType === ResourceType.Dataset && ( -
只允许上传.zip格式文件
+
只允许上传 .zip 格式文件
)}
diff --git a/react-ui/src/pages/Dataset/components/ResourceInfo/index.less b/react-ui/src/pages/Dataset/components/ResourceInfo/index.less new file mode 100644 index 00000000..9da228b4 --- /dev/null +++ b/react-ui/src/pages/Dataset/components/ResourceInfo/index.less @@ -0,0 +1,59 @@ +.resource-info { + height: 100%; + + &__top { + width: 100%; + height: 125px; + margin-bottom: 10px; + padding: 20px 30px; + background-image: url(@/assets/img/dataset-intro-top.png); + background-repeat: no-repeat; + background-position: top center; + background-size: 100% 100%; + + &__name { + margin-right: 10px; + color: @text-color; + font-weight: 500; + font-size: 20px; + } + + &__tag { + padding: 4px 10px; + color: @primary-color; + font-size: 14px; + background: .addAlpha(@primary-color, 0.1) []; + border-radius: 4px; + } + + :global { + .ant-btn-dangerous { + background-color: transparent !important; + } + } + } + + &__bottom { + height: calc(100% - 135px); + padding: 8px 30px 20px; + background: #ffffff; + border-radius: 10px; + box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09); + + :global { + .ant-tabs { + height: 100%; + .ant-tabs-content-holder { + height: 100%; + .ant-tabs-content { + height: 100%; + .ant-tabs-tabpane { + height: 100%; + overflow-y: auto; + } + } + } + } + } + } +} diff --git a/react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx b/react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx new file mode 100644 index 00000000..9aa752b5 --- /dev/null +++ b/react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx @@ -0,0 +1,237 @@ +/* + * @Author: 赵伟 + * @Date: 2024-09-06 09:23:15 + * @Description: 数据集、模型详情 + */ + +import KFIcon from '@/components/KFIcon'; +import { + ResourceData, + ResourceType, + ResourceVersionData, + resourceConfig, +} from '@/pages/Dataset/config'; +import ModelEvolution from '@/pages/Model/components/ModelEvolution'; +import { openAntdModal } from '@/utils/modal'; +import { to } from '@/utils/promise'; +import { getSessionStorageItem, resourceItemKey } from '@/utils/sessionStorage'; +import { modalConfirm } from '@/utils/ui'; +import { useParams, useSearchParams } from '@umijs/max'; +import { App, Button, Flex, Select, Tabs } from 'antd'; +import { pick } from 'lodash'; +import { useEffect, useState } from 'react'; +import AddVersionModal from '../AddVersionModal'; +import ResourceIntro from '../ResourceIntro'; +import ResourceVersion from '../ResourceVersion'; +import styles from './index.less'; + +// 这里值小写是因为值会写在 url 中 +export enum ResourceInfoTabKeys { + Introduction = 'introduction', // 简介 + Version = 'version', // 版本 + Evolution = 'evolution', // 演化 +} + +type ResourceInfoProps = { + resourceType: ResourceType; +}; + +const ResourceInfo = ({ resourceType }: ResourceInfoProps) => { + const [info, setInfo] = useState({} as ResourceData); + const locationParams = useParams(); + const [searchParams] = useSearchParams(); + // 模型演化传入的 tab + const defaultTab = searchParams.get('tab') || ResourceInfoTabKeys.Introduction; + // 模型演化传入的版本 + let versionParam = searchParams.get('version'); + const [versionList, setVersionList] = useState([]); + const [version, setVersion] = useState(undefined); + const [activeTab, setActiveTab] = useState(defaultTab); + const resourceId = Number(locationParams.id); + const config = resourceConfig[resourceType]; + const typeName = config.name; // 数据集/模型 + const { message } = App.useApp(); + + useEffect(() => { + const info = getSessionStorageItem(resourceItemKey, true); + if (info) { + setInfo(info); + getVersionList(pick(info, ['owner', 'identifier'])); + } + }, [resourceId]); + + useEffect(() => { + if (version) { + getResourceDetail({ + ...pick(info, ['owner', 'name', 'id', 'identifier']), + version, + }); + } + }, [version]); + + // 获取详情 + const getResourceDetail = async (params: { + owner: string; + name: string; + id: number; + identifier: string; + version?: string; + }) => { + const request = config.getInfo; + const [res] = await to(request(params)); + if (res) { + setInfo(res.data); + } + }; + + // 获取版本列表 + const getVersionList = async (params: { owner: string; identifier: string }) => { + const request = config.getVersions; + const [res] = await to(request(params)); + if (res && res.data && res.data.length > 0) { + setVersionList(res.data); + if ( + versionParam && + res.data.find((item: ResourceVersionData) => item.name === versionParam) + ) { + setVersion(versionParam); + versionParam = null; + } else { + setVersion(res.data[0].name); + } + } else { + setVersion(undefined); + } + }; + + // 新建版本 + const showModal = () => { + const { close } = openAntdModal(AddVersionModal, { + resourceType: resourceType, + resourceId: resourceId, + resoureName: info.name, + identifier: info.identifier, + onOk: () => { + getVersionList(pick(info, ['owner', 'identifier'])); + close(); + }, + }); + }; + + // 版本变化 + const handleVersionChange = (value: string) => { + setVersion(value); + }; + + // 删除版本 + const deleteVersion = async () => { + const request = config.deleteVersion; + const params = { + identifier: info.identifier, + owner: info.owner, + version, + }; + const [res] = await to(request(params)); + if (res) { + message.success('删除成功'); + setVersion(undefined); + getVersionList(pick(info, ['owner', 'identifier'])); + } + }; + + // 处理删除 + const hanldeDelete = () => { + modalConfirm({ + title: '删除后,该版本将不可恢复', + content: '是否确认删除?', + okText: '确认', + cancelText: '取消', + onOk: () => { + deleteVersion(); + }, + }); + }; + + const items = [ + { + key: ResourceInfoTabKeys.Introduction, + label: `${typeName}简介`, + icon: , + children: , + }, + { + key: ResourceInfoTabKeys.Version, + label: `${typeName}文件`, + icon: , + children: , + }, + ]; + + if (resourceType === ResourceType.Model) { + items.push({ + key: ResourceInfoTabKeys.Evolution, + label: `模型演化`, + icon: , + children: ( + + ), + }); + } + + const infoTypePropertyName = config.infoTypePropertyName as keyof ResourceData; + const infoTagPropertyName = config.infoTagPropertyName as keyof ResourceData; + + return ( +
+
+ +
{info.name}
+ {info[infoTypePropertyName] && ( +
+ {(info[infoTypePropertyName] as string) || '--'} +
+ )} + {info[infoTagPropertyName] && ( +
+ {(info[infoTagPropertyName] as string) || '--'} +
+ )} +
+ + 版本号: + - - - - {!isPublic && ( - - )} -
- {fileList.length > 0 && fileList[0].description - ? '版本描述:' + fileList[0].description - : null} -
); diff --git a/react-ui/src/pages/Dataset/config.tsx b/react-ui/src/pages/Dataset/config.tsx index 822b7bfe..e40aaa4a 100644 --- a/react-ui/src/pages/Dataset/config.tsx +++ b/react-ui/src/pages/Dataset/config.tsx @@ -1,19 +1,17 @@ import KFIcon from '@/components/KFIcon'; import { CommonTabKeys } from '@/enums'; import { - addDatasetVersionDetail, + addDatasetVersion, addModelsVersionDetail, deleteDataset, deleteDatasetVersion, deleteModel, deleteModelVersion, - getDatasetById, + getDatasetInfo, getDatasetList, - getDatasetVersionIdList, - getDatasetVersionsById, + getDatasetVersionList, getModelById, getModelList, - getModelVersionIdList, getModelVersionsById, } from '@/services/dataset/index.js'; import type { TabsProps } from 'antd'; @@ -26,7 +24,6 @@ export enum ResourceType { type ResourceTypeInfo = { getList: (params: any) => Promise; // 获取资源列表 getVersions: (params: any) => Promise; // 获取版本列表 - getFiles: (params: any) => Promise; // 获取版本下的文件列表 deleteRecord: (params: any) => Promise; // 删除 addVersion: (params: any) => Promise; // 新增版本 deleteVersion: (params: any) => Promise; // 删除版本 @@ -55,12 +52,11 @@ type ResourceTypeInfo = { export const resourceConfig: Record = { [ResourceType.Dataset]: { getList: getDatasetList, - getVersions: getDatasetVersionsById, - getFiles: getDatasetVersionIdList, + getVersions: getDatasetVersionList, deleteRecord: deleteDataset, - addVersion: addDatasetVersionDetail, + addVersion: addDatasetVersion, deleteVersion: deleteDatasetVersion, - getInfo: getDatasetById, + getInfo: getDatasetInfo, name: '数据集', typeParamKey: 'data_type', tagParamKey: 'data_tag', @@ -85,17 +81,16 @@ export const resourceConfig: Record = { deleteModalTitle: '确定删除该条数据集实例吗?', addBtnTitle: '新建数据集', idParamKey: 'dataset_id', - uploadAction: '/api/mmp/dataset/upload', + uploadAction: '/api/mmp/newdataset/upload', uploadAccept: '.zip,.tgz', - downloadAllAction: '/api/mmp/dataset/downloadAllFilesl', - downloadSingleAction: '/api/mmp/dataset/download', - infoTypePropertyName: 'dataset_type_name', - infoTagPropertyName: 'dataset_tag_name', + downloadAllAction: '/api/mmp/newdataset/downloadAllFiles', + downloadSingleAction: '/api/mmp/newdataset/downloadSinggerFile', + infoTypePropertyName: 'data_type', + infoTagPropertyName: 'data_tag', }, [ResourceType.Model]: { getList: getModelList, getVersions: getModelVersionsById, - getFiles: getModelVersionIdList, deleteRecord: deleteModel, addVersion: addModelsVersionDetail, deleteVersion: deleteModelVersion, @@ -145,32 +140,37 @@ export type CategoryData = { export type ResourceData = { id: number; name: string; + identifier: string; description: string; create_by: string; + owner: string; update_time: string; - available_range: number; + time_ago: string; + is_public: boolean; model_type_name?: string; model_tag_name?: string; - dataset_type_name?: string; - dataset_tag_name?: string; + data_type?: string; + data_tag?: string; + version?: string; + version_desc?: string; + processing_code?: string; + dataset_source?: string; + usage?: string; + dataset_version_vos: ResourceFileData[]; }; // 版本数据 export type ResourceVersionData = { - label: string; - value: string; + name: string; + http_url: string; + tar_url: string; + zip_url: string; }; // 版本文件数据 export type ResourceFileData = { - id: number; file_name: string; file_size: string; - description: string; - create_by: string; - create_time: string; - update_by: string; - update_time: string; url: string; - version: string; + update_time?: string; }; diff --git a/react-ui/src/pages/Dataset/intro.tsx b/react-ui/src/pages/Dataset/intro.tsx index 6a9e14ec..e40f8288 100644 --- a/react-ui/src/pages/Dataset/intro.tsx +++ b/react-ui/src/pages/Dataset/intro.tsx @@ -1,8 +1,8 @@ -import ResourceIntro from '@/pages/Dataset/components/ResourceIntro'; +import ResourceInfo from '@/pages/Dataset/components/ResourceInfo'; import { ResourceType } from '@/pages/Dataset/config'; -function DatasetIntro() { - return ; +function DatasetInfo() { + return ; } -export default DatasetIntro; +export default DatasetInfo; diff --git a/react-ui/src/pages/DevelopmentEnvironment/Create/index.tsx b/react-ui/src/pages/DevelopmentEnvironment/Create/index.tsx index 4278a358..73a2006f 100644 --- a/react-ui/src/pages/DevelopmentEnvironment/Create/index.tsx +++ b/react-ui/src/pages/DevelopmentEnvironment/Create/index.tsx @@ -8,11 +8,11 @@ import KFRadio, { type KFRadioItem } from '@/components/KFRadio'; import PageTitle from '@/components/PageTitle'; import ResourceSelect, { requiredValidator, + ResourceSelectorType, type ParameterInputObject, } from '@/components/ResourceSelect'; import SubAreaTitle from '@/components/SubAreaTitle'; import { useComputingResource } from '@/hooks/resource'; -import { ResourceSelectorType } from '@/pages/Pipeline/components/ResourceSelectorModal'; import { createEditorReq } from '@/services/developmentEnvironment'; import { to } from '@/utils/promise'; import { useNavigate } from '@umijs/max'; @@ -90,7 +90,6 @@ function EditorCreate() { { const type = item.item_type; + if (type === 'code') { + return ; + } + let selectorType: ResourceSelectorType; if (type === 'dataset') { selectorType = ResourceSelectorType.Dataset; diff --git a/react-ui/src/pages/Pipeline/components/ResourceSelectorModal/config.tsx b/react-ui/src/pages/Pipeline/components/ResourceSelectorModal/config.tsx index 01e30ab0..c1619ad0 100644 --- a/react-ui/src/pages/Pipeline/components/ResourceSelectorModal/config.tsx +++ b/react-ui/src/pages/Pipeline/components/ResourceSelectorModal/config.tsx @@ -1,17 +1,21 @@ 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 { AvailableRange, CommonTabKeys } from '@/enums'; +import { ResourceData, ResourceVersionData } from '@/pages/Dataset/config'; +import { MirrorVersionData } from '@/pages/Mirror/Info'; +import { MirrorData } from '@/pages/Mirror/List'; import { + getDatasetInfo, getDatasetList, - getDatasetVersionIdList, - getDatasetVersionsById, + getDatasetVersionList, getModelList, getModelVersionIdList, getModelVersionsById, } from '@/services/dataset/index.js'; import { getMirrorListReq, getMirrorVersionListReq } from '@/services/mirror'; -import type { TabsProps } from 'antd'; +import type { TabsProps, TreeDataNode } from 'antd'; +import { pick } from 'lodash'; export enum ResourceSelectorType { Model = 'Model', // 模型 @@ -19,111 +23,347 @@ export enum ResourceSelectorType { Mirror = 'Mirror', //镜像 } -export type MirrorVersion = { - id: number; // 镜像版本 id - status: MirrorVersionStatus; // 镜像版本状态 - tag_name: string; // 镜像版本 name - url: string; // 镜像版本路径 +// 数据集、模型列表转为树形结构 +const convertDatasetToTreeData = (list: ResourceData[]): TreeDataNode[] => { + return list.map((v) => ({ + ...v, + key: `${v.id}`, + title: v.name, + isLeaf: false, + checkable: false, + })); }; -export type SelectorTypeInfo = { - getList: (params: any) => Promise; // 获取资源列表 - getVersions: (params: any) => Promise; // 获取资源版本列表 - getFiles: (params: any) => Promise; // 获取资源文件列表 - handleVersionResponse: (res: any) => any[]; // 处理版本列表接口数据 - modalIcon: string; // modal icon - buttonIcon: string; // button icon - name: string; // 名称 - litReqParamKey: 'available_range' | 'image_type'; // 表示是公开还是私有的参数名称,获取资源列表接口使用 - fileReqParamKey: 'models_id' | 'dataset_id'; // 文件请求参数名称,获取文件列表接口使用 - tabItems: TabsProps['items']; // tab 列表 - buttontTitle: string; // 按钮 title +// 镜像列表转为树形结构 +const convertMirrorToTreeData = (list: MirrorData[]): TreeDataNode[] => { + return list.map((v) => ({ + key: `${v.id}`, + title: v.name, + isLeaf: false, + checkable: false, + })); }; -// 获取镜像文件列表,为了兼容数据集和模型 -const getMirrorFilesReq = ({ id, version }: { id: number; version: string }): Promise => { - const index = version.indexOf('-'); - const url = version.slice(index + 1); - return Promise.resolve({ - data: { +// 数据集版本列表转为树形结构 +const convertDatasetVersionToTreeData = ( + parentId: string, + info: ResourceData, + list: ResourceVersionData[], +): TreeDataNode[] => { + return list.map((item: ResourceVersionData) => ({ + ...pick(info, ['id', 'name', 'owner', 'identifier']), + version: item.name, + title: item.name, + key: `${parentId}-${item.name}`, + isLeaf: true, + checkable: true, + })); +}; + +// 镜像版本列表转为树形结构 +const convertMirrorVersionToTreeData = ( + parentId: string, + list: MirrorVersionData[], +): TreeDataNode[] => { + return list.map((item: MirrorVersionData) => ({ + url: item.url, + title: item.tag_name, + key: `${parentId}-${item.id}`, + isLeaf: true, + checkable: true, + })); +}; + +// 从树形数据节点 id 中获取数据集版本列表的参数 +// const parseDatasetVersionId = (id: string) => { +// const list = id.split('-'); +// return { +// id: Number(list[0]), +// name: list[1], +// owner: list[2], +// identifier: list[3], +// version: list[4], +// }; +// }; + +// 从树形数据节点 id 中获取数据集版本列表的参数 +// const parseMirrorVersionId = (id: string) => { +// const list = id.split('-'); +// return { +// parentId: Number(list[0]), +// id: list[1], +// url: list[2], +// }; +// }; + +// export type MirrorVersion = { +// id: number; // 镜像版本 id +// status: MirrorVersionStatus; // 镜像版本状态 +// tag_name: string; // 镜像版本 name +// url: string; // 镜像版本路径 +// }; + +// export type SelectorTypeInfo = { +// getList: (params: any) => Promise; // 获取资源列表 +// getVersions: (params: any) => Promise; // 获取资源版本列表 +// getFiles: (params: any) => Promise; // 获取资源文件列表 +// handleVersionResponse: (res: any) => any[]; // 处理版本列表接口数据 +// dataToTreeData: (data: any) => TreeDataNode[]; // 数据转树形结构 +// parseTreeNodeId: (id: string) => any; // 获取版本列表请求参数 +// modalIcon: string; // modal icon +// buttonIcon: string; // button icon +// name: string; // 名称 +// litReqParamKey: 'available_range' | 'image_type'; // 表示是公开还是私有的参数名称,获取资源列表接口使用 +// tabItems: TabsProps['items']; // tab 列表 +// buttontTitle: string; // 按钮 title +// }; + +// export const selectorTypeConfig: Record = { +// [ResourceSelectorType.Model]: { +// getList: getModelList, +// getVersions: getModelVersionsById, +// getFiles: getModelVersionIdList, + +// name: '模型', +// modalIcon: modelImg, +// buttonIcon: 'icon-xuanzemoxing', +// litReqParamKey: 'available_range', +// tabItems: [ +// { +// key: CommonTabKeys.Private, +// label: '我的模型', +// }, +// { +// key: CommonTabKeys.Public, +// label: '公开模型', +// }, +// ], +// buttontTitle: '选择模型', +// }, +// [ResourceSelectorType.Dataset]: { +// getList: getDatasetList, +// getVersions: getDatasetVersionList, +// getFiles: getDatasetInfo, + +// name: '数据集', +// modalIcon: datasetImg, +// buttonIcon: 'icon-xuanzeshujuji', +// litReqParamKey: 'available_range', +// tabItems: [ +// { +// key: CommonTabKeys.Private, +// label: '我的数据集', +// }, +// { +// key: CommonTabKeys.Public, +// label: '公开数据集', +// }, +// ], +// buttontTitle: '选择数据集', +// }, +// [ResourceSelectorType.Mirror]: { +// getList: getMirrorListReq, +// getVersions: (id: number) => getMirrorVersionListReq({ image_id: id, page: 0, size: 200 }), +// getFiles: getMirrorFilesReq, +// handleVersionResponse: (res) => +// res.data?.content?.filter( +// (v: MirrorVersionData) => v.status === MirrorVersionStatus.Available, +// ) || [], +// dataToTreeData: convertMirrorToTreeData, +// parseTreeNodeId: (id: string) => id, +// name: '镜像', +// modalIcon: mirrorImg, +// buttonIcon: 'icon-xuanzejingxiang', +// litReqParamKey: 'image_type', +// tabItems: [ +// { +// key: CommonTabKeys.Private, +// label: '我的镜像', +// }, +// { +// key: CommonTabKeys.Public, +// label: '公开镜像', +// }, +// ], +// buttontTitle: '选择镜像', +// }, +// }; + +interface SelectorTypeInfo { + getList: (isPublic: boolean) => Promise; // 获取资源列表 + getVersions: (parentKey: string, parentNode: any) => Promise; // 获取资源版本列表 + getFiles: (parentKey: string, parentNode: any) => Promise; // 获取资源文件列表 + readonly modalIcon: string; // modal icon + readonly buttonIcon: string; // button icon + readonly name: string; // 名称 + readonly tabItems: TabsProps['items']; // tab 列表 + readonly buttontTitle: string; // 按钮 title +} + +export class DatasetSelector implements SelectorTypeInfo { + readonly name = '数据集'; + readonly modalIcon = datasetImg; + readonly buttonIcon = 'icon-xuanzeshujuji'; + readonly tabItems = [ + { + key: CommonTabKeys.Private, + label: '我的数据集', + }, + { + key: CommonTabKeys.Public, + label: '公开数据集', + }, + ]; + readonly buttontTitle = '选择数据集'; + + async getList(isPublic: boolean) { + const res = await getDatasetList({ is_public: isPublic, page: 0, size: 2000 }); + if (res && res.data) { + const list = res.data.content || []; + return convertDatasetToTreeData(list); + } else { + return Promise.reject('获取数据集列表失败'); + } + } + async getVersions(parentKey: string, parentNode: ResourceData) { + // const obj = parseDatasetVersionId(id); + const res = await getDatasetVersionList(pick(parentNode, ['owner', 'identifier'])); + if (res && res.data) { + const list = res.data; + return convertDatasetVersionToTreeData(parentKey, parentNode, list); + } else { + return Promise.reject('获取数据集版本列表失败'); + } + } + + async getFiles(_parentKey: string, parentNode: ResourceData & ResourceVersionData) { + //const obj = parseDatasetVersionId(parentKey); + const params = pick(parentNode, ['owner', 'identifier', 'id', 'name', 'version']); + const res = await getDatasetInfo(params); + if (res && res.data) { + const path = res.data.relative_paths || ''; + const list = res.data.dataset_version_vos || []; + return { + path, + content: list, + }; + } else { + return Promise.reject('获取数据集文件列表失败'); + } + } +} + +export class ModelSelector implements SelectorTypeInfo { + readonly name = '模型'; + readonly modalIcon = modelImg; + readonly buttonIcon = 'icon-xuanzemoxing'; + readonly tabItems = [ + { + key: CommonTabKeys.Private, + label: '我的模型', + }, + { + key: CommonTabKeys.Public, + label: '公开模型', + }, + ]; + readonly buttontTitle = '选择模型'; + + async getList(isPublic: boolean) { + const res = await getModelList({ is_public: isPublic, page: 0, size: 2000 }); + if (res && res.data) { + const list = res.data.content || []; + return convertDatasetToTreeData(list); + } else { + return Promise.reject('获取数据集列表失败'); + } + } + async getVersions(key: string, parentNode: ResourceData) { + //const obj = parseDatasetVersionId(id); + const res = await getModelVersionIdList(pick(parentNode, ['owner', 'identifier'])); + if (res && res.data) { + const list = res.data.content || []; + return convertDatasetVersionToTreeData(key, parentNode, list); + } else { + return Promise.reject('获取数据集版本列表失败'); + } + } + + async getFiles(_parentKey: string, parentNode: ResourceData & ResourceVersionData) { + // const obj = parseDatasetVersionId(id); + const params = pick(parentNode, ['owner', 'identifier', 'id', 'name', 'version']); + const res = await getModelVersionsById(params); + if (res && res.data) { + const list = res.data.dataset_version_vos || []; + return { + path: res.data.path || '', + content: list, + }; + } else { + return Promise.reject('获取数据集文件列表失败'); + } + } +} + +export class MirrorSelector implements SelectorTypeInfo { + readonly name = '镜像'; + readonly modalIcon = mirrorImg; + readonly buttonIcon = 'icon-xuanzejingxiang'; + readonly tabItems = [ + { + key: CommonTabKeys.Private, + label: '我的镜像', + }, + { + key: CommonTabKeys.Public, + label: '公开镜像', + }, + ]; + readonly buttontTitle = '选择镜像'; + + async getList(isPublic: boolean) { + const res = await getMirrorListReq({ + image_type: isPublic ? AvailableRange.Public : AvailableRange.Private, + page: 0, + size: 2000, + }); + if (res && res.data) { + const list = res.data.content || []; + return convertMirrorToTreeData(list); + } else { + return Promise.reject('获取镜像列表失败'); + } + } + async getVersions(parentKey: string) { + const res = await getMirrorVersionListReq({ + image_id: parentKey, + page: 0, + size: 2000, + }); + if (res && res.data) { + const list = res.data.content || []; + return convertMirrorVersionToTreeData(parentKey, list); + } else { + return Promise.reject('获取镜像版本列表失败'); + } + } + + async getFiles(parentKey: string, parentNode: MirrorVersionData) { + const { url } = parentNode; + return { path: url, content: [ { - id: `${id}-${version}`, + id: parentKey, 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: '公开模型', - }, - ], - buttontTitle: '选择模型', - }, - [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: '公开数据集', - }, - ], - buttontTitle: '选择数据集', - }, - [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: '公开镜像', - }, - ], - buttontTitle: '选择镜像', - }, + [ResourceSelectorType.Model]: new ModelSelector(), + [ResourceSelectorType.Dataset]: new DatasetSelector(), + [ResourceSelectorType.Mirror]: new MirrorSelector(), }; diff --git a/react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.less b/react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.less index bfff8100..64aaad64 100644 --- a/react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.less +++ b/react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.less @@ -67,3 +67,8 @@ } } } + +.kf-tree-title { + display: inline-block; + .singleLine(); +} diff --git a/react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.tsx b/react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.tsx index f4caf03a..4c7d33b9 100644 --- a/react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.tsx +++ b/react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.tsx @@ -11,24 +11,19 @@ import { Icon } from '@umijs/max'; 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 { ResourceSelectorType, selectorTypeConfig } from './config'; import styles from './index.less'; export { ResourceSelectorType, selectorTypeConfig }; // 选择数据集\模型\镜像的返回类型 export type ResourceSelectorResponse = { - id: number; // 数据集\模型\镜像 id + id: string; // 数据集\模型\镜像 id name: string; // 数据集\模型\镜像 name version: string; // 数据集\模型\镜像版本 path: string; // 数据集\模型\镜像版本路径 activeTab: CommonTabKeys; // 是我的还是公开的 }; -type ResourceGroup = { - id: number; // 数据集\模型\镜像 id - name: string; // 数据集\模型\镜像 name -}; - type ResourceFile = { id: number; // 文件 id file_name: string; // 文件 name @@ -44,39 +39,8 @@ export interface ResourceSelectorModalProps extends Omit { type TreeRef = GetRef>; -// list 数据转成 treeData -const convertToTreeData = (list: ResourceGroup[]): TreeDataNode[] => { - return list.map((v) => ({ - title: v.name, - key: v.id, - isLeaf: false, - checkable: false, - })); -}; - -// 版本数据转成 treeData -const convertVersionToTreeData = (parentId: number) => { - return (item: string | MirrorVersion): TreeDataNode => { - if (typeof item === 'string') { - return { - title: item, - key: `${parentId}-${item}`, - isLeaf: true, - checkable: true, - }; - } else { - return { - title: item.tag_name, - key: `${parentId}-${item.id}-${item.url}`, - isLeaf: true, - checkable: true, - }; - } - }; -}; - // 更新树形结构的 children -const updateChildren = (parentId: number, children: TreeDataNode[]) => { +const updateChildren = (parentId: string, children: TreeDataNode[]) => { return (node: TreeDataNode) => { if (node.key === parentId) { return { @@ -91,7 +55,7 @@ const updateChildren = (parentId: number, children: TreeDataNode[]) => { // 得到数据集\模型\镜像 id 和下属版本号 const getIdAndVersion = (versionKey: string) => { const index = versionKey.indexOf('-'); - const id = Number(versionKey.slice(0, index)); + const id = versionKey.slice(0, index); const version = versionKey.slice(index + 1); return { id, @@ -115,8 +79,8 @@ function ResourceSelectorModal({ const [files, setFiles] = useState([]); const [versionPath, setVersionPath] = useState(''); const [searchText, setSearchText] = useState(''); - const [fisrtLoadList, setFisrtLoadList] = useState(false); - const [fisrtLoadVersions, setFisrtLoadVersions] = useState(false); + const [firstLoadList, setFirstLoadList] = useState(false); + const [firstLoadVersions, setFirstLoadVersions] = useState(false); const treeRef = useRef(null); const config = selectorTypeConfig[type]; @@ -140,18 +104,10 @@ function ResourceSelectorModal({ // 获取数据集\模型\镜像列表 const getTreeData = async () => { - const available_range = activeTab === CommonTabKeys.Private ? 0 : 1; - const params = { - page: 0, - size: 1000, - [config.litReqParamKey]: available_range, - }; - const getListReq = config.getList; - const [res] = await to(getListReq(params)); + const isPublic = activeTab === CommonTabKeys.Private ? false : true; + const [res] = await to(config.getList(isPublic)); if (res) { - const list = res.data?.content || []; - const treeData = convertToTreeData(list); - setOriginTreeData(treeData); + setOriginTreeData(res); // 恢复上一次的 Expand 操作 restoreLastExpand(); @@ -161,21 +117,22 @@ function ResourceSelectorModal({ }; // 获取数据集\模型\镜像版本列表 - const getVersions = async (parentId: number) => { - const getVersionsReq = config.getVersions; - const [res, error] = await to(getVersionsReq(parentId)); + const getVersions = async (parentId: string, parentNode: any) => { + const [res, error] = await to(config.getVersions(parentId, parentNode)); if (res) { - const list = config.handleVersionResponse(res); - const children = list.map(convertVersionToTreeData(parentId)); // 更新 treeData children - setOriginTreeData((prev) => prev.map(updateChildren(parentId, children))); + setOriginTreeData((prev) => prev.map(updateChildren(parentId, res))); + // 缓存 loadedKeys const index = loadedKeys.find((v) => v === parentId); if (!index) { setLoadedKeys((prev) => prev.concat(parentId)); } + // 恢复上一次的 Check 操作 - restoreLastCheck(parentId); + setTimeout(() => { + restoreLastCheck(parentId, res); + }, 300); } else { setExpandedKeys([]); return Promise.reject(error); @@ -183,14 +140,11 @@ function ResourceSelectorModal({ }; // 获取版本下的文件 - const getFiles = async (id: number, version: string) => { - const getFilesReq = config.getFiles; - const paramsKey = config.fileReqParamKey; - const params = { version: version, [paramsKey]: id }; - const [res] = await to(getFilesReq(params)); + const getFiles = async (parentId: string, parentNode: any) => { + const [res] = await to(config.getFiles(parentId, parentNode)); if (res) { - setVersionPath(res.data?.path || ''); - setFiles(res.data?.content || []); + setVersionPath(res.path); + setFiles(res.content); } else { setVersionPath(''); setFiles([]); @@ -198,11 +152,11 @@ function ResourceSelectorModal({ }; // 动态加载 tree children - const onLoadData = ({ key, children }: TreeDataNode) => { + const onLoadData = ({ key, children, ...rest }: TreeDataNode) => { if (children) { return Promise.resolve(); } else { - return getVersions(key as number); + return getVersions(key as string, rest); } }; @@ -213,13 +167,13 @@ function ResourceSelectorModal({ }; // 选中 - const onCheck: TreeProps['onCheck'] = (checkedKeysValue) => { + const onCheck: TreeProps['onCheck'] = (checkedKeysValue, { checkedNodes }) => { const lastKeys = (checkedKeysValue as React.Key[]).slice(-1); setCheckedKeys(lastKeys); - if (lastKeys.length) { + if (lastKeys.length && checkedNodes.length) { const last = lastKeys[0] as string; - const { id, version } = getIdAndVersion(last); - getFiles(id, version); + const lastNode = checkedNodes[checkedNodes.length - 1]; + getFiles(last, lastNode); } else { setFiles([]); } @@ -229,10 +183,10 @@ function ResourceSelectorModal({ // 判断是否有 defaultExpandedKeys,如果有,设置 expandedKeys // fisrtLoadList 标志位 const restoreLastExpand = () => { - if (!fisrtLoadList && defaultExpandedKeys.length > 0) { + if (!firstLoadList && defaultExpandedKeys.length > 0) { setTimeout(() => { setExpandedKeys(defaultExpandedKeys); - setFisrtLoadList(true); + setFirstLoadList(true); setTimeout(() => { treeRef.current?.scrollTo({ key: defaultExpandedKeys[0], align: 'bottom' }); }, 100); @@ -243,16 +197,17 @@ function ResourceSelectorModal({ // 恢复上一次的 Check 操作 // 判断是否有 defaultCheckedKeys,如果有,设置 checkedKeys,并且调用获取文件列表接口 // fisrtLoadVersions 标志位 - const restoreLastCheck = (parentId: number) => { - if (!fisrtLoadVersions && defaultCheckedKeys.length > 0) { + const restoreLastCheck = (parentId: string, versions: TreeDataNode[]) => { + if (!firstLoadVersions && defaultCheckedKeys.length > 0) { const last = defaultCheckedKeys[0] as string; - const { id, version } = getIdAndVersion(last); + const { id } = getIdAndVersion(last); // 判断正在打开的 id 和 defaultCheckedKeys 的 id 是否一致 if (id === parentId) { setTimeout(() => { setCheckedKeys(defaultCheckedKeys); - getFiles(id, version); - setFisrtLoadVersions(true); + const parentNode = versions.find((v) => v.key === last); + getFiles(last, parentNode); + setFirstLoadVersions(true); setTimeout(() => { treeRef?.current?.scrollTo({ key: defaultCheckedKeys[0], @@ -269,7 +224,7 @@ function ResourceSelectorModal({ if (checkedKeys.length > 0) { const last = checkedKeys[0] as string; const { id, version } = getIdAndVersion(last); - const name = (treeData.find((v) => Number(v.key) === id)?.title ?? '') as string; + const name = (treeData.find((v) => v.key === id)?.title ?? '') as string; const res = { id, name, @@ -323,6 +278,18 @@ function ResourceSelectorModal({ loadedKeys={loadedKeys} expandedKeys={expandedKeys} onExpand={onExpand} + titleRender={(nodeData) => { + console.log(nodeData); + + return ( + + {nodeData.title as string} + + ); + }} checkable /> diff --git a/react-ui/src/pages/System/User/edit.tsx b/react-ui/src/pages/System/User/edit.tsx index 324d5df0..4fde814d 100644 --- a/react-ui/src/pages/System/User/edit.tsx +++ b/react-ui/src/pages/System/User/edit.tsx @@ -62,6 +62,8 @@ const UserForm: React.FC = (props) => { loginIp: props.values.loginIp, loginDate: props.values.loginDate, remark: props.values.remark, + gitLinkUsername: props.values.gitLinkUsername, + gitLinkPassword: props.values.gitLinkPassword, }); }, [form, props]); @@ -275,6 +277,28 @@ const UserForm: React.FC = (props) => { colProps={{ md: 12, xl: 12 }} rules={[{ required: true, message: '请选择角色!' }]} /> + + ( -
- - 页面开发中,敬请期待...... -
-); +const MissingPage = () => { + const navigate = useNavigate(); + + return ( + navigate('/')} + > + ); +}; export default MissingPage; diff --git a/react-ui/src/requestConfig.ts b/react-ui/src/requestConfig.ts index 01911926..5e02e678 100644 --- a/react-ui/src/requestConfig.ts +++ b/react-ui/src/requestConfig.ts @@ -7,6 +7,7 @@ import type { AxiosRequestConfig, AxiosResponse, RequestConfig, RequestOptions } import { message } from 'antd'; import { clearSessionToken, getAccessToken } from './access'; import { setRemoteMenu } from './services/session'; +import Loading from './utils/loading'; import { gotoLoginPage } from './utils/ui'; // [antd: Notification] You are calling notice in render which will break in React 18 concurrent mode. Please trigger in effect instead. @@ -36,12 +37,14 @@ export const requestConfig: RequestConfig = { headers['Authorization'] = `Bearer ${accessToken}`; } } + Loading.show(); return { url, options }; }, ], responseInterceptors: [ [ (response: AxiosResponse) => { + Loading.hide(); const { status, data, config } = response || {}; const skipErrorHandler = (config as RequestOptions)?.skipErrorHandler; if (status >= 200 && status < 300) { @@ -63,6 +66,7 @@ export const requestConfig: RequestConfig = { } }, (error: Error) => { + Loading.hide(); popupError(error.message ?? '请求失败'); return Promise.reject(error); }, diff --git a/react-ui/src/services/dataset/index.js b/react-ui/src/services/dataset/index.js index 1764c8d0..dec43db8 100644 --- a/react-ui/src/services/dataset/index.js +++ b/react-ui/src/services/dataset/index.js @@ -1,21 +1,34 @@ import { request } from '@umijs/max'; -// 分页查询数据集 + +// 查询数据集、模型分类 +export function getAssetIcon(params) { + return request(`/api/mmp/assetIcon`, { + method: 'GET', + params, + }); +} + +// ----------------------------数据集--------------------------------- + +// 分页查询数据集列表 export function getDatasetList(params) { - return request(`/api/mmp/dataset`, { + return request(`/api/mmp/newdataset/queryDatasets`, { method: 'GET', params, }); } -// 分页查询模型 -export function getModelList(params) { - return request(`/api/mmp/models`, { + +// 查询数据集详情 +export function getDatasetInfo(params) { + return request(`/api/mmp/newdataset/getDatasetDetail`, { method: 'GET', params, }); } + // 新增数据集 -export function addDatesetAndVesion(data) { - return request(`/api/mmp/dataset/addDatasetAndVersion`, { +export function addDateset(data) { + return request(`/api/mmp/newdataset/addDatasetAndVersion`, { method: 'POST', headers: { 'Content-Type': 'application/json;charset=UTF-8', @@ -23,9 +36,35 @@ export function addDatesetAndVesion(data) { data, }); } -// 新增模型 -export function addModel(data) { - return request(`/api/mmp/models/addModelAndVersion`, { + +// 删除数据集 +export function deleteDataset(params) { + return request(`/api/mmp/newdataset/deleteDataset`, { + method: 'DELETE', + params, + }); +} + + +// 查询数据集版本列表 +export function getDatasetVersionList(params) { + return request(`/api/mmp/newdataset/getVersionList`, { + method: 'GET', + params, + }); +} + +// 查询数据集版本文件列表 +// export function getDatasetVersionFiles(params) { +// return request(`/api/mmp/datasetVersion/versions`, { +// method: 'GET', +// params, +// }); +// } + +// 新增数据集版本 +export function addDatasetVersion(data) { + return request(`/api/mmp/newdataset/addVersion`, { method: 'POST', headers: { 'Content-Type': 'application/json;charset=UTF-8', @@ -33,75 +72,74 @@ export function addModel(data) { data, }); } -// 查询数据集简介 -export function getDatasetById(id) { - return request(`/api/mmp/dataset/${id}`, { + +// 下载数据集所有文件 +export function downloadAllFiles(params) { + return request(`/api/mmp/newdataset/downloadAllFiles`, { method: 'GET', + params }); } -// 查询左侧列表 -export function getAssetIcon(params) { - return request(`/api/mmp/assetIcon`, { + +// 下载数据集单个文件 +export function downloadSingleFile(params) { + return request(`/api/mmp/newdataset/downloadSinggerFile`, { method: 'GET', params, }); } -// 查询模型简介 -export function getModelById(id) { - return request(`/api/mmp/models/${id}`, { - method: 'GET', + +// 删除数据集版本 +export function deleteDatasetVersion(params) { + return request(`/api/mmp/newdataset/deleteDatasetVersion`, { + method: 'DELETE', + params, }); } -// 查询数据版本集 -export function getDatasetVersionsById(id) { - return request(`/api/mmp/dataset/versions/${id}`, { + +// ----------------------------模型--------------------------------- + +// 分页查询模型列表 +export function getModelList(params) { + return request(`/api/mmp/models`, { method: 'GET', + params, }); } -// 查询模型版本集 -export function getModelVersionsById(id) { - return request(`/api/mmp/models/versions/${id}`, { + +// 新增模型 +export function addModel(data) { + return request(`/api/mmp/models/addModelAndVersion`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json;charset=UTF-8', + }, + data, + }); +} + +// 查询模型简介 +export function getModelById(id) { + return request(`/api/mmp/models/${id}`, { method: 'GET', }); } -// 分页查询数据集 -export function getDatasetVersionIdList(params) { - return request(`/api/mmp/datasetVersion/versions`, { + +// 查询模型版本列表 +export function getModelVersionsById(id) { + return request(`/api/mmp/models/versions/${id}`, { method: 'GET', - params, }); } -// 根据版本查询模型 + +// 根据版本查询文件列表 export function getModelVersionIdList(params) { return request(`/api/mmp/modelsVersion/versions`, { method: 'GET', params, }); } -// 删除数据集 -export function deleteDatasetVersion(params) { - return request(`/api/mmp/datasetVersion/deleteVersion`, { - method: 'DELETE', - params, - }); -} -// 删除模型 -export function deleteModelVersion(params) { - return request(`/api/mmp/modelsVersion/deleteVersion`, { - method: 'DELETE', - params, - }); -} -// 新增数据集版本 -export function addDatasetVersionDetail(data) { - return request(`/api/mmp/datasetVersion/addDatasetVersions`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json;charset=UTF-8', - }, - data, - }); -} + // 新增模型版本 export function addModelsVersionDetail(data) { return request(`/api/mmp/modelsVersion/addModelVersions`, { @@ -112,24 +150,22 @@ export function addModelsVersionDetail(data) { data, }); } -// 下载数据集 -export function exportDataset(id) { - return request(`/api/mmp/dataset/download/${id}`, { - method: 'GET', - }); -} -// 删除模型集 + +// 删除模型 export function deleteModel(id) { return request(`/api/mmp/models/${id}`, { method: 'DELETE', }); } -// 删除数据集 -export function deleteDataset(id) { - return request(`/api/mmp/dataset/${id}`, { + +// 删除模型版本 +export function deleteModelVersion(params) { + return request(`/api/mmp/modelsVersion/deleteVersion`, { method: 'DELETE', + params, }); } + // 获取模型依赖 export function getModelAtlasReq(data) { return request(`/api/mmp/modelDependency/queryModelAtlas`, { diff --git a/react-ui/src/types/system/user.d.ts b/react-ui/src/types/system/user.d.ts index 5f0ba1c2..a74ee0e8 100644 --- a/react-ui/src/types/system/user.d.ts +++ b/react-ui/src/types/system/user.d.ts @@ -19,6 +19,8 @@ declare namespace API.System { updateBy: string; updateTime: Date; remark: string; + gitLinkUsername?: string; + gitLinkPassword?: string; } export interface UserListParams { diff --git a/react-ui/src/utils/loading.tsx b/react-ui/src/utils/loading.tsx index aba4a16c..8486a6b9 100644 --- a/react-ui/src/utils/loading.tsx +++ b/react-ui/src/utils/loading.tsx @@ -11,29 +11,37 @@ import zhCN from 'antd/locale/zh_CN'; import { createRoot } from 'react-dom/client'; export class Loading { - static total = 0; + static total: number = 0; + static isShowing: boolean = false; + static startTime: Date = new Date(); + static removeTimeout: ReturnType | undefined; static show(props?: SpinProps) { - Loading.total += 1; - if (Loading.total > 1) { + this.total += 1; + if (this.total > 1) { + return; + } + + // 是否有延时未关闭的 loading + if (this.isShowing) { + this.clearRemoveTimeout(); return; } const container = document.createElement('div'); container.id = 'loading'; - const rootContainer = document.getElementsByTagName('main')[0]; + const rootContainer = document.body; //document.getElementsByTagName('main')[0]; rootContainer?.appendChild(container); const root = createRoot(container); const global = globalConfig(); - let timeoutId: ReturnType; + let renderTimeoutId: ReturnType; - function render(spinProps: SpinProps) { - clearTimeout(timeoutId); + const render = (spinProps: SpinProps) => { + clearTimeout(renderTimeoutId); - timeoutId = setTimeout(() => { + renderTimeoutId = setTimeout(() => { const rootPrefixCls = global.getPrefixCls(); const iconPrefixCls = global.getIconPrefixCls(); const theme = global.getTheme(); const dom = ; - root.render( , ); }); - } + }; render({ size: 'large', ...props, spinning: true }); + this.startTime = new Date(); + this.isShowing = true; + } + + static clearRemoveTimeout() { + if (this.removeTimeout) { + clearTimeout(this.removeTimeout); + this.removeTimeout = undefined; + } + } + + static removeLoading() { + this.clearRemoveTimeout(); + const rootContainer = document.body; //document.getElementsByTagName('main')[0]; + const container = document.getElementById('loading'); + if (container) { + rootContainer?.removeChild(container); + } + this.isShowing = false; } static hide(force: boolean = false) { - Loading.total -= 1; - if (Loading.total <= 0 || force) { - Loading.total = 0; - const rootContainer = document.getElementsByTagName('main')[0]; - const container = document.getElementById('loading'); - if (container) { - rootContainer?.removeChild(container); - } + this.total -= 1; + if (this.total > 0 && !force) { + return; } + + this.total = 0; + const duration = new Date().getTime() - this.startTime.getTime(); + this.removeTimeout = setTimeout(() => { + this.removeLoading(); + }, Math.max(300 - duration, 0)); } } diff --git a/react-ui/src/utils/sessionStorage.ts b/react-ui/src/utils/sessionStorage.ts index 00b2e313..dbb4de4f 100644 --- a/react-ui/src/utils/sessionStorage.ts +++ b/react-ui/src/utils/sessionStorage.ts @@ -4,8 +4,20 @@ export const mirrorNameKey = 'mirror-name'; export const modelDeploymentInfoKey = 'model-deployment-info'; // 编辑器 url export const editorUrlKey = 'editor-url'; +// 数据集、模型资源 +export const resourceItemKey = 'resource-item'; -export const getSessionStorageItem = (key: string, isObject: boolean = false) => { +/** + * Retrieves an item from session storage by key. + * + * If `isObject` is true, the function attempts to parse the stored value as JSON. + * If parsing fails, the function returns undefined. + * + * @param {string} key - The key of the item to retrieve + * @param {boolean} [isObject=false] - Whether to parse the stored value as JSON + * @return {any} The retrieved item, or undefined if not found or parsing fails + */ +export const getSessionStorageItem = (key: string, isObject: boolean = false): any => { const jsonStr = sessionStorage.getItem(key); if (!isObject) { return jsonStr; @@ -20,18 +32,40 @@ export const getSessionStorageItem = (key: string, isObject: boolean = false) => return undefined; }; +/** + * Sets an item in session storage by key. + * + * If `isObject` is true, the function stringifies the state as JSON before storing. + * + * @param {string} key - The key of the item to set + * @param {any} [state] - The value of the item to set + * @param {boolean} [isObject=false] - Whether to stringify the state as JSON + */ export const setSessionStorageItem = (key: string, state?: any, isObject: boolean = false) => { if (state) { sessionStorage.setItem(key, isObject ? JSON.stringify(state) : state); } }; +/** + * Removes an item from session storage by key. + * + * @param {string} key - The key of the item to remove + */ export const removeSessionStorageItem = (key: string) => { sessionStorage.removeItem(key); }; -// 获取之后就删除,多用于上一个页面传递数据到下一个页面 -export const getSessionItemThenRemove = (key: string, isObject: boolean = false) => { +/** + * Retrieves an item from session storage by key and then removes it. + * + * This function is useful for passing data from one page to another. + * + * @param {string} key - The key of the item to retrieve + * @param {boolean} [isObject=false] - Whether to parse the stored value as JSON + * @return {any} The retrieved item, or undefined if not found or parsing fails + */ +export const getSessionItemThenRemove = (key: string, isObject: boolean = false): any => { const res = getSessionStorageItem(key, isObject); sessionStorage.removeItem(key); return res; From b7d81b000b647b1e2264fd32bded367b5ecf4ccc Mon Sep 17 00:00:00 2001 From: cp3hnu Date: Fri, 13 Sep 2024 08:32:39 +0800 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E6=94=B9=E9=80=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- react-ui/src/components/BasicInfo/index.less | 51 ++++++---- react-ui/src/components/BasicInfo/index.tsx | 64 +++++++++++-- .../src/components/ErrorBoundary/index.less | 0 .../src/components/ErrorBoundary/index.tsx | 0 react-ui/src/components/KFEmpty/index.less | 3 +- react-ui/src/components/KFEmpty/index.tsx | 12 +-- .../src/components/ParameterSelect/config.tsx | 2 +- react-ui/src/pages/404.tsx | 4 +- react-ui/src/pages/CodeConfig/List/index.tsx | 3 + .../components/AddDatasetModal/index.tsx | 16 +++- .../components/AddModelModal/index.tsx | 80 ++++++++++------ .../components/AddVersionModal/index.tsx | 6 +- .../Dataset/components/ResourceInfo/index.tsx | 18 ++-- .../components/ResourceIntro/index.tsx | 96 ++++++++++++++++++- .../Dataset/components/ResourceItem/index.tsx | 2 +- .../Dataset/components/ResourceList/index.tsx | 13 ++- .../components/ResourceVersion/index.tsx | 5 +- react-ui/src/pages/Dataset/config.tsx | 69 ++++++------- .../components/ExportModelModal/index.tsx | 8 +- .../Model/components/ModelEvolution/index.tsx | 20 +--- react-ui/src/pages/Model/intro.tsx | 8 +- .../ResourceSelectorModal/config.tsx | 31 +++--- .../ResourceSelectorModal/index.less | 10 +- .../ResourceSelectorModal/index.tsx | 17 ++-- react-ui/src/pages/missingPage.jsx | 4 +- react-ui/src/requestConfig.ts | 1 + react-ui/src/services/dataset/index.js | 69 +++++-------- react-ui/src/utils/index.ts | 10 ++ 28 files changed, 383 insertions(+), 239 deletions(-) create mode 100644 react-ui/src/components/ErrorBoundary/index.less create mode 100644 react-ui/src/components/ErrorBoundary/index.tsx diff --git a/react-ui/src/components/BasicInfo/index.less b/react-ui/src/components/BasicInfo/index.less index 0a07e4aa..dd4d1ab1 100644 --- a/react-ui/src/components/BasicInfo/index.less +++ b/react-ui/src/components/BasicInfo/index.less @@ -5,31 +5,40 @@ gap: 20px 40px; align-items: flex-start; width: 80%; +} - &__item { - display: flex; - align-items: flex-start; - width: calc(50% - 20px); - font-size: 16px; - line-height: 1.6; +.kf-basic-info-item { + display: flex; + align-items: flex-start; + width: calc(50% - 20px); + font-size: 16px; + line-height: 1.6; - &__label { - width: 100px; - color: @text-color-secondary; - text-align: justify; - text-align-last: justify; + &__label { + position: relative; + color: @text-color-secondary; + text-align: justify; + text-align-last: justify; + + &::after { + position: absolute; + content: ':'; } + } - &__value { - display: flex; - flex: 1; - color: @text-color; - word-break: break-all; + &__value { + flex: 1; + margin-left: 16px; + white-space: pre-line; + word-break: break-all; + } - &::before { - margin-right: 16px; - content: ':'; - } - } + &__text { + color: @text-color; + } + + &__link:hover { + text-decoration: underline @underline-color; + text-underline-offset: 3px; } } diff --git a/react-ui/src/components/BasicInfo/index.tsx b/react-ui/src/components/BasicInfo/index.tsx index d0253dd8..8cfee0ae 100644 --- a/react-ui/src/components/BasicInfo/index.tsx +++ b/react-ui/src/components/BasicInfo/index.tsx @@ -1,27 +1,73 @@ +import { isEmptyString } from '@/utils'; +import { Link } from '@umijs/max'; +import classNames from 'classnames'; import './index.less'; + export type BasicInfoData = { label: string; value?: any; - format?: (_value: any) => string; + link?: string; + externalLink?: string; + format?: (_value?: any) => string | undefined; }; type BasicInfoProps = { datas: BasicInfoData[]; + className?: string; + style?: React.CSSProperties; + labelWidth?: number; }; -function BasicInfo({ datas }: BasicInfoProps) { +function BasicInfo({ datas, className, style, labelWidth = 100 }: BasicInfoProps) { return ( -
+
{datas.map((item) => ( -
-
{item.label}
-
- {item.format ? item.format(item.value) ?? '--' : item.value ?? '--'} -
-
+ ))}
); } +type BasicInfoItemProps = { + data: BasicInfoData; + labelWidth?: number; +}; +function BasicInfoItem({ data, labelWidth = 100 }: BasicInfoItemProps) { + const { label, value, externalLink, link, format } = data; + const showValue = format ? format(value) : value; + let valueComponent = undefined; + if (externalLink && showValue) { + valueComponent = ( + + {showValue} + + ); + } else if (link && showValue) { + valueComponent = ( + + {showValue} + + ); + } else { + valueComponent = ( +
+ {isEmptyString(showValue) ? '--' : showValue} +
+ ); + } + return ( +
+
+ {label} +
+ {valueComponent} +
+ ); +} + export default BasicInfo; diff --git a/react-ui/src/components/ErrorBoundary/index.less b/react-ui/src/components/ErrorBoundary/index.less new file mode 100644 index 00000000..e69de29b diff --git a/react-ui/src/components/ErrorBoundary/index.tsx b/react-ui/src/components/ErrorBoundary/index.tsx new file mode 100644 index 00000000..e69de29b diff --git a/react-ui/src/components/KFEmpty/index.less b/react-ui/src/components/KFEmpty/index.less index 9a9cc689..e62edff5 100644 --- a/react-ui/src/components/KFEmpty/index.less +++ b/react-ui/src/components/KFEmpty/index.less @@ -7,7 +7,6 @@ &__image { width: 475px; - height: 292px; } &__title { @@ -15,7 +14,6 @@ color: @text-color; font-weight: 500; font-size: 30px; - letter-spacing: 5px; text-align: center; } @@ -32,6 +30,7 @@ align-items: center; justify-content: center; margin-top: 20px; + margin-bottom: 30px; &__back-btn { height: 32px; diff --git a/react-ui/src/components/KFEmpty/index.tsx b/react-ui/src/components/KFEmpty/index.tsx index 492de75c..e9bc79e1 100644 --- a/react-ui/src/components/KFEmpty/index.tsx +++ b/react-ui/src/components/KFEmpty/index.tsx @@ -16,8 +16,8 @@ type EmptyProps = { content?: string; hasFooter?: boolean; footer?: () => React.ReactNode; - backTitle?: string; - onBack?: () => void; + buttonTitle?: string; + onRefresh?: () => void; }; function getEmptyImage(type: EmptyType) { @@ -39,8 +39,8 @@ function KFEmpty({ content, hasFooter = false, footer, - backTitle = '返回', - onBack, + buttonTitle = '刷新', + onRefresh, }: EmptyProps) { const image = getEmptyImage(type); @@ -54,8 +54,8 @@ function KFEmpty({ {footer ? ( footer() ) : ( - )}
diff --git a/react-ui/src/components/ParameterSelect/config.tsx b/react-ui/src/components/ParameterSelect/config.tsx index eae63ec2..689f618d 100644 --- a/react-ui/src/components/ParameterSelect/config.tsx +++ b/react-ui/src/components/ParameterSelect/config.tsx @@ -14,7 +14,7 @@ const filterResourceStandard: SelectProps['filterOpti }; // id 从 number 转换为 string -const convertId = (item: any) => ({ ...item, id: String(item.id) }); +const convertId = (item: any) => ({ ...item, id: `${item.id}-${item.identifier}` }); export type SelectPropsConfig = { getOptions: () => Promise; // 获取下拉数据 diff --git a/react-ui/src/pages/404.tsx b/react-ui/src/pages/404.tsx index 23242d56..dbacba53 100644 --- a/react-ui/src/pages/404.tsx +++ b/react-ui/src/pages/404.tsx @@ -11,8 +11,8 @@ const NoFoundPage = () => { title="404" content={'很抱歉,您访问的页面地址有误,\n或者该页面不存在。'} hasFooter={true} - backTitle="返回首页" - onBack={() => navigate('/')} + buttonTitle="返回首页" + onRefresh={() => navigate('/')} > ); }; diff --git a/react-ui/src/pages/CodeConfig/List/index.tsx b/react-ui/src/pages/CodeConfig/List/index.tsx index 1a307751..b61e9016 100644 --- a/react-ui/src/pages/CodeConfig/List/index.tsx +++ b/react-ui/src/pages/CodeConfig/List/index.tsx @@ -171,6 +171,9 @@ function CodeConfigList() { className={styles['code-config-list__empty']} type={EmptyType.NoData} title="暂无数据" + content={'很抱歉,没有搜索到您想要的内容\n建议刷新试试'} + hasFooter={true} + onRefresh={getDataList} /> )} diff --git a/react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx b/react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx index bf3dc0d1..1d825896 100644 --- a/react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx +++ b/react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx @@ -1,7 +1,8 @@ import { getAccessToken } from '@/access'; import KFIcon from '@/components/KFIcon'; import KFModal from '@/components/KFModal'; -import { addDateset } from '@/services/dataset/index.js'; +import { CategoryData, ResourceType, resourceConfig } from '@/pages/Dataset/config'; +import { addDataset } from '@/services/dataset/index.js'; import { to } from '@/utils/promise'; import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; import { @@ -18,7 +19,6 @@ import { } from 'antd'; import { omit } from 'lodash'; import { useState } from 'react'; -import { CategoryData } from '../../config'; import styles from './index.less'; interface AddDatasetModalProps extends Omit { @@ -37,7 +37,7 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr // 上传组件参数 const uploadProps: UploadProps = { - action: '/api/mmp/newdataset/upload', + action: resourceConfig[ResourceType.Dataset].uploadAction, headers: { Authorization: getAccessToken() || '', }, @@ -54,7 +54,7 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr // 上传请求 const createDataset = async (params: any) => { - const [res] = await to(addDateset(params)); + const [res] = await to(addDataset(params)); if (res) { message.success('创建成功'); onOk?.(); @@ -120,6 +120,14 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr required: true, message: '请输入数据集版本', }, + { + validator: (_rule, value) => { + if (value === 'master') { + return Promise.reject(`版本不能为 master`); + } + return Promise.resolve(); + }, + }, ]} > diff --git a/react-ui/src/pages/Dataset/components/AddModelModal/index.tsx b/react-ui/src/pages/Dataset/components/AddModelModal/index.tsx index 7d7758e7..af08aadf 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/config'; +import { CategoryData, ResourceType, resourceConfig } from '@/pages/Dataset/config'; import { addModel } from '@/services/dataset/index.js'; import { to } from '@/utils/promise'; import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui'; @@ -9,6 +9,7 @@ import { Button, Form, Input, + Radio, Select, Upload, UploadFile, @@ -31,7 +32,7 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) // 上传组件参数 const uploadProps: UploadProps = { - action: '/api/mmp/models/upload', + action: resourceConfig[ResourceType.Model].uploadAction, headers: { Authorization: getAccessToken() || '', }, @@ -53,7 +54,7 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) if (validateUploadFiles(fileList)) { const params = { ...omit(formData, ['fileList']), - models_version_vos: fileList.map((item) => { + model_version_vos: fileList.map((item) => { const data = item.response?.data?.[0] ?? {}; return { file_name: data.fileName, @@ -77,7 +78,13 @@ function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) form: 'form', }} > - + - + - - - - { + if (value === 'master') { + return Promise.reject(`版本不能为 master`); + } + return Promise.resolve(); + }, }, ]} > - + - {/* - - 仅自己可见 - 工作空间可见 - - */}
+
); } diff --git a/react-ui/src/pages/Dataset/config.tsx b/react-ui/src/pages/Dataset/config.tsx index e40aaa4a..b1677bc3 100644 --- a/react-ui/src/pages/Dataset/config.tsx +++ b/react-ui/src/pages/Dataset/config.tsx @@ -2,7 +2,7 @@ import KFIcon from '@/components/KFIcon'; import { CommonTabKeys } from '@/enums'; import { addDatasetVersion, - addModelsVersionDetail, + addModelVersion, deleteDataset, deleteDatasetVersion, deleteModel, @@ -10,9 +10,9 @@ import { getDatasetInfo, getDatasetList, getDatasetVersionList, - getModelById, + getModelInfo, getModelList, - getModelVersionsById, + getModelVersionList, } from '@/services/dataset/index.js'; import type { TabsProps } from 'antd'; @@ -31,7 +31,7 @@ type ResourceTypeInfo = { name: string; // 名称 typeParamKey: string; // 类型参数名称,获取资源列表接口使用 tagParamKey: string; // 标签参数名称,获取资源列表接口使用 - fileReqParamKey: 'models_id' | 'dataset_id'; // 文件请求参数名称,获取文件列表接口使用 + filePropKey: string; tabItems: TabsProps['items']; // tab 列表 typeTitle: string; // 类型标题 tagTitle: string; // 标签标题 @@ -40,13 +40,10 @@ type ResourceTypeInfo = { prefix: string; // 图片资源、详情 url 的前缀 deleteModalTitle: string; // 删除弹框的title addBtnTitle: string; // 新增按钮的title - idParamKey: 'models_id' | 'dataset_id'; // 新建版本、删除版本接口,版本 id 的参数名称 uploadAction: string; // 上传接口 url uploadAccept?: string; // 上传文件类型 downloadAllAction: string; // 批量下载接口 url downloadSingleAction: string; // 单个下载接口 url - infoTypePropertyName: string; // 详情数据中,类型属性名称 - infoTagPropertyName: string; // 详情数据中,标签属性名称 }; export const resourceConfig: Record = { @@ -60,7 +57,7 @@ export const resourceConfig: Record = { name: '数据集', typeParamKey: 'data_type', tagParamKey: 'data_tag', - fileReqParamKey: 'dataset_id', + filePropKey: 'dataset_version_vos', tabItems: [ { key: CommonTabKeys.Public, @@ -80,25 +77,22 @@ export const resourceConfig: Record = { prefix: 'dataset', deleteModalTitle: '确定删除该条数据集实例吗?', addBtnTitle: '新建数据集', - idParamKey: 'dataset_id', uploadAction: '/api/mmp/newdataset/upload', uploadAccept: '.zip,.tgz', downloadAllAction: '/api/mmp/newdataset/downloadAllFiles', downloadSingleAction: '/api/mmp/newdataset/downloadSinggerFile', - infoTypePropertyName: 'data_type', - infoTagPropertyName: 'data_tag', }, [ResourceType.Model]: { getList: getModelList, - getVersions: getModelVersionsById, + getVersions: getModelVersionList, deleteRecord: deleteModel, - addVersion: addModelsVersionDetail, + addVersion: addModelVersion, deleteVersion: deleteModelVersion, - getInfo: getModelById, + getInfo: getModelInfo, name: '模型', typeParamKey: 'model_type', tagParamKey: 'model_tag', - fileReqParamKey: 'models_id', + filePropKey: 'model_version_vos', tabItems: [ { key: CommonTabKeys.Public, @@ -118,13 +112,10 @@ export const resourceConfig: Record = { prefix: 'model', deleteModalTitle: '确定删除该条模型实例吗?', addBtnTitle: '新建模型', - idParamKey: 'models_id', - uploadAction: '/api/mmp/models/upload', + uploadAction: '/api/mmp/newmodel/upload', uploadAccept: undefined, - downloadAllAction: '/api/mmp/models/downloadAllFiles', - downloadSingleAction: '/api/mmp/models/download_model', - infoTypePropertyName: 'model_type_name', - infoTagPropertyName: 'model_tag_name', + downloadAllAction: '/api/mmp/newmodel/downloadAllFiles', + downloadSingleAction: '/api/mmp/newmodel/downloadSingleFile', }, }; @@ -136,27 +127,39 @@ export type CategoryData = { path: string; }; -// 资源数据 +// 数据集、模型列表数据 export type ResourceData = { id: number; name: string; identifier: string; - description: string; - create_by: string; owner: string; - update_time: string; - time_ago: string; + version: string; is_public: boolean; - model_type_name?: string; - model_tag_name?: string; - data_type?: string; - data_tag?: string; - version?: string; + description?: string; + create_by?: string; + update_time?: string; + time_ago?: string; version_desc?: string; - processing_code?: string; - dataset_source?: string; usage?: string; + relative_paths?: string; + // 数据集 + data_type?: string; // 数据集分类 + data_tag?: string; // 研究方向 + processing_code?: string; // 处理代码 + dataset_source?: string; // 数据来源 dataset_version_vos: ResourceFileData[]; + // 模型 + model_type?: string; // 模型框架 + model_tag?: string; // 模型能力 + image?: string; // 训练镜像 + code?: string; // 训练镜像 + train_datasets?: string[]; // 训练数据集 + test_datasets?: string[]; // 测试数据集 + params?: Record; // 参数 + metrics?: Record; // 指标 + train_task?: string; // 训练任务 + model_source?: string; // 模型来源 + model_version_vos: ResourceFileData[]; }; // 版本数据 diff --git a/react-ui/src/pages/Experiment/components/ExportModelModal/index.tsx b/react-ui/src/pages/Experiment/components/ExportModelModal/index.tsx index b1d09da0..28489862 100644 --- a/react-ui/src/pages/Experiment/components/ExportModelModal/index.tsx +++ b/react-ui/src/pages/Experiment/components/ExportModelModal/index.tsx @@ -2,10 +2,10 @@ import editExperimentIcon from '@/assets/img/edit-experiment.png'; import KFModal from '@/components/KFModal'; import { type ResourceData } from '@/pages/Dataset/config'; import { - addModelsVersionDetail, + addModelVersion, exportModelReq, getModelList, - getModelVersionsById, + getModelVersionList, } from '@/services/dataset'; import { to } from '@/utils/promise'; import { InfoCircleOutlined } from '@ant-design/icons'; @@ -85,7 +85,7 @@ function ExportModelModal({ path, onOk, ...rest }: ExportModelModalProps) { // 获取模型版本列表 const getModelVersions = async (id: number) => { - const [res] = await to(getModelVersionsById(id)); + const [res] = await to(getModelVersionList(id)); if (res && res.data) { setVersions(res.data); } @@ -118,7 +118,7 @@ function ExportModelModal({ path, onOk, ...rest }: ExportModelModalProps) { // 创建模型版本 const createModelVersion = async (params: CreateModelVersionParams[]) => { - const [res] = await to(addModelsVersionDetail(params)); + const [res] = await to(addModelVersion(params)); if (res) { onOk(); } diff --git a/react-ui/src/pages/Model/components/ModelEvolution/index.tsx b/react-ui/src/pages/Model/components/ModelEvolution/index.tsx index a0e9beaf..f6712051 100644 --- a/react-ui/src/pages/Model/components/ModelEvolution/index.tsx +++ b/react-ui/src/pages/Model/components/ModelEvolution/index.tsx @@ -5,13 +5,11 @@ */ import { useEffectWhen } from '@/hooks'; -import { ResourceVersionData } from '@/pages/Dataset/config'; import { getModelAtlasReq } from '@/services/dataset/index.js'; import themes from '@/styles/theme.less'; import { to } from '@/utils/promise'; import G6, { G6GraphEvent, Graph, INode } from '@antv/g6'; -// @ts-ignore -import { Flex, Select } from 'antd'; +import { Flex } from 'antd'; import { useEffect, useRef, useState } from 'react'; import GraphLegend from '../GraphLegend'; import NodeTooltips from '../NodeTooltips'; @@ -29,7 +27,7 @@ import { type modeModelEvolutionProps = { resourceId: number; - versionList: ResourceVersionData[]; + identifier: string; version?: string; isActive: boolean; onVersionChange: (version: string) => void; @@ -38,7 +36,7 @@ type modeModelEvolutionProps = { let graph: Graph; function ModelEvolution({ resourceId, - versionList, + identifier, version, isActive, onVersionChange, @@ -217,7 +215,8 @@ function ModelEvolution({ // 获取模型依赖 const getModelAtlas = async () => { const params = { - current_model_id: resourceId, + id: resourceId, + identifier, version, }; const [res] = await to(getModelAtlasReq(params)); @@ -250,15 +249,6 @@ function ModelEvolution({ return (
- 版本号: -