Browse Source

Merge pull request '524' (#51) from dev into master

pull/71/head
fanshuai 1 year ago
parent
commit
cb7fcd4347
100 changed files with 3986 additions and 3817 deletions
  1. +0
    -1
      react-ui/config/defaultSettings.ts
  2. +46
    -25
      react-ui/config/routes.ts
  3. BIN
      react-ui/public/assets/images/compoent-icon-6.png
  4. BIN
      react-ui/public/assets/images/icon/流水线-1.png
  5. BIN
      react-ui/public/assets/images/mindspore模型转换.png
  6. BIN
      react-ui/public/assets/images/pipelieEditIcon.png
  7. BIN
      react-ui/public/assets/images/pipeline-edit-icon.png
  8. BIN
      react-ui/public/assets/images/pytorch推理.png
  9. BIN
      react-ui/public/assets/images/pytorch训练.png
  10. BIN
      react-ui/public/assets/images/sjj-icon-1.png
  11. BIN
      react-ui/public/assets/images/tensorflow模型转换.png
  12. BIN
      react-ui/public/assets/images/发送通知.png
  13. +30
    -32
      react-ui/src/app.tsx
  14. BIN
      react-ui/src/assets/img/model-deployment.png
  15. +15
    -1
      react-ui/src/components/CommonTableCell/index.tsx
  16. +2
    -1
      react-ui/src/components/DateTableCell/index.tsx
  17. +1
    -0
      react-ui/src/components/KFIcon/index.tsx
  18. +0
    -2
      react-ui/src/components/KFModal/index.less
  19. +2
    -1
      react-ui/src/components/KFModal/index.tsx
  20. +5
    -0
      react-ui/src/components/KFRadio/index.less
  21. +4
    -1
      react-ui/src/components/PageTitle/index.less
  22. +64
    -0
      react-ui/src/components/ParameterInput/index.less
  23. +106
    -0
      react-ui/src/components/ParameterInput/index.tsx
  24. +1
    -0
      react-ui/src/components/RightContent/index.tsx
  25. +19
    -1
      react-ui/src/enums/index.ts
  26. +46
    -0
      react-ui/src/hooks/resource.ts
  27. +19
    -0
      react-ui/src/hooks/sessionStorage.ts
  28. +1
    -0
      react-ui/src/iconfont/iconfont-menu.js
  29. +1
    -1
      react-ui/src/iconfont/iconfont.js
  30. +29
    -13
      react-ui/src/overrides.less
  31. +9
    -0
      react-ui/src/pages/Dataset/components/AddDatasetModal/index.less
  32. +199
    -0
      react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx
  33. +178
    -0
      react-ui/src/pages/Dataset/components/AddModelModal/index.tsx
  34. +170
    -0
      react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx
  35. +41
    -0
      react-ui/src/pages/Dataset/components/CategoryItem/index.less
  36. +37
    -0
      react-ui/src/pages/Dataset/components/CategoryItem/index.tsx
  37. +22
    -0
      react-ui/src/pages/Dataset/components/CategoryList/index.less
  38. +71
    -0
      react-ui/src/pages/Dataset/components/CategoryList/index.tsx
  39. +39
    -0
      react-ui/src/pages/Dataset/components/ResourceList/index.less
  40. +211
    -0
      react-ui/src/pages/Dataset/components/ResourceList/index.tsx
  41. +11
    -0
      react-ui/src/pages/Dataset/components/ResourcePage/index.less
  42. +113
    -0
      react-ui/src/pages/Dataset/components/ResourcePage/index.tsx
  43. +61
    -0
      react-ui/src/pages/Dataset/components/Resourcetem/index.less
  44. +54
    -0
      react-ui/src/pages/Dataset/components/Resourcetem/index.tsx
  45. +5
    -88
      react-ui/src/pages/Dataset/index.jsx
  46. +0
    -337
      react-ui/src/pages/Dataset/index.less
  47. +55
    -192
      react-ui/src/pages/Dataset/intro.jsx
  48. +82
    -0
      react-ui/src/pages/Dataset/intro.less
  49. +0
    -479
      react-ui/src/pages/Dataset/personalData.jsx
  50. +0
    -284
      react-ui/src/pages/Dataset/publicData.jsx
  51. +129
    -0
      react-ui/src/pages/Dataset/types.tsx
  52. +1
    -1
      react-ui/src/pages/DatasetPreparation/DatasetAnnotation/index.tsx
  53. +9
    -0
      react-ui/src/pages/Experiment/components/AddExperimentModal/index.less
  54. +7
    -2
      react-ui/src/pages/Experiment/components/AddExperimentModal/index.tsx
  55. +16
    -0
      react-ui/src/pages/Experiment/components/ExperimentParameter/index.less
  56. +172
    -0
      react-ui/src/pages/Experiment/components/ExperimentParameter/index.tsx
  57. +38
    -0
      react-ui/src/pages/Experiment/components/ExperimentResult/index.less
  58. +59
    -0
      react-ui/src/pages/Experiment/components/ExperimentResult/index.tsx
  59. +34
    -0
      react-ui/src/pages/Experiment/components/LogGroup/index.less
  60. +16
    -28
      react-ui/src/pages/Experiment/components/LogGroup/index.tsx
  61. +3
    -0
      react-ui/src/pages/Experiment/components/LogList/index.less
  62. +21
    -0
      react-ui/src/pages/Experiment/components/LogList/index.tsx
  63. +0
    -0
      react-ui/src/pages/Experiment/components/ViewParamsModal/index.less
  64. +3
    -2
      react-ui/src/pages/Experiment/components/ViewParamsModal/index.tsx
  65. +0
    -19
      react-ui/src/pages/Experiment/experimentText/LogList.tsx
  66. +0
    -22
      react-ui/src/pages/Experiment/experimentText/addExperimentModal.less
  67. +0
    -90
      react-ui/src/pages/Experiment/experimentText/index.less
  68. +0
    -34
      react-ui/src/pages/Experiment/experimentText/logGroup.less
  69. +0
    -439
      react-ui/src/pages/Experiment/experimentText/props.jsx
  70. +11
    -12
      react-ui/src/pages/Experiment/index.jsx
  71. +4
    -0
      react-ui/src/pages/Experiment/index.less
  72. +3
    -6
      react-ui/src/pages/Experiment/status.ts
  73. +58
    -125
      react-ui/src/pages/Experiment/training/index.jsx
  74. +33
    -0
      react-ui/src/pages/Experiment/training/index.less
  75. +37
    -0
      react-ui/src/pages/Experiment/training/props.less
  76. +171
    -0
      react-ui/src/pages/Experiment/training/props.tsx
  77. +3
    -3
      react-ui/src/pages/Mirror/Create/index.less
  78. +26
    -30
      react-ui/src/pages/Mirror/Create/index.tsx
  79. +0
    -0
      react-ui/src/pages/Mirror/Info/index.less
  80. +10
    -9
      react-ui/src/pages/Mirror/Info/index.tsx
  81. +5
    -2
      react-ui/src/pages/Mirror/List/index.less
  82. +23
    -25
      react-ui/src/pages/Mirror/List/index.tsx
  83. +3
    -6
      react-ui/src/pages/Mirror/components/MirrorStatusCell/index.tsx
  84. +5
    -87
      react-ui/src/pages/Model/index.jsx
  85. +0
    -327
      react-ui/src/pages/Model/index.less
  86. +43
    -178
      react-ui/src/pages/Model/intro.jsx
  87. +80
    -0
      react-ui/src/pages/Model/intro.less
  88. +0
    -525
      react-ui/src/pages/Model/personalData.jsx
  89. +0
    -384
      react-ui/src/pages/Model/publicData.jsx
  90. +19
    -0
      react-ui/src/pages/ModelDeployment/Create/index.less
  91. +449
    -0
      react-ui/src/pages/ModelDeployment/Create/index.tsx
  92. +56
    -0
      react-ui/src/pages/ModelDeployment/Info/index.less
  93. +194
    -0
      react-ui/src/pages/ModelDeployment/Info/index.tsx
  94. +21
    -0
      react-ui/src/pages/ModelDeployment/List/index.less
  95. +349
    -0
      react-ui/src/pages/ModelDeployment/List/index.tsx
  96. +19
    -0
      react-ui/src/pages/ModelDeployment/components/ModelDeployStatusCell/index.less
  97. +44
    -0
      react-ui/src/pages/ModelDeployment/components/ModelDeployStatusCell/index.tsx
  98. +32
    -0
      react-ui/src/pages/ModelDeployment/types.ts
  99. +2
    -2
      react-ui/src/pages/Monitor/Job/edit.tsx
  100. +29
    -0
      react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.less

+ 0
- 1
react-ui/config/defaultSettings.ts View File

@@ -19,7 +19,6 @@ const Settings: ProLayoutProps & {
title: '智能软件开发平台', title: '智能软件开发平台',
pwa: true, pwa: true,
logo: '/assets/images/left-top-logo.png', logo: '/assets/images/left-top-logo.png',
iconfontUrl: '//at.alicdn.com/t/c/font_4511326_1cmi0j3dj1x.js',
token: { token: {
// 参见ts声明,demo 见文档,通过token 修改样式 // 参见ts声明,demo 见文档,通过token 修改样式
//https://procomponents.ant.design/components/layout#%E9%80%9A%E8%BF%87-token-%E4%BF%AE%E6%94%B9%E6%A0%B7%E5%BC%8F //https://procomponents.ant.design/components/layout#%E9%80%9A%E8%BF%87-token-%E4%BF%AE%E6%94%B9%E6%A0%B7%E5%BC%8F


+ 46
- 25
react-ui/config/routes.ts View File

@@ -67,24 +67,36 @@ export default [
path: '/pipeline', path: '/pipeline',
routes: [ routes: [
{ {
name: '流水线',
path: '/pipeline/pipelineText',
component: './Pipeline/index',
},
{
name: '训练',
path: '/pipeline/pytorchtext/:id/:name',
component: './Pipeline/editPipeline/index',
name: '流水线模板',
path: 'template',
routes: [
{
name: '流水线模板',
path: '',
component: './Pipeline/index',
},
{
name: '流水线详情',
path: ':id/:name',
component: './Pipeline/editPipeline/index',
},
],
}, },
{ {
name: '实验', name: '实验',
path: '/pipeline/experimentText',
component: './Experiment/index',
},
{
name: '实验训练',
path: '/pipeline/experimentPytorchtext/:workflowId/:id',
component: './Experiment/experimentText/index',
path: 'experiment',
routes: [
{
name: '实验',
path: '',
component: './Experiment/index',
},
{
name: '实验训练',
path: ':workflowId/:id',
component: './Experiment/training/index',
},
],
}, },
], ],
}, },
@@ -131,7 +143,7 @@ export default [
{ {
name: '数据集简介', name: '数据集简介',
path: ':id', path: ':id',
component: './Dataset/datasetIntro',
component: './Dataset/intro',
}, },
], ],
}, },
@@ -147,7 +159,7 @@ export default [
{ {
name: '模型简介', name: '模型简介',
path: ':id', path: ':id',
component: './Model/modelIntro',
component: './Model/intro',
}, },
], ],
}, },
@@ -158,17 +170,17 @@ export default [
{ {
name: '镜像列表', name: '镜像列表',
path: '', path: '',
component: './Mirror/list',
component: './Mirror/List',
}, },
{ {
name: '镜像详情', name: '镜像详情',
path: ':id', path: ':id',
component: './Mirror/info',
component: './Mirror/Info',
}, },
{ {
name: '创建镜像', name: '创建镜像',
path: 'create', path: 'create',
component: './Mirror/create',
component: './Mirror/Create',
}, },
], ],
}, },
@@ -188,14 +200,23 @@ export default [
], ],
}, },
{ {
name: 'modelDseployment',
path: '/modelDseployment',
name: 'modelDeployment',
path: '/modelDeployment',
routes: [ routes: [
{ {
name: '模型部署',
name: '模型列表',
path: '', path: '',
key: 'modelDseployment',
component: './missingPage.jsx',
component: './ModelDeployment/List',
},
{
name: '镜像详情',
path: ':id',
component: './ModelDeployment/Info',
},
{
name: '创建镜像',
path: 'create',
component: './ModelDeployment/Create',
}, },
], ],
}, },


BIN
react-ui/public/assets/images/compoent-icon-6.png View File

Before After
Width: 72  |  Height: 72  |  Size: 1.5 kB

BIN
react-ui/public/assets/images/icon/流水线-1.png View File

Before After
Width: 54  |  Height: 54  |  Size: 1.6 kB

BIN
react-ui/public/assets/images/mindspore模型转换.png View File

Before After
Width: 72  |  Height: 72  |  Size: 1.9 kB

BIN
react-ui/public/assets/images/pipelieEditIcon.png View File

Before After
Width: 42  |  Height: 40  |  Size: 1.1 kB

BIN
react-ui/public/assets/images/pipeline-edit-icon.png View File

Before After
Width: 42  |  Height: 40  |  Size: 1.1 kB

BIN
react-ui/public/assets/images/pytorch推理.png View File

Before After
Width: 72  |  Height: 72  |  Size: 2.3 kB

BIN
react-ui/public/assets/images/pytorch训练.png View File

Before After
Width: 72  |  Height: 72  |  Size: 4.8 kB

BIN
react-ui/public/assets/images/sjj-icon-1.png View File

Before After
Width: 44  |  Height: 44  |  Size: 1.1 kB

BIN
react-ui/public/assets/images/tensorflow模型转换.png View File

Before After
Width: 72  |  Height: 72  |  Size: 4.0 kB

BIN
react-ui/public/assets/images/发送通知.png View File

Before After
Width: 72  |  Height: 72  |  Size: 1.2 kB

+ 30
- 32
react-ui/src/app.tsx View File

@@ -17,9 +17,10 @@ import {
patchRouteWithRemoteMenus, patchRouteWithRemoteMenus,
setRemoteMenu, setRemoteMenu,
} from './services/session'; } from './services/session';
import './styles/menu.less';
export { requestConfig as request } from './requestConfig'; export { requestConfig as request } from './requestConfig';
// const isDev = process.env.NODE_ENV === 'development'; // const isDev = process.env.NODE_ENV === 'development';
import { menuItemRender } from '@/utils/menuRender';
/** /**
* @see https://umijs.org/zh-CN/plugins/plugin-initial-state * @see https://umijs.org/zh-CN/plugins/plugin-initial-state
* */ * */
@@ -139,10 +140,8 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => {
onClick: () => { onClick: () => {
// 点击菜单项,删除所有的页面 state 缓存 // 点击菜单项,删除所有的页面 state 缓存
removeAllPageCacheState(); removeAllPageCacheState();
// console.log('click menu');
}, },
// onSelect: (e) => {
// console.log(e);
// },
}, },
...initialState?.settings, ...initialState?.settings,
token: { token: {
@@ -150,60 +149,49 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => {
colorTextMenu: themes['textColor'], colorTextMenu: themes['textColor'],
colorTextMenuSelected: themes['primaryColor'], colorTextMenuSelected: themes['primaryColor'],
colorTextMenuActive: themes['primaryColor'], colorTextMenuActive: themes['primaryColor'],
colorTextMenuItemHover: themes['primaryColor'],
colorBgMenuItemSelected: 'rgba(197, 232, 255, 0.8)', colorBgMenuItemSelected: 'rgba(197, 232, 255, 0.8)',
colorMenuBackground: themes['siderBGColor'], colorMenuBackground: themes['siderBGColor'],
}, },
}, },
// menuItemRender: (itemProps, defaultDom, props) => {
// console.log('menuItemProps', itemProps);
// console.log('defaultDom', defaultDom);
// console.log('props', props);

// const { pathname } = window.location;
// const isSelected = pathname === itemProps.path;

// // 根据菜单项的状态动态显示不同的 icon
// const icon = isSelected ? 'icon-developmentEnvironment-icon' : 'icon-kaifahuanjing';
// return (
// <Link to={itemProps.path || ''} target={itemProps.target}>
// <KFIcon type={icon} />
// {itemProps.name}
// </Link>
// );
// },
menuItemRender: menuItemRender(false),
subMenuItemRender: menuItemRender(true),
}; };
}; };


export const onRouteChange: RuntimeConfig['onRouteChange'] = async (e) => { export const onRouteChange: RuntimeConfig['onRouteChange'] = async (e) => {
const { location } = e; const { location } = e;
const menus = getRemoteMenu(); const menus = getRemoteMenu();
console.log('onRouteChange', e);
// console.log('onRouteChange', e);
if (menus === null && location.pathname !== PageEnum.LOGIN) { if (menus === null && location.pathname !== PageEnum.LOGIN) {
console.log('refresh');
history.go(0); history.go(0);
} }
}; };


export const patchRoutes: RuntimeConfig['patchRoutes'] = (e) => { export const patchRoutes: RuntimeConfig['patchRoutes'] = (e) => {
console.log('patchRoutes', e);
//console.log('patchRoutes', e);
}; };


export const patchClientRoutes: RuntimeConfig['patchClientRoutes'] = (e) => { export const patchClientRoutes: RuntimeConfig['patchClientRoutes'] = (e) => {
console.log('patchClientRoutes', e);
//console.log('patchClientRoutes', e);
patchRouteWithRemoteMenus(e.routes); patchRouteWithRemoteMenus(e.routes);
}; };


export function render(oldRender: () => void) { export function render(oldRender: () => void) {
console.log('render');
//console.log('render');
const token = getAccessToken(); const token = getAccessToken();
if (!token || token?.length === 0) { if (!token || token?.length === 0) {
oldRender(); oldRender();
return; return;
} }
getRoutersInfo().then((res) => {
setRemoteMenu(res);
oldRender();
});
getRoutersInfo()
.then((res) => {
setRemoteMenu(res);
oldRender();
})
.catch(() => {
oldRender();
});
} }


// 主题修改 // 主题修改
@@ -215,6 +203,7 @@ export const antd: RuntimeAntdConfig = (memo) => {
colorError: themes['errorColor'], colorError: themes['errorColor'],
colorWarning: themes['warningColor'], colorWarning: themes['warningColor'],
colorLink: themes['primaryColor'], colorLink: themes['primaryColor'],
colorText: themes['textColor'],
}; };
memo.theme.components ??= {}; memo.theme.components ??= {};
memo.theme.components.Tabs = {}; memo.theme.components.Tabs = {};
@@ -229,10 +218,14 @@ export const antd: RuntimeAntdConfig = (memo) => {
defaultActiveBorderColor: 'rgba(22, 100, 255, 0.75)', defaultActiveBorderColor: 'rgba(22, 100, 255, 0.75)',
defaultActiveColor: themes['primaryColor'], defaultActiveColor: themes['primaryColor'],
contentFontSize: parseInt(themes['fontSize']), contentFontSize: parseInt(themes['fontSize']),
controlHeight: 34,
}; };
memo.theme.components.Input = { memo.theme.components.Input = {
inputFontSize: parseInt(themes['fontSize']),
inputFontSize: parseInt(themes['fontSizeInput']),
inputFontSizeLG: parseInt(themes['fontSizeInputLg']),
paddingBlockLG: 10,
};
memo.theme.components.Select = {
singleItemHeightLG: 46,
}; };
memo.theme.components.Table = { memo.theme.components.Table = {
headerBg: 'rgba(242, 244, 247, 0.36)', headerBg: 'rgba(242, 244, 247, 0.36)',
@@ -241,6 +234,11 @@ export const antd: RuntimeAntdConfig = (memo) => {
memo.theme.components.Tabs = { memo.theme.components.Tabs = {
titleFontSize: 16, titleFontSize: 16,
}; };

memo.theme.components.Form = {
labelColor: 'rgba(29, 29, 32, 0.8);',
};

memo.theme.cssVar = true; memo.theme.cssVar = true;
// memo.theme.hashed = false; // memo.theme.hashed = false;




BIN
react-ui/src/assets/img/model-deployment.png View File

Before After
Width: 48  |  Height: 47  |  Size: 1.6 kB

+ 15
- 1
react-ui/src/components/CommonTableCell/index.tsx View File

@@ -4,8 +4,22 @@
* @Description: 自定义 Table 单元格,没有数据时展示 -- * @Description: 自定义 Table 单元格,没有数据时展示 --
*/ */


function CommonTableCell(text?: string | null) {
import { Tooltip } from 'antd';

function renderCell(text?: string | null) {
return <span>{text ?? '--'}</span>; return <span>{text ?? '--'}</span>;
} }


function CommonTableCell(ellipsis: boolean = false) {
if (ellipsis) {
return (text?: string | null) => (
<Tooltip title={text} placement="topLeft" overlayStyle={{ maxWidth: '400px' }}>
{renderCell(text)}
</Tooltip>
);
} else {
return renderCell;
}
}

export default CommonTableCell; export default CommonTableCell;

+ 2
- 1
react-ui/src/components/DateTableCell/index.tsx View File

@@ -4,6 +4,7 @@
* @Description: 自定义 Table 日期类单元格 * @Description: 自定义 Table 日期类单元格
*/ */


import { formatDate } from '@/utils/date';
import dayjs from 'dayjs'; import dayjs from 'dayjs';


function DateTableCell(text?: string | null) { function DateTableCell(text?: string | null) {
@@ -13,7 +14,7 @@ function DateTableCell(text?: string | null) {
if (!dayjs(text).isValid()) { if (!dayjs(text).isValid()) {
return <span>无效的日期</span>; return <span>无效的日期</span>;
} }
return <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>;
return <span>{formatDate(text)}</span>;
} }


export default DateTableCell; export default DateTableCell;

+ 1
- 0
react-ui/src/components/KFIcon/index.tsx View File

@@ -3,6 +3,7 @@
* @Date: 2024-04-17 12:53:06 * @Date: 2024-04-17 12:53:06
* @Description: * @Description:
*/ */
import '@/iconfont/iconfont-menu.js';
import '@/iconfont/iconfont.js'; import '@/iconfont/iconfont.js';
import { createFromIconfontCN } from '@ant-design/icons'; import { createFromIconfontCN } from '@ant-design/icons';




+ 0
- 2
react-ui/src/components/KFModal/index.less View File

@@ -23,8 +23,6 @@
border-radius: 10px; border-radius: 10px;
} }
.ant-btn-default { .ant-btn-default {
color: @text-color;
background: rgba(22, 100, 255, 0.06);
border-color: transparent; border-color: transparent;
} }
.ant-btn + .ant-btn { .ant-btn + .ant-btn {


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

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


+ 5
- 0
react-ui/src/components/KFRadio/index.less View File

@@ -23,6 +23,11 @@
&--active { &--active {
color: @primary-color; color: @primary-color;
border: 1px solid @primary-color; border: 1px solid @primary-color;

&:hover {
color: @primary-color;
border: 1px solid @primary-color;
}
} }


& + & { & + & {


+ 4
- 1
react-ui/src/components/PageTitle/index.less View File

@@ -3,5 +3,8 @@
align-items: center; align-items: center;
height: 50px; height: 50px;
padding-left: 30px; padding-left: 30px;
background-image: url('../../assets/img/page-title-bg.png');
background-image: url(@/assets/img/page-title-bg.png);
background-repeat: no-repeat;
background-position: top center;
background-size: 100%;
} }

+ 64
- 0
react-ui/src/components/ParameterInput/index.less View File

@@ -0,0 +1,64 @@
.parameter-input {
width: 100%;
min-width: 0;
padding: 4px 11px;
border: 1px solid #d9d9d9;
border-radius: 6px;

&:hover {
border-color: @primary-color;
}

&__content {
display: flex;
align-items: center;
width: fit-content;
max-width: 100%;
min-height: 22px;
padding: 0 8px;
color: .addAlpha(@text-color, 0.8) [];
background-color: rgba(0, 0, 0, 0.06);
border-radius: 4px;

&__value {
.singleLine();
margin-right: 8px;
font-size: @font-size-input;
line-height: 1.5714285714285714;
}

&__close-icon {
font-size: 10px;

&:hover {
color: #000;
}
}
}

&__placeholder {
min-height: 22px;
color: rgba(0, 0, 0, 0.25);
font-size: @font-size-input;
line-height: 1.5714285714285714;
}
}

.parameter-input.parameter-input--large {
padding: 10px 11px;
font-size: @font-size-input-lg;

.parameter-input__placeholder {
font-size: @font-size-input-lg;
line-height: 1.5;
}

.parameter-input__content__value {
font-size: @font-size-input-lg;
line-height: 1.5;
}

.parameter-input__content__close-icon {
font-size: 12px;
}
}

+ 106
- 0
react-ui/src/components/ParameterInput/index.tsx View File

@@ -0,0 +1,106 @@
import { CloseOutlined } from '@ant-design/icons';
import { Input } from 'antd';
import classNames from 'classnames';
import './index.less';

type ParameterInputData = {
value?: any;
showValue?: any;
fromSelect?: boolean;
} & Record<string, any>;

interface ParameterInputProps {
value?: ParameterInputData;
onChange?: (value: ParameterInputData) => void;
onClick?: () => void;
canInput?: boolean;
textArea?: boolean;
placeholder?: string;
allowClear?: boolean;
className?: string;
style?: React.CSSProperties;
size?: 'middle' | 'small' | 'large';
disabled?: boolean;
}

function ParameterInput({
value,
onChange,
onClick,
canInput = true,
textArea = false,
placeholder,
allowClear,
className,
style,
size = 'middle',
disabled = false,
...rest
}: ParameterInputProps) {
// console.log('ParameterInput', value);

const valueObj =
typeof value === 'string' ? { value: value, fromSelect: false, showValue: value } : value;
if (valueObj && !valueObj.showValue) {
valueObj.showValue = valueObj.value;
}
const isSelect = valueObj?.fromSelect;
const InputComponent = textArea ? Input.TextArea : Input;

return (
<>
{(isSelect || !canInput) && !disabled ? (
<div
className={classNames(
'parameter-input',
{ 'parameter-input--large': size === 'large' },
className,
)}
style={style}
onClick={onClick}
>
{valueObj?.showValue ? (
<div className="parameter-input__content">
<span className="parameter-input__content__value">{valueObj?.showValue}</span>
<CloseOutlined
className="parameter-input__content__close-icon"
onClick={(e) => {
e.stopPropagation();
onChange?.({
...valueObj,
fromSelect: false,
value: undefined,
showValue: undefined,
});
}}
/>
</div>
) : (
<div className="parameter-input__placeholder">{placeholder}</div>
)}
</div>
) : (
<InputComponent
{...rest}
size={size}
className={className}
style={style}
placeholder={placeholder}
allowClear={allowClear}
value={valueObj?.showValue}
disabled={disabled}
onChange={(e) =>
onChange?.({
...valueObj,
fromSelect: false,
value: e.target.value,
showValue: e.target.value,
})
}
/>
)}
</>
);
}

export default ParameterInput;

+ 1
- 0
react-ui/src/components/RightContent/index.tsx View File

@@ -2,6 +2,7 @@ import { useEmotionCss } from '@ant-design/use-emotion-css';
import { useModel } from '@umijs/max'; import { useModel } from '@umijs/max';
import React from 'react'; import React from 'react';
import Avatar from './AvatarDropdown'; import Avatar from './AvatarDropdown';
// import { SelectLang } from '@umijs/max';


export type SiderTheme = 'light' | 'dark'; export type SiderTheme = 'light' | 'dark';




+ 19
- 1
react-ui/src/enums/index.ts View File

@@ -4,9 +4,27 @@ export enum CommonTabKeys {
Public = 'Public', // 公开 Public = 'Public', // 公开
} }


// 镜像状态
// 镜像版本状态
export enum MirrorVersionStatus { export enum MirrorVersionStatus {
Available = 'available', // 可用 Available = 'available', // 可用
Building = 'building', // 构建中 Building = 'building', // 构建中
Failed = 'failed', // 构建中 Failed = 'failed', // 构建中
} }

// 模型部署状态
export enum ModelDeploymentStatus {
Init = 'Init', // 启动中
Running = 'Running', // 运行中
Stopped = 'Stopped', // 已停止
Failed = 'Failed', // 失败
Pending = 'Pending', // 挂起中
}

export const modelDeploymentStatusOptions = [
{ label: '全部', value: '' },
{ label: '启动中', value: ModelDeploymentStatus.Init },
{ label: '运行中', value: ModelDeploymentStatus.Running },
{ label: '已停止', value: ModelDeploymentStatus.Stopped },
{ label: '失败', value: ModelDeploymentStatus.Failed },
{ label: '挂起中', value: ModelDeploymentStatus.Pending },
];

+ 46
- 0
react-ui/src/hooks/resource.ts View File

@@ -0,0 +1,46 @@
import { getComputingResourceReq } from '@/services/pipeline';
import { ComputingResource } from '@/types';
import { to } from '@/utils/promise';
import { type SelectProps } from 'antd';
import { useCallback, useEffect, useState } from 'react';

// 获取资源规格
export function useComputingResource() {
const [resourceStandardList, setResourceStandardList] = useState<ComputingResource[]>([]);

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

// 获取资源规格列表数据
const getComputingResource = useCallback(async () => {
const params = {
page: 0,
size: 1000,
resource_type: '',
};
const [res] = await to(getComputingResourceReq(params));
if (res && res.data && res.data.content) {
setResourceStandardList(res.data.content);
}
}, []);

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

// 根据 standard 获取 description
const getDescription = useCallback(
(standard: string) => {
return resourceStandardList.find((item) => item.standard === standard)?.description;
},
[resourceStandardList],
);

return [resourceStandardList, filterResourceStandard, getDescription] as const;
}

+ 19
- 0
react-ui/src/hooks/sessionStorage.ts View File

@@ -0,0 +1,19 @@
import { getSessionStorageItem, removeSessionStorageItem } from '@/utils/sessionStorage';
import { useEffect, useState } from 'react';

// 获取缓存数据
export function useSessionStorage<T>(key: string, isObject: boolean, initialValue: T) {
const [storage, setStorage] = useState<T>(initialValue);

useEffect(() => {
const res = getSessionStorageItem(key, isObject);
if (res) {
setStorage(res);
}
return () => {
removeSessionStorageItem(key);
};
}, []);

return [storage];
}

+ 1
- 0
react-ui/src/iconfont/iconfont-menu.js
File diff suppressed because it is too large
View File


+ 1
- 1
react-ui/src/iconfont/iconfont.js
File diff suppressed because it is too large
View File


+ 29
- 13
react-ui/src/overrides.less View File

@@ -57,11 +57,6 @@
overflow-y: auto; overflow-y: auto;
} }


// Input
.ant-input-textarea-affix-wrapper.ant-input-affix-wrapper {
padding: 0;
}

// Modal // Modal
.ant-modal { .ant-modal {
.ant-modal-close { .ant-modal-close {
@@ -81,18 +76,24 @@
} }
} }


.ant-form-item .ant-form-item-label > label {
font-size: @font-size;
}

// 输入框高度为46px
.ant-input-affix-wrapper { .ant-input-affix-wrapper {
height: 46px;
padding: 1px 11px;
padding-top: 2px;
padding-bottom: 2px;

.ant-input {
height: 40px;
}
} }


// 选择框高度为46px
.ant-select-single { .ant-select-single {
height: 46px; height: 46px;
} }

.ant-select-single .ant-select-selector .ant-select-selection-placeholder {
line-height: 46px;
}
} }


// Confirm Modal // Confirm Modal
@@ -128,8 +129,6 @@
border-radius: 10px; border-radius: 10px;
} }
.ant-btn-default { .ant-btn-default {
color: @text-color;
background: rgba(22, 100, 255, 0.06);
border-color: transparent; border-color: transparent;
} }
.ant-btn + .ant-btn { .ant-btn + .ant-btn {
@@ -137,3 +136,20 @@
} }
} }
} }

// 表单类型为large时,font-size为15px
.ant-form-large {
.ant-form-item-label {
label {
font-size: @font-size;
}
}
}

// 取消 hover 颜色变化
.ant-menu .ant-menu-title-content {
transition: color 0s;
a {
transition: color 0s;
}
}

+ 9
- 0
react-ui/src/pages/Dataset/components/AddDatasetModal/index.less View File

@@ -0,0 +1,9 @@
.upload-tip {
margin-top: 5px;
color: @error-color;
}

.upload-button {
height: 46px;
font-size: 15px;
}

+ 199
- 0
react-ui/src/pages/Dataset/components/AddDatasetModal/index.tsx View File

@@ -0,0 +1,199 @@
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 { to } from '@/utils/promise';
import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui';
import {
App,
Button,
Form,
Input,
Radio,
Select,
Upload,
UploadFile,
type ModalProps,
type UploadProps,
} from 'antd';
import { omit } from 'lodash';
import { useEffect, useState } from 'react';
import { CategoryData } from '../../types';
import styles from './index.less';

interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> {
typeList: CategoryData[];
tagList: CategoryData[];
onOk: () => void;
}

function AddDatasetModal({ typeList, tagList, onOk, ...rest }: AddDatasetModalProps) {
const [uuid] = useState(Date.now());
const [clusterOptions, setClusterOptions] = useState<DictValueEnumObj[]>([]);
const { message } = App.useApp();

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

// 上传组件参数
const uploadProps: UploadProps = {
action: '/api/mmp/dataset/upload',
headers: {
Authorization: getAccessToken() || '',
},
defaultFileList: [],
};

// 获取集群版本数据
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));
if (res) {
message.success('创建成功');
onOk?.();
}
};

// 提交
const onFinish = (formData: any) => {
const fileList: UploadFile[] = formData['fileList'] ?? [];
if (validateUploadFiles(fileList)) {
const params = {
...omit(formData, ['fileList']),
dataset_version_vos: fileList.map((item) => {
const data = item.response?.data?.[0] ?? {};
return {
file_name: data.fileName,
file_size: data.fileSize,
url: data.url,
};
}),
};
createDataset(params);
}
};

return (
<KFModal
{...rest}
title="新建数据集"
image={require('@/assets/img/create-experiment.png')}
width={825}
okButtonProps={{
htmlType: 'submit',
form: 'form',
}}
destroyOnClose
>
<Form name="form" layout="vertical" onFinish={onFinish} autoComplete="off">
<Form.Item
label="数据集名称"
name="name"
required
rules={[
{
required: true,
message: '请输入数据集名称',
},
]}
>
<Input placeholder="请输入数据名称" showCount allowClear maxLength={64} />
</Form.Item>
<Form.Item
label="数据集版本"
name="version"
rules={[
{
required: true,
message: '请输入数据集版本',
},
]}
>
<Input placeholder="请输入数据集版本" showCount allowClear maxLength={64} />
</Form.Item>
<Form.Item label="数据集分类" name="data_type">
<Select
allowClear
placeholder="请选择数据集分类"
options={typeList}
fieldNames={{ label: 'name', value: 'id' }}
optionFilterProp="name"
showSearch
/>
</Form.Item>
<Form.Item label="研究方向/应用领域" name="data_tag">
<Select
allowClear
placeholder="请选择研究方向/应用领域"
options={tagList}
fieldNames={{ label: 'name', value: 'id' }}
optionFilterProp="name"
showSearch
/>
</Form.Item>
<Form.Item label="集群版本" name="available_cluster">
<Select allowClear placeholder="请选择集群版本" options={clusterOptions} />
</Form.Item>
<Form.Item
label="数据集简介"
name="description"
rules={[
{
required: true,
message: '请输入数据集简介',
},
]}
>
<Input.TextArea
placeholder="请输入数据集简介"
showCount
maxLength={256}
autoSize={{ minRows: 2, maxRows: 6 }}
allowClear
/>
</Form.Item>
<Form.Item label="选择流水线" name="range">
<Radio.Group>
<Radio value="0">仅自己可见</Radio>
<Radio value="1">工作空间可见</Radio>
</Radio.Group>
</Form.Item>
<Form.Item
label="数据集文件"
name="fileList"
valuePropName="fileList"
getValueFromEvent={getFileListFromEvent}
rules={[
{
required: true,
message: '请上传数据集文件',
},
]}
>
<Upload {...uploadProps} data={{ uuid: uuid }} accept=".zip,.tgz">
<Button
className={styles['upload-button']}
type="default"
icon={<KFIcon type="icon-shangchuan" />}
>
上传文件
</Button>
<div className={styles['upload-tip']}>只允许上传.zip,.tgz格式文件</div>
</Upload>
</Form.Item>
</Form>
</KFModal>
);
}

export default AddDatasetModal;

+ 178
- 0
react-ui/src/pages/Dataset/components/AddModelModal/index.tsx View File

@@ -0,0 +1,178 @@
import { getAccessToken } from '@/access';
import KFIcon from '@/components/KFIcon';
import KFModal from '@/components/KFModal';
import { CategoryData } from '@/pages/Dataset/types';
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,
type ModalProps,
type UploadProps,
} from 'antd';
import { omit } from 'lodash';
import { useState } from 'react';
import styles from '../AddDatasetModal/index.less';

interface AddModelModalProps extends Omit<ModalProps, 'onOk'> {
typeList: CategoryData[];
tagList: CategoryData[];
onOk: () => void;
}

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

// 上传组件参数
const uploadProps: UploadProps = {
action: '/api/mmp/models/upload',
headers: {
Authorization: getAccessToken() || '',
},
defaultFileList: [],
};

// 上传请求
const createModel = async (params: any) => {
const [res] = await to(addModel(params));
if (res) {
message.success('创建成功');
onOk?.();
}
};

// 提交
const onFinish = (formData: any) => {
const fileList: UploadFile[] = formData['fileList'] ?? [];
if (validateUploadFiles(fileList)) {
const params = {
...omit(formData, ['fileList']),
models_version_vos: fileList.map((item) => {
const data = item.response?.data?.[0] ?? {};
return {
file_name: data.fileName,
file_size: data.fileSize,
url: data.url,
};
}),
};
createModel(params);
}
};

return (
<KFModal
{...rest}
title="新建模型"
image={require('@/assets/img/create-experiment.png')}
width={825}
okButtonProps={{
htmlType: 'submit',
form: 'form',
}}
>
<Form name="form" layout="vertical" onFinish={onFinish} autoComplete="off">
<Form.Item
label="模型名称"
name="name"
rules={[
{
required: true,
message: '请输入模型名称!',
},
]}
>
<Input placeholder="请输入模型名称" showCount allowClear maxLength={64} />
</Form.Item>

<Form.Item
label="模型版本"
name="version"
rules={[
{
required: true,
message: '请输入模型版本',
},
]}
>
<Input placeholder="请输入模型版本" allowClear maxLength={64} />
</Form.Item>
<Form.Item
label="模型简介"
name="description"
rules={[
{
required: true,
message: '请输入模型简介',
},
]}
>
<Input.TextArea
placeholder="请输入模型简介"
showCount
maxLength={256}
autoSize={{ minRows: 2, maxRows: 6 }}
allowClear
/>
</Form.Item>
{/* <Form.Item label="可见范围" name="available_range">
<Radio.Group>
<Radio value="0">仅自己可见</Radio>
<Radio value="1">工作空间可见</Radio>
</Radio.Group>
</Form.Item> */}
<Form.Item label="模型框架" name="model_type">
<Select
allowClear
placeholder="请选择模型类型"
options={typeList}
fieldNames={{ label: 'name', value: 'id' }}
optionFilterProp="name"
showSearch
/>
</Form.Item>
<Form.Item label="模型能力" name="model_tag">
<Select
allowClear
placeholder="请选择模型标签"
options={tagList}
fieldNames={{ label: 'name', value: 'id' }}
optionFilterProp="name"
showSearch
/>
</Form.Item>
<Form.Item
label="模型文件"
name="fileList"
valuePropName="fileList"
getValueFromEvent={getFileListFromEvent}
rules={[
{
required: true,
message: '请上传模型文件',
},
]}
>
<Upload {...uploadProps} data={{ uuid: uuid }}>
<Button
className={styles['upload-button']}
type="default"
icon={<KFIcon type="icon-shangchuan" />}
>
上传文件
</Button>
</Upload>
</Form.Item>
</Form>
</KFModal>
);
}

export default AddModelModal;

+ 170
- 0
react-ui/src/pages/Dataset/components/AddVersionModal/index.tsx View File

@@ -0,0 +1,170 @@
import { getAccessToken } from '@/access';
import KFIcon from '@/components/KFIcon';
import KFModal from '@/components/KFModal';
import { ResourceType, resourceConfig } from '@/pages/Dataset/types';
import { to } from '@/utils/promise';
import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui';
import {
App,
Button,
Form,
Input,
Upload,
UploadFile,
type ModalProps,
type UploadProps,
} from 'antd';
import { omit } from 'lodash';
import { useState } from 'react';
import styles from '../AddDatasetModal/index.less';

interface AddVersionModalProps extends Omit<ModalProps, 'onOk'> {
resourceType: ResourceType;
resourceId: number;
initialName: string;
onOk: () => void;
}

function AddVersionModal({
resourceType,
resourceId,
initialName,
onOk,
...rest
}: AddVersionModalProps) {
const [uuid] = useState(Date.now());
const { message } = App.useApp();

// 上传组件参数
const uploadProps: UploadProps = {
action: resourceConfig[resourceType].uploadAction,
headers: {
Authorization: getAccessToken() || '',
},
defaultFileList: [],
};

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

// 提交
const onFinish = (formData: any) => {
const fileList: UploadFile[] = formData['fileList'] ?? [];
if (validateUploadFiles(fileList)) {
const otherParams = omit(formData, ['fileList']);
const params = fileList.map((item) => {
const data = item.response?.data?.[0] ?? {};
return {
...otherParams,
[resourceConfig[resourceType].idParamKey]: resourceId,
file_name: data.fileName,
file_size: data.fileSize,
url: data.url,
};
});
createDatasetVersion(params);
}
};

const name = resourceConfig[resourceType].name;
const accept = resourceConfig[resourceType].uploadAccept;
return (
<KFModal
{...rest}
title="创建新版本"
image={require('@/assets/img/create-experiment.png')}
width={825}
okButtonProps={{
htmlType: 'submit',
form: 'form',
}}
>
<Form
name="form"
layout="vertical"
initialValues={{
name: initialName,
}}
onFinish={onFinish}
autoComplete="off"
>
<Form.Item
label={`${name}名称`}
name="name"
rules={[
{
required: true,
message: `请输入${name}名称`,
},
]}
>
<Input disabled placeholder={`请输入${name}名称`} />
</Form.Item>
<Form.Item
label={`${name}版本`}
name="version"
rules={[
{
required: true,
message: `请输入${name}版本`,
},
]}
>
<Input placeholder={`请输入${name}版本`} maxLength={64} showCount allowClear />
</Form.Item>
<Form.Item
label="版本描述"
name="description"
rules={[
{
required: true,
message: '请输入版本描述',
},
]}
>
<Input.TextArea
placeholder="请输入版本描述"
autoSize={{ minRows: 2, maxRows: 6 }}
maxLength={256}
showCount
allowClear
/>
</Form.Item>
<Form.Item
label={`${name}文件`}
name="fileList"
valuePropName="fileList"
getValueFromEvent={getFileListFromEvent}
rules={[
{
required: true,
message: `请上传${name}文件`,
},
]}
>
<Upload {...uploadProps} data={{ uuid: uuid }} accept={accept}>
<Button
className={styles['upload-button']}
type="default"
icon={<KFIcon type="icon-shangchuan" />}
>
上传文件
</Button>
{resourceType === ResourceType.Dataset && (
<div className={styles['upload-tip']}>只允许上传.zip格式文件</div>
)}
</Upload>
</Form.Item>
</Form>
</KFModal>
);
}

export default AddVersionModal;

+ 41
- 0
react-ui/src/pages/Dataset/components/CategoryItem/index.less View File

@@ -0,0 +1,41 @@
.category-item {
display: flex;
flex-direction: column;
align-items: center;
width: 92px;
height: 62px;
padding: 11px 8px 7px;
color: @text-color;
font-size: 12px;
border: 1px solid rgba(22, 100, 255, 0.05);
border-radius: 4px;
cursor: pointer;

&__icon {
display: block;
}
&__active-icon {
display: none;
}
&__name {
width: 100%;
margin-top: 4px;
overflow: hidden;
white-space: nowrap;
text-align: center;
text-overflow: ellipsis;
}

&:hover,
&--active {
background: rgba(22, 100, 255, 0.03);
border: 1px solid @primary-color;

.category-item__icon {
display: none;
}
.category-item__active-icon {
display: block;
}
}
}

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

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

type CategoryItemProps = {
resourceType: ResourceType;
item: CategoryData;
isSelected: boolean;
onClick: (item: CategoryData) => void;
};

function CategoryItem({ resourceType, item, isSelected, onClick }: CategoryItemProps) {
return (
<div
className={classNames(styles['category-item'], {
[styles['category-item--active']]: isSelected,
})}
onClick={() => onClick(item)}
>
<img
className={styles['category-item__icon']}
style={{ width: '22px' }}
src={`/assets/images/${resourceConfig[resourceType].iconPathPrefix}/${item.path}.png`}
alt=""
/>
<img
className={styles['category-item__active-icon']}
style={{ width: '22px' }}
src={`/assets/images/${resourceConfig[resourceType].iconPathPrefix}/${item.path}-hover.png`}
alt=""
/>
<span className={styles['category-item__name']}>{item.name}</span>
</div>
);
}

export default CategoryItem;

+ 22
- 0
react-ui/src/pages/Dataset/components/CategoryList/index.less View File

@@ -0,0 +1,22 @@
.category-list {
width: 340px;
height: 100%;
margin-right: 10px;
padding: 15px 0;
background: white;
border-radius: 4px;
box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09);

&__content {
height: calc(100% - 32px - 15px);
margin-top: 15px;
padding-left: 20px;
overflow-y: auto;

&__title {
margin-bottom: 15px;
color: @text-color;
font-size: 14px;
}
}
}

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

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

export type CategoryValue = {
dataType: number | undefined;
dataTag: number | undefined;
};

type CategoryProps = {
resourceType: ResourceType; // 资源类型,数据集还是模型
typeList: CategoryData[];
tagList: CategoryData[];
activeType?: number;
activeTag?: number;
onTypeSelect: (value: CategoryData) => void;
onTagSelect: (value: CategoryData) => void;
onSearch: (value: string) => void;
};

function CategoryList({
resourceType,
typeList,
tagList,
activeType,
activeTag,
onTypeSelect,
onTagSelect,
onSearch,
}: CategoryProps) {
return (
<div className={styles['category-list']}>
<div style={{ padding: '0 20px' }}>
<Input.Search placeholder="搜索" allowClear onSearch={onSearch} />
</div>
<div className={styles['category-list__content']}>
<div className={styles['category-list__content__title']}>
{resourceConfig[resourceType].typeTitle}
</div>
<Flex wrap="wrap" gap="20px 12px">
{typeList?.map((item) => (
<CategoryItem
key={item.id}
resourceType={resourceType}
item={item}
onClick={onTypeSelect}
isSelected={item.id === activeType}
></CategoryItem>
))}
</Flex>
<div className={styles['category-list__content__title']} style={{ marginTop: '25px' }}>
{resourceConfig[resourceType].tagTitle}
</div>
<Flex wrap="wrap" gap="20px 12px">
{tagList?.map((item) => (
<CategoryItem
key={item.id}
resourceType={resourceType}
item={item}
onClick={onTagSelect}
isSelected={item.id === activeTag}
></CategoryItem>
))}
</Flex>
</div>
</div>
);
}

export default CategoryList;

+ 39
- 0
react-ui/src/pages/Dataset/components/ResourceList/index.less View File

@@ -0,0 +1,39 @@
.resource-list {
display: flex;
flex: 1;
flex-direction: column;
height: 100%;
padding: 20px 0;
background: white;
box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09);

&__header {
display: flex;
align-items: center;
justify-content: space-between;
height: 32px;
margin-bottom: 30px;
padding: 0 30px;
color: @text-color;
font-size: 15px;
}

&__content {
display: flex;
flex: 1;
flex-wrap: wrap;
gap: 20px;
align-content: flex-start;
width: 100%;
margin-bottom: 30px;
padding: 0 30px;
overflow-y: auto;
}

:global {
.ant-pagination {
margin-right: 30px;
text-align: right;
}
}
}

+ 211
- 0
react-ui/src/pages/Dataset/components/ResourceList/index.tsx View File

@@ -0,0 +1,211 @@
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 { 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 AddDatasetModal from '../AddDatasetModal';
import ResourceItem from '../Resourcetem';
import styles from './index.less';

export type ResourceListRef = {
reset: () => void;
};

type ResourceListProps = {
resourceType: ResourceType;
dataType?: number;
dataTag?: number;
isPublic: boolean;
typeList: CategoryData[];
tagList: CategoryData[];
initialSearchText?: string;
initialPagination?: PaginationProps;
setCacheState: (state?: any) => void;
};

function ResourceList(
{
resourceType,
dataType,
dataTag,
typeList,
tagList,
isPublic,
initialSearchText,
initialPagination,
setCacheState,
}: ResourceListProps,
ref: Ref<ResourceListRef>,
) {
const navigate = useNavigate();
const [dataList, setDataList] = useState<ResourceData[]>([]);
const [total, setTotal] = useState(0);
const [pagination, setPagination] = useState<PaginationProps>(
initialPagination ?? {
current: 1,
pageSize: 20,
},
);
const [searchText, setSearchText] = useState(initialSearchText);
const [inputText, setInputText] = useState(initialSearchText);
const { message } = App.useApp();

useEffect(() => {
getDataList();
}, [resourceType, dataType, dataTag, pagination, searchText, isPublic]);

useImperativeHandle(
ref,
() => {
return {
reset: () => {
setPagination({
current: 1,
pageSize: 20,
});
setSearchText('');
setInputText('');
setDataList([]);
},
};
},
[],
);

// 获取数据请求
const getDataList = async () => {
const params = {
page: pagination.current! - 1,
size: pagination.pageSize,
[resourceConfig[resourceType].typeParamKey]: dataType,
[resourceConfig[resourceType].tagParamKey]: dataTag,
available_range: isPublic ? 1 : 0,
name: searchText !== '' ? searchText : undefined,
};
const request = resourceConfig[resourceType].getList;
const [res] = await to(request(params));
if (res && res.data && res.data.content) {
setDataList(res.data.content);
setTotal(res.data.totalElements);
}
};

// 删除请求
const deleteRecord = async (id: number) => {
const request = resourceConfig[resourceType].deleteRecord;
const [res] = await to(request(id));
if (res) {
getDataList();
message.success('删除成功');
}
};

// 搜索
const handleSearch = (value: string) => {
setSearchText(value);
};

// 删除
const handleRemove = (record: ResourceData) => {
modalConfirm({
title: resourceConfig[resourceType].deleteModalTitle,
onOk: () => {
deleteRecord(record.id);
},
});
};

// 跳转
const handleClick = (record: ResourceData) => {
setCacheState({
activeTab: isPublic ? CommonTabKeys.Public : CommonTabKeys.Private,
pagination,
searchText,
activeType: dataType,
activeTag: dataTag,
});
if (resourceType === ResourceType.Dataset) {
navigate(`/dataset/dataset/${record.id}?isPublic=${isPublic}`);
} else {
navigate(`/dataset/model/${record.id}?isPublic=${isPublic}`);
}
};

// 分页切换
const handlePageChange: PaginationProps['onChange'] = (page, pageSize) => {
setPagination({
current: page,
pageSize: pageSize,
});
};

// 新建弹框
const showModal = () => {
const Modal = resourceType === ResourceType.Dataset ? AddDatasetModal : AddModelModal;
const { close } = openAntdModal(Modal, {
typeList: typeList,
tagList: tagList,
onOk: () => {
getDataList();
close();
},
});
};

return (
<div className={styles['resource-list']}>
<div className={styles['resource-list__header']}>
<span>数据总数:{total}个</span>
<div>
<Input.Search
placeholder="按数据名称筛选"
allowClear
onSearch={handleSearch}
style={{
width: 300,
}}
onChange={(e) => setInputText(e.target.value)}
value={inputText}
/>
{!isPublic && (
<Button
type="default"
style={{ marginLeft: '20px' }}
onClick={showModal}
icon={<KFIcon type="icon-xinjian2" />}
>
{resourceConfig[resourceType].addBtnTitle}
</Button>
)}
</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}
/>
</div>
);
}

export default forwardRef(ResourceList);

+ 11
- 0
react-ui/src/pages/Dataset/components/ResourcePage/index.less View File

@@ -0,0 +1,11 @@
.resource-page {
height: 100%;
&__tabs-container {
height: 50px;
padding-left: 27px;
background-image: url(@/assets/img/page-title-bg.png);
background-repeat: no-repeat;
background-position: top center;
background-size: 100% 100%;
}
}

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

@@ -0,0 +1,113 @@
import { CommonTabKeys } from '@/enums';
import { useCacheState } from '@/hooks/pageCacheState';
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 CategoryList from '../CategoryList';
import ResourceList, { ResourceListRef } from '../ResourceList';
import styles from './index.less';

type ResourcePageProps = {
resourceType: ResourceType; // 资源类型,数据集还是模型
};

function ResourcePage({ resourceType }: ResourcePageProps) {
const [cacheState, setCacheState] = useCacheState();
const [activeTab, setActiveTab] = useState<string>(cacheState?.activeTab ?? CommonTabKeys.Public);
const [typeList, setTypeList] = useState<CategoryData[]>([]);
const [tagList, setTagList] = useState<CategoryData[]>([]);
const [activeType, setActiveType] = useState<number | undefined>(cacheState?.activeType);
const [activeTag, setActiveTag] = useState<number | undefined>(cacheState?.activeTag);
const dataListRef = useRef<ResourceListRef>(null);

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

// 分类搜索
const handleCategorySearch = (value: string) => {
getAssetIconList(value);
};

// 选择类型
const chooseType = (record: CategoryData) => {
setActiveType((prev) => (prev === record.id ? undefined : record.id));
};

// 选择 Tag
const chooseTag = (record: CategoryData) => {
setActiveTag((prev) => (prev === record.id ? undefined : record.id));
};

// 获取分类
const getAssetIconList = async (name: string = '') => {
const params = {
name: name,
page: 0,
size: 10000,
};
const [res] = await to(getAssetIcon(params));
if (res && res.data && res.data.content) {
const { content } = res.data;
setTypeList(
content.filter(
(item: CategoryData) =>
Number(item.category_id) === resourceConfig[resourceType].typeValue,
),
);
setTagList(
content.filter(
(item: CategoryData) =>
Number(item.category_id) === resourceConfig[resourceType].tagValue,
),
);
}
};

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

const isPublic = activeTab === CommonTabKeys.Public;
return (
<div className={styles['resource-page']}>
<div className={styles['resource-page__tabs-container']}>
<Tabs
activeKey={activeTab}
items={resourceConfig[resourceType].tabItems}
onChange={hanleTabChange}
/>
</div>
<Flex style={{ marginTop: '10px', height: 'calc(100% - 59px)' }}>
<CategoryList
resourceType={resourceType}
typeList={typeList}
tagList={tagList}
activeType={activeType}
activeTag={activeTag}
onTypeSelect={chooseType}
onTagSelect={chooseTag}
onSearch={handleCategorySearch}
/>
<ResourceList
ref={dataListRef}
resourceType={resourceType}
typeList={typeList}
tagList={tagList}
isPublic={isPublic}
dataType={activeType}
dataTag={activeTag}
initialSearchText={cacheState?.searchText}
initialPagination={cacheState?.initialPagination}
setCacheState={setCacheState}
></ResourceList>
</Flex>
</div>
);
}

export default ResourcePage;

+ 61
- 0
react-ui/src/pages/Dataset/components/Resourcetem/index.less View File

@@ -0,0 +1,61 @@
.resource-item {
position: relative;
width: calc(25% - 15px);
padding: 20px;
background: white;
border: 1px solid #eaeaea;
border-radius: 4px;
cursor: pointer;

@media screen and (max-width: 1860px) {
& {
width: calc(33.33% - 13.33px);
}
}

&__name {
position: relative;
display: inline-block;
height: 24px;
margin: 0 10px 0 0 !important;
color: @text-color;
font-size: 16px;
}

&__description {
height: 44px;
margin-bottom: 20px;
color: @text-color-secondary;
font-size: 14px;
.multiLine(2);
}
&__time {
display: flex;
flex: 0 1 content;
align-items: center;
width: 100%;
color: #808080;
font-size: 13px;
}

&:hover {
border-color: @primary-color;
box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1);

.resource-item__name {
color: @primary-color;
}
}
}

.resource-item__name {
&::after {
position: absolute;
top: 14px;
left: 0;
width: 100%;
height: 6px;
background: linear-gradient(to right, rgba(22, 100, 255, 0.3) 0, rgba(22, 100, 255, 0) 100%);
content: '';
}
}

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

@@ -0,0 +1,54 @@
import clock from '@/assets/img/clock.png';
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 styles from './index.less';

type ResourceItemProps = {
item: ResourceData;
isPublic: boolean;
onRemove: (item: ResourceData) => void;
onClick: (item: ResourceData) => void;
};

function ResourceItem({ item, isPublic, onClick, onRemove }: ResourceItemProps) {
return (
<div className={styles['resource-item']} onClick={() => onClick(item)}>
<Flex justify="space-between" align="center" style={{ marginBottom: '20px', height: '32px' }}>
<Typography.Paragraph
className={styles['resource-item__name']}
ellipsis={{ tooltip: item.name }}
>
{item.name}
</Typography.Paragraph>
{!isPublic && (
<Button
type="text"
shape="circle"
onClick={(e) => {
e.stopPropagation();
onRemove(item);
}}
>
<KFIcon type="icon-shanchu" font={17} />
</Button>
)}
</Flex>
<div className={styles['resource-item__description']}>{item.description}</div>
<Flex justify="space-between">
<div className={styles['resource-item__time']}>
<img style={{ width: '17px', marginRight: '6px' }} src={creatByImg} alt="" />
<span>{item.create_by}</span>
</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>
</div>
</Flex>
</div>
);
}

export default ResourceItem;

+ 5
- 88
react-ui/src/pages/Dataset/index.jsx View File

@@ -1,90 +1,7 @@
import { getDatasetList } from '@/services/dataset/index.js';
import { Form, Input, Tabs } from 'antd';
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import Styles from './index.less';
import PersonalData from './personalData';
import PublicData from './publicData';
const { Search } = Input;
const { TabPane } = Tabs;
const leftdataList = [1, 2, 3];
import ResourcePage from './components/ResourcePage';
import { ResourceType } from './types';


const Dataset = () => {
const [queryFlow, setQueryFlow] = useState({
page: 0,
size: 10,
name: null,
});
const navgite = useNavigate();
const [isModalOpen, setIsModalOpen] = useState(false);
const [datasetList, setDatasetList] = useState([]);
const [total, setTotal] = useState(0);
const [form] = Form.useForm();
const [dialogTitle, setDialogTitle] = useState('新建数据');
const getDatasetlist = () => {
getDatasetList(queryFlow).then((ret) => {
console.log(ret);
if (ret.code == 200) {
setDatasetList(ret.data.content);
setTotal(ret.data.totalElements);
}
});
};

const showModal = () => {
form.resetFields();
setDialogTitle('新建数据集');
setIsModalOpen(true);
};
const handleOk = () => {
console.log(1111);
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
const onFinish = (values) => {};
const routeToIntro = (e, record) => {
e.stopPropagation();
navgite({ pathname: '/dataset/dataset' });
};
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
};
useEffect(() => {
//getDatasetlist();
return () => {};
}, []);
return (
<div className={Styles.datasetBox}>
<div className={Styles.datasetTopBox}></div>
<div className={Styles.datasetAllBox}>
<Tabs defaultActiveKey="1">
<TabPane
tab="数据广场"
key="1"
icon={
<svg className="icon" style={{ width: '14px', height: '14px' }} aria-hidden="true">
<use xlinkHref="#icon-shujujiguangchang"></use>
</svg>
}
>
<PublicData />
</TabPane>
<TabPane
tab="个人数据"
key="2"
icon={
<svg className="icon" style={{ width: '14px', height: '14px' }} aria-hidden="true">
<use xlinkHref="#icon-gerenshujuji"></use>
</svg>
}
>
<PersonalData />
</TabPane>
</Tabs>
</div>
</div>
);
const DatasetPage = () => {
return <ResourcePage resourceType={ResourceType.Dataset} />;
}; };
export default Dataset;
export default DatasetPage;

+ 0
- 337
react-ui/src/pages/Dataset/index.less View File

@@ -1,337 +0,0 @@
.datasetTopBox {
display: flex;
align-items: center;
width: 100%;
height: 49px;
padding: 0 30px;
padding-right: 30px;
font-family: 'Alibaba';
background-image: url(/assets/images/pipeline-back.png);
background-size: 100% 100%;
}
.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-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 {
font-family: 'Alibaba';
background: #f9fafb;

:global {
.ant-tabs-top > .ant-tabs-nav {
margin: 0;
}
.ant-pagination {
text-align: right;
}
}
}
.datasetAllBox {
:global {
.ant-tabs-nav .ant-tabs-nav-wrap {
margin: -48px 0 0 30px;
}
}
}
.plusButton {
margin: 0 18px 0 20px;
}

.datasetCneterBox {
display: flex;
justify-content: space-between;
width: 100%;
height: 87.5vh;
:global {
.ant-btn {
color: #1d1d20;
font-size: 14px;
}
}
.datasetCneterLeftBox {
width: 340px;
height: 100%;
margin-right: 10px;
padding-top: 15px;
font-family: 'Alibaba';
background: #ffffff;
box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09);
.custTab {
display: flex;
height: 32px;
border-bottom: 1px solid #e0eaff;
.tabItem {
width: 52px;
height: 100%;
color: #808080;
font-size: 15px;
text-align: center;
cursor: pointer;
}
}
.leftContentBox {
max-height: 80vh;
padding: 15px 20px;
overflow-x: hidden;
overflow-y: auto;
font-family: 'Alibaba';
.itemTitle {
margin-bottom: 15px;
color: #1d1d20;
font-size: 14px;
}
.itemBox {
display: flex;
flex-wrap: wrap;
align-content: start;
width: 110%;
.messageBox {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
width: 92px;
height: 62px;
margin: 0 12px 20px 0;
padding: 11px 0px 7px 0px;
color: #1d1d20;
font-size: 12px;
border: 1px solid;
border-color: rgba(22, 100, 255, 0.05);
border-radius: 4px;
cursor: pointer;
.ptIcon {
display: block;
}
.hoverIcon {
display: none;
}
.messageText {
width: 65px;
overflow: hidden;
white-space: nowrap;
text-align: center;
text-overflow: ellipsis;
}
}
.messageBox:hover {
background: rgba(22, 100, 255, 0.03);
border: 1px solid;
border-color: #1664ff;
.ptIcon {
display: none;
}
.hoverIcon {
display: block;
}
}
.active {
background: rgba(22, 100, 255, 0.03) !important;
border: 1px solid !important;
border-color: #1664ff !important;
.ptIcon {
display: none !important;
}
.hoverIcon {
display: block !important;
}
}
}
}
}

.datasetCneterRightBox {
display: flex;
flex: 1;
flex-direction: column;
height: 100%;
overflow-y: auto;
padding: 22px 30px 26px 30px;
background: #ffffff;
box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09);
.dataSource {
display: flex;
align-items: center;
justify-content: space-between;
height: 32px;
margin-bottom: 30px;
color: rgba(29, 29, 32, 0.8);
font-size: 15px;
}
.dataContent {
display: flex;
flex: 1;
flex-wrap: wrap;
align-content: flex-start;

width: 100%;
.dataItem {
position: relative;
width: 23.8%;
height:164px;
margin: 0 20px 25px 0;
background: #ffffff;
border: 1px solid;
border-color: #eaeaea;
border-radius: 4px;
cursor: pointer;
.dropdown{
position: absolute;
right: 20px;
top: 15px;
}
.itemText {
position: absolute;
top: 20px;
left: 20px;
height: 6px;
color: #1d1d20;
font-size: 16px;
font-family: 'Alibaba';
line-height: 0px;
background: linear-gradient(
to right,
rgba(22, 100, 255, 0.3) 0,
rgba(22, 100, 255, 0) 100%
);
}
.itemDescripition {
position: absolute;
top: 57px;
left: 20px;
display: -webkit-box;
padding-right: 28px;
overflow: hidden;
color: #575757;
font-size: 14px;
word-break: break-all;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.itemTime {
position: absolute;
bottom: 22px;
left: 20px;
display: flex;
align-items: center;
color: #808080;
font-size: 13px;
}
.itemIcon {
position: absolute;
right: 20px;
bottom: 22px;
display: flex;
align-items: center;
color: #808080;
font-size: 13px;
}
}
.dataItem:hover {
border-color: #1664ff;
box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1);
}
.dataItem:hover .itemText {
color: #1664ff;
}
}
}
}
.tipContent{
color: #c73131;
margin-top: 5px;
}
.modal {
:global {
.ant-modal-content {
width: 825px;
padding: 20px 67px;
background-image: url(/assets/images/modal-back.png);
background-repeat: no-repeat;
background-position: top center;
background-size: 100%;
border-radius: 21px;
}
.ant-modal-header {
margin: 20px 0;
background-color: transparent;
}
.ant-input {
height: 40px;
border-color: #e6e6e6;
}
.ant-form-item .ant-form-item-label > label {
color: rgba(29, 29, 32, 0.8);
}
.ant-modal-footer {
display: flex;
justify-content: center;
margin: 40px 0 30px 0;
}
.ant-btn {
width: 110px;
height: 40px;
font-size: 18px;
background: rgba(22, 100, 255, 0.06);
border-color: transparent;
border-radius: 10px;
}
.ant-btn-primary {
background: #1664ff;
}
}
}

react-ui/src/pages/Dataset/datasetIntro.jsx → react-ui/src/pages/Dataset/intro.jsx View File

@@ -1,61 +1,35 @@
import { getAccessToken } from '@/access';
import KFIcon from '@/components/KFIcon'; import KFIcon from '@/components/KFIcon';
import { ResourceType } from '@/pages/Dataset/types';
import { import {
addDatasetVersionDetail,
deleteDatasetVersion, deleteDatasetVersion,
getDatasetById, getDatasetById,
getDatasetVersionIdList, getDatasetVersionIdList,
getDatasetVersionsById, getDatasetVersionsById,
} from '@/services/dataset/index.js'; } from '@/services/dataset/index.js';
import { formatDate } from '@/utils/date';
import { downLoadZip } from '@/utils/downloadfile'; import { downLoadZip } from '@/utils/downloadfile';
import { UploadOutlined } from '@ant-design/icons';
import { Button, Form, Input, Modal, Select, Table, Tabs, Upload, message } from 'antd';
import moment from 'moment';
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 { useEffect, useRef, useState } from 'react';
import { useParams } from 'react-router-dom';
import Styles from './index.less';
import AddVersionModal from './components/AddVersionModal';
import Styles from './intro.less';
const { Search } = Input; const { Search } = Input;
const { TabPane } = Tabs; const { TabPane } = Tabs;


const Dataset = () => { const Dataset = () => {
const props = {
action: '/api/mmp/dataset/upload',
// headers: {
// 'X-Requested-With': null
// },
headers: {
Authorization: getAccessToken(),
'X-Requested-With': null,
},
onChange({ file, fileList }) {
if (file.status !== 'uploading') {
console.log(file, fileList);
setFormList(
fileList.map((item) => {
return {
...form.getFieldsValue(),
dataset_id: locationParams.id,
file_name: item.response.code === 200 ? item.response.data[0].fileName : null,
file_size: item.response.code === 200 ? item.response.data[0].fileSize : null,
url: item.response.code === 200 ? item.response.data[0].url : null,
};
}),
);
}
},
defaultFileList: [],
};
const [form] = Form.useForm();
const { message } = App.useApp();
const [formList, setFormList] = useState([]); const [formList, setFormList] = useState([]);
const [dialogTitle, setDialogTitle] = useState('新建版本');
const [isModalOpen, setIsModalOpen] = useState(false);
const [datasetDetailObj, setDatasetDetailObj] = useState({}); const [datasetDetailObj, setDatasetDetailObj] = useState({});
const [version, setVersion] = useState(null); const [version, setVersion] = useState(null);
const [versionList, setVersionList] = useState([]); const [versionList, setVersionList] = useState([]);
const locationParams = useParams(); //新版本获取路由参数接口 const locationParams = useParams(); //新版本获取路由参数接口
const [searchParams] = useSearchParams();
const [wordList, setWordList] = useState([]); const [wordList, setWordList] = useState([]);
const [activeTabKey, setActiveTabKey] = useState('1'); const [activeTabKey, setActiveTabKey] = useState('1');
const [uuid, setUuid] = useState(Date.now());
const isPublic = searchParams.get('isPublic') === 'true';

const getDatasetByDetail = () => { const getDatasetByDetail = () => {
getDatasetById(locationParams.id).then((ret) => { getDatasetById(locationParams.id).then((ret) => {
console.log(ret); console.log(ret);
@@ -77,6 +51,9 @@ const Dataset = () => {
); );
setVersion(ret.data[0]); setVersion(ret.data[0]);
getDatasetVersions({ version: ret.data[0], dataset_id: locationParams.id }); getDatasetVersions({ version: ret.data[0], dataset_id: locationParams.id });
} else {
setVersion(null);
setWordList([]);
} }
}); });
}; };
@@ -86,37 +63,21 @@ const Dataset = () => {
return () => {}; return () => {};
}, []); }, []);
const showModal = () => { const showModal = () => {
form.resetFields();
form.setFieldsValue({ name: datasetDetailObj.name });

setDialogTitle('创建新版本');
setUuid(Date.now());
setIsModalOpen(true);
};
const handleCancel = () => {
setIsModalOpen(false);
};
const handleExport = async () => {
const hide = message.loading('正在下载');
hide();
downLoadZip(`/api/mmp/dataset/downloadAllFiles`, { dataset_id: locationParams.id, version });
const { close } = openAntdModal(AddVersionModal, {
resourceType: ResourceType.Dataset,
resourceId: locationParams.id,
initialName: datasetDetailObj.name,
onOk: () => {
getDatasetVersionList();
close();
},
});
}; };
const deleteDataset = () => {
Modal.confirm({
title: (
<div>
<img
src="/assets/images/delete-icon.png"
style={{ width: '120px', marginBottom: '24px' }}
alt=""
/>
<div style={{ color: '#1d1d20', fontSize: '16px' }}>删除后,该数据集版本将不可恢复</div>
</div>
),
content: <div style={{ color: '#1d1d20', fontSize: '15px' }}>是否确认删除?</div>,
okText: '确认',
cancelText: '取消',


const deleteDataset = () => {
modalConfirm({
title: '删除后,该数据集版本将不可恢复',
content: '是否确认删除?',
onOk: () => { onOk: () => {
deleteDatasetVersion({ dataset_id: locationParams.id, version }).then((ret) => { deleteDatasetVersion({ dataset_id: locationParams.id, version }).then((ret) => {
getDatasetVersionList(); getDatasetVersionList();
@@ -125,19 +86,26 @@ const Dataset = () => {
}, },
}); });
}; };
const onFinish = (values) => {
addDatasetVersionDetail(formList).then((ret) => {
getDatasetVersionList();
setIsModalOpen(false);
message.success('创建成功');
});
};
// 获取版本下的文件列表 // 获取版本下的文件列表
const getDatasetVersions = (params) => { const getDatasetVersions = (params) => {
getDatasetVersionIdList(params).then((res) => { getDatasetVersionIdList(params).then((res) => {
setWordList(res?.data?.content ?? []); 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) => { const handleChange = (value) => {
console.log(value); console.log(value);
if (value) { if (value) {
@@ -147,15 +115,7 @@ const Dataset = () => {
setVersion(null); setVersion(null);
} }
}; };
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
};
const downloadAlone = (e, record) => {
console.log(record);
const hide = message.loading('正在下载');
hide();
downLoadZip(`/api/mmp/dataset/download/${record.id}`);
};

const columns = [ const columns = [
{ {
title: '序号', title: '序号',
@@ -187,7 +147,7 @@ const Dataset = () => {
title: '更新时间', title: '更新时间',
dataIndex: 'update_time', dataIndex: 'update_time',
key: 'update_time', key: 'update_time',
render: (text) => <span>{moment(text).format('YYYY-MM-DD HH:mm:ss')}</span>,
render: (text) => <span>{formatDate(text)}</span>,
}, },
{ {
title: '操作', title: '操作',
@@ -264,15 +224,17 @@ const Dataset = () => {
<div <div
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}
> >
<Button
type="default"
className={Styles.plusButton}
style={{ margin: '0 20px 0 0' }}
onClick={deleteDataset}
icon={<KFIcon type="icon-shanchu" />}
>
删除
</Button>
{!isPublic && (
<Button
type="default"
className={Styles.plusButton}
style={{ margin: '0 20px 0 0' }}
onClick={deleteDataset}
icon={<KFIcon type="icon-shanchu" />}
>
删除
</Button>
)}
<Button <Button
type="default" type="default"
disabled={!version} disabled={!version}
@@ -295,105 +257,6 @@ const Dataset = () => {
</TabPane> </TabPane>
</Tabs> </Tabs>
</div> </div>
<Modal
title={
<div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}>
<img
style={{ width: '20px', marginRight: '10px' }}
src={`/assets/images/pipeline-edit-icon.png`}
alt=""
/>
{dialogTitle}
</div>
}
open={isModalOpen}
className={Styles.modal}
okButtonProps={{
htmlType: 'submit',
form: 'form',
}}
onCancel={handleCancel}
>
<Form
name="form"
form={form}
layout="vertical"
initialValues={{
remember: true,
}}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
>
<Form.Item
label="数据集名称"
name="name"
rules={[
{
required: true,
message: '请输入数据集名称',
},
]}
>
<Input disabled placeholder="请输入数据集名称" />
</Form.Item>
<Form.Item
label="数据集版本"
name="version"
rules={[
{
required: true,
message: '请输入数据集版本',
},
]}
>
<Input placeholder="请输入数据集版本" maxLength={64} showCount allowClear />
</Form.Item>
<Form.Item
label="版本描述"
name="description"
rules={[
{
required: true,
message: '请输入版本描述',
},
]}
>
<Input.TextArea
placeholder="请输入版本描述"
autoSize={{ minRows: 2, maxRows: 6 }}
maxLength={256}
showCount
allowClear
/>
</Form.Item>
<Form.Item
label="数据集文件"
name="dataset_version_vos"
rules={[
{
required: true,
message: '请上传数据集文件',
},
]}
>
<Upload {...props} data={{ uuid: uuid }} accept=".zip,.tgz">
<Button
style={{
fontSize: '14px',
border: '1px solid',
borderColor: '#1664ff',
background: '#fff',
}}
icon={<UploadOutlined style={{ color: '#1664ff' }} />}
>
上传文件
</Button>
<div className={Styles.tipContent}>只允许上传.zip,.tgz格式文件</div>
</Upload>
</Form.Item>
</Form>
</Modal>
</div> </div>
); );
}; };

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

@@ -0,0 +1,82 @@
.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;
}

+ 0
- 479
react-ui/src/pages/Dataset/personalData.jsx View File

@@ -1,479 +0,0 @@
import { getAccessToken } from '@/access';
import clock from '@/assets/img/clock.png';
import creatByImg from '@/assets/img/creatBy.png';
import deleteIcon from '@/assets/img/delete-icon.png';
import KFIcon from '@/components/KFIcon';
import {
addDatesetAndVesion,
deleteDataset,
getAssetIcon,
getDatasetList,
} from '@/services/dataset/index.js';
import { getDictSelectOption } from '@/services/system/dict';
import { modalConfirm } from '@/utils/ui';
import { UploadOutlined } from '@ant-design/icons';
import { Button, Form, Input, Modal, Pagination, Radio, Select, Upload } from 'antd';
import moment from 'moment';
import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';

import './index.less';
import Styles from './index.less';
const { Search } = Input;
const leftdataList = [1, 2, 3];

const PublicData = (React.FC = () => {
const props = {
action: '/api/mmp/dataset/upload',
// headers: {
// 'X-Requested-With': null
// },
headers: {
Authorization: getAccessToken(),
'X-Requested-With': null,
},
onChange({ file, fileList }) {
if (file.status !== 'uploading') {
console.log(file, fileList);
form.setFieldsValue({
dataset_version_vos: fileList.map((item) => {
const data = item.response.data[0];
return {
file_name: data.fileName,
file_size: data.fileSize,
url: data.url,
};
}),
});
}
},
defaultFileList: [],
};
const [queryFlow, setQueryFlow] = useState({
page: 0,
size: 20,
name: null,
available_range: 0,
});
const [iconParams, setIconParams] = useState({
name: null,
page: 0,
size: 10000,
});
const [activeType, setActiveType] = useState(null);
const [activeTag, setActiveTag] = useState(null);
const [datasetTypeList, setDatasetTypeList] = useState([]);
const [datasetDirectionList, setDatasetDirectionList] = useState([]);
const navgite = useNavigate();
const [clusterOptions, setClusterOptions] = useState([]);
const [isModalOpen, setIsModalOpen] = useState(false);
const [datasetList, setDatasetList] = useState([]);
const [total, setTotal] = useState(0);
const [form] = Form.useForm();
const [dialogTitle, setDialogTitle] = useState('新建数据');
const [uuid, setUuid] = useState(Date.now());
const getDatasetlist = (queryFlow) => {
getDatasetList(queryFlow).then((ret) => {
console.log(ret);
if (ret.code == 200) {
setDatasetList(ret.data.content);
setTotal(ret.data.totalElements);
}
});
};

const showModal = () => {
form.resetFields();
setDialogTitle('新建数据集');
setUuid(Date.now());
setIsModalOpen(true);
};
const getAssetIconList = (params) => {
getAssetIcon(params).then((ret) => {
console.log(ret);
if (ret.code == 200 && ret.data.content && ret.data.content.length > 0) {
setDatasetTypeList(ret.data.content.filter((item) => item.category_id == 1));
setDatasetDirectionList(ret.data.content.filter((item) => item.category_id == 2));
} else {
setDatasetTypeList([]);
setDatasetDirectionList([]);
}
});
};
const onSearch = (values) => {
console.log(values);
getAssetIconList({ ...iconParams, name: values });
};
const nameSearch = (values) => {
console.log(values);
getDatasetlist({ ...queryFlow, name: values });
};
const handleOk = () => {
console.log(1111);
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
const chooseDatasetType = (val, item) => {
console.log(val, item);
if (item.id == queryFlow.data_type) {
setActiveType('');
setQueryFlow({ ...queryFlow, data_type: null });
getDatasetlist({ ...queryFlow, data_type: null });
} else {
setActiveType(item.id);
setQueryFlow({ ...queryFlow, data_type: item.id });
getDatasetlist({ ...queryFlow, data_type: item.id });
}
// setQueryFlow({...queryFlow,data_type:item.path},()=>{
// getDatasetlist()
// })
};
const chooseDatasetTag = (val, item) => {
console.log(val, item);
if (item.id == queryFlow.data_tag) {
setActiveTag('');
setQueryFlow({ ...queryFlow, data_tag: null });
getDatasetlist({ ...queryFlow, data_tag: null });
} else {
setActiveTag(item.id);
setQueryFlow({ ...queryFlow, data_tag: item.id });
getDatasetlist({ ...queryFlow, data_tag: item.id });
}
// setQueryFlow({...queryFlow,data_type:item.path},()=>{
// getDatasetlist()
// })
};
const onFinish = (values) => {
addDatesetAndVesion(values).then((ret) => {
console.log(ret);
setIsModalOpen(false);
getDatasetlist(queryFlow);
});
};
const routeToIntro = (e, record) => {
e.stopPropagation();
console.log(record);
navgite({ pathname: `/dataset/dataset/${record.id}` });
};
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
};
const onPageChange = (pageNum, pageSize) => {
console.log(pageNum, pageSize);
setQueryFlow({ ...queryFlow, page: pageNum - 1, size: pageSize });
getDatasetlist({ ...queryFlow, page: pageNum - 1, size: pageSize });
};
useEffect(() => {
getDictSelectOption('available_cluster').then((data) => {
setClusterOptions(data);
});
getAssetIconList(iconParams);
getDatasetlist(queryFlow);
return () => {};
}, []);
return (
<>
<div className={Styles.datasetCneterBox}>
<div className={Styles.datasetCneterLeftBox}>
<div className={Styles.leftContentBox}>
<Search
placeholder="搜索"
allowClear
onSearch={onSearch}
style={{
width: 300,
marginBottom: '15px',
}}
/>
<div className={Styles.itemTitle}>分类</div>
<div className={Styles.itemBox}>
{datasetTypeList && datasetTypeList.length > 0
? datasetTypeList.map((item) => {
return (
<div>
<div
className={[
Styles.messageBox,
item.id === activeType ? Styles.active : null,
].join(' ')}
onClick={(e) => {
chooseDatasetType(e, item);
}}
>
<img
className={Styles.ptIcon}
style={{ width: '22px' }}
src={`/assets/images/dataset/${item.path}.png`}
alt=""
/>
<img
className={Styles.hoverIcon}
style={{ width: '22px' }}
src={`/assets/images/dataset/${item.path}-hover.png`}
alt=""
/>
<span className={Styles.messageText}>{item.name}</span>
</div>
</div>
);
})
: ''}
</div>
<div className={Styles.itemTitle}>研究方向/应用领域</div>
<div className={Styles.itemBox}>
{datasetDirectionList && datasetDirectionList.length > 0
? datasetDirectionList.map((item) => {
return (
<div>
<div
className={[
Styles.messageBox,
item.id === activeTag ? Styles.active : null,
].join(' ')}
onClick={(e) => {
chooseDatasetTag(e, item);
}}
>
<img
className={Styles.ptIcon}
style={{ width: '22px' }}
src={`/assets/images/dataset/${item.path}.png`}
alt=""
/>
<img
className={Styles.hoverIcon}
style={{ width: '22px' }}
src={`/assets/images/dataset/${item.path}-hover.png`}
alt=""
/>
<span className={Styles.messageText}>{item.name}</span>
</div>
</div>
);
})
: ''}
</div>
</div>
</div>
<div className={Styles.datasetCneterRightBox}>
<div className={Styles.dataSource}>
<span>数据总数:{total}个</span>
<div>
<Search
placeholder="按数据名称筛选"
allowClear
onSearch={nameSearch}
style={{
width: 300,
}}
/>
<Button
type="default"
style={{ marginLeft: '20px' }}
onClick={showModal}
icon={<KFIcon type="icon-xinjian2" />}
>
新建数据集
</Button>
</div>
</div>
<div className={Styles.dataContent}>
{datasetList && datasetList.length > 0
? datasetList.map((item) => {
return (
<div className={Styles.dataItem} onClick={(e) => routeToIntro(e, item)}>
<span className={Styles.itemText}>{item.name}</span>
<img
onClick={(e) => {
e.stopPropagation();
modalConfirm({
title: '确定删除该条数据集实例吗?',
onOk: () => {
deleteDataset(item.id).then((ret) => {
if (ret.code === 200) {
message.success('删除成功');
getModelLists(queryFlow);
} else {
message.error(ret.msg);
}
});
},
});
}}
className={Styles.dropdown}
style={{ width: '17px', marginRight: '6px' }}
src={deleteIcon}
alt=""
/>
<div className={Styles.itemDescripition}>{item.description}</div>

<div className={Styles.itemTime}>
<img
style={{ width: '17px', marginRight: '6px' }}
src={creatByImg}
alt=""
/>
<span>{item.create_by}</span>
</div>
<div className={Styles.itemIcon}>
<img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" />
<span>最近更新: {moment(item.update_time).format('YYYY-MM-DD')}</span>
</div>
</div>
);
})
: ''}
{/* <Select.Option value="demo">Demo</Select.Option> */}
</div>
<Pagination
total={total}
showSizeChanger
defaultPageSize={20}
pageSizeOptions={[20, 40, 60, 80, 100]}
showQuickJumper
onChange={onPageChange}
/>
</div>
</div>
<Modal
title={
<div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}>
<img
style={{ width: '20px', marginRight: '10px' }}
src={`/assets/images/pipeline-edit-icon.png`}
alt=""
/>
{dialogTitle}
</div>
}
open={isModalOpen}
className={Styles.modal}
okButtonProps={{
htmlType: 'submit',
form: 'form',
}}
onCancel={handleCancel}
>
<Form
name="form"
form={form}
layout="vertical"
initialValues={{
remember: true,
}}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
>
<Form.Item
label="数据名称"
name="name"
required
rules={[
{
required: true,
message: '请输入数据名称e!',
},
]}
>
<Input placeholder="请输入数据名称" showCount maxLength={64} />
</Form.Item>
<Form.Item
label="数据集版本"
name="version"
rules={[
{
required: true,
message: '请输入数据集版本!',
},
]}
>
<Input placeholder="请输入数据集版本" showCount maxLength={64} />
</Form.Item>
<Form.Item
label="数据集分类"
name="data_type"
rules={
[
// {
// required: true,
// message: 'Please input your username!',
// },
]
}
>
<Select
allowClear
placeholder="请选择数据集分类"
options={datasetTypeList.map((item) => {
return { value: item.id, label: item.name };
})}
/>
</Form.Item>
<Form.Item
label="研究方向/应用领域"
name="data_tag"
rules={
[
// {
// required: true,
// message: 'Please input your username!',
// },
]
}
>
<Select
allowClear
placeholder="请选择研究方向/应用领域"
options={datasetDirectionList.map((item) => {
return { value: item.id, label: item.name };
})}
/>
</Form.Item>
<Form.Item label="集群版本" name="available_cluster">
<Select allowClear placeholder="请选择集群版本" options={clusterOptions} />
</Form.Item>
<Form.Item
label="数据简介"
name="description"
rules={
[
// {
// required: true,
// message: 'Please input your username!',
// },
]
}
>
<Input placeholder="请输入数据简介" showCount maxLength={256} />
</Form.Item>
<Form.Item label="选择流水线" name="range">
<Radio.Group>
<Radio value="0">仅自己可见</Radio>
<Radio value="1">工作空间可见</Radio>
</Radio.Group>
</Form.Item>
<Form.Item label="数据文件" name="dataset_version_vos">
<Upload {...props} data={{ uuid: uuid }} accept=".zip,.tgz">
<Button
style={{
fontSize: '14px',
border: '1px solid',
borderColor: '#1664ff',
background: '#fff',
}}
icon={<UploadOutlined style={{ color: '#1664ff', fontSize: '14px' }} />}
>
上传文件
</Button>
<div className={Styles.tipContent}>只允许上传.zip,.tgz格式文件</div>
</Upload>
</Form.Item>
</Form>
</Modal>
</>
);
});
export default PublicData;

+ 0
- 284
react-ui/src/pages/Dataset/publicData.jsx View File

@@ -1,284 +0,0 @@
import clock from '@/assets/img/clock.png';
import creatByImg from '@/assets/img/creatBy.png';
import deleteIcon from '@/assets/img/delete-icon.png';
import { deleteDataset, getAssetIcon, getDatasetList } from '@/services/dataset/index.js';
import { modalConfirm } from '@/utils/ui';
import { Form, Input, Pagination } from 'antd';
import moment from 'moment';
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import Styles from './index.less';
const { Search } = Input;
const leftdataList = [1, 2, 3];

const PublicData = () => {
const [queryFlow, setQueryFlow] = useState({
page: 0,
size: 10,
name: null,
available_range: 1,
});
const [iconParams, setIconParams] = useState({
name: null,
page: 0,
size: 10000,
});
const navgite = useNavigate();
const [datasetTypeList, setDatasetTypeList] = useState([]);
const [datasetDirectionList, setDatasetDirectionList] = useState([]);
const [activeType, setActiveType] = useState(null);
const [activeTag, setActiveTag] = useState(null);
const [isModalOpen, setIsModalOpen] = useState(false);
const [datasetList, setDatasetList] = useState([]);
const [total, setTotal] = useState(0);
const [form] = Form.useForm();
const [dialogTitle, setDialogTitle] = useState('新建数据');
const getDatasetlist = (queryFlow) => {
getDatasetList(queryFlow).then((ret) => {
console.log(ret);
if (ret.code == 200) {
setDatasetList(ret.data.content);
setTotal(ret.data.totalElements);
}
});
};
const onSearch = (values) => {
console.log(values);
getAssetIconList({ ...iconParams, name: values });
};
const getAssetIconList = (params) => {
getAssetIcon(params).then((ret) => {
console.log(ret);
if (ret.code == 200 && ret.data.content && ret.data.content.length > 0) {
setDatasetTypeList(ret.data.content.filter((item) => item.category_id == 1));
setDatasetDirectionList(ret.data.content.filter((item) => item.category_id == 2));
} else {
setDatasetTypeList([]);
setDatasetDirectionList([]);
}
});
};
const nameSearch = (values) => {
console.log(values);
getDatasetlist({ ...queryFlow, name: values });
};
const showModal = () => {
form.resetFields();
setDialogTitle('新建数据集');
setIsModalOpen(true);
};
const handleOk = () => {
console.log(1111);
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
const chooseDatasetType = (val, item) => {
console.log(val, item);
if (item.id == queryFlow.data_type) {
setActiveType('');
setQueryFlow({ ...queryFlow, data_type: null });
getDatasetlist({ ...queryFlow, data_type: null });
} else {
setActiveType(item.id);
setQueryFlow({ ...queryFlow, data_type: item.id });
getDatasetlist({ ...queryFlow, data_type: item.id });
}
// setQueryFlow({...queryFlow,data_type:item.path},()=>{
// getDatasetlist()
// })
};
const chooseDatasetTag = (val, item) => {
console.log(val, item);
if (item.id == queryFlow.data_tag) {
setActiveTag('');
setQueryFlow({ ...queryFlow, data_tag: null });
getDatasetlist({ ...queryFlow, data_tag: null });
} else {
setActiveTag(item.id);
setQueryFlow({ ...queryFlow, data_tag: item.id });
getDatasetlist({ ...queryFlow, data_tag: item.id });
}
// setQueryFlow({...queryFlow,data_type:item.path},()=>{
// getDatasetlist()
// })
};

const routeToIntro = (e, record) => {
e.stopPropagation();
console.log(record);
navgite({ pathname: `/dataset/dataset/${record.id}` });
};
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
};
const onPageChange = (pageNum, pageSize) => {
console.log(pageNum, pageSize);
setQueryFlow({ ...queryFlow, page: pageNum - 1, size: pageSize });
getDatasetlist({ ...queryFlow, page: pageNum - 1, size: pageSize });
};
useEffect(() => {
getAssetIconList(iconParams);
getDatasetlist(queryFlow);
return () => {};
}, []);
return (
<>
<div className={Styles.datasetCneterBox}>
<div className={Styles.datasetCneterLeftBox}>
<div className={Styles.leftContentBox}>
<Search
placeholder="搜索"
allowClear
onSearch={onSearch}
style={{
width: 300,
marginBottom: '15px',
}}
/>
<div className={Styles.itemTitle}>分类</div>
<div className={Styles.itemBox}>
{datasetTypeList && datasetTypeList.length > 0
? datasetTypeList.map((item) => {
return (
<div>
<div
className={[
Styles.messageBox,
item.id === activeType ? Styles.active : null,
].join(' ')}
onClick={(e) => {
chooseDatasetType(e, item);
}}
>
<img
className={Styles.ptIcon}
style={{ width: '22px' }}
src={`/assets/images/dataset/${item.path}.png`}
alt=""
/>
<img
className={Styles.hoverIcon}
style={{ width: '22px' }}
src={`/assets/images/dataset/${item.path}-hover.png`}
alt=""
/>
<span className={Styles.messageText}>{item.name}</span>
</div>
</div>
);
})
: ''}
</div>
<div className={Styles.itemTitle}>研究方向/应用领域</div>
<div className={Styles.itemBox}>
{datasetDirectionList && datasetDirectionList.length > 0
? datasetDirectionList.map((item) => {
return (
<div>
<div
className={[
Styles.messageBox,
item.id === activeTag ? Styles.active : null,
].join(' ')}
onClick={(e) => {
chooseDatasetTag(e, item);
}}
>
<img
className={Styles.ptIcon}
style={{ width: '22px' }}
src={`/assets/images/dataset/${item.path}.png`}
alt=""
/>
<img
className={Styles.hoverIcon}
style={{ width: '22px' }}
src={`/assets/images/dataset/${item.path}-hover.png`}
alt=""
/>
<span className={Styles.messageText}>{item.name}</span>
</div>
</div>
);
})
: ''}
</div>
</div>
</div>
<div className={Styles.datasetCneterRightBox}>
<div className={Styles.dataSource}>
<span>数据总数:{total}个</span>
<div>
<Search
placeholder="按数据名称筛选"
allowClear
onSearch={nameSearch}
style={{
width: 300,
}}
/>
</div>
</div>
<div className={Styles.dataContent}>
{datasetList && datasetList.length > 0
? datasetList.map((item) => {
return (
<div className={Styles.dataItem} onClick={(e) => routeToIntro(e, item)}>
<span className={Styles.itemText}>{item.name}</span>
<img
onClick={(e) => {
e.stopPropagation();
modalConfirm({
title: '确定删除该条数据集实例吗?',
onOk: () => {
deleteDataset(item.id).then((ret) => {
if (ret.code === 200) {
message.success('删除成功');
getModelLists(queryFlow);
} else {
message.error(ret.msg);
}
});
},
});
}}
className={Styles.dropdown}
style={{ width: '17px', marginRight: '6px' }}
src={deleteIcon}
alt=""
/>
<div className={Styles.itemDescripition}>{item.description}</div>
<div className={Styles.itemTime}>
<img
style={{ width: '17px', marginRight: '6px' }}
src={creatByImg}
alt=""
/>
<span>{item.create_by}</span>
</div>
<div className={Styles.itemIcon}>
<img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" />
<span>最近更新: {moment(item.update_time).format('YYYY-MM-DD')}</span>
</div>
</div>
);
})
: ''}
{/* <Select.Option value="demo">Demo</Select.Option> */}
</div>
<Pagination
total={total}
showSizeChanger
defaultPageSize={20}
pageSizeOptions={[20, 40, 60, 80, 100]}
showQuickJumper
onChange={onPageChange}
/>
</div>
</div>
</>
);
};
export default PublicData;

+ 129
- 0
react-ui/src/pages/Dataset/types.tsx View File

@@ -0,0 +1,129 @@
import KFIcon from '@/components/KFIcon';
import { CommonTabKeys } from '@/enums';
import {
addDatasetVersionDetail,
addModelsVersionDetail,
deleteDataset,
deleteModel,
getDatasetList,
getDatasetVersionIdList,
getDatasetVersionsById,
getModelList,
getModelVersionIdList,
getModelVersionsById,
} from '@/services/dataset/index.js';
import type { TabsProps } from 'antd';

export enum ResourceType {
Model = 'Model', // 模型
Dataset = 'Dataset', // 数据集
}

type ResourceTypeInfo = {
getList: (params: any) => Promise<any>;
getVersions: (params: any) => Promise<any>;
getFiles: (params: any) => Promise<any>;
deleteRecord: (params: any) => Promise<any>;
name: string;
typeParamKey: string;
tagParamKey: string;
fileReqParamKey: 'models_id' | 'dataset_id';
tabItems: TabsProps['items'];
typeTitle: string;
tagTitle: string;
typeValue: number; // 从 getAssetIcon 接口获取特定值的数据为 type 分类 (category_id === typeValue)
tagValue: number; // 从 getAssetIcon 接口获取特定值的数据为 tag 分类(category_id === tagValue)
iconPathPrefix: string; // 图标路径前缀
deleteModalTitle: string; // 删除弹框的title
addBtnTitle: string; // 新增按钮的title
addVersionReq: (params: any) => Promise<any>;
idParamKey: string;
uploadAction: string;
uploadAccept?: string;
};

export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = {
[ResourceType.Dataset]: {
getList: getDatasetList,
getVersions: getDatasetVersionsById,
getFiles: getDatasetVersionIdList,
deleteRecord: deleteDataset,
name: '数据集',
typeParamKey: 'data_type',
tagParamKey: 'data_tag',
fileReqParamKey: 'dataset_id',
tabItems: [
{
key: CommonTabKeys.Public,
label: '数据广场',
icon: <KFIcon type="icon-shujuguangchang" />,
},
{
key: CommonTabKeys.Private,
label: '个人数据',
icon: <KFIcon type="icon-gerenshuju" />,
},
],
typeTitle: '分类',
tagTitle: '研究方向/应用领域',
typeValue: 1,
tagValue: 2,
iconPathPrefix: 'dataset',
deleteModalTitle: '确定删除该条数据集实例吗?',
addBtnTitle: '新建数据集',
addVersionReq: addDatasetVersionDetail,
idParamKey: 'dataset_id',
uploadAction: '/api/mmp/dataset/upload',
uploadAccept: '.zip,.tgz',
},
[ResourceType.Model]: {
getList: getModelList,
getVersions: getModelVersionsById,
getFiles: getModelVersionIdList,
deleteRecord: deleteModel,
name: '模型',
typeParamKey: 'model_type',
tagParamKey: 'model_tag',
fileReqParamKey: 'models_id',
tabItems: [
{
key: CommonTabKeys.Public,
label: '模型广场',
icon: <KFIcon type="icon-moxingguangchang" />,
},
{
key: CommonTabKeys.Private,
label: '个人模型',
icon: <KFIcon type="icon-gerenmoxing" />,
},
],
typeTitle: '模型框架',
tagTitle: '模型能力',
typeValue: 3,
tagValue: 4,
iconPathPrefix: 'model',
deleteModalTitle: '确定删除该条模型实例吗?',
addBtnTitle: '新建模型',
addVersionReq: addModelsVersionDetail,
idParamKey: 'models_id',
uploadAction: '/api/mmp/models/upload',
uploadAccept: undefined,
},
};

// 分类数据
export type CategoryData = {
id: number;
category_id: number;
name: string;
path: string;
};

// 数据类型
export type ResourceData = {
id: number;
name: string;
description: string;
create_by: string;
update_time: string;
};

+ 1
- 1
react-ui/src/pages/DatasetPreparation/DatasetAnnotation/index.tsx View File

@@ -16,7 +16,7 @@ function DatasetAnnotation() {
}; };
return ( return (
<div className={styles.container}> <div className={styles.container}>
{iframeUrl && <iframe src={iframeUrl} className={styles.frame}></iframe>}
<iframe src="http://172.20.32.181:31213/label-studio" className={styles.frame}></iframe>
</div> </div>
); );
} }


+ 9
- 0
react-ui/src/pages/Experiment/components/AddExperimentModal/index.less View File

@@ -0,0 +1,9 @@
.modal {
.global_param_item {
max-height: 230px;
padding: 24px 12px 0;
overflow-y: auto;
border: 1px solid #e6e6e6;
border-radius: 6px;
}
}

react-ui/src/pages/Experiment/experimentText/addExperimentModal.tsx → react-ui/src/pages/Experiment/components/AddExperimentModal/index.tsx View File

@@ -4,7 +4,7 @@ import KFModal from '@/components/KFModal';
import { type PipelineGlobalParam } from '@/types'; import { type PipelineGlobalParam } from '@/types';
import { Form, Input, Radio, Select, type FormRule } from 'antd'; import { Form, Input, Radio, Select, type FormRule } from 'antd';
import { useState } from 'react'; import { useState } from 'react';
import styles from './addExperimentModal.less';
import styles from './index.less';


type FormData = { type FormData = {
name?: string; name?: string;
@@ -97,6 +97,11 @@ function AddExperimentModal({
wrapperCol: { span: 20 }, wrapperCol: { span: 20 },
}; };


const paramLayout = {
labelCol: { span: 8 },
wrapperCol: { span: 16 },
};

// 除了流水线选择发生变化 // 除了流水线选择发生变化
const handleWorkflowChange = (id: string | number) => { const handleWorkflowChange = (id: string | number) => {
const pipeline: Workflow | undefined = workflowList.find((v) => v.id === id); const pipeline: Workflow | undefined = workflowList.find((v) => v.id === id);
@@ -187,7 +192,7 @@ function AddExperimentModal({
fields.map(({ key, name, ...restField }) => ( fields.map(({ key, name, ...restField }) => (
<Form.Item <Form.Item
{...restField} {...restField}
{...layout}
{...paramLayout}
key={key} key={key}
label={getParamType(globalParam[name])} label={getParamType(globalParam[name])}
name={[name, 'param_value']} name={[name, 'param_value']}

+ 16
- 0
react-ui/src/pages/Experiment/components/ExperimentParameter/index.less View File

@@ -0,0 +1,16 @@
.experiment-parameter {
padding-top: 8px;

&__title {
display: flex;
align-items: center;
height: 43px;
margin-right: 8px;
margin-bottom: 20px;
margin-left: 8px;
padding: 0 24px;
color: @text-color;
font-size: @font-size;
background: #f8fbff;
}
}

+ 172
- 0
react-ui/src/pages/Experiment/components/ExperimentParameter/index.tsx View File

@@ -0,0 +1,172 @@
import SubAreaTitle from '@/components/SubAreaTitle';
import { useComputingResource } from '@/hooks/resource';
import { PipelineNodeModelSerialize } from '@/types';
import { Form, Input, Select, type FormProps } from 'antd';
import styles from './index.less';
const { TextArea } = Input;

type ExperimentParameterProps = {
form: FormProps['form'];
nodeData: PipelineNodeModelSerialize;
};

function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) {
const [resourceStandardList] = useComputingResource(); // 资源规模

// 控制策略
const controlStrategyList = Object.entries(nodeData.control_strategy ?? {}).map(
([key, value]) => ({ key, value }),
);

// 输入参数
const inParametersList = Object.entries(nodeData.in_parameters ?? {}).map(([key, value]) => ({
key,
value,
}));

// 输出参数
const outParametersList = Object.entries(nodeData.out_parameters ?? {}).map(([key, value]) => ({
key,
value,
}));

return (
<Form
name="form"
layout="vertical"
labelCol={{
span: 24,
}}
wrapperCol={{
span: 24,
}}
form={form}
style={{
maxWidth: 600,
}}
autoComplete="off"
className={styles['experiment-parameter']}
>
<div className={styles['experiment-parameter__title']}>
<SubAreaTitle image="/assets/images/static-message.png" title="基本信息"></SubAreaTitle>
</div>
<Form.Item
label="任务名称"
name="label"
rules={[
{
required: true,
message: '请输入任务名称',
},
]}
>
<Input disabled />
</Form.Item>
<Form.Item
label="任务ID"
name="id"
rules={[
{
required: true,
message: '请输入任务id',
},
]}
>
<Input disabled />
</Form.Item>
<div className={styles['experiment-parameter__title']}>
<SubAreaTitle image="/assets/images/duty-message.png" title="任务信息"></SubAreaTitle>
</div>
<Form.Item
label="镜像"
name="image"
rules={[
{
required: true,
message: '请输入镜像',
},
]}
>
<Input disabled />
</Form.Item>
<Form.Item label="工作目录" name="working_directory">
<Input disabled />
</Form.Item>

<Form.Item label="启动命令" name="command">
<TextArea disabled />
</Form.Item>
<Form.Item
label="资源规格"
name="resources_standard"
rules={[
{
required: true,
message: '请输入资源规格',
},
]}
>
<Select
options={resourceStandardList}
disabled
fieldNames={{
label: 'description',
value: 'standard',
}}
/>
</Form.Item>
<Form.Item label="挂载路径" name="mount_path">
<Input disabled />
</Form.Item>
<Form.Item label="环境变量" name="env_variables">
<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>
))}
<div className={styles['experiment-parameter__title']}>
<SubAreaTitle image="/assets/images/duty-message.png" title="输入参数"></SubAreaTitle>
</div>
{inParametersList.map((item) => (
<Form.Item
key={item.key}
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 />
</Form.Item>
))}
<div className={styles['experiment-parameter__title']}>
<SubAreaTitle image="/assets/images/duty-message.png" title="输出参数"></SubAreaTitle>
</div>
{outParametersList.map((item) => (
<Form.Item
key={item.key}
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 />
</Form.Item>
))}
</Form>
);
}

export default ExperimentParameter;

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

@@ -0,0 +1,38 @@
.experiment-result {
padding: 8px;
color: @text-color;
font-size: 14px;

&__content {
padding: 10px 20px 20px 20px;
background-color: rgba(234, 234, 234, 0.5);
}

&__item {
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}

&__name {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 0;
border-bottom: 1px solid rgba(234, 234, 234, 0.8);
}

&__file {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
margin-bottom: 10px;
padding: 0 20px 0 0;

&:last-child {
margin-bottom: 0;
}
}
}
}

+ 59
- 0
react-ui/src/pages/Experiment/components/ExperimentResult/index.tsx View File

@@ -0,0 +1,59 @@
import { downLoadZip } from '@/utils/downloadfile';
import { Button } from 'antd';
import styles from './index.less';
type ExperimentResultProps = {
results?: ExperimentResultData[] | null;
};

type ExperimentResultData = {
name: string;
path: string;
type: string;
value: {
name: string;
size: string;
}[];
};

function ExperimentResult({ results }: ExperimentResultProps) {
const exportResult = (val: string) => {
downLoadZip(`/api/mmp/minioStorage/download`, { path: val });
};

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>
<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>
</div>
);
}

export default ExperimentResult;

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

@@ -0,0 +1,34 @@
.log-group {
padding-bottom: 10px;

&__pod {
display: flex;
align-items: center;
justify-content: space-between;
padding: 15px;
background: rgba(234, 234, 234, 0.5);
cursor: pointer;

&__name {
margin-right: 10px;
color: @text-color;
font-size: 14px;
}
}

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

&__more-button {
display: flex;
justify-content: center;
color: white;
background: #19253b;
}
}

react-ui/src/pages/Experiment/experimentText/logGroup.tsx → react-ui/src/pages/Experiment/components/LogGroup/index.tsx View File

@@ -1,16 +1,19 @@
/*
* @Author: 赵伟
* @Date: 2024-05-16 08:47:46
* @Description: 日志组件
*/

import { useStateRef } from '@/hooks'; import { useStateRef } from '@/hooks';
import { ExperimentStatus } from '@/pages/Experiment/status';
import { ExperimentLog } from '@/pages/Experiment/training/props';
import { getExperimentPodsLog } from '@/services/experiment/index.js'; import { getExperimentPodsLog } from '@/services/experiment/index.js';
import { DoubleRightOutlined, DownOutlined, UpOutlined } from '@ant-design/icons'; import { DoubleRightOutlined, DownOutlined, UpOutlined } from '@ant-design/icons';
import { Button } from 'antd'; import { Button } from 'antd';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { ExperimentStatus } from '../status';
import styles from './logGroup.less';
import styles from './index.less';


export type LogGroupProps = {
log_type: 'normal' | 'resource'; // 日志类型
pod_name?: string; // 分布式名称
log_content?: string; // 日志内容
start_time?: string; // 日志开始时间
export type LogGroupProps = ExperimentLog & {
status: ExperimentStatus; // 实验状态 status: ExperimentStatus; // 实验状态
}; };


@@ -23,7 +26,7 @@ function LogGroup({
log_type = 'normal', log_type = 'normal',
pod_name = '', pod_name = '',
log_content = '', log_content = '',
start_time = '',
start_time,
status = ExperimentStatus.Pending, status = ExperimentStatus.Pending,
}: LogGroupProps) { }: LogGroupProps) {
const [collapse, setCollapse] = useState(true); const [collapse, setCollapse] = useState(true);
@@ -57,21 +60,6 @@ function LogGroup({
setCompleted(true); setCompleted(true);
} }
}; };
// 请求实时日志
// const requestExperimentPodsRealtimeLog = async () => {
// const params = {
// pod_name,
// namespace: namespace,
// container_name: log_type === 'resource' ? '' : 'main',
// };
// const res = await getExperimentPodsRealtimeLog(params);
// const { log_detail } = res.data;
// if (log_detail && log_detail.log_content) {
// setLogList((list) => list.concat(log_detail));
// } else {
// setCompleted(true);
// }
// };


// 处理折叠 // 处理折叠
const handleCollapse = async () => { const handleCollapse = async () => {
@@ -101,15 +89,15 @@ function LogGroup({
const logText = log_content + logList.map((v) => v.log_content).join(''); const logText = log_content + logList.map((v) => v.log_content).join('');
const showMoreBtn = status !== 'Running' && showLog && !completed && logText !== ''; const showMoreBtn = status !== 'Running' && showLog && !completed && logText !== '';
return ( return (
<div className={styles.log_group}>
<div className={styles['log-group']}>
{log_type === 'resource' && ( {log_type === 'resource' && (
<div className={styles.log_group_pod} onClick={handleCollapse}>
<div className={styles.log_group_pod_name}>{pod_name}</div>
<div className={styles['log-group__pod']} onClick={handleCollapse}>
<div className={styles['log-group__pod__name']}>{pod_name}</div>
{collapse ? <DownOutlined /> : <UpOutlined />} {collapse ? <DownOutlined /> : <UpOutlined />}
</div> </div>
)} )}
{showLog && <div className={styles.log_group_detail}>{logText}</div>}
<div className={styles.log_group_more_button}>
{showLog && <div className={styles['log-group__detail']}>{logText}</div>}
<div className={styles['log-group__more-button']}>
{showMoreBtn && ( {showMoreBtn && (
<Button <Button
type="text" type="text"

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

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

+ 21
- 0
react-ui/src/pages/Experiment/components/LogList/index.tsx View File

@@ -0,0 +1,21 @@
import { ExperimentStatus } from '@/pages/Experiment/status';
import { ExperimentLog } from '@/pages/Experiment/training/props';
import LogGroup from '../LogGroup';
import styles from './index.less';

type LogListProps = {
list: ExperimentLog[];
status: ExperimentStatus;
};

function LogList({ list = [], status }: LogListProps) {
return (
<div className={styles['log-list']}>
{list.map((v) => (
<LogGroup key={v.pod_name} {...v} status={status} />
))}
</div>
);
}

export default LogList;

react-ui/src/pages/Experiment/experimentText/paramsModal.less → react-ui/src/pages/Experiment/components/ViewParamsModal/index.less View File


react-ui/src/pages/Experiment/experimentText/paramsModal.tsx → react-ui/src/pages/Experiment/components/ViewParamsModal/index.tsx View File

@@ -6,8 +6,8 @@
import parameterImg from '@/assets/img/modal-parameter.png'; import parameterImg from '@/assets/img/modal-parameter.png';
import KFModal from '@/components/KFModal'; import KFModal from '@/components/KFModal';
import { type PipelineGlobalParam } from '@/types'; import { type PipelineGlobalParam } from '@/types';
import { getParamType } from './addExperimentModal';
import styles from './paramsModal.less';
import { getParamType } from '../AddExperimentModal';
import styles from './index.less';


type ParamsModalProps = { type ParamsModalProps = {
open: boolean; open: boolean;
@@ -24,6 +24,7 @@ function ParamsModal({ open, onCancel, globalParam = [] }: ParamsModalProps) {
onOk={onCancel} onOk={onCancel}
onCancel={onCancel} onCancel={onCancel}
cancelButtonProps={{ style: { display: 'none' } }} cancelButtonProps={{ style: { display: 'none' } }}
width={825}
> >
<div className={styles.params_container}> <div className={styles.params_container}>
{globalParam?.map((item) => ( {globalParam?.map((item) => (

+ 0
- 19
react-ui/src/pages/Experiment/experimentText/LogList.tsx View File

@@ -1,19 +0,0 @@
import { ExperimentStatus } from '../status';
import LogGroup, { type LogGroupProps } from './logGroup';

type LogListProps = {
list: Omit<LogGroupProps, 'status'>[];
status: ExperimentStatus;
};

function LogList({ list = [], status }: LogListProps) {
return (
<div>
{list.map((v) => (
<LogGroup key={v.pod_name} {...v} status={status} />
))}
</div>
);
}

export default LogList;

+ 0
- 22
react-ui/src/pages/Experiment/experimentText/addExperimentModal.less View File

@@ -1,22 +0,0 @@
.modal {
:global {
// .ant-input {
// height: 30px;
// border-color: #e6e6e6;
// }
// .ant-select-single {
// height: 40px;
// }
.ant-form-item .ant-form-item-label > label {
color: rgba(29, 29, 32, 0.8);
}
}

.global_param_item {
max-height: 230px;
padding: 24px 12px 0;
overflow-y: auto;
border: 1px solid #e6e6e6;
border-radius: 6px;
}
}

+ 0
- 90
react-ui/src/pages/Experiment/experimentText/index.less View File

@@ -1,90 +0,0 @@
#graph {
position: relative;
width: 100%;
height: 100%;
}
.editPipelinePropsContent {
display: flex;
align-items: center;
width: 100%;
height: 43px;
margin-bottom: 20px;
padding: 0 24px;
color: #1d1d20;
font-size: 15px;
font-family: 'Alibaba';
background: #f8fbff;
}
.centerContainer {
display: flex;
flex: 1;
flex-direction: column;
}
.buttonList {
display: flex;
align-items: center;
width: 100%;
height: 56px;
padding: 0 30px;
background: #ffffff;
box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09);
}
.drawBox{
:global{
.ant-drawer .ant-drawer-body{
overflow: hidden;
}
}
}
.experimentDrawer{
:global{
.ant-tabs >.ant-tabs-nav .ant-tabs-nav-list{
margin-left: 24px;
}
.ant-drawer .ant-drawer-body{
overflow-y: hidden;
}
.ant-tabs {
height: calc(100% - 160px);
overflow-y: auto;
}
}

}
.detailBox {
display: flex;
align-items: center;
margin-bottom: 15px;
color: #1d1d20;
font-size: 15px;
padding-left: 24px;

}
.allMessageItem {
display: flex;
align-items: center;
margin-right: 30px;
color: rgba(29, 29, 32, 0.8);
font-size: 15px;
}
.param_button {
margin-right: 0;
margin-left: auto;
}
.resultTop {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 0;
border-bottom: 1px solid #eee;
}
.resultContent {
display: flex;

align-items: center;
justify-content: space-between;
width: 100%;
margin-bottom: 10px;
padding: 0 20px 0 0;
}

+ 0
- 34
react-ui/src/pages/Experiment/experimentText/logGroup.less View File

@@ -1,34 +0,0 @@
.log_group {
padding-bottom: 10px;
}

.log_group_pod {
display: flex;
align-items: center;
justify-content: space-between;
padding: 15px;
background: rgba(234, 234, 234, 0.5);
cursor: pointer;

&_name {
margin-right: 10px;
color: #1d1d20;
font-size: 14px;
}
}

.log_group_detail {
padding: 15px;
color: white;
font-size: 14px;
white-space: pre-line;
word-break: break-all;
background: #19253b;
}

.log_group_more_button {
display: flex;
justify-content: center;
color: white;
background: #19253b;
}

+ 0
- 439
react-ui/src/pages/Experiment/experimentText/props.jsx View File

@@ -1,439 +0,0 @@
import { getNodeResult, getQueryByExperimentLog } from '@/services/experiment/index.js';
import { elapsedTime } from '@/utils/date';
import { downLoadZip } from '@/utils/downloadfile';
import { DatabaseOutlined, ProfileOutlined } from '@ant-design/icons';
import { Drawer, Form, Input, Tabs, message } from 'antd';
import moment from 'moment';
import { forwardRef, useImperativeHandle, useState } from 'react';
import LogList from './LogList';
import Styles from './index.less';
const { TextArea } = Input;
const Props = forwardRef(({ onParentChange }, ref) => {
const [form] = Form.useForm();
const [stagingItem, setStagingItem] = useState({});
const [resultObj, setResultObj] = useState([]);
const [logList, setLogList] = useState([]);
const statusObj = {
Running: '运行中',
Succeeded: '成功',
Pending: '等待中',
Failed: '失败',
Error: '错误',
Terminated: '终止',
Skipped: '未执行',
Omitted: '未执行',
};
const statusColorObj = {
Running: '#165bff',
Succeeded: '#63a728',
Pending: '#f981eb',
Failed: '#c73131',
Error: '#c73131',
Terminated: '#8a8a8a',
Skipped: '#8a8a8a',
Omitted: '#8a8a8ae',
};
const exportResult = (e, val) => {
const hide = message.loading('正在下载');
hide();
downLoadZip(`/api/mmp/minioStorage/download`, { path: val });
};
const timers = (time) => {
let timer = new Date(time);
let hours = timer.getHours(); //转换成时
let minutes = timer.getMinutes(); //转换成分
let secend = timer.getSeconds(); //转换成秒

let str = `${minutes}分${secend}秒`;
return str;
};
const items = [
{
key: '1',
label: '日志详情',
children: <LogList list={logList} status={stagingItem.experimentStatus}></LogList>,
icon: <ProfileOutlined />,
},
{
key: '2',
label: '配置参数',
icon: <DatabaseOutlined />,
children: (
<Form
name="form"
form={form}
layout="vertical"
labelCol={{
span: 16,
}}
wrapperCol={{
span: 24,
}}
style={{
maxWidth: 600,
}}
initialValues={{
remember: true,
}}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
>
<div className={Styles.editPipelinePropsContent}>
<img
style={{ width: '15px', marginRight: '10px' }}
src={'/assets/images/static-message.png'}
alt=""
/>
基本信息
</div>
<Form.Item
label="任务名称"
name="label"
rules={[
{
required: true,
message: '请输入任务名称',
},
]}
>
<Input disabled />
</Form.Item>
<Form.Item
label="任务ID"
name="id"
rules={[
{
required: true,
message: '请输入任务id',
},
]}
>
<Input disabled />
</Form.Item>
<div className={Styles.editPipelinePropsContent}>
<img
style={{ width: '15px', marginRight: '10px' }}
src={'/assets/images/duty-message.png'}
alt=""
/>
任务信息
</div>
<Form.Item
label="镜像"
name="image"
rules={[
{
required: true,
message: '请输入镜像',
},
]}
>
<Input disabled />
</Form.Item>
<Form.Item label="工作目录" name="working_directory">
<Input disabled />
</Form.Item>

<Form.Item label="启动命令" name="command">
<TextArea disabled />
</Form.Item>
<Form.Item
label="资源规格"
name="resources_standard"
rules={[
{
required: true,
message: '请输入资源规格',
},
]}
>
<Input disabled />
</Form.Item>
<Form.Item label="挂载路径" name="mount_path">
<Input disabled />
</Form.Item>
<Form.Item label="环境变量" name="env_variables">
<TextArea disabled />
</Form.Item>
{stagingItem.control_strategy &&
Object.keys(stagingItem.control_strategy) &&
Object.keys(stagingItem.control_strategy).length > 0
? Object.keys(stagingItem.control_strategy).map((item) => (
<Form.Item
key={item}
label={stagingItem.control_strategy[item].label}
disabled
name={item}
>
<Input disabled />
</Form.Item>
))
: ''}
<div className={Styles.editPipelinePropsContent}>
<img
style={{ width: '13px', marginRight: '10px' }}
src={'/assets/images/duty-message.png'}
alt=""
/>
输入参数
</div>
{stagingItem.in_parameters &&
Object.keys(stagingItem.in_parameters) &&
Object.keys(stagingItem.in_parameters).length > 0
? Object.keys(stagingItem.in_parameters).map((item) => (
<Form.Item
key={item}
label={stagingItem.in_parameters[item].label + '(' + item + ')'}
name={item}
disabled
rules={[{ required: stagingItem.in_parameters[item].require ? true : false }]}
>
<Input disabled />
</Form.Item>
))
: ''}
<div className={Styles.editPipelinePropsContent}>
<img
style={{ width: '15px', marginRight: '10px' }}
src={'/assets/images/duty-message.png'}
alt=""
/>
输出参数
</div>
{stagingItem.out_parameters &&
Object.keys(stagingItem.out_parameters) &&
Object.keys(stagingItem.out_parameters).length > 0
? Object.keys(stagingItem.out_parameters).map((item) => (
<Form.Item
key={item}
label={stagingItem.out_parameters[item].label + '(' + item + ')'}
disabled
rules={[{ required: stagingItem.out_parameters[item].require ? true : false }]}
name={item}
>
<Input disabled />
</Form.Item>
))
: ''}
</Form>
),
},
{
key: '3',
label: '输出结果',
children: (
<div
style={{
minHeight: '740px',
background: '#f4f4f4',
color: '#000',
fontSize: '14px',
padding: '0 10px 20px 20px',
}}
>
{resultObj && resultObj.length > 0
? resultObj.map((item) => (
<div key={item.name}>
<div className={Styles.resultTop}>
<span>{item.name}</span>
<div style={{ display: 'flex' }}>
<a
onClick={(e) => {
exportResult(e, item.path);
}}
style={{ marginRight: '10px' }}
>
下载
</a>
<a style={{ marginRight: '10px' }}>导出到模型库</a>
<a style={{ marginRight: '10px' }}>导出到数据集</a>
</div>
</div>
<div style={{ margin: '15px 0' }} className={Styles.resultContent}>
<span>文件名称</span>
<span>文件大小</span>
</div>
{item.value && item.value.length > 0
? item.value.map((ele) => (
<div className={Styles.resultContent} key={ele.name}>
<span>{ele.name}</span>
<span>{ele.size}</span>
</div>
))
: null}
</div>
))
: null}
</div>
),
icon: <ProfileOutlined />,
},
];
const [open, setOpen] = useState(false);
const afterOpenChange = () => {
if (!open) {
console.log(111, open);

console.log(stagingItem, form.getFieldsValue());
for (let i in form.getFieldsValue()) {
for (let j in stagingItem.in_parameters) {
if (i == j) {
console.log(j, i);
stagingItem.in_parameters[j].value = form.getFieldsValue()[i];
}
}
for (let p in stagingItem.out_parameters) {
if (i == p) {
stagingItem.out_parameters[p].value = form.getFieldsValue()[i];
}
}
for (let k in stagingItem.control_strategy) {
if (i == k) {
stagingItem.control_strategy[k].value = form.getFieldsValue()[i];
}
}
}
// setStagingItem({...stagingItem,})
console.log(stagingItem.control_strategy);
onParentChange({
...stagingItem,
control_strategy: JSON.stringify(stagingItem.control_strategy),
in_parameters: JSON.stringify(stagingItem.in_parameters),
out_parameters: JSON.stringify(stagingItem.out_parameters),
...form.getFieldsValue(),
});
// onParentChange({...stagingItem,...form.getFieldsValue()})
}
};
const onClose = () => {
setOpen(false);
};
const onFinish = (values) => {
console.log('Success:', values);
};
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
};
useImperativeHandle(ref, () => ({
showDrawer(e, id, message) {
setLogList([]);
if (e.item && e.item.getModel().component_id) {
const model = e.item.getModel() || {};
const start_time = moment(model.experimentStartTime).valueOf() * 1.0e6;
const params = {
task_id: model.id,
component_id: model.component_id,
name: message.argo_ins_name,
namespace: message.argo_ins_ns,
start_time: start_time,
};
getQueryByExperimentLog(params).then((ret) => {
const { log_type, pods, log_detail } = ret.data;
if (log_type === 'normal') {
const list = [
{
...log_detail,
log_type,
},
];
setLogList(list);
} else if (log_type === 'resource') {
const list = pods.map((v) => ({
log_type,
pod_name: v,
log_content: '',
start_time,
}));
setLogList(list);
}

getNodeResult({ id, node_id: e.item.getModel().id }).then((res) => {
setResultObj(res.data);
form.resetFields();
form.setFieldsValue({
...e.item.getModel(),
in_parameters: JSON.parse(e.item.getModel().in_parameters),
out_parameters: JSON.parse(e.item.getModel().out_parameters),
control_strategy: JSON.parse(e.item.getModel().control_strategy),
});
setStagingItem({
...e.item.getModel(),
in_parameters: JSON.parse(e.item.getModel().in_parameters),
out_parameters: JSON.parse(e.item.getModel().out_parameters),
control_strategy: JSON.parse(e.item.getModel().control_strategy),
});
setOpen(true);
});
});
} else {
form.resetFields();
form.setFieldsValue({
...e.item.getModel(),
in_parameters: JSON.parse(e.item.getModel().in_parameters),
out_parameters: JSON.parse(e.item.getModel().out_parameters),
control_strategy: JSON.parse(e.item.getModel().control_strategy),
});
setStagingItem({
...e.item.getModel(),
in_parameters: JSON.parse(e.item.getModel().in_parameters),
out_parameters: JSON.parse(e.item.getModel().out_parameters),
control_strategy: JSON.parse(e.item.getModel().control_strategy),
});
setOpen(true);
}
// console.log(e.item.getModel().in_parameters);
},
}));
return (
<div className={Styles.drawBox}>
<Drawer
title="任务执行详情"
placement="right"
rootStyle={{ marginTop: '68px' }}
getContainer={false}
closeIcon={false}
onClose={onClose}
afterOpenChange={afterOpenChange}
open={open}
width={420}
className={Styles.experimentDrawer}
destroyOnClose={true}
>
<div className={Styles.detailBox} style={{ marginTop: '15px' }}>
任务名称:{stagingItem.label}
</div>
<div className={Styles.detailBox}>
执行状态:
<div
style={{
width: '8px',
height: '8px',
borderRadius: '50%',
marginRight: '6px',
backgroundColor: statusColorObj[stagingItem.experimentStatus],
}}
></div>
<span style={{ color: statusColorObj[stagingItem.experimentStatus] }}>
{statusObj[stagingItem.experimentStatus]}
</span>
</div>
<div className={Styles.detailBox}>
启动时间:{moment(stagingItem.experimentStartTime).format('YYYY-MM-DD HH:mm:ss')}
</div>
<div className={Styles.detailBox}>
耗时:
{stagingItem.experimentEndTime
? elapsedTime(
new Date(stagingItem.experimentStartTime),
new Date(stagingItem.experimentEndTime),
)
: elapsedTime(new Date(stagingItem.experimentStartTime), new Date())}
</div>
<Tabs defaultActiveKey="1" items={items} />
</Drawer>
</div>
);
});

export default Props;

+ 11
- 12
react-ui/src/pages/Experiment/index.jsx View File

@@ -1,3 +1,4 @@
import CommonTableCell from '@/components/CommonTableCell';
import KFIcon from '@/components/KFIcon'; import KFIcon from '@/components/KFIcon';
import { import {
deleteExperimentById, deleteExperimentById,
@@ -14,16 +15,15 @@ import {
} from '@/services/experiment/index.js'; } from '@/services/experiment/index.js';
import { getWorkflow } from '@/services/pipeline/index.js'; import { getWorkflow } from '@/services/pipeline/index.js';
import themes from '@/styles/theme.less'; import themes from '@/styles/theme.less';
import { elapsedTime } from '@/utils/date';
import { elapsedTime, formatDate } from '@/utils/date';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import { modalConfirm } from '@/utils/ui'; import { modalConfirm } from '@/utils/ui';
import { Button, ConfigProvider, Space, Table, message } from 'antd';
import { App, Button, ConfigProvider, Space, Table } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import momnet from 'moment';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import AddExperimentModal from './components/AddExperimentModal';
import TensorBoardStatus, { TensorBoardStatusEnum } from './components/TensorBoardStatus'; import TensorBoardStatus, { TensorBoardStatusEnum } from './components/TensorBoardStatus';
import AddExperimentModal from './experimentText/addExperimentModal';
import Styles from './index.less'; import Styles from './index.less';
import { experimentStatusInfo } from './status'; import { experimentStatusInfo } from './status';


@@ -47,6 +47,7 @@ function Experiment() {
const [isAdd, setIsAdd] = useState(true); const [isAdd, setIsAdd] = useState(true);
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
const [addFormData, setAddFormData] = useState({}); const [addFormData, setAddFormData] = useState({});
const { message } = App.useApp();


useEffect(() => { useEffect(() => {
getList(); getList();
@@ -199,7 +200,7 @@ function Experiment() {
}; };
const routeToEdit = (e, record) => { const routeToEdit = (e, record) => {
e.stopPropagation(); e.stopPropagation();
navgite({ pathname: `/pipeline/pytorchtext/${record.workflow_id}/${record.workflow_name}` });
navgite({ pathname: `/pipeline/template/${record.workflow_id}/${record.workflow_name}` });
}; };
// 创建或者编辑实验接口请求 // 创建或者编辑实验接口请求
const handleAddExperiment = async (values) => { const handleAddExperiment = async (values) => {
@@ -256,7 +257,7 @@ function Experiment() {
}; };
const routerToText = (e, item, record) => { const routerToText = (e, item, record) => {
e.stopPropagation(); e.stopPropagation();
navgite({ pathname: `/pipeline/experimentPytorchtext/${record.workflow_id}/${item.id}` });
navgite({ pathname: `/pipeline/experiment/${record.workflow_id}/${item.id}` });
}; };


const handleTensorboard = async (experimentIn) => { const handleTensorboard = async (experimentIn) => {
@@ -291,6 +292,8 @@ function Experiment() {
title: '实验描述', title: '实验描述',
dataIndex: 'description', dataIndex: 'description',
key: 'description', key: 'description',
render: CommonTableCell(true),
ellipsis: { showTitle: false },
}, },
{ {
title: '最近五次运行状态', title: '最近五次运行状态',
@@ -438,13 +441,9 @@ function Experiment() {
</div> </div>
<div className={Styles.description}> <div className={Styles.description}>
<div style={{ width: '50%' }}> <div style={{ width: '50%' }}>
{item.finish_time
? elapsedTime(new Date(item.create_time), new Date(item.finish_time))
: elapsedTime(new Date(item.create_time), new Date())}
</div>
<div style={{ width: '50%' }}>
{momnet(item.create_time).format('YYYY-MM-DD HH:mm:ss')}
{elapsedTime(item.create_time, item.finish_time)}
</div> </div>
<div style={{ width: '50%' }}>{formatDate(item.create_time)}</div>
</div> </div>
<div className={Styles.statusBox}> <div className={Styles.statusBox}>
<img <img


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

@@ -6,6 +6,8 @@
height: 49px; height: 49px;
padding-right: 30px; padding-right: 30px;
background-image: url(/assets/images/pipeline-back.png); background-image: url(/assets/images/pipeline-back.png);
background-repeat: no-repeat;
background-position: top center;
background-size: 100% 100%; background-size: 100% 100%;
} }
.pipelineTopBox { .pipelineTopBox {
@@ -17,6 +19,8 @@
margin-bottom: 10px; margin-bottom: 10px;
padding-right: 30px; padding-right: 30px;
background-image: url(/assets/images/pipeline-back.png); background-image: url(/assets/images/pipeline-back.png);
background-repeat: no-repeat;
background-position: top center;
background-size: 100% 100%; background-size: 100% 100%;
} }
.tableExpandBox { .tableExpandBox {


+ 3
- 6
react-ui/src/pages/Experiment/status.ts View File

@@ -15,13 +15,10 @@ export enum ExperimentStatus {
Omitted = 'Omitted', Omitted = 'Omitted',
} }


type ExperimentStatusKeys = keyof typeof ExperimentStatus;
export type ExperimentStatusValues = (typeof ExperimentStatus)[ExperimentStatusKeys];

export const experimentStatusInfo: Record<ExperimentStatusValues, StatusInfo | undefined> = {
export const experimentStatusInfo: Record<ExperimentStatus, StatusInfo | undefined> = {
Running: { Running: {
label: '运行中', label: '运行中',
color: '#165bff',
color: '#1664ff',
icon: '/assets/images/running-icon.png', icon: '/assets/images/running-icon.png',
}, },
Succeeded: { Succeeded: {
@@ -56,7 +53,7 @@ export const experimentStatusInfo: Record<ExperimentStatusValues, StatusInfo | u
}, },
Omitted: { Omitted: {
label: '未执行', label: '未执行',
color: '#8a8a8ae',
color: '#8a8a8a',
icon: '/assets/images/omitted-icon.png', icon: '/assets/images/omitted-icon.png',
}, },
}; };

react-ui/src/pages/Experiment/experimentText/index.jsx → react-ui/src/pages/Experiment/training/index.jsx View File

@@ -1,51 +1,26 @@
import { useVisible } from '@/hooks';
import { useStateRef, useVisible } from '@/hooks';
import { getExperimentIns } from '@/services/experiment/index.js'; import { getExperimentIns } from '@/services/experiment/index.js';
import { getWorkflowById } from '@/services/pipeline/index.js'; import { getWorkflowById } from '@/services/pipeline/index.js';
import { elapsedTime } from '@/utils/date';
import { useEmotionCss } from '@ant-design/use-emotion-css';
import { elapsedTime, formatDate } from '@/utils/date';
import G6 from '@antv/g6'; import G6 from '@antv/g6';
import { Button } from 'antd'; import { Button } from 'antd';
import momnet from 'moment';
import { useEffect, useRef, useState } from 'react';
import { useEffect, useRef } from 'react';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { s8 } from '../../../utils'; import { s8 } from '../../../utils';
import ParamsModal from '../components/ViewParamsModal';
import { experimentStatusInfo } from '../status'; import { experimentStatusInfo } from '../status';
import styles from './index.less'; import styles from './index.less';
import ParamsModal from './paramsModal';
import Props from './props'; import Props from './props';


let graph = null;

function ExperimentText() { function ExperimentText() {
const [message, setMessage] = useState({});
const messageRef = useRef(message);
const [message, setMessage, messageRef] = useStateRef({});
const propsRef = useRef(); const propsRef = useRef();
const navgite = useNavigate(); const navgite = useNavigate();
const locationParams = useParams(); //新版本获取路由参数接口 const locationParams = useParams(); //新版本获取路由参数接口
let graph = null;
const [paramsModalOpen, openParamsModal, closeParamsModal] = useVisible(false); const [paramsModalOpen, openParamsModal, closeParamsModal] = useVisible(false);


const timers = (time) => {
let timer = new Date(time);
let hours = timer.getHours(); //转换成时
let minutes = timer.getMinutes(); //转换成分
let secend = timer.getSeconds(); //转换成秒

let str = `${minutes}分${secend}秒`;
return str;
};
const pipelineContainer = useEmotionCss(() => {
return {
display: 'flex',
backgroundColor: '#fff',
height: '98vh',
};
});
const graphStyle = useEmotionCss(() => {
return {
width: '100%',
backgroundColor: '#f9fafb',
flex: 1,
};
});
const graphRef = useRef(); const graphRef = useRef();
const onDragEnd = (val) => { const onDragEnd = (val) => {
console.log(val, 'eee'); console.log(val, 'eee');
@@ -61,12 +36,8 @@ function ExperimentText() {
id: val.component_name + '-' + s8(), id: val.component_name + '-' + s8(),
isCluster: false, isCluster: false,
}; };
console.log(graph, model);

graph.addItem('node', model, true); graph.addItem('node', model, true);
console.log(graph);
}; };
const formChange = (val) => {};
const handlerClick = (e) => { const handlerClick = (e) => {
if (e.target.get('name') !== 'anchor-point' && e.item) { if (e.target.get('name') !== 'anchor-point' && e.item) {
propsRef.current.showDrawer(e, locationParams.id, messageRef.current); propsRef.current.showDrawer(e, locationParams.id, messageRef.current);
@@ -74,7 +45,6 @@ function ExperimentText() {
}; };
const getGraphData = (data) => { const getGraphData = (data) => {
if (graph) { if (graph) {
console.log(graph);
graph.data(data); graph.data(data);
graph.render(); graph.render();
} else { } else {
@@ -84,77 +54,39 @@ function ExperimentText() {
} }
}; };
const getFirstWorkflow = (val) => { const getFirstWorkflow = (val) => {
getWorkflowById(val).then((ret) => {
if (graph && ret.data && ret.data.dag) {
console.log(JSON.parse(ret.data.dag));
getExperimentIns(locationParams.id).then((res) => {
if (res.code == 200) {
console.log(ret.data, 'data');
setMessage(res.data);
const experimentStatusObjs = JSON.parse(res.data.nodes_status);
const newNodeList = JSON.parse(ret.data.dag).nodes.map((item) => {
console.log(experimentStatusObjs);
getWorkflowById(val).then((pipelineRes) => {
if (graph && pipelineRes.data && pipelineRes.data.dag) {
getExperimentIns(locationParams.id).then((experimentRes) => {
if (experimentRes.code === 200) {
setMessage(experimentRes.data);
const experimentStatusObjs = JSON.parse(experimentRes.data.nodes_status);
const newNodeList = JSON.parse(pipelineRes.data.dag).nodes.map((item) => {
return { return {
...item, ...item,
experimentEndTime:
experimentStatusObjs &&
experimentStatusObjs[item.id] &&
experimentStatusObjs[item.id].finishedAt,
experimentStartTime:
experimentStatusObjs &&
experimentStatusObjs[item.id] &&
experimentStatusObjs[item.id].startedAt,
experimentStatus:
experimentStatusObjs &&
experimentStatusObjs[item.id] &&
experimentStatusObjs[item.id].phase,
component_id:
experimentStatusObjs &&
experimentStatusObjs[item.id] &&
experimentStatusObjs[item.id].id,
img:
experimentStatusObjs &&
experimentStatusObjs[item.id] &&
experimentStatusObjs[item.id].phase
? item.img.slice(0, item.img.length - 4) +
'-' +
experimentStatusObjs[item.id].phase +
'.png'
: item.img,
experimentEndTime: experimentStatusObjs?.[item.id]?.finishedAt,
experimentStartTime: experimentStatusObjs?.[item.id]?.startedAt,
experimentStatus: experimentStatusObjs?.[item.id]?.phase,
component_id: experimentStatusObjs?.[item.id]?.id,
img: experimentStatusObjs?.[item.id]?.phase
? item.img.slice(0, item.img.length - 4) +
'-' +
experimentStatusObjs[item.id].phase +
'.png'
: item.img,
}; };
}); });
const newData = { ...JSON.parse(ret.data.dag), nodes: newNodeList };

const newData = { ...JSON.parse(pipelineRes.data.dag), nodes: newNodeList };
getGraphData(newData); getGraphData(newData);
// setExperimentStatusObj(JSON.parse(ret.data.nodes_status))
} }
}); });
} }
// graph&&graph.data(JSON.parse(ret.dag))
// graph.render()
}); });
}; };
// const getExperimentIn=(val)=>{
// getExperimentIns(val).then(ret=>{
// if(ret.code==200){
// console.log(JSON.parse(ret.data.nodes_status));
// setExperimentStatusObj(JSON.parse(ret.data.nodes_status))
// setTimeout(() => {
// console.log(experimentStatusObj);

// }, 1000);
// }


// })
// }
useEffect(() => { useEffect(() => {
initGraph(); initGraph();
getFirstWorkflow(locationParams.workflowId); getFirstWorkflow(locationParams.workflowId);
}, []); }, []);
useEffect(() => {
// Update the refs whenever the state changes
messageRef.current = message;
}, [message]);


const initGraph = () => { const initGraph = () => {
const fittingString = (str, maxWidth, fontSize) => { const fittingString = (str, maxWidth, fontSize) => {
@@ -375,6 +307,8 @@ function ExperimentText() {
}, },
// linkCenter: true, // linkCenter: true,
fitView: true, fitView: true,
minZoom: 0.5,
maxZoom: 3,
fitViewPadding: [320, 320, 220, 320], fitViewPadding: [320, 320, 220, 320],
}); });
graph.on('node:click', handlerClick); graph.on('node:click', handlerClick);
@@ -386,40 +320,39 @@ function ExperimentText() {
}; };
}; };
return ( return (
<div className={pipelineContainer}>
<div className={styles.centerContainer}>
<div className={styles.buttonList}>
<div className={styles.allMessageItem}>
启动时间:{momnet(message.create_time).format('YYYY-MM-DD HH:mm:ss')}
</div>
<div className={styles.allMessageItem}>
执行时长:
{message.finish_time
? elapsedTime(new Date(message.create_time), new Date(message.finish_time))
: elapsedTime(new Date(message.create_time), new Date())}
</div>
<div className={styles.allMessageItem}>
状态:
<div
style={{
width: '8px',
height: '8px',
borderRadius: '50%',
marginRight: '6px',
backgroundColor: experimentStatusInfo[message.status]?.color,
}}
></div>
<span style={{ color: experimentStatusInfo[message.status]?.color }}>
{experimentStatusInfo[message.status]?.label}
</span>
</div>
<Button className={styles.param_button} onClick={openParamsModal}>
执行参数
</Button>
<div className={styles['pipeline-container']}>
<div className={styles['pipeline-container__top']}>
<div className={styles['pipeline-container__top__info']}>
启动时间:{formatDate(message.create_time)}
</div>
<div className={styles['pipeline-container__top__info']}>
执行时长:
{elapsedTime(message.create_time, message.finish_time)}
</div>
<div className={styles['pipeline-container__top__info']}>
状态:
<div
style={{
width: '8px',
height: '8px',
borderRadius: '50%',
marginRight: '6px',
backgroundColor: experimentStatusInfo[message.status]?.color,
}}
></div>
<span style={{ color: experimentStatusInfo[message.status]?.color }}>
{experimentStatusInfo[message.status]?.label}
</span>
</div> </div>
<div className={graphStyle} ref={graphRef} id={styles.graphStyle}></div>
<Button
className={styles['pipeline-container__top__param-button']}
onClick={openParamsModal}
>
执行参数
</Button>
</div> </div>
<Props ref={propsRef} onParentChange={formChange}></Props>
<div className={styles['pipeline-container__graph']} ref={graphRef}></div>
<Props ref={propsRef}></Props>
<ParamsModal <ParamsModal
open={paramsModalOpen} open={paramsModalOpen}
onCancel={closeParamsModal} onCancel={closeParamsModal}

+ 33
- 0
react-ui/src/pages/Experiment/training/index.less View File

@@ -0,0 +1,33 @@
.pipeline-container {
height: 100%;
background-color: #fff;

&__top {
display: flex;
align-items: center;
width: 100%;
height: 56px;
padding: 0 30px;
background: #ffffff;
box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09);

&__info {
display: flex;
align-items: center;
margin-right: 30px;
color: rgba(29, 29, 32, 0.8);
font-size: 15px;
}
&__param-button {
margin-right: 0;
margin-left: auto;
}
}
&__graph {
width: 100%;
height: calc(100% - 56px);
background-color: @background-color;
background-image: url(/assets/images/pipeline-canvas-back.png);
background-size: 100% 100%;
}
}

+ 37
- 0
react-ui/src/pages/Experiment/training/props.less View File

@@ -0,0 +1,37 @@
.experiment-drawer {
:global {
.ant-drawer-body {
overflow-y: hidden;
}
}

&__tabs {
height: calc(100% - 170px);
:global {
.ant-tabs-nav {
padding-left: 24px;
background-color: #f8fbff;
border: 1px solid #e0eaff;
}
.ant-tabs-content-holder {
overflow-y: auto;
}
}
}

&__info {
display: flex;
align-items: center;
margin-bottom: 15px;
padding-left: 24px;
color: @text-color;
font-size: 15px;
}

&__status-dot {
width: 8px;
height: 8px;
margin-right: 6px;
border-radius: 50%;
}
}

+ 171
- 0
react-ui/src/pages/Experiment/training/props.tsx View File

@@ -0,0 +1,171 @@
import { getNodeResult, getQueryByExperimentLog } from '@/services/experiment/index.js';
import { PipelineNodeModelSerialize } from '@/types';
import { elapsedTime, formatDate } from '@/utils/date';
import { to } from '@/utils/promise';
import { DatabaseOutlined, ProfileOutlined } from '@ant-design/icons';
import { Drawer, Form, Tabs } from 'antd';
import dayjs from 'dayjs';
import { forwardRef, useImperativeHandle, useState } from 'react';
import ExperimentParameter from '../components/ExperimentParameter';
import ExperimentResult from '../components/ExperimentResult';
import LogList from '../components/LogList';
import { experimentStatusInfo } from '../status';
import styles from './props.less';

export type ExperimentLog = {
log_type: 'normal' | 'resource'; // 日志类型
pod_name?: string; // 分布式名称
log_content?: string; // 日志内容
start_time?: string; // 日志开始时间
};

const Props = forwardRef((_, ref) => {
const [form] = Form.useForm();
const [experimentNodeData, setExperimentNodeData] = useState<PipelineNodeModelSerialize>(
{} as PipelineNodeModelSerialize,
);
const [experimentResults, setExperimentResults] = useState([]);
const [experimentLogList, setExperimentLogList] = useState<ExperimentLog[]>([]);

const items = [
{
key: '1',
label: '日志详情',
children: (
<LogList list={experimentLogList} status={experimentNodeData.experimentStatus}></LogList>
),
icon: <ProfileOutlined />,
},
{
key: '2',
label: '配置参数',
icon: <DatabaseOutlined />,
children: <ExperimentParameter form={form} nodeData={experimentNodeData} />,
},
{
key: '3',
label: '输出结果',
children: <ExperimentResult results={experimentResults}></ExperimentResult>,
icon: <ProfileOutlined />,
},
];
const [open, setOpen] = useState(false);
const onClose = () => {
setOpen(false);
};

// 获取实验日志
const getExperimentLog = async (params: any, start_time: number) => {
const [res] = await to(getQueryByExperimentLog(params));
if (res && res.data) {
const { log_type, pods, log_detail } = res.data;
if (log_type === 'normal') {
const list = [
{
...log_detail,
log_type,
},
];
setExperimentLogList(list);
} else if (log_type === 'resource') {
const list = pods.map((v: string) => ({
log_type,
pod_name: v,
log_content: '',
start_time,
}));
setExperimentLogList(list);
}
}
};

// 获取实验结果
const getExperimentResult = async (params: any) => {
const [res] = await to(getNodeResult(params));
if (res && res.data) {
setExperimentResults(res.data);
}
};

useImperativeHandle(ref, () => ({
showDrawer(e: any, id: string, message: any) {
setOpen(true);

// 获取实验参数
const model = e.item.getModel();
try {
const nodeData = {
...model,
in_parameters: JSON.parse(model.in_parameters),
out_parameters: JSON.parse(model.out_parameters),
control_strategy: JSON.parse(model.control_strategy),
};
setExperimentNodeData(nodeData);
form.setFieldsValue(nodeData);
} catch (error) {
console.log(error);
}

// 获取实验日志和实验结果
setExperimentLogList([]);
setExperimentResults([]);
if (e.item && e.item.getModel()) {
const model = e.item.getModel();
const start_time = dayjs(model.experimentStartTime).valueOf() * 1.0e6;
const params = {
task_id: model.id,
component_id: model.component_id,
name: message.argo_ins_name,
namespace: message.argo_ins_ns,
start_time: start_time,
};
getExperimentLog(params, start_time);
getExperimentResult({ id, node_id: model.id });
}
},
}));
return (
<Drawer
title="任务执行详情"
placement="right"
getContainer={false}
closeIcon={false}
onClose={onClose}
open={open}
width={520}
className={styles['experiment-drawer']}
destroyOnClose={true}
>
<div style={{ paddingTop: '15px' }}>
<div className={styles['experiment-drawer__info']}>
任务名称:{experimentNodeData.label}
</div>
<div className={styles['experiment-drawer__info']}>
执行状态:
<div
className={styles['experiment-drawer__status-dot']}
style={{
backgroundColor: experimentStatusInfo[experimentNodeData.experimentStatus]?.color,
}}
></div>
<span style={{ color: experimentStatusInfo[experimentNodeData.experimentStatus]?.color }}>
{experimentStatusInfo[experimentNodeData.experimentStatus]?.label}
</span>
</div>
<div className={styles['experiment-drawer__info']}>
启动时间:{formatDate(experimentNodeData.experimentStartTime)}
</div>
<div className={styles['experiment-drawer__info']}>
耗时:
{elapsedTime(
experimentNodeData.experimentStartTime,
experimentNodeData.experimentEndTime,
)}
</div>
</div>
<Tabs defaultActiveKey="1" items={items} className={styles['experiment-drawer__tabs']} />
</Drawer>
);
});

export default Props;

react-ui/src/pages/Mirror/create.less → react-ui/src/pages/Mirror/Create/index.less View File

@@ -9,9 +9,9 @@
background-color: white; background-color: white;
border-radius: 10px; border-radius: 10px;


&__title {
display: flex;
align-items: center;
&__type {
color: @text-color;
font-size: @font-size-input-lg;
} }
} }
} }

react-ui/src/pages/Mirror/create.tsx → react-ui/src/pages/Mirror/Create/index.tsx View File

@@ -11,13 +11,17 @@ import SubAreaTitle from '@/components/SubAreaTitle';
import { CommonTabKeys } from '@/enums'; import { CommonTabKeys } from '@/enums';
import { createMirrorReq } from '@/services/mirror'; import { createMirrorReq } from '@/services/mirror';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import { getSessionItemThenRemove, mirrorNameKey } from '@/utils/sessionStorage';
import { getFileListFromEvent } from '@/utils/ui';
import {
getSessionStorageItem,
mirrorNameKey,
removeSessionStorageItem,
} from '@/utils/sessionStorage';
import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui';
import { useNavigate } from '@umijs/max'; import { useNavigate } from '@umijs/max';
import { Button, Col, Form, Input, Row, Upload, UploadFile, message, type UploadProps } from 'antd';
import { App, Button, Col, Form, Input, Row, Upload, UploadFile, type UploadProps } from 'antd';
import { omit } from 'lodash'; import { omit } from 'lodash';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import styles from './create.less';
import styles from './index.less';


type FormData = { type FormData = {
name: string; name: string;
@@ -45,6 +49,7 @@ function MirrorCreate() {
const navgite = useNavigate(); const navgite = useNavigate();
const [form] = Form.useForm(); const [form] = Form.useForm();
const [nameDisabled, setNameDisabled] = useState(false); const [nameDisabled, setNameDisabled] = useState(false);
const { message } = App.useApp();


const uploadProps: UploadProps = { const uploadProps: UploadProps = {
action: '/api/mmp/image/upload', action: '/api/mmp/image/upload',
@@ -56,11 +61,14 @@ function MirrorCreate() {
}; };


useEffect(() => { useEffect(() => {
const name = getSessionItemThenRemove(mirrorNameKey);
const name = getSessionStorageItem(mirrorNameKey);
if (name) { if (name) {
form.setFieldValue('name', name); form.setFieldValue('name', name);
setNameDisabled(true); setNameDisabled(true);
} }
return () => {
removeSessionStorageItem(mirrorNameKey);
};
}, []); }, []);


// 创建公网、本地镜像 // 创建公网、本地镜像
@@ -75,30 +83,16 @@ function MirrorCreate() {
}; };
} else { } else {
const fileList = formData['fileList'] ?? []; const fileList = formData['fileList'] ?? [];
if (fileList.length === 0) {
message.error('请上传文件');
return;
if (validateUploadFiles(fileList)) {
const file = fileList[0];
params = {
...omit(formData, ['fileList', 'upload_type']),
path: file.response.data.url,
file_size: file.response.data.fileSize,
upload_type: 1,
image_type: 0,
};
} }
const file = fileList[0];
if (file.status === 'uploading') {
message.error('请等待文件上传完成');
return;
} else if (file.status === 'error') {
message.error('文件上传失败,请重新上传文件');
return;
}
if (!file.response || !file.response.data) {
message.error('文件上传失败,请重新上传文件');
return;
}

params = {
...omit(formData, ['fileList', 'upload_type']),
path: file.response.data.url,
file_size: file.response.data.fileSize,
upload_type: 1,
image_type: 0,
};
} }


const [res] = await to(createMirrorReq(params)); const [res] = await to(createMirrorReq(params));
@@ -118,6 +112,7 @@ function MirrorCreate() {
navgite(-1); navgite(-1);
}; };


// 上传前认证
const beforeUpload: UploadProps['beforeUpload'] = () => { const beforeUpload: UploadProps['beforeUpload'] = () => {
const fileList = form.getFieldValue('fileList'); const fileList = form.getFieldValue('fileList');
if (Array.isArray(fileList) && fileList.length >= 1) { if (Array.isArray(fileList) && fileList.length >= 1) {
@@ -134,12 +129,13 @@ function MirrorCreate() {
<div> <div>
<Form <Form
name="mirror-create" name="mirror-create"
labelCol={{ flex: '120px' }}
labelCol={{ flex: '130px' }}
wrapperCol={{ flex: 1 }} wrapperCol={{ flex: 1 }}
labelAlign="left" labelAlign="left"
form={form} form={form}
initialValues={{ upload_type: CommonTabKeys.Public }} initialValues={{ upload_type: CommonTabKeys.Public }}
onFinish={handleSubmit} onFinish={handleSubmit}
size="large"
> >
<SubAreaTitle <SubAreaTitle
title="基本信息" title="基本信息"
@@ -242,7 +238,7 @@ function MirrorCreate() {
<Row gutter={10}> <Row gutter={10}>
<Col span={10}> <Col span={10}>
<Form.Item label="仓库类型" required> <Form.Item label="仓库类型" required>
<span>公网</span>
<span className={styles['mirror-create__content__type']}>公网</span>
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>

react-ui/src/pages/Mirror/info.less → react-ui/src/pages/Mirror/Info/index.less View File


react-ui/src/pages/Mirror/info.tsx → react-ui/src/pages/Mirror/Info/index.tsx View File

@@ -16,6 +16,7 @@ import {
getMirrorVersionListReq, getMirrorVersionListReq,
} from '@/services/mirror'; } from '@/services/mirror';
import themes from '@/styles/theme.less'; import themes from '@/styles/theme.less';
import { formatDate } from '@/utils/date';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import { mirrorNameKey, setSessionStorageItem } from '@/utils/sessionStorage'; import { mirrorNameKey, setSessionStorageItem } from '@/utils/sessionStorage';
import { modalConfirm } from '@/utils/ui'; import { modalConfirm } from '@/utils/ui';
@@ -32,10 +33,9 @@ import {
type TableProps, type TableProps,
} from 'antd'; } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import dayjs from 'dayjs';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import MirrorStatusCell from './components/MirrorStatusCell';
import styles from './info.less';
import MirrorStatusCell from '../components/MirrorStatusCell';
import styles from './index.less';


type MirrorInfoData = { type MirrorInfoData = {
name?: string; name?: string;
@@ -68,11 +68,13 @@ function MirrorInfo() {
pageSize: 10, pageSize: 10,
}, },
); );
const isPublic = searchParams.get('isPublic') === 'true';
const { message } = App.useApp(); const { message } = App.useApp();
const isPublic = searchParams.get('isPublic') === 'true';

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

useEffect(() => { useEffect(() => {
getMirrorVersionList(); getMirrorVersionList();
}, [pagination]); }, [pagination]);
@@ -83,8 +85,7 @@ function MirrorInfo() {
const [res] = await to(getMirrorInfoReq(id)); const [res] = await to(getMirrorInfoReq(id));
if (res && res.data) { if (res && res.data) {
const { name = '', description = '', version_count = '', create_time: time } = res.data; const { name = '', description = '', version_count = '', create_time: time } = res.data;
let create_time =
time && dayjs(time).isValid() ? dayjs(time).format('YYYY-MM-DD HH:mm:ss') : '--';
const create_time = formatDate(time);
setMirrorInfo({ setMirrorInfo({
name, name,
description, description,
@@ -161,13 +162,13 @@ function MirrorInfo() {
dataIndex: 'tag_name', dataIndex: 'tag_name',
key: 'tag_name', key: 'tag_name',
width: '25%', width: '25%',
render: CommonTableCell,
render: CommonTableCell(),
}, },
{ {
title: '镜像地址', title: '镜像地址',
dataIndex: 'url', dataIndex: 'url',
key: 'url', key: 'url',
render: CommonTableCell,
render: CommonTableCell(),
}, },
{ {
title: '状态', title: '状态',
@@ -181,7 +182,7 @@ function MirrorInfo() {
dataIndex: 'file_size', dataIndex: 'file_size',
key: 'file_size', key: 'file_size',
width: 150, width: 150,
render: CommonTableCell,
render: CommonTableCell(),
}, },
{ {
title: '创建时间', title: '创建时间',

react-ui/src/pages/Mirror/list.less → react-ui/src/pages/Mirror/List/index.less View File

@@ -3,7 +3,10 @@
&__tabs-container { &__tabs-container {
height: 50px; height: 50px;
padding-left: 27px; padding-left: 27px;
background-image: url('../../assets/img/page-title-bg.png');
background-image: url(@/assets/img/page-title-bg.png);
background-repeat: no-repeat;
background-position: top center;
background-size: 100% 100%;
} }


&__content { &__content {
@@ -20,7 +23,7 @@
} }


&__table { &__table {
height: calc(100% - 34px - 28px);
height: calc(100% - 32px - 28px);
margin-top: 28px; margin-top: 28px;
} }
} }

react-ui/src/pages/Mirror/list.tsx → react-ui/src/pages/Mirror/List/index.tsx View File

@@ -11,15 +11,16 @@ import { useCacheState } from '@/hooks/pageCacheState';
import { deleteMirrorReq, getMirrorListReq } from '@/services/mirror'; import { deleteMirrorReq, getMirrorListReq } from '@/services/mirror';
import themes from '@/styles/theme.less'; import themes from '@/styles/theme.less';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import { mirrorNameKey, setSessionStorageItem } from '@/utils/sessionStorage';
import { modalConfirm } from '@/utils/ui'; import { modalConfirm } from '@/utils/ui';
import { useNavigate } from '@umijs/max'; import { useNavigate } from '@umijs/max';
import { import {
App,
Button, Button,
ConfigProvider, ConfigProvider,
Input, Input,
Table, Table,
Tabs, Tabs,
message,
type TablePaginationConfig, type TablePaginationConfig,
type TableProps, type TableProps,
type TabsProps, type TabsProps,
@@ -27,7 +28,7 @@ import {
import { type SearchProps } from 'antd/es/input'; import { type SearchProps } from 'antd/es/input';
import classNames from 'classnames'; import classNames from 'classnames';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import styles from './list.less';
import styles from './index.less';


const mirrorTabItems = [ const mirrorTabItems = [
{ {
@@ -54,6 +55,7 @@ function MirrorList() {
const [cacheState, setCacheState] = useCacheState(); const [cacheState, setCacheState] = useCacheState();
const [activeTab, setActiveTab] = useState<string>(cacheState?.activeTab ?? CommonTabKeys.Public); const [activeTab, setActiveTab] = useState<string>(cacheState?.activeTab ?? CommonTabKeys.Public);
const [searchText, setSearchText] = useState(cacheState?.searchText); const [searchText, setSearchText] = useState(cacheState?.searchText);
const [inputText, setInputText] = useState(cacheState?.searchText);
const [tableData, setTableData] = useState<MirrorData[]>([]); const [tableData, setTableData] = useState<MirrorData[]>([]);
const [total, setTotal] = useState(0); const [total, setTotal] = useState(0);
const [pagination, setPagination] = useState<TablePaginationConfig>( const [pagination, setPagination] = useState<TablePaginationConfig>(
@@ -62,14 +64,16 @@ function MirrorList() {
pageSize: 10, pageSize: 10,
}, },
); );
const { message } = App.useApp();


useEffect(() => { useEffect(() => {
getMirrorList(); getMirrorList();
}, [activeTab, pagination]);
}, [activeTab, pagination, searchText]);


// 切换 Tab,重置数据 // 切换 Tab,重置数据
const hanleTabChange: TabsProps['onChange'] = (value) => { const hanleTabChange: TabsProps['onChange'] = (value) => {
setSearchText(''); setSearchText('');
setInputText('');
setPagination({ setPagination({
current: 1, current: 1,
pageSize: 10, pageSize: 10,
@@ -78,16 +82,16 @@ function MirrorList() {
setTableData([]); setTableData([]);
setActiveTab(value); setActiveTab(value);
}; };

// 获取镜像列表 // 获取镜像列表
const getMirrorList = async (params?: Record<string, any>) => {
const reqParams = {
const getMirrorList = async () => {
const params: Record<string, any> = {
page: pagination.current! - 1, page: pagination.current! - 1,
size: pagination.pageSize, size: pagination.pageSize,
name: searchText, name: searchText,
image_type: activeTab === CommonTabKeys.Public ? 1 : 0, image_type: activeTab === CommonTabKeys.Public ? 1 : 0,
...params,
}; };
const [res] = await to(getMirrorListReq(reqParams));
const [res] = await to(getMirrorListReq(params));
if (res && res.data) { if (res && res.data) {
const { content = [], totalElements = 0 } = res.data; const { content = [], totalElements = 0 } = res.data;
setTableData(content); setTableData(content);
@@ -116,10 +120,7 @@ function MirrorList() {


// 搜索 // 搜索
const onSearch: SearchProps['onSearch'] = (value) => { const onSearch: SearchProps['onSearch'] = (value) => {
// 带参数是为了点清除时,searchText 更新不及时的问题
getMirrorList({
name: value,
});
setSearchText(value);
}; };


// 查看详情 // 查看详情
@@ -146,6 +147,7 @@ function MirrorList() {
// 创建镜像 // 创建镜像
const createMirror = () => { const createMirror = () => {
navigate(`/dataset/mirror/create`); navigate(`/dataset/mirror/create`);
setSessionStorageItem(mirrorNameKey, '');
setCacheState({ setCacheState({
activeTab, activeTab,
pagination, pagination,
@@ -167,27 +169,28 @@ function MirrorList() {
dataIndex: 'name', dataIndex: 'name',
key: 'name', key: 'name',
width: '30%', width: '30%',
render: CommonTableCell,
render: CommonTableCell(),
}, },
{ {
title: '版本数据', title: '版本数据',
dataIndex: 'version_count', dataIndex: 'version_count',
key: 'version_count', key: 'version_count',
width: 100,
render: CommonTableCell,
width: '15%',
render: CommonTableCell(),
}, },
{ {
title: '镜像描述', title: '镜像描述',
dataIndex: 'description', dataIndex: 'description',
key: 'description', key: 'description',
render: CommonTableCell,
ellipsis: true,
width: '35%',
render: CommonTableCell(true),
ellipsis: { showTitle: false },
}, },
{ {
title: '创建时间', title: '创建时间',
dataIndex: 'create_time', dataIndex: 'create_time',
key: 'create_time', key: 'create_time',
width: 200,
width: '20%',
render: DateTableCell, render: DateTableCell,
}, },
{ {
@@ -233,12 +236,7 @@ function MirrorList() {
return ( return (
<div className={styles['mirror-list']}> <div className={styles['mirror-list']}>
<div className={styles['mirror-list__tabs-container']}> <div className={styles['mirror-list__tabs-container']}>
<Tabs
activeKey={activeTab}
items={mirrorTabItems}
onChange={hanleTabChange}
className={styles['model-tabs']}
/>
<Tabs activeKey={activeTab} items={mirrorTabItems} onChange={hanleTabChange} />
</div> </div>
<div className={styles['mirror-list__content']}> <div className={styles['mirror-list__content']}>
<div className={styles['mirror-list__content__filter']}> <div className={styles['mirror-list__content__filter']}>
@@ -246,9 +244,9 @@ function MirrorList() {
placeholder="按数据集名称筛选" placeholder="按数据集名称筛选"
allowClear allowClear
onSearch={onSearch} onSearch={onSearch}
onChange={(e) => setSearchText(e.target.value)}
onChange={(e) => setInputText(e.target.value)}
style={{ width: 300 }} style={{ width: 300 }}
value={searchText}
value={inputText}
/> />
{activeTab === CommonTabKeys.Private && ( {activeTab === CommonTabKeys.Private && (
<Button <Button

+ 3
- 6
react-ui/src/pages/Mirror/components/MirrorStatusCell/index.tsx View File

@@ -1,20 +1,17 @@
/* /*
* @Author: 赵伟 * @Author: 赵伟
* @Date: 2024-04-18 18:35:41 * @Date: 2024-04-18 18:35:41
* @Description:
* @Description: 镜像状态组件
*/ */
import { MirrorVersionStatus } from '@/enums'; import { MirrorVersionStatus } from '@/enums';
import styles from './index.less'; import styles from './index.less';


type MirrorVersionStatusKeys = keyof typeof MirrorVersionStatus;
type MirrorVersionStatusValues = (typeof MirrorVersionStatus)[MirrorVersionStatusKeys];

export type MirrorVersionStatusInfo = { export type MirrorVersionStatusInfo = {
text: string; text: string;
classname: string; classname: string;
}; };


const statusInfo: Record<MirrorVersionStatusValues, MirrorVersionStatusInfo> = {
const statusInfo: Record<MirrorVersionStatus, MirrorVersionStatusInfo> = {
[MirrorVersionStatus.Building]: { [MirrorVersionStatus.Building]: {
text: '构建中', text: '构建中',
classname: styles['mirror-status-cell'], classname: styles['mirror-status-cell'],
@@ -29,7 +26,7 @@ const statusInfo: Record<MirrorVersionStatusValues, MirrorVersionStatusInfo> = {
}, },
}; };


function MirrorStatusCell(status: MirrorVersionStatus) {
function MirrorStatusCell(status?: MirrorVersionStatus | null) {
if (status === null || status === undefined || !statusInfo[status]) { if (status === null || status === undefined || !statusInfo[status]) {
return <span>--</span>; return <span>--</span>;
} }


+ 5
- 87
react-ui/src/pages/Model/index.jsx View File

@@ -1,89 +1,7 @@
import { Form, Input, Tabs } from 'antd';
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import Styles from './index.less';
import PersonalData from './personalData';
import PublicData from './publicData';
// import {getModelList} from '@/services/dataset/index.js'
const { Search } = Input;
const { TabPane } = Tabs;
const leftdataList = [1, 2, 3];
import ResourcePage from '@/pages/Dataset/components/ResourcePage';
import { ResourceType } from '@/pages/Dataset/types';


const Dataset = () => {
const [queryFlow, setQueryFlow] = useState({
page: 0,
size: 10,
name: null,
});
const navgite = useNavigate();
const [isModalOpen, setIsModalOpen] = useState(false);
const [datasetList, setDatasetList] = useState([]);
const [total, setTotal] = useState(0);
const [form] = Form.useForm();
const [dialogTitle, setDialogTitle] = useState('新建数据');
// const getModelLists=()=>{
// getModelList(queryFlow).then(ret=>{
// console.log(ret);
// if(ret.code==200){
// setDatasetList(ret.data.content)
// setTotal(ret.data.totalElements)
// }
// })
// }

const showModal = () => {
form.resetFields();
setDialogTitle('新建数据集');
setIsModalOpen(true);
};
const handleOk = () => {
console.log(1111);
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
const onFinish = (values) => {};
const routeToIntro = (e, record) => {
e.stopPropagation();
navgite({ pathname: '/dataset/dataset' });
};
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
};
useEffect(() => {
return () => {};
}, []);
return (
<div className={Styles.datasetBox}>
<div className={Styles.datasetTopBox}></div>
<div className={Styles.datasetAllBox}>
<Tabs defaultActiveKey="1">
<TabPane
tab="模型广场"
key="1"
icon={
<svg className="icon" style={{ width: '14px', height: '14px' }} aria-hidden="true">
<use xlinkHref="#icon-shujujiguangchang"></use>
</svg>
}
>
<PublicData />
</TabPane>
<TabPane
tab="个人模型"
key="2"
icon={
<svg className="icon" style={{ width: '14px', height: '14px' }} aria-hidden="true">
<use xlinkHref="#icon-gerenshujuji"></use>
</svg>
}
>
<PersonalData />
</TabPane>
</Tabs>
</div>
</div>
);
const ModelPage = () => {
return <ResourcePage resourceType={ResourceType.Model} />;
}; };
export default Dataset;
export default ModelPage;

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

@@ -1,327 +0,0 @@
.datasetTopBox {
display: flex;
align-items: center;
width: 100%;
height: 49px;
padding: 0 30px;
padding-right: 30px;
background-image: url(/assets/images/pipeline-back.png);
background-size: 100% 100%;
}
.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-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;
}
}
}
.datasetAllBox {
:global {
.ant-tabs-nav .ant-tabs-nav-wrap {
margin: -48px 0 0 30px;
}
}
}
.plusButton {
margin: 0 18px 0 20px;
}

.datasetCneterBox {
display: flex;
justify-content: space-between;
width: 100%;
height: 87.5vh;

.datasetCneterLeftBox {
width: 340px;
height: 100%;
margin-right: 10px;
padding-top: 15px;
background: #ffffff;
box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09);
.custTab {
display: flex;
height: 32px;
border-bottom: 1px solid #e0eaff;
.tabItem {
width: 52px;
height: 100%;
color: #808080;
font-size: 15px;
text-align: center;
cursor: pointer;
}
}
.leftContentBox {
max-height: 80vh;
padding: 15px 20px;
overflow-x: hidden;
overflow-y: auto;
.itemTitle {
margin-bottom: 15px;
color: #1d1d20;
font-size: 14px;
}
.itemBox {
display: flex;
flex-wrap: wrap;
align-content: start;
width: 110%;
.messageBox {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
width: 92px;
height: 62px;
margin: 0 12px 20px 0;
padding: 11px 0px 7px 0px;
color: #1d1d20;
font-size: 12px;
border: 1px solid;
border-color: rgba(22, 100, 255, 0.05);
border-radius: 4px;
cursor: pointer;
.ptIcon {
display: block;
}
.hoverIcon {
display: none;
}
.messageText {
width: 65px;
overflow: hidden;
white-space: nowrap;
text-align: center;
text-overflow: ellipsis;
transition: all 0.2s;
}
}
.messageBox:hover {
background: rgba(22, 100, 255, 0.03);
border: 1px solid;
border-color: #1664ff;
.ptIcon {
display: none;
}
.hoverIcon {
display: block;
}
}
.active {
background: rgba(22, 100, 255, 0.03) !important;
border: 1px solid !important;
border-color: #1664ff !important;
.ptIcon {
display: none !important;
}
.hoverIcon {
display: block !important;
}
}
}
}
}
.datasetCneterRightBox {
display: flex;
flex: 1;
flex-direction: column;
height: 100%;
overflow-y: auto;
padding: 22px 30px 26px 30px;
background: #ffffff;
box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09);
.dataSource {
display: flex;
align-items: center;
justify-content: space-between;
height: 32px;
margin-bottom: 30px;
color: rgba(29, 29, 32, 0.8);
font-size: 15px;
}
.dataContent {
display: flex;
flex: 1;
flex-wrap: wrap;
align-content: flex-start;
width: 100%;
.dataItem {
position: relative;
width: 23.8%;
height:164px;
margin: 0 20px 25px 0;
background: #ffffff;
border: 1px solid;
border-color: #eaeaea;
border-radius: 4px;
cursor: pointer;
.dropdown{
position: absolute;
right: 20px;
top: 15px;
}
.itemText {
position: absolute;
top: 20px;
left: 20px;
height: 6px;
color: #1d1d20;
font-size: 16px;
font-family: 'Alibaba';
line-height: 0px;
background: linear-gradient(
to right,
rgba(22, 100, 255, 0.3) 0,
rgba(22, 100, 255, 0) 100%
);
}
.itemDescripition {
position: absolute;
top: 57px;
left: 20px;
display: -webkit-box;
padding-right: 28px;
overflow: hidden;
color: #575757;
font-size: 14px;
word-break: break-all;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.itemTime {
position: absolute;
bottom: 22px;
left: 20px;
display: flex;
align-items: center;
color: #808080;
font-size: 13px;
}
.itemIcon {
position: absolute;
right: 20px;
bottom: 22px;
display: flex;
align-items: center;
color: #808080;
font-size: 13px;
}
}
.dataItem:hover {
border-color: #1664ff;
box-shadow: 0px 0px 6px 1px rgba(0, 0, 0, 0.1);
}
.dataItem:hover .itemText {
color: #1664ff;
}
}
}
}
.tipContent{
color: #c73131;
margin-top: 5px;
}
.modal {
:global {
.ant-modal-content {
width: 825px;
padding: 20px 67px;
background-image: url(/assets/images/modal-back.png);
background-repeat: no-repeat;
background-position: top center;
background-size: 100%;
border-radius: 21px;
}
.ant-modal-header {
margin: 20px 0;
background-color: transparent;
}
.ant-input {
height: 40px;
border-color: #e6e6e6;
}
.ant-form-item .ant-form-item-label > label {
color: rgba(29, 29, 32, 0.8);
}
.ant-modal-footer {
display: flex;
justify-content: center;
margin: 40px 0 30px 0;
}
.ant-btn {
width: 110px;
height: 40px;
font-size: 18px;
background: rgba(22, 100, 255, 0.06);
border-color: transparent;
border-radius: 10px;
}
.ant-btn-primary {
background: #1664ff;
}
}
}

react-ui/src/pages/Model/modelIntro.jsx → react-ui/src/pages/Model/intro.jsx View File

@@ -1,61 +1,34 @@
import { getAccessToken } from '@/access';
import KFIcon from '@/components/KFIcon'; import KFIcon from '@/components/KFIcon';
import AddVersionModal from '@/pages/Dataset/components/AddVersionModal';
import { ResourceType } from '@/pages/Dataset/types';
import { import {
addModelsVersionDetail,
deleteModelVersion, deleteModelVersion,
getModelById, getModelById,
getModelVersionIdList, getModelVersionIdList,
getModelVersionsById, getModelVersionsById,
} from '@/services/dataset/index.js'; } from '@/services/dataset/index.js';
import { formatDate } from '@/utils/date';
import { downLoadZip } from '@/utils/downloadfile'; import { downLoadZip } from '@/utils/downloadfile';
import { UploadOutlined } from '@ant-design/icons';
import { Button, Form, Input, Modal, Select, Table, Tabs, Upload, message } from 'antd';
import moment from 'moment';
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 { useEffect, useRef, useState } from 'react';
import { useParams } from 'react-router-dom';
import Styles from './index.less';
import Styles from './intro.less';
const { Search } = Input; const { Search } = Input;
const { TabPane } = Tabs; const { TabPane } = Tabs;


const Dataset = () => { const Dataset = () => {
const props = {
action: '/api/mmp/dataset/upload',
// headers: {
// 'X-Requested-With': null
// },
headers: {
Authorization: getAccessToken(),
'X-Requested-With': null,
},
onChange({ file, fileList }) {
if (file.status !== 'uploading') {
console.log(file, fileList);
setFormList(
fileList.map((item) => {
return {
...form.getFieldsValue(),
models_id: locationParams.id,
file_name: item.response.code === 200 ? item.response.data[0].fileName : null,
file_size: item.response.code === 200 ? item.response.data[0].fileSize : null,
url: item.response.code === 200 ? item.response.data[0].url : null,
};
}),
);
}
},
defaultFileList: [],
};
const [form] = Form.useForm();
const [formList, setFormList] = useState([]); const [formList, setFormList] = useState([]);
const [dialogTitle, setDialogTitle] = useState('新建版本');
const [isModalOpen, setIsModalOpen] = useState(false);
const [datasetDetailObj, setDatasetDetailObj] = useState({}); const [datasetDetailObj, setDatasetDetailObj] = useState({});
const [version, setVersion] = useState(null); const [version, setVersion] = useState(null);
const [versionList, setVersionList] = useState([]); const [versionList, setVersionList] = useState([]);
const locationParams = useParams(); //新版本获取路由参数接口 const locationParams = useParams(); //新版本获取路由参数接口
console.log(locationParams);
const [searchParams] = useSearchParams();
const [wordList, setWordList] = useState([]); const [wordList, setWordList] = useState([]);
const [uuid, setUuid] = useState(Date.now());
const { message } = App.useApp();
const isPublic = searchParams.get('isPublic') === 'true';

const getModelByDetail = () => { const getModelByDetail = () => {
getModelById(locationParams.id).then((ret) => { getModelById(locationParams.id).then((ret) => {
console.log(ret); console.log(ret);
@@ -76,6 +49,9 @@ const Dataset = () => {
); );
setVersion(ret.data[0]); setVersion(ret.data[0]);
getModelVersions({ version: ret.data[0], models_id: locationParams.id }); getModelVersions({ version: ret.data[0], models_id: locationParams.id });
} else {
setVersion(null);
setWordList([]);
} }
}); });
}; };
@@ -85,29 +61,21 @@ const Dataset = () => {
return () => {}; return () => {};
}, []); }, []);
const showModal = () => { const showModal = () => {
form.resetFields();
form.setFieldsValue({ name: datasetDetailObj.name });
setDialogTitle('创建新版本');
setUuid(Date.now());
setIsModalOpen(true);
};
const handleCancel = () => {
setIsModalOpen(false);
const { close } = openAntdModal(AddVersionModal, {
resourceType: ResourceType.Model,
resourceId: locationParams.id,
initialName: datasetDetailObj.name,
onOk: () => {
getModelVersionsList();
close();
},
});
}; };

const deleteDataset = () => { const deleteDataset = () => {
Modal.confirm({
title: (
<div>
<img
src="/assets/images/delete-icon.png"
style={{ width: '120px', marginBottom: '24px' }}
alt=""
/>
<div style={{ color: '#1d1d20', fontSize: '16px' }}>删除后,该模型版本将不可恢复</div>
</div>
),
content: <div style={{ color: '#1d1d20', fontSize: '15px' }}>是否确认删除?</div>,
modalConfirm({
title: '删除后,该版本将不可恢复',
content: '是否确认删除?',
okText: '确认', okText: '确认',
cancelText: '取消', cancelText: '取消',


@@ -119,13 +87,7 @@ const Dataset = () => {
}, },
}); });
}; };
const onFinish = () => {
addModelsVersionDetail(formList).then((ret) => {
getModelVersionsList();
setIsModalOpen(false);
message.success('创建成功');
});
};

const getModelVersions = (params) => { const getModelVersions = (params) => {
getModelVersionIdList(params).then((ret) => { getModelVersionIdList(params).then((ret) => {
setWordList(ret?.data?.content ?? []); setWordList(ret?.data?.content ?? []);
@@ -151,9 +113,7 @@ const Dataset = () => {
setVersion(''); setVersion('');
} }
}; };
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
};

const columns = [ const columns = [
{ {
title: '序号', title: '序号',
@@ -185,7 +145,7 @@ const Dataset = () => {
title: '更新时间', title: '更新时间',
dataIndex: 'update_time', dataIndex: 'update_time',
key: 'update_time', key: 'update_time',
render: (text) => <span>{moment(text).format('YYYY-MM-DD HH:mm:ss')}</span>,
render: (text) => <span>{formatDate(text)}</span>,
}, },
{ {
title: '操作', title: '操作',
@@ -262,15 +222,18 @@ const Dataset = () => {
<div <div
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}
> >
<Button
type="default"
className={Styles.plusButton}
style={{ margin: '0 20px 0 0' }}
onClick={deleteDataset}
icon={<KFIcon type="icon-shanchu" />}
>
删除
</Button>
{!isPublic && (
<Button
type="default"
className={Styles.plusButton}
style={{ margin: '0 20px 0 0' }}
onClick={deleteDataset}
icon={<KFIcon type="icon-shanchu" />}
>
删除
</Button>
)}

<Button <Button
type="default" type="default"
className={Styles.plusButton} className={Styles.plusButton}
@@ -293,104 +256,6 @@ const Dataset = () => {
</TabPane> </TabPane>
</Tabs> </Tabs>
</div> </div>
<Modal
title={
<div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}>
<img
style={{ width: '20px', marginRight: '10px' }}
src={`/assets/images/pipeline-edit-icon.png`}
alt=""
/>
{dialogTitle}
</div>
}
open={isModalOpen}
className={Styles.modal}
okButtonProps={{
htmlType: 'submit',
form: 'form',
}}
onCancel={handleCancel}
>
<Form
name="form"
form={form}
layout="vertical"
initialValues={{
remember: true,
}}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
>
<Form.Item
label="模型名称"
name="name"
rules={[
{
required: true,
message: '请输入模型名称',
},
]}
>
<Input disabled placeholder="请输入模型名称" />
</Form.Item>
<Form.Item
label="模型版本"
name="version"
rules={[
{
required: true,
message: '请输入模型版本',
},
]}
>
<Input placeholder="请输入模型版本" maxLength={64} showCount allowClear />
</Form.Item>
<Form.Item
label="版本描述"
name="description"
rules={[
{
required: true,
message: '请输入版本描述',
},
]}
>
<Input.TextArea
placeholder="请输入版本描述"
autoSize={{ minRows: 2, maxRows: 6 }}
maxLength={256}
showCount
allowClear
/>
</Form.Item>
<Form.Item
label="模型文件"
name="dataset_version_vos"
rules={[
{
required: true,
message: '请上传模型文件',
},
]}
>
<Upload {...props} data={{ uuid: uuid }}>
<Button
style={{
fontSize: '14px',
border: '1px solid',
borderColor: '#1664ff',
background: '#fff',
}}
icon={<UploadOutlined style={{ color: '#1664ff' }} />}
>
上传文件
</Button>
</Upload>
</Form.Item>
</Form>
</Modal>
</div> </div>
); );
}; };

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

@@ -0,0 +1,80 @@
.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;
}

+ 0
- 525
react-ui/src/pages/Model/personalData.jsx View File

@@ -1,525 +0,0 @@
import { getAccessToken } from '@/access';
import clock from '@/assets/img/clock.png';
import creatByImg from '@/assets/img/creatBy.png';
import deleteIcon from '@/assets/img/delete-icon.png';
import KFIcon from '@/components/KFIcon';
import { addModel, deleteModel, getAssetIcon, getModelList } from '@/services/dataset/index.js';
import { modalConfirm } from '@/utils/ui';
import { UploadOutlined } from '@ant-design/icons';
import { Button, Form, Input, Modal, Pagination, Select, Upload, message } from 'antd';
import moment from 'moment';
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import Styles from './index.less';
const { Search } = Input;

const leftdataList = [1, 2, 3];

const PublicData = () => {
const props = {
action: '/api/mmp/dataset/upload',
// headers: {
// 'X-Requested-With': null
// },
headers: {
Authorization: getAccessToken(),
'X-Requested-With': null,
},
onChange({ file, fileList }) {
if (file.status !== 'uploading') {
console.log(file, fileList);
form.setFieldsValue({
models_version_vos: fileList.map((item) => {
const data = item.response.data[0];
return {
file_name: data.fileName,
file_size: data.fileSize,
url: data.url,
};
}),
});
}
},
defaultFileList: [],
};
const [queryFlow, setQueryFlow] = useState({
page: 0,
size: 20,
name: null,
available_range: 0,
});
const navgite = useNavigate();
const [iconParams, setIconParams] = useState({
name: null,
page: 0,
size: 10000,
});
const [activeType, setActiveType] = useState(null);
const [activeTag, setActiveTag] = useState(null);
const [modelTypeList, setmodelTypeList] = useState([]);
const [modelDirectionList, setmodelDirectionList] = useState([]);
const [isModalOpen, setIsModalOpen] = useState(false);
const [datasetList, setDatasetList] = useState([]);
const [total, setTotal] = useState(0);
const [form] = Form.useForm();
const [dialogTitle, setDialogTitle] = useState('新建模型');
const [uuid, setUuid] = useState(Date.now());
const getModelLists = (queryFlow) => {
getModelList(queryFlow).then((ret) => {
console.log(ret);
if (ret.code == 200) {
setDatasetList(ret.data.content);
setTotal(ret.data.totalElements);
}
});
};

const showModal = () => {
form.resetFields();
setDialogTitle('新建模型');
setUuid(Date.now());
setIsModalOpen(true);
};
const getAssetIconList = (params) => {
getAssetIcon(params).then((ret) => {
console.log(ret);
if (ret.code == 200 && ret.data.content && ret.data.content.length > 0) {
setmodelTypeList(ret.data.content.filter((item) => item.category_id == 3));
setmodelDirectionList(ret.data.content.filter((item) => item.category_id == 4));
} else {
setmodelTypeList([]);
setmodelDirectionList([]);
}
});
};
const onSearch = (values) => {
console.log(values);
getAssetIconList({ ...iconParams, name: values });
};
const nameSearch = (values) => {
console.log(values);
getModelLists({ ...queryFlow, name: values });
};
const handleOk = () => {
console.log(1111);
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
const onFinish = (values) => {
const params = {
...values,
available_range: 0,
};
addModel(values).then((ret) => {
console.log(ret);
getModelLists(queryFlow);
setIsModalOpen(false);
});
};

const chooseModelType = (val, item) => {
console.log(val, item);
if (item.id == queryFlow.model_type) {
setActiveType('');
setQueryFlow({ ...queryFlow, model_type: null });
getModelLists({ ...queryFlow, model_type: null });
} else {
setActiveType(item.id);
setQueryFlow({ ...queryFlow, model_type: item.id });
getModelLists({ ...queryFlow, model_type: item.id });
}

// setQueryFlow({...queryFlow,data_type:item.path},()=>{
// getDatasetlist()
// })
};
const chooseModelTag = (val, item) => {
if (item.id == queryFlow.model_tag) {
setActiveTag('');
setQueryFlow({ ...queryFlow, model_tag: null });
getModelLists({ ...queryFlow, model_tag: null });
} else {
setActiveTag(item.id);
setQueryFlow({ ...queryFlow, model_tag: item.id });
getModelLists({ ...queryFlow, model_tag: item.id });
}
// setQueryFlow({...queryFlow,data_type:item.path},()=>{
// getDatasetlist()
// })
};
const routeToIntro = (e, record) => {
e.stopPropagation();
console.log(record);
navgite({ pathname: `/dataset/model/${record.id}` });
};
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
};
const onPageChange = (pageNum, pageSize) => {
console.log(pageNum, pageSize);
setQueryFlow({ ...queryFlow, page: pageNum - 1, size: pageSize });
getModelLists({ ...queryFlow, page: pageNum - 1, size: pageSize });
};
useEffect(() => {
getAssetIconList(iconParams);
getModelLists(queryFlow);
return () => {};
}, []);
return (
<>
<div className={Styles.datasetCneterBox}>
<div className={Styles.datasetCneterLeftBox}>
<div className={Styles.leftContentBox}>
<Search
placeholder="搜索"
allowClear
onSearch={onSearch}
style={{
width: 300,
marginBottom: '15px',
}}
/>
<div className={Styles.itemTitle}>模型框架</div>
<div className={Styles.itemBox}>
{modelTypeList && modelTypeList.length > 0
? modelTypeList.map((item) => {
return (
<div>
<div
className={[
Styles.messageBox,
item.id === activeType ? Styles.active : null,
].join(' ')}
onClick={(e) => {
chooseModelType(e, item);
}}
>
<img
className={Styles.ptIcon}
style={{ width: '22px' }}
src={`/assets/images/model/${item.path}.png`}
alt=""
/>
<img
className={Styles.hoverIcon}
style={{ width: '22px' }}
src={`/assets/images/model/${item.path}-hover.png`}
alt=""
/>
<span
className={Styles.messageText}
onClick={(e) => {
chooseModelTag(e, item);
}}
>
{item.name}
</span>
</div>
</div>
);
})
: ''}
</div>
<div className={Styles.itemTitle}>模型能力</div>
<div className={Styles.itemBox}>
{modelDirectionList && modelDirectionList.length > 0
? modelDirectionList.map((item) => {
return (
<div>
<div
className={[
Styles.messageBox,
item.id === activeTag ? Styles.active : null,
].join(' ')}
onClick={(e) => {
chooseModelTag(e, item);
}}
>
<img
className={Styles.ptIcon}
style={{ width: '22px' }}
src={`/assets/images/model/${item.path}.png`}
alt=""
/>
<img
className={Styles.hoverIcon}
style={{ width: '22px' }}
src={`/assets/images/model/${item.path}-hover.png`}
alt=""
/>
<span className={Styles.messageText}>{item.name}</span>
</div>
</div>
);
})
: ''}
</div>
</div>
</div>
<div className={Styles.datasetCneterRightBox}>
<div className={Styles.dataSource}>
<span>数据总数:{total}个</span>
<div>
<Search
placeholder="按模型名称筛选"
allowClear
onSearch={nameSearch}
style={{
width: 300,
}}
/>
<Button
type="default"
style={{ marginLeft: '20px' }}
onClick={showModal}
icon={<KFIcon type="icon-xinjian2" />}
>
模型注册
</Button>
</div>
</div>
<div className={Styles.dataContent}>
{datasetList && datasetList.length > 0
? datasetList.map((item) => {
return (
<div
className={Styles.dataItem}
onClick={(e) => {
routeToIntro(e, item);
}}
>
<span className={Styles.itemText}>{item.name}</span>
<img
onClick={(e) => {
e.stopPropagation();
modalConfirm({
title: '确定删除该条模型实例吗?',
onOk: () => {
deleteModel(item.id).then((ret) => {
if (ret.code === 200) {
message.success('删除成功');
getModelLists(queryFlow);
} else {
message.error(ret.msg);
}
});
},
});
}}
className={Styles.dropdown}
style={{ width: '17px', marginRight: '6px' }}
src={deleteIcon}
alt=""
/>
{/* <Dropdown
className={Styles.dropdown}
key={item.name}
menu={{
items: [
{
label: '详情',
key: 'detail',
},
{
label: '删除',
key: 'delete',
},
],
onClick: (e) => {
console.log(e);
if (e.key === 'detail') {
routeToIntro(e, item);
} else if (e.key === 'delete') {
modalConfirm({
title: '确定删除该条模型实例吗?',
onOk: () => {
deleteModel(item.id).then((ret) => {
if (ret.code === 200) {
message.success('删除成功');
getModelLists(queryFlow);
} else {
message.error(ret.msg);
}
});
},
});
}
},
}}
>
<div>
<img
style={{ width: '17px', marginRight: '6px' }}
src={moreBack}
alt=""
/>
</div>
</Dropdown> */}
,<div className={Styles.itemDescripition}>{item.description}</div>
<div className={Styles.itemTime}>
<img
style={{ width: '17px', marginRight: '6px' }}
src={creatByImg}
alt=""
/>
<span>{item.create_by}</span>
</div>
<div className={Styles.itemIcon}>
<img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" />
<span>最近更新: {moment(item.update_time).format('YYYY-MM-DD')}</span>
</div>
</div>
);
})
: ''}
{/* <Select.Option value="demo">Demo</Select.Option> */}
</div>
<Pagination
total={total}
showSizeChanger
defaultPageSize={20}
pageSizeOptions={[20, 40, 60, 80, 100]}
showQuickJumper
onChange={onPageChange}
/>
</div>
</div>
<Modal
title={
<div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}>
<img
style={{ width: '20px', marginRight: '10px' }}
src={`/assets/images/pipeline-edit-icon.png`}
alt=""
/>
{dialogTitle}
</div>
}
open={isModalOpen}
className={Styles.modal}
okButtonProps={{
htmlType: 'submit',
form: 'form',
}}
onCancel={handleCancel}
>
<Form
name="form"
form={form}
layout="vertical"
initialValues={{
remember: true,
}}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
>
<Form.Item
label="模型名称"
name="name"
rules={[
{
required: true,
message: '请输入模型名称!',
},
]}
>
<Input placeholder="请输入模型名称" showCount maxLength={64} />
</Form.Item>

<Form.Item
label="模型版本"
name="version"
rules={
[
// {
// required: true,
// message: 'Please input your username!',
// },
]
}
>
<Input placeholder="请输入模型版本" />
</Form.Item>

<Form.Item
label="模型描述"
name="description"
rules={[
{
required: true,
message: '请输入模型描述!',
},
]}
>
<Input placeholder="请输入模型描述" showCount maxLength={256} />
</Form.Item>
{/* <Form.Item label="可见范围" name="available_range">
<Radio.Group>
<Radio value="0">仅自己可见</Radio>
<Radio value="1">工作空间可见</Radio>
</Radio.Group>
</Form.Item> */}
<Form.Item
label="模型框架"
name="model_type"
rules={
[
// {
// required: true,
// message: 'Please input your username!',
// },
]
}
>
<Select
allowClear
placeholder="请选择模型类型"
options={modelTypeList.map((item) => {
return { value: item.id, label: item.name };
})}
/>
</Form.Item>
<Form.Item
label="模型能力"
name="model_tag"
rules={
[
// {
// required: true,
// message: 'Please input your username!',
// },
]
}
>
<Select
allowClear
placeholder="请选择模型标签"
options={modelDirectionList.map((item) => {
return { value: item.id, label: item.name };
})}
/>
</Form.Item>
<Form.Item label="模型文件" name="models_version_vos">
<Upload {...props} data={{ uuid: uuid }}>
<Button
style={{
fontSize: '14px',
border: '1px solid',
borderColor: '#1664ff',
background: '#fff',
}}
icon={<UploadOutlined style={{ color: '#1664ff', fontSize: '14px' }} />}
>
上传文件
</Button>
</Upload>
</Form.Item>
</Form>
</Modal>
</>
);
};
export default PublicData;

+ 0
- 384
react-ui/src/pages/Model/publicData.jsx View File

@@ -1,384 +0,0 @@
import clock from '@/assets/img/clock.png';
import creatByImg from '@/assets/img/creatBy.png';
import deleteIcon from '@/assets/img/delete-icon.png';
import { deleteModel, getAssetIcon, getModelList } from '@/services/dataset/index.js';
import { modalConfirm } from '@/utils/ui';
import { Form, Input, Modal, Pagination, Radio, message } from 'antd';
import moment from 'moment';
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import Styles from './index.less';
const { Search } = Input;
const leftdataList = [1, 2, 3];

const PublicData = () => {
const [queryFlow, setQueryFlow] = useState({
page: 0,
size: 20,
name: null,
available_range: 1,
});
const [iconParams, setIconParams] = useState({
name: null,
page: 0,
size: 10000,
});
const [activeType, setActiveType] = useState(null);
const [activeTag, setActiveTag] = useState(null);
const [datasetTypeList, setDatasetTypeList] = useState([]);
const [datasetDirectionList, setDatasetDirectionList] = useState([]);
const navgite = useNavigate();
const [isModalOpen, setIsModalOpen] = useState(false);
const [datasetList, setDatasetList] = useState([]);
const [total, setTotal] = useState(0);
const [form] = Form.useForm();
const [dialogTitle, setDialogTitle] = useState('新建数据');
const getModelLists = (queryFlow) => {
getModelList(queryFlow).then((ret) => {
console.log(ret);
if (ret.code == 200) {
setDatasetList(ret.data.content);
setTotal(ret.data.totalElements);
}
});
};

const showModal = () => {
form.resetFields();
setDialogTitle('新建数据集');
setIsModalOpen(true);
};
const nameSearch = (values) => {
console.log(values);
getModelLists({ ...queryFlow, name: values });
};
const getAssetIconList = (params) => {
getAssetIcon(params).then((ret) => {
console.log(ret);
if (ret.code == 200 && ret.data.content && ret.data.content.length > 0) {
setDatasetTypeList(ret.data.content.filter((item) => item.category_id == 3));
setDatasetDirectionList(ret.data.content.filter((item) => item.category_id == 4));
} else {
setDatasetTypeList([]);
setDatasetDirectionList([]);
}
});
};
const onSearch = (values) => {
console.log(values);
getAssetIconList({ ...iconParams, name: values });
};
const handleOk = () => {
console.log(1111);
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
const chooseModelType = (val, item) => {
console.log(val, item);
if (item.id == queryFlow.model_type) {
setActiveType('');
setQueryFlow({ ...queryFlow, model_type: null });
getModelLists({ ...queryFlow, model_type: null });
} else {
setActiveType(item.id);
setQueryFlow({ ...queryFlow, model_type: item.id });
getModelLists({ ...queryFlow, model_type: item.id });
}

// setQueryFlow({...queryFlow,data_type:item.path},()=>{
// getDatasetlist()
// })
};
const chooseModelTag = (val, item) => {
if (item.id == queryFlow.model_tag) {
setActiveTag('');
setQueryFlow({ ...queryFlow, model_tag: null });
getModelLists({ ...queryFlow, model_tag: null });
} else {
setActiveTag(item.id);
setQueryFlow({ ...queryFlow, model_tag: item.id });
getModelLists({ ...queryFlow, model_tag: item.id });
}
// setQueryFlow({...queryFlow,data_type:item.path},()=>{
// getDatasetlist()
// })
};
const onFinish = (values) => {};
const routeToIntro = (e, record) => {
e.stopPropagation();
console.log(record);
navgite({ pathname: `/dataset/model/${record.id}` });
};
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
};
const onPageChange = (pageNum, pageSize) => {
console.log(pageNum, pageSize);
setQueryFlow({ ...queryFlow, page: pageNum - 1, size: pageSize });
getModelLists({ ...queryFlow, page: pageNum - 1, size: pageSize });
};
useEffect(() => {
getAssetIconList(iconParams);
getModelLists(queryFlow);
return () => {};
}, []);
return (
<>
<div className={Styles.datasetCneterBox}>
<div className={Styles.datasetCneterLeftBox}>
<div className={Styles.leftContentBox}>
<Search
placeholder="搜索"
allowClear
onSearch={onSearch}
style={{
width: 300,
marginBottom: '15px',
}}
/>
<div className={Styles.itemTitle}>模型框架</div>
<div className={Styles.itemBox}>
{datasetTypeList && datasetTypeList.length > 0
? datasetTypeList.map((item) => {
return (
<div>
<div
className={[
Styles.messageBox,
item.id === activeType ? Styles.active : null,
].join(' ')}
onClick={(e) => {
chooseModelType(e, item);
}}
>
<img
className={Styles.ptIcon}
style={{ width: '22px' }}
src={`/assets/images/model/${item.path}.png`}
alt=""
/>
<img
className={Styles.hoverIcon}
style={{ width: '22px' }}
src={`/assets/images/model/${item.path}-hover.png`}
alt=""
/>
<span className={Styles.messageText}>{item.name}</span>
</div>
</div>
);
})
: ''}
</div>
<div className={Styles.itemTitle}>模型能力</div>
<div className={Styles.itemBox}>
{datasetDirectionList && datasetDirectionList.length > 0
? datasetDirectionList.map((item) => {
return (
<div>
<div
className={[
Styles.messageBox,
item.id === activeTag ? Styles.active : null,
].join(' ')}
onClick={(e) => {
chooseModelTag(e, item);
}}
>
<img
className={Styles.ptIcon}
style={{ width: '22px' }}
src={`/assets/images/model/${item.path}.png`}
alt=""
/>
<img
className={Styles.hoverIcon}
style={{ width: '22px' }}
src={`/assets/images/model/${item.path}-hover.png`}
alt=""
/>
<span className={Styles.messageText}>{item.name}</span>
</div>
</div>
);
})
: ''}
</div>
</div>
</div>
<div className={Styles.datasetCneterRightBox}>
<div className={Styles.dataSource}>
<span>数据总数:{total}个</span>
<div>
<Search
placeholder="按数据名称筛选"
allowClear
onSearch={nameSearch}
style={{
width: 300,
}}
/>
</div>
</div>
<div className={Styles.dataContent}>
{datasetList && datasetList.length > 0
? datasetList.map((item) => {
return (
<div
className={Styles.dataItem}
onClick={(e) => {
routeToIntro(e, item);
}}
>
<span className={Styles.itemText}>{item.name}</span>
<img
onClick={(e) => {
e.stopPropagation();
modalConfirm({
title: '确定删除该条模型实例吗?',
onOk: () => {
deleteModel(item.id).then((ret) => {
if (ret.code === 200) {
message.success('删除成功');
getModelLists(queryFlow);
} else {
message.error(ret.msg);
}
});
},
});
}}
className={Styles.dropdown}
style={{ width: '17px', marginRight: '6px' }}
src={deleteIcon}
alt=""
/>
<div className={Styles.itemDescripition}>{item.description}</div>
<div className={Styles.itemTime}>
<img
style={{ width: '17px', marginRight: '6px' }}
src={creatByImg}
alt=""
/>
<span>{item.create_by}</span>
</div>
<div className={Styles.itemIcon}>
<img style={{ width: '12px', marginRight: '5px' }} src={clock} alt="" />
<span>最近更新: {moment(item.update_time).format('YYYY-MM-DD')}</span>
</div>
</div>
);
})
: ''}
{/* <Select.Option value="demo">Demo</Select.Option> */}
</div>
<Pagination
total={total}
showSizeChanger
defaultPageSize={20}
pageSizeOptions={[20, 40, 60, 80, 100]}
showQuickJumper
onChange={onPageChange}
/>
</div>
</div>
<Modal
title={
<div style={{ display: 'flex', alignItems: 'center', fontWeight: 500 }}>
<img
style={{ width: '20px', marginRight: '10px' }}
src={`/assets/images/pipeline-edit-icon.png`}
alt=""
/>
{dialogTitle}
</div>
}
open={isModalOpen}
className={Styles.modal}
okButtonProps={{
htmlType: 'submit',
form: 'form',
}}
onCancel={handleCancel}
>
<Form
name="form"
form={form}
layout="vertical"
initialValues={{
remember: true,
}}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
>
<Form.Item
label="数据名称"
name="name"
rules={
[
// {
// required: true,
// message: 'Please input your username!',
// },
]
}
>
<Input placeholder="请输入数据名称" />
</Form.Item>
<Form.Item
label="数据集版本"
name="data_type"
rules={
[
// {
// required: true,
// message: 'Please input your username!',
// },
]
}
>
<Input placeholder="请输入数据集版本" />
</Form.Item>
<Form.Item
label="数据描述"
name="description"
rules={
[
// {
// required: true,
// message: 'Please input your username!',
// },
]
}
>
<Input placeholder="请输入数据描述" />
</Form.Item>
<Form.Item label="选择流水线" name="description1">
<Radio.Group>
<Radio value="0">仅自己可见</Radio>
<Radio value="1">工作空间可见</Radio>
</Radio.Group>
</Form.Item>
<Form.Item
label="数据标签"
name="description3"
rules={
[
// {
// required: true,
// message: 'Please input your username!',
// },
]
}
>
<Input placeholder="请输入数据标签" />
</Form.Item>
</Form>
</Modal>
</>
);
};
export default PublicData;

+ 19
- 0
react-ui/src/pages/ModelDeployment/Create/index.less View File

@@ -0,0 +1,19 @@
.model-deployment-create {
height: 100%;

&__content {
height: calc(100% - 60px);
margin-top: 10px;
padding: 30px 30px 10px;
overflow: auto;
color: @text-color;
font-size: @font-size-content;
background-color: white;
border-radius: 10px;

&__type {
color: @text-color;
font-size: @font-size-input-lg;
}
}
}

+ 449
- 0
react-ui/src/pages/ModelDeployment/Create/index.tsx View File

@@ -0,0 +1,449 @@
/*
* @Author: 赵伟
* @Date: 2024-04-16 13:58:08
* @Description: 创建模型部署
*/
import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle';
import ParameterInput from '@/components/ParameterInput';
import SubAreaTitle from '@/components/SubAreaTitle';
import { CommonTabKeys } from '@/enums';
import { useComputingResource } from '@/hooks/resource';
import ResourceSelectorModal, {
ResourceSelectorResponse,
ResourceSelectorType,
selectorTypeConfig,
} from '@/pages/Pipeline/components/ResourceSelectorModal';
import {
createModelDeploymentReq,
restartModelDeploymentReq,
updateModelDeploymentReq,
} from '@/services/modelDeployment';
import { camelCaseToUnderscore, underscoreToCamelCase } from '@/utils';
import { openAntdModal } from '@/utils/modal';
import { to } from '@/utils/promise';
import {
getSessionStorageItem,
modelDeploymentInfoKey,
removeSessionStorageItem,
} from '@/utils/sessionStorage';
import { modalConfirm } from '@/utils/ui';
import { useNavigate } from '@umijs/max';
import { App, Button, Col, Flex, Form, Input, Row, Select } from 'antd';
import { omit, pick } from 'lodash';
import { useEffect, useState } from 'react';
import { ModelDeploymentData, ModelDeploymentOperationType } from '../types';
import styles from './index.less';

// 表单数据
export type FormData = {
serviceName: string; // 服务名称
description: string; // 描述
model: {
id: number;
version: string;
value: string;
showValue: string;
}; // 模型
image: string; // 镜像
resource: string; // 资源规格
replicas: string; // 副本数量
modelPath: string; // 模型路径
env: { key: string; value: string }[]; // 环境变量
};

function ModelDeploymentCreate() {
const navgite = useNavigate();
const [form] = Form.useForm();
const [resourceStandardList, filterResourceStandard] = useComputingResource();
const [selectedModel, setSelectedModel] = useState<ResourceSelectorResponse | undefined>(
undefined,
); // 选择的模型,为了再次打开时恢复原来的选择
const [operationType, setOperationType] = useState(ModelDeploymentOperationType.Create);
const [modelDeploymentInfo, setModelDeploymentInfo] = useState<ModelDeploymentData | undefined>(
undefined,
);
const { message } = App.useApp();

useEffect(() => {
const res = getSessionStorageItem(modelDeploymentInfoKey, true);
if (res) {
setOperationType(res.operationType);
setModelDeploymentInfo(res);
const formData = underscoreToCamelCase(res) as FormData;
form.setFieldsValue(formData);
}
return () => {
removeSessionStorageItem(modelDeploymentInfoKey);
};
}, []);

// 获取选择数据集、模型后面按钮 icon
const getSelectBtnIcon = (type: ResourceSelectorType) => {
return <KFIcon type={selectorTypeConfig[type].buttonIcon} font={16} />;
};

// 选择模型、镜像
const selectResource = (name: string, selectType: string) => {
let type;
let resource: ResourceSelectorResponse | undefined;
switch (selectType) {
case 'model':
type = ResourceSelectorType.Model;
resource = selectedModel;
break;
default:
type = ResourceSelectorType.Mirror;
break;
}
const { close } = openAntdModal(ResourceSelectorModal, {
type,
defaultExpandedKeys: resource ? [resource.id] : [],
defaultCheckedKeys: resource ? [`${resource.id}-${resource.version}`] : [],
defaultActiveTab: resource?.activeTab,
onOk: (res) => {
if (res) {
if (type === ResourceSelectorType.Mirror) {
form.setFieldValue(name, res);
} else {
const response = res as ResourceSelectorResponse;
const showValue = `${response.name}:${response.version}`;
form.setFieldValue(name, {
...pick(response, ['id', 'version', 'path']),
showValue,
});
setSelectedModel(response);
}
} else {
if (type === ResourceSelectorType.Model) {
setSelectedModel(undefined);
}
form.setFieldValue(name, '');
}
close();
},
});
};

// 创建
const createModelDeployment = async (formData: FormData) => {
const envList = formData['env'] ?? [];
const env = envList.reduce((acc, cur) => {
acc[cur.key] = cur.value;
return acc;
}, {} as Record<string, string>);

const object = camelCaseToUnderscore({
...omit(formData, ['replicas', 'env']),
replicas: Number(formData.replicas),
env,
});

const params =
operationType === ModelDeploymentOperationType.Create
? object
: {
...pick(modelDeploymentInfo, ['service_id', 'service_ins_id']),
update_model: {
...pick(object, ['description', 'env', 'replicas', 'resource', 'image']),
},
};

let request = createModelDeploymentReq;
if (operationType === ModelDeploymentOperationType.Restart) {
request = restartModelDeploymentReq;
} else if (operationType === ModelDeploymentOperationType.Update) {
request = updateModelDeploymentReq;
}
const [res] = await to(request(params));
if (res) {
message.success('操作成功');
navgite(-1);
}
};

// 提交
const handleSubmit = (values: FormData) => {
createModelDeployment(values);
};

// 取消
const cancel = () => {
navgite(-1);
};

const disabled = operationType !== ModelDeploymentOperationType.Create;
let buttonText = '新建';
if (operationType === ModelDeploymentOperationType.Update) {
buttonText = '更新';
} else if (operationType === ModelDeploymentOperationType.Restart) {
buttonText = '重启';
}

return (
<div className={styles['model-deployment-create']}>
<PageTitle title="创建推理服务"></PageTitle>
<div className={styles['model-deployment-create__content']}>
<div>
<Form
name="model-deployment-create"
labelCol={{ flex: '100px' }}
labelAlign="left"
form={form}
initialValues={{ upload_type: CommonTabKeys.Public }}
onFinish={handleSubmit}
size="large"
>
<SubAreaTitle
title="基本信息"
image={require('@/assets/img/mirror-basic.png')}
style={{ marginBottom: '26px' }}
></SubAreaTitle>
<Row gutter={8}>
<Col span={10}>
<Form.Item
label="服务名称"
name="serviceName"
rules={[
{
required: true,
message: '请输入服务名称',
},
]}
>
<Input
placeholder="请输入服务名称"
disabled={disabled}
maxLength={30}
showCount
allowClear
/>
</Form.Item>
</Col>
</Row>
<Row gutter={8}>
<Col span={20}>
<Form.Item
label="描  述"
name="description"
rules={[
{
required: true,
message: '请输入描述',
},
]}
>
<Input.TextArea
autoSize={{ minRows: 2, maxRows: 6 }}
placeholder="请输入描述,最长128字符"
maxLength={128}
showCount
allowClear
/>
</Form.Item>
</Col>
</Row>
<SubAreaTitle
title="部署构建"
image={require('@/assets/img/model-deployment.png')}
style={{ marginTop: '20px', marginBottom: '24px' }}
></SubAreaTitle>

<Row gutter={8}>
<Col span={10}>
<Form.Item
label="选择模型"
name="model"
rules={[
{
required: true,
message: '请选择模型',
},
]}
>
<ParameterInput
placeholder="请选择模型"
disabled={disabled}
canInput={false}
size="large"
/>
</Form.Item>
</Col>
<Col span={10}>
<Button
disabled={disabled}
size="large"
type="link"
icon={getSelectBtnIcon(ResourceSelectorType.Model)}
onClick={() => selectResource('model', 'model')}
>
选择模型
</Button>
</Col>
</Row>
<Row gutter={8}>
<Col span={10}>
<Form.Item
label="选择镜像"
name="image"
rules={[
{
required: true,
message: '请输入镜像',
},
]}
>
<ParameterInput placeholder="请选择镜像" canInput={false} size="large" />
</Form.Item>
</Col>
<Col span={10}>
<Button
size="large"
type="link"
icon={getSelectBtnIcon(ResourceSelectorType.Mirror)}
onClick={() => selectResource('image', 'image')}
>
选择镜像
</Button>
</Col>
</Row>
<Row gutter={8}>
<Col span={10}>
<Form.Item
label="资源规格"
name="resource"
rules={[
{
required: true,
message: '请选择资源规格',
},
]}
>
<Select
showSearch
placeholder="请选择资源规格"
filterOption={filterResourceStandard}
options={resourceStandardList}
fieldNames={{
label: 'description',
value: 'standard',
}}
/>
</Form.Item>
</Col>
</Row>
<Row gutter={8}>
<Col span={10}>
<Form.Item
label="副本数量"
name="replicas"
rules={[
{
required: true,
message: '请输入副本数量',
},
{
pattern: /^-?\d+(\.\d+)?$/,
message: '副本数量必须是数字',
},
]}
>
<Input placeholder="请输入副本数量" allowClear />
</Form.Item>
</Col>
</Row>

<Row gutter={8}>
<Col span={10}>
<Form.Item
label="挂载路径"
name="modelPath"
rules={[
{
required: true,
message: '请输入模型挂载路径',
},
]}
>
<Input
placeholder="请输入模型挂载路径"
disabled={disabled}
maxLength={64}
showCount
allowClear
/>
</Form.Item>
</Col>
</Row>

<Form.List name="env">
{(fields, { add, remove }) => (
<>
<Row gutter={8}>
<Col span={10}>
<Form.Item label="环境变量">
<Button type="link" style={{ padding: '0' }} onClick={() => add()}>
添加环境变量
</Button>
</Form.Item>
</Col>
</Row>
{fields.map(({ key, name, ...restField }) => (
<Flex key={key} align="center" gap="0 8px" style={{ width: '50%' }}>
<Form.Item
{...restField}
name={[name, 'key']}
style={{ flex: 1 }}
rules={[{ required: true, message: '请输入变量名' }]}
>
<Input placeholder="请输入变量名" />
</Form.Item>
<span style={{ marginBottom: '24px' }}>=</span>
<Form.Item
{...restField}
name={[name, 'value']}
style={{ flex: 1 }}
rules={[{ required: true, message: '请输入变量值' }]}
>
<Input placeholder="请输入变量值" />
</Form.Item>
<Button
type="link"
style={{ marginBottom: '24px' }}
icon={<KFIcon type="icon-shanchu" font={16} />}
onClick={() => {
modalConfirm({
content: '是否确认删除?',
onOk: () => {
remove(name);
},
});
}}
></Button>
</Flex>
))}
</>
)}
</Form.List>

<Form.Item wrapperCol={{ offset: 0, span: 16 }}>
<Button type="primary" htmlType="submit">
{buttonText}
</Button>
<Button
type="default"
htmlType="button"
onClick={cancel}
style={{ marginLeft: '20px' }}
>
取消
</Button>
</Form.Item>
</Form>
</div>
</div>
</div>
);
}

export default ModelDeploymentCreate;

+ 56
- 0
react-ui/src/pages/ModelDeployment/Info/index.less View File

@@ -0,0 +1,56 @@
.model-deployment-info {
height: 100%;

&__basic {
&__item {
display: flex;
align-items: flex-start;
font-size: 16px;
line-height: 1.6;

.label {
flex: none;
width: 80px;
color: @text-color-secondary;
}

.value {
flex: 1;
color: @text-color;
white-space: pre-line;
word-break: break-all;
}
}
}

&__content {
height: calc(100% - 60px);
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;
}
}
}
}
}
}

+ 194
- 0
react-ui/src/pages/ModelDeployment/Info/index.tsx View File

@@ -0,0 +1,194 @@
/*
* @Author: 赵伟
* @Date: 2024-04-16 13:58:08
* @Description: 镜像详情
*/
import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle';
import SubAreaTitle from '@/components/SubAreaTitle';
import { useComputingResource } from '@/hooks/resource';
import { useSessionStorage } from '@/hooks/sessionStorage';
import { formatDate } from '@/utils/date';
import { modelDeploymentInfoKey } from '@/utils/sessionStorage';
import { Col, Row, Tabs, type TabsProps } from 'antd';
import { useEffect, useState } from 'react';
import ModelDeploymentStatusCell from '../components/ModelDeployStatusCell';
import { ModelDeploymentData } from '../types';
import styles from './index.less';

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

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

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

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

const formatEnvText = () => {
if (!modelDeployementInfo?.env) {
return '--';
}
const env = modelDeployementInfo.env;
return Object.entries(env)
.map(([key, value]) => `${key}: ${value}`)
.join('\n');
};

return (
<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>
</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>
</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>
</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>
<div style={{ marginTop: '20px' }}>
<Tabs activeKey={activeTab} items={tabItems} onChange={hanleTabChange} />
</div>
</div>
</div>
</div>
);
}

export default ModelDeploymentInfo;

+ 21
- 0
react-ui/src/pages/ModelDeployment/List/index.less View File

@@ -0,0 +1,21 @@
.model-deployment {
height: 100%;
&__content {
height: calc(100% - 60px);
margin-top: 10px;
padding: 20px 30px 0;
background-color: white;
border-radius: 10px;

&__filter {
display: flex;
align-items: center;
justify-content: space-between;
}

&__table {
height: calc(100% - 32px - 28px);
margin-top: 28px;
}
}
}

+ 349
- 0
react-ui/src/pages/ModelDeployment/List/index.tsx View File

@@ -0,0 +1,349 @@
/*
* @Author: 赵伟
* @Date: 2024-04-16 13:58:08
* @Description: 模型部署列表
*/
import CommonTableCell from '@/components/CommonTableCell';
import DateTableCell from '@/components/DateTableCell';
import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle';
import { ModelDeploymentStatus, modelDeploymentStatusOptions } from '@/enums';
import { useCacheState } from '@/hooks/pageCacheState';
import {
deleteModelDeploymentReq,
getModelDeploymentListReq,
stopModelDeploymentReq,
} from '@/services/modelDeployment';
import themes from '@/styles/theme.less';
import { to } from '@/utils/promise';
import { modelDeploymentInfoKey, setSessionStorageItem } from '@/utils/sessionStorage';
import { modalConfirm } from '@/utils/ui';
import { useNavigate } from '@umijs/max';
import {
App,
Button,
ConfigProvider,
Input,
Select,
Table,
type TablePaginationConfig,
type TableProps,
} from 'antd';
import { type SearchProps } from 'antd/es/input';
import classNames from 'classnames';
import { pick } from 'lodash';
import { useEffect, useState } from 'react';
import ModelDeploymentStatusCell from '../components/ModelDeployStatusCell';
import { ModelDeploymentData, ModelDeploymentOperationType } from '../types';
import styles from './index.less';

function ModelDeployment() {
const navigate = useNavigate();
const { message } = App.useApp();
const [cacheState, setCacheState] = useCacheState();
const [searchStatus, setSearchStatus] = useState(cacheState?.searchStatus ?? '');
const [searchText, setSearchText] = useState(cacheState?.searchText);
const [inputText, setInputText] = useState(cacheState?.searchText);
const [tableData, setTableData] = useState<ModelDeploymentData[]>([]);
const [total, setTotal] = useState(0);
const [pagination, setPagination] = useState<TablePaginationConfig>(
cacheState?.pagination ?? {
current: 1,
pageSize: 10,
},
);

useEffect(() => {
getModelDeploymentList();
}, [pagination, searchText, searchStatus]);

// 获取模型部署列表
const getModelDeploymentList = async () => {
const params: Record<string, any> = {
page: pagination.current!,
size: pagination.pageSize,
service_name: searchText,
status: searchStatus,
};
const [res] = await to(getModelDeploymentListReq(params));
if (res && res.data) {
const { service_list = [], total = 0 } = res.data;
setTableData(service_list);
setTotal(total);
}
};

// 删除模型部署
const deleteModelDeploy = async (record: ModelDeploymentData) => {
const params = pick(record, ['service_id', 'service_ins_id']);
const [res] = await to(deleteModelDeploymentReq(params));
if (res) {
message.success('删除成功');
// 如果是一页的唯一数据,删除时,请求第一页的数据
// 否则直接刷新这一页的数据
// 避免回到第一页
if (tableData.length > 1) {
setPagination((prev) => ({
...prev,
current: 1,
}));
} else {
getModelDeploymentList();
}
}
};

// 停止模型部署
const stopModelDeploy = async (record: ModelDeploymentData) => {
const params = pick(record, ['service_id', 'service_ins_id']);
const [res] = await to(stopModelDeploymentReq(params));
if (res) {
message.success('操作成功');
getModelDeploymentList();
}
};

// 搜索
const onSearch: SearchProps['onSearch'] = (value) => {
setSearchText(value);
};

// 处理删除
const handleModelDeployDelete = (record: ModelDeploymentData) => {
modalConfirm({
title: '删除后,该模型部署将不可恢复',
content: '是否确认删除?',
onOk: () => {
deleteModelDeploy(record);
},
});
};

// 处理停止
const handleModelDeployStop = async (record: ModelDeploymentData) => {
modalConfirm({
content: '是否确认停止?',
onOk: () => {
stopModelDeploy(record);
},
});
};

// 创建、更新、重启模型部署
const createModelDeployment = (
type: ModelDeploymentOperationType,
record?: ModelDeploymentData,
) => {
setSessionStorageItem(
modelDeploymentInfoKey,
{
...record,
operationType: type,
},
true,
);

setCacheState({
pagination,
searchText,
searchStatus,
});

navigate(`/modelDeployment/create`);
};

// 查看详情
const toDetail = (record: ModelDeploymentData) => {
setSessionStorageItem(modelDeploymentInfoKey, record, true);

setCacheState({
pagination,
searchText,
searchStatus,
});

navigate(`/modelDeployment/${record.service_id}`);
};

// 分页切换
const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => {
if (action === 'paginate') {
setPagination(pagination);
}
// console.log(pagination, filters, sorter, action);
};

const columns: TableProps<ModelDeploymentData>['columns'] = [
{
title: '序号',
dataIndex: 'index',
key: 'index',
width: '20%',
render(text, record, index) {
return <span>{(pagination.current! - 1) * pagination.pageSize! + index + 1}</span>;
},
},
{
title: '服务名称',
dataIndex: 'service_name',
key: 'service_name',
width: '20%',
render: (text, record) => {
return <a onClick={() => toDetail(record)}>{text}</a>;
},
},
{
title: '模型',
dataIndex: ['model', 'show_value'],
key: 'model',
width: '20%',
render: CommonTableCell(),
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: '20%',
render: ModelDeploymentStatusCell,
},
{
title: '创建人',
dataIndex: 'created_by',
key: 'created_by',
render: CommonTableCell(),
width: '20%',
},
{
title: '更新时间',
dataIndex: 'update_time',
key: 'update_time',
width: '20%',
render: DateTableCell,
},
{
title: '操作',
dataIndex: 'operation',
width: 350,
key: 'operation',
render: (_: any, record: ModelDeploymentData) => (
<div>
<Button
type="link"
size="small"
key="edit"
icon={<KFIcon type="icon-bianji" />}
onClick={() => createModelDeployment(ModelDeploymentOperationType.Update, record)}
>
更新
</Button>
{(record.status === ModelDeploymentStatus.Failed ||
record.status === ModelDeploymentStatus.Stopped) && (
<Button
type="link"
size="small"
key="run"
icon={<KFIcon type="icon-yunhang" />}
onClick={() => createModelDeployment(ModelDeploymentOperationType.Restart, record)}
>
重启
</Button>
)}
{(record.status === ModelDeploymentStatus.Running ||
record.status === ModelDeploymentStatus.Init ||
record.status === ModelDeploymentStatus.Pending) && (
<Button
type="link"
size="small"
key="stop"
icon={<KFIcon type="icon-tingzhi" />}
onClick={() => handleModelDeployStop(record)}
>
停止
</Button>
)}
<ConfigProvider
theme={{
token: {
colorLink: themes['warningColor'],
},
}}
>
<Button
type="link"
size="small"
key="remove"
icon={<KFIcon type="icon-shanchu" />}
onClick={() => handleModelDeployDelete(record)}
>
删除
</Button>
</ConfigProvider>
</div>
),
},
];

return (
<div className={styles['model-deployment']}>
<PageTitle title="模型列表"></PageTitle>
<div className={styles['model-deployment__content']}>
<div className={styles['model-deployment__content__filter']}>
<Input.Search
placeholder="按模型服务名称筛选"
onSearch={onSearch}
onChange={(e) => setInputText(e.target.value)}
style={{ width: 300 }}
value={inputText}
allowClear
/>
<Select
style={{ width: 100, marginLeft: '20px' }}
placeholder="请选择"
onChange={(value) => setSearchStatus(value)}
options={modelDeploymentStatusOptions}
value={searchStatus}
allowClear
></Select>
<Button
style={{ marginLeft: '20px' }}
type="default"
onClick={() => createModelDeployment(ModelDeploymentOperationType.Create)}
icon={<KFIcon type="icon-xinjian2" />}
>
创建推理服务
</Button>
<Button
style={{ marginRight: 0, marginLeft: 'auto' }}
type="default"
onClick={getModelDeploymentList}
icon={<KFIcon type="icon-shuaxin" />}
>
刷新
</Button>
</div>
<div
className={classNames(
'vertical-scroll-table',
styles['model-deployment__content__table'],
)}
>
<Table
dataSource={tableData}
columns={columns}
scroll={{ y: 'calc(100% - 55px)' }}
pagination={{
...pagination,
total: total,
showSizeChanger: true,
showQuickJumper: true,
}}
onChange={handleTableChange}
rowKey="service_id"
/>
</div>
</div>
</div>
);
}

export default ModelDeployment;

+ 19
- 0
react-ui/src/pages/ModelDeployment/components/ModelDeployStatusCell/index.less View File

@@ -0,0 +1,19 @@
.model-deployment-status-cell {
color: @text-color;

&--running {
color: @primary-color;
}

&--stopped {
color: @abort-color;
}

&--error {
color: @error-color;
}

&--pending {
color: @warning-color;
}
}

+ 44
- 0
react-ui/src/pages/ModelDeployment/components/ModelDeployStatusCell/index.tsx View File

@@ -0,0 +1,44 @@
/*
* @Author: 赵伟
* @Date: 2024-04-18 18:35:41
* @Description: 模型部署状态
*/
import { ModelDeploymentStatus } from '@/enums';
import styles from './index.less';

export type ModelDeploymentStatusInfo = {
text: string;
classname: string;
};

export const statusInfo: Record<ModelDeploymentStatus, ModelDeploymentStatusInfo> = {
[ModelDeploymentStatus.Init]: {
text: '启动中',
classname: styles['model-deployment-status-cell'],
},
[ModelDeploymentStatus.Running]: {
classname: styles['model-deployment-status-cell--running'],
text: '运行中',
},
[ModelDeploymentStatus.Stopped]: {
classname: styles['model-deployment-status-cell--stopped'],
text: '已停止',
},
[ModelDeploymentStatus.Failed]: {
classname: styles['model-deployment-status-cell--error'],
text: '失败',
},
[ModelDeploymentStatus.Pending]: {
classname: styles['model-deployment-status-cell--pending'],
text: '挂起中',
},
};

function ModelDeploymentStatusCell(status?: ModelDeploymentStatus | null) {
if (status === null || status === undefined || !statusInfo[status]) {
return <span>--</span>;
}
return <span className={statusInfo[status].classname}>{statusInfo[status].text}</span>;
}

export default ModelDeploymentStatusCell;

+ 32
- 0
react-ui/src/pages/ModelDeployment/types.ts View File

@@ -0,0 +1,32 @@
import { ModelDeploymentStatus } from '@/enums';

// 模型部署列表数据类型
export type ModelDeploymentData = {
service_id: number;
service_ins_id: number;
service_name: string;
description: string;
status: ModelDeploymentStatus;
update_time: string;
create_time: string;
created_by: string;
model_path: string;
url: string;
image: string;
replicas: number;
resource: string;
model: {
id: number;
version: string;
path: string;
show_value: string;
};
env: Record<string, string>;
};

// 操作类型
export enum ModelDeploymentOperationType {
Create = 'Create',
Update = 'Update',
Restart = 'Restart',
}

+ 2
- 2
react-ui/src/pages/Monitor/Job/edit.tsx View File

@@ -1,4 +1,5 @@
import { DictOptionType, DictValueEnumObj } from '@/components/DictTag'; import { DictOptionType, DictValueEnumObj } from '@/components/DictTag';
import KFModal from '@/components/KFModal';
import { import {
ProForm, ProForm,
ProFormCaptcha, ProFormCaptcha,
@@ -9,9 +10,8 @@ import {
ProFormTextArea, ProFormTextArea,
} from '@ant-design/pro-components'; } from '@ant-design/pro-components';
import { FormattedMessage, useIntl } from '@umijs/max'; import { FormattedMessage, useIntl } from '@umijs/max';
import { Form, Modal } from 'antd';
import { Form } from 'antd';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import KFModal from '@/components/KFModal';
/** /**
* 定时任务调度 Edit Form * 定时任务调度 Edit Form
* *


+ 29
- 0
react-ui/src/pages/Pipeline/components/GlobalParamsDrawer/index.less View File

@@ -0,0 +1,29 @@
.form-item {
position: relative;
padding-top: 40px;
border-bottom: 1px dashed rgba(20, 49, 179, 0.12);

&__delete-button {
position: absolute;
top: 5px;
right: 24px;
}

:global {
.anticon.anticon-question-circle {
margin-top: -14px;
}
}
}

.form-item-add {
margin-top: 15px;

&:first-child {
margin-top: 0;
}

&__add-button {
padding: 0;
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save