Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
commit
4a6ab8ff90
|
@ -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_a182r7rksx5.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',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -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',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -194,17 +206,17 @@ export default [
|
|||
{
|
||||
name: '模型列表',
|
||||
path: '',
|
||||
component: './ModelDeployment/list',
|
||||
component: './ModelDeployment/List',
|
||||
},
|
||||
{
|
||||
name: '镜像详情',
|
||||
path: ':id',
|
||||
component: './ModelDeployment/info',
|
||||
component: './ModelDeployment/Info',
|
||||
},
|
||||
{
|
||||
name: '创建镜像',
|
||||
path: 'create',
|
||||
component: './ModelDeployment/create',
|
||||
component: './ModelDeployment/Create',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -224,6 +224,9 @@ export const antd: RuntimeAntdConfig = (memo) => {
|
|||
inputFontSizeLG: parseInt(themes['fontSizeInputLg']),
|
||||
paddingBlockLG: 10,
|
||||
};
|
||||
memo.theme.components.Select = {
|
||||
singleItemHeightLG: 46,
|
||||
};
|
||||
memo.theme.components.Table = {
|
||||
headerBg: 'rgba(242, 244, 247, 0.36)',
|
||||
headerBorderRadius: 4,
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.6 KiB |
|
@ -12,7 +12,11 @@ function renderCell(text?: string | null) {
|
|||
|
||||
function CommonTableCell(ellipsis: boolean = false) {
|
||||
if (ellipsis) {
|
||||
return (text?: string | null) => <Tooltip title={text}>{renderCell(text)}</Tooltip>;
|
||||
return (text?: string | null) => (
|
||||
<Tooltip title={text} placement="topLeft" overlayStyle={{ maxWidth: '400px' }}>
|
||||
{renderCell(text)}
|
||||
</Tooltip>
|
||||
);
|
||||
} else {
|
||||
return renderCell;
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -4,4 +4,7 @@
|
|||
height: 50px;
|
||||
padding-left: 30px;
|
||||
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;
|
|
@ -4,9 +4,25 @@ 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', // 失败
|
||||
}
|
||||
|
||||
export const modelDeploymentStatusOptions = [
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '启动中', value: ModelDeploymentStatus.Init },
|
||||
{ label: '运行中', value: ModelDeploymentStatus.Running },
|
||||
{ label: '已停止', value: ModelDeploymentStatus.Stopped },
|
||||
{ label: '失败', value: ModelDeploymentStatus.Failed },
|
||||
];
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
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,18 @@
|
|||
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];
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -20,7 +20,7 @@ import {
|
|||
} from 'antd';
|
||||
import { omit } from 'lodash';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { CategoryData } from '../../type';
|
||||
import { CategoryData } from '../../types';
|
||||
import styles from './index.less';
|
||||
|
||||
interface AddDatasetModalProps extends Omit<ModalProps, 'onOk'> {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { getAccessToken } from '@/access';
|
||||
import KFIcon from '@/components/KFIcon';
|
||||
import KFModal from '@/components/KFModal';
|
||||
import { CategoryData } from '@/pages/Dataset/type';
|
||||
import { CategoryData } from '@/pages/Dataset/types';
|
||||
import { addModel } from '@/services/dataset/index.js';
|
||||
import { to } from '@/utils/promise';
|
||||
import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { getAccessToken } from '@/access';
|
||||
import KFIcon from '@/components/KFIcon';
|
||||
import KFModal from '@/components/KFModal';
|
||||
import { ResourceType, resourceConfig } from '@/pages/Dataset/type';
|
||||
import { ResourceType, resourceConfig } from '@/pages/Dataset/types';
|
||||
import { to } from '@/utils/promise';
|
||||
import { getFileListFromEvent, validateUploadFiles } from '@/utils/ui';
|
||||
import {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import classNames from 'classnames';
|
||||
import { CategoryData, ResourceType, resourceConfig } from '../../type';
|
||||
import { CategoryData, ResourceType, resourceConfig } from '../../types';
|
||||
import styles from './index.less';
|
||||
|
||||
type CategoryItemProps = {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Flex, Input } from 'antd';
|
||||
import { CategoryData, ResourceType, resourceConfig } from '../../type';
|
||||
import { CategoryData, ResourceType, resourceConfig } from '../../types';
|
||||
import CategoryItem from '../CategoryItem';
|
||||
import styles from './index.less';
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import { modalConfirm } from '@/utils/ui';
|
|||
import { useNavigate } from '@umijs/max';
|
||||
import { Button, Input, Pagination, PaginationProps, message } from 'antd';
|
||||
import { Ref, forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
||||
import { CategoryData, ResourceData, ResourceType, resourceConfig } from '../../type';
|
||||
import { CategoryData, ResourceData, ResourceType, resourceConfig } from '../../types';
|
||||
import AddDatasetModal from '../AddDatasetModal';
|
||||
import ResourceItem from '../Resourcetem';
|
||||
import styles from './index.less';
|
||||
|
|
|
@ -4,5 +4,8 @@
|
|||
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%;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import { getAssetIcon } from '@/services/dataset/index.js';
|
|||
import { to } from '@/utils/promise';
|
||||
import { Flex, Tabs, type TabsProps } from 'antd';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { CategoryData, ResourceType, resourceConfig } from '../../type';
|
||||
import { CategoryData, ResourceType, resourceConfig } from '../../types';
|
||||
import CategoryList from '../CategoryList';
|
||||
import ResourceList, { ResourceListRef } from '../ResourceList';
|
||||
import styles from './index.less';
|
||||
|
|
|
@ -3,7 +3,7 @@ import creatByImg from '@/assets/img/creatBy.png';
|
|||
import KFIcon from '@/components/KFIcon';
|
||||
import { formatDate } from '@/utils/date';
|
||||
import { Button, Flex, Typography } from 'antd';
|
||||
import { ResourceData } from '../../type';
|
||||
import { ResourceData } from '../../types';
|
||||
import styles from './index.less';
|
||||
|
||||
type ResourceItemProps = {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import ResourcePage from './components/ResourcePage';
|
||||
import { ResourceType } from './type';
|
||||
import { ResourceType } from './types';
|
||||
|
||||
const DatasetPage = () => {
|
||||
return <ResourcePage resourceType={ResourceType.Dataset} />;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import KFIcon from '@/components/KFIcon';
|
||||
import { ResourceType } from '@/pages/Dataset/type';
|
||||
import { ResourceType } from '@/pages/Dataset/types';
|
||||
import {
|
||||
deleteDatasetVersion,
|
||||
getDatasetById,
|
||||
|
|
|
@ -7,7 +7,10 @@
|
|||
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;
|
||||
|
|
|
@ -19,9 +19,6 @@ export enum ResourceType {
|
|||
Dataset = 'Dataset', // 数据集
|
||||
}
|
||||
|
||||
type ResourceTypeKeys = keyof typeof ResourceType;
|
||||
export type ResourceTypeValues = (typeof ResourceType)[ResourceTypeKeys];
|
||||
|
||||
type ResourceTypeInfo = {
|
||||
getList: (params: any) => Promise<any>;
|
||||
getVersions: (params: any) => Promise<any>;
|
||||
|
@ -45,7 +42,7 @@ type ResourceTypeInfo = {
|
|||
uploadAccept?: string;
|
||||
};
|
||||
|
||||
export const resourceConfig: Record<ResourceTypeValues, ResourceTypeInfo> = {
|
||||
export const resourceConfig: Record<ResourceType, ResourceTypeInfo> = {
|
||||
[ResourceType.Dataset]: {
|
||||
getList: getDatasetList,
|
||||
getVersions: getDatasetVersionsById,
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import SubAreaTitle from '@/components/SubAreaTitle';
|
||||
import { getComputingResourceReq } from '@/services/pipeline';
|
||||
import { PipelineNodeModelSerialize } from '@/types';
|
||||
import { Form, Input, type FormProps } from 'antd';
|
||||
import { to } from '@/utils/promise';
|
||||
import { Form, Input, Select, type FormProps } from 'antd';
|
||||
import { useEffect, useState } from 'react';
|
||||
import styles from './index.less';
|
||||
const { TextArea } = Input;
|
||||
|
||||
|
@ -10,6 +13,25 @@ type ExperimentParameterProps = {
|
|||
};
|
||||
|
||||
function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) {
|
||||
const [resourceStandardList, setResourceStandardList] = useState([]); // 资源规模列表
|
||||
|
||||
useEffect(() => {
|
||||
getComputingResource();
|
||||
}, []);
|
||||
|
||||
// 获取资源规格列表数据
|
||||
const getComputingResource = 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 controlStrategyList = Object.entries(nodeData.control_strategy ?? {}).map(
|
||||
([key, value]) => ({ key, value }),
|
||||
|
@ -103,7 +125,14 @@ function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) {
|
|||
},
|
||||
]}
|
||||
>
|
||||
<Input disabled />
|
||||
<Select
|
||||
options={resourceStandardList}
|
||||
disabled
|
||||
fieldNames={{
|
||||
label: 'description',
|
||||
value: 'standard',
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="挂载路径" name="mount_path">
|
||||
<Input disabled />
|
||||
|
@ -117,7 +146,7 @@ function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) {
|
|||
name={['control_strategy', item.key]}
|
||||
label={item.value.label}
|
||||
getValueProps={(e) => {
|
||||
return { value: e.value };
|
||||
return { value: e.showValue || e.value };
|
||||
}}
|
||||
>
|
||||
<Input disabled />
|
||||
|
@ -133,7 +162,7 @@ function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) {
|
|||
label={item.value.label + '(' + item.key + ')'}
|
||||
rules={[{ required: item.value.require ? true : false }]}
|
||||
getValueProps={(e) => {
|
||||
return { value: e.value };
|
||||
return { value: e.showValue || e.value };
|
||||
}}
|
||||
>
|
||||
<Input disabled />
|
||||
|
@ -149,7 +178,7 @@ function ExperimentParameter({ form, nodeData }: ExperimentParameterProps) {
|
|||
label={item.value.label + '(' + item.key + ')'}
|
||||
rules={[{ required: item.value.require ? true : false }]}
|
||||
getValueProps={(e) => {
|
||||
return { value: e.value };
|
||||
return { value: e.showValue || e.value };
|
||||
}}
|
||||
>
|
||||
<Input disabled />
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
*/
|
||||
|
||||
import { useStateRef } from '@/hooks';
|
||||
import { ExperimentLog } from '@/pages/Experiment/experimentText/props';
|
||||
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';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { ExperimentLog } from '@/pages/Experiment/experimentText/props';
|
||||
import { ExperimentStatus } from '@/pages/Experiment/status';
|
||||
import { ExperimentLog } from '@/pages/Experiment/training/props';
|
||||
import LogGroup from '../LogGroup';
|
||||
import styles from './index.less';
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import CommonTableCell from '@/components/CommonTableCell';
|
||||
import KFIcon from '@/components/KFIcon';
|
||||
import {
|
||||
deleteExperimentById,
|
||||
|
@ -198,7 +199,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) => {
|
||||
|
@ -255,7 +256,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) => {
|
||||
|
@ -290,6 +291,8 @@ function Experiment() {
|
|||
title: '实验描述',
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
render: CommonTableCell(true),
|
||||
ellipsis: { showTitle: false },
|
||||
},
|
||||
{
|
||||
title: '最近五次运行状态',
|
||||
|
|
|
@ -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,10 +15,7 @@ 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',
|
||||
|
|
|
@ -27,5 +27,7 @@
|
|||
width: 100%;
|
||||
height: calc(100% - 56px);
|
||||
background-color: @background-color;
|
||||
background-image: url(/assets/images/pipeline-canvas-back.png);
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
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 { omit } from 'lodash';
|
||||
import { useEffect, useState } from 'react';
|
||||
import styles from './create.less';
|
||||
import styles from './index.less';
|
||||
|
||||
type FormData = {
|
||||
name: string;
|
||||
|
@ -56,11 +60,14 @@ function MirrorCreate() {
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
const name = getSessionItemThenRemove(mirrorNameKey);
|
||||
const name = getSessionStorageItem(mirrorNameKey);
|
||||
if (name) {
|
||||
form.setFieldValue('name', name);
|
||||
setNameDisabled(true);
|
||||
}
|
||||
return () => {
|
||||
removeSessionStorageItem(mirrorNameKey);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 创建公网、本地镜像
|
|
@ -34,8 +34,8 @@ import {
|
|||
} from 'antd';
|
||||
import classNames from 'classnames';
|
||||
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;
|
|
@ -4,6 +4,9 @@
|
|||
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%;
|
||||
}
|
||||
|
||||
&__content {
|
|
@ -11,6 +11,7 @@ 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 {
|
||||
|
@ -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 = [
|
||||
{
|
||||
|
@ -145,6 +146,7 @@ function MirrorList() {
|
|||
// 创建镜像
|
||||
const createMirror = () => {
|
||||
navigate(`/dataset/mirror/create`);
|
||||
setSessionStorageItem(mirrorNameKey, '');
|
||||
setCacheState({
|
||||
activeTab,
|
||||
pagination,
|
|
@ -6,15 +6,12 @@
|
|||
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'],
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import ResourcePage from '@/pages/Dataset/components/ResourcePage';
|
||||
import { ResourceType } from '@/pages/Dataset/type';
|
||||
import { ResourceType } from '@/pages/Dataset/types';
|
||||
|
||||
const ModelPage = () => {
|
||||
return <ResourcePage resourceType={ResourceType.Model} />;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import KFIcon from '@/components/KFIcon';
|
||||
import AddVersionModal from '@/pages/Dataset/components/AddVersionModal';
|
||||
import { ResourceType } from '@/pages/Dataset/type';
|
||||
import { ResourceType } from '@/pages/Dataset/types';
|
||||
import {
|
||||
deleteModelVersion,
|
||||
getModelById,
|
||||
|
|
|
@ -7,8 +7,10 @@
|
|||
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;
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
margin-top: 10px;
|
||||
padding: 30px 30px 10px;
|
||||
overflow: auto;
|
||||
color: @text-color;
|
||||
font-size: @font-size-content;
|
||||
background-color: white;
|
||||
border-radius: 10px;
|
||||
|
|
@ -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;
|
|
@ -9,6 +9,7 @@
|
|||
line-height: 1.6;
|
||||
|
||||
.label {
|
||||
flex: none;
|
||||
width: 80px;
|
||||
color: @text-color-secondary;
|
||||
}
|
||||
|
@ -16,6 +17,8 @@
|
|||
.value {
|
||||
flex: 1;
|
||||
color: @text-color;
|
||||
white-space: pre-line;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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={24}>
|
||||
<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,348 @@
|
|||
/*
|
||||
* @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) && (
|
||||
<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;
|
|
@ -1,11 +0,0 @@
|
|||
.mirror-status-cell {
|
||||
color: @text-color;
|
||||
|
||||
&--success {
|
||||
color: @success-color;
|
||||
}
|
||||
|
||||
&--error {
|
||||
color: @error-color;
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
* @Author: 赵伟
|
||||
* @Date: 2024-04-18 18:35:41
|
||||
* @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> = {
|
||||
[MirrorVersionStatus.Building]: {
|
||||
text: '构建中',
|
||||
classname: styles['mirror-status-cell'],
|
||||
},
|
||||
[MirrorVersionStatus.Available]: {
|
||||
classname: styles['mirror-status-cell--success'],
|
||||
text: '可用',
|
||||
},
|
||||
[MirrorVersionStatus.Failed]: {
|
||||
classname: styles['mirror-status-cell--error'],
|
||||
text: '构建失败',
|
||||
},
|
||||
};
|
||||
|
||||
function MirrorStatusCell(status: MirrorVersionStatus) {
|
||||
if (status === null || status === undefined || !statusInfo[status]) {
|
||||
return <span>--</span>;
|
||||
}
|
||||
return <span className={statusInfo[status].classname}>{statusInfo[status].text}</span>;
|
||||
}
|
||||
|
||||
export default MirrorStatusCell;
|
|
@ -0,0 +1,15 @@
|
|||
.model-deployment-status-cell {
|
||||
color: @text-color;
|
||||
|
||||
&--running {
|
||||
color: @primary-color;
|
||||
}
|
||||
|
||||
&--stopped {
|
||||
color: @warning-color;
|
||||
}
|
||||
|
||||
&--error {
|
||||
color: @error-color;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* @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: '失败',
|
||||
},
|
||||
};
|
||||
|
||||
function ModelDeploymentStatusCell(status: ModelDeploymentStatus | undefined) {
|
||||
if (status === null || status === undefined || !statusInfo[status]) {
|
||||
return <span>--</span>;
|
||||
}
|
||||
return <span className={statusInfo[status].classname}>{statusInfo[status].text}</span>;
|
||||
}
|
||||
|
||||
export default ModelDeploymentStatusCell;
|
|
@ -1,297 +0,0 @@
|
|||
/*
|
||||
* @Author: 赵伟
|
||||
* @Date: 2024-04-16 13:58:08
|
||||
* @Description: 创建模型部署
|
||||
*/
|
||||
import PageTitle from '@/components/PageTitle';
|
||||
import SubAreaTitle from '@/components/SubAreaTitle';
|
||||
import { CommonTabKeys } from '@/enums';
|
||||
import { createMirrorReq } from '@/services/mirror';
|
||||
import { getComputingResourceReq } from '@/services/pipeline';
|
||||
import { to } from '@/utils/promise';
|
||||
import { getSessionItemThenRemove, mirrorNameKey } from '@/utils/sessionStorage';
|
||||
import { validateUploadFiles } from '@/utils/ui';
|
||||
import { useNavigate } from '@umijs/max';
|
||||
import { Button, Col, Form, Input, Row, Select, UploadFile, message, type SelectProps } from 'antd';
|
||||
import { omit } from 'lodash';
|
||||
import { useEffect, useState } from 'react';
|
||||
import styles from './create.less';
|
||||
|
||||
type FormData = {
|
||||
name: string;
|
||||
tag: string;
|
||||
description: string;
|
||||
path?: string;
|
||||
upload_type: string;
|
||||
fileList?: UploadFile[];
|
||||
};
|
||||
|
||||
function ModelDeploymentCreate() {
|
||||
const navgite = useNavigate();
|
||||
const [form] = Form.useForm();
|
||||
const [nameDisabled, setNameDisabled] = useState(false);
|
||||
const [resourceStandardList, setResourceStandardList] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
const name = getSessionItemThenRemove(mirrorNameKey);
|
||||
if (name) {
|
||||
form.setFieldValue('name', name);
|
||||
setNameDisabled(true);
|
||||
}
|
||||
getComputingResource();
|
||||
}, []);
|
||||
|
||||
const getComputingResource = 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['filterOption'] = (
|
||||
input: string,
|
||||
{ computing_resource = '' },
|
||||
) => {
|
||||
return computing_resource.toLocaleLowerCase().includes(input.toLocaleLowerCase());
|
||||
};
|
||||
|
||||
// 创建公网、本地镜像
|
||||
const createPublicMirror = async (formData: FormData) => {
|
||||
const upload_type = formData['upload_type'];
|
||||
let params;
|
||||
if (upload_type === CommonTabKeys.Public) {
|
||||
params = {
|
||||
...omit(formData, ['upload_type']),
|
||||
upload_type: 0,
|
||||
image_type: 0,
|
||||
};
|
||||
} else {
|
||||
const fileList = formData['fileList'] ?? [];
|
||||
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 [res] = await to(createMirrorReq(params));
|
||||
if (res) {
|
||||
message.success('创建成功');
|
||||
navgite(-1);
|
||||
}
|
||||
};
|
||||
|
||||
// 提交
|
||||
const handleSubmit = (values: FormData) => {
|
||||
createPublicMirror(values);
|
||||
};
|
||||
|
||||
// 取消
|
||||
const cancel = () => {
|
||||
navgite(-1);
|
||||
};
|
||||
|
||||
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: '130px' }}
|
||||
wrapperCol={{ flex: 1 }}
|
||||
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={10}>
|
||||
<Col span={10}>
|
||||
<Form.Item
|
||||
label="服务名称"
|
||||
name="name"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入服务名称',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
placeholder="请输入服务名称"
|
||||
maxLength={64}
|
||||
disabled={nameDisabled}
|
||||
showCount
|
||||
allowClear
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={10}>
|
||||
<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/mirror-version.png')}
|
||||
style={{ marginTop: '20px', marginBottom: '24px' }}
|
||||
></SubAreaTitle>
|
||||
|
||||
<Row gutter={10}>
|
||||
<Col span={10}>
|
||||
<Form.Item
|
||||
label="选择模型"
|
||||
name="name"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入模型',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
placeholder="请输入模型"
|
||||
maxLength={64}
|
||||
disabled={nameDisabled}
|
||||
showCount
|
||||
allowClear
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={10}>
|
||||
<Col span={10}>
|
||||
<Form.Item
|
||||
label="选择镜像"
|
||||
name="name"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入镜像',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
placeholder="请输入镜像"
|
||||
maxLength={64}
|
||||
disabled={nameDisabled}
|
||||
showCount
|
||||
allowClear
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={10}>
|
||||
<Col span={10}>
|
||||
<Form.Item
|
||||
label="资源规格"
|
||||
name="name"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择资源规格',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Select
|
||||
showSearch
|
||||
placeholder="请选择资源规格"
|
||||
filterOption={filterResourceStandard}
|
||||
options={resourceStandardList}
|
||||
fieldNames={{
|
||||
label: 'description',
|
||||
value: 'standard',
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row gutter={10}>
|
||||
<Col span={10}>
|
||||
<Form.Item
|
||||
label="副本数量"
|
||||
name="name"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入副本数量',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
placeholder="请输入副本数量"
|
||||
maxLength={64}
|
||||
disabled={nameDisabled}
|
||||
showCount
|
||||
allowClear
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row gutter={10}>
|
||||
<Col span={10}>
|
||||
<Form.Item label="环境变量" name="name">
|
||||
<Button type="link" style={{ padding: '0' }}>
|
||||
添加环境变量
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Form.Item wrapperCol={{ offset: 0, span: 16 }}>
|
||||
<Button type="primary" htmlType="submit">
|
||||
确定
|
||||
</Button>
|
||||
<Button
|
||||
type="default"
|
||||
htmlType="button"
|
||||
onClick={cancel}
|
||||
style={{ marginLeft: '20px' }}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ModelDeploymentCreate;
|
|
@ -1,148 +0,0 @@
|
|||
/*
|
||||
* @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 { getMirrorInfoReq } from '@/services/mirror';
|
||||
import { formatDate } from '@/utils/date';
|
||||
import { to } from '@/utils/promise';
|
||||
import { useNavigate, useParams } from '@umijs/max';
|
||||
import { Col, Row, Tabs, type TabsProps } from 'antd';
|
||||
import { useEffect, useState } from 'react';
|
||||
import styles from './info.less';
|
||||
|
||||
type MirrorInfoData = {
|
||||
name?: string;
|
||||
description?: string;
|
||||
version_count?: string;
|
||||
create_time?: string;
|
||||
};
|
||||
|
||||
type MirrorVersionData = {
|
||||
id: number;
|
||||
version: string;
|
||||
url: string;
|
||||
status: string;
|
||||
file_size: string;
|
||||
create_time: string;
|
||||
};
|
||||
|
||||
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 navigate = useNavigate();
|
||||
const urlParams = useParams();
|
||||
|
||||
const [mirrorInfo, setMirrorInfo] = useState<MirrorInfoData>({});
|
||||
|
||||
const [activeTab, setActiveTab] = useState<string>('1');
|
||||
useEffect(() => {
|
||||
getMirrorInfo();
|
||||
}, []);
|
||||
|
||||
// 获取镜像详情
|
||||
const getMirrorInfo = async () => {
|
||||
const id = Number(urlParams.id);
|
||||
const [res] = await to(getMirrorInfoReq(id));
|
||||
if (res && res.data) {
|
||||
const { name = '', description = '', version_count = '', create_time: time } = res.data;
|
||||
const create_time = formatDate(time);
|
||||
setMirrorInfo({
|
||||
name,
|
||||
description,
|
||||
version_count,
|
||||
create_time,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 切换 Tab,重置数据
|
||||
const hanleTabChange: TabsProps['onChange'] = (value) => {
|
||||
setActiveTab(value);
|
||||
};
|
||||
|
||||
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']}>{mirrorInfo.name}</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={10}>
|
||||
<div className={styles['model-deployment-info__basic__item']}>
|
||||
<div className={styles['label']}>镜像:</div>
|
||||
<div className={styles['value']}>{mirrorInfo.version_count ?? '--'}</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']}>{mirrorInfo.name}</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={10}>
|
||||
<div className={styles['model-deployment-info__basic__item']}>
|
||||
<div className={styles['label']}>模型:</div>
|
||||
<div className={styles['value']}>{mirrorInfo.version_count ?? '--'}</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']}>{mirrorInfo.name}</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={40}>
|
||||
<Col span={24}>
|
||||
<div className={styles['model-deployment-info__basic__item']}>
|
||||
<div className={styles['label']}>描述:</div>
|
||||
<div className={styles['value']}>{mirrorInfo.description}</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
<div>
|
||||
<Tabs activeKey={activeTab} items={tabItems} onChange={hanleTabChange} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ModelDeploymentInfo;
|
|
@ -1,283 +0,0 @@
|
|||
/*
|
||||
* @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 { useCacheState } from '@/hooks/pageCacheState';
|
||||
import { deleteMirrorReq, getMirrorListReq } from '@/services/mirror';
|
||||
import themes from '@/styles/theme.less';
|
||||
import { to } from '@/utils/promise';
|
||||
import { modalConfirm } from '@/utils/ui';
|
||||
import { useNavigate } from '@umijs/max';
|
||||
import {
|
||||
App,
|
||||
Button,
|
||||
ConfigProvider,
|
||||
Input,
|
||||
Table,
|
||||
type TablePaginationConfig,
|
||||
type TableProps,
|
||||
} from 'antd';
|
||||
import { type SearchProps } from 'antd/es/input';
|
||||
import classNames from 'classnames';
|
||||
import { useEffect, useState } from 'react';
|
||||
import styles from './list.less';
|
||||
|
||||
export type MirrorData = {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
create_time: string;
|
||||
};
|
||||
|
||||
function ModelDeployment() {
|
||||
const navigate = useNavigate();
|
||||
const { message } = App.useApp();
|
||||
const [cacheState, setCacheState] = useCacheState();
|
||||
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<Required<TablePaginationConfig>>(
|
||||
cacheState?.pagination ?? {
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
getMirrorList();
|
||||
}, [pagination, searchText]);
|
||||
|
||||
// 获取镜像列表
|
||||
const getMirrorList = async () => {
|
||||
const params: Record<string, any> = {
|
||||
page: pagination.current - 1,
|
||||
size: pagination.pageSize,
|
||||
name: searchText,
|
||||
image_type: 1,
|
||||
};
|
||||
const [res] = await to(getMirrorListReq(params));
|
||||
if (res && res.data) {
|
||||
const { content = [], totalElements = 0 } = res.data;
|
||||
setTableData(content);
|
||||
setTotal(totalElements);
|
||||
}
|
||||
};
|
||||
|
||||
// 删除镜像
|
||||
const deleteMirror = async (id: number) => {
|
||||
const [res] = await to(deleteMirrorReq(id));
|
||||
if (res) {
|
||||
message.success('删除成功');
|
||||
// 如果是一页的唯一数据,删除时,请求第一页的数据
|
||||
// 否则直接刷新这一页的数据
|
||||
// 避免回到第一页
|
||||
if (tableData.length > 1) {
|
||||
setPagination((prev) => ({
|
||||
...prev,
|
||||
current: 1,
|
||||
}));
|
||||
} else {
|
||||
getMirrorList();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 搜索
|
||||
const onSearch: SearchProps['onSearch'] = (value) => {
|
||||
setSearchText(value);
|
||||
};
|
||||
|
||||
// 查看详情
|
||||
const toDetail = (record: MirrorData) => {
|
||||
navigate(`/modelDeployment/${record.id}`);
|
||||
setCacheState({
|
||||
pagination,
|
||||
searchText,
|
||||
});
|
||||
};
|
||||
|
||||
// 处理删除
|
||||
const handleMirrorDelete = (record: MirrorData) => {
|
||||
modalConfirm({
|
||||
title: '删除后,该镜像将不可恢复',
|
||||
content: '是否确认删除?',
|
||||
onOk: () => {
|
||||
deleteMirror(record.id);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 创建镜像
|
||||
const createMirror = () => {
|
||||
navigate(`/modelDeployment/create`);
|
||||
setCacheState({
|
||||
pagination,
|
||||
searchText,
|
||||
});
|
||||
};
|
||||
|
||||
// 分页切换
|
||||
const handleTableChange: TableProps['onChange'] = (pagination, filters, sorter, { action }) => {
|
||||
if (action === 'paginate') {
|
||||
setPagination(pagination);
|
||||
}
|
||||
// console.log(pagination, filters, sorter, action);
|
||||
};
|
||||
|
||||
const columns: TableProps<MirrorData>['columns'] = [
|
||||
{
|
||||
title: '序号',
|
||||
dataIndex: 'index',
|
||||
key: 'index',
|
||||
width: 100,
|
||||
align: 'center',
|
||||
render(text, record, index) {
|
||||
return <span>{(pagination.current - 1) * pagination.pageSize + index + 1}</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '服务名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: '30%',
|
||||
render: CommonTableCell(),
|
||||
},
|
||||
{
|
||||
title: '模型',
|
||||
dataIndex: 'version_count',
|
||||
key: 'version_count',
|
||||
width: '20%',
|
||||
render: CommonTableCell(),
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'version_count',
|
||||
key: 'version_count',
|
||||
width: '10%',
|
||||
render: CommonTableCell(),
|
||||
},
|
||||
{
|
||||
title: '创建人',
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
render: CommonTableCell(true),
|
||||
width: '20%',
|
||||
ellipsis: { showTitle: false },
|
||||
},
|
||||
{
|
||||
title: '更新时间',
|
||||
dataIndex: 'create_time',
|
||||
key: 'create_time',
|
||||
width: '20%',
|
||||
render: DateTableCell,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
width: 350,
|
||||
key: 'operation',
|
||||
render: (_: any, record: MirrorData) => (
|
||||
<div>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
key="edit"
|
||||
icon={<KFIcon type="icon-bianji" />}
|
||||
onClick={() => toDetail(record)}
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
key="run"
|
||||
icon={<KFIcon type="icon-yunhang" />}
|
||||
onClick={() => toDetail(record)}
|
||||
>
|
||||
启动
|
||||
</Button>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
key="stop"
|
||||
icon={<KFIcon type="icon-tingzhi" />}
|
||||
onClick={() => toDetail(record)}
|
||||
>
|
||||
停止
|
||||
</Button>
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
token: {
|
||||
colorLink: themes['warningColor'],
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
key="remove"
|
||||
icon={<KFIcon type="icon-shanchu" />}
|
||||
onClick={() => handleMirrorDelete(record)}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
</ConfigProvider>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className={styles['model-deployment']}>
|
||||
<PageTitle title="模型列表"></PageTitle>
|
||||
<div className={styles['model-deployment__content']}>
|
||||
<div className={styles['model-deployment__filter']}>
|
||||
<Input.Search
|
||||
placeholder="按数据集名称筛选"
|
||||
allowClear
|
||||
onSearch={onSearch}
|
||||
onChange={(e) => setInputText(e.target.value)}
|
||||
style={{ width: 300 }}
|
||||
value={inputText}
|
||||
/>
|
||||
<Button
|
||||
style={{ marginLeft: '20px' }}
|
||||
type="default"
|
||||
onClick={createMirror}
|
||||
icon={<KFIcon type="icon-xinjian2" />}
|
||||
>
|
||||
创建推理服务
|
||||
</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="id"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ModelDeployment;
|
|
@ -0,0 +1,38 @@
|
|||
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',
|
||||
}
|
||||
|
||||
// 状态
|
||||
export type ModelDeploymentStatusInfo = {
|
||||
text: string;
|
||||
classname: string;
|
||||
};
|
|
@ -1,20 +1,29 @@
|
|||
.form_item_block {
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
.delete_button {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 0;
|
||||
}
|
||||
.add_button_form_item {
|
||||
|
||||
.form-item-add {
|
||||
margin-top: 15px;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
.add_button_form_item .add_button {
|
||||
padding: 0;
|
||||
|
||||
&__add-button {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import KFIcon from '@/components/KFIcon';
|
||||
import { getParamComponent, getParamRules } from '@/pages/Experiment/components/AddExperimentModal';
|
||||
import { type PipelineGlobalParam } from '@/types';
|
||||
import { to } from '@/utils/promise';
|
||||
import { modalConfirm } from '@/utils/ui';
|
||||
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { Button, Drawer, Form, Input, Radio, Tooltip } from 'antd';
|
||||
import { NamePath } from 'antd/es/form/interface';
|
||||
import { forwardRef, useImperativeHandle } from 'react';
|
||||
|
@ -55,14 +56,14 @@ const GlobalParamsDrawer = forwardRef(
|
|||
getContainer={false}
|
||||
onClose={onClose}
|
||||
open={open}
|
||||
width={420}
|
||||
width={520}
|
||||
>
|
||||
<Form
|
||||
name="global_params_form"
|
||||
autoComplete="off"
|
||||
form={form}
|
||||
labelCol={{ span: 7 }}
|
||||
wrapperCol={{ span: 17 }}
|
||||
labelCol={{ span: 5 }}
|
||||
wrapperCol={{ span: 19 }}
|
||||
initialValues={{ global_param: globalParam }}
|
||||
labelAlign="left"
|
||||
>
|
||||
|
@ -70,7 +71,7 @@ const GlobalParamsDrawer = forwardRef(
|
|||
{(fields, { add, remove }) => (
|
||||
<>
|
||||
{fields.map(({ key, name, ...restField }) => (
|
||||
<div key={key} className={styles.form_item_block}>
|
||||
<div key={key} className={styles['form-item']}>
|
||||
<Form.Item
|
||||
{...restField}
|
||||
name={[name, 'param_name']}
|
||||
|
@ -140,17 +141,17 @@ const GlobalParamsDrawer = forwardRef(
|
|||
</Form.Item>
|
||||
<Tooltip title="删除参数">
|
||||
<Button
|
||||
className={styles.delete_button}
|
||||
className={styles['form-item__delete-button']}
|
||||
type="link"
|
||||
onClick={() => removeParameter(name, remove)}
|
||||
icon={<DeleteOutlined />}
|
||||
icon={<KFIcon type="icon-shanchu" />}
|
||||
></Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
))}
|
||||
<Form.Item className={styles.add_button_form_item}>
|
||||
<Form.Item className={styles['form-item-add']}>
|
||||
<Button
|
||||
className={styles.add_button}
|
||||
className={styles['form-item-add__add-button']}
|
||||
type="link"
|
||||
onClick={() => add()}
|
||||
icon={<PlusOutlined />}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Button, Dropdown, type MenuProps } from 'antd';
|
||||
import { Dropdown, type MenuProps } from 'antd';
|
||||
import { useEffect } from 'react';
|
||||
import styles from './index.less';
|
||||
|
||||
|
@ -22,20 +22,18 @@ function PropsLabel({ title, menuItems, onClick }: PropsLabelProps) {
|
|||
|
||||
return (
|
||||
<div className={styles['props-label']}>
|
||||
<span>{title}</span>
|
||||
<div>{title}</div>
|
||||
<Dropdown
|
||||
menu={{
|
||||
items: menuItems,
|
||||
onClick: handleItemClick,
|
||||
triggerSubMenuAction: 'click',
|
||||
triggerSubMenuAction: 'hover',
|
||||
}}
|
||||
trigger={['click']}
|
||||
placement="topRight"
|
||||
arrow
|
||||
>
|
||||
<Button size="small" type="link">
|
||||
参数
|
||||
</Button>
|
||||
<a onClick={(e) => e.preventDefault()}>参数</a>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
import datasetImg from '@/assets/img/modal-select-dataset.png';
|
||||
import mirrorImg from '@/assets/img/modal-select-mirror.png';
|
||||
import modelImg from '@/assets/img/modal-select-model.png';
|
||||
import { CommonTabKeys, MirrorVersionStatus } from '@/enums';
|
||||
import {
|
||||
getDatasetList,
|
||||
getDatasetVersionIdList,
|
||||
getDatasetVersionsById,
|
||||
getModelList,
|
||||
getModelVersionIdList,
|
||||
getModelVersionsById,
|
||||
} from '@/services/dataset/index.js';
|
||||
import { getMirrorListReq, getMirrorVersionListReq } from '@/services/mirror';
|
||||
import type { TabsProps } from 'antd';
|
||||
|
||||
export enum ResourceSelectorType {
|
||||
Model = 'Model', // 模型
|
||||
Dataset = 'Dataset', // 数据集
|
||||
Mirror = 'Mirror', //镜像
|
||||
}
|
||||
|
||||
export type MirrorVersion = {
|
||||
id: number; // 镜像版本id
|
||||
status: MirrorVersionStatus; // 镜像版本状态
|
||||
tag_name: string; // 镜像版本
|
||||
url: string; // 镜像版本路径
|
||||
};
|
||||
|
||||
export type SelectorTypeInfo = {
|
||||
getList: (params: any) => Promise<any>;
|
||||
getVersions: (params: any) => Promise<any>;
|
||||
getFiles: (params: any) => Promise<any>;
|
||||
handleVersionResponse: (res: any) => any[];
|
||||
modalIcon: string;
|
||||
buttonIcon: string;
|
||||
name: string;
|
||||
litReqParamKey: 'available_range' | 'image_type';
|
||||
fileReqParamKey: 'models_id' | 'dataset_id';
|
||||
tabItems: TabsProps['items'];
|
||||
};
|
||||
|
||||
// 获取镜像列表,为了兼容数据集和模型
|
||||
const getMirrorFilesReq = ({ id, version }: { id: number; version: string }): Promise<any> => {
|
||||
const index = version.indexOf('-');
|
||||
const url = version.slice(index + 1);
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
content: [
|
||||
{
|
||||
id: `${id}-${version}`,
|
||||
file_name: `${url}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const selectorTypeConfig: Record<ResourceSelectorType, SelectorTypeInfo> = {
|
||||
[ResourceSelectorType.Model]: {
|
||||
getList: getModelList,
|
||||
getVersions: getModelVersionsById,
|
||||
getFiles: getModelVersionIdList,
|
||||
handleVersionResponse: (res) => res.data || [],
|
||||
name: '模型',
|
||||
modalIcon: modelImg,
|
||||
buttonIcon: 'icon-xuanzemoxing',
|
||||
litReqParamKey: 'available_range',
|
||||
fileReqParamKey: 'models_id',
|
||||
tabItems: [
|
||||
{
|
||||
key: CommonTabKeys.Private,
|
||||
label: '我的模型',
|
||||
},
|
||||
{
|
||||
key: CommonTabKeys.Public,
|
||||
label: '公开模型',
|
||||
},
|
||||
],
|
||||
},
|
||||
[ResourceSelectorType.Dataset]: {
|
||||
getList: getDatasetList,
|
||||
getVersions: getDatasetVersionsById,
|
||||
getFiles: getDatasetVersionIdList,
|
||||
handleVersionResponse: (res) => res.data || [],
|
||||
name: '数据集',
|
||||
modalIcon: datasetImg,
|
||||
buttonIcon: 'icon-xuanzeshujuji',
|
||||
litReqParamKey: 'available_range',
|
||||
fileReqParamKey: 'dataset_id',
|
||||
tabItems: [
|
||||
{
|
||||
key: CommonTabKeys.Private,
|
||||
label: '我的数据集',
|
||||
},
|
||||
{
|
||||
key: CommonTabKeys.Public,
|
||||
label: '公开数据集',
|
||||
},
|
||||
],
|
||||
},
|
||||
[ResourceSelectorType.Mirror]: {
|
||||
getList: getMirrorListReq,
|
||||
getVersions: (id: number) => getMirrorVersionListReq({ image_id: id, page: 0, size: 200 }),
|
||||
getFiles: getMirrorFilesReq,
|
||||
handleVersionResponse: (res) =>
|
||||
res.data?.content?.filter((v: MirrorVersion) => v.status === MirrorVersionStatus.Available) ||
|
||||
[],
|
||||
name: '镜像',
|
||||
modalIcon: mirrorImg,
|
||||
buttonIcon: 'icon-xuanzejingxiang',
|
||||
litReqParamKey: 'image_type',
|
||||
fileReqParamKey: 'dataset_id',
|
||||
tabItems: [
|
||||
{
|
||||
key: CommonTabKeys.Private,
|
||||
label: '我的镜像',
|
||||
},
|
||||
{
|
||||
key: CommonTabKeys.Public,
|
||||
label: '公开镜像',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
|
@ -1,133 +1,22 @@
|
|||
/*
|
||||
* @Author: 赵伟
|
||||
* @Date: 2024-04-11 16:31:18
|
||||
* @Description: 选择数据集和模型
|
||||
* @Description: 选择数据集、模型、镜像
|
||||
*/
|
||||
|
||||
import datasetImg from '@/assets/img/modal-select-dataset.png';
|
||||
import mirrorImg from '@/assets/img/modal-select-mirror.png';
|
||||
import modelImg from '@/assets/img/modal-select-model.png';
|
||||
import KFModal from '@/components/KFModal';
|
||||
import { CommonTabKeys, MirrorVersionStatus } from '@/enums';
|
||||
import {
|
||||
getDatasetList,
|
||||
getDatasetVersionIdList,
|
||||
getDatasetVersionsById,
|
||||
getModelList,
|
||||
getModelVersionIdList,
|
||||
getModelVersionsById,
|
||||
} from '@/services/dataset/index.js';
|
||||
import { getMirrorListReq, getMirrorVersionListReq } from '@/services/mirror';
|
||||
import { CommonTabKeys } from '@/enums';
|
||||
import { to } from '@/utils/promise';
|
||||
import { Icon } from '@umijs/max';
|
||||
import type { GetRef, ModalProps, TabsProps, TreeDataNode, TreeProps } from 'antd';
|
||||
import type { GetRef, ModalProps, TreeDataNode, TreeProps } from 'antd';
|
||||
import { Input, Tabs, Tree } from 'antd';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { MirrorVersion, ResourceSelectorType, selectorTypeConfig } from './config';
|
||||
import styles from './index.less';
|
||||
export { ResourceSelectorType, selectorTypeConfig };
|
||||
|
||||
export enum ResourceSelectorType {
|
||||
Model = 'Model', // 模型
|
||||
Dataset = 'Dataset', // 数据集
|
||||
Mirror = 'Mirror', //镜像
|
||||
}
|
||||
|
||||
type ResourceSelectorTypeKeys = keyof typeof ResourceSelectorType;
|
||||
type ResourceSelectorTypeValues = (typeof ResourceSelectorType)[ResourceSelectorTypeKeys];
|
||||
|
||||
export type SelectorTypeInfo = {
|
||||
getList: (params: any) => Promise<any>;
|
||||
getVersions: (params: any) => Promise<any>;
|
||||
getFiles: (params: any) => Promise<any>;
|
||||
handleVersionResponse: (res: any) => any[];
|
||||
modalIcon: string;
|
||||
name: string;
|
||||
litReqParamKey: 'available_range' | 'image_type';
|
||||
fileReqParamKey: 'models_id' | 'dataset_id';
|
||||
tabItems: TabsProps['items'];
|
||||
};
|
||||
|
||||
// 获取镜像列表,为了兼容之前的结构
|
||||
const getMirrorFilesReq = ({ id, version }: { id: number; version: string }): Promise<any> => {
|
||||
const index = version.indexOf('-');
|
||||
const url = version.slice(index + 1);
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
content: [
|
||||
{
|
||||
id: `${id}-${version}`,
|
||||
file_name: `${url}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const selectorTypeData: Record<ResourceSelectorTypeValues, SelectorTypeInfo> = {
|
||||
[ResourceSelectorType.Model]: {
|
||||
getList: getModelList,
|
||||
getVersions: getModelVersionsById,
|
||||
getFiles: getModelVersionIdList,
|
||||
handleVersionResponse: (res) => res.data || [],
|
||||
name: '模型',
|
||||
modalIcon: modelImg,
|
||||
litReqParamKey: 'available_range',
|
||||
fileReqParamKey: 'models_id',
|
||||
tabItems: [
|
||||
{
|
||||
key: CommonTabKeys.Private,
|
||||
label: '我的模型',
|
||||
},
|
||||
{
|
||||
key: CommonTabKeys.Public,
|
||||
label: '公开模型',
|
||||
},
|
||||
],
|
||||
},
|
||||
[ResourceSelectorType.Dataset]: {
|
||||
getList: getDatasetList,
|
||||
getVersions: getDatasetVersionsById,
|
||||
getFiles: getDatasetVersionIdList,
|
||||
handleVersionResponse: (res) => res.data || [],
|
||||
name: '数据集',
|
||||
modalIcon: datasetImg,
|
||||
litReqParamKey: 'available_range',
|
||||
fileReqParamKey: 'dataset_id',
|
||||
tabItems: [
|
||||
{
|
||||
key: CommonTabKeys.Private,
|
||||
label: '我的数据集',
|
||||
},
|
||||
{
|
||||
key: CommonTabKeys.Public,
|
||||
label: '公开数据集',
|
||||
},
|
||||
],
|
||||
},
|
||||
[ResourceSelectorType.Mirror]: {
|
||||
getList: getMirrorListReq,
|
||||
getVersions: (id: number) => getMirrorVersionListReq({ image_id: id, page: 0, size: 200 }),
|
||||
getFiles: getMirrorFilesReq,
|
||||
handleVersionResponse: (res) =>
|
||||
res.data?.content?.filter((v: MirrorVersion) => v.status === MirrorVersionStatus.Available) ||
|
||||
[],
|
||||
name: '镜像',
|
||||
modalIcon: mirrorImg,
|
||||
litReqParamKey: 'image_type',
|
||||
fileReqParamKey: 'dataset_id',
|
||||
tabItems: [
|
||||
{
|
||||
key: CommonTabKeys.Private,
|
||||
label: '我的镜像',
|
||||
},
|
||||
{
|
||||
key: CommonTabKeys.Public,
|
||||
label: '公开镜像',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
type ResourceSelectorResponse = {
|
||||
// 选择数据集和模型的返回类型
|
||||
export type ResourceSelectorResponse = {
|
||||
id: number; // 数据集或者模型 id
|
||||
name: string; // 数据集或者模型 name
|
||||
version: string; // 数据集或者模型版本
|
||||
|
@ -135,11 +24,11 @@ type ResourceSelectorResponse = {
|
|||
activeTab: CommonTabKeys; // 是我的还是公开的
|
||||
};
|
||||
|
||||
interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> {
|
||||
export interface ResourceSelectorModalProps extends Omit<ModalProps, 'onOk'> {
|
||||
type: ResourceSelectorType; // 模型 | 数据集
|
||||
defaultExpandedKeys: React.Key[];
|
||||
defaultCheckedKeys: React.Key[];
|
||||
defaultActiveTab: CommonTabKeys;
|
||||
defaultExpandedKeys?: React.Key[];
|
||||
defaultCheckedKeys?: React.Key[];
|
||||
defaultActiveTab?: CommonTabKeys;
|
||||
onOk?: (params: ResourceSelectorResponse | string | null) => void;
|
||||
}
|
||||
|
||||
|
@ -148,13 +37,6 @@ type ResourceGroup = {
|
|||
name: string; // 数据集或者模型 id
|
||||
};
|
||||
|
||||
type MirrorVersion = {
|
||||
id: number; // 镜像版本id
|
||||
status: MirrorVersionStatus; // 镜像版本状态
|
||||
tag_name: string; // 镜像版本
|
||||
url: string; // 镜像版本路径
|
||||
};
|
||||
|
||||
type ResourceFile = {
|
||||
id: number; // 文件 id
|
||||
file_name: string; // 文件 name
|
||||
|
@ -261,9 +143,9 @@ function ResourceSelectorModal({
|
|||
const params = {
|
||||
page: 0,
|
||||
size: 200,
|
||||
[selectorTypeData[type].litReqParamKey]: available_range,
|
||||
[selectorTypeConfig[type].litReqParamKey]: available_range,
|
||||
};
|
||||
const getListReq = selectorTypeData[type].getList;
|
||||
const getListReq = selectorTypeConfig[type].getList;
|
||||
const [res] = await to(getListReq(params));
|
||||
if (res) {
|
||||
const list = res.data?.content || [];
|
||||
|
@ -279,10 +161,10 @@ function ResourceSelectorModal({
|
|||
|
||||
// 获取数据集或模型版本列表
|
||||
const getVersions = async (parentId: number) => {
|
||||
const getVersionsReq = selectorTypeData[type].getVersions;
|
||||
const getVersionsReq = selectorTypeConfig[type].getVersions;
|
||||
const [res, error] = await to(getVersionsReq(parentId));
|
||||
if (res) {
|
||||
const list = selectorTypeData[type].handleVersionResponse(res);
|
||||
const list = selectorTypeConfig[type].handleVersionResponse(res);
|
||||
const children = list.map(convertVersionToTreeData(parentId));
|
||||
// 更新 treeData children
|
||||
setOriginTreeData((prev) => prev.map(updateChildren(parentId, children)));
|
||||
|
@ -301,8 +183,8 @@ function ResourceSelectorModal({
|
|||
|
||||
// 获取版本下的文件
|
||||
const getFiles = async (id: number, version: string) => {
|
||||
const getFilesReq = selectorTypeData[type].getFiles;
|
||||
const paramsKey = selectorTypeData[type].fileReqParamKey;
|
||||
const getFilesReq = selectorTypeConfig[type].getFiles;
|
||||
const paramsKey = selectorTypeConfig[type].fileReqParamKey;
|
||||
const params = { version: version, [paramsKey]: id };
|
||||
const [res] = await to(getFilesReq(params));
|
||||
if (res) {
|
||||
|
@ -404,14 +286,14 @@ function ResourceSelectorModal({
|
|||
}
|
||||
};
|
||||
|
||||
const title = `选择${selectorTypeData[type].name}`;
|
||||
const palceholder = `请输入${selectorTypeData[type].name}名称`;
|
||||
const title = `选择${selectorTypeConfig[type].name}`;
|
||||
const palceholder = `请输入${selectorTypeConfig[type].name}名称`;
|
||||
const fileTitle =
|
||||
type === ResourceSelectorType.Mirror
|
||||
? '已选镜像'
|
||||
: `已选${selectorTypeData[type].name}文件(${files.length})`;
|
||||
const tabItems = selectorTypeData[type].tabItems;
|
||||
const titleImg = selectorTypeData[type].modalIcon;
|
||||
: `已选${selectorTypeConfig[type].name}文件(${files.length})`;
|
||||
const tabItems = selectorTypeConfig[type].tabItems;
|
||||
const titleImg = selectorTypeConfig[type].modalIcon;
|
||||
|
||||
return (
|
||||
<KFModal {...rest} title={title} image={titleImg} onOk={handleOk} width={920} destroyOnClose>
|
||||
|
|
|
@ -1,80 +0,0 @@
|
|||
#graph {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.editPipelineProps {
|
||||
:global {
|
||||
label {
|
||||
width: 100%;
|
||||
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.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;
|
||||
justify-content: end;
|
||||
width: 100%;
|
||||
height: 45px;
|
||||
padding: 0 30px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09);
|
||||
}
|
||||
.rightmenu {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 120px;
|
||||
height: 146px;
|
||||
overflow-y: auto;
|
||||
color: #333333;
|
||||
font-size: 12px;
|
||||
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.rightmenuItem {
|
||||
padding: 10px 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.rightmenuItem:hover {
|
||||
color: #ffffff;
|
||||
background-color: rgba(24, 144, 255, 0.3);
|
||||
}
|
||||
|
||||
.ref-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.select-button {
|
||||
display: flex;
|
||||
flex: none;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
width: 100px;
|
||||
margin-left: 10px;
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
|
@ -2,14 +2,13 @@ import KFIcon from '@/components/KFIcon';
|
|||
import { useStateRef, useVisible } from '@/hooks';
|
||||
import { getWorkflowById, saveWorkflow } from '@/services/pipeline/index.js';
|
||||
import { to } from '@/utils/promise';
|
||||
import { useEmotionCss } from '@ant-design/use-emotion-css';
|
||||
import G6 from '@antv/g6';
|
||||
import { Button, message } from 'antd';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { s8 } from '../../../utils';
|
||||
import GlobalParamsDrawer from '../components/GlobalParamsDrawer';
|
||||
import Styles from './editPipeline.less';
|
||||
import styles from './index.less';
|
||||
import ModelMenus from './modelMenus';
|
||||
import Props from './props';
|
||||
import { findAllParentNodes, findFirstDuplicate } from './utils';
|
||||
|
@ -20,42 +19,6 @@ const EditPipeline = () => {
|
|||
const navgite = useNavigate();
|
||||
let contextMenu = {};
|
||||
const locationParams = useParams(); //新版本获取路由参数接口
|
||||
|
||||
const pipelineContainer = useEmotionCss(() => {
|
||||
return {
|
||||
display: 'flex',
|
||||
backgroundColor: '#fff',
|
||||
height: '98vh',
|
||||
position: 'relative',
|
||||
};
|
||||
});
|
||||
const rightmenu = useEmotionCss(() => {
|
||||
return {
|
||||
position: 'absolute',
|
||||
width: '120px',
|
||||
height: '146px',
|
||||
left: '0px',
|
||||
top: '0px',
|
||||
|
||||
color: '#333333',
|
||||
overflowY: 'auto',
|
||||
};
|
||||
});
|
||||
const rightmenuItem = useEmotionCss(() => {
|
||||
return {
|
||||
padding: '10px 20px',
|
||||
cursor: 'pointer',
|
||||
fontSize: '12px',
|
||||
};
|
||||
});
|
||||
const graphStyle = useEmotionCss(() => {
|
||||
return {
|
||||
width: '100%',
|
||||
backgroundSize: '100% 100%',
|
||||
backgroundImage: 'url(/assets/images/pipeline-canvas-back.png)',
|
||||
flex: 1,
|
||||
};
|
||||
});
|
||||
const graphRef = useRef();
|
||||
const paramsDrawerRef = useRef();
|
||||
const propsRef = useRef();
|
||||
|
@ -65,7 +28,7 @@ const EditPipeline = () => {
|
|||
let sourceAnchorIdx, targetAnchorIdx;
|
||||
|
||||
const onDragEnd = (val) => {
|
||||
console.log(val, 'eee');
|
||||
console.log(val);
|
||||
const _x = val.x;
|
||||
const _y = val.y;
|
||||
const point = graph.getPointByClient(_x, _y);
|
||||
|
@ -78,10 +41,8 @@ const EditPipeline = () => {
|
|||
id: val.component_name + '-' + s8(),
|
||||
isCluster: false,
|
||||
};
|
||||
console.log(graph, model);
|
||||
|
||||
console.log('model', model);
|
||||
graph.addItem('node', model, true);
|
||||
console.log(graph);
|
||||
};
|
||||
const formChange = (val) => {
|
||||
if (graph) {
|
||||
|
@ -110,7 +71,6 @@ const EditPipeline = () => {
|
|||
}
|
||||
|
||||
const [propsRes, propsError] = await to(propsRef.current.getFieldsValue());
|
||||
console.log(await to(propsRef.current.getFieldsValue()));
|
||||
if (propsError) {
|
||||
message.error('基本信息必填项需配置');
|
||||
return;
|
||||
|
@ -147,7 +107,6 @@ const EditPipeline = () => {
|
|||
}
|
||||
};
|
||||
const getGraphData = (data) => {
|
||||
console.log('graph', graph);
|
||||
if (graph) {
|
||||
console.log(data);
|
||||
graph.data(data);
|
||||
|
@ -230,6 +189,7 @@ const EditPipeline = () => {
|
|||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
for (const key in edgeMap) {
|
||||
const arcEdges = edgeMap[key];
|
||||
const { length } = arcEdges;
|
||||
|
@ -472,7 +432,7 @@ const EditPipeline = () => {
|
|||
height: graphRef.current.clientHeight || '100%',
|
||||
animate: false,
|
||||
groupByTypes: false,
|
||||
fitView: true,
|
||||
fitView: false,
|
||||
plugins: [contextMenu],
|
||||
enabledStack: true,
|
||||
modes: {
|
||||
|
@ -730,10 +690,10 @@ const EditPipeline = () => {
|
|||
};
|
||||
};
|
||||
return (
|
||||
<div className={pipelineContainer}>
|
||||
<div className={styles['pipeline-container']}>
|
||||
<ModelMenus onParDragEnd={onDragEnd}></ModelMenus>
|
||||
<div className={Styles.centerContainer}>
|
||||
<div className={Styles.buttonList}>
|
||||
<div className={styles['pipeline-container__workflow']}>
|
||||
<div className={styles['pipeline-container__workflow__top']}>
|
||||
<Button
|
||||
type="default"
|
||||
icon={<KFIcon type="icon-quanjucanshu" />}
|
||||
|
@ -768,7 +728,7 @@ const EditPipeline = () => {
|
|||
保存并返回
|
||||
</Button>
|
||||
</div>
|
||||
<div className={graphStyle} ref={graphRef} id={Styles.graphStyle}></div>
|
||||
<div className={styles['pipeline-container__workflow__graph']} ref={graphRef}></div>
|
||||
</div>
|
||||
<Props ref={propsRef} onParentChange={formChange}></Props>
|
||||
<GlobalParamsDrawer
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
.pipeline-container {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
|
||||
&__workflow {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
|
||||
&__top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
width: 100%;
|
||||
height: 45px;
|
||||
padding: 0 30px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09);
|
||||
}
|
||||
|
||||
&__graph {
|
||||
width: 100%;
|
||||
height: calc(100% - 45px);
|
||||
background-color: @background-color;
|
||||
background-image: url(/assets/images/pipeline-canvas-back.png);
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,7 +24,7 @@ const ModelMenus = ({ onParDragEnd }) => {
|
|||
};
|
||||
const { Panel } = Collapse;
|
||||
return (
|
||||
<div style={{ width: '250px', height: '99%' }} className={Styles.collapse}>
|
||||
<div className={Styles.collapse}>
|
||||
<div className={Styles.modelMenusTitle}>组件库</div>
|
||||
{modelMenusList && modelMenusList.length > 0 ? (
|
||||
<Collapse
|
||||
|
|
|
@ -1,4 +1,34 @@
|
|||
.collapseList {
|
||||
.collapse {
|
||||
width: 250px;
|
||||
height: 100%;
|
||||
|
||||
:global {
|
||||
.ant-collapse {
|
||||
height: calc(100% - 60px);
|
||||
overflow-y: auto;
|
||||
background-color: #fff;
|
||||
border-color: transparent !important;
|
||||
}
|
||||
.ant-collapse > .ant-collapse-item > .ant-collapse-header {
|
||||
margin-bottom: 5px;
|
||||
padding: 20px 16px 15px 16px;
|
||||
background-color: #fff;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.ant-collapse > .ant-collapse-item {
|
||||
margin: 0 10px;
|
||||
border-bottom: 0.5px dashed rgba(20, 49, 179, 0.12);
|
||||
border-radius: 0px;
|
||||
}
|
||||
.ant-collapse .ant-collapse-content {
|
||||
padding-bottom: 15px;
|
||||
border-top: 1px solid transparent;
|
||||
}
|
||||
.ant-collapse .ant-collapse-content > .ant-collapse-content-box {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.collapseItem {
|
||||
display: flex;
|
||||
|
@ -11,40 +41,12 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
.collapseItem:hover {
|
||||
color: #1664ff;
|
||||
background: rgba(22, 100, 255, 0.08);
|
||||
color:#1664ff;
|
||||
}
|
||||
.collapse {
|
||||
:global {
|
||||
.ant-collapse {
|
||||
background-color: #fff;
|
||||
border-color: transparent !important;
|
||||
}
|
||||
.ant-collapse > .ant-collapse-item > .ant-collapse-header {
|
||||
margin-bottom: 5px;
|
||||
background-color: #fff;
|
||||
border-color: transparent;
|
||||
padding: 20px 16px 15px 16px;
|
||||
}
|
||||
|
||||
.ant-collapse > .ant-collapse-item {
|
||||
margin: 0 10px;
|
||||
border-bottom:0.5px dashed rgba(20, 49, 179, 0.12);
|
||||
border-radius: 0px;
|
||||
}
|
||||
.ant-collapse .ant-collapse-content {
|
||||
padding-bottom: 15px;
|
||||
border-top: 1px solid transparent;
|
||||
}
|
||||
.ant-collapse .ant-collapse-content > .ant-collapse-content-box {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.modelMenusTitle{
|
||||
padding: 12px 25px;
|
||||
.modelMenusTitle {
|
||||
margin-bottom: 10px;
|
||||
color:#111111;
|
||||
font-size:16px;
|
||||
font-family: 'Alibaba';
|
||||
padding: 12px 25px;
|
||||
color: #111111;
|
||||
font-size: 16px;
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
import KFIcon from '@/components/KFIcon';
|
||||
import ParameterInput from '@/components/ParameterInput';
|
||||
import SubAreaTitle from '@/components/SubAreaTitle';
|
||||
import { getComputingResourceReq } from '@/services/pipeline';
|
||||
import { openAntdModal } from '@/utils/modal';
|
||||
|
@ -8,8 +9,8 @@ import { pick } from 'lodash';
|
|||
import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
||||
import PropsLabel from '../components/PropsLabel';
|
||||
import ResourceSelectorModal, { ResourceSelectorType } from '../components/ResourceSelectorModal';
|
||||
import styles from './editPipeline.less';
|
||||
import { createMenuItems } from './utils';
|
||||
import styles from './props.less';
|
||||
import { canInput, createMenuItems } from './utils';
|
||||
const { TextArea } = Input;
|
||||
|
||||
const Props = forwardRef(({ onParentChange }, ref) => {
|
||||
|
@ -40,7 +41,7 @@ const Props = forwardRef(({ onParentChange }, ref) => {
|
|||
|
||||
const afterOpenChange = () => {
|
||||
if (!open) {
|
||||
console.log('zzzz', form.getFieldsValue());
|
||||
console.log('zzzzz', form.getFieldsValue());
|
||||
const control_strategy = form.getFieldValue('control_strategy');
|
||||
const in_parameters = form.getFieldValue('in_parameters');
|
||||
const out_parameters = form.getFieldValue('out_parameters');
|
||||
|
@ -77,6 +78,7 @@ const Props = forwardRef(({ onParentChange }, ref) => {
|
|||
out_parameters: JSON.parse(model.out_parameters),
|
||||
control_strategy: JSON.parse(model.control_strategy),
|
||||
};
|
||||
console.log('model', nodeData);
|
||||
setStagingItem({
|
||||
...nodeData,
|
||||
});
|
||||
|
@ -95,11 +97,11 @@ const Props = forwardRef(({ onParentChange }, ref) => {
|
|||
}
|
||||
},
|
||||
propClose: () => {
|
||||
close();
|
||||
onClose();
|
||||
},
|
||||
}));
|
||||
|
||||
// 选择数据集、模型
|
||||
// 选择数据集、模型、镜像
|
||||
const selectResource = (name, item) => {
|
||||
let type;
|
||||
let resource;
|
||||
|
@ -128,19 +130,20 @@ const Props = forwardRef(({ onParentChange }, ref) => {
|
|||
} else {
|
||||
const jsonObj = pick(res, ['id', 'version', 'path']);
|
||||
const value = JSON.stringify(jsonObj);
|
||||
form.setFieldValue(name, { ...item, value });
|
||||
}
|
||||
const showValue = `${res.name}:${res.version}`;
|
||||
form.setFieldValue(name, { ...item, value, showValue, fromSelect: true });
|
||||
|
||||
if (type === ResourceSelectorType.Dataset) {
|
||||
setSelectedDataset(res);
|
||||
} else if (type === ResourceSelectorType.Model) {
|
||||
setSelectedModel(res);
|
||||
if (type === ResourceSelectorType.Dataset) {
|
||||
setSelectedDataset(res);
|
||||
} else if (type === ResourceSelectorType.Model) {
|
||||
setSelectedModel(res);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (type === ResourceSelectorType.Dataset) {
|
||||
setSelectedDataset(null);
|
||||
setSelectedDataset(undefined);
|
||||
} else if (type === ResourceSelectorType.Model) {
|
||||
setSelectedModel(null);
|
||||
setSelectedModel(undefined);
|
||||
}
|
||||
form.setFieldValue(name, '');
|
||||
}
|
||||
|
@ -188,275 +191,276 @@ const Props = forwardRef(({ onParentChange }, ref) => {
|
|||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Drawer
|
||||
title="编辑任务"
|
||||
placement="right"
|
||||
rootStyle={{ marginTop: '45px' }}
|
||||
getContainer={false}
|
||||
closeIcon={false}
|
||||
onClose={onClose}
|
||||
afterOpenChange={afterOpenChange}
|
||||
open={open}
|
||||
width={520}
|
||||
className={styles.editPipelineProps}
|
||||
<Drawer
|
||||
title="编辑任务"
|
||||
placement="right"
|
||||
rootStyle={{ marginTop: '45px' }}
|
||||
getContainer={false}
|
||||
closeIcon={false}
|
||||
onClose={onClose}
|
||||
afterOpenChange={afterOpenChange}
|
||||
open={open}
|
||||
width={520}
|
||||
className={styles['pipeline-drawer']}
|
||||
>
|
||||
<Form
|
||||
name="form"
|
||||
form={form}
|
||||
layout="vertical"
|
||||
labelCol={{
|
||||
span: 24,
|
||||
}}
|
||||
wrapperCol={{
|
||||
span: 24,
|
||||
}}
|
||||
style={{
|
||||
maxWidth: 600,
|
||||
}}
|
||||
autoComplete="off"
|
||||
>
|
||||
<Form
|
||||
name="form"
|
||||
form={form}
|
||||
layout="vertical"
|
||||
labelCol={{
|
||||
span: 24,
|
||||
}}
|
||||
wrapperCol={{
|
||||
span: 24,
|
||||
}}
|
||||
style={{
|
||||
maxWidth: 600,
|
||||
}}
|
||||
autoComplete="off"
|
||||
<div className={styles['pipeline-drawer__title']}>
|
||||
<SubAreaTitle image="/assets/images/static-message.png" title="基本信息"></SubAreaTitle>
|
||||
</div>
|
||||
<Form.Item
|
||||
label="任务名称"
|
||||
name="label"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入任务名称',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<div className={styles.editPipelinePropsContent}>
|
||||
<SubAreaTitle image="/assets/images/static-message.png" title="基本信息"></SubAreaTitle>
|
||||
<Input placeholder="请输入任务名称" allowClear />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="任务ID"
|
||||
name="id"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入任务id',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input disabled />
|
||||
</Form.Item>
|
||||
<div className={styles['pipeline-drawer__title']}>
|
||||
<SubAreaTitle image="/assets/images/duty-message.png" title="任务信息"></SubAreaTitle>
|
||||
</div>
|
||||
<Form.Item label="镜像" required>
|
||||
<div className={styles['pipeline-drawer__ref-row']}>
|
||||
<Form.Item name="image" noStyle rules={[{ required: true, message: '请输入镜像' }]}>
|
||||
<Input placeholder="请输入或选择镜像" allowClear />
|
||||
</Form.Item>
|
||||
<Form.Item noStyle>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
icon={getSelectBtnIcon({ item_type: 'image' })}
|
||||
onClick={() => selectResource('image', { item_type: 'image' })}
|
||||
className={styles['pipeline-drawer__ref-row__select-button']}
|
||||
>
|
||||
选择镜像
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Form.Item
|
||||
label="任务名称"
|
||||
name="label"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入任务名称',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input placeholder="请输入任务名称" allowClear />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="任务ID"
|
||||
name="id"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入任务id',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input disabled />
|
||||
</Form.Item>
|
||||
<div className={styles.editPipelinePropsContent}>
|
||||
<SubAreaTitle image="/assets/images/duty-message.png" title="任务信息"></SubAreaTitle>
|
||||
</div>
|
||||
<Form.Item label="镜像" required>
|
||||
<div className={styles['ref-row']}>
|
||||
<Form.Item name="image" noStyle rules={[{ required: true, message: '请输入镜像' }]}>
|
||||
<Input placeholder="请输入或选择镜像" allowClear />
|
||||
</Form.Item>
|
||||
<Form.Item noStyle>
|
||||
<Button
|
||||
type="link"
|
||||
icon={getSelectBtnIcon({ item_type: 'image' })}
|
||||
onClick={() => selectResource('image', { item_type: 'image' })}
|
||||
className={styles['select-button']}
|
||||
>
|
||||
选择镜像
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="working_directory"
|
||||
label={
|
||||
<PropsLabel
|
||||
menuItems={menuItems}
|
||||
title="工作目录"
|
||||
onClick={(value) => {
|
||||
handleParameterClick('working_directory', value);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Input placeholder="请输入工作目录" allowClear />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="command"
|
||||
label={
|
||||
<PropsLabel
|
||||
menuItems={menuItems}
|
||||
title="启动命令"
|
||||
onClick={(value) => {
|
||||
handleParameterClick('command', value);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<TextArea placeholder="请输入启动命令" allowClear />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="资源规格"
|
||||
name="resources_standard"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择资源规格',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Select
|
||||
showSearch
|
||||
placeholder="请选择资源规格"
|
||||
filterOption={filterResourceStandard}
|
||||
options={resourceStandardList}
|
||||
fieldNames={{
|
||||
label: 'description',
|
||||
value: 'standard',
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="working_directory"
|
||||
label={
|
||||
<PropsLabel
|
||||
menuItems={menuItems}
|
||||
title="工作目录"
|
||||
onClick={(value) => {
|
||||
handleParameterClick('working_directory', value);
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
}
|
||||
>
|
||||
<Input placeholder="请输入工作目录" allowClear />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="command"
|
||||
label={
|
||||
<PropsLabel
|
||||
menuItems={menuItems}
|
||||
title="启动命令"
|
||||
onClick={(value) => {
|
||||
handleParameterClick('command', value);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<TextArea placeholder="请输入启动命令" allowClear />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="资源规格"
|
||||
name="resources_standard"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择资源规格',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Select
|
||||
showSearch
|
||||
placeholder="请选择资源规格"
|
||||
filterOption={filterResourceStandard}
|
||||
options={resourceStandardList}
|
||||
fieldNames={{
|
||||
label: 'description',
|
||||
value: 'standard',
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="mount_path"
|
||||
label={
|
||||
<PropsLabel
|
||||
menuItems={menuItems}
|
||||
title="挂载路径"
|
||||
onClick={(value) => {
|
||||
handleParameterClick('mount_path', value);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Input placeholder="请输入挂载路径" allowClear />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="env_variables"
|
||||
label={
|
||||
<PropsLabel
|
||||
menuItems={menuItems}
|
||||
title="环境变量"
|
||||
onClick={(value) => {
|
||||
handleParameterClick('env_variables', value);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<TextArea placeholder="请输入环境变量" allowClear />
|
||||
</Form.Item>
|
||||
{controlStrategyList.map((item) => (
|
||||
<Form.Item
|
||||
name="mount_path"
|
||||
key={item.key}
|
||||
name={['control_strategy', item.key]}
|
||||
label={
|
||||
<PropsLabel
|
||||
menuItems={menuItems}
|
||||
title="挂载路径"
|
||||
title={item.value.label}
|
||||
onClick={(value) => {
|
||||
handleParameterClick('mount_path', value);
|
||||
handleParameterClick(['control_strategy', item.key], {
|
||||
...item.value,
|
||||
value,
|
||||
fromSelect: true,
|
||||
showValue: value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
}
|
||||
// getValueProps={(e) => {
|
||||
// return { value: e.value };
|
||||
// }}
|
||||
// getValueFromEvent={(e) => {
|
||||
// return {
|
||||
// ...item.value,
|
||||
// value: e.target.value,
|
||||
// };
|
||||
// }}
|
||||
>
|
||||
<Input placeholder="请输入挂载路径" allowClear />
|
||||
<ParameterInput placeholder={item.value.placeholder} allowClear></ParameterInput>
|
||||
</Form.Item>
|
||||
))}
|
||||
<div className={styles['pipeline-drawer__title']}>
|
||||
<SubAreaTitle image="/assets/images/duty-message.png" title="输入参数"></SubAreaTitle>
|
||||
</div>
|
||||
{inParametersList.map((item) => (
|
||||
<Form.Item
|
||||
name="env_variables"
|
||||
key={item.key}
|
||||
label={
|
||||
<PropsLabel
|
||||
menuItems={menuItems}
|
||||
title="环境变量"
|
||||
title={item.value.label + '(' + item.key + ')'}
|
||||
onClick={(value) => {
|
||||
handleParameterClick('env_variables', value);
|
||||
handleParameterClick(['in_parameters', item.key], {
|
||||
...item.value,
|
||||
value,
|
||||
fromSelect: true,
|
||||
showValue: value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
}
|
||||
required={item.value.require ? true : false}
|
||||
>
|
||||
<TextArea placeholder="请输入环境变量" allowClear />
|
||||
</Form.Item>
|
||||
{controlStrategyList.map((item) => (
|
||||
<Form.Item
|
||||
key={item.key}
|
||||
name={['control_strategy', item.key]}
|
||||
label={
|
||||
<PropsLabel
|
||||
menuItems={menuItems}
|
||||
title={item.value.label}
|
||||
onClick={(value) => {
|
||||
handleParameterClick(['control_strategy', item.key], {
|
||||
...item.value,
|
||||
value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
}
|
||||
getValueProps={(e) => {
|
||||
return { value: e.value };
|
||||
}}
|
||||
getValueFromEvent={(e) => {
|
||||
return {
|
||||
...item.value,
|
||||
value: e.target.value,
|
||||
};
|
||||
}}
|
||||
>
|
||||
<Input placeholder={item.value.label} allowClear />
|
||||
</Form.Item>
|
||||
))}
|
||||
<div className={styles.editPipelinePropsContent}>
|
||||
<SubAreaTitle image="/assets/images/duty-message.png" title="输入参数"></SubAreaTitle>
|
||||
</div>
|
||||
{inParametersList.map((item) => (
|
||||
<Form.Item
|
||||
key={item.key}
|
||||
label={
|
||||
<PropsLabel
|
||||
menuItems={menuItems}
|
||||
title={item.value.label + '(' + item.key + ')'}
|
||||
onClick={(value) => {
|
||||
handleParameterClick(['in_parameters', item.key], {
|
||||
...item.value,
|
||||
value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
}
|
||||
required={item.value.require ? true : false}
|
||||
>
|
||||
<div className={styles['ref-row']}>
|
||||
<Form.Item
|
||||
name={['in_parameters', item.key]}
|
||||
noStyle
|
||||
rules={[{ required: item.value.require ? true : false }]}
|
||||
getValueProps={(e) => {
|
||||
return { value: e.value };
|
||||
}}
|
||||
getValueFromEvent={(e) => {
|
||||
return {
|
||||
...item.value,
|
||||
value: e.target.value,
|
||||
};
|
||||
}}
|
||||
>
|
||||
<Input placeholder={item.value.label} allowClear />
|
||||
<div className={styles['pipeline-drawer__ref-row']}>
|
||||
<Form.Item
|
||||
name={['in_parameters', item.key]}
|
||||
noStyle
|
||||
rules={[{ required: item.value.require ? true : false }]}
|
||||
>
|
||||
<ParameterInput
|
||||
placeholder={item.value.placeholder}
|
||||
canInput={canInput(item.value)}
|
||||
allowClear
|
||||
></ParameterInput>
|
||||
</Form.Item>
|
||||
{item.value.type === 'ref' && (
|
||||
<Form.Item noStyle>
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
icon={getSelectBtnIcon(item.value)}
|
||||
onClick={() => selectResource(['in_parameters', item.key], item.value)}
|
||||
className={styles['pipeline-drawer__ref-row__select-button']}
|
||||
>
|
||||
{item.value.label}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
{item.value.type === 'ref' && (
|
||||
<Form.Item noStyle>
|
||||
<Button
|
||||
type="link"
|
||||
icon={getSelectBtnIcon(item.value)}
|
||||
onClick={() => selectResource(['in_parameters', item.key], item.value)}
|
||||
className={styles['select-button']}
|
||||
>
|
||||
{item.value.label}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
)}
|
||||
</div>
|
||||
</Form.Item>
|
||||
))}
|
||||
<div className={styles.editPipelinePropsContent}>
|
||||
<SubAreaTitle image="/assets/images/duty-message.png" title="输出参数"></SubAreaTitle>
|
||||
</div>
|
||||
{outParametersList.map((item) => (
|
||||
<Form.Item
|
||||
key={item.key}
|
||||
name={['out_parameters', item.key]}
|
||||
label={
|
||||
<PropsLabel
|
||||
menuItems={menuItems}
|
||||
title={item.value.label + '(' + item.key + ')'}
|
||||
onClick={(value) => {
|
||||
handleParameterClick(['out_parameters', item.key], {
|
||||
...item.value,
|
||||
value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
}
|
||||
rules={[{ required: item.value.require ? true : false }]}
|
||||
getValueProps={(e) => {
|
||||
return { value: e.value };
|
||||
}}
|
||||
getValueFromEvent={(e) => {
|
||||
return {
|
||||
...item.value,
|
||||
value: e.target.value,
|
||||
};
|
||||
}}
|
||||
>
|
||||
<Input placeholder={item.value.label} allowClear />
|
||||
</Form.Item>
|
||||
))}
|
||||
</Form>
|
||||
</Drawer>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Form.Item>
|
||||
))}
|
||||
<div className={styles['pipeline-drawer__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={
|
||||
<PropsLabel
|
||||
menuItems={menuItems}
|
||||
title={item.value.label + '(' + item.key + ')'}
|
||||
onClick={(value) => {
|
||||
handleParameterClick(['out_parameters', item.key], {
|
||||
...item.value,
|
||||
value,
|
||||
fromSelect: true,
|
||||
showValue: value,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
}
|
||||
rules={[{ required: item.value.require ? true : false }]}
|
||||
// getValueProps={(e) => {
|
||||
// return { value: e.value };
|
||||
// }}
|
||||
// getValueFromEvent={(e) => {
|
||||
// return {
|
||||
// ...item.value,
|
||||
// value: e.target.value,
|
||||
// };
|
||||
// }}
|
||||
>
|
||||
<ParameterInput placeholder={item.value.placeholder} allowClear></ParameterInput>
|
||||
</Form.Item>
|
||||
))}
|
||||
</Form>
|
||||
</Drawer>
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
.pipeline-drawer {
|
||||
:global {
|
||||
label {
|
||||
width: 100%;
|
||||
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 43px;
|
||||
margin-bottom: 20px;
|
||||
padding: 0 24px;
|
||||
color: @text-color;
|
||||
font-size: @font-size;
|
||||
background: #f8fbff;
|
||||
}
|
||||
|
||||
&__ref-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&__select-button {
|
||||
display: flex;
|
||||
flex: none;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
margin-left: 10px;
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
import { PipelineGlobalParam } from '@/types';
|
||||
import { PipelineGlobalParam, PipelineNodeModelParameter } from '@/types';
|
||||
import { parseJsonText } from '@/utils';
|
||||
import { Graph, INode } from '@antv/g6';
|
||||
import { type MenuProps } from 'antd';
|
||||
|
||||
|
@ -67,13 +68,19 @@ export function createMenuItems(
|
|||
];
|
||||
}
|
||||
|
||||
function parseJsonText(text?: string | null): any | null {
|
||||
if (!text) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch (error) {
|
||||
return null;
|
||||
export function getInParameterComponent(
|
||||
parameter: PipelineNodeModelParameter,
|
||||
): React.ReactNode | null {
|
||||
if (parameter.value) {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function canInput(parameter: PipelineNodeModelParameter) {
|
||||
const { type, item_type } = parameter;
|
||||
return !(
|
||||
type === 'ref' &&
|
||||
(item_type === 'dataset' || item_type === 'model' || item_type === 'image')
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import CommonTableCell from '@/components/CommonTableCell';
|
||||
import DateTableCell from '@/components/DateTableCell';
|
||||
import KFIcon from '@/components/KFIcon';
|
||||
import KFModal from '@/components/KFModal';
|
||||
|
@ -41,7 +42,7 @@ const Pipeline = () => {
|
|||
};
|
||||
const routeToEdit = (e, record) => {
|
||||
e.stopPropagation();
|
||||
navgite({ pathname: `/pipeline/pytorchtext/${record.id}/${record.name}` });
|
||||
navgite({ pathname: `/pipeline/template/${record.id}/${record.name}` });
|
||||
};
|
||||
const showModal = () => {
|
||||
form.resetFields();
|
||||
|
@ -66,7 +67,7 @@ const Pipeline = () => {
|
|||
addWorkflow(values).then((ret) => {
|
||||
console.log(ret);
|
||||
if (ret.code == 200) {
|
||||
navgite({ pathname: `/pipeline/pytorchtext/${ret.data.id}/${ret.data.name}` });
|
||||
navgite({ pathname: `/pipeline/template/${ret.data.id}/${ret.data.name}` });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -119,9 +120,8 @@ const Pipeline = () => {
|
|||
width: 120,
|
||||
align: 'center',
|
||||
render(text, record, index) {
|
||||
return <span>{(pageOption.current.page - 1) * 10 + index + 1}</span>;
|
||||
return <span>{(pageOption.current.page - 1) * pageOption.current.size + index + 1}</span>;
|
||||
},
|
||||
// render: (text, record, index) => `${((curPage-1)*10)+(index+1)}`,
|
||||
},
|
||||
{
|
||||
title: '流水线名称',
|
||||
|
@ -133,6 +133,8 @@ const Pipeline = () => {
|
|||
title: '流水线描述',
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
render: CommonTableCell(true),
|
||||
ellipsis: { showTitle: false },
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
margin-bottom: 10px;
|
||||
padding-right: 30px;
|
||||
background-image: url(/assets/images/pipeline-back.png);
|
||||
background-repeat: no-repeat;
|
||||
background-position: top left;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ type ExperimentTableProps = {
|
|||
function ExperimentTable({ tableData = [], style }: ExperimentTableProps) {
|
||||
const navgite = useNavigate();
|
||||
const gotoExperiment = (record: ExperimentInstance) => {
|
||||
navgite(`/pipeline/experimentPytorchtext/${record.workflow_id}/${record.id}`);
|
||||
navgite(`/pipeline/experiment/${record.workflow_id}/${record.id}`);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -75,7 +75,7 @@ function QuickStart() {
|
|||
buttonTop={20}
|
||||
x={left + 2 * (192 + space)}
|
||||
y={276}
|
||||
onClick={() => navgite('/pipeline/pipelineText')}
|
||||
onClick={() => navgite('/pipeline/template')}
|
||||
/>
|
||||
<WorkFlow
|
||||
content="开发者可以在这里运行流水线模板,产生实验实例,对比实验训练过程与产生的实验训练数据"
|
||||
|
@ -83,7 +83,7 @@ function QuickStart() {
|
|||
buttonTop={40}
|
||||
x={left + 3 * (192 + space)}
|
||||
y={295}
|
||||
onClick={() => navgite('/pipeline/experimentText')}
|
||||
onClick={() => navgite('/pipeline/experiment')}
|
||||
/>
|
||||
<WorkFlow
|
||||
content="支持异构硬件(CPU/GPU)的模型加载,高吞吐,低延迟;支持大规模复杂模型的一键部署,实时弹性扩缩容;提供完整的运维监控体系。"
|
||||
|
|
|
@ -43,7 +43,7 @@ export function deleteMirrorReq(id: number) {
|
|||
});
|
||||
}
|
||||
|
||||
// 删除镜像
|
||||
// 删除镜像版本
|
||||
export function deleteMirrorVersionReq(id: number) {
|
||||
return request(`/api/mmp/imageVersion/${id}`, {
|
||||
method: 'DELETE',
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* @Author: 赵伟
|
||||
* @Date: 2024-04-16 14:29:44
|
||||
* @Description: 模型部署接口
|
||||
*/
|
||||
import { request } from '@umijs/max';
|
||||
|
||||
// 分页查询模型部署列表
|
||||
export function getModelDeploymentListReq(data: any) {
|
||||
return request(`/api/v1/model/get`, {
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// 查询模型部署详情
|
||||
export function getModelDeploymentInfoReq(id: number) {
|
||||
return request(`/api/mmp/image/${id}`, {
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
// 创建模型部署
|
||||
export function createModelDeploymentReq(data: any) {
|
||||
return request(`/api/v1/model/create`, {
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// 删除模型部署
|
||||
export function deleteModelDeploymentReq(data: any) {
|
||||
return request(`/api/v1/model/delete`, {
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// 重启模型部署
|
||||
export function restartModelDeploymentReq(data: any) {
|
||||
return request(`/api/v1/model/restart`, {
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// 停止模型部署
|
||||
export function stopModelDeploymentReq(data: any) {
|
||||
return request(`/api/v1/model/stop`, {
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// 更新模型部署
|
||||
export function updateModelDeploymentReq(data: any) {
|
||||
return request(`/api/v1/model/update`, {
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
}
|
|
@ -49,10 +49,17 @@ export type PipelineNodeModelParameter = {
|
|||
value: any;
|
||||
require: number;
|
||||
type: string;
|
||||
item_type: string;
|
||||
placeholder?: string;
|
||||
describe?: string;
|
||||
fromSelect?: boolean;
|
||||
showValue?: any;
|
||||
editable: number;
|
||||
};
|
||||
|
||||
// type ChangePropertyType<T, K extends keyof T, NewType> = Omit<T, K> & { [P in K]: NewType }
|
||||
|
||||
// 序列化后的流水线节点
|
||||
export type PipelineNodeModelSerialize = Omit<
|
||||
PipelineNodeModel,
|
||||
'control_strategy' | 'in_parameters' | 'out_parameters'
|
||||
|
@ -61,3 +68,12 @@ export type PipelineNodeModelSerialize = Omit<
|
|||
in_parameters: Record<string, PipelineNodeModelParameter>;
|
||||
out_parameters: Record<string, PipelineNodeModelParameter>;
|
||||
};
|
||||
|
||||
// 资源规格
|
||||
export type ComputingResource = {
|
||||
id: number;
|
||||
computing_resource: string;
|
||||
description: string;
|
||||
standard: string;
|
||||
create_by: string;
|
||||
};
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
* @Date: 2024-03-25 13:52:54
|
||||
* @Description: 工具类
|
||||
*/
|
||||
|
||||
// 生成 8 位随机数
|
||||
export function s8() {
|
||||
return (((1 + Math.random()) * 0x100000000) | 0).toString(16).substring(1);
|
||||
}
|
||||
|
@ -14,3 +16,48 @@ export function getNameByCode(list: any[], code: any) {
|
|||
});
|
||||
return name;
|
||||
}
|
||||
|
||||
// 解析 json 字符串
|
||||
export function parseJsonText(text?: string | null): any | null {
|
||||
if (!text) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Underscore-to-camelCase
|
||||
export function underscoreToCamelCase(obj: Record<string, any>) {
|
||||
const newObj: Record<string, any> = {};
|
||||
for (const key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
const newKey = key.replace(/([-_][a-z])/gi, function ($1) {
|
||||
return $1.toUpperCase().replace('[-_]', '').replace('_', '');
|
||||
});
|
||||
let value = obj[key];
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
value = underscoreToCamelCase(value);
|
||||
}
|
||||
newObj[newKey] = value;
|
||||
}
|
||||
}
|
||||
return newObj;
|
||||
}
|
||||
|
||||
export function camelCaseToUnderscore(obj: Record<string, any>) {
|
||||
const newObj: Record<string, any> = {};
|
||||
for (const key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
const newKey = key.replace(/([A-Z])/g, '_$1').toLowerCase();
|
||||
let value = obj[key];
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
value = camelCaseToUnderscore(value);
|
||||
}
|
||||
newObj[newKey] = value;
|
||||
}
|
||||
}
|
||||
return newObj;
|
||||
}
|
||||
|
|
|
@ -16,7 +16,8 @@ import { createRoot } from 'react-dom/client';
|
|||
* @param modalProps - The modal properties.
|
||||
* @return An object with a destroy method to close the modal.
|
||||
*/
|
||||
export const openAntdModal = <T extends ModalProps>(
|
||||
|
||||
export const openAntdModal = <T extends Omit<ModalProps, 'onOk'>>(
|
||||
modal: (props: T) => React.ReactNode,
|
||||
modalProps: T,
|
||||
) => {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
// 用于新建镜像
|
||||
export const mirrorNameKey = 'mirror-name';
|
||||
// 模型部署
|
||||
export const modelDeploymentInfoKey = 'model-deployment-info';
|
||||
|
||||
export const getSessionStorageItem = (key: string, isObject: boolean = false) => {
|
||||
const jsonStr = sessionStorage.getItem(key);
|
||||
|
@ -22,6 +24,10 @@ export const setSessionStorageItem = (key: string, state?: any, isObject: boolea
|
|||
}
|
||||
};
|
||||
|
||||
export const removeSessionStorageItem = (key: string) => {
|
||||
sessionStorage.removeItem(key);
|
||||
};
|
||||
|
||||
// 获取之后就删除,多用于上一个页面传递数据到下一个页面
|
||||
export const getSessionItemThenRemove = (key: string, isObject: boolean = false) => {
|
||||
const res = getSessionStorageItem(key, isObject);
|
||||
|
|
Loading…
Reference in New Issue