Browse Source

Merge pull request 'refactor: 重构数据集、模型简介' (#53) from dev-zw into dev

pull/56/head
cp3hnu 1 year ago
parent
commit
ae82533ee3
57 changed files with 1191 additions and 1177 deletions
  1. +10
    -1
      react-ui/src/components/KFModal/index.tsx
  2. +5
    -4
      react-ui/src/components/ParameterInput/index.tsx
  3. +72
    -0
      react-ui/src/components/ParameterSelect/config.tsx
  4. +58
    -0
      react-ui/src/components/ParameterSelect/index.tsx
  5. +2
    -3
      react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx
  6. +2
    -3
      react-ui/src/pages/Dataset/components/AddModelModal/index.tsx
  7. +3
    -4
      react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx
  8. +3
    -3
      react-ui/src/pages/Dataset/components/CategoryItem/index.tsx
  9. +1
    -1
      react-ui/src/pages/Dataset/components/CategoryList/index.tsx
  10. +52
    -0
      react-ui/src/pages/Dataset/components/ResourceIntro/index.less
  11. +86
    -0
      react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx
  12. +3
    -6
      react-ui/src/pages/Dataset/components/ResourceList/index.tsx
  13. +1
    -1
      react-ui/src/pages/Dataset/components/ResourcePage/index.tsx
  14. +4
    -0
      react-ui/src/pages/Dataset/components/ResourceVersion/index.less
  15. +236
    -0
      react-ui/src/pages/Dataset/components/ResourceVersion/index.tsx
  16. +1
    -1
      react-ui/src/pages/Dataset/components/Resourcetem/index.tsx
  17. +48
    -8
      react-ui/src/pages/Dataset/config.tsx
  18. +1
    -1
      react-ui/src/pages/Dataset/index.tsx
  19. +0
    -263
      react-ui/src/pages/Dataset/intro.jsx
  20. +0
    -82
      react-ui/src/pages/Dataset/intro.less
  21. +8
    -0
      react-ui/src/pages/Dataset/intro.tsx
  22. +10
    -17
      react-ui/src/pages/Experiment/components/ExperimentParameter/index.tsx
  23. +5
    -0
      react-ui/src/pages/Experiment/components/ExperimentResult/index.less
  24. +30
    -26
      react-ui/src/pages/Experiment/components/ExperimentResult/index.tsx
  25. +5
    -0
      react-ui/src/pages/Experiment/components/LogGroup/index.less
  26. +53
    -2
      react-ui/src/pages/Experiment/components/LogGroup/index.tsx
  27. +10
    -0
      react-ui/src/pages/Experiment/components/LogList/index.less
  28. +5
    -3
      react-ui/src/pages/Experiment/components/LogList/index.tsx
  29. +3
    -3
      react-ui/src/pages/Experiment/components/TensorBoardStatus/index.less
  30. +4
    -4
      react-ui/src/pages/Experiment/components/ViewParamsModal/index.less
  31. +9
    -8
      react-ui/src/pages/Experiment/index.jsx
  32. +8
    -3
      react-ui/src/pages/Experiment/index.less
  33. +10
    -8
      react-ui/src/pages/Experiment/status.ts
  34. +2
    -1
      react-ui/src/pages/Experiment/training/props.tsx
  35. +0
    -0
      react-ui/src/pages/Model/components/ModelEvolution/index.less
  36. +0
    -0
      react-ui/src/pages/Model/components/ModelEvolution/index.tsx
  37. +1
    -1
      react-ui/src/pages/Model/index.tsx
  38. +0
    -262
      react-ui/src/pages/Model/intro.jsx
  39. +0
    -80
      react-ui/src/pages/Model/intro.less
  40. +8
    -0
      react-ui/src/pages/Model/intro.tsx
  41. +17
    -28
      react-ui/src/pages/ModelDeployment/Info/index.less
  42. +145
    -121
      react-ui/src/pages/ModelDeployment/Info/index.tsx
  43. +0
    -0
      react-ui/src/pages/Pipeline/components/ModelMenu/index.less
  44. +90
    -0
      react-ui/src/pages/Pipeline/components/ModelMenu/index.tsx
  45. +4
    -3
      react-ui/src/pages/Pipeline/components/ResourceSelectorModal/config.tsx
  46. +32
    -36
      react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.tsx
  47. +12
    -4
      react-ui/src/pages/Pipeline/editPipeline/index.jsx
  48. +0
    -67
      react-ui/src/pages/Pipeline/editPipeline/modelMenus.jsx
  49. +104
    -107
      react-ui/src/pages/Pipeline/editPipeline/props.tsx
  50. +1
    -0
      react-ui/src/pages/Pipeline/editPipeline/utils.tsx
  51. +1
    -2
      react-ui/src/pages/Pipeline/index.jsx
  52. +1
    -1
      react-ui/src/pages/Pipeline/index.less
  53. +8
    -0
      react-ui/src/services/modelDeployment/index.ts
  54. +2
    -2
      react-ui/src/styles/menu.less
  55. +4
    -1
      react-ui/src/styles/theme.less
  56. +10
    -5
      react-ui/src/types.ts
  57. +1
    -1
      react-ui/src/utils/downloadfile.ts

+ 10
- 1
react-ui/src/components/KFModal/index.tsx View File

@@ -12,12 +12,21 @@ import './index.less';
export interface KFModalProps extends ModalProps {
image?: string;
}
function KFModal({ title, image, children, className = '', centered, ...rest }: KFModalProps) {
function KFModal({
title,
image,
children,
className = '',
centered,
maskClosable,
...rest
}: KFModalProps) {
return (
<Modal
className={classNames(['kf-modal', className])}
{...rest}
centered={centered ?? true}
maskClosable={maskClosable ?? false}
title={<ModalTitle title={title} image={image}></ModalTitle>}
>
{children}


+ 5
- 4
react-ui/src/components/ParameterInput/index.tsx View File

@@ -29,7 +29,6 @@ function ParameterInput({
onClick,
canInput = true,
textArea = false,
placeholder,
allowClear,
className,
style,
@@ -37,8 +36,6 @@ function ParameterInput({
disabled = false,
...rest
}: ParameterInputProps) {
// console.log('ParameterInput', value);

const valueObj =
typeof value === 'string' ? { value: value, fromSelect: false, showValue: value } : value;
if (valueObj && !valueObj.showValue) {
@@ -46,6 +43,7 @@ function ParameterInput({
}
const isSelect = valueObj?.fromSelect;
const InputComponent = textArea ? Input.TextArea : Input;
const placeholder = valueObj?.placeholder;

return (
<>
@@ -68,9 +66,12 @@ function ParameterInput({
e.stopPropagation();
onChange?.({
...valueObj,
fromSelect: false,
value: undefined,
showValue: undefined,
fromSelect: false,
activeTab: undefined,
expandedKeys: undefined,
checkedKeys: undefined,
});
}}
/>


+ 72
- 0
react-ui/src/components/ParameterSelect/config.tsx View File

@@ -0,0 +1,72 @@
import { getDatasetList, getModelList } from '@/services/dataset/index.js';
import { getComputingResourceReq } from '@/services/pipeline';
import { ComputingResource } from '@/types';
import { type SelectProps } from 'antd';

// 过滤资源规格
const filterResourceStandard: SelectProps<string, ComputingResource>['filterOption'] = (
input: string,
option?: ComputingResource,
) => {
return (
option?.computing_resource?.toLocaleLowerCase()?.includes(input.toLocaleLowerCase()) ?? false
);
};

// id 从 number 转换为 string
const convertId = (item: any) => ({ ...item, id: String(item.id) });

export type SelectPropsConfig = {
getOptions: () => Promise<any>;
fieldNames?: SelectProps['fieldNames'];
optionFilterProp?: SelectProps['optionFilterProp'];
filterOption?: SelectProps['filterOption'];
};

export const paramSelectConfig: Record<string, SelectPropsConfig> = {
dataset: {
getOptions: async () => {
const res = await getDatasetList({
page: 0,
size: 1000,
available_range: 0,
});
return res?.data?.content?.map(convertId) ?? [];
},
fieldNames: {
label: 'name',
value: 'id',
},
optionFilterProp: 'name',
},
model: {
getOptions: async () => {
const res = await getModelList({
page: 0,
size: 1000,
available_range: 0,
});
return res?.data?.content?.map(convertId) ?? [];
},
fieldNames: {
label: 'name',
value: 'id',
},
optionFilterProp: 'name',
},
resource: {
getOptions: async () => {
const res = await getComputingResourceReq({
page: 0,
size: 1000,
resource_type: '',
});
return res?.data?.content ?? [];
},
fieldNames: {
label: 'description',
value: 'standard',
},
filterOption: filterResourceStandard as SelectProps['filterOption'],
},
};

+ 58
- 0
react-ui/src/components/ParameterSelect/index.tsx View File

@@ -0,0 +1,58 @@
import { PipelineNodeModelParameter } from '@/types';
import { to } from '@/utils/promise';
import { Select } from 'antd';
import { useEffect, useState } from 'react';
import { paramSelectConfig } from './config';

type ParameterSelectProps = {
value?: PipelineNodeModelParameter;
onChange?: (value: PipelineNodeModelParameter) => void;
disabled?: boolean;
};

function ParameterSelect({ value, onChange, disabled = false }: ParameterSelectProps) {
const [options, setOptions] = useState([]);
const valueNonNullable = value ?? ({} as PipelineNodeModelParameter);
const { item_type } = valueNonNullable;
const propsConfig = paramSelectConfig[item_type];

useEffect(() => {
getSelectOptions();
}, []);

const hangleChange = (e: string) => {
onChange?.({
...valueNonNullable,
value: e,
});
};

// 获取下拉数据
const getSelectOptions = async () => {
if (!propsConfig) {
return;
}
const getOptions = propsConfig.getOptions;
const [res] = await to(getOptions());
if (res) {
setOptions(res);
}
};

return (
<Select
placeholder={valueNonNullable.placeholder}
filterOption={propsConfig?.filterOption}
options={options}
fieldNames={propsConfig?.fieldNames}
value={valueNonNullable.value}
optionFilterProp={propsConfig.optionFilterProp}
onChange={hangleChange}
disabled={disabled}
showSearch
allowClear
/>
);
}

export default ParameterSelect;

+ 2
- 3
react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx View File

@@ -7,7 +7,6 @@ import { getDictSelectOption } from '@/services/system/dict';
import { to } from '@/utils/promise';
import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui';
import {
App,
Button,
Form,
Input,
@@ -15,12 +14,13 @@ import {
Select,
Upload,
UploadFile,
message,
type ModalProps,
type UploadProps,
} from 'antd';
import { omit } from 'lodash';
import { useEffect, useState } from 'react';
import { CategoryData } from '../../types';
import { CategoryData } from '../../config';
import styles from './index.less';

interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> {
@@ -32,7 +32,6 @@ interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> {
function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalProps) {
const [uuid] = useState(Date.now());
const [clusterOptions, setClusterOptions] = useState<DictValueEnumObj[]>([]);
const { message } = App.useApp();

useEffect(() => {
getClusterOptions();


+ 2
- 3
react-ui/src/pages/Dataset/components/AddModelModal/index.tsx View File

@@ -1,18 +1,18 @@
import { getAccessToken } from '@/access';
import KFIcon from '@/components/KFIcon';
import KFModal from '@/components/KFModal';
import { CategoryData } from '@/pages/Dataset/types';
import { CategoryData } from '@/pages/Dataset/config';
import { addModel } from '@/services/dataset/index.js';
import { to } from '@/utils/promise';
import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui';
import {
App,
Button,
Form,
Input,
Select,
Upload,
UploadFile,
message,
type ModalProps,
type UploadProps,
} from 'antd';
@@ -28,7 +28,6 @@ interface AddModelModalProps extends Omit<ModalProps, 'onOk'> {

function AddModelModal({ typeList, tagList, onOk, ...rest }: AddModelModalProps) {
const [uuid] = useState(Date.now());
const { message } = App.useApp();

// 上传组件参数
const uploadProps: UploadProps = {


+ 3
- 4
react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx View File

@@ -1,16 +1,16 @@
import { getAccessToken } from '@/access';
import KFIcon from '@/components/KFIcon';
import KFModal from '@/components/KFModal';
import { ResourceType, resourceConfig } from '@/pages/Dataset/types';
import { ResourceType, resourceConfig } from '@/pages/Dataset/config';
import { to } from '@/utils/promise';
import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui';
import {
App,
Button,
Form,
Input,
Upload,
UploadFile,
message,
type ModalProps,
type UploadProps,
} from 'antd';
@@ -33,7 +33,6 @@ function AddVersionModal({
...rest
}: AddVersionModalProps) {
const [uuid] = useState(Date.now());
const { message } = App.useApp();

// 上传组件参数
const uploadProps: UploadProps = {
@@ -46,7 +45,7 @@ function AddVersionModal({

// 上传请求
const createDatasetVersion = async (params: any) => {
const request = resourceConfig[resourceType].addVersionReq;
const request = resourceConfig[resourceType].addVersion;
const [res] = await to(request(params));
if (res) {
message.success('创建成功');


+ 3
- 3
react-ui/src/pages/Dataset/components/CategoryItem/index.tsx View File

@@ -1,5 +1,5 @@
import classNames from 'classnames';
import { CategoryData, ResourceType, resourceConfig } from '../../types';
import { CategoryData, ResourceType, resourceConfig } from '../../config';
import styles from './index.less';

type CategoryItemProps = {
@@ -20,13 +20,13 @@ function CategoryItem({ resourceType, item, isSelected, onClick }: CategoryItemP
<img
className={styles['category-item__icon']}
style={{ width: '22px' }}
src={`/assets/images/${resourceConfig[resourceType].iconPathPrefix}/${item.path}.png`}
src={`/assets/images/${resourceConfig[resourceType].prefix}/${item.path}.png`}
alt=""
/>
<img
className={styles['category-item__active-icon']}
style={{ width: '22px' }}
src={`/assets/images/${resourceConfig[resourceType].iconPathPrefix}/${item.path}-hover.png`}
src={`/assets/images/${resourceConfig[resourceType].prefix}/${item.path}-hover.png`}
alt=""
/>
<span className={styles['category-item__name']}>{item.name}</span>


+ 1
- 1
react-ui/src/pages/Dataset/components/CategoryList/index.tsx View File

@@ -1,5 +1,5 @@
import { Flex, Input } from 'antd';
import { CategoryData, ResourceType, resourceConfig } from '../../types';
import { CategoryData, ResourceType, resourceConfig } from '../../config';
import CategoryItem from '../CategoryItem';
import styles from './index.less';



+ 52
- 0
react-ui/src/pages/Dataset/components/ResourceIntro/index.less View File

@@ -0,0 +1,52 @@
.resource-intro {
height: 100%;

&__top {
display: flex;
flex-direction: column;
justify-content: space-between;
width: 100%;
height: 110px;
margin-bottom: 10px;
padding: 25px 30px;
background-image: url(/assets/images/dataset-back.png);
background-repeat: no-repeat;
background-position: top center;
background-size: 100% 100%;

&__name {
margin-bottom: 12px;
color: @text-color;
font-size: 20;
}

&__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);
}

&__title {
margin: 30px 0 10px;
color: @text-color;
font-weight: 500;
font-size: @font-size;
}

&__intro {
color: @text-color-secondary;
font-size: 14px;
}
}

+ 86
- 0
react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx View File

@@ -0,0 +1,86 @@
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 styles from './index.less';

type ResourceIntroProps = {
resourceType: ResourceType;
};

const ResourceIntro = ({ resourceType }: ResourceIntroProps) => {
const [info, setInfo] = useState<ResourceData>({} as ResourceData);
const locationParams = useParams(); //新版本获取路由参数接口
const [searchParams] = useSearchParams();
const isPublic = searchParams.get('isPublic') === 'true';
const resourceId = Number(locationParams.id);
const name = resourceConfig[resourceType].name;

useEffect(() => {
getModelByDetail();
}, []);

// 获取详情
const getModelByDetail = async () => {
const request = resourceConfig[resourceType].getInfo;
const [res] = await to(request(resourceId));
if (res) {
setInfo(res.data);
}
};

const items = [
{
key: '1',
label: `${name}简介`,
children: (
<>
<div className={styles['resource-intro__title']}>简介</div>
<div className={styles['resource-intro__intro']}>{info.description}</div>
</>
),
},
{
key: '2',
label: `${name}文件/版本`,
children: (
<ResourceVersion
resourceType={resourceType}
resourceId={resourceId}
resourceName={info.name}
isPublic={isPublic}
></ResourceVersion>
),
},
];

const infoTypePropertyName = resourceConfig[resourceType]
.infoTypePropertyName as keyof ResourceData;
const infoTagPropertyName = resourceConfig[resourceType]
.infoTagPropertyName as keyof ResourceData;

return (
<div className={styles['resource-intro']}>
<div className={styles['resource-intro__top']}>
<span className={styles['resource-intro__top__name']}>{info.name}</span>
<Flex align="center">
<div className={styles['resource-intro__top__tag']}>
{name} id:{info.id}
</div>
<div className={styles['resource-intro__top__tag']}>
{info[infoTypePropertyName] || '--'}
</div>
<div className={styles['resource-intro__top__tag']}>
{info[infoTagPropertyName] || '--'}
</div>
</Flex>
</div>
<div className={styles['resource-intro__bottom']}>
<Tabs defaultActiveKey="1" items={items}></Tabs>
</div>
</div>
);
};
export default ResourceIntro;

+ 3
- 6
react-ui/src/pages/Dataset/components/ResourceList/index.tsx View File

@@ -7,7 +7,7 @@ import { modalConfirm } from '@/utils/ui';
import { useNavigate } from '@umijs/max';
import { App, Button, Input, Pagination, PaginationProps } from 'antd';
import { Ref, forwardRef, useEffect, useImperativeHandle, useState } from 'react';
import { CategoryData, ResourceData, ResourceType, resourceConfig } from '../../types';
import { CategoryData, ResourceData, ResourceType, resourceConfig } from '../../config';
import AddDatasetModal from '../AddDatasetModal';
import ResourceItem from '../Resourcetem';
import styles from './index.less';
@@ -129,11 +129,8 @@ function ResourceList(
activeType: dataType,
activeTag: dataTag,
});
if (resourceType === ResourceType.Dataset) {
navigate(`/dataset/dataset/${record.id}?isPublic=${isPublic}`);
} else {
navigate(`/dataset/model/${record.id}?isPublic=${isPublic}`);
}
const prefix = resourceConfig[resourceType].prefix;
navigate(`/dataset/${prefix}/${record.id}?isPublic=${isPublic}`);
};

// 分页切换


+ 1
- 1
react-ui/src/pages/Dataset/components/ResourcePage/index.tsx View File

@@ -4,7 +4,7 @@ import { getAssetIcon } from '@/services/dataset/index.js';
import { to } from '@/utils/promise';
import { Flex, Tabs, type TabsProps } from 'antd';
import { useEffect, useRef, useState } from 'react';
import { CategoryData, ResourceType, resourceConfig } from '../../types';
import { CategoryData, ResourceType, resourceConfig } from '../../config';
import CategoryList from '../CategoryList';
import ResourceList, { ResourceListRef } from '../ResourceList';
import styles from './index.less';


+ 4
- 0
react-ui/src/pages/Dataset/components/ResourceVersion/index.less View File

@@ -0,0 +1,4 @@
.resource-version {
color: @text-color;
font-size: @font-size-content;
}

+ 236
- 0
react-ui/src/pages/Dataset/components/ResourceVersion/index.tsx View File

@@ -0,0 +1,236 @@
import CommonTableCell from '@/components/CommonTableCell';
import DateTableCell from '@/components/DateTableCell';
import KFIcon from '@/components/KFIcon';
import AddVersionModal from '@/pages/Dataset/components/AddVersionModal';
import { ResourceType } 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 { useEffect, useState } from 'react';
import { ResourceFileData, resourceConfig } from '../../config';
import styles from './index.less';

type ResourceVersionProps = {
resourceType: ResourceType;
resourceId: number;
resourceName: string;
isPublic: boolean;
};
function ResourceVersion({
resourceType,
resourceId,
resourceName,
isPublic,
}: ResourceVersionProps) {
const [versionList, setVersionList] = useState([]);
const [version, setVersion] = useState<string | undefined>(undefined);
const [fileList, setFileList] = useState<ResourceFileData[]>([]);
const { message } = App.useApp();

useEffect(() => {
getVersionList();
}, []);

// 获取版本列表
const getVersionList = async () => {
const request = resourceConfig[resourceType].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,
};
}),
);
setVersion(res.data[0]);
getFileList(res.data[0]);
} else {
setVersion(undefined);
setFileList([]);
}
};

// 获取版本下的文件列表
const getFileList = async (version: string) => {
const params = {
version,
[resourceConfig[resourceType].fileReqParamKey]: resourceId,
};
const request = resourceConfig[resourceType].getFiles;
const [res] = await to(request(params));
if (res) {
setFileList(res?.data?.content ?? []);
}
};

// 删除版本
const deleteVersion = async () => {
const request = resourceConfig[resourceType].deleteVersion;
const params = {
[resourceConfig[resourceType].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 handleExport = async () => {
const url = resourceConfig[resourceType].downloadAllAction;
downLoadZip(url, { models_id: resourceId, version });
};

// 单个导出
const downloadAlone = (record: ResourceFileData) => {
const url = resourceConfig[resourceType].downloadSingleAction;
downLoadZip(`${url}/${record.id}`);
};

// 版本变化
const handleChange = (value: string) => {
if (value) {
getFileList(value);
setVersion(value);
} else {
setVersion(undefined);
}
};

const columns = [
{
title: '序号',
dataIndex: 'index',
key: 'index',
width: 80,
render(text: string, record: ResourceFileData, index: number) {
return <span>{index + 1}</span>;
},
},
{
title: '文件名称',
dataIndex: 'file_name',
key: 'file_name',
render: (text: string, record: ResourceFileData) => (
<a onClick={() => downloadAlone(record)}>{text}</a>
),
},
{
title: '版本号',
dataIndex: 'version',
key: 'version',
render: CommonTableCell(),
},
{
title: '文件大小',
dataIndex: 'file_size',
key: 'file_size',
render: CommonTableCell(),
},
{
title: '更新时间',
dataIndex: 'update_time',
key: 'update_time',
render: DateTableCell,
},
{
title: '操作',
dataIndex: 'option',
width: '100px',
key: 'option',
render: (_: any, record: ResourceFileData) => [
<Button
type="link"
size="small"
key="download"
icon={<KFIcon type="icon-xiazai" />}
onClick={() => downloadAlone(record)}
>
下载
</Button>,
],
},
];

return (
<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}
allowClear
onChange={handleChange}
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}
onClick={handleExport}
icon={<KFIcon type="icon-xiazai" />}
>
下载
</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>
);
}

export default ResourceVersion;

+ 1
- 1
react-ui/src/pages/Dataset/components/Resourcetem/index.tsx View File

@@ -3,7 +3,7 @@ import creatByImg from '@/assets/img/creatBy.png';
import KFIcon from '@/components/KFIcon';
import { formatDate } from '@/utils/date';
import { Button, Flex, Typography } from 'antd';
import { ResourceData } from '../../types';
import { ResourceData } from '../../config';
import styles from './index.less';

type ResourceItemProps = {


react-ui/src/pages/Dataset/types.tsx → react-ui/src/pages/Dataset/config.tsx View File

@@ -4,10 +4,14 @@ import {
addDatasetVersionDetail,
addModelsVersionDetail,
deleteDataset,
deleteDatasetVersion,
deleteModel,
deleteModelVersion,
getDatasetById,
getDatasetList,
getDatasetVersionIdList,
getDatasetVersionsById,
getModelById,
getModelList,
getModelVersionIdList,
getModelVersionsById,
@@ -24,6 +28,9 @@ type ResourceTypeInfo = {
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>;
getInfo: (params: any) => Promise<any>;
name: string;
typeParamKey: string;
tagParamKey: string;
@@ -33,13 +40,16 @@ type ResourceTypeInfo = {
tagTitle: string;
typeValue: number; // 从 getAssetIcon 接口获取特定值的数据为 type 分类 (category_id === typeValue)
tagValue: number; // 从 getAssetIcon 接口获取特定值的数据为 tag 分类(category_id === tagValue)
iconPathPrefix: string; // 图标路径前缀
prefix: string; // 前缀
deleteModalTitle: string; // 删除弹框的title
addBtnTitle: string; // 新增按钮的title
addVersionReq: (params: any) => Promise<any>;
idParamKey: string;
idParamKey: 'models_id' | 'dataset_id';
uploadAction: string;
uploadAccept?: string;
downloadAllAction: string;
downloadSingleAction: string;
infoTypePropertyName: string;
infoTagPropertyName: string;
};

export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = {
@@ -48,6 +58,9 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = {
getVersions: getDatasetVersionsById,
getFiles: getDatasetVersionIdList,
deleteRecord: deleteDataset,
addVersion: addDatasetVersionDetail,
deleteVersion: deleteDatasetVersion,
getInfo: getDatasetById,
name: '数据集',
typeParamKey: 'data_type',
tagParamKey: 'data_tag',
@@ -68,19 +81,25 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = {
tagTitle: '研究方向/应用领域',
typeValue: 1,
tagValue: 2,
iconPathPrefix: 'dataset',
prefix: 'dataset',
deleteModalTitle: '确定删除该条数据集实例吗?',
addBtnTitle: '新建数据集',
addVersionReq: addDatasetVersionDetail,
idParamKey: 'dataset_id',
uploadAction: '/api/mmp/dataset/upload',
uploadAccept: '.zip,.tgz',
downloadAllAction: '/api/mmp/dataset/downloadAllFilesl',
downloadSingleAction: '/api/mmp/dataset/download',
infoTypePropertyName: 'dataset_type_name',
infoTagPropertyName: 'dataset_tag_name',
},
[ResourceType.Model]: {
getList: getModelList,
getVersions: getModelVersionsById,
getFiles: getModelVersionIdList,
deleteRecord: deleteModel,
addVersion: addModelsVersionDetail,
deleteVersion: deleteModelVersion,
getInfo: getModelById,
name: '模型',
typeParamKey: 'model_type',
tagParamKey: 'model_tag',
@@ -101,13 +120,16 @@ export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = {
tagTitle: '模型能力',
typeValue: 3,
tagValue: 4,
iconPathPrefix: 'model',
prefix: 'model',
deleteModalTitle: '确定删除该条模型实例吗?',
addBtnTitle: '新建模型',
addVersionReq: addModelsVersionDetail,
idParamKey: 'models_id',
uploadAction: '/api/mmp/models/upload',
uploadAccept: undefined,
downloadAllAction: '/api/mmp/models/downloadAllFiles',
downloadSingleAction: '/api/mmp/models/download_model',
infoTypePropertyName: 'model_type_name',
infoTagPropertyName: 'model_tag_name',
},
};

@@ -119,11 +141,29 @@ export type CategoryData = {
path: string;
};

// 数据类型
// 资源数据
export type ResourceData = {
id: number;
name: string;
description: string;
create_by: string;
update_time: string;
model_type_name?: string;
model_tag_name?: string;
dataset_type_name?: string;
dataset_tag_name?: 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;
};

react-ui/src/pages/Dataset/index.jsx → react-ui/src/pages/Dataset/index.tsx View File

@@ -1,5 +1,5 @@
import ResourcePage from './components/ResourcePage';
import { ResourceType } from './types';
import { ResourceType } from './config';

const DatasetPage = () => {
return <ResourcePage resourceType={ResourceType.Dataset} />;

+ 0
- 263
react-ui/src/pages/Dataset/intro.jsx View File

@@ -1,263 +0,0 @@
import KFIcon from '@/components/KFIcon';
import { ResourceType } from '@/pages/Dataset/types';
import {
deleteDatasetVersion,
getDatasetById,
getDatasetVersionIdList,
getDatasetVersionsById,
} from '@/services/dataset/index.js';
import { formatDate } from '@/utils/date';
import { downLoadZip } from '@/utils/downloadfile';
import { openAntdModal } from '@/utils/modal';
import { modalConfirm } from '@/utils/ui';
import { useParams, useSearchParams } from '@umijs/max';
import { App, Button, Input, Select, Table, Tabs } from 'antd';
import { useEffect, useRef, useState } from 'react';
import AddVersionModal from './components/AddVersionModal';
import Styles from './intro.less';
const { Search } = Input;
const { TabPane } = Tabs;

const Dataset = () => {
const { message } = App.useApp();
const [formList, setFormList] = useState([]);
const [datasetDetailObj, setDatasetDetailObj] = useState({});
const [version, setVersion] = useState(null);
const [versionList, setVersionList] = useState([]);
const locationParams = useParams(); //新版本获取路由参数接口
const [searchParams] = useSearchParams();
const [wordList, setWordList] = useState([]);
const [activeTabKey, setActiveTabKey] = useState('1');
const isPublic = searchParams.get('isPublic') === 'true';

const getDatasetByDetail = () => {
getDatasetById(locationParams.id).then((ret) => {
console.log(ret);
setDatasetDetailObj(ret.data);
});
};
// 获取数据集版本
const getDatasetVersionList = () => {
getDatasetVersionsById(locationParams.id).then((ret) => {
console.log(ret);
if (ret.data && ret.data.length > 0) {
setVersionList(
ret.data.map((item) => {
return {
label: item,
value: item,
};
}),
);
setVersion(ret.data[0]);
getDatasetVersions({ version: ret.data[0], dataset_id: locationParams.id });
} else {
setVersion(null);
setWordList([]);
}
});
};
useEffect(() => {
getDatasetByDetail();
getDatasetVersionList();
return () => {};
}, []);
const showModal = () => {
const { close } = openAntdModal(AddVersionModal, {
resourceType: ResourceType.Dataset,
resourceId: locationParams.id,
initialName: datasetDetailObj.name,
onOk: () => {
getDatasetVersionList();
close();
},
});
};

const deleteDataset = () => {
modalConfirm({
title: '删除后,该数据集版本将不可恢复',
content: '是否确认删除?',
onOk: () => {
deleteDatasetVersion({ dataset_id: locationParams.id, version }).then((ret) => {
getDatasetVersionList();
message.success('删除成功');
});
},
});
};
// 获取版本下的文件列表
const getDatasetVersions = (params) => {
getDatasetVersionIdList(params).then((res) => {
setWordList(res?.data?.content ?? []);
});
};

const handleExport = async () => {
const hide = message.loading('正在下载');
hide();
downLoadZip(`/api/mmp/dataset/downloadAllFiles`, { dataset_id: locationParams.id, version });
};

const downloadAlone = (e, record) => {
console.log(record);
const hide = message.loading('正在下载');
hide();
downLoadZip(`/api/mmp/dataset/download/${record.id}`);
};

const handleChange = (value) => {
console.log(value);
if (value) {
getDatasetVersions({ version: value, dataset_id: locationParams.id });
setVersion(value);
} else {
setVersion(null);
}
};

const columns = [
{
title: '序号',
dataIndex: 'index',
key: 'index',
width: 80,
render(text, record, index) {
return <span>{(pageOption.current.page - 1) * 10 + index + 1}</span>;
},
// render: (text, record, index) => `${((curPage-1)*10)+(index+1)}`,
},
{
title: '文件名称',
dataIndex: 'file_name',
key: 'file_name',
render: (text, record) => <a onClick={(e) => downloadAlone(e, record)}>{text}</a>,
},
{
title: '版本号',
dataIndex: 'version',
key: 'version',
},
{
title: '文件大小',
dataIndex: 'file_size',
key: 'file_size',
},
{
title: '更新时间',
dataIndex: 'update_time',
key: 'update_time',
render: (text) => <span>{formatDate(text)}</span>,
},
{
title: '操作',
dataIndex: 'option',
width: '100px',
key: 'option',
render: (_, record) => [
<Button
type="link"
size="small"
key="download"
icon={<KFIcon type="icon-xiazai" />}
onClick={(e) => downloadAlone(e, record)}
>
下载
</Button>,
],
},
];
const pageOption = useRef({ page: 1, size: 10 });

// 当前页面切换
const paginationChange = async (current, size) => {
console.log('page', current, size);
pageOption.current = {
page: current,
size: size,
};
// getList()
};
return (
<div className={Styles.datasetBox}>
<div className={Styles.datasetIntroTopBox}>
<span style={{ color: '#1d1d20', fontSize: '20px' }}>{datasetDetailObj.name}</span>
<div className={Styles.smallTagBox}>
<div className={Styles.tagItem}>数据集 id:{datasetDetailObj.id}</div>
<div className={Styles.tagItem}>{datasetDetailObj.dataset_type_name || '...'}</div>
<div className={Styles.tagItem}>{datasetDetailObj.dataset_tag_name || '...'}</div>
</div>
</div>
<div className={Styles.datasetIntroCneterBox}>
<Tabs activeKey={activeTabKey} onChange={(key) => setActiveTabKey(key)}>
<TabPane tab="数据集简介" key="1">
<div className={Styles.datasetIntroTitle}>简介</div>
<div className={Styles.datasetIntroText}>{datasetDetailObj.description}</div>
</TabPane>
<TabPane tab="数据集文件/版本" key="2">
<div className={Styles.dataListBox}>
<div>数据集文件列表</div>
<div className={Styles.dataButtonList}>
<div
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}
>
<span style={{ marginRight: '10px' }}>版本号:</span>
<Select
placeholder="请选择版本号"
style={{
width: 160,
}}
allowClear
value={version}
onChange={handleChange}
options={versionList}
/>
<Button
type="default"
className={Styles.plusButton}
onClick={showModal}
icon={<KFIcon type="icon-xinjian2" />}
>
创建新版本
</Button>
</div>
<div
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}
>
{!isPublic && (
<Button
type="default"
className={Styles.plusButton}
style={{ margin: '0 20px 0 0' }}
onClick={deleteDataset}
icon={<KFIcon type="icon-shanchu" />}
>
删除
</Button>
)}
<Button
type="default"
disabled={!version}
className={Styles.plusButton}
style={{ margin: '0 20px 0 0' }}
onClick={handleExport}
icon={<KFIcon type="icon-xiazai" />}
>
下载
</Button>
</div>
</div>
<div style={{ marginBottom: '10px', fontSize: '14px' }}>
{wordList.length > 0 && wordList[0].description
? '版本描述:' + wordList[0].description
: null}
</div>
<Table columns={columns} dataSource={wordList} pagination={false} rowKey="id" />
</div>
</TabPane>
</Tabs>
</div>
</div>
);
};
export default Dataset;

+ 0
- 82
react-ui/src/pages/Dataset/intro.less View File

@@ -1,82 +0,0 @@
.datasetIntroTopBox {
display: flex;
flex-direction: column;
justify-content: space-between;
width: 100%;
height: 110px;
margin-bottom: 10px;
padding: 25px 30px;
background-image: url(/assets/images/dataset-back.png);
background-repeat: no-repeat;
background-position: top center;
background-size: 100% 100%;

.smallTagBox {
display: flex;
align-items: center;
color: #1664ff;
font-size: 14px;
.tagItem {
margin-right: 20px;
padding: 4px 10px;
background: rgba(22, 100, 255, 0.1);
border-radius: 4px;
}
}
}
.dataListBox {
padding: 20px 30px;
color: #1d1d20;
font-size: 16px;
font-family: alibaba;
background: #ffffff;
border-radius: 10px;
box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09);
.dataButtonList {
display: flex;
align-items: center;
justify-content: space-between;
height: 32px;
margin: 24px 0 30px 0;
color: #575757;
font-size: 16px;
}
}
.datasetIntroCneterBox {
height: 77vh;
padding: 20px 30px;
background: #ffffff;
border-radius: 10px;
box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09);
}
.datasetIntroTitle {
margin: 37px 0 10px 0;
color: #1d1d20;
font-size: 15px;
}
.datasetIntroText {
margin-bottom: 30px;
color: #575757;
font-size: 14px;
}
.datasetBox {
background: #f9fafb;

:global {
.ant-tabs-top > .ant-tabs-nav {
margin: 0;
}
.ant-pagination {
text-align: right;
}
}
}

.plusButton {
margin: 0 18px 0 20px;
}

.tipContent {
margin-top: 5px;
color: #c73131;
}

+ 8
- 0
react-ui/src/pages/Dataset/intro.tsx View File

@@ -0,0 +1,8 @@
import ResourceIntro from '@/pages/Dataset/components/ResourceIntro';
import { ResourceType } from '@/pages/Dataset/config';

function DatasetIntro() {
return <ResourceIntro resourceType={ResourceType.Dataset} />;
}

export default DatasetIntro;

+ 10
- 17
react-ui/src/pages/Experiment/components/ExperimentParameter/index.tsx View File

@@ -1,3 +1,5 @@
import ParameterInput from '@/components/ParameterInput';
import ParameterSelect from '@/components/ParameterSelect';
import SubAreaTitle from '@/components/SubAreaTitle';
import { useComputingResource } from '@/hooks/resource';
import { PipelineNodeModelSerialize } from '@/types';
@@ -122,15 +124,8 @@ function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) {
<TextArea disabled />
</Form.Item>
{controlStrategyList.map((item) => (
<Form.Item
key={item.key}
name={['control_strategy', item.key]}
label={item.value.label}
getValueProps={(e) => {
return { value: e.showValue || e.value };
}}
>
<Input disabled />
<Form.Item key={item.key} name={['control_strategy', item.key]} label={item.value.label}>
<ParameterInput disabled />
</Form.Item>
))}
<div className={styles['experiment-parameter__title']}>
@@ -142,11 +137,12 @@ function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) {
name={['in_parameters', item.key]}
label={item.value.label + '(' + item.key + ')'}
rules={[{ required: item.value.require ? true : false }]}
getValueProps={(e) => {
return { value: e.showValue || e.value };
}}
>
<Input disabled />
{item.value.type === 'select' ? (
<ParameterSelect disabled />
) : (
<ParameterInput disabled />
)}
</Form.Item>
))}
<div className={styles['experiment-parameter__title']}>
@@ -158,11 +154,8 @@ function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) {
name={['out_parameters', item.key]}
label={item.value.label + '(' + item.key + ')'}
rules={[{ required: item.value.require ? true : false }]}
getValueProps={(e) => {
return { value: e.showValue || e.value };
}}
>
<Input disabled />
<ParameterInput disabled />
</Form.Item>
))}
</Form>


+ 5
- 0
react-ui/src/pages/Experiment/components/ExperimentResult/index.less View File

@@ -35,4 +35,9 @@
}
}
}

&__empty {
margin-top: 10px;
text-align: center;
}
}

+ 30
- 26
react-ui/src/pages/Experiment/components/ExperimentResult/index.tsx View File

@@ -23,34 +23,38 @@ function ExperimentResult({ results }: ExperimentResultProps) {
return (
<div className={styles['experiment-result']}>
<div className={styles['experiment-result__content']}>
{results?.map((item) => (
<div key={item.name} className={styles['experiment-result__item']}>
<div className={styles['experiment-result__item__name']}>
<span>{item.name}</span>
<Button
size="small"
type="link"
onClick={() => {
exportResult(item.path);
}}
>
下载
</Button>
{/* <a style={{ marginRight: '10px' }}>导出到模型库</a>
{results && results.length > 0 ? (
results.map((item) => (
<div key={item.name} className={styles['experiment-result__item']}>
<div className={styles['experiment-result__item__name']}>
<span>{item.name}</span>
<Button
size="small"
type="link"
onClick={() => {
exportResult(item.path);
}}
>
下载
</Button>
{/* <a style={{ marginRight: '10px' }}>导出到模型库</a>
<a style={{ marginRight: '10px' }}>导出到数据集</a> */}
</div>
<div style={{ margin: '15px 0' }} className={styles['experiment-result__item__file']}>
<span>文件名称</span>
<span>文件大小</span>
</div>
{item.value?.map((ele) => (
<div className={styles['experiment-result__item__file']} key={ele.name}>
<span>{ele.name}</span>
<span>{ele.size}</span>
</div>
))}
</div>
))}
<div style={{ margin: '15px 0' }} className={styles['experiment-result__item__file']}>
<span>文件名称</span>
<span>文件大小</span>
</div>
{item.value?.map((ele) => (
<div className={styles['experiment-result__item__file']} key={ele.name}>
<span>{ele.name}</span>
<span>{ele.size}</span>
</div>
))}
</div>
))
) : (
<div className={styles['experiment-result__empty']}>暂无结果</div>
)}
</div>
</div>
);


+ 5
- 0
react-ui/src/pages/Experiment/components/LogGroup/index.less View File

@@ -21,8 +21,13 @@
color: white;
font-size: 14px;
white-space: pre-line;
text-align: left;
word-break: break-all;
background: #19253b;

&--empty {
text-align: center;
}
}

&__more-button {


+ 53
- 2
react-ui/src/pages/Experiment/components/LogGroup/index.tsx View File

@@ -10,6 +10,7 @@ import { ExperimentLog } from '@/pages/Experiment/training/props';
import { getExperimentPodsLog } from '@/services/experiment/index.js';
import { DoubleRightOutlined, DownOutlined, UpOutlined } from '@ant-design/icons';
import { Button } from 'antd';
import classNames from 'classnames';
import { useEffect, useState } from 'react';
import styles from './index.less';

@@ -22,6 +23,21 @@ type Log = {
log_content: string; // 日志内容
};

// 滚动到底部
const scrollToBottom = (smooth: boolean = true) => {
const element = document.getElementsByClassName('ant-tabs-content-holder')?.[0];
if (element) {
if (smooth) {
element.scrollTo({
top: element.scrollHeight,
behavior: 'smooth',
});
} else {
element.scrollTo({ top: element.scrollHeight });
}
}
};

function LogGroup({
log_type = 'normal',
pod_name = '',
@@ -32,8 +48,11 @@ function LogGroup({
const [collapse, setCollapse] = useState(true);
const [logList, setLogList, logListRef] = useStateRef<Log[]>([]);
const [completed, setCompleted] = useState(false);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [isMouseDown, setIsMouseDown, isMouseDownRef] = useStateRef(false);

useEffect(() => {
scrollToBottom(false);
if (status === ExperimentStatus.Running) {
const timerId = setInterval(() => {
requestExperimentPodsLog();
@@ -44,6 +63,24 @@ function LogGroup({
}
}, []);

useEffect(() => {
const mouseDown = () => {
setIsMouseDown(true);
};

const mouseUp = () => {
setIsMouseDown(false);
};

document.addEventListener('mousedown', mouseDown);
document.addEventListener('mouseup', mouseUp);

return () => {
document.removeEventListener('mousedown', mouseDown);
document.removeEventListener('mouseup', mouseUp);
};
}, []);

// 请求日志
const requestExperimentPodsLog = async () => {
const list = logListRef.current;
@@ -54,8 +91,14 @@ function LogGroup({
};
const res = await getExperimentPodsLog(params);
const { log_detail } = res.data;
if (log_detail && log_detail.log_content) {
if (log_detail) {
setLogList((oldList) => oldList.concat(log_detail));

if (!isMouseDownRef.current && log_detail.log_content) {
setTimeout(() => {
scrollToBottom();
}, 100);
}
} else {
setCompleted(true);
}
@@ -96,7 +139,15 @@ function LogGroup({
{collapse ? <DownOutlined /> : <UpOutlined />}
</div>
)}
{showLog && <div className={styles['log-group__detail']}>{logText}</div>}
{showLog && (
<div
className={classNames(styles['log-group__detail'], {
[styles['log-group__detail--empty']]: !logText,
})}
>
{logText ? logText : '暂无日志'}
</div>
)}
<div className={styles['log-group__more-button']}>
{showMoreBtn && (
<Button


+ 10
- 0
react-ui/src/pages/Experiment/components/LogList/index.less View File

@@ -1,3 +1,13 @@
.log-list {
padding: 8px;

&__empty {
padding: 15px;
color: white;
font-size: 14px;
white-space: pre-line;
text-align: center;
word-break: break-all;
background: #19253b;
}
}

+ 5
- 3
react-ui/src/pages/Experiment/components/LogList/index.tsx View File

@@ -11,9 +11,11 @@ type LogListProps = {
function LogList({ list = [], status }: LogListProps) {
return (
<div className={styles['log-list']}>
{list.map((v) => (
<LogGroup key={v.pod_name} {...v} status={status} />
))}
{list.length > 0 ? (
list.map((v) => <LogGroup key={v.pod_name} {...v} status={status} />)
) : (
<div className={styles['log-list__empty']}>暂无日志</div>
)}
</div>
);
}


+ 3
- 3
react-ui/src/pages/Experiment/components/TensorBoardStatus/index.less View File

@@ -8,15 +8,15 @@
font-size: 15px;

&--running {
color: #6ac21d;
color: @success-color;
}
&--failed {
color: #df6d6d;
color: @error-color;
}
}
&__icon {
width: 14px;
color: #6ac21d;
color: @success-color;
cursor: pointer;

& + & {


+ 4
- 4
react-ui/src/pages/Experiment/components/ViewParamsModal/index.less View File

@@ -11,8 +11,8 @@
margin-bottom: 15px;

&_label {
width: 120px;
color: #1d1d20;
width: 180px;
color: @text-color;
font-size: 15px;
}
&_value {
@@ -20,8 +20,8 @@
width: 100px;
margin-left: 15px;
padding: 10px 20px;
color: #1d1d20;
font-size: 15px;
color: @text-color;
font-size: @font-size;
line-height: 20px;
background: #f6f6f6;
border: 1px solid #e0e0e1;


+ 9
- 8
react-ui/src/pages/Experiment/index.jsx View File

@@ -18,7 +18,7 @@ import themes from '@/styles/theme.less';
import { elapsedTime, formatDate } from '@/utils/date';
import { to } from '@/utils/promise';
import { modalConfirm } from '@/utils/ui';
import { App, Button, ConfigProvider, Space, Table } from 'antd';
import { App, Button, ConfigProvider, Space, Table, Tooltip } from 'antd';
import classNames from 'classnames';
import { useEffect, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
@@ -84,7 +84,6 @@ function Experiment() {
// 获取实验实例
const getQueryByExperiment = (val) => {
getQueryByExperimentId(val).then((ret) => {
console.log(val);
setExpandedRowKeys(val);
if (ret && ret.data && ret.data.length > 0) {
try {
@@ -162,7 +161,6 @@ function Experiment() {
};
const expandChange = (e, record) => {
clearExperimentInTimers();
console.log(e, record);
if (record.id === expandedRowKeys) {
setExpandedRowKeys(null);
} else {
@@ -238,7 +236,6 @@ function Experiment() {
};
// 当前页面切换
const paginationChange = async (current, size) => {
console.log('page', current, size);
pageOption.current = {
page: current,
size: size,
@@ -279,14 +276,14 @@ function Experiment() {
dataIndex: 'name',
key: 'name',
render: (text) => <div>{text}</div>,
width: '20%',
width: '16%',
},
{
title: '关联流水线名称',
dataIndex: 'workflow_name',
key: 'workflow_name',
render: (text, record) => <a onClick={(e) => routeToEdit(e, record)}>{text}</a>,
width: '20%',
width: '16%',
},
{
title: '实验描述',
@@ -443,13 +440,17 @@ function Experiment() {
<div style={{ width: '50%' }}>
{elapsedTime(item.create_time, item.finish_time)}
</div>
<div style={{ width: '50%' }}>{formatDate(item.create_time)}</div>
<div style={{ width: '50%' }} className={Styles.startTime}>
<Tooltip title={formatDate(item.create_time)}>
<span>{formatDate(item.create_time)}</span>
</Tooltip>
</div>
</div>
<div className={Styles.statusBox}>
<img
style={{ width: '17px', marginRight: '7px' }}
src={experimentStatusInfo[item.status]?.icon}
/>{' '}
/>
<span
style={{ color: experimentStatusInfo[item.status]?.color }}
className={Styles.statusIcon}


+ 8
- 3
react-ui/src/pages/Experiment/index.less View File

@@ -36,17 +36,21 @@
}

.index {
width: calc((100% + 32px + 33px) / 5);
width: calc((100% + 32px + 33px) / 6.25);
}

.tensorBoard {
width: calc((100% + 32px + 33px) / 5);
width: calc((100% + 32px + 33px) / 6.25);
}

.description {
display: flex;
flex: 1;
align-items: center;

.startTime {
.singleLine();
}
}

.status {
@@ -80,8 +84,9 @@
.statusBox:hover .statusIcon {
visibility: visible;
}

.experimentBox {
height: calc(100% - 20px);
height: 100%;
.experimentTable {
height: calc(100% - 60px);
:global {


+ 10
- 8
react-ui/src/pages/Experiment/status.ts View File

@@ -1,3 +1,5 @@
import themes from '@/styles/theme.less';

export interface StatusInfo {
label: string;
color: string;
@@ -18,42 +20,42 @@ export enum ExperimentStatus {
export const experimentStatusInfo: Record<ExperimentStatus, StatusInfo | undefined> = {
Running: {
label: '运行中',
color: '#1664ff',
color: themes.primaryColor,
icon: '/assets/images/running-icon.png',
},
Succeeded: {
label: '成功',
color: '#63a728',
color: themes.successColor,
icon: '/assets/images/success-icon.png',
},
Pending: {
label: '等待中',
color: '#f981eb',
color: themes.pendingColor,
icon: '/assets/images/pending-icon.png',
},
Failed: {
label: '失败',
color: '#c73131',
color: themes.errorColor,
icon: '/assets/images/fail-icon.png',
},
Error: {
label: '错误',
color: '#c73131',
color: themes.errorColor,
icon: '/assets/images/fail-icon.png',
},
Terminated: {
label: '终止',
color: '#8a8a8a',
color: themes.abortColor,
icon: '/assets/images/omitted-icon.png',
},
Skipped: {
label: '未执行',
color: '#8a8a8a',
color: themes.abortColor,
icon: '/assets/images/omitted-icon.png',
},
Omitted: {
label: '未执行',
color: '#8a8a8a',
color: themes.abortColor,
icon: '/assets/images/omitted-icon.png',
},
};

+ 2
- 1
react-ui/src/pages/Experiment/training/props.tsx View File

@@ -109,7 +109,8 @@ const Props = forwardRef((_, ref) => {
// 获取实验日志和实验结果
setExperimentLogList([]);
setExperimentResults([]);
if (e.item && e.item.getModel()) {
// 如果已经运行到了
if (e.item?.getModel()?.component_id) {
const model = e.item.getModel();
const start_time = dayjs(model.experimentStartTime).valueOf() * 1.0e6;
const params = {


+ 0
- 0
react-ui/src/pages/Model/components/ModelEvolution/index.less View File


+ 0
- 0
react-ui/src/pages/Model/components/ModelEvolution/index.tsx View File


react-ui/src/pages/Model/index.jsx → react-ui/src/pages/Model/index.tsx View File

@@ -1,5 +1,5 @@
import ResourcePage from '@/pages/Dataset/components/ResourcePage';
import { ResourceType } from '@/pages/Dataset/types';
import { ResourceType } from '@/pages/Dataset/config';

const ModelPage = () => {
return <ResourcePage resourceType={ResourceType.Model} />;

+ 0
- 262
react-ui/src/pages/Model/intro.jsx View File

@@ -1,262 +0,0 @@
import KFIcon from '@/components/KFIcon';
import AddVersionModal from '@/pages/Dataset/components/AddVersionModal';
import { ResourceType } from '@/pages/Dataset/types';
import {
deleteModelVersion,
getModelById,
getModelVersionIdList,
getModelVersionsById,
} from '@/services/dataset/index.js';
import { formatDate } from '@/utils/date';
import { downLoadZip } from '@/utils/downloadfile';
import { openAntdModal } from '@/utils/modal';
import { modalConfirm } from '@/utils/ui';
import { useParams, useSearchParams } from '@umijs/max';
import { App, Button, Input, Select, Table, Tabs } from 'antd';
import { useEffect, useRef, useState } from 'react';
import Styles from './intro.less';
const { Search } = Input;
const { TabPane } = Tabs;

const Dataset = () => {
const [formList, setFormList] = useState([]);
const [datasetDetailObj, setDatasetDetailObj] = useState({});
const [version, setVersion] = useState(null);
const [versionList, setVersionList] = useState([]);
const locationParams = useParams(); //新版本获取路由参数接口
const [searchParams] = useSearchParams();
const [wordList, setWordList] = useState([]);
const { message } = App.useApp();
const isPublic = searchParams.get('isPublic') === 'true';

const getModelByDetail = () => {
getModelById(locationParams.id).then((ret) => {
console.log(ret);
setDatasetDetailObj(ret.data);
});
};
const getModelVersionsList = () => {
getModelVersionsById(locationParams.id).then((ret) => {
console.log(ret);
if (ret && ret.data && ret.data.length > 0) {
setVersionList(
ret.data.map((item) => {
return {
label: item,
value: item,
};
}),
);
setVersion(ret.data[0]);
getModelVersions({ version: ret.data[0], models_id: locationParams.id });
} else {
setVersion(null);
setWordList([]);
}
});
};
useEffect(() => {
getModelByDetail();
getModelVersionsList();
return () => {};
}, []);
const showModal = () => {
const { close } = openAntdModal(AddVersionModal, {
resourceType: ResourceType.Model,
resourceId: locationParams.id,
initialName: datasetDetailObj.name,
onOk: () => {
getModelVersionsList();
close();
},
});
};

const deleteDataset = () => {
modalConfirm({
title: '删除后,该版本将不可恢复',
content: '是否确认删除?',
okText: '确认',
cancelText: '取消',

onOk: () => {
deleteModelVersion({ models_id: locationParams.id, version }).then((ret) => {
getModelVersionsList();
message.success('删除成功');
});
},
});
};

const getModelVersions = (params) => {
getModelVersionIdList(params).then((ret) => {
setWordList(ret?.data?.content ?? []);
});
};
const handleExport = async () => {
const hide = message.loading('正在下载');
hide();
downLoadZip(`/api/mmp/models/downloadAllFiles`, { models_id: locationParams.id, version });
};
const downloadAlone = (e, record) => {
console.log(record);
const hide = message.loading('正在下载');
hide();
downLoadZip(`/api/mmp/models/download_model/${record.id}`);
};
const handleChange = (value) => {
console.log(value);
if (value) {
getModelVersions({ version: value, models_id: locationParams.id });
setVersion(value);
} else {
setVersion('');
}
};

const columns = [
{
title: '序号',
dataIndex: 'index',
key: 'index',
width: 80,
render(text, record, index) {
return <span>{(pageOption.current.page - 1) * 10 + index + 1}</span>;
},
// render: (text, record, index) => `${((curPage-1)*10)+(index+1)}`,
},
{
title: '文件名称',
dataIndex: 'file_name',
key: 'file_name',
render: (text, record) => <a onClick={(e) => downloadAlone(e, record)}>{text}</a>,
},
{
title: '版本号',
dataIndex: 'version',
key: 'version',
},
{
title: '文件大小',
dataIndex: 'file_size',
key: 'file_size',
},
{
title: '更新时间',
dataIndex: 'update_time',
key: 'update_time',
render: (text) => <span>{formatDate(text)}</span>,
},
{
title: '操作',
dataIndex: 'option',
width: '100px',
key: 'option',
render: (_, record) => [
<Button
type="link"
size="small"
key="download"
icon={<KFIcon type="icon-xiazai" />}
onClick={(e) => downloadAlone(e, record)}
>
下载
</Button>,
],
},
];
const pageOption = useRef({ page: 1, size: 10 });

// 当前页面切换
const paginationChange = async (current, size) => {
console.log('page', current, size);
pageOption.current = {
page: current,
size: size,
};
// getList()
};
return (
<div className={Styles.datasetBox}>
<div className={Styles.datasetIntroTopBox}>
<span style={{ color: '#1d1d20', fontSize: '20px' }}>{datasetDetailObj.name}</span>
<div className={Styles.smallTagBox}>
<div className={Styles.tagItem}>模型 id:{datasetDetailObj.id}</div>
<div className={Styles.tagItem}>{datasetDetailObj.model_type_name || '...'}</div>
<div className={Styles.tagItem}>{datasetDetailObj.model_tag_name || '...'}</div>
</div>
</div>
<div className={Styles.datasetIntroCneterBox}>
<Tabs defaultActiveKey="1">
<TabPane tab="模型简介" key="1">
<div className={Styles.datasetIntroTitle}>简介</div>
<div className={Styles.datasetIntroText}>{datasetDetailObj.description}</div>
</TabPane>
<TabPane tab="模型文件/版本" key="2">
<div className={Styles.dataListBox}>
<div>模型列表</div>
<div className={Styles.dataButtonList}>
<div
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}
>
<span style={{ marginRight: '10px' }}>版本号:</span>
<Select
placeholder="请选择版本号"
style={{
width: 160,
}}
value={version}
allowClear
onChange={handleChange}
options={versionList}
/>
<Button
type="default"
className={Styles.plusButton}
onClick={showModal}
icon={<KFIcon type="icon-xinjian2" />}
>
创建新版本
</Button>
</div>
<div
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}
>
{!isPublic && (
<Button
type="default"
className={Styles.plusButton}
style={{ margin: '0 20px 0 0' }}
onClick={deleteDataset}
icon={<KFIcon type="icon-shanchu" />}
>
删除
</Button>
)}

<Button
type="default"
className={Styles.plusButton}
disabled={!version}
style={{ margin: '0 20px 0 0' }}
onClick={handleExport}
icon={<KFIcon type="icon-xiazai" />}
>
下载
</Button>
</div>
</div>
<div style={{ marginBottom: '10px', fontSize: '14px' }}>
{wordList.length > 0 && wordList[0].description
? '版本描述:' + wordList[0].description
: null}
</div>
<Table columns={columns} dataSource={wordList} pagination={false} rowKey="id" />
</div>
</TabPane>
</Tabs>
</div>
</div>
);
};
export default Dataset;

+ 0
- 80
react-ui/src/pages/Model/intro.less View File

@@ -1,80 +0,0 @@
.datasetIntroTopBox {
display: flex;
flex-direction: column;
justify-content: space-between;
width: 100%;
height: 110px;
margin-bottom: 10px;
padding: 25px 30px;
background-image: url(/assets/images/dataset-back.png);
background-repeat: no-repeat;
background-position: top center;
background-size: 100% 100%;

.smallTagBox {
display: flex;
align-items: center;
color: #1664ff;
font-size: 14px;
.tagItem {
margin-right: 20px;
padding: 4px 10px;
background: rgba(22, 100, 255, 0.1);
border-radius: 4px;
}
}
}
.dataListBox {
padding: 20px 30px;
color: #1d1d20;
font-size: 16px;
background: #ffffff;
border-radius: 10px;
box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09);
.dataButtonList {
display: flex;
align-items: center;
justify-content: space-between;
height: 32px;
margin: 24px 0 30px 0;
color: #575757;
font-size: 16px;
}
}
.datasetIntroCneterBox {
height: 77vh;
padding: 20px 30px;
background: #ffffff;
border-radius: 10px;
box-shadow: 0px 2px 12px rgba(180, 182, 191, 0.09);
}
.datasetIntroTitle {
margin: 37px 0 10px 0;
color: #1d1d20;
font-size: 15px;
}
.datasetIntroText {
margin-bottom: 30px;
color: #575757;
font-size: 14px;
}
.datasetBox {
font-family: 'Alibaba';
background: #f9fafb;
:global {
.ant-tabs-top > .ant-tabs-nav {
margin: 0;
}
.ant-pagination {
text-align: right;
}
}
}
.plusButton {
margin: 0 18px 0 20px;
}

.tipContent {
margin-top: 5px;
color: #c73131;
}

+ 8
- 0
react-ui/src/pages/Model/intro.tsx View File

@@ -0,0 +1,8 @@
import ResourceIntro from '@/pages/Dataset/components/ResourceIntro';
import { ResourceType } from '@/pages/Dataset/config';

function ModelIntro() {
return <ResourceIntro resourceType={ResourceType.Model} />;
}

export default ModelIntro;

+ 17
- 28
react-ui/src/pages/ModelDeployment/Info/index.less View File

@@ -1,6 +1,16 @@
.model-deployment-info {
height: 100%;

&__content {
display: flex;
flex-direction: column;
height: calc(100% - 60px);
margin-top: 10px;
padding: 30px 30px 0;
background-color: white;
border-radius: 10px;
}

&__basic {
&__item {
display: flex;
@@ -23,34 +33,13 @@
}
}

&__content {
height: calc(100% - 60px);
&__guide {
flex: 1;
margin-top: 10px;
padding: 30px 30px 0;
background-color: white;
border-radius: 10px;

&__title {
display: flex;
align-items: center;
}

&__table {
:global {
.ant-table-wrapper {
height: 100%;
.ant-spin-nested-loading {
height: 100%;
}
.ant-spin-container {
height: 100%;
}
.ant-table {
height: calc(100% - 74px);
overflow: auto;
}
}
}
}
padding: 10px;
overflow-y: auto;
color: white;
white-space: pre-wrap;
background-color: rgba(0, 0, 0, 0.85);
}
}

+ 145
- 121
react-ui/src/pages/ModelDeployment/Info/index.tsx View File

@@ -8,48 +8,70 @@ import PageTitle from '@/components/PageTitle';
import SubAreaTitle from '@/components/SubAreaTitle';
import { useComputingResource } from '@/hooks/resource';
import { useSessionStorage } from '@/hooks/sessionStorage';
import { getModelDeploymentDocsReq } from '@/services/modelDeployment';
import { formatDate } from '@/utils/date';
import { to } from '@/utils/promise';
import { modelDeploymentInfoKey } from '@/utils/sessionStorage';
import { Col, Row, Tabs, type TabsProps } from 'antd';
import { pick } from 'lodash';
import { useEffect, useState } from 'react';
import ModelDeploymentStatusCell from '../components/ModelDeployStatusCell';
import { ModelDeploymentData } from '../types';
import styles from './index.less';

export enum ModelDeploymentTabKey {
Predict = 'Predict',
Guide = 'Guide',
Log = 'Log',
}

const tabItems = [
{
key: '1',
key: ModelDeploymentTabKey.Predict,
label: '预测',
icon: <KFIcon type="icon-yuce" />,
},
{
key: '2',
key: ModelDeploymentTabKey.Guide,
label: '调用指南',
icon: <KFIcon type="icon-tiaoyongzhinan" />,
},
{
key: '3',
key: ModelDeploymentTabKey.Log,
label: '服务日志',
icon: <KFIcon type="icon-fuwurizhi" />,
},
];

function ModelDeploymentInfo() {
const [activeTab, setActiveTab] = useState<string>('1');
const [activeTab, setActiveTab] = useState<string>(ModelDeploymentTabKey.Predict);
const [modelDeployementInfo] = useSessionStorage<ModelDeploymentData | undefined>(
modelDeploymentInfoKey,
true,
undefined,
);
const getResourceDescription = useComputingResource()[2];
const [docs, setDocs] = useState('');

useEffect(() => {}, []);
useEffect(() => {
getModelDeploymentDocs();
}, [modelDeployementInfo]);

// 获取模型部署文档
const getModelDeploymentDocs = async () => {
const params = pick(modelDeployementInfo, ['service_id', 'service_ins_id']);
const [res] = await to(getModelDeploymentDocsReq(params));
if (res && res.data && res.data.docs) {
setDocs(JSON.stringify(res.data.docs, null, 2));
}
};

// 切换 Tab,重置数据
const hanleTabChange: TabsProps['onChange'] = (value) => {
setActiveTab(value);
};

// 格式化环境变量
const formatEnvText = () => {
if (!modelDeployementInfo?.env) {
return '--';
@@ -64,128 +86,130 @@ function ModelDeploymentInfo() {
<div className={styles['model-deployment-info']}>
<PageTitle title="服务详情"></PageTitle>
<div className={styles['model-deployment-info__content']}>
<div>
<SubAreaTitle
title="基本信息"
image={require('@/assets/img/mirror-basic.png')}
style={{ marginBottom: '26px' }}
></SubAreaTitle>
<div className={styles['model-deployment-info__basic']}>
<Row gutter={40} style={{ marginBottom: '20px' }}>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>服务名称:</div>
<div className={styles['value']}>
{modelDeployementInfo?.service_name ?? '--'}
</div>
</div>
</Col>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>镜  像:</div>
<div className={styles['value']}>{modelDeployementInfo?.image ?? '--'}</div>
</div>
</Col>
</Row>
<Row gutter={40} style={{ marginBottom: '20px' }}>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>状  态:</div>
<div className={styles['value']}>
{ModelDeploymentStatusCell(modelDeployementInfo?.status)}
</div>
</div>
</Col>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>模  型:</div>
<div className={styles['value']}>
{modelDeployementInfo?.model?.show_value ?? '--'}
</div>
</div>
</Col>
</Row>
<Row gutter={40} style={{ marginBottom: '20px' }}>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>创建人:</div>
<div className={styles['value']}>{modelDeployementInfo?.created_by ?? '--'}</div>
</div>
</Col>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>挂载路径:</div>
<div className={styles['value']}>{modelDeployementInfo?.model_path ?? '--'}</div>
</div>
</Col>
</Row>
<Row gutter={40} style={{ marginBottom: '20px' }}>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>API URL:</div>
<div className={styles['value']}>{modelDeployementInfo?.url ?? '--'}</div>
</div>
</Col>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>副本数量:</div>
<div className={styles['value']}>{modelDeployementInfo?.replicas ?? '--'}</div>
</div>
</Col>
</Row>
<Row gutter={40} style={{ marginBottom: '20px' }}>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>创建时间:</div>
<div className={styles['value']}>
{modelDeployementInfo?.create_time
? formatDate(modelDeployementInfo.create_time)
: '--'}
</div>
<SubAreaTitle
title="基本信息"
image={require('@/assets/img/mirror-basic.png')}
style={{ marginBottom: '26px' }}
></SubAreaTitle>
<div className={styles['model-deployment-info__basic']}>
<Row gutter={40} style={{ marginBottom: '20px' }}>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>服务名称:</div>
<div className={styles['value']}>{modelDeployementInfo?.service_name ?? '--'}</div>
</div>
</Col>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>镜  像:</div>
<div className={styles['value']}>{modelDeployementInfo?.image ?? '--'}</div>
</div>
</Col>
</Row>
<Row gutter={40} style={{ marginBottom: '20px' }}>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>状  态:</div>
<div className={styles['value']}>
{ModelDeploymentStatusCell(modelDeployementInfo?.status)}
</div>
</Col>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>更新时间:</div>
<div className={styles['value']}>
{modelDeployementInfo?.update_time
? formatDate(modelDeployementInfo.update_time)
: '--'}
</div>
</div>
</Col>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>模  型:</div>
<div className={styles['value']}>
{modelDeployementInfo?.model?.show_value ?? '--'}
</div>
</Col>
</Row>
<Row gutter={40} style={{ marginBottom: '20px' }}>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>环境变量:</div>
<div className={styles['value']}>{formatEnvText()}</div>
</div>
</Col>
</Row>
<Row gutter={40} style={{ marginBottom: '20px' }}>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>创建人:</div>
<div className={styles['value']}>{modelDeployementInfo?.created_by ?? '--'}</div>
</div>
</Col>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>挂载路径:</div>
<div className={styles['value']}>{modelDeployementInfo?.model_path ?? '--'}</div>
</div>
</Col>
</Row>
<Row gutter={40} style={{ marginBottom: '20px' }}>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>API URL:</div>
<div className={styles['value']}>{modelDeployementInfo?.url ?? '--'}</div>
</div>
</Col>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>副本数量:</div>
<div className={styles['value']}>{modelDeployementInfo?.replicas ?? '--'}</div>
</div>
</Col>
</Row>
<Row gutter={40} style={{ marginBottom: '20px' }}>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>创建时间:</div>
<div className={styles['value']}>
{modelDeployementInfo?.create_time
? formatDate(modelDeployementInfo.create_time)
: '--'}
</div>
</Col>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>资源规格:</div>
<div className={styles['value']}>
{modelDeployementInfo?.resource
? getResourceDescription(modelDeployementInfo.resource)
: '--'}
</div>
</div>
</Col>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>更新时间:</div>
<div className={styles['value']}>
{modelDeployementInfo?.update_time
? formatDate(modelDeployementInfo.update_time)
: '--'}
</div>
</Col>
</Row>
<Row gutter={40}>
<Col span={18}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>描  述:</div>
<div className={styles['value']}>{modelDeployementInfo?.description ?? '--'}</div>
</div>
</Col>
</Row>
<Row gutter={40} style={{ marginBottom: '20px' }}>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>环境变量:</div>
<div className={styles['value']}>{formatEnvText()}</div>
</div>
</Col>
<Col span={10}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>资源规格:</div>
<div className={styles['value']}>
{modelDeployementInfo?.resource
? getResourceDescription(modelDeployementInfo.resource)
: '--'}
</div>
</Col>
</Row>
</div>
<div style={{ marginTop: '20px' }}>
<Tabs activeKey={activeTab} items={tabItems} onChange={hanleTabChange} />
</div>
</div>
</Col>
</Row>
<Row gutter={40}>
<Col span={18}>
<div className={styles['model-deployment-info__basic__item']}>
<div className={styles['label']}>描  述:</div>
<div className={styles['value']}>{modelDeployementInfo?.description ?? '--'}</div>
</div>
</Col>
</Row>
</div>
<Tabs
activeKey={activeTab}
style={{ marginTop: '20px' }}
items={tabItems}
onChange={hanleTabChange}
/>
{activeTab === ModelDeploymentTabKey.Guide && (
<div className={styles['model-deployment-info__guide']}>{docs}</div>
)}
</div>
</div>
);


react-ui/src/pages/Pipeline/editPipeline/modelMenus.less → react-ui/src/pages/Pipeline/components/ModelMenu/index.less View File


+ 90
- 0
react-ui/src/pages/Pipeline/components/ModelMenu/index.tsx View File

@@ -0,0 +1,90 @@
import { getComponentAll } from '@/services/pipeline/index.js';
import { PipelineNodeModel } from '@/types';
import { to } from '@/utils/promise';
import { Collapse, type CollapseProps } from 'antd';
import { useEffect, useState } from 'react';
import Styles from './index.less';

type ModelMenuData = {
key: string;
name: string;
value: PipelineNodeModel[];
};

type ModelMenuProps = {
onComponentDragEnd: (
data: PipelineNodeModel & { x: number; y: number; label: string; img: string },
) => void;
};
const ModelMenu = ({ onComponentDragEnd }: ModelMenuProps) => {
const [modelMenusList, setModelMenusList] = useState<ModelMenuData[]>([]);
const [collapseItems, setCollapseItems] = useState<CollapseProps['items']>([]);

useEffect(() => {
getAllComponents();
}, []);

// 获取所有组件
const getAllComponents = async () => {
const [res] = await to(getComponentAll());
if (res && res.data) {
const menus = res.data as ModelMenuData[];
setModelMenusList(menus);
const items = menus.map((item) => {
return {
key: item.key,
label: item.name,
children: item.value.map((ele) => {
return (
<div
key={ele.id}
draggable="true"
onDragEnd={(e) => {
dragEnd(e, ele);
}}
className={Styles.collapseItem}
>
{ele.icon_path && (
<img
style={{ height: '16px', marginRight: '15px' }}
src={`/assets/images/${ele.icon_path}.png`}
alt=""
/>
)}
{ele.component_label}
</div>
);
}),
};
});
setCollapseItems(items);
}
};

const dragEnd = (e: React.DragEvent<HTMLDivElement>, data: PipelineNodeModel) => {
onComponentDragEnd({
...data,
x: e.clientX,
y: e.clientY,
label: data.component_label,
img: `/assets/images/${data.icon_path}.png`,
});
};

const defaultActiveKey = modelMenusList.map((item) => item.key + '');
return (
<div className={Styles.collapse}>
<div className={Styles.modelMenusTitle}>组件库</div>
{modelMenusList.length > 0 ? (
<Collapse
collapsible="header"
expandIconPosition="end"
defaultActiveKey={defaultActiveKey}
items={collapseItems}
></Collapse>
) : null}
</div>
);
};

export default ModelMenu;

+ 4
- 3
react-ui/src/pages/Pipeline/components/ResourceSelectorModal/config.tsx View File

@@ -20,9 +20,9 @@ export enum ResourceSelectorType {
}

export type MirrorVersion = {
id: number; // 镜像版本id
id: number; // 镜像版本 id
status: MirrorVersionStatus; // 镜像版本状态
tag_name: string; // 镜像版本
tag_name: string; // 镜像版本 name
url: string; // 镜像版本路径
};

@@ -39,12 +39,13 @@ export type SelectorTypeInfo = {
tabItems: TabsProps['items'];
};

// 获取镜像列表,为了兼容数据集和模型
// 获取镜像文件列表,为了兼容数据集和模型
const getMirrorFilesReq = ({ id, version }: { id: number; version: string }): Promise<any> => {
const index = version.indexOf('-');
const url = version.slice(index + 1);
return Promise.resolve({
data: {
path: url,
content: [
{
id: `${id}-${version}`,


+ 32
- 36
react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.tsx View File

@@ -15,26 +15,18 @@ import { MirrorVersion, ResourceSelectorType, selectorTypeConfig } from './confi
import styles from './index.less';
export { ResourceSelectorType, selectorTypeConfig };

// 选择数据集和模型的返回类型
// 选择数据集\模型\镜像的返回类型
export type ResourceSelectorResponse = {
id: number; // 数据集或者模型 id
name: string; // 数据集或者模型 name
version: string; // 数据集或者模型版本
path: string; // 数据集或者模型版本路径
id: number; // 数据集\模型\镜像 id
name: string; // 数据集\模型\镜像 name
version: string; // 数据集\模型\镜像版本
path: string; // 数据集\模型\镜像版本路径
activeTab: CommonTabKeys; // 是我的还是公开的
};

export interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> {
type: ResourceSelectorType; // 模型 | 数据集
defaultExpandedKeys?: React.Key[];
defaultCheckedKeys?: React.Key[];
defaultActiveTab?: CommonTabKeys;
onOk?: (params: ResourceSelectorResponse | string | null) => void;
}

type ResourceGroup = {
id: number; // 数据集或者模型 id
name: string; // 数据集或者模型 id
id: number; // 数据集\模型\镜像 id
name: string; // 数据集\模型\镜像 name
};

type ResourceFile = {
@@ -42,9 +34,17 @@ type ResourceFile = {
file_name: string; // 文件 name
};

export interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> {
type: ResourceSelectorType; // 数据集\模型\镜像
defaultExpandedKeys?: React.Key[];
defaultCheckedKeys?: React.Key[];
defaultActiveTab?: CommonTabKeys;
onOk?: (params: ResourceSelectorResponse | null) => void;
}

type TreeRef = GetRef<typeof Tree<TreeDataNode>>;

// list 转成 treeData
// list 数据转成 treeData
const convertToTreeData = (list: ResourceGroup[]): TreeDataNode[] => {
return list.map((v) => ({
title: v.name,
@@ -54,7 +54,7 @@ const convertToTreeData = (list: ResourceGroup[]): TreeDataNode[] => {
}));
};

// 版本转成 treeData
// 版本数据转成 treeData
const convertVersionToTreeData = (parentId: number) => {
return (item: string | MirrorVersion): TreeDataNode => {
if (typeof item === 'string') {
@@ -88,7 +88,7 @@ const updateChildren = (parentId: number, children: TreeDataNode[]) => {
};
};

// 得到数据集或者模型 id 和下属版本号
// 得到数据集\模型\镜像 id 和下属版本号
const getIdAndVersion = (versionKey: string) => {
const index = versionKey.indexOf('-');
const id = Number(versionKey.slice(0, index));
@@ -137,12 +137,12 @@ function ResourceSelectorModal({
[originTreeData, searchText],
);

// 获取数据集或模型列表
// 获取数据集\模型\镜像列表
const getTreeData = async () => {
const available_range = activeTab === CommonTabKeys.Private ? 0 : 1;
const params = {
page: 0,
size: 200,
size: 1000,
[selectorTypeConfig[type].litReqParamKey]: available_range,
};
const getListReq = selectorTypeConfig[type].getList;
@@ -159,7 +159,7 @@ function ResourceSelectorModal({
}
};

// 获取数据集或模型版本列表
// 获取数据集\模型\镜像版本列表
const getVersions = async (parentId: number) => {
const getVersionsReq = selectorTypeConfig[type].getVersions;
const [res, error] = await to(getVersionsReq(parentId));
@@ -266,21 +266,17 @@ function ResourceSelectorModal({
// 提交
const handleOk = () => {
if (checkedKeys.length > 0) {
if (type === ResourceSelectorType.Mirror) {
onOk?.(files[0].file_name);
} else {
const last = checkedKeys[0] as string;
const { id, version } = getIdAndVersion(last);
const name = (treeData.find((v) => Number(v.key) === id)?.title ?? '') as string;
const res = {
id,
name,
path: versionPath,
version,
activeTab: activeTab as CommonTabKeys,
};
onOk?.(res);
}
const last = checkedKeys[0] as string;
const { id, version } = getIdAndVersion(last);
const name = (treeData.find((v) => Number(v.key) === id)?.title ?? '') as string;
const res = {
id,
name,
path: versionPath,
version,
activeTab: activeTab as CommonTabKeys,
};
onOk?.(res);
} else {
onOk?.(null);
}


+ 12
- 4
react-ui/src/pages/Pipeline/editPipeline/index.jsx View File

@@ -8,8 +8,8 @@ import { useEffect, useRef } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { s8 } from '../../../utils';
import GlobalParamsDrawer from '../components/GlobalParamsDrawer';
import ModelMenu from '../components/ModelMenu';
import styles from './index.less';
import ModelMenus from './modelMenus';
import Props from './props';
import { findAllParentNodes, findFirstDuplicate } from './utils';

@@ -51,8 +51,16 @@ const EditPipeline = () => {
return item.id === val.id;
});
data.nodes[index] = val;
const zoom = graph.getZoom();
// 在拉取新数据重新渲染页面之前先获取点(0, 0)在画布上的位置
const lastPoint = graph.getCanvasByPoint(0, 0);
graph.changeData(data);
graph.render();
graph.zoomTo(zoom);
// 获取重新渲染之后点(0, 0)在画布的位置
const newPoint = graph.getCanvasByPoint(0, 0);
// 移动画布相对位移;
graph.translate(lastPoint.x - newPoint.x, lastPoint.y - newPoint.y);
}
};
const savePipeline = async (val) => {
@@ -89,7 +97,7 @@ const EditPipeline = () => {
closeParamsDrawer();
setTimeout(() => {
if (val) {
navgite({ pathname: `/pipeline` });
navgite({ pathname: `/pipeline/template` });
}
}, 500);
});
@@ -432,7 +440,7 @@ const EditPipeline = () => {
height: graphRef.current.clientHeight || '100%',
animate: false,
groupByTypes: false,
fitView: false,
fitView: true,
plugins: [contextMenu],
enabledStack: true,
modes: {
@@ -691,7 +699,7 @@ const EditPipeline = () => {
};
return (
<div className={styles['pipeline-container']}>
<ModelMenus onParDragEnd={onDragEnd}></ModelMenus>
<ModelMenu onComponentDragEnd={onDragEnd}></ModelMenu>
<div className={styles['pipeline-container__workflow']}>
<div className={styles['pipeline-container__workflow__top']}>
<Button


+ 0
- 67
react-ui/src/pages/Pipeline/editPipeline/modelMenus.jsx View File

@@ -1,67 +0,0 @@
import { getComponentAll } from '@/services/pipeline/index.js';
import { Collapse } from 'antd';
import { useEffect, useState } from 'react';
import Styles from './modelMenus.less';
const ModelMenus = ({ onParDragEnd }) => {
const [modelMenusList, setModelMenusList] = useState([]);
useEffect(() => {
getComponentAll().then((ret) => {
console.log(ret);
if (ret.code === 200) {
setModelMenusList(ret.data);
}
});
}, []);
const dragEnd = (e, data) => {
console.log(e, data);
onParDragEnd({
...data,
x: e.clientX,
y: e.clientY,
label: data.component_label,
img: `/assets/images/${data.icon_path}.png`,
});
};
const { Panel } = Collapse;
return (
<div className={Styles.collapse}>
<div className={Styles.modelMenusTitle}>组件库</div>
{modelMenusList && modelMenusList.length > 0 ? (
<Collapse
collapsible="header"
defaultActiveKey={modelMenusList.map((item) => item.key + '')}
expandIconPosition="end"
>
{modelMenusList && modelMenusList.length > 0
? modelMenusList.map((item) => (
<Panel header={<div>{item.name}</div>} key={item.key}>
{item.value && item.value.length > 0
? item.value.map((ele) => (
<div
key={ele.id}
draggable="true"
onDragEnd={(e) => {
dragEnd(e, ele);
}}
className={Styles.collapseItem}
>
{ele.icon_path && (
<img
style={{ height: '16px', marginRight: '15px' }}
src={`/assets/images/${ele.icon_path}.png`}
alt=""
/>
)}
{ele.component_label}
</div>
))
: ''}
</Panel>
))
: ''}
</Collapse>
) : null}
</div>
);
};
export default ModelMenus;

react-ui/src/pages/Pipeline/editPipeline/props.jsx → react-ui/src/pages/Pipeline/editPipeline/props.tsx View File

@@ -1,11 +1,19 @@
import KFIcon from '@/components/KFIcon';
import ParameterInput from '@/components/ParameterInput';
import ParameterSelect from '@/components/ParameterSelect';
import SubAreaTitle from '@/components/SubAreaTitle';
import { CommonTabKeys } from '@/enums';
import { useComputingResource } from '@/hooks/resource';
import {
PipelineGlobalParam,
PipelineNodeModelParameter,
PipelineNodeModelSerialize,
} from '@/types';
import { openAntdModal } from '@/utils/modal';
import { to } from '@/utils/promise';
import { Button, Drawer, Form, Input, Select } from 'antd';
import { pick } from 'lodash';
import { INode } from '@antv/g6';
import { Button, Drawer, Form, Input, MenuProps, Select } from 'antd';
import { NamePath } from 'antd/es/form/interface';
import { forwardRef, useImperativeHandle, useState } from 'react';
import PropsLabel from '../components/PropsLabel';
import ResourceSelectorModal, {
@@ -16,18 +24,22 @@ import styles from './props.less';
import { canInput, createMenuItems } from './utils';
const { TextArea } = Input;

const Props = forwardRef(({ onParentChange }, ref) => {
type PipelineNodeParameterProps = {
onParentChange: (data: PipelineNodeModelSerialize) => void;
};

const PipelineNodeParameter = forwardRef(({ onParentChange }: PipelineNodeParameterProps, ref) => {
const [form] = Form.useForm();
const [stagingItem, setStagingItem] = useState({});
const [stagingItem, setStagingItem] = useState<PipelineNodeModelSerialize>(
{} as PipelineNodeModelSerialize,
);
const [open, setOpen] = useState(false);
const [selectedModel, setSelectedModel] = useState(undefined); // 选择的模型,为了再次打开时恢复原来的选择
const [selectedDataset, setSelectedDataset] = useState(undefined); // 选择的数据集,为了再次打开时恢复原来的选择
const [resourceStandardList, filterResourceStandard] = useComputingResource(); // 资源规模
const [menuItems, setMenuItems] = useState([]);
const [menuItems, setMenuItems] = useState<MenuProps['items']>([]);

const afterOpenChange = () => {
if (!open) {
console.log('zzzzz', form.getFieldsValue());
console.log('getFieldsValue', form.getFieldsValue());
const control_strategy = form.getFieldValue('control_strategy');
const in_parameters = form.getFieldValue('in_parameters');
const out_parameters = form.getFieldValue('out_parameters');
@@ -54,7 +66,7 @@ const Props = forwardRef(({ onParentChange }, ref) => {
return Promise.reject(propsError);
}
},
showDrawer(e, params, parentNodes) {
showDrawer(e: any, params: PipelineGlobalParam[], parentNodes: INode[]) {
if (e.item && e.item.getModel()) {
form.resetFields();
const model = e.item.getModel();
@@ -75,8 +87,6 @@ const Props = forwardRef(({ onParentChange }, ref) => {
} catch (error) {
console.log(error);
}
setSelectedModel(undefined);
setSelectedDataset(undefined);
setOpen(true);

// 参数下拉菜单
@@ -89,50 +99,70 @@ const Props = forwardRef(({ onParentChange }, ref) => {
}));

// 选择数据集、模型、镜像
const selectResource = (name, item) => {
let type;
let resource;
const selectResource = (
formItemName: NamePath,
item: PipelineNodeModelParameter | Pick<PipelineNodeModelParameter, 'item_type'>,
) => {
let type: ResourceSelectorType;
switch (item.item_type) {
case 'dataset':
type = ResourceSelectorType.Dataset;
resource = selectedDataset;
break;
case 'model':
type = ResourceSelectorType.Model;
resource = selectedModel;
break;
default:
type = ResourceSelectorType.Mirror;
break;
}

const fieldValue = form.getFieldValue(formItemName);
const activeTab = fieldValue?.activeTab as CommonTabKeys | undefined;
const expandedKeys = Array.isArray(fieldValue?.expandedKeys) ? fieldValue?.expandedKeys : [];
const checkedKeys = Array.isArray(fieldValue?.checkedKeys) ? fieldValue?.checkedKeys : [];
const { close } = openAntdModal(ResourceSelectorModal, {
type,
defaultExpandedKeys: resource ? [resource.id] : [],
defaultCheckedKeys: resource ? [`${resource.id}-${resource.version}`] : [],
defaultActiveTab: resource?.activeTab,
defaultExpandedKeys: expandedKeys,
defaultCheckedKeys: checkedKeys,
defaultActiveTab: activeTab,
onOk: (res) => {
if (res) {
if (type === ResourceSelectorType.Mirror) {
form.setFieldValue(name, res);
const { activeTab, id, version, path } = res;
if (formItemName === 'image') {
form.setFieldValue(formItemName, path);
} else {
form.setFieldValue(formItemName, {
...item,
value: path,
showValue: path,
fromSelect: true,
activeTab,
expandedKeys: [id],
checkedKeys: [`${id}-${version}`],
});
}
} else {
const jsonObj = pick(res, ['id', 'version', 'path']);
const { activeTab, id, name, version, path } = res;
const jsonObj = {
id,
version,
path,
};
const value = JSON.stringify(jsonObj);
const showValue = `${res.name}:${res.version}`;
form.setFieldValue(name, { ...item, value, showValue, fromSelect: true });

if (type === ResourceSelectorType.Dataset) {
setSelectedDataset(res);
} else if (type === ResourceSelectorType.Model) {
setSelectedModel(res);
}
const showValue = `${name}:${version}`;
form.setFieldValue(formItemName, {
...item,
value,
showValue,
fromSelect: true,
activeTab,
expandedKeys: [id],
checkedKeys: [`${id}-${version}`],
});
}
} else {
if (type === ResourceSelectorType.Dataset) {
setSelectedDataset(undefined);
} else if (type === ResourceSelectorType.Model) {
setSelectedModel(undefined);
}
form.setFieldValue(name, '');
form.setFieldValue(formItemName, '');
}
close();
},
@@ -140,9 +170,9 @@ const Props = forwardRef(({ onParentChange }, ref) => {
};

// 获取选择数据集、模型后面按钮 icon
const getSelectBtnIcon = (item) => {
const getSelectBtnIcon = (item: { item_type: string }) => {
const type = item.item_type;
let selectorType;
let selectorType: ResourceSelectorType;
if (type === 'dataset') {
selectorType = ResourceSelectorType.Dataset;
} else if (type === 'model') {
@@ -155,10 +185,33 @@ const Props = forwardRef(({ onParentChange }, ref) => {
};

// 参数回填
const handleParameterClick = (name, value) => {
const handleParameterClick = (name: NamePath, value: any) => {
form.setFieldValue(name, value);
};

// form item label
const getLabel = (
item: { key: string; value: PipelineNodeModelParameter },
namePrefix: string,
) => {
return item.value.type === 'select' ? (
item.value.label + '(' + item.key + ')'
) : (
<PropsLabel
menuItems={menuItems}
title={item.value.label + '(' + item.key + ')'}
onClick={(value) => {
handleParameterClick([namePrefix, item.key], {
...item.value,
value,
fromSelect: true,
showValue: value,
});
}}
/>
);
};

// 控制策略
const controlStrategyList = Object.entries(stagingItem.control_strategy ?? {}).map(
([key, value]) => ({ key, value }),
@@ -290,7 +343,6 @@ const Props = forwardRef(({ onParentChange }, ref) => {
]}
>
<Select
showSearch
placeholder="请选择资源规格"
filterOption={filterResourceStandard}
options={resourceStandardList}
@@ -298,6 +350,8 @@ const Props = forwardRef(({ onParentChange }, ref) => {
label: 'description',
value: 'standard',
}}
showSearch
allowClear
/>
</Form.Item>
<Form.Item
@@ -332,31 +386,9 @@ const Props = forwardRef(({ onParentChange }, ref) => {
<Form.Item
key={item.key}
name={['control_strategy', item.key]}
label={
<PropsLabel
menuItems={menuItems}
title={item.value.label}
onClick={(value) => {
handleParameterClick(['control_strategy', item.key], {
...item.value,
value,
fromSelect: true,
showValue: value,
});
}}
/>
}
// getValueProps={(e) => {
// return { value: e.value };
// }}
// getValueFromEvent={(e) => {
// return {
// ...item.value,
// value: e.target.value,
// };
// }}
label={getLabel(item, 'control_strategy')}
>
<ParameterInput placeholder={item.value.placeholder} allowClear></ParameterInput>
<ParameterInput allowClear></ParameterInput>
</Form.Item>
))}
<div className={styles['pipeline-drawer__title']}>
@@ -365,20 +397,7 @@ const Props = forwardRef(({ onParentChange }, ref) => {
{inParametersList.map((item) => (
<Form.Item
key={item.key}
label={
<PropsLabel
menuItems={menuItems}
title={item.value.label + '(' + item.key + ')'}
onClick={(value) => {
handleParameterClick(['in_parameters', item.key], {
...item.value,
value,
fromSelect: true,
showValue: value,
});
}}
/>
}
label={getLabel(item, 'in_parameters')}
required={item.value.require ? true : false}
>
<div className={styles['pipeline-drawer__ref-row']}>
@@ -387,11 +406,11 @@ const Props = forwardRef(({ onParentChange }, ref) => {
noStyle
rules={[{ required: item.value.require ? true : false }]}
>
<ParameterInput
placeholder={item.value.placeholder}
canInput={canInput(item.value)}
allowClear
></ParameterInput>
{item.value.type === 'select' ? (
<ParameterSelect />
) : (
<ParameterInput canInput={canInput(item.value)} allowClear></ParameterInput>
)}
</Form.Item>
{item.value.type === 'ref' && (
<Form.Item noStyle>
@@ -416,32 +435,10 @@ const Props = forwardRef(({ onParentChange }, ref) => {
<Form.Item
key={item.key}
name={['out_parameters', item.key]}
label={
<PropsLabel
menuItems={menuItems}
title={item.value.label + '(' + item.key + ')'}
onClick={(value) => {
handleParameterClick(['out_parameters', item.key], {
...item.value,
value,
fromSelect: true,
showValue: value,
});
}}
/>
}
label={getLabel(item, 'out_parameters')}
rules={[{ required: item.value.require ? true : false }]}
// getValueProps={(e) => {
// return { value: e.value };
// }}
// getValueFromEvent={(e) => {
// return {
// ...item.value,
// value: e.target.value,
// };
// }}
>
<ParameterInput placeholder={item.value.placeholder} allowClear></ParameterInput>
<ParameterInput allowClear></ParameterInput>
</Form.Item>
))}
</Form>
@@ -449,4 +446,4 @@ const Props = forwardRef(({ onParentChange }, ref) => {
);
});

export default Props;
export default PipelineNodeParameter;

+ 1
- 0
react-ui/src/pages/Pipeline/editPipeline/utils.tsx View File

@@ -77,6 +77,7 @@ export function getInParameterComponent(
return null;
}

// 判断是否允许输入
export function canInput(parameter: PipelineNodeModelParameter) {
const { type, item_type } = parameter;
return !(


+ 1
- 2
react-ui/src/pages/Pipeline/index.jsx View File

@@ -101,9 +101,8 @@ const Pipeline = () => {
page: pageOption.current.page - 1,
size: pageOption.current.size,
};
console.log(params, pageOption);
getWorkflow(params).then((ret) => {
if (ret.code == 200) {
if (ret.code === 200) {
setPipeList(ret.data.content);

setTotal(ret.data.totalElements);


+ 1
- 1
react-ui/src/pages/Pipeline/index.less View File

@@ -13,7 +13,7 @@
}

.PipelineBox {
height: calc(100% - 20px);
height: 100%;
.PipelineTable {
height: calc(100% - 60px);
:global {


+ 8
- 0
react-ui/src/services/modelDeployment/index.ts View File

@@ -59,3 +59,11 @@ export function updateModelDeploymentReq(data: any) {
data,
});
}

// 获取模型部署操作指南
export function getModelDeploymentDocsReq(data: any) {
return request(`/api/v1/model/getDocs`, {
method: 'POST',
data,
});
}

+ 2
- 2
react-ui/src/styles/menu.less View File

@@ -4,7 +4,7 @@
display: flex;
align-items: center;
justify-content: flex-start;
font-size: 16px;
font-size: @font-size-content;

.anticon.kf-menu-item__default-icon {
display: inline !important;
@@ -54,6 +54,6 @@

.ant-menu-submenu {
.ant-menu-submenu-title:hover {
color: #1664ff !important;
color: @primary-color !important;
}
}

+ 4
- 1
react-ui/src/styles/theme.less View File

@@ -11,10 +11,11 @@
@background-color: #f9fafb; // 页面背景颜色
@text-color: #1d1d20;
@text-color-secondary: #575757;
@success-color: #1ace62;
@success-color: #6ac21d;
@error-color: #c73131;
@warning-color: #f98e1b;
@abort-color: #8a8a8a;
@pending-color: #ecb934;

@border-color: rgba(22, 100, 255, 0.3);
@border-color-secondary: rgba(22, 100, 255, 0.1);
@@ -78,4 +79,6 @@
fontSizeInput: @font-size-input;
fontSizeInputLg: @font-size-input-lg;
siderBGColor: @sider-background-color;
abortColor: @abort-color;
pendingColor: @pending-color;
}

+ 10
- 5
react-ui/src/types.ts View File

@@ -41,20 +41,25 @@ export type PipelineNodeModel = {
control_strategy: string;
in_parameters: string;
out_parameters: string;
component_label: string;
icon_path: string;
};

// 流水线
// 流水线节点模型数据
export type PipelineNodeModelParameter = {
label: string;
value: any;
require: number;
type: string;
item_type: string;
label: string;
value: any;
require?: number;
placeholder?: string;
describe?: string;
fromSelect?: boolean;
showValue?: any;
editable: number;
editable?: number;
activeTab?: string; // ResourceSelectorModal tab
expandedKeys?: string[]; // ResourceSelectorModal expandedKeys
checkedKeys?: string[]; // ResourceSelectorModal checkedKeys
};

// type ChangePropertyType<T, K extends keyof T, NewType> = Omit<T, K> & { [P in K]: NewType }


+ 1
- 1
react-ui/src/utils/downloadfile.ts View File

@@ -29,7 +29,7 @@ export function resolveBlob(res: any, mimeType: string) {
document.body.removeChild(aLink);
}

export function downLoadZip(url: string, params: any) {
export function downLoadZip(url: string, params?: any) {
request(url, {
method: 'GET',
params,


Loading…
Cancel
Save