feat: 完成数据集的修改

This commit is contained in:
cp3hnu 2024-09-11 09:00:53 +08:00
parent 877816cb14
commit da3f801fd0
43 changed files with 1371 additions and 742 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -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: ':';
}
}
}
}

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -4,7 +4,7 @@
right: 0;
bottom: 0;
left: 0;
z-index: 1000;
z-index: 1001;
display: flex;
flex-direction: column;
align-items: center;

View File

@ -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;

File diff suppressed because one or more lines are too long

View File

@ -116,6 +116,10 @@
}
}
.ant-input.ant-input-disabled {
height: 46px;
}
// 选择框高度为46px
.ant-select-single {
height: 46px;

View File

@ -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;

View File

@ -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>
);

View File

@ -1,6 +1,7 @@
.upload-tip {
margin-top: 5px;
color: @error-color;
color: @text-color-secondary;
font-size: 14px;
}
.upload-button {

View File

@ -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>

View File

@ -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>

View File

@ -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;
}
}
}
}
}
}
}

View File

@ -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;

View File

@ -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%;
}
}

View File

@ -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[] = [
{
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.name,
},
{
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.version,
},
{
label: '创建人',
value: info.create_by,
},
{
label: '更新时间',
value: info.update_time,
},
{
label: '数据来源',
value: info.dataset_source,
},
{
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;

View File

@ -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>

View File

@ -36,4 +36,8 @@
text-align: right;
}
}
&__empty {
flex: 1;
}
}

View File

@ -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>
);
}

View File

@ -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>
);

View File

@ -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;
};

View File

@ -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;

View File

@ -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 }}

View File

@ -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() {

View File

@ -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';

View File

@ -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,

View File

@ -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

View File

@ -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;

View File

@ -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(),
};

View File

@ -67,3 +67,8 @@
}
}
}
.kf-tree-title {
display: inline-block;
.singleLine();
}

View File

@ -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>

View File

@ -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({

View File

@ -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;

View File

@ -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);
},

View File

@ -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,6 +36,77 @@ export function addDatesetAndVesion(data) {
data,
});
}
// 删除数据集
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',
},
data,
});
}
// 下载数据集所有文件
export function downloadAllFiles(params) {
return request(`/api/mmp/newdataset/downloadAllFiles`, {
method: 'GET',
params
});
}
// 下载数据集单个文件
export function downloadSingleFile(params) {
return request(`/api/mmp/newdataset/downloadSinggerFile`, {
method: 'GET',
params,
});
}
// 删除数据集版本
export function deleteDatasetVersion(params) {
return request(`/api/mmp/newdataset/deleteDatasetVersion`, {
method: 'DELETE',
params,
});
}
// ----------------------------模型---------------------------------
// 分页查询模型列表
export function getModelList(params) {
return request(`/api/mmp/models`, {
method: 'GET',
params,
});
}
// 新增模型
export function addModel(data) {
return request(`/api/mmp/models/addModelAndVersion`, {
@ -33,75 +117,29 @@ export function addModel(data) {
data,
});
}
// 查询数据集简介
export function getDatasetById(id) {
return request(`/api/mmp/dataset/${id}`, {
method: 'GET',
});
}
// 查询左侧列表
export function getAssetIcon(params) {
return request(`/api/mmp/assetIcon`, {
method: 'GET',
params,
});
}
// 查询模型简介
export function getModelById(id) {
return request(`/api/mmp/models/${id}`, {
method: 'GET',
});
}
// 查询数据版本集
export function getDatasetVersionsById(id) {
return request(`/api/mmp/dataset/versions/${id}`, {
method: 'GET',
});
}
// 查询模型版本集
// 查询模型版本列表
export function getModelVersionsById(id) {
return request(`/api/mmp/models/versions/${id}`, {
method: 'GET',
});
}
// 分页查询数据集
export function getDatasetVersionIdList(params) {
return request(`/api/mmp/datasetVersion/versions`, {
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`, {

View File

@ -19,6 +19,8 @@ declare namespace API.System {
updateBy: string;
updateTime: Date;
remark: string;
gitLinkUsername?: string;
gitLinkPassword?: string;
}
export interface UserListParams {

View File

@ -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));
}
}

View File

@ -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;