diff --git a/react-ui/src/assets/img/404.png b/react-ui/src/assets/img/404.png new file mode 100644 index 00000000..610b7986 Binary files /dev/null and b/react-ui/src/assets/img/404.png differ diff --git a/react-ui/src/assets/img/modal-code-config.png b/react-ui/src/assets/img/modal-code-config.png new file mode 100644 index 00000000..776d4825 Binary files /dev/null and b/react-ui/src/assets/img/modal-code-config.png differ diff --git a/react-ui/src/assets/img/no-data.png b/react-ui/src/assets/img/no-data.png new file mode 100644 index 00000000..d2239289 Binary files /dev/null and b/react-ui/src/assets/img/no-data.png differ diff --git a/react-ui/src/assets/img/usage-icon.png b/react-ui/src/assets/img/usage-icon.png new file mode 100644 index 00000000..cda2cfaf Binary files /dev/null and b/react-ui/src/assets/img/usage-icon.png differ diff --git a/react-ui/src/components/BasicInfo/index.less b/react-ui/src/components/BasicInfo/index.less new file mode 100644 index 00000000..dd4d1ab1 --- /dev/null +++ b/react-ui/src/components/BasicInfo/index.less @@ -0,0 +1,44 @@ +.kf-basic-info { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 20px 40px; + align-items: flex-start; + width: 80%; +} + +.kf-basic-info-item { + display: flex; + align-items: flex-start; + width: calc(50% - 20px); + font-size: 16px; + line-height: 1.6; + + &__label { + position: relative; + color: @text-color-secondary; + text-align: justify; + text-align-last: justify; + + &::after { + position: absolute; + content: ':'; + } + } + + &__value { + flex: 1; + margin-left: 16px; + white-space: pre-line; + word-break: break-all; + } + + &__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 new file mode 100644 index 00000000..8cfee0ae --- /dev/null +++ b/react-ui/src/components/BasicInfo/index.tsx @@ -0,0 +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; + link?: string; + externalLink?: string; + format?: (_value?: any) => string | undefined; +}; + +type BasicInfoProps = { + datas: BasicInfoData[]; + className?: string; + style?: React.CSSProperties; + labelWidth?: number; +}; + +function BasicInfo({ datas, className, style, labelWidth = 100 }: BasicInfoProps) { + return ( +
+ {datas.map((item) => ( + + ))} +
+ ); +} + +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/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/components/KFEmpty/index.less b/react-ui/src/components/KFEmpty/index.less new file mode 100644 index 00000000..e62edff5 --- /dev/null +++ b/react-ui/src/components/KFEmpty/index.less @@ -0,0 +1,39 @@ +.kf-empty { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + + &__image { + width: 475px; + } + + &__title { + margin-top: 15px; + color: @text-color; + font-weight: 500; + font-size: 30px; + text-align: center; + } + + &__content { + margin-top: 15px; + color: @text-color-secondary; + font-size: 15px; + white-space: pre-line; + text-align: center; + } + + &__footer { + display: flex; + 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 new file mode 100644 index 00000000..e9bc79e1 --- /dev/null +++ b/react-ui/src/components/KFEmpty/index.tsx @@ -0,0 +1,67 @@ +import { Button } from 'antd'; +import classNames from 'classnames'; +import './index.less'; + +export enum EmptyType { + NoData = 'NoData', + NotFound = 'NotFound', + Developing = 'Developing', +} + +type EmptyProps = { + className?: string; + style?: React.CSSProperties; + type: EmptyType; + title?: string; + content?: string; + hasFooter?: boolean; + footer?: () => React.ReactNode; + buttonTitle?: string; + onRefresh?: () => void; +}; + +function getEmptyImage(type: EmptyType) { + switch (type) { + case EmptyType.NoData: + return require('@/assets/img/no-data.png'); + case EmptyType.NotFound: + return require('@/assets/img/404.png'); + case EmptyType.Developing: + return require('@/assets/img/missing-back.png'); + } +} + +function KFEmpty({ + className, + style, + type, + title, + content, + hasFooter = false, + footer, + buttonTitle = '刷新', + onRefresh, +}: EmptyProps) { + const image = getEmptyImage(type); + + return ( +
+ +
{title}
+
{content}
+ {hasFooter && ( +
+ {footer ? ( + footer() + ) : ( + + )} +
+ )} +
+ ); +} + +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/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/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..dbacba53 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..b61e9016 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..1d825896 100644 --- a/react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx +++ b/react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx @@ -1,9 +1,8 @@ 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 { 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 { @@ -19,8 +18,7 @@ import { type UploadProps, } from 'antd'; import { omit } from 'lodash'; -import { useEffect, useState } from 'react'; -import { CategoryData } from '../../config'; +import { useState } from 'react'; import styles from './index.less'; interface AddDatasetModalProps extends Omit { @@ -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: resourceConfig[ResourceType.Dataset].uploadAction, 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(addDataset(params)); if (res) { message.success('创建成功'); onOk?.(); @@ -94,7 +92,13 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr }} destroyOnClose > -
+ - + { + if (value === 'master') { + return Promise.reject(`版本不能为 master`); + } + return Promise.resolve(); + }, + }, ]} > @@ -125,7 +137,7 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr allowClear placeholder="请选择数据集分类" options={typeList} - fieldNames={{ label: 'name', value: 'id' }} + fieldNames={{ label: 'name', value: 'name' }} optionFilterProp="name" showSearch /> @@ -135,14 +147,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 /> - + {/* + - - - - { + if (value === 'master') { + return Promise.reject(`版本不能为 master`); + } + return Promise.resolve(); + }, }, ]} > - + - {/* - - 仅自己可见 - 工作空间可见 - - */} {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..73aa2852 --- /dev/null +++ b/react-ui/src/pages/Dataset/components/ResourceInfo/index.tsx @@ -0,0 +1,235 @@ +/* + * @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 = { + ...pick(info, ['id', 'owner', 'identifier', 'relative_paths']), + version, + }; + const [res] = await to(request(params)); + if (res) { + message.success('删除成功'); + 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 typePropertyName = config.typeParamKey as keyof ResourceData; + const tagPropertyName = config.tagParamKey as keyof ResourceData; + + return ( +
+
+ +
{info.name}
+ {info[typePropertyName] && ( +
+ {(info[typePropertyName] as string) || '--'} +
+ )} + {info[tagPropertyName] && ( +
+ {(info[tagPropertyName] 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..b1677bc3 100644 --- a/react-ui/src/pages/Dataset/config.tsx +++ b/react-ui/src/pages/Dataset/config.tsx @@ -1,20 +1,18 @@ import KFIcon from '@/components/KFIcon'; import { CommonTabKeys } from '@/enums'; import { - addDatasetVersionDetail, - addModelsVersionDetail, + addDatasetVersion, + addModelVersion, deleteDataset, deleteDatasetVersion, deleteModel, deleteModelVersion, - getDatasetById, + getDatasetInfo, getDatasetList, - getDatasetVersionIdList, - getDatasetVersionsById, - getModelById, + getDatasetVersionList, + getModelInfo, getModelList, - getModelVersionIdList, - getModelVersionsById, + getModelVersionList, } 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; // 删除版本 @@ -34,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; // 标签标题 @@ -43,28 +40,24 @@ 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 = { [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', - fileReqParamKey: 'dataset_id', + filePropKey: 'dataset_version_vos', tabItems: [ { key: CommonTabKeys.Public, @@ -84,26 +77,22 @@ export const resourceConfig: Record = { prefix: 'dataset', 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', }, [ResourceType.Model]: { getList: getModelList, - getVersions: getModelVersionsById, - getFiles: getModelVersionIdList, + 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, @@ -123,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', }, }; @@ -141,36 +127,53 @@ export type CategoryData = { path: string; }; -// 资源数据 +// 数据集、模型列表数据 export type ResourceData = { id: number; name: string; - description: string; - create_by: string; - update_time: string; - available_range: number; - model_type_name?: string; - model_tag_name?: string; - dataset_type_name?: string; - dataset_tag_name?: string; + identifier: string; + owner: string; + version: string; + is_public: boolean; + description?: string; + create_by?: string; + update_time?: string; + time_ago?: string; + version_desc?: 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[]; }; // 版本数据 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 [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/Mirror/Info/index.tsx b/react-ui/src/pages/Mirror/Info/index.tsx index 99448356..a3074a55 100644 --- a/react-ui/src/pages/Mirror/Info/index.tsx +++ b/react-ui/src/pages/Mirror/Info/index.tsx @@ -8,6 +8,7 @@ import DateTableCell from '@/components/DateTableCell'; import KFIcon from '@/components/KFIcon'; import PageTitle from '@/components/PageTitle'; import SubAreaTitle from '@/components/SubAreaTitle'; +import { MirrorVersionStatus } from '@/enums'; import { useDomSize } from '@/hooks'; import { useCacheState } from '@/hooks/pageCacheState'; import { @@ -36,7 +37,7 @@ import { useEffect, useMemo, useState } from 'react'; import MirrorStatusCell from '../components/MirrorStatusCell'; import styles from './index.less'; -type MirrorInfoData = { +export type MirrorInfoData = { name?: string; description?: string; version_count?: string; @@ -44,13 +45,14 @@ type MirrorInfoData = { image_type?: number; }; -type MirrorVersionData = { +export type MirrorVersionData = { id: number; version: string; url: string; - status: string; + status: MirrorVersionStatus; file_size: string; create_time: string; + tag_name: string; }; function MirrorInfo() { 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 (
- 版本号: -