diff --git a/react-ui/config/routes.ts b/react-ui/config/routes.ts index 9c30003d..959bf0aa 100644 --- a/react-ui/config/routes.ts +++ b/react-ui/config/routes.ts @@ -135,6 +135,17 @@ export default [ path: '/dataset/datasetIntro/:id', component: './Dataset/datasetIntro', }, + { + name: '镜像', + path: 'mirror', + routes: [ + { + name: '镜像列表', + path: '', + component: './Mirror/list', + }, + ], + }, { name: '模型管理', path: '/dataset/modelIndex', diff --git a/react-ui/src/assets/img/mirror-tabs-bg.png b/react-ui/src/assets/img/mirror-tabs-bg.png new file mode 100644 index 00000000..9e01b8f3 Binary files /dev/null and b/react-ui/src/assets/img/mirror-tabs-bg.png differ diff --git a/react-ui/src/components/KFModal/index.tsx b/react-ui/src/components/KFModal/index.tsx index ba3233c6..3491ca6b 100644 --- a/react-ui/src/components/KFModal/index.tsx +++ b/react-ui/src/components/KFModal/index.tsx @@ -5,22 +5,31 @@ */ import ModalTitle from '@/components/ModalTitle'; -import { Modal, type ModalProps } from 'antd'; +import { ConfigProvider, Modal, theme, type ModalProps } from 'antd'; import classNames from 'classnames'; +import { useAntdConfig } from 'umi'; import './index.less'; +const { useToken } = theme; + export interface KFModalProps extends ModalProps { image: string; } function KFModal({ title, image, children, className = '', ...rest }: KFModalProps) { + const { token } = useToken(); + console.log('token', token); + const antdConfig = useAntdConfig(); + console.log('antdConfig', antdConfig); return ( - } - > - {children} - + + } + > + {children} + + ); } diff --git a/react-ui/src/components/KFTabs/index.less b/react-ui/src/components/KFTabs/index.less new file mode 100644 index 00000000..e69de29b diff --git a/react-ui/src/components/KFTabs/index.tsx b/react-ui/src/components/KFTabs/index.tsx new file mode 100644 index 00000000..a9a9f2f1 --- /dev/null +++ b/react-ui/src/components/KFTabs/index.tsx @@ -0,0 +1,32 @@ +/* + * @Author: 赵伟 + * @Date: 2024-04-16 14:55:40 + * @Description: + */ +// import { useEffect, useState } from 'react'; +// import styles from './index.less'; +// import { Tabs } from "antd" + +// function KFTabs() { +// const [iframeUrl, setIframeUrl] = useState(''); +// useEffect(() => { + +// }, []); + +// return ( +//
+//
+// +// +// +// +// +// +// +// +//
+//
+// ); +// } + +// export default KFTabs; diff --git a/react-ui/src/global.less b/react-ui/src/global.less index 2316c464..986af482 100644 --- a/react-ui/src/global.less +++ b/react-ui/src/global.less @@ -194,6 +194,6 @@ ol { } } -.local-svg { +.umi-local-svg { vertical-align: -1px; } diff --git a/react-ui/src/icons/magnifying-glass.svg b/react-ui/src/icons/magnifying-glass.svg new file mode 100644 index 00000000..69bf383b --- /dev/null +++ b/react-ui/src/icons/magnifying-glass.svg @@ -0,0 +1,3 @@ + + + diff --git a/react-ui/src/pages/Experiment/experimentText/addExperimentModal.tsx b/react-ui/src/pages/Experiment/experimentText/addExperimentModal.tsx index 53ebc7ca..3e58ee26 100644 --- a/react-ui/src/pages/Experiment/experimentText/addExperimentModal.tsx +++ b/react-ui/src/pages/Experiment/experimentText/addExperimentModal.tsx @@ -62,7 +62,7 @@ export const getParamRules = (paramType: number, required: boolean = false): For }; // 根据参数设置 label -const getParamType = (param: PipelineGlobalParam): string => { +export const getParamType = (param: PipelineGlobalParam): string => { const paramTypes: Readonly> = { 1: '字符串', 2: '整型', diff --git a/react-ui/src/pages/Experiment/experimentText/paramsModal.less b/react-ui/src/pages/Experiment/experimentText/paramsModal.less index 88287c9d..54e164bf 100644 --- a/react-ui/src/pages/Experiment/experimentText/paramsModal.less +++ b/react-ui/src/pages/Experiment/experimentText/paramsModal.less @@ -1,6 +1,7 @@ .params_container { max-height: 230px; padding: 15px 15px 0; + overflow-y: auto; border: 1px solid #e6e6e6; border-radius: 8px; diff --git a/react-ui/src/pages/Experiment/experimentText/paramsModal.tsx b/react-ui/src/pages/Experiment/experimentText/paramsModal.tsx index 1a1cf41f..9a75c2e8 100644 --- a/react-ui/src/pages/Experiment/experimentText/paramsModal.tsx +++ b/react-ui/src/pages/Experiment/experimentText/paramsModal.tsx @@ -1,6 +1,12 @@ +/* + * @Author: 赵伟 + * @Date: 2024-04-09 15:59:14 + * @Description: 查看实验使用的参数 + */ import parameterImg from '@/assets/img/modal-parameter.png'; import KFModal from '@/components/KFModal'; import { type PipelineGlobalParam } from '@/types'; +import { getParamType } from './addExperimentModal'; import styles from './paramsModal.less'; type ParamsModalProps = { @@ -22,7 +28,7 @@ function ParamsModal({ open, onCancel, globalParam = [] }: ParamsModalProps) {
{globalParam.map((item) => (
- {item.param_name} + {getParamType(item)} {item.param_value}
))} diff --git a/react-ui/src/pages/Mirror/list.less b/react-ui/src/pages/Mirror/list.less new file mode 100644 index 00000000..ee9fbbd6 --- /dev/null +++ b/react-ui/src/pages/Mirror/list.less @@ -0,0 +1,42 @@ +.mirror-list { + height: 100%; + background-color: #f9fafb; + &__tabs-container { + height: 59px; + background-image: url('../../assets/img/mirror-tabs-bg.png'); + } + + &__content { + height: calc(100% - 69px); + margin-top: 10px; + padding: 20px 30px 0; + background-color: white; + border-radius: 10px; + + &__filter { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 10px; + } + + &__table { + height: calc(100% - 44px); + margin-top: 12px; + :global { + .ant-table-wrapper { + height: 100%; + .ant-spin-nested-loading { + height: 100%; + } + .ant-spin-container { + height: 100%; + } + .ant-table { + height: calc(100% - 74px); + } + } + } + } + } +} diff --git a/react-ui/src/pages/Mirror/list.tsx b/react-ui/src/pages/Mirror/list.tsx new file mode 100644 index 00000000..972b0595 --- /dev/null +++ b/react-ui/src/pages/Mirror/list.tsx @@ -0,0 +1,155 @@ +/* + * @Author: 赵伟 + * @Date: 2024-04-16 13:58:08 + * @Description: 镜像列表 + */ +import { getMirrorListReq } from '@/services/mirror'; +import { to } from '@/utils/promise'; +import { Button, Input, Table } from 'antd'; +import dayjs from 'dayjs'; +import { useEffect, useRef, useState } from 'react'; +import styles from './list.less'; + +const tabItems = [ + { + key: 'Public', + label: '公共镜像', + }, + { + key: 'Private', + label: '个人镜像', + }, +]; + +function MirrorList() { + const [activeTab, setActiveTab] = useState('Public'); + const [tableData, setTableData] = useState([]); + const contentRef = useRef(null); + const [tableHeight, setTableHeight] = useState(0); + const [pagination, setPagination] = useState({ + showSizeChanger: true, + showQuickJumper: true, + current: 1, + pageSize: 10, + total: 0, + }); + useEffect(() => { + getMirrorList(''); + }, []); + + useEffect(() => { + const setTableScollHeight = () => { + if (contentRef.current) { + setTableHeight(contentRef.current.offsetHeight - 74 - 55); + } + }; + setTableScollHeight(); + window.addEventListener('resize', setTableScollHeight); + + return () => { + window.removeEventListener('resize', setTableScollHeight); + }; + }, [contentRef]); + + const columns = [ + { + title: '镜像名称', + dataIndex: 'name', + key: 'name', + width: '10%', + }, + { + title: '版本数据', + dataIndex: 'version_count', + key: 'version_count', + width: '10%', + }, + { + title: '镜像描述', + dataIndex: 'description', + key: 'description', + width: '50%', + }, + { + title: '创建时间', + dataIndex: 'create_time', + key: 'create_time', + width: '20%', + render: (text: string) => {dayjs(text).format('YYYY-MM-DD HH:mm:ss')}, + }, + { + title: '操作', + dataIndex: 'operation', + width: '100px', + key: 'operation', + render: (_: any, record: any) => [ + , + ], + }, + ]; + + const getMirrorList = async (name: string) => { + const params = { + page: pagination.current - 1, + size: pagination.pageSize, + name, + image_type: 1, + }; + const [res] = await to(getMirrorListReq(params)); + if (res && res.data) { + const { content = [], totalElements = 0 } = res.data; + console.log(res); + setTableData(content); + setPagination((prev) => ({ + ...prev, + total: totalElements, + })); + } + }; + + const onSearch = (value: string) => { + getMirrorList(value); + }; + + return ( +
+
+ {/* */} +
+
+
+ + +
+
+ + + + + ); +} + +export default MirrorList; diff --git a/react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.less b/react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.less index 6126cf3f..d77d5519 100644 --- a/react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.less +++ b/react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.less @@ -1,13 +1,18 @@ @import '@/styles/theme.less'; .model-tabs { + margin-left: 8px; :global { .ant-tabs-tab { - padding-top: 0 !important; + padding-top: 0; } .ant-tabs-nav::before, div > .ant-tabs-nav::before { - border: none !important; + border: none; + } + + .ant-tabs-nav { + margin-bottom: 0; } } } @@ -16,6 +21,12 @@ display: flex; align-items: flex-start; + :global { + .ant-input-affix-wrapper .ant-input-prefix { + margin-inline-end: 12px; + } + } + &__left { width: 488px; height: 398px; @@ -27,20 +38,11 @@ &__search { margin-bottom: 14px; + padding-left: 0; background-color: transparent; border-width: 0; border-bottom: 1px solid @border-color-second; border-radius: 0; - - // &:hover { - // background-color: transparent; - // } - // &:active { - // background-color: transparent; - // } - // &:focus { - // background-color: transparent; - // } } } diff --git a/react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.tsx b/react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.tsx index 1532af3f..213f9453 100644 --- a/react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.tsx +++ b/react-ui/src/pages/Pipeline/components/ResourceSelectorModal/index.tsx @@ -16,9 +16,10 @@ import { getModelVersionsById, } from '@/services/dataset/index.js'; import { to } from '@/utils/promise'; +import { Icon } from '@umijs/max'; import type { GetRef, ModalProps, TabsProps, TreeDataNode, TreeProps } from 'antd'; import { Input, Tabs, Tree } from 'antd'; -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import styles from './index.less'; export enum ResourceSelectorType { @@ -30,7 +31,7 @@ type ResourceSelectorTypeKeys = keyof typeof ResourceSelectorType; type ResourceSelectorTypeValues = (typeof ResourceSelectorType)[ResourceSelectorTypeKeys]; type GetModelFilesReqParam = { - model_id: number; + models_id: number; version: string; }; @@ -52,6 +53,11 @@ export type SelectorTypeInfo = { tabItems: TabsProps['items']; }; +enum TabItemKeys { + Private = 'Private', // 我的 + Public = 'Public', // 公开 +} + export const selectorTypeData: Record = { Model: { getList: getModelList, @@ -63,11 +69,11 @@ export const selectorTypeData: Record { type: ResourceSelectorType; // 模型 | 数据集 defaultExpandedKeys: React.Key[]; defaultCheckedKeys: React.Key[]; - defaultActiveTab: string; + defaultActiveTab: TabItemKeys; onOk?: (params: ResourceSelectorResponse | null) => void; } type ResourceGroup = { - id: number; - name: string; + id: number; // 数据集或者模型 id + name: string; // 数据集或者模型 id }; type ResourceFile = { - id: number; - file_name: string; + id: number; // 文件 id + file_name: string; // 文件 name }; +type TreeRef = GetRef>; + // list 转成 treeData const convertToTreeData = (list: ResourceGroup[]): TreeDataNode[] => { return list.map((v) => ({ @@ -129,6 +137,7 @@ const convertToTreeData = (list: ResourceGroup[]): TreeDataNode[] => { })); }; +// 更新树形结构的 children const updateChildren = (parentId: number, children: TreeDataNode[]) => { return (node: TreeDataNode) => { if (node.key === parentId) { @@ -141,22 +150,30 @@ const updateChildren = (parentId: number, children: TreeDataNode[]) => { }; }; -type TreeRef = GetRef>; +// 得到数据集或者模型 id 和下属版本号 +const getIdAndVersion = (versionKey: string) => { + const index = versionKey.indexOf('-'); + const id = Number(versionKey.slice(0, index)); + const version = versionKey.slice(index + 1); + return { + id, + version, + }; +}; function ResourceSelectorModal({ type, defaultExpandedKeys = [], defaultCheckedKeys = [], - defaultActiveTab = '0', + defaultActiveTab = TabItemKeys.Private, onOk, ...rest }: ResourceSelectorModalProps) { - const [activeTab, setActiveTab] = useState(defaultActiveTab); + const [activeTab, setActiveTab] = useState(defaultActiveTab); const [expandedKeys, setExpandedKeys] = useState([]); const [checkedKeys, setCheckedKeys] = useState([]); const [loadedKeys, setLoadedKeys] = useState([]); const [originTreeData, setOriginTreeData] = useState([]); - const [treeData, setTreeData] = useState([]); const [files, setFiles] = useState([]); const [versionPath, setVersionPath] = useState(''); const [searchText, setSearchText] = useState(''); @@ -174,19 +191,21 @@ function ResourceSelectorModal({ getTreeData(); }, [activeTab, type]); - useEffect(() => { - const treeData = originTreeData.filter((v) => - (v.title as string).toLowerCase()?.includes(searchText.toLowerCase()), - ); - setTreeData(treeData); - }, [originTreeData, searchText]); + const treeData = useMemo( + () => + originTreeData.filter((v) => + (v.title as string).toLowerCase()?.includes(searchText.toLowerCase()), + ), + [originTreeData, searchText], + ); // 获取数据集或模型列表 const getTreeData = async () => { + const available_range = activeTab === TabItemKeys.Private ? '0' : '1'; const params = { page: 0, size: 200, - available_range: activeTab, + available_range: available_range, }; const getListReq = selectorTypeData[type].getList; const [res] = await to(getListReq(params)); @@ -255,7 +274,7 @@ function ResourceSelectorModal({ // 扩展 const onExpand: TreeProps['onExpand'] = (expandedKeysValue) => { - const lastKeys = (expandedKeysValue as React.Key[]).slice(-1); + const lastKeys = expandedKeysValue.slice(-1); setExpandedKeys(lastKeys); }; @@ -265,9 +284,7 @@ function ResourceSelectorModal({ setCheckedKeys(lastKeys); if (lastKeys.length) { const last = lastKeys[0] as string; - const index = last.indexOf('-'); - const id = Number(last.slice(0, index)); - const version = last.slice(index + 1); + const { id, version } = getIdAndVersion(last); getFiles(id, version); } else { setFiles([]); @@ -295,9 +312,7 @@ function ResourceSelectorModal({ const restoreLastCheck = (parentId: number) => { if (!fisrtLoadVersions && defaultCheckedKeys.length > 0) { const last = defaultCheckedKeys[0] as string; - const index = last.indexOf('-'); - const id = Number(last.slice(0, index)); - const version = last.slice(index + 1); + const { id, version } = getIdAndVersion(last); // 判断正在打开的 id 和 defaultCheckedKeys 的 id 是否一致 if (id === parentId) { setTimeout(() => { @@ -319,16 +334,14 @@ function ResourceSelectorModal({ const handleOk = () => { if (checkedKeys.length > 0) { const last = checkedKeys[0] as string; - const index = last.indexOf('-'); - const id = Number(last.slice(0, index)); - const version = last.slice(index + 1); + const { id, version } = getIdAndVersion(last); const name = (treeData.find((v) => Number(v.key) === id)?.title ?? '') as string; const res = { id, name, path: versionPath, version, - activeTab, + activeTab: activeTab as TabItemKeys, }; onOk?.(res); } else { @@ -344,7 +357,7 @@ function ResourceSelectorModal({ return ( -
+
setSearchText(e.target.value)} + prefix={} /> { const [form] = Form.useForm(); const [stagingItem, setStagingItem] = useState({}); const [open, setOpen] = useState(false); - // const [modelSelectorOpen, openModelSelector, closeModelSelector] = useVisible(false); - // const [selectorType, setSelectorType] = useState(SelectorType.Model); - // const [formItemName, setFormItemName] = useState(''); const [selectedModel, setSelectedModel] = useState(undefined); const [selectedDataset, setSelectedDataset] = useState(undefined); const afterOpenChange = () => { if (!open) { - console.log(111, open); - console.log(stagingItem, form.getFieldsValue()); + // 禁止校验 guard-for-in + /* eslint-disable */ for (let i in form.getFieldsValue()) { for (let j in stagingItem.in_parameters) { if (i == j) { @@ -38,6 +36,7 @@ const Props = forwardRef(({ onParentChange }, ref) => { } } } + /* eslint-enable */ // setStagingItem({...stagingItem,}) console.log(stagingItem.control_strategy); onParentChange({ @@ -86,14 +85,12 @@ const Props = forwardRef(({ onParentChange }, ref) => { }, })); + // 选择数据集、模型 const selectResource = (name, item) => { - // setFormItemName(name); - // setSelectorType(item.item_type === 'dataset' ? SelectorType.Dataset : SelectorType.Model); - // openModelSelector(); const type = item.item_type === 'dataset' ? ResourceSelectorType.Dataset : ResourceSelectorType.Model; const resource = type === ResourceSelectorType.Dataset ? selectedDataset : selectedModel; - const { destroy } = openAntdModal( + const { close } = openAntdModal( ResourceSelectorModal, { type, @@ -102,7 +99,8 @@ const Props = forwardRef(({ onParentChange }, ref) => { defaultActiveTab: resource?.activeTab, onOk: (res) => { if (res) { - const value = JSON.stringify(res); + const jsonObj = pick(res, ['id', 'version', 'path']); + const value = JSON.stringify(jsonObj); form.setFieldValue(name, value); if (type === ResourceSelectorType.Dataset) { setSelectedDataset(res); @@ -117,35 +115,32 @@ const Props = forwardRef(({ onParentChange }, ref) => { } form.setFieldValue(name, ''); } - destroy(); + close(); }, }, true, ); }; - const handleModelSelect = (obj) => { - if (obj) { - const value = JSON.stringify(obj); - setSelectedModel(obj); - form.setFieldValue(formItemName, value); - } else { - form.setFieldValue(formItemName, ''); - } - closeModelSelector(); - }; - + // 获取选择数据集、模型后面按钮 icon const getSelectBtnIcon = (item) => { const type = item.item_type; if (type === 'dataset') { - return ; + return ; } else if (type === 'model') { - return ; + return ; } else { - return ; + return ; } }; + // 控制策略 + const controlStrategy = stagingItem.control_strategy; + // 输入参数 + const inParameters = stagingItem.in_parameters; + // 输出参数 + const outParameters = stagingItem.out_parameters; + return ( <> {