| @@ -19,7 +19,6 @@ const Settings: ProLayoutProps & { | |||
| title: '智能软件开发平台', | |||
| pwa: true, | |||
| logo: '/assets/images/left-top-logo.png', | |||
| iconfontUrl: '//at.alicdn.com/t/c/font_4511326_1cmi0j3dj1x.js', | |||
| 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 | |||
| @@ -67,24 +67,36 @@ export default [ | |||
| path: '/pipeline', | |||
| 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: '实验', | |||
| 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: '数据集简介', | |||
| path: ':id', | |||
| component: './Dataset/datasetIntro', | |||
| component: './Dataset/intro', | |||
| }, | |||
| ], | |||
| }, | |||
| @@ -147,7 +159,7 @@ export default [ | |||
| { | |||
| name: '模型简介', | |||
| path: ':id', | |||
| component: './Model/modelIntro', | |||
| component: './Model/intro', | |||
| }, | |||
| ], | |||
| }, | |||
| @@ -158,17 +170,17 @@ export default [ | |||
| { | |||
| name: '镜像列表', | |||
| path: '', | |||
| component: './Mirror/list', | |||
| component: './Mirror/List', | |||
| }, | |||
| { | |||
| name: '镜像详情', | |||
| path: ':id', | |||
| component: './Mirror/info', | |||
| component: './Mirror/Info', | |||
| }, | |||
| { | |||
| name: '创建镜像', | |||
| path: 'create', | |||
| component: './Mirror/create', | |||
| component: './Mirror/Create', | |||
| }, | |||
| ], | |||
| }, | |||
| @@ -188,14 +200,23 @@ export default [ | |||
| ], | |||
| }, | |||
| { | |||
| name: 'modelDseployment', | |||
| path: '/modelDseployment', | |||
| name: 'modelDeployment', | |||
| path: '/modelDeployment', | |||
| routes: [ | |||
| { | |||
| name: '模型部署', | |||
| name: '模型列表', | |||
| path: '', | |||
| key: 'modelDseployment', | |||
| component: './missingPage.jsx', | |||
| component: './ModelDeployment/List', | |||
| }, | |||
| { | |||
| name: '镜像详情', | |||
| path: ':id', | |||
| component: './ModelDeployment/Info', | |||
| }, | |||
| { | |||
| name: '创建镜像', | |||
| path: 'create', | |||
| component: './ModelDeployment/Create', | |||
| }, | |||
| ], | |||
| }, | |||
| @@ -17,9 +17,10 @@ import { | |||
| patchRouteWithRemoteMenus, | |||
| setRemoteMenu, | |||
| } from './services/session'; | |||
| import './styles/menu.less'; | |||
| export { requestConfig as request } from './requestConfig'; | |||
| // const isDev = process.env.NODE_ENV === 'development'; | |||
| import { menuItemRender } from '@/utils/menuRender'; | |||
| /** | |||
| * @see https://umijs.org/zh-CN/plugins/plugin-initial-state | |||
| * */ | |||
| @@ -139,10 +140,8 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { | |||
| onClick: () => { | |||
| // 点击菜单项,删除所有的页面 state 缓存 | |||
| removeAllPageCacheState(); | |||
| // console.log('click menu'); | |||
| }, | |||
| // onSelect: (e) => { | |||
| // console.log(e); | |||
| // }, | |||
| }, | |||
| ...initialState?.settings, | |||
| token: { | |||
| @@ -150,60 +149,49 @@ export const layout: RuntimeConfig['layout'] = ({ initialState }) => { | |||
| colorTextMenu: themes['textColor'], | |||
| colorTextMenuSelected: themes['primaryColor'], | |||
| colorTextMenuActive: themes['primaryColor'], | |||
| colorTextMenuItemHover: themes['primaryColor'], | |||
| colorBgMenuItemSelected: 'rgba(197, 232, 255, 0.8)', | |||
| 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) => { | |||
| const { location } = e; | |||
| const menus = getRemoteMenu(); | |||
| console.log('onRouteChange', e); | |||
| // console.log('onRouteChange', e); | |||
| if (menus === null && location.pathname !== PageEnum.LOGIN) { | |||
| console.log('refresh'); | |||
| history.go(0); | |||
| } | |||
| }; | |||
| export const patchRoutes: RuntimeConfig['patchRoutes'] = (e) => { | |||
| console.log('patchRoutes', e); | |||
| //console.log('patchRoutes', e); | |||
| }; | |||
| export const patchClientRoutes: RuntimeConfig['patchClientRoutes'] = (e) => { | |||
| console.log('patchClientRoutes', e); | |||
| //console.log('patchClientRoutes', e); | |||
| patchRouteWithRemoteMenus(e.routes); | |||
| }; | |||
| export function render(oldRender: () => void) { | |||
| console.log('render'); | |||
| //console.log('render'); | |||
| const token = getAccessToken(); | |||
| if (!token || token?.length === 0) { | |||
| oldRender(); | |||
| 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'], | |||
| colorWarning: themes['warningColor'], | |||
| colorLink: themes['primaryColor'], | |||
| colorText: themes['textColor'], | |||
| }; | |||
| memo.theme.components ??= {}; | |||
| memo.theme.components.Tabs = {}; | |||
| @@ -229,10 +218,14 @@ export const antd: RuntimeAntdConfig = (memo) => { | |||
| defaultActiveBorderColor: 'rgba(22, 100, 255, 0.75)', | |||
| defaultActiveColor: themes['primaryColor'], | |||
| contentFontSize: parseInt(themes['fontSize']), | |||
| controlHeight: 34, | |||
| }; | |||
| 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 = { | |||
| headerBg: 'rgba(242, 244, 247, 0.36)', | |||
| @@ -241,6 +234,11 @@ export const antd: RuntimeAntdConfig = (memo) => { | |||
| memo.theme.components.Tabs = { | |||
| titleFontSize: 16, | |||
| }; | |||
| memo.theme.components.Form = { | |||
| labelColor: 'rgba(29, 29, 32, 0.8);', | |||
| }; | |||
| memo.theme.cssVar = true; | |||
| // memo.theme.hashed = false; | |||
| @@ -4,8 +4,22 @@ | |||
| * @Description: 自定义 Table 单元格,没有数据时展示 -- | |||
| */ | |||
| function CommonTableCell(text?: string | null) { | |||
| import { Tooltip } from 'antd'; | |||
| function renderCell(text?: string | null) { | |||
| 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; | |||
| @@ -4,6 +4,7 @@ | |||
| * @Description: 自定义 Table 日期类单元格 | |||
| */ | |||
| import { formatDate } from '@/utils/date'; | |||
| import dayjs from 'dayjs'; | |||
| function DateTableCell(text?: string | null) { | |||
| @@ -13,7 +14,7 @@ function DateTableCell(text?: string | null) { | |||
| if (!dayjs(text).isValid()) { | |||
| return <span>无效的日期</span>; | |||
| } | |||
| return <span>{dayjs(text).format('YYYY-MM-DD HH:mm:ss')}</span>; | |||
| return <span>{formatDate(text)}</span>; | |||
| } | |||
| export default DateTableCell; | |||
| @@ -3,6 +3,7 @@ | |||
| * @Date: 2024-04-17 12:53:06 | |||
| * @Description: | |||
| */ | |||
| import '@/iconfont/iconfont-menu.js'; | |||
| import '@/iconfont/iconfont.js'; | |||
| import { createFromIconfontCN } from '@ant-design/icons'; | |||
| @@ -23,8 +23,6 @@ | |||
| border-radius: 10px; | |||
| } | |||
| .ant-btn-default { | |||
| color: @text-color; | |||
| background: rgba(22, 100, 255, 0.06); | |||
| border-color: transparent; | |||
| } | |||
| .ant-btn + .ant-btn { | |||
| @@ -12,11 +12,12 @@ import './index.less'; | |||
| export interface KFModalProps extends ModalProps { | |||
| image?: string; | |||
| } | |||
| function KFModal({ title, image, children, className = '', ...rest }: KFModalProps) { | |||
| function KFModal({ title, image, children, className = '', centered, ...rest }: KFModalProps) { | |||
| return ( | |||
| <Modal | |||
| className={classNames(['kf-modal', className])} | |||
| {...rest} | |||
| centered={centered ?? true} | |||
| title={<ModalTitle title={title} image={image}></ModalTitle>} | |||
| > | |||
| {children} | |||
| @@ -23,6 +23,11 @@ | |||
| &--active { | |||
| color: @primary-color; | |||
| border: 1px solid @primary-color; | |||
| &:hover { | |||
| color: @primary-color; | |||
| border: 1px solid @primary-color; | |||
| } | |||
| } | |||
| & + & { | |||
| @@ -3,5 +3,8 @@ | |||
| align-items: center; | |||
| height: 50px; | |||
| 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%; | |||
| } | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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; | |||
| @@ -2,6 +2,7 @@ import { useEmotionCss } from '@ant-design/use-emotion-css'; | |||
| import { useModel } from '@umijs/max'; | |||
| import React from 'react'; | |||
| import Avatar from './AvatarDropdown'; | |||
| // import { SelectLang } from '@umijs/max'; | |||
| export type SiderTheme = 'light' | 'dark'; | |||
| @@ -4,9 +4,27 @@ export enum CommonTabKeys { | |||
| Public = 'Public', // 公开 | |||
| } | |||
| // 镜像状态 | |||
| // 镜像版本状态 | |||
| export enum MirrorVersionStatus { | |||
| Available = 'available', // 可用 | |||
| Building = 'building', // 构建中 | |||
| 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 }, | |||
| ]; | |||
| @@ -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; | |||
| } | |||
| @@ -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]; | |||
| } | |||
| @@ -57,11 +57,6 @@ | |||
| overflow-y: auto; | |||
| } | |||
| // Input | |||
| .ant-input-textarea-affix-wrapper.ant-input-affix-wrapper { | |||
| padding: 0; | |||
| } | |||
| // Modal | |||
| .ant-modal { | |||
| .ant-modal-close { | |||
| @@ -81,18 +76,24 @@ | |||
| } | |||
| } | |||
| .ant-form-item .ant-form-item-label > label { | |||
| font-size: @font-size; | |||
| } | |||
| // 输入框高度为46px | |||
| .ant-input-affix-wrapper { | |||
| height: 46px; | |||
| padding: 1px 11px; | |||
| padding-top: 2px; | |||
| padding-bottom: 2px; | |||
| .ant-input { | |||
| height: 40px; | |||
| } | |||
| } | |||
| // 选择框高度为46px | |||
| .ant-select-single { | |||
| height: 46px; | |||
| } | |||
| .ant-select-single .ant-select-selector .ant-select-selection-placeholder { | |||
| line-height: 46px; | |||
| } | |||
| } | |||
| // Confirm Modal | |||
| @@ -128,8 +129,6 @@ | |||
| border-radius: 10px; | |||
| } | |||
| .ant-btn-default { | |||
| color: @text-color; | |||
| background: rgba(22, 100, 255, 0.06); | |||
| border-color: transparent; | |||
| } | |||
| .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; | |||
| } | |||
| } | |||
| @@ -0,0 +1,9 @@ | |||
| .upload-tip { | |||
| margin-top: 5px; | |||
| color: @error-color; | |||
| } | |||
| .upload-button { | |||
| height: 46px; | |||
| font-size: 15px; | |||
| } | |||
| @@ -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; | |||
| @@ -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; | |||
| @@ -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; | |||
| @@ -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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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; | |||
| @@ -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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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; | |||
| @@ -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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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); | |||
| @@ -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%; | |||
| } | |||
| } | |||
| @@ -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; | |||
| @@ -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: ''; | |||
| } | |||
| } | |||
| @@ -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; | |||
| @@ -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; | |||
| @@ -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; | |||
| } | |||
| } | |||
| } | |||
| @@ -1,61 +1,35 @@ | |||
| import { getAccessToken } from '@/access'; | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { ResourceType } from '@/pages/Dataset/types'; | |||
| import { | |||
| addDatasetVersionDetail, | |||
| deleteDatasetVersion, | |||
| getDatasetById, | |||
| getDatasetVersionIdList, | |||
| getDatasetVersionsById, | |||
| } from '@/services/dataset/index.js'; | |||
| import { formatDate } from '@/utils/date'; | |||
| 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 { useParams } from 'react-router-dom'; | |||
| import Styles from './index.less'; | |||
| import AddVersionModal from './components/AddVersionModal'; | |||
| import Styles from './intro.less'; | |||
| const { Search } = Input; | |||
| const { TabPane } = Tabs; | |||
| 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 [dialogTitle, setDialogTitle] = useState('新建版本'); | |||
| const [isModalOpen, setIsModalOpen] = useState(false); | |||
| const [datasetDetailObj, setDatasetDetailObj] = useState({}); | |||
| const [version, setVersion] = useState(null); | |||
| const [versionList, setVersionList] = useState([]); | |||
| const locationParams = useParams(); //新版本获取路由参数接口 | |||
| const [searchParams] = useSearchParams(); | |||
| const [wordList, setWordList] = useState([]); | |||
| const [activeTabKey, setActiveTabKey] = useState('1'); | |||
| const [uuid, setUuid] = useState(Date.now()); | |||
| const isPublic = searchParams.get('isPublic') === 'true'; | |||
| const getDatasetByDetail = () => { | |||
| getDatasetById(locationParams.id).then((ret) => { | |||
| console.log(ret); | |||
| @@ -77,6 +51,9 @@ const Dataset = () => { | |||
| ); | |||
| setVersion(ret.data[0]); | |||
| getDatasetVersions({ version: ret.data[0], dataset_id: locationParams.id }); | |||
| } else { | |||
| setVersion(null); | |||
| setWordList([]); | |||
| } | |||
| }); | |||
| }; | |||
| @@ -86,37 +63,21 @@ const Dataset = () => { | |||
| return () => {}; | |||
| }, []); | |||
| 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: () => { | |||
| deleteDatasetVersion({ dataset_id: locationParams.id, version }).then((ret) => { | |||
| getDatasetVersionList(); | |||
| @@ -125,19 +86,26 @@ const Dataset = () => { | |||
| }, | |||
| }); | |||
| }; | |||
| const onFinish = (values) => { | |||
| addDatasetVersionDetail(formList).then((ret) => { | |||
| getDatasetVersionList(); | |||
| setIsModalOpen(false); | |||
| message.success('创建成功'); | |||
| }); | |||
| }; | |||
| // 获取版本下的文件列表 | |||
| const getDatasetVersions = (params) => { | |||
| getDatasetVersionIdList(params).then((res) => { | |||
| setWordList(res?.data?.content ?? []); | |||
| }); | |||
| }; | |||
| const handleExport = async () => { | |||
| const hide = message.loading('正在下载'); | |||
| hide(); | |||
| downLoadZip(`/api/mmp/dataset/downloadAllFiles`, { dataset_id: locationParams.id, version }); | |||
| }; | |||
| const downloadAlone = (e, record) => { | |||
| console.log(record); | |||
| const hide = message.loading('正在下载'); | |||
| hide(); | |||
| downLoadZip(`/api/mmp/dataset/download/${record.id}`); | |||
| }; | |||
| const handleChange = (value) => { | |||
| console.log(value); | |||
| if (value) { | |||
| @@ -147,15 +115,7 @@ const Dataset = () => { | |||
| 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 = [ | |||
| { | |||
| title: '序号', | |||
| @@ -187,7 +147,7 @@ const Dataset = () => { | |||
| title: '更新时间', | |||
| dataIndex: '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: '操作', | |||
| @@ -264,15 +224,17 @@ const Dataset = () => { | |||
| <div | |||
| 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 | |||
| type="default" | |||
| disabled={!version} | |||
| @@ -295,105 +257,6 @@ const Dataset = () => { | |||
| </TabPane> | |||
| </Tabs> | |||
| </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> | |||
| ); | |||
| }; | |||
| @@ -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; | |||
| } | |||
| @@ -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; | |||
| @@ -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; | |||
| @@ -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; | |||
| }; | |||
| @@ -16,7 +16,7 @@ function DatasetAnnotation() { | |||
| }; | |||
| return ( | |||
| <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> | |||
| ); | |||
| } | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -4,7 +4,7 @@ import KFModal from '@/components/KFModal'; | |||
| import { type PipelineGlobalParam } from '@/types'; | |||
| import { Form, Input, Radio, Select, type FormRule } from 'antd'; | |||
| import { useState } from 'react'; | |||
| import styles from './addExperimentModal.less'; | |||
| import styles from './index.less'; | |||
| type FormData = { | |||
| name?: string; | |||
| @@ -97,6 +97,11 @@ function AddExperimentModal({ | |||
| wrapperCol: { span: 20 }, | |||
| }; | |||
| const paramLayout = { | |||
| labelCol: { span: 8 }, | |||
| wrapperCol: { span: 16 }, | |||
| }; | |||
| // 除了流水线选择发生变化 | |||
| const handleWorkflowChange = (id: string | number) => { | |||
| const pipeline: Workflow | undefined = workflowList.find((v) => v.id === id); | |||
| @@ -187,7 +192,7 @@ function AddExperimentModal({ | |||
| fields.map(({ key, name, ...restField }) => ( | |||
| <Form.Item | |||
| {...restField} | |||
| {...layout} | |||
| {...paramLayout} | |||
| key={key} | |||
| label={getParamType(globalParam[name])} | |||
| name={[name, 'param_value']} | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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; | |||
| @@ -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; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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; | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -1,16 +1,19 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-05-16 08:47:46 | |||
| * @Description: 日志组件 | |||
| */ | |||
| 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 { DoubleRightOutlined, DownOutlined, UpOutlined } from '@ant-design/icons'; | |||
| import { Button } from 'antd'; | |||
| 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; // 实验状态 | |||
| }; | |||
| @@ -23,7 +26,7 @@ function LogGroup({ | |||
| log_type = 'normal', | |||
| pod_name = '', | |||
| log_content = '', | |||
| start_time = '', | |||
| start_time, | |||
| status = ExperimentStatus.Pending, | |||
| }: LogGroupProps) { | |||
| const [collapse, setCollapse] = useState(true); | |||
| @@ -57,21 +60,6 @@ function LogGroup({ | |||
| 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 () => { | |||
| @@ -101,15 +89,15 @@ function LogGroup({ | |||
| const logText = log_content + logList.map((v) => v.log_content).join(''); | |||
| const showMoreBtn = status !== 'Running' && showLog && !completed && logText !== ''; | |||
| return ( | |||
| <div className={styles.log_group}> | |||
| <div className={styles['log-group']}> | |||
| {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 />} | |||
| </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 && ( | |||
| <Button | |||
| type="text" | |||
| @@ -0,0 +1,3 @@ | |||
| .log-list { | |||
| padding: 8px; | |||
| } | |||
| @@ -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; | |||
| @@ -6,8 +6,8 @@ | |||
| import parameterImg from '@/assets/img/modal-parameter.png'; | |||
| import KFModal from '@/components/KFModal'; | |||
| 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 = { | |||
| open: boolean; | |||
| @@ -24,6 +24,7 @@ function ParamsModal({ open, onCancel, globalParam = [] }: ParamsModalProps) { | |||
| onOk={onCancel} | |||
| onCancel={onCancel} | |||
| cancelButtonProps={{ style: { display: 'none' } }} | |||
| width={825} | |||
| > | |||
| <div className={styles.params_container}> | |||
| {globalParam?.map((item) => ( | |||
| @@ -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; | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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; | |||
| } | |||
| @@ -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; | |||
| } | |||
| @@ -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; | |||
| @@ -1,3 +1,4 @@ | |||
| import CommonTableCell from '@/components/CommonTableCell'; | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import { | |||
| deleteExperimentById, | |||
| @@ -14,16 +15,15 @@ import { | |||
| } from '@/services/experiment/index.js'; | |||
| import { getWorkflow } from '@/services/pipeline/index.js'; | |||
| import themes from '@/styles/theme.less'; | |||
| import { elapsedTime } from '@/utils/date'; | |||
| import { elapsedTime, formatDate } from '@/utils/date'; | |||
| import { to } from '@/utils/promise'; | |||
| 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 momnet from 'moment'; | |||
| import { useEffect, useRef, useState } from 'react'; | |||
| import { useNavigate } from 'react-router-dom'; | |||
| import AddExperimentModal from './components/AddExperimentModal'; | |||
| import TensorBoardStatus, { TensorBoardStatusEnum } from './components/TensorBoardStatus'; | |||
| import AddExperimentModal from './experimentText/addExperimentModal'; | |||
| import Styles from './index.less'; | |||
| import { experimentStatusInfo } from './status'; | |||
| @@ -47,6 +47,7 @@ function Experiment() { | |||
| const [isAdd, setIsAdd] = useState(true); | |||
| const [isModalOpen, setIsModalOpen] = useState(false); | |||
| const [addFormData, setAddFormData] = useState({}); | |||
| const { message } = App.useApp(); | |||
| useEffect(() => { | |||
| getList(); | |||
| @@ -199,7 +200,7 @@ function Experiment() { | |||
| }; | |||
| const routeToEdit = (e, record) => { | |||
| 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) => { | |||
| @@ -256,7 +257,7 @@ function Experiment() { | |||
| }; | |||
| const routerToText = (e, item, record) => { | |||
| e.stopPropagation(); | |||
| navgite({ pathname: `/pipeline/experimentPytorchtext/${record.workflow_id}/${item.id}` }); | |||
| navgite({ pathname: `/pipeline/experiment/${record.workflow_id}/${item.id}` }); | |||
| }; | |||
| const handleTensorboard = async (experimentIn) => { | |||
| @@ -291,6 +292,8 @@ function Experiment() { | |||
| title: '实验描述', | |||
| dataIndex: 'description', | |||
| key: 'description', | |||
| render: CommonTableCell(true), | |||
| ellipsis: { showTitle: false }, | |||
| }, | |||
| { | |||
| title: '最近五次运行状态', | |||
| @@ -438,13 +441,9 @@ function Experiment() { | |||
| </div> | |||
| <div className={Styles.description}> | |||
| <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 style={{ width: '50%' }}>{formatDate(item.create_time)}</div> | |||
| </div> | |||
| <div className={Styles.statusBox}> | |||
| <img | |||
| @@ -6,6 +6,8 @@ | |||
| height: 49px; | |||
| padding-right: 30px; | |||
| background-image: url(/assets/images/pipeline-back.png); | |||
| background-repeat: no-repeat; | |||
| background-position: top center; | |||
| background-size: 100% 100%; | |||
| } | |||
| .pipelineTopBox { | |||
| @@ -17,6 +19,8 @@ | |||
| margin-bottom: 10px; | |||
| padding-right: 30px; | |||
| background-image: url(/assets/images/pipeline-back.png); | |||
| background-repeat: no-repeat; | |||
| background-position: top center; | |||
| background-size: 100% 100%; | |||
| } | |||
| .tableExpandBox { | |||
| @@ -15,13 +15,10 @@ export enum ExperimentStatus { | |||
| 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: { | |||
| label: '运行中', | |||
| color: '#165bff', | |||
| color: '#1664ff', | |||
| icon: '/assets/images/running-icon.png', | |||
| }, | |||
| Succeeded: { | |||
| @@ -56,7 +53,7 @@ export const experimentStatusInfo: Record<ExperimentStatusValues, StatusInfo | u | |||
| }, | |||
| Omitted: { | |||
| label: '未执行', | |||
| color: '#8a8a8ae', | |||
| color: '#8a8a8a', | |||
| icon: '/assets/images/omitted-icon.png', | |||
| }, | |||
| }; | |||
| @@ -1,51 +1,26 @@ | |||
| import { useVisible } from '@/hooks'; | |||
| import { useStateRef, useVisible } from '@/hooks'; | |||
| import { getExperimentIns } from '@/services/experiment/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 { 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 { s8 } from '../../../utils'; | |||
| import ParamsModal from '../components/ViewParamsModal'; | |||
| import { experimentStatusInfo } from '../status'; | |||
| import styles from './index.less'; | |||
| import ParamsModal from './paramsModal'; | |||
| import Props from './props'; | |||
| let graph = null; | |||
| function ExperimentText() { | |||
| const [message, setMessage] = useState({}); | |||
| const messageRef = useRef(message); | |||
| const [message, setMessage, messageRef] = useStateRef({}); | |||
| const propsRef = useRef(); | |||
| const navgite = useNavigate(); | |||
| const locationParams = useParams(); //新版本获取路由参数接口 | |||
| let graph = null; | |||
| 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 onDragEnd = (val) => { | |||
| console.log(val, 'eee'); | |||
| @@ -61,12 +36,8 @@ function ExperimentText() { | |||
| id: val.component_name + '-' + s8(), | |||
| isCluster: false, | |||
| }; | |||
| console.log(graph, model); | |||
| graph.addItem('node', model, true); | |||
| console.log(graph); | |||
| }; | |||
| const formChange = (val) => {}; | |||
| const handlerClick = (e) => { | |||
| if (e.target.get('name') !== 'anchor-point' && e.item) { | |||
| propsRef.current.showDrawer(e, locationParams.id, messageRef.current); | |||
| @@ -74,7 +45,6 @@ function ExperimentText() { | |||
| }; | |||
| const getGraphData = (data) => { | |||
| if (graph) { | |||
| console.log(graph); | |||
| graph.data(data); | |||
| graph.render(); | |||
| } else { | |||
| @@ -84,77 +54,39 @@ function ExperimentText() { | |||
| } | |||
| }; | |||
| 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 { | |||
| ...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); | |||
| // 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(() => { | |||
| initGraph(); | |||
| getFirstWorkflow(locationParams.workflowId); | |||
| }, []); | |||
| useEffect(() => { | |||
| // Update the refs whenever the state changes | |||
| messageRef.current = message; | |||
| }, [message]); | |||
| const initGraph = () => { | |||
| const fittingString = (str, maxWidth, fontSize) => { | |||
| @@ -375,6 +307,8 @@ function ExperimentText() { | |||
| }, | |||
| // linkCenter: true, | |||
| fitView: true, | |||
| minZoom: 0.5, | |||
| maxZoom: 3, | |||
| fitViewPadding: [320, 320, 220, 320], | |||
| }); | |||
| graph.on('node:click', handlerClick); | |||
| @@ -386,40 +320,39 @@ function ExperimentText() { | |||
| }; | |||
| }; | |||
| 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 className={graphStyle} ref={graphRef} id={styles.graphStyle}></div> | |||
| <Button | |||
| className={styles['pipeline-container__top__param-button']} | |||
| onClick={openParamsModal} | |||
| > | |||
| 执行参数 | |||
| </Button> | |||
| </div> | |||
| <Props ref={propsRef} onParentChange={formChange}></Props> | |||
| <div className={styles['pipeline-container__graph']} ref={graphRef}></div> | |||
| <Props ref={propsRef}></Props> | |||
| <ParamsModal | |||
| open={paramsModalOpen} | |||
| onCancel={closeParamsModal} | |||
| @@ -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%; | |||
| } | |||
| } | |||
| @@ -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%; | |||
| } | |||
| } | |||
| @@ -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; | |||
| @@ -9,9 +9,9 @@ | |||
| background-color: white; | |||
| border-radius: 10px; | |||
| &__title { | |||
| display: flex; | |||
| align-items: center; | |||
| &__type { | |||
| color: @text-color; | |||
| font-size: @font-size-input-lg; | |||
| } | |||
| } | |||
| } | |||
| @@ -11,13 +11,17 @@ import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { CommonTabKeys } from '@/enums'; | |||
| import { createMirrorReq } from '@/services/mirror'; | |||
| 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 { 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 { useEffect, useState } from 'react'; | |||
| import styles from './create.less'; | |||
| import styles from './index.less'; | |||
| type FormData = { | |||
| name: string; | |||
| @@ -45,6 +49,7 @@ function MirrorCreate() { | |||
| const navgite = useNavigate(); | |||
| const [form] = Form.useForm(); | |||
| const [nameDisabled, setNameDisabled] = useState(false); | |||
| const { message } = App.useApp(); | |||
| const uploadProps: UploadProps = { | |||
| action: '/api/mmp/image/upload', | |||
| @@ -56,11 +61,14 @@ function MirrorCreate() { | |||
| }; | |||
| useEffect(() => { | |||
| const name = getSessionItemThenRemove(mirrorNameKey); | |||
| const name = getSessionStorageItem(mirrorNameKey); | |||
| if (name) { | |||
| form.setFieldValue('name', name); | |||
| setNameDisabled(true); | |||
| } | |||
| return () => { | |||
| removeSessionStorageItem(mirrorNameKey); | |||
| }; | |||
| }, []); | |||
| // 创建公网、本地镜像 | |||
| @@ -75,30 +83,16 @@ function MirrorCreate() { | |||
| }; | |||
| } else { | |||
| 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)); | |||
| @@ -118,6 +112,7 @@ function MirrorCreate() { | |||
| navgite(-1); | |||
| }; | |||
| // 上传前认证 | |||
| const beforeUpload: UploadProps['beforeUpload'] = () => { | |||
| const fileList = form.getFieldValue('fileList'); | |||
| if (Array.isArray(fileList) && fileList.length >= 1) { | |||
| @@ -134,12 +129,13 @@ function MirrorCreate() { | |||
| <div> | |||
| <Form | |||
| name="mirror-create" | |||
| labelCol={{ flex: '120px' }} | |||
| labelCol={{ flex: '130px' }} | |||
| wrapperCol={{ flex: 1 }} | |||
| labelAlign="left" | |||
| form={form} | |||
| initialValues={{ upload_type: CommonTabKeys.Public }} | |||
| onFinish={handleSubmit} | |||
| size="large" | |||
| > | |||
| <SubAreaTitle | |||
| title="基本信息" | |||
| @@ -242,7 +238,7 @@ function MirrorCreate() { | |||
| <Row gutter={10}> | |||
| <Col span={10}> | |||
| <Form.Item label="仓库类型" required> | |||
| <span>公网</span> | |||
| <span className={styles['mirror-create__content__type']}>公网</span> | |||
| </Form.Item> | |||
| </Col> | |||
| </Row> | |||
| @@ -16,6 +16,7 @@ import { | |||
| getMirrorVersionListReq, | |||
| } from '@/services/mirror'; | |||
| import themes from '@/styles/theme.less'; | |||
| import { formatDate } from '@/utils/date'; | |||
| import { to } from '@/utils/promise'; | |||
| import { mirrorNameKey, setSessionStorageItem } from '@/utils/sessionStorage'; | |||
| import { modalConfirm } from '@/utils/ui'; | |||
| @@ -32,10 +33,9 @@ import { | |||
| type TableProps, | |||
| } from 'antd'; | |||
| import classNames from 'classnames'; | |||
| import dayjs from 'dayjs'; | |||
| 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 = { | |||
| name?: string; | |||
| @@ -68,11 +68,13 @@ function MirrorInfo() { | |||
| pageSize: 10, | |||
| }, | |||
| ); | |||
| const isPublic = searchParams.get('isPublic') === 'true'; | |||
| const { message } = App.useApp(); | |||
| const isPublic = searchParams.get('isPublic') === 'true'; | |||
| useEffect(() => { | |||
| getMirrorInfo(); | |||
| }, []); | |||
| useEffect(() => { | |||
| getMirrorVersionList(); | |||
| }, [pagination]); | |||
| @@ -83,8 +85,7 @@ function MirrorInfo() { | |||
| const [res] = await to(getMirrorInfoReq(id)); | |||
| if (res && 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({ | |||
| name, | |||
| description, | |||
| @@ -161,13 +162,13 @@ function MirrorInfo() { | |||
| dataIndex: 'tag_name', | |||
| key: 'tag_name', | |||
| width: '25%', | |||
| render: CommonTableCell, | |||
| render: CommonTableCell(), | |||
| }, | |||
| { | |||
| title: '镜像地址', | |||
| dataIndex: 'url', | |||
| key: 'url', | |||
| render: CommonTableCell, | |||
| render: CommonTableCell(), | |||
| }, | |||
| { | |||
| title: '状态', | |||
| @@ -181,7 +182,7 @@ function MirrorInfo() { | |||
| dataIndex: 'file_size', | |||
| key: 'file_size', | |||
| width: 150, | |||
| render: CommonTableCell, | |||
| render: CommonTableCell(), | |||
| }, | |||
| { | |||
| title: '创建时间', | |||
| @@ -3,7 +3,10 @@ | |||
| &__tabs-container { | |||
| height: 50px; | |||
| 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 { | |||
| @@ -20,7 +23,7 @@ | |||
| } | |||
| &__table { | |||
| height: calc(100% - 34px - 28px); | |||
| height: calc(100% - 32px - 28px); | |||
| margin-top: 28px; | |||
| } | |||
| } | |||
| @@ -11,15 +11,16 @@ import { useCacheState } from '@/hooks/pageCacheState'; | |||
| import { deleteMirrorReq, getMirrorListReq } from '@/services/mirror'; | |||
| import themes from '@/styles/theme.less'; | |||
| import { to } from '@/utils/promise'; | |||
| import { mirrorNameKey, setSessionStorageItem } from '@/utils/sessionStorage'; | |||
| import { modalConfirm } from '@/utils/ui'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import { | |||
| App, | |||
| Button, | |||
| ConfigProvider, | |||
| Input, | |||
| Table, | |||
| Tabs, | |||
| message, | |||
| type TablePaginationConfig, | |||
| type TableProps, | |||
| type TabsProps, | |||
| @@ -27,7 +28,7 @@ import { | |||
| import { type SearchProps } from 'antd/es/input'; | |||
| import classNames from 'classnames'; | |||
| import { useEffect, useState } from 'react'; | |||
| import styles from './list.less'; | |||
| import styles from './index.less'; | |||
| const mirrorTabItems = [ | |||
| { | |||
| @@ -54,6 +55,7 @@ function MirrorList() { | |||
| const [cacheState, setCacheState] = useCacheState(); | |||
| const [activeTab, setActiveTab] = useState<string>(cacheState?.activeTab ?? CommonTabKeys.Public); | |||
| const [searchText, setSearchText] = useState(cacheState?.searchText); | |||
| const [inputText, setInputText] = useState(cacheState?.searchText); | |||
| const [tableData, setTableData] = useState<MirrorData[]>([]); | |||
| const [total, setTotal] = useState(0); | |||
| const [pagination, setPagination] = useState<TablePaginationConfig>( | |||
| @@ -62,14 +64,16 @@ function MirrorList() { | |||
| pageSize: 10, | |||
| }, | |||
| ); | |||
| const { message } = App.useApp(); | |||
| useEffect(() => { | |||
| getMirrorList(); | |||
| }, [activeTab, pagination]); | |||
| }, [activeTab, pagination, searchText]); | |||
| // 切换 Tab,重置数据 | |||
| const hanleTabChange: TabsProps['onChange'] = (value) => { | |||
| setSearchText(''); | |||
| setInputText(''); | |||
| setPagination({ | |||
| current: 1, | |||
| pageSize: 10, | |||
| @@ -78,16 +82,16 @@ function MirrorList() { | |||
| setTableData([]); | |||
| setActiveTab(value); | |||
| }; | |||
| // 获取镜像列表 | |||
| const getMirrorList = async (params?: Record<string, any>) => { | |||
| const reqParams = { | |||
| const getMirrorList = async () => { | |||
| const params: Record<string, any> = { | |||
| page: pagination.current! - 1, | |||
| size: pagination.pageSize, | |||
| name: searchText, | |||
| image_type: activeTab === CommonTabKeys.Public ? 1 : 0, | |||
| ...params, | |||
| }; | |||
| const [res] = await to(getMirrorListReq(reqParams)); | |||
| const [res] = await to(getMirrorListReq(params)); | |||
| if (res && res.data) { | |||
| const { content = [], totalElements = 0 } = res.data; | |||
| setTableData(content); | |||
| @@ -116,10 +120,7 @@ function MirrorList() { | |||
| // 搜索 | |||
| const onSearch: SearchProps['onSearch'] = (value) => { | |||
| // 带参数是为了点清除时,searchText 更新不及时的问题 | |||
| getMirrorList({ | |||
| name: value, | |||
| }); | |||
| setSearchText(value); | |||
| }; | |||
| // 查看详情 | |||
| @@ -146,6 +147,7 @@ function MirrorList() { | |||
| // 创建镜像 | |||
| const createMirror = () => { | |||
| navigate(`/dataset/mirror/create`); | |||
| setSessionStorageItem(mirrorNameKey, ''); | |||
| setCacheState({ | |||
| activeTab, | |||
| pagination, | |||
| @@ -167,27 +169,28 @@ function MirrorList() { | |||
| dataIndex: 'name', | |||
| key: 'name', | |||
| width: '30%', | |||
| render: CommonTableCell, | |||
| render: CommonTableCell(), | |||
| }, | |||
| { | |||
| title: '版本数据', | |||
| dataIndex: 'version_count', | |||
| key: 'version_count', | |||
| width: 100, | |||
| render: CommonTableCell, | |||
| width: '15%', | |||
| render: CommonTableCell(), | |||
| }, | |||
| { | |||
| title: '镜像描述', | |||
| dataIndex: 'description', | |||
| key: 'description', | |||
| render: CommonTableCell, | |||
| ellipsis: true, | |||
| width: '35%', | |||
| render: CommonTableCell(true), | |||
| ellipsis: { showTitle: false }, | |||
| }, | |||
| { | |||
| title: '创建时间', | |||
| dataIndex: 'create_time', | |||
| key: 'create_time', | |||
| width: 200, | |||
| width: '20%', | |||
| render: DateTableCell, | |||
| }, | |||
| { | |||
| @@ -233,12 +236,7 @@ function MirrorList() { | |||
| return ( | |||
| <div className={styles['mirror-list']}> | |||
| <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 className={styles['mirror-list__content']}> | |||
| <div className={styles['mirror-list__content__filter']}> | |||
| @@ -246,9 +244,9 @@ function MirrorList() { | |||
| placeholder="按数据集名称筛选" | |||
| allowClear | |||
| onSearch={onSearch} | |||
| onChange={(e) => setSearchText(e.target.value)} | |||
| onChange={(e) => setInputText(e.target.value)} | |||
| style={{ width: 300 }} | |||
| value={searchText} | |||
| value={inputText} | |||
| /> | |||
| {activeTab === CommonTabKeys.Private && ( | |||
| <Button | |||
| @@ -1,20 +1,17 @@ | |||
| /* | |||
| * @Author: 赵伟 | |||
| * @Date: 2024-04-18 18:35:41 | |||
| * @Description: | |||
| * @Description: 镜像状态组件 | |||
| */ | |||
| import { MirrorVersionStatus } from '@/enums'; | |||
| import styles from './index.less'; | |||
| type MirrorVersionStatusKeys = keyof typeof MirrorVersionStatus; | |||
| type MirrorVersionStatusValues = (typeof MirrorVersionStatus)[MirrorVersionStatusKeys]; | |||
| export type MirrorVersionStatusInfo = { | |||
| text: string; | |||
| classname: string; | |||
| }; | |||
| const statusInfo: Record<MirrorVersionStatusValues, MirrorVersionStatusInfo> = { | |||
| const statusInfo: Record<MirrorVersionStatus, MirrorVersionStatusInfo> = { | |||
| [MirrorVersionStatus.Building]: { | |||
| text: '构建中', | |||
| 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]) { | |||
| return <span>--</span>; | |||
| } | |||
| @@ -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; | |||
| @@ -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; | |||
| } | |||
| } | |||
| } | |||
| @@ -1,61 +1,34 @@ | |||
| import { getAccessToken } from '@/access'; | |||
| import KFIcon from '@/components/KFIcon'; | |||
| import AddVersionModal from '@/pages/Dataset/components/AddVersionModal'; | |||
| import { ResourceType } from '@/pages/Dataset/types'; | |||
| import { | |||
| addModelsVersionDetail, | |||
| deleteModelVersion, | |||
| getModelById, | |||
| getModelVersionIdList, | |||
| getModelVersionsById, | |||
| } from '@/services/dataset/index.js'; | |||
| import { formatDate } from '@/utils/date'; | |||
| 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 { useParams } from 'react-router-dom'; | |||
| import Styles from './index.less'; | |||
| import Styles from './intro.less'; | |||
| const { Search } = Input; | |||
| const { TabPane } = Tabs; | |||
| 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 [dialogTitle, setDialogTitle] = useState('新建版本'); | |||
| const [isModalOpen, setIsModalOpen] = useState(false); | |||
| const [datasetDetailObj, setDatasetDetailObj] = useState({}); | |||
| const [version, setVersion] = useState(null); | |||
| const [versionList, setVersionList] = useState([]); | |||
| const locationParams = useParams(); //新版本获取路由参数接口 | |||
| console.log(locationParams); | |||
| const [searchParams] = useSearchParams(); | |||
| const [wordList, setWordList] = useState([]); | |||
| const [uuid, setUuid] = useState(Date.now()); | |||
| const { message } = App.useApp(); | |||
| const isPublic = searchParams.get('isPublic') === 'true'; | |||
| const getModelByDetail = () => { | |||
| getModelById(locationParams.id).then((ret) => { | |||
| console.log(ret); | |||
| @@ -76,6 +49,9 @@ const Dataset = () => { | |||
| ); | |||
| setVersion(ret.data[0]); | |||
| getModelVersions({ version: ret.data[0], models_id: locationParams.id }); | |||
| } else { | |||
| setVersion(null); | |||
| setWordList([]); | |||
| } | |||
| }); | |||
| }; | |||
| @@ -85,29 +61,21 @@ const Dataset = () => { | |||
| return () => {}; | |||
| }, []); | |||
| 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 = () => { | |||
| 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: '确认', | |||
| cancelText: '取消', | |||
| @@ -119,13 +87,7 @@ const Dataset = () => { | |||
| }, | |||
| }); | |||
| }; | |||
| const onFinish = () => { | |||
| addModelsVersionDetail(formList).then((ret) => { | |||
| getModelVersionsList(); | |||
| setIsModalOpen(false); | |||
| message.success('创建成功'); | |||
| }); | |||
| }; | |||
| const getModelVersions = (params) => { | |||
| getModelVersionIdList(params).then((ret) => { | |||
| setWordList(ret?.data?.content ?? []); | |||
| @@ -151,9 +113,7 @@ const Dataset = () => { | |||
| setVersion(''); | |||
| } | |||
| }; | |||
| const onFinishFailed = (errorInfo) => { | |||
| console.log('Failed:', errorInfo); | |||
| }; | |||
| const columns = [ | |||
| { | |||
| title: '序号', | |||
| @@ -185,7 +145,7 @@ const Dataset = () => { | |||
| title: '更新时间', | |||
| dataIndex: '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: '操作', | |||
| @@ -262,15 +222,18 @@ const Dataset = () => { | |||
| <div | |||
| 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 | |||
| type="default" | |||
| className={Styles.plusButton} | |||
| @@ -293,104 +256,6 @@ const Dataset = () => { | |||
| </TabPane> | |||
| </Tabs> | |||
| </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> | |||
| ); | |||
| }; | |||
| @@ -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; | |||
| } | |||
| @@ -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; | |||
| @@ -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; | |||
| @@ -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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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; | |||
| @@ -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; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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; | |||
| @@ -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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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; | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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; | |||
| @@ -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', | |||
| } | |||
| @@ -1,4 +1,5 @@ | |||
| import { DictOptionType, DictValueEnumObj } from '@/components/DictTag'; | |||
| import KFModal from '@/components/KFModal'; | |||
| import { | |||
| ProForm, | |||
| ProFormCaptcha, | |||
| @@ -9,9 +10,8 @@ import { | |||
| ProFormTextArea, | |||
| } from '@ant-design/pro-components'; | |||
| import { FormattedMessage, useIntl } from '@umijs/max'; | |||
| import { Form, Modal } from 'antd'; | |||
| import { Form } from 'antd'; | |||
| import React, { useEffect } from 'react'; | |||
| import KFModal from '@/components/KFModal'; | |||
| /** | |||
| * 定时任务调度 Edit Form | |||
| * | |||
| @@ -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; | |||
| } | |||
| } | |||