| @@ -1,6 +1,6 @@ | |||
| import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { AutoMLTaskType } from '@/enums'; | |||
| import { modalConfirm } from '@/utils/ui'; | |||
| import { removeFormListItem } from '@/utils/ui'; | |||
| import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons'; | |||
| import { Button, Col, Flex, Form, Input, InputNumber, Radio, Row, Select } from 'antd'; | |||
| import { classificationMetrics, regressionMetrics } from './ExecuteConfig'; | |||
| @@ -72,12 +72,14 @@ function TrialConfig() { | |||
| type="text" | |||
| icon={<MinusCircleOutlined />} | |||
| onClick={() => { | |||
| modalConfirm({ | |||
| title: '确定要删除该指标权重吗?', | |||
| onOk: () => { | |||
| remove(name); | |||
| }, | |||
| }); | |||
| removeFormListItem( | |||
| form, | |||
| 'metrics', | |||
| name, | |||
| remove, | |||
| ['name', 'value'], | |||
| '删除后,该该指标权重将不可恢复', | |||
| ); | |||
| }} | |||
| ></Button> | |||
| {index === fields.length - 1 && ( | |||
| @@ -8,7 +8,7 @@ import SubAreaTitle from '@/components/SubAreaTitle'; | |||
| import { hyperParameterOptimizedModeOptions } from '@/enums'; | |||
| import { useComputingResource } from '@/hooks/resource'; | |||
| import { isEmpty } from '@/utils'; | |||
| import { modalConfirm } from '@/utils/ui'; | |||
| import { modalConfirm, removeFormListItem } from '@/utils/ui'; | |||
| import { MinusCircleOutlined, PlusCircleOutlined, QuestionCircleOutlined } from '@ant-design/icons'; | |||
| import { | |||
| Button, | |||
| @@ -396,12 +396,14 @@ function ExecuteConfig() { | |||
| size="middle" | |||
| icon={<MinusCircleOutlined />} | |||
| onClick={() => { | |||
| modalConfirm({ | |||
| title: '确定要删除该参数吗?', | |||
| onOk: () => { | |||
| remove(name); | |||
| }, | |||
| }); | |||
| removeFormListItem( | |||
| form, | |||
| 'parameters', | |||
| name, | |||
| remove, | |||
| ['name', 'type'], | |||
| '删除后,该参数将不可恢复', | |||
| ); | |||
| }} | |||
| ></Button> | |||
| {index === fields.length - 1 && ( | |||
| @@ -460,7 +462,7 @@ function ExecuteConfig() { | |||
| ); | |||
| if (arr.length > 0 && arr.length < runParameters.length) { | |||
| return Promise.reject( | |||
| new Error(`手动运行超参数 ${name} 必须全部填写或者都不填写`), | |||
| new Error(`手动运行超参数 "${name}" 必须全部填写或者都不填写`), | |||
| ); | |||
| } | |||
| } | |||
| @@ -518,7 +520,8 @@ function ExecuteConfig() { | |||
| icon={<MinusCircleOutlined />} | |||
| onClick={() => { | |||
| modalConfirm({ | |||
| title: '确定要删除该运行参数吗?', | |||
| title: '删除后,该运行参数将不可恢复', | |||
| content: '是否确认删除?', | |||
| onOk: () => { | |||
| remove(name); | |||
| }, | |||
| @@ -68,7 +68,7 @@ function ParameterRange({ type, value, onConfirm }: ParameterRangeProps) { | |||
| <Flex | |||
| style={{ | |||
| marginLeft: '10px', | |||
| marginBottom: '20px', | |||
| marginBottom: '24px', | |||
| flex: 'none', | |||
| width: '66px', | |||
| }} | |||
| @@ -45,11 +45,14 @@ function ModelEvolution({ | |||
| const [enterTooltip, setEnterTooltip] = useState(false); | |||
| const [nodeTooltipX, setNodeToolTipX] = useState(0); | |||
| const [nodeTooltipY, setNodeToolTipY] = useState(0); | |||
| const [isNodeTooltipLeft, setIsNodeTooltipLeft] = useState(true); | |||
| const [hoverNodeData, setHoverNodeData] = useState< | |||
| ModelDepsData | ProjectDependency | TrainDataset | undefined | |||
| >(undefined); | |||
| const apiData = useRef<ModelDepsData | undefined>(undefined); // 接口返回的树形结构 | |||
| const hierarchyNodes = useRef<ModelDepsData[]>([]); // 层级迭代树形结构,得到的节点列表 | |||
| const leaveNodeTimeout = useRef<ReturnType<typeof setTimeout> | null>(null); | |||
| const leaveTooltipTimeout = useRef<ReturnType<typeof setTimeout> | null>(null); | |||
| useEffect(() => { | |||
| initGraph(); | |||
| @@ -134,6 +137,12 @@ function ModelEvolution({ | |||
| // 绑定事件 | |||
| const bindEvents = () => { | |||
| graph.on('node:mouseenter', (e: G6GraphEvent) => { | |||
| // 清除延时关闭tooltip的定时器 | |||
| if (leaveNodeTimeout.current) { | |||
| clearTimeout(leaveNodeTimeout.current); | |||
| leaveNodeTimeout.current = null; | |||
| } | |||
| const nodeItem = e.item; | |||
| graph.setItemState(nodeItem, 'hover', true); | |||
| @@ -141,26 +150,37 @@ function ModelEvolution({ | |||
| const { x, y } = model; | |||
| const point = graph.getCanvasByPoint(x!, y!); | |||
| const zoom = graph.getZoom(); | |||
| // 更加缩放,调整 tooltip 位置 | |||
| const offsetX = (nodeWidth * zoom) / 4; | |||
| const offsetY = (nodeHeight * zoom) / 4; | |||
| point.x += offsetX; | |||
| // 根据缩放,调整 tooltip 位置 | |||
| // const offsetX = (nodeWidth * zoom) / 4; | |||
| const offsetY = (nodeHeight * zoom) / 2; | |||
| // 25 是 `.model-evolution` 的 `padding-left` 值 | |||
| const tooltipX = point.x + 25 - 20; | |||
| // 20 是 `.model-evolution` 的 `padding-bottom` 值 | |||
| const tooltipY = graphRef.current!.clientHeight - point.y + offsetY + 20 + 10; | |||
| setNodeToolTipY(tooltipY); | |||
| // 如果右边显示不下 | |||
| const canvasWidth = graphRef.current!.clientWidth; | |||
| if (point.x + 300 > canvasWidth + 30) { | |||
| point.x = canvasWidth + 30 - 300; | |||
| // 300 是 NodeTool 的宽度,canvasWidth + 50 是 `.model-evolution` 的宽度 | |||
| if (tooltipX + 300 > canvasWidth + 50) { | |||
| setIsNodeTooltipLeft(false); | |||
| setNodeToolTipX(canvasWidth + 50 - (point.x + 25) - 20); | |||
| } else { | |||
| setNodeToolTipX(tooltipX); | |||
| setIsNodeTooltipLeft(true); | |||
| } | |||
| setHoverNodeData(model); | |||
| setNodeToolTipX(point.x); | |||
| setNodeToolTipY(graphRef.current!.clientHeight - point.y + offsetY); | |||
| setShowNodeTooltip(true); | |||
| }); | |||
| graph.on('node:mouseleave', (e: G6GraphEvent) => { | |||
| const nodeItem = e.item; | |||
| graph.setItemState(nodeItem, 'hover', false); | |||
| setShowNodeTooltip(false); | |||
| leaveNodeTimeout.current = setTimeout(() => { | |||
| setShowNodeTooltip(false); | |||
| }, 100); | |||
| }); | |||
| graph.on('node:click', (e: G6GraphEvent) => { | |||
| @@ -191,6 +211,12 @@ function ModelEvolution({ | |||
| setShowNodeTooltip(false); | |||
| setEnterTooltip(false); | |||
| }); | |||
| // 开始拖拽画布时触发 | |||
| graph.on('DRAG_START', () => { | |||
| setShowNodeTooltip(false); | |||
| setEnterTooltip(false); | |||
| }); | |||
| }; | |||
| // toggle 展开 | |||
| @@ -205,11 +231,18 @@ function ModelEvolution({ | |||
| }; | |||
| const handleTooltipsMouseEnter = () => { | |||
| // 清除延时关闭tooltip的定时器 | |||
| if (leaveTooltipTimeout.current) { | |||
| clearTimeout(leaveTooltipTimeout.current); | |||
| leaveTooltipTimeout.current = null; | |||
| } | |||
| setEnterTooltip(true); | |||
| }; | |||
| const handleTooltipsMouseLeave = () => { | |||
| setEnterTooltip(false); | |||
| leaveTooltipTimeout.current = setTimeout(() => { | |||
| setEnterTooltip(false); | |||
| }, 100); | |||
| }; | |||
| // 获取模型依赖 | |||
| @@ -254,6 +287,7 @@ function ModelEvolution({ | |||
| resourceId={resourceId} | |||
| x={nodeTooltipX} | |||
| y={nodeTooltipY} | |||
| isLeft={isNodeTooltipLeft} | |||
| data={hoverNodeData!} | |||
| onVersionChange={onVersionChange} | |||
| onMouseEnter={handleTooltipsMouseEnter} | |||
| @@ -33,7 +33,7 @@ export type Rect = { | |||
| }; | |||
| export interface TrainDataset extends NodeConfig { | |||
| repo_id: number; | |||
| repo_id: string; | |||
| name: string; | |||
| version: string; | |||
| identifier: string; | |||
| @@ -150,11 +150,14 @@ function ModelMetrics({ resourceId, identifier, owner, version }: ModelMetricsPr | |||
| // 表头 | |||
| const columns: TableProps<TableData>['columns'] = useMemo(() => { | |||
| const first: TableData | undefined = tableData.find( | |||
| const firstMetrics: TableData | undefined = tableData.find( | |||
| (item) => item.metrics_names && item.metrics_names.length > 0, | |||
| ); | |||
| const metricsNames = first?.metrics_names ?? []; | |||
| const paramsNames = first?.params_names ?? []; | |||
| const firstParams: TableData | undefined = tableData.find( | |||
| (item) => item.params_names && item.params_names.length > 0, | |||
| ); | |||
| const metricsNames = firstMetrics?.metrics_names ?? []; | |||
| const paramsNames = firstParams?.params_names ?? []; | |||
| return [ | |||
| { | |||
| title: '基本信息', | |||
| @@ -1,7 +1,5 @@ | |||
| .node-tooltips { | |||
| position: absolute; | |||
| bottom: -100px; | |||
| left: -300px; | |||
| z-index: 10; | |||
| width: 300px; | |||
| padding: 10px; | |||
| @@ -10,6 +8,30 @@ | |||
| border-radius: 4px; | |||
| box-shadow: 0px 3px 6px rgba(146, 146, 146, 0.09); | |||
| &::after { | |||
| position: absolute; | |||
| bottom: -8px; /* 让三角形紧贴 div 底部 */ | |||
| left: 12px; /* 控制三角形相对 div 的位置 */ | |||
| width: 0; | |||
| height: 0; | |||
| border-top: 8px solid white; /* 主要颜色 */ | |||
| border-right: 8px solid transparent; | |||
| border-left: 8px solid transparent; | |||
| content: ''; | |||
| } | |||
| &::before { | |||
| position: absolute; | |||
| bottom: -10px; /* 边框略大,形成描边效果 */ | |||
| left: 10px; /* 调整边框的偏移量,使其覆盖白色三角形 */ | |||
| width: 0; | |||
| height: 0; | |||
| border-top: 10px solid #eaeaea; /* 这是边框颜色 */ | |||
| border-right: 10px solid transparent; | |||
| border-left: 10px solid transparent; | |||
| content: ''; | |||
| } | |||
| &__title { | |||
| margin: 10px 0; | |||
| color: @text-color; | |||
| @@ -59,3 +81,31 @@ | |||
| } | |||
| } | |||
| } | |||
| .node-tooltips.node-tooltips--right { | |||
| &::after { | |||
| position: absolute; | |||
| right: 12px; /* 控制三角形相对 div 的位置 */ | |||
| bottom: -8px; /* 让三角形紧贴 div 底部 */ | |||
| left: auto; | |||
| width: 0; | |||
| height: 0; | |||
| border-top: 8px solid white; /* 主要颜色 */ | |||
| border-right: 8px solid transparent; | |||
| border-left: 8px solid transparent; | |||
| content: ''; | |||
| } | |||
| &::before { | |||
| position: absolute; | |||
| right: 10px; /* 调整边框的偏移量,使其覆盖白色三角形 */ | |||
| bottom: -10px; /* 边框略大,形成描边效果 */ | |||
| left: auto; | |||
| width: 0; | |||
| height: 0; | |||
| border-top: 10px solid #eaeaea; /* 这是边框颜色 */ | |||
| border-right: 10px solid transparent; | |||
| border-left: 10px solid transparent; | |||
| content: ''; | |||
| } | |||
| } | |||
| @@ -2,6 +2,7 @@ import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceInfo'; | |||
| import { getGitUrl } from '@/utils'; | |||
| import { formatDate } from '@/utils/date'; | |||
| import { useNavigate } from '@umijs/max'; | |||
| import classNames from 'classnames'; | |||
| import { ModelDepsData, NodeType, ProjectDependency, TrainDataset } from '../ModelEvolution/utils'; | |||
| import styles from './index.less'; | |||
| @@ -180,6 +181,7 @@ type NodeTooltipsProps = { | |||
| data: ModelDepsData | ProjectDependency | TrainDataset; | |||
| x: number; | |||
| y: number; | |||
| isLeft: boolean; | |||
| onMouseEnter?: () => void; | |||
| onMouseLeave?: () => void; | |||
| onVersionChange: (version: string) => void; | |||
| @@ -190,6 +192,7 @@ function NodeTooltips({ | |||
| data, | |||
| x, | |||
| y, | |||
| isLeft, | |||
| onMouseEnter, | |||
| onMouseLeave, | |||
| onVersionChange, | |||
| @@ -208,10 +211,13 @@ function NodeTooltips({ | |||
| ) { | |||
| Component = <ModelInfo resourceId={resourceId} data={data} onVersionChange={onVersionChange} />; | |||
| } | |||
| const style = isLeft | |||
| ? { left: `${x}px`, bottom: `${y}px` } | |||
| : { right: `${x}px`, bottom: `${y}px` }; | |||
| return ( | |||
| <div | |||
| className={styles['node-tooltips']} | |||
| style={{ left: `${x}px`, bottom: `${y}px` }} | |||
| className={classNames(styles['node-tooltips'], { [styles['node-tooltips--right']]: !isLeft })} | |||
| style={style} | |||
| onMouseEnter={onMouseEnter} | |||
| onMouseLeave={onMouseLeave} | |||
| > | |||
| @@ -20,7 +20,7 @@ import { | |||
| import { changePropertyName } from '@/utils'; | |||
| import { to } from '@/utils/promise'; | |||
| import SessionStorage from '@/utils/sessionStorage'; | |||
| import { modalConfirm } from '@/utils/ui'; | |||
| import { removeFormListItem } from '@/utils/ui'; | |||
| import { MinusCircleOutlined, PlusCircleOutlined, PlusOutlined } from '@ant-design/icons'; | |||
| import { useNavigate, useParams } from '@umijs/max'; | |||
| import { App, Button, Col, Flex, Form, Input, InputNumber, Row, Select } from 'antd'; | |||
| @@ -434,7 +434,6 @@ function CreateServiceVersion() { | |||
| {fields.map(({ key, name, ...restField }, index) => ( | |||
| <Flex | |||
| key={key} | |||
| align="center" | |||
| gap="0 8px" | |||
| style={{ | |||
| position: 'relative', | |||
| @@ -453,16 +452,16 @@ function CreateServiceVersion() { | |||
| }, | |||
| ]} | |||
| > | |||
| <Input placeholder="请输入变量名" disabled={disabled} /> | |||
| <Input placeholder="请输入变量名" disabled={disabled} allowClear /> | |||
| </Form.Item> | |||
| <span style={{ marginBottom: '24px' }}>=</span> | |||
| <span style={{ lineHeight: '46px' }}>=</span> | |||
| <Form.Item | |||
| {...restField} | |||
| name={[name, 'value']} | |||
| style={{ flex: 1 }} | |||
| rules={[{ required: true, message: '请输入变量值' }]} | |||
| > | |||
| <Input placeholder="请输入变量值" disabled={disabled} /> | |||
| <Input placeholder="请输入变量值" disabled={disabled} allowClear /> | |||
| </Form.Item> | |||
| <Flex | |||
| style={{ | |||
| @@ -484,12 +483,14 @@ function CreateServiceVersion() { | |||
| icon={<MinusCircleOutlined />} | |||
| disabled={disabled} | |||
| onClick={() => { | |||
| modalConfirm({ | |||
| title: '确定要删除该环境变量吗?', | |||
| onOk: () => { | |||
| remove(name); | |||
| }, | |||
| }); | |||
| removeFormListItem( | |||
| form, | |||
| 'env_variables', | |||
| name, | |||
| remove, | |||
| ['key', 'value'], | |||
| '删除后,该环境变量将不可恢复', | |||
| ); | |||
| }} | |||
| ></Button> | |||
| {index === fields.length - 1 && ( | |||
| @@ -40,7 +40,8 @@ const GlobalParamsDrawer = forwardRef( | |||
| const removeParameter = (name: number, remove: (param: number) => void) => { | |||
| modalConfirm({ | |||
| title: '确认删除该参数吗?', | |||
| title: '删除后,该全局参数将不可恢复', | |||
| content: '是否确认删除?', | |||
| onOk: () => { | |||
| remove(name); | |||
| }, | |||
| @@ -8,7 +8,16 @@ import { removeAllPageCacheState } from '@/hooks/pageCacheState'; | |||
| import themes from '@/styles/theme.less'; | |||
| import { type ClientInfo } from '@/types'; | |||
| import { history } from '@umijs/max'; | |||
| import { Modal, Upload, message, type ModalFuncProps, type UploadFile } from 'antd'; | |||
| import { | |||
| Modal, | |||
| Upload, | |||
| message, | |||
| type FormInstance, | |||
| type ModalFuncProps, | |||
| type UploadFile, | |||
| } from 'antd'; | |||
| import { NamePath } from 'antd/es/form/interface'; | |||
| import { isEmpty } from './index'; | |||
| import { closeAllModals } from './modal'; | |||
| import SessionStorage from './sessionStorage'; | |||
| @@ -143,16 +152,38 @@ export const limitUploadFileType = (type: string) => { | |||
| }; | |||
| /** | |||
| * 滚动到底部 | |||
| * | |||
| * @param {boolean} smooth - Determines if the scroll should be smooth | |||
| * 删除 FormList 表单项,如果表单项没有值,则直接删除,否则弹出确认框 | |||
| * @param form From实例 | |||
| * @param listName FormList 的 name | |||
| * @param name FormList 的其中一项 | |||
| * @param remove FormList 的删除方法 | |||
| * @param fieldNames FormList 的子项名称数组 | |||
| * @param confirmTitle 弹出确认框的标题 | |||
| */ | |||
| export const scrollToBottom = (element: HTMLElement | null, smooth: boolean = true) => { | |||
| if (element) { | |||
| const optons: ScrollToOptions = { | |||
| top: element.scrollHeight, | |||
| behavior: smooth ? 'smooth' : 'instant', | |||
| }; | |||
| element.scrollTo(optons); | |||
| export const removeFormListItem = ( | |||
| form: FormInstance, | |||
| listName: NamePath, | |||
| name: number, | |||
| remove: (name: number) => void, | |||
| fieldNames: NamePath[], | |||
| confirmTitle: string, | |||
| ) => { | |||
| const fields = fieldNames.map((item) => [listName, name, item].flat()); | |||
| const isEmptyField = fields.every((item) => { | |||
| const value = form.getFieldValue(item); | |||
| return isEmpty(value); | |||
| }); | |||
| if (isEmptyField) { | |||
| remove(name); | |||
| return; | |||
| } | |||
| modalConfirm({ | |||
| title: confirmTitle, | |||
| content: '是否确认删除?', | |||
| onOk: () => { | |||
| remove(name); | |||
| }, | |||
| }); | |||
| }; | |||
| @@ -44,6 +44,7 @@ public class ServiceServiceImpl implements ServiceService { | |||
| private ServiceDao serviceDao; | |||
| @Resource | |||
| private AssetWorkflowDao assetWorkflowDao; | |||
| @Override | |||
| public Page<com.ruoyi.platform.domain.Service> queryByPageService(com.ruoyi.platform.domain.Service service, PageRequest pageRequest) { | |||
| long total = serviceDao.countService(service); | |||
| @@ -225,9 +226,9 @@ public class ServiceServiceImpl implements ServiceService { | |||
| String req = HttpUtils.sendPost(argoUrl + modelService + "/delete", JSON.toJSONString(paramMap)); | |||
| if (StringUtils.isNotEmpty(req)) { | |||
| Map<String, Object> reqMap = JacksonUtil.parseJSONStr2Map(req); | |||
| if ((Integer) reqMap.get("code") == 200) { | |||
| return serviceDao.updateServiceVersion(serviceVersion) > 0 ? "删除成功" : "删除失败"; | |||
| } | |||
| // if ((Integer) reqMap.get("code") == 200) { | |||
| return serviceDao.updateServiceVersion(serviceVersion) > 0 ? "删除成功" : "删除失败"; | |||
| // } | |||
| } | |||
| return "删除失败"; | |||
| } | |||