| @@ -0,0 +1,35 @@ | |||
| .kf-basic-info { | |||
| display: flex; | |||
| flex-direction: row; | |||
| flex-wrap: wrap; | |||
| 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; | |||
| &__label { | |||
| width: 100px; | |||
| color: @text-color-secondary; | |||
| text-align: justify; | |||
| text-align-last: justify; | |||
| } | |||
| &__value { | |||
| display: flex; | |||
| flex: 1; | |||
| color: @text-color; | |||
| word-break: break-all; | |||
| &::before { | |||
| margin-right: 16px; | |||
| content: ':'; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,27 @@ | |||
| import './index.less'; | |||
| export type BasicInfoData = { | |||
| label: string; | |||
| value?: any; | |||
| format?: (_value: any) => string; | |||
| }; | |||
| type BasicInfoProps = { | |||
| datas: BasicInfoData[]; | |||
| }; | |||
| function BasicInfo({ datas }: BasicInfoProps) { | |||
| return ( | |||
| <div className="kf-basic-info"> | |||
| {datas.map((item) => ( | |||
| <div className="kf-basic-info__item" key={item.label}> | |||
| <div className="kf-basic-info__item__label">{item.label}</div> | |||
| <div className="kf-basic-info__item__value"> | |||
| {item.format ? item.format(item.value) ?? '--' : item.value ?? '--'} | |||
| </div> | |||
| </div> | |||
| ))} | |||
| </div> | |||
| ); | |||
| } | |||
| export default BasicInfo; | |||
| @@ -0,0 +1,40 @@ | |||
| .kf-empty { | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| justify-content: center; | |||
| width: 100%; | |||
| &__image { | |||
| width: 475px; | |||
| height: 292px; | |||
| } | |||
| &__title { | |||
| margin-top: 15px; | |||
| color: @text-color; | |||
| font-weight: 500; | |||
| font-size: 30px; | |||
| letter-spacing: 5px; | |||
| 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; | |||
| &__back-btn { | |||
| height: 32px; | |||
| } | |||
| } | |||
| } | |||
| @@ -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; | |||
| backTitle?: string; | |||
| onBack?: () => 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, | |||
| backTitle = '返回', | |||
| onBack, | |||
| }: EmptyProps) { | |||
| const image = getEmptyImage(type); | |||
| return ( | |||
| <div className={classNames('kf-empty', className)} style={style}> | |||
| <img className="kf-empty__image" src={image} /> | |||
| <div className="kf-empty__title">{title}</div> | |||
| <div className="kf-empty__content">{content}</div> | |||
| {hasFooter && ( | |||
| <div className="kf-empty__footer"> | |||
| {footer ? ( | |||
| footer() | |||
| ) : ( | |||
| <Button className="kf-empty__footer__back-btn" type="primary" onClick={onBack}> | |||
| {backTitle} | |||
| </Button> | |||
| )} | |||
| </div> | |||
| )} | |||
| </div> | |||
| ); | |||
| } | |||
| export default KFEmpty; | |||
| @@ -4,7 +4,7 @@ | |||
| right: 0; | |||
| bottom: 0; | |||
| left: 0; | |||
| z-index: 1000; | |||
| z-index: 1001; | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| @@ -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; | |||
| @@ -116,6 +116,10 @@ | |||
| } | |||
| } | |||
| .ant-input.ant-input-disabled { | |||
| height: 46px; | |||
| } | |||
| // 选择框高度为46px | |||
| .ant-select-single { | |||
| height: 46px; | |||
| @@ -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 = () => ( | |||
| <Result | |||
| status="404" | |||
| title="404" | |||
| subTitle="Sorry, the page you visited does not exist." | |||
| extra={ | |||
| <Button type="primary" onClick={() => history.push('/')}> | |||
| Back Home | |||
| </Button> | |||
| } | |||
| /> | |||
| ); | |||
| const NoFoundPage = () => { | |||
| const navigate = useNavigate(); | |||
| return ( | |||
| <KFEmpty | |||
| style={{ height: '100vh' }} | |||
| type={EmptyType.NotFound} | |||
| title="404" | |||
| content={'很抱歉,您访问的页面地址有误,\n或者该页面不存在。'} | |||
| hasFooter={true} | |||
| backTitle="返回首页" | |||
| onBack={() => navigate('/')} | |||
| ></KFEmpty> | |||
| ); | |||
| }; | |||
| export default NoFoundPage; | |||
| @@ -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<CodeConfigData[]>([]); | |||
| const [dataList, setDataList] = useState<CodeConfigData[] | undefined>(undefined); | |||
| const [total, setTotal] = useState(0); | |||
| const [pagination, setPagination] = useState<PaginationProps>({ | |||
| 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 ( | |||
| <div className={styles['code-config-list']}> | |||
| <div className={styles['code-config-list__header']}> | |||
| <span>数据总数:{total}个</span> | |||
| <span>数据总数:{total} 个</span> | |||
| <div> | |||
| <Input.Search | |||
| placeholder="按代码仓库名称筛选" | |||
| @@ -139,10 +143,10 @@ function CodeConfigList() { | |||
| </Button> | |||
| </div> | |||
| </div> | |||
| {dataList?.length !== 0 ? ( | |||
| {dataList && dataList.length !== 0 && ( | |||
| <> | |||
| <div className={styles['code-config-list__content']}> | |||
| {dataList?.map((item) => ( | |||
| {dataList.map((item) => ( | |||
| <CodeConfigItem | |||
| item={item} | |||
| key={item.id} | |||
| @@ -161,10 +165,13 @@ function CodeConfigList() { | |||
| {...pagination} | |||
| /> | |||
| </> | |||
| ) : ( | |||
| <div className={styles['code-config-list__empty']}> | |||
| <Empty></Empty> | |||
| </div> | |||
| )} | |||
| {dataList && dataList.length === 0 && ( | |||
| <KFEmpty | |||
| className={styles['code-config-list__empty']} | |||
| type={EmptyType.NoData} | |||
| title="暂无数据" | |||
| /> | |||
| )} | |||
| </div> | |||
| ); | |||
| @@ -1,6 +1,7 @@ | |||
| .upload-tip { | |||
| margin-top: 5px; | |||
| color: @error-color; | |||
| color: @text-color-secondary; | |||
| font-size: 14px; | |||
| } | |||
| .upload-button { | |||
| @@ -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<ModalProps, 'onOk'> { | |||
| function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalProps) { | |||
| const [uuid] = useState(Date.now()); | |||
| const [clusterOptions, setClusterOptions] = useState<DictValueEnumObj[]>([]); | |||
| // const [clusterOptions, setClusterOptions] = useState<DictValueEnumObj[]>([]); | |||
| 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 | |||
| > | |||
| <Form name="form" layout="vertical" onFinish={onFinish} autoComplete="off"> | |||
| <Form | |||
| name="form" | |||
| layout="vertical" | |||
| onFinish={onFinish} | |||
| initialValues={{ is_public: false }} | |||
| autoComplete="off" | |||
| > | |||
| <Form.Item | |||
| label="数据集名称" | |||
| name="name" | |||
| @@ -106,7 +110,7 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||
| }, | |||
| ]} | |||
| > | |||
| <Input placeholder="请输入数据名称" showCount allowClear maxLength={64} /> | |||
| <Input placeholder="请输入数据名称" showCount allowClear maxLength={50} /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="数据集版本" | |||
| @@ -125,7 +129,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 +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 | |||
| /> | |||
| </Form.Item> | |||
| <Form.Item label="集群版本" name="available_cluster"> | |||
| {/* <Form.Item label="集群版本" name="available_cluster"> | |||
| <Select allowClear placeholder="请选择集群版本" options={clusterOptions} /> | |||
| </Form.Item> | |||
| </Form.Item> */} | |||
| <Form.Item | |||
| label="数据集简介" | |||
| name="description" | |||
| @@ -156,15 +160,19 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||
| <Input.TextArea | |||
| placeholder="请输入数据集简介" | |||
| showCount | |||
| maxLength={256} | |||
| maxLength={200} | |||
| autoSize={{ minRows: 2, maxRows: 6 }} | |||
| allowClear | |||
| /> | |||
| </Form.Item> | |||
| <Form.Item label="选择流水线" name="range"> | |||
| <Form.Item | |||
| label="可见性" | |||
| name="is_public" | |||
| rules={[{ required: true, message: '请选择可见性' }]} | |||
| > | |||
| <Radio.Group> | |||
| <Radio value="0">仅自己可见</Radio> | |||
| <Radio value="1">工作空间可见</Radio> | |||
| <Radio value={false}>私有</Radio> | |||
| <Radio value={true}>公开</Radio> | |||
| </Radio.Group> | |||
| </Form.Item> | |||
| <Form.Item | |||
| @@ -187,7 +195,7 @@ function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalPr | |||
| > | |||
| 上传文件 | |||
| </Button> | |||
| <div className={styles['upload-tip']}>只允许上传.zip,.tgz格式文件</div> | |||
| <div className={styles['upload-tip']}>只允许上传 .zip 和 .tgz 格式文件</div> | |||
| </Upload> | |||
| </Form.Item> | |||
| </Form> | |||
| @@ -21,14 +21,16 @@ import styles from '../AddDatasetModal/index.less'; | |||
| interface AddVersionModalProps extends Omit<ModalProps, 'onOk'> { | |||
| resourceType: ResourceType; | |||
| resourceId: number; | |||
| initialName: string; | |||
| identifier: string; | |||
| resoureName: string; | |||
| onOk: () => void; | |||
| } | |||
| function AddVersionModal({ | |||
| resourceType, | |||
| resourceId, | |||
| initialName, | |||
| resoureName, | |||
| identifier, | |||
| onOk, | |||
| ...rest | |||
| }: AddVersionModalProps) { | |||
| @@ -58,17 +60,20 @@ function AddVersionModal({ | |||
| const onFinish = (formData: any) => { | |||
| const fileList: UploadFile[] = formData['fileList'] ?? []; | |||
| if (validateUploadFiles(fileList)) { | |||
| const otherParams = omit(formData, ['fileList']); | |||
| const params = fileList.map((item) => { | |||
| const dataset_version_vos = fileList.map((item) => { | |||
| const data = item.response?.data?.[0] ?? {}; | |||
| return { | |||
| ...otherParams, | |||
| [config.idParamKey]: resourceId, | |||
| file_name: data.fileName, | |||
| file_size: data.fileSize, | |||
| url: data.url, | |||
| }; | |||
| }); | |||
| const params = { | |||
| id: resourceId, | |||
| identifier, | |||
| dataset_version_vos, | |||
| ...omit(formData, 'fileList'), | |||
| }; | |||
| createDatasetVersion(params); | |||
| } | |||
| }; | |||
| @@ -90,7 +95,7 @@ function AddVersionModal({ | |||
| name="form" | |||
| layout="vertical" | |||
| initialValues={{ | |||
| name: initialName, | |||
| name: resoureName, | |||
| }} | |||
| onFinish={onFinish} | |||
| autoComplete="off" | |||
| @@ -115,13 +120,21 @@ function AddVersionModal({ | |||
| required: true, | |||
| message: `请输入${name}版本`, | |||
| }, | |||
| { | |||
| validator: (_rule, value) => { | |||
| if (value === 'master') { | |||
| return Promise.reject(`版本号不能为 master`); | |||
| } | |||
| return Promise.resolve(); | |||
| }, | |||
| }, | |||
| ]} | |||
| > | |||
| <Input placeholder={`请输入${name}版本`} maxLength={64} showCount allowClear /> | |||
| </Form.Item> | |||
| <Form.Item | |||
| label="版本描述" | |||
| name="description" | |||
| name="version_desc" | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| @@ -158,7 +171,7 @@ function AddVersionModal({ | |||
| 上传文件 | |||
| </Button> | |||
| {resourceType === ResourceType.Dataset && ( | |||
| <div className={styles['upload-tip']}>只允许上传.zip格式文件</div> | |||
| <div className={styles['upload-tip']}>只允许上传 .zip 格式文件</div> | |||
| )} | |||
| </Upload> | |||
| </Form.Item> | |||
| @@ -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; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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<ResourceData>({} 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<ResourceVersionData[]>([]); | |||
| const [version, setVersion] = useState<string | undefined>(undefined); | |||
| const [activeTab, setActiveTab] = useState<string>(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: <KFIcon type="icon-moxingjianjie" />, | |||
| children: <ResourceIntro resourceType={resourceType} info={info}></ResourceIntro>, | |||
| }, | |||
| { | |||
| key: ResourceInfoTabKeys.Version, | |||
| label: `${typeName}文件`, | |||
| icon: <KFIcon type="icon-moxingwenjian" />, | |||
| children: <ResourceVersion resourceType={resourceType} info={info}></ResourceVersion>, | |||
| }, | |||
| ]; | |||
| if (resourceType === ResourceType.Model) { | |||
| items.push({ | |||
| key: ResourceInfoTabKeys.Evolution, | |||
| label: `模型演化`, | |||
| icon: <KFIcon type="icon-moxingyanhua1" />, | |||
| children: ( | |||
| <ModelEvolution | |||
| resourceId={resourceId} | |||
| versionList={versionList} | |||
| version={version} | |||
| isActive={activeTab === ResourceInfoTabKeys.Evolution} | |||
| onVersionChange={handleVersionChange} | |||
| ></ModelEvolution> | |||
| ), | |||
| }); | |||
| } | |||
| const infoTypePropertyName = config.infoTypePropertyName as keyof ResourceData; | |||
| const infoTagPropertyName = config.infoTagPropertyName as keyof ResourceData; | |||
| return ( | |||
| <div className={styles['resource-info']}> | |||
| <div className={styles['resource-info__top']}> | |||
| <Flex align="center" gap={10} style={{ marginBottom: '20px' }}> | |||
| <div className={styles['resource-info__top__name']}>{info.name}</div> | |||
| {info[infoTypePropertyName] && ( | |||
| <div className={styles['resource-info__top__tag']}> | |||
| {(info[infoTypePropertyName] as string) || '--'} | |||
| </div> | |||
| )} | |||
| {info[infoTagPropertyName] && ( | |||
| <div className={styles['resource-info__top__tag']}> | |||
| {(info[infoTagPropertyName] as string) || '--'} | |||
| </div> | |||
| )} | |||
| </Flex> | |||
| <Flex align="center"> | |||
| <span style={{ marginRight: '10px' }}>版本号:</span> | |||
| <Select | |||
| placeholder="请选择版本号" | |||
| style={{ width: '160px', marginRight: '20px' }} | |||
| value={version} | |||
| onChange={handleVersionChange} | |||
| fieldNames={{ label: 'name', value: 'name' }} | |||
| options={versionList} | |||
| /> | |||
| <Button type="default" onClick={showModal} icon={<KFIcon type="icon-xinjian2" />}> | |||
| 创建新版本 | |||
| </Button> | |||
| <Button | |||
| type="default" | |||
| style={{ marginLeft: 'auto', marginRight: 0 }} | |||
| onClick={hanldeDelete} | |||
| icon={<KFIcon type="icon-shanchu" />} | |||
| disabled={!version} | |||
| danger | |||
| > | |||
| 删除版本 | |||
| </Button> | |||
| </Flex> | |||
| </div> | |||
| <div className={styles['resource-info__bottom']}> | |||
| <Tabs activeKey={activeTab} items={items} onChange={(key) => setActiveTab(key)}></Tabs> | |||
| </div> | |||
| </div> | |||
| ); | |||
| }; | |||
| export default ResourceInfo; | |||
| @@ -1,65 +1,10 @@ | |||
| .resource-intro { | |||
| height: 100%; | |||
| &__top { | |||
| width: 100%; | |||
| margin-top: 24px; | |||
| &__basic { | |||
| width: 100%; | |||
| height: 110px; | |||
| margin-bottom: 10px; | |||
| padding: 20px 30px 0; | |||
| background-image: url(@/assets/img/dataset-intro-top.png); | |||
| background-repeat: no-repeat; | |||
| background-position: top center; | |||
| background-size: 100% 100%; | |||
| &__name { | |||
| margin-bottom: 12px; | |||
| color: @text-color; | |||
| font-size: 20px; | |||
| } | |||
| &__tag { | |||
| margin-right: 10px; | |||
| padding: 4px 10px; | |||
| color: @primary-color; | |||
| font-size: 14px; | |||
| background: rgba(22, 100, 255, 0.1); | |||
| border-radius: 4px; | |||
| } | |||
| } | |||
| &__bottom { | |||
| height: calc(100% - 120px); | |||
| 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; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| &__title { | |||
| margin: 30px 0 10px; | |||
| color: @text-color; | |||
| font-weight: 500; | |||
| font-size: @font-size; | |||
| } | |||
| &__intro { | |||
| color: @text-color-secondary; | |||
| font-size: 14px; | |||
| &__usage { | |||
| width: 100%; | |||
| } | |||
| } | |||
| @@ -1,156 +1,78 @@ | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import ModelEvolution from '@/pages/Model/components/ModelEvolution'; | |||
| import { to } from '@/utils/promise'; | |||
| import { useParams, useSearchParams } from '@umijs/max'; | |||
| import { Flex, Tabs } from 'antd'; | |||
| import { useEffect, useState } from 'react'; | |||
| import { ResourceData, ResourceType, resourceConfig } from '../../config'; | |||
| import ResourceVersion from '../ResourceVersion'; | |||
| import BasicInfo, { BasicInfoData } from '@/components/BasicInfo'; | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { ResourceData, ResourceType } from '@/pages/Dataset/config'; | |||
| import styles from './index.less'; | |||
| // 这里值小写是因为值会写在 url 中 | |||
| export enum ResourceInfoTabKeys { | |||
| Introduction = 'introduction', // 简介 | |||
| Version = 'version', // 版本 | |||
| Evolution = 'evolution', // 演化 | |||
| } | |||
| type ResourceIntroProps = { | |||
| resourceType: ResourceType; | |||
| info: ResourceData; | |||
| }; | |||
| const ResourceIntro = ({ resourceType }: ResourceIntroProps) => { | |||
| const [info, setInfo] = useState<ResourceData>({} as ResourceData); | |||
| const locationParams = useParams(); | |||
| const [searchParams] = useSearchParams(); | |||
| const defaultTab = searchParams.get('tab') || ResourceInfoTabKeys.Introduction; | |||
| let versionParam = searchParams.get('version'); | |||
| const [versionList, setVersionList] = useState([]); | |||
| const [version, setVersion] = useState<string | undefined>(undefined); | |||
| const [activeTab, setActiveTab] = useState<string>(defaultTab); | |||
| const resourceId = Number(locationParams.id); | |||
| const config = resourceConfig[resourceType]; | |||
| const typeName = config.name; // 数据集/模型 | |||
| useEffect(() => { | |||
| getModelByDetail(); | |||
| getVersionList(); | |||
| }, [resourceId]); | |||
| // 获取详情 | |||
| const getModelByDetail = async () => { | |||
| const request = config.getInfo; | |||
| const [res] = await to(request(resourceId)); | |||
| if (res) { | |||
| setInfo(res.data); | |||
| } | |||
| }; | |||
| // 获取版本列表 | |||
| const getVersionList = async () => { | |||
| const request = config.getVersions; | |||
| const [res] = await to(request(resourceId)); | |||
| if (res && res.data && res.data.length > 0) { | |||
| setVersionList( | |||
| res.data.map((item: string) => { | |||
| return { | |||
| label: item, | |||
| value: item, | |||
| }; | |||
| }), | |||
| ); | |||
| if (versionParam && res.data.includes(versionParam)) { | |||
| setVersion(versionParam); | |||
| versionParam = null; | |||
| } else { | |||
| setVersion(res.data[0]); | |||
| } | |||
| } else { | |||
| setVersion(undefined); | |||
| } | |||
| }; | |||
| // 版本变化 | |||
| const handleVersionChange = (value: string) => { | |||
| setVersion(value); | |||
| }; | |||
| const items = [ | |||
| function ResourceIntro({ info }: ResourceIntroProps) { | |||
| const basicDatas: BasicInfoData[] = [ | |||
| { | |||
| label: '数据集名称', | |||
| value: info.name, | |||
| }, | |||
| { | |||
| label: '版本', | |||
| value: info.version, | |||
| }, | |||
| { | |||
| label: '创建人', | |||
| value: info.create_by, | |||
| }, | |||
| { | |||
| label: '更新时间', | |||
| value: info.update_time, | |||
| }, | |||
| { | |||
| key: ResourceInfoTabKeys.Introduction, | |||
| label: `${typeName}简介`, | |||
| icon: <KFIcon type="icon-moxingjianjie" />, | |||
| children: ( | |||
| <> | |||
| <div className={styles['resource-intro__title']}>简介</div> | |||
| <div className={styles['resource-intro__intro']}>{info.description}</div> | |||
| </> | |||
| ), | |||
| label: '数据来源', | |||
| value: info.dataset_source, | |||
| }, | |||
| { | |||
| key: ResourceInfoTabKeys.Version, | |||
| label: `${typeName}文件/版本`, | |||
| icon: <KFIcon type="icon-moxingwenjian" />, | |||
| children: ( | |||
| <ResourceVersion | |||
| resourceType={resourceType} | |||
| resourceId={resourceId} | |||
| resourceName={info.name} | |||
| isPublic={info.available_range === 1} | |||
| versionList={versionList} | |||
| version={version} | |||
| isActive={activeTab === ResourceInfoTabKeys.Version} | |||
| getVersionList={getVersionList} | |||
| onVersionChange={handleVersionChange} | |||
| ></ResourceVersion> | |||
| ), | |||
| label: '处理代码', | |||
| value: info.processing_code, | |||
| }, | |||
| { | |||
| label: '数据集分类', | |||
| value: info.data_type, | |||
| }, | |||
| { | |||
| label: '研究方向', | |||
| value: info.data_tag, | |||
| }, | |||
| { | |||
| label: '数据集描述', | |||
| value: info.description, | |||
| }, | |||
| { | |||
| label: '版本描述', | |||
| value: info.version_desc, | |||
| }, | |||
| ]; | |||
| if (resourceType === ResourceType.Model) { | |||
| items.push({ | |||
| key: ResourceInfoTabKeys.Evolution, | |||
| label: `模型演化`, | |||
| icon: <KFIcon type="icon-moxingyanhua1" />, | |||
| children: ( | |||
| <ModelEvolution | |||
| resourceId={resourceId} | |||
| versionList={versionList} | |||
| version={version} | |||
| isActive={activeTab === ResourceInfoTabKeys.Evolution} | |||
| onVersionChange={handleVersionChange} | |||
| ></ModelEvolution> | |||
| ), | |||
| }); | |||
| } | |||
| const infoTypePropertyName = config.infoTypePropertyName as keyof ResourceData; | |||
| const infoTagPropertyName = config.infoTagPropertyName as keyof ResourceData; | |||
| return ( | |||
| <div className={styles['resource-intro']}> | |||
| <div className={styles['resource-intro__top']}> | |||
| <div className={styles['resource-intro__top__name']}>{info.name}</div> | |||
| <Flex align="center"> | |||
| <div className={styles['resource-intro__top__tag']}> | |||
| {typeName} id:{info.id} | |||
| </div> | |||
| {info[infoTypePropertyName] && ( | |||
| <div className={styles['resource-intro__top__tag']}> | |||
| {info[infoTypePropertyName] || '--'} | |||
| </div> | |||
| )} | |||
| {info[infoTagPropertyName] && ( | |||
| <div className={styles['resource-intro__top__tag']}> | |||
| {info[infoTagPropertyName] || '--'} | |||
| </div> | |||
| )} | |||
| </Flex> | |||
| </div> | |||
| <div className={styles['resource-intro__bottom']}> | |||
| <Tabs activeKey={activeTab} items={items} onChange={(key) => setActiveTab(key)}></Tabs> | |||
| <SubAreaTitle | |||
| title="基本信息" | |||
| image={require('@/assets/img/mirror-basic.png')} | |||
| style={{ marginBottom: '26px' }} | |||
| ></SubAreaTitle> | |||
| <div className={styles['resource-intro__basic']}> | |||
| <BasicInfo datas={basicDatas}></BasicInfo> | |||
| </div> | |||
| <SubAreaTitle | |||
| title="实例用法" | |||
| image={require('@/assets/img/usage-icon.png')} | |||
| style={{ margin: '40px 0 24px' }} | |||
| ></SubAreaTitle> | |||
| <div | |||
| className={styles['resource-intro__usage']} | |||
| dangerouslySetInnerHTML={{ __html: info.usage ?? '暂无实例用法' }} | |||
| ></div> | |||
| </div> | |||
| ); | |||
| }; | |||
| } | |||
| export default ResourceIntro; | |||
| @@ -44,7 +44,10 @@ function ResourceItem({ item, isPublic, onClick, onRemove }: ResourceItemProps) | |||
| </div> | |||
| <div className={styles['resource-item__time']}> | |||
| <img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" /> | |||
| <span>最近更新: {formatDate(item.update_time, 'YYYY-MM-DD')}</span> | |||
| <span> | |||
| {'最近更新: '} | |||
| {item.update_time ? formatDate(item.update_time, 'YYYY-MM-DD') : item.time_ago ?? ''} | |||
| </span> | |||
| </div> | |||
| </Flex> | |||
| </div> | |||
| @@ -36,4 +36,8 @@ | |||
| text-align: right; | |||
| } | |||
| } | |||
| &__empty { | |||
| flex: 1; | |||
| } | |||
| } | |||
| @@ -1,11 +1,14 @@ | |||
| import KFEmpty, { EmptyType } from '@/components/KFEmpty'; | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { CommonTabKeys } from '@/enums'; | |||
| import AddModelModal from '@/pages/Dataset/components/AddModelModal'; | |||
| import { openAntdModal } from '@/utils/modal'; | |||
| import { to } from '@/utils/promise'; | |||
| import { resourceItemKey, setSessionStorageItem } from '@/utils/sessionStorage'; | |||
| import { modalConfirm } from '@/utils/ui'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import { App, Button, Input, Pagination, PaginationProps } from 'antd'; | |||
| import { pick } from 'lodash'; | |||
| import { Ref, forwardRef, useEffect, useImperativeHandle, useState } from 'react'; | |||
| import { CategoryData, ResourceData, ResourceType, resourceConfig } from '../../config'; | |||
| import AddDatasetModal from '../AddDatasetModal'; | |||
| @@ -43,7 +46,7 @@ function ResourceList( | |||
| ref: Ref<ResourceListRef>, | |||
| ) { | |||
| const navigate = useNavigate(); | |||
| const [dataList, setDataList] = useState<ResourceData[]>([]); | |||
| const [dataList, setDataList] = useState<ResourceData[] | undefined>(undefined); | |||
| const [total, setTotal] = useState(0); | |||
| const [pagination, setPagination] = useState<PaginationProps>( | |||
| initialPagination ?? { | |||
| @@ -71,7 +74,8 @@ function ResourceList( | |||
| }); | |||
| setSearchText(''); | |||
| setInputText(''); | |||
| setDataList([]); | |||
| setDataList(undefined); | |||
| setTotal(0); | |||
| }, | |||
| }; | |||
| }, | |||
| @@ -80,26 +84,33 @@ function ResourceList( | |||
| // 获取数据请求 | |||
| const getDataList = async () => { | |||
| const params = { | |||
| const params: Record<string, any> = { | |||
| page: pagination.current! - 1, | |||
| size: pagination.pageSize, | |||
| [config.typeParamKey]: dataType, | |||
| [config.tagParamKey]: dataTag, | |||
| available_range: isPublic ? 1 : 0, | |||
| name: searchText !== '' ? searchText : undefined, | |||
| }; | |||
| if (resourceType === ResourceType.Dataset) { | |||
| params['is_public'] = isPublic; | |||
| } else { | |||
| params['available_range'] = isPublic ? 1 : 0; | |||
| } | |||
| const request = config.getList; | |||
| const [res] = await to(request(params)); | |||
| if (res && res.data && res.data.content) { | |||
| setDataList(res.data.content); | |||
| setTotal(res.data.totalElements); | |||
| } else { | |||
| setDataList([]); | |||
| setTotal(0); | |||
| } | |||
| }; | |||
| // 删除请求 | |||
| const deleteRecord = async (id: number) => { | |||
| const deleteRecord = async (params: { owner: string; identifier: string }) => { | |||
| const request = config.deleteRecord; | |||
| const [res] = await to(request(id)); | |||
| const [res] = await to(request(params)); | |||
| if (res) { | |||
| getDataList(); | |||
| message.success('删除成功'); | |||
| @@ -116,7 +127,7 @@ function ResourceList( | |||
| modalConfirm({ | |||
| title: config.deleteModalTitle, | |||
| onOk: () => { | |||
| deleteRecord(record.id); | |||
| deleteRecord(pick(record, ['owner', 'identifier'])); | |||
| }, | |||
| }); | |||
| }; | |||
| @@ -131,6 +142,7 @@ function ResourceList( | |||
| activeTag: dataTag, | |||
| }); | |||
| const prefix = config.prefix; | |||
| setSessionStorageItem(resourceItemKey, record, true); | |||
| navigate(`/dataset/${prefix}/info/${record.id}`); | |||
| }; | |||
| @@ -158,7 +170,7 @@ function ResourceList( | |||
| return ( | |||
| <div className={styles['resource-list']}> | |||
| <div className={styles['resource-list__header']}> | |||
| <span>数据总数:{total}个</span> | |||
| <span>数据总数:{total} 个</span> | |||
| <div> | |||
| <Input.Search | |||
| placeholder={`按${config.name}名称筛选`} | |||
| @@ -182,26 +194,37 @@ function ResourceList( | |||
| )} | |||
| </div> | |||
| </div> | |||
| <div className={styles['resource-list__content']}> | |||
| {dataList?.map((item) => ( | |||
| <ResourceItem | |||
| item={item} | |||
| key={item.id} | |||
| isPublic={isPublic} | |||
| onRemove={handleRemove} | |||
| onClick={handleClick} | |||
| ></ResourceItem> | |||
| ))} | |||
| </div> | |||
| <Pagination | |||
| total={total} | |||
| showSizeChanger | |||
| defaultPageSize={20} | |||
| pageSizeOptions={[20, 40, 60, 80, 100]} | |||
| showQuickJumper | |||
| onChange={handlePageChange} | |||
| {...pagination} | |||
| /> | |||
| {dataList && dataList.length > 0 && ( | |||
| <> | |||
| <div className={styles['resource-list__content']}> | |||
| {dataList?.map((item) => ( | |||
| <ResourceItem | |||
| item={item} | |||
| key={item.id} | |||
| isPublic={isPublic} | |||
| onRemove={handleRemove} | |||
| onClick={handleClick} | |||
| ></ResourceItem> | |||
| ))} | |||
| </div> | |||
| <Pagination | |||
| total={total} | |||
| showSizeChanger | |||
| defaultPageSize={20} | |||
| pageSizeOptions={[20, 40, 60, 80, 100]} | |||
| showQuickJumper | |||
| onChange={handlePageChange} | |||
| {...pagination} | |||
| /> | |||
| </> | |||
| )} | |||
| {dataList && dataList.length === 0 && ( | |||
| <KFEmpty | |||
| className={styles['resource-list__empty']} | |||
| type={EmptyType.NoData} | |||
| title="暂无数据" | |||
| /> | |||
| )} | |||
| </div> | |||
| ); | |||
| } | |||
| @@ -1,125 +1,35 @@ | |||
| import CommonTableCell from '@/components/CommonTableCell'; | |||
| import DateTableCell from '@/components/DateTableCell'; | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { useEffectWhen } from '@/hooks'; | |||
| import AddVersionModal from '@/pages/Dataset/components/AddVersionModal'; | |||
| import { | |||
| ResourceData, | |||
| ResourceFileData, | |||
| ResourceType, | |||
| ResourceVersionData, | |||
| resourceConfig, | |||
| } from '@/pages/Dataset/config'; | |||
| import { downLoadZip } from '@/utils/downloadfile'; | |||
| import { openAntdModal } from '@/utils/modal'; | |||
| import { to } from '@/utils/promise'; | |||
| import { modalConfirm } from '@/utils/ui'; | |||
| import { App, Button, Flex, Select, Table } from 'antd'; | |||
| import { useState } from 'react'; | |||
| import { Button, Flex, Table } from 'antd'; | |||
| import styles from './index.less'; | |||
| type ResourceVersionProps = { | |||
| resourceType: ResourceType; | |||
| resourceId: number; | |||
| resourceName: string; | |||
| isPublic: boolean; | |||
| versionList: ResourceVersionData[]; | |||
| version?: string; | |||
| isActive: boolean; | |||
| getVersionList: () => void; | |||
| onVersionChange: (version: string) => void; | |||
| info: ResourceData; | |||
| }; | |||
| function ResourceVersion({ | |||
| resourceType, | |||
| resourceId, | |||
| resourceName, | |||
| isPublic, | |||
| versionList, | |||
| version, | |||
| isActive, | |||
| getVersionList, | |||
| onVersionChange, | |||
| }: ResourceVersionProps) { | |||
| const [fileList, setFileList] = useState<ResourceFileData[]>([]); | |||
| const { message } = App.useApp(); | |||
| function ResourceVersion({ resourceType, info }: ResourceVersionProps) { | |||
| const config = resourceConfig[resourceType]; | |||
| // 获取版本文件列表 | |||
| useEffectWhen( | |||
| () => { | |||
| if (version) { | |||
| getFileList(version); | |||
| } else { | |||
| setFileList([]); | |||
| } | |||
| }, | |||
| [resourceId, version], | |||
| isActive, | |||
| ); | |||
| // 获取版本下的文件列表 | |||
| const getFileList = async (version: string) => { | |||
| const params = { | |||
| version, | |||
| [config.fileReqParamKey]: resourceId, | |||
| }; | |||
| const request = config.getFiles; | |||
| const [res] = await to(request(params)); | |||
| if (res) { | |||
| setFileList(res?.data?.content ?? []); | |||
| } | |||
| }; | |||
| // 删除版本 | |||
| const deleteVersion = async () => { | |||
| const request = config.deleteVersion; | |||
| const params = { | |||
| [config.idParamKey]: resourceId, | |||
| version, | |||
| }; | |||
| const [res] = await to(request(params)); | |||
| if (res) { | |||
| getVersionList(); | |||
| message.success('删除成功'); | |||
| } | |||
| }; | |||
| // 新建版本 | |||
| const showModal = () => { | |||
| const { close } = openAntdModal(AddVersionModal, { | |||
| resourceType: resourceType, | |||
| resourceId: resourceId, | |||
| initialName: resourceName, | |||
| onOk: () => { | |||
| getVersionList(); | |||
| close(); | |||
| }, | |||
| }); | |||
| }; | |||
| // 处理删除 | |||
| const hanldeDelete = () => { | |||
| modalConfirm({ | |||
| title: '删除后,该版本将不可恢复', | |||
| content: '是否确认删除?', | |||
| okText: '确认', | |||
| cancelText: '取消', | |||
| onOk: () => { | |||
| deleteVersion(); | |||
| }, | |||
| }); | |||
| }; | |||
| const fileList = info.dataset_version_vos ?? []; | |||
| fileList.forEach((item) => (item.update_time = info.update_time)); | |||
| // 全部导出 | |||
| const handleExport = async () => { | |||
| const url = config.downloadAllAction; | |||
| downLoadZip(url, { models_id: resourceId, version }); | |||
| downLoadZip(url, { name: info.name, id: info.id, version: info.version }); | |||
| }; | |||
| // 单个导出 | |||
| const downloadAlone = (record: ResourceFileData) => { | |||
| const downloadAlone = async (record: ResourceFileData) => { | |||
| const url = config.downloadSingleAction; | |||
| downLoadZip(`${url}/${record.id}`); | |||
| downLoadZip(url, { url: record.url }); | |||
| }; | |||
| const columns = [ | |||
| @@ -142,12 +52,6 @@ function ResourceVersion({ | |||
| </a> | |||
| ), | |||
| }, | |||
| { | |||
| title: '版本号', | |||
| dataIndex: 'version', | |||
| key: 'version', | |||
| render: CommonTableCell(), | |||
| }, | |||
| { | |||
| title: '文件大小', | |||
| dataIndex: 'file_size', | |||
| @@ -163,7 +67,7 @@ function ResourceVersion({ | |||
| { | |||
| title: '操作', | |||
| dataIndex: 'option', | |||
| width: '100px', | |||
| width: 160, | |||
| key: 'option', | |||
| render: (_: any, record: ResourceFileData) => [ | |||
| <Button | |||
| @@ -183,32 +87,9 @@ function ResourceVersion({ | |||
| <div className={styles['resource-version']}> | |||
| <Flex justify="space-between" align="center" style={{ margin: '30px 0' }}> | |||
| <Flex align="center"> | |||
| <span style={{ marginRight: '10px' }}>版本号:</span> | |||
| <Select | |||
| placeholder="请选择版本号" | |||
| style={{ width: '160px', marginRight: '20px' }} | |||
| value={version} | |||
| onChange={onVersionChange} | |||
| options={versionList} | |||
| /> | |||
| <Button type="default" onClick={showModal} icon={<KFIcon type="icon-xinjian2" />}> | |||
| 创建新版本 | |||
| </Button> | |||
| </Flex> | |||
| <Flex align="center"> | |||
| {!isPublic && ( | |||
| <Button | |||
| type="default" | |||
| style={{ marginRight: '20px' }} | |||
| onClick={hanldeDelete} | |||
| icon={<KFIcon type="icon-shanchu" />} | |||
| > | |||
| 删除 | |||
| </Button> | |||
| )} | |||
| <Button | |||
| type="default" | |||
| disabled={!version} | |||
| disabled={fileList.length === 0} | |||
| onClick={handleExport} | |||
| icon={<KFIcon type="icon-xiazai" />} | |||
| > | |||
| @@ -216,11 +97,6 @@ function ResourceVersion({ | |||
| </Button> | |||
| </Flex> | |||
| </Flex> | |||
| <div style={{ marginBottom: '30px', fontSize: '15px' }}> | |||
| {fileList.length > 0 && fileList[0].description | |||
| ? '版本描述:' + fileList[0].description | |||
| : null} | |||
| </div> | |||
| <Table columns={columns} dataSource={fileList} pagination={false} rowKey="id" /> | |||
| </div> | |||
| ); | |||
| @@ -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<any>; // 获取资源列表 | |||
| getVersions: (params: any) => Promise<any>; // 获取版本列表 | |||
| getFiles: (params: any) => Promise<any>; // 获取版本下的文件列表 | |||
| deleteRecord: (params: any) => Promise<any>; // 删除 | |||
| addVersion: (params: any) => Promise<any>; // 新增版本 | |||
| deleteVersion: (params: any) => Promise<any>; // 删除版本 | |||
| @@ -55,12 +52,11 @@ type ResourceTypeInfo = { | |||
| export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = { | |||
| [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<ResourceType, ResourceTypeInfo> = { | |||
| 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; | |||
| }; | |||
| @@ -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 <ResourceIntro resourceType={ResourceType.Dataset} />; | |||
| function DatasetInfo() { | |||
| return <ResourceInfo resourceType={ResourceType.Dataset} />; | |||
| } | |||
| export default DatasetIntro; | |||
| export default DatasetInfo; | |||
| @@ -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() { | |||
| <Form | |||
| name="editor-create" | |||
| labelCol={{ flex: '100px' }} | |||
| wrapperCol={{ flex: 1 }} | |||
| labelAlign="left" | |||
| form={form} | |||
| initialValues={{ computing_resource: ComputingResourceType.GPU }} | |||
| @@ -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() { | |||
| @@ -1,4 +1,4 @@ | |||
| import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceIntro'; | |||
| import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceInfo'; | |||
| import { formatDate } from '@/utils/date'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import { ModelDepsData, NodeType, ProjectDependency, TrainDataset } from '../ModelEvolution/utils'; | |||
| @@ -7,12 +7,12 @@ import KFIcon from '@/components/KFIcon'; | |||
| import PageTitle from '@/components/PageTitle'; | |||
| import ResourceSelect, { | |||
| requiredValidator, | |||
| ResourceSelectorType, | |||
| type ParameterInputObject, | |||
| } from '@/components/ResourceSelect'; | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { CommonTabKeys } from '@/enums'; | |||
| import { useComputingResource } from '@/hooks/resource'; | |||
| import { ResourceSelectorType } from '@/pages/Pipeline/components/ResourceSelectorModal'; | |||
| import { | |||
| createModelDeploymentReq, | |||
| restartModelDeploymentReq, | |||
| @@ -67,7 +67,7 @@ function CodeSelectorModal({ onOk, ...rest }: CodeSelectorModalProps) { | |||
| <KFModal | |||
| {...rest} | |||
| title="选择代码配置" | |||
| image={require('@/assets/img/edit-experiment.png')} | |||
| image={require('@/assets/img/modal-code-config.png')} | |||
| width={920} | |||
| footer={null} | |||
| destroyOnClose | |||
| @@ -245,6 +245,10 @@ const PipelineNodeParameter = forwardRef(({ onFormChange }: PipelineNodeParamete | |||
| // 获取选择数据集、模型后面按钮 icon | |||
| const getSelectBtnIcon = (item: { item_type: string }) => { | |||
| const type = item.item_type; | |||
| if (type === 'code') { | |||
| return <KFIcon type="icon-xuanzedaimapeizhi" />; | |||
| } | |||
| let selectorType: ResourceSelectorType; | |||
| if (type === 'dataset') { | |||
| selectorType = ResourceSelectorType.Dataset; | |||
| @@ -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<any>; // 获取资源列表 | |||
| getVersions: (params: any) => Promise<any>; // 获取资源版本列表 | |||
| getFiles: (params: any) => Promise<any>; // 获取资源文件列表 | |||
| 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<any> => { | |||
| 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<any>; // 获取资源列表 | |||
| // getVersions: (params: any) => Promise<any>; // 获取资源版本列表 | |||
| // getFiles: (params: any) => Promise<any>; // 获取资源文件列表 | |||
| // 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, SelectorTypeInfo> = { | |||
| // [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<any>; // 获取资源列表 | |||
| getVersions: (parentKey: string, parentNode: any) => Promise<any>; // 获取资源版本列表 | |||
| getFiles: (parentKey: string, parentNode: any) => Promise<any>; // 获取资源文件列表 | |||
| 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, SelectorTypeInfo> = { | |||
| [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(), | |||
| }; | |||
| @@ -67,3 +67,8 @@ | |||
| } | |||
| } | |||
| } | |||
| .kf-tree-title { | |||
| display: inline-block; | |||
| .singleLine(); | |||
| } | |||
| @@ -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<ModalProps, 'onOk'> { | |||
| type TreeRef = GetRef<typeof Tree<TreeDataNode>>; | |||
| // 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<ResourceFile[]>([]); | |||
| 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<TreeRef>(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 ( | |||
| <span | |||
| className={styles['kf-tree-title']} | |||
| style={{ width: nodeData.isLeaf ? '370px' : '420px' }} | |||
| > | |||
| {nodeData.title as string} | |||
| </span> | |||
| ); | |||
| }} | |||
| checkable | |||
| /> | |||
| </div> | |||
| @@ -62,6 +62,8 @@ const UserForm: React.FC<UserFormProps> = (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<UserFormProps> = (props) => { | |||
| colProps={{ md: 12, xl: 12 }} | |||
| rules={[{ required: true, message: '请选择角色!' }]} | |||
| /> | |||
| <ProFormText | |||
| name="gitLinkUsername" | |||
| label="Git 用户名" | |||
| placeholder="请输入 Git 用户名" | |||
| colProps={{ xs: 24, md: 12, xl: 12 }} | |||
| rules={[ | |||
| { | |||
| required: true, | |||
| message: '请输入 Git 用户名!', | |||
| }, | |||
| ]} | |||
| /> | |||
| <ProFormText.Password | |||
| name="gitLinkPassword" | |||
| label="Git 密码" | |||
| placeholder="请输入 Git 密码" | |||
| colProps={{ xs: 24, md: 12, xl: 12 }} | |||
| fieldProps={{ | |||
| autoComplete: 'new-password', | |||
| }} | |||
| rules={props.values.userId ? [] : [{ required: true, message: '请输入 Git 密码!' }]} | |||
| /> | |||
| <ProFormTextArea | |||
| name="remark" | |||
| label={intl.formatMessage({ | |||
| @@ -1,10 +1,20 @@ | |||
| import missingPage from '@/assets/img/missing-back.png'; | |||
| import KFEmpty, { EmptyType } from '@/components/KFEmpty'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| const MissingPage = () => ( | |||
| <div style={{ width: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center' }}> | |||
| <img src={missingPage} style={{ width: '575px', margin: '278px 0 44px 0' }} alt="" /> | |||
| <span style={{ color: '#575757', fontSize: '16px' }}>页面开发中,敬请期待......</span> | |||
| </div> | |||
| ); | |||
| const MissingPage = () => { | |||
| const navigate = useNavigate(); | |||
| return ( | |||
| <KFEmpty | |||
| style={{ height: '100%' }} | |||
| type={EmptyType.Developing} | |||
| title="敬请期待~" | |||
| content={'很抱歉,您访问的正在开发中,\n请耐心等待。'} | |||
| hasFooter={true} | |||
| backTitle="返回首页" | |||
| onBack={() => navigate('/')} | |||
| ></KFEmpty> | |||
| ); | |||
| }; | |||
| export default MissingPage; | |||
| @@ -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); | |||
| }, | |||
| @@ -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`, { | |||
| @@ -19,6 +19,8 @@ declare namespace API.System { | |||
| updateBy: string; | |||
| updateTime: Date; | |||
| remark: string; | |||
| gitLinkUsername?: string; | |||
| gitLinkPassword?: string; | |||
| } | |||
| export interface UserListParams { | |||
| @@ -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<typeof setTimeout> | 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<typeof setTimeout>; | |||
| let renderTimeoutId: ReturnType<typeof setTimeout>; | |||
| 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 = <KFSpin {...spinProps} />; | |||
| root.render( | |||
| <ConfigProvider | |||
| prefixCls={rootPrefixCls} | |||
| @@ -45,21 +53,41 @@ export class Loading { | |||
| </ConfigProvider>, | |||
| ); | |||
| }); | |||
| } | |||
| }; | |||
| 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)); | |||
| } | |||
| } | |||
| @@ -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; | |||