diff --git a/web/src/views/Workflow/components/Nodes/AddNode.tsx b/web/src/views/Workflow/components/Nodes/AddNode.tsx index d2d1d15c..f9561a44 100644 --- a/web/src/views/Workflow/components/Nodes/AddNode.tsx +++ b/web/src/views/Workflow/components/Nodes/AddNode.tsx @@ -2,7 +2,7 @@ import { useState } from 'react'; import { Popover } from 'antd'; import clsx from 'clsx'; import type { ReactShapeConfig } from '@antv/x6-react-shape'; -import { nodeLibrary, graphNodeLibrary } from '../../constant'; +import { nodeLibrary, graphNodeLibrary, edgeAttrs } from '../../constant'; import { useTranslation } from 'react-i18next'; const AddNode: ReactShapeConfig['component'] = ({ node, graph }) => { @@ -47,7 +47,7 @@ const AddNode: ReactShapeConfig['component'] = ({ node, graph }) => { graph.addEdge({ source: { cell: edge.getSourceCellId(), port: edge.getSourcePortId() }, target: { cell: newNode.id, port: newNode.getPorts().find((port: any) => port.group === 'left')?.id || 'left' }, - attrs: edge.getAttrs(), + ...edgeAttrs }); }); @@ -57,7 +57,7 @@ const AddNode: ReactShapeConfig['component'] = ({ node, graph }) => { graph.addEdge({ source: { cell: newNode.id, port: newNode.getPorts().find((port: any) => port.group === 'right')?.id || 'right' }, target: { cell: edge.getTargetCellId(), port: targetPortId }, - attrs: edge.getAttrs(), + ...edgeAttrs }); }); diff --git a/web/src/views/Workflow/components/Nodes/LoopNode.tsx b/web/src/views/Workflow/components/Nodes/LoopNode.tsx index 26109d58..97136746 100644 --- a/web/src/views/Workflow/components/Nodes/LoopNode.tsx +++ b/web/src/views/Workflow/components/Nodes/LoopNode.tsx @@ -2,9 +2,7 @@ import { useEffect } from 'react'; import { useTranslation } from 'react-i18next' import clsx from 'clsx'; import type { ReactShapeConfig } from '@antv/x6-react-shape'; -import { graphNodeLibrary } from '../../constant'; - -import { edge_color } from '../../hooks/useWorkflowGraph' +import { graphNodeLibrary, edgeAttrs } from '../../constant'; const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => { const data = node.getData() || {}; @@ -56,16 +54,7 @@ const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => { graph.addEdge({ source: { cell: cycleStartNode.id, port: sourcePort }, target: { cell: addNode.id, port: targetPort }, - attrs: { - line: { - stroke: edge_color, - strokeWidth: 1, - targetMarker: { - name: 'block', - size: 8, - }, - }, - }, + ...edgeAttrs, }); } } @@ -122,16 +111,7 @@ const LoopNode: ReactShapeConfig['component'] = ({ node, graph }) => { cell: addNode.id, port: targetPorts.find((port: any) => port.group === 'left')?.id || 'left' }, - attrs: { - line: { - stroke: edge_color, - strokeWidth: 1, - targetMarker: { - name: 'block', - size: 2, - }, - }, - }, + ...edgeAttrs } graph.addEdge(edgeConfig) } diff --git a/web/src/views/Workflow/components/PortClickHandler.tsx b/web/src/views/Workflow/components/PortClickHandler.tsx index 8d95431c..0a1e3906 100644 --- a/web/src/views/Workflow/components/PortClickHandler.tsx +++ b/web/src/views/Workflow/components/PortClickHandler.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react'; import { Popover } from 'antd'; import { useTranslation } from 'react-i18next'; -import { nodeLibrary, graphNodeLibrary } from '../constant'; +import { nodeLibrary, graphNodeLibrary, edgeAttrs } from '../constant'; interface PortClickHandlerProps { graph: any; @@ -149,16 +149,7 @@ const PortClickHandler: React.FC = ({ graph }) => { graph.addEdge({ source: { cell: sourceNode.id, port: sourcePort }, target: { cell: newNode.id, port: targetPort }, - attrs: { - line: { - stroke: '#155EEF', - strokeWidth: 1, - targetMarker: { - name: 'block', - size: 8, - }, - }, - }, + ...edgeAttrs // zIndex: sourceNodeData.cycle && sourceNodeType == 'cycle-start' ? 1 : sourceNodeData.cycle ? 2 : 0 }); diff --git a/web/src/views/Workflow/components/Properties/CaseList/index.tsx b/web/src/views/Workflow/components/Properties/CaseList/index.tsx index 8caa601e..d9521059 100644 --- a/web/src/views/Workflow/components/Properties/CaseList/index.tsx +++ b/web/src/views/Workflow/components/Properties/CaseList/index.tsx @@ -6,6 +6,7 @@ import { Form, Button, Select, Space, Divider, InputNumber, Radio, type SelectPr import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin' import VariableSelect from '../VariableSelect' import Editor from '../../Editor' +import { edgeAttrs } from '../../../constant' interface CaseListProps { value?: Array<{ logical_operator: 'and' | 'or'; expressions: { left: string; operator: string; right: string; input_type?: string; }[] }>; @@ -120,16 +121,7 @@ const CaseList: FC = ({ graphRef.current?.addEdge({ source: { cell: sourceCellId, port: sourcePortId }, target: { cell: selectedNode.id, port: targetPortId }, - attrs: { - line: { - stroke: '#155EEF', - strokeWidth: 1, - targetMarker: { - name: 'block', - size: 8, - }, - }, - }, + ...edgeAttrs, }); } graphRef.current?.removeCell(edge); @@ -174,16 +166,7 @@ const CaseList: FC = ({ graphRef.current?.addEdge({ source: { cell: selectedNode.id, port: newPortId }, target: { cell: targetCellId, port: targetPortId }, - attrs: { - line: { - stroke: '#155EEF', - strokeWidth: 1, - targetMarker: { - name: 'block', - size: 8, - }, - }, - }, + ...edgeAttrs }); } } diff --git a/web/src/views/Workflow/components/Properties/CategoryList/index.tsx b/web/src/views/Workflow/components/Properties/CategoryList/index.tsx index ca7f8115..aabc3ad3 100644 --- a/web/src/views/Workflow/components/Properties/CategoryList/index.tsx +++ b/web/src/views/Workflow/components/Properties/CategoryList/index.tsx @@ -5,6 +5,7 @@ import { Graph, Node } from '@antv/x6'; import Editor from '../../Editor'; import type { Suggestion } from '../../Editor/plugin/AutocompletePlugin' +import { edgeAttrs } from '../../../constant' interface CategoryListProps { parentName: string; @@ -70,16 +71,7 @@ const CategoryList: FC = ({ parentName, selectedNode, graphRe graphRef.current?.addEdge({ source: { cell: sourceCellId, port: sourcePortId }, target: { cell: selectedNode.id, port: targetPortId }, - attrs: { - line: { - stroke: '#155EEF', - strokeWidth: 1, - targetMarker: { - name: 'block', - size: 8, - }, - }, - }, + ...edgeAttrs }); } return; @@ -110,16 +102,7 @@ const CategoryList: FC = ({ parentName, selectedNode, graphRe graphRef.current?.addEdge({ source: { cell: selectedNode.id, port: newPortId }, target: { cell: targetCellId, port: targetPortId }, - attrs: { - line: { - stroke: '#155EEF', - strokeWidth: 1, - targetMarker: { - name: 'block', - size: 8, - }, - }, - }, + ...edgeAttrs }); } } diff --git a/web/src/views/Workflow/constant.ts b/web/src/views/Workflow/constant.ts index bea518bf..17a5fd2b 100644 --- a/web/src/views/Workflow/constant.ts +++ b/web/src/views/Workflow/constant.ts @@ -517,6 +517,8 @@ interface NodeConfig { ports?: PortsConfig; } +export const edge_color = '#155EEF'; +export const edge_selected_color = '#4DA8FF' // 统一的端口 markup 配置 export const portMarkup = [ { @@ -534,9 +536,9 @@ export const portAttrs = { body: { r: 6, magnet: true, - stroke: '#155EEF', + stroke: edge_color, strokeWidth: 2, - fill: '#155EEF', + fill: edge_color, }, label: { text: '+', @@ -776,4 +778,18 @@ export const outputVariable: { [key: string]: OutputVariable } = { { name: "output", type: "string" }, ], }, +} + +export const edgeAttrs = { + attrs: { + line: { + stroke: edge_color, + strokeWidth: 1, + targetMarker: { + name: 'block', + width: 4, + height: 4, + }, + }, + }, } \ No newline at end of file diff --git a/web/src/views/Workflow/hooks/useWorkflowGraph.ts b/web/src/views/Workflow/hooks/useWorkflowGraph.ts index 615cd3e5..a9dc39c3 100644 --- a/web/src/views/Workflow/hooks/useWorkflowGraph.ts +++ b/web/src/views/Workflow/hooks/useWorkflowGraph.ts @@ -5,7 +5,7 @@ import { App } from 'antd' import { Graph, Node, MiniMap, Snapline, Clipboard, Keyboard, type Edge } from '@antv/x6'; import { register } from '@antv/x6-react-shape'; -import { nodeRegisterLibrary, graphNodeLibrary, nodeLibrary, portMarkup, portAttrs } from '../constant'; +import { nodeRegisterLibrary, graphNodeLibrary, nodeLibrary, portMarkup, portAttrs, edgeAttrs, edge_color, edge_selected_color } from '../constant'; import type { WorkflowConfig, NodeProperties, ChatVariable } from '../types'; import { getWorkflowConfig, saveWorkflowConfig } from '@/api/application' import type { PortMetadata } from '@antv/x6/lib/model/port'; @@ -23,12 +23,8 @@ export interface UseWorkflowGraphReturn { setSelectedNode: React.Dispatch>; zoomLevel: number; setZoomLevel: React.Dispatch>; - canUndo: boolean; - canRedo: boolean; isHandMode: boolean; setIsHandMode: React.Dispatch>; - onUndo: () => void; - onRedo: () => void; onDrop: (event: React.DragEvent) => void; blankClick: () => void; deleteEvent: () => boolean | void; @@ -39,8 +35,6 @@ export interface UseWorkflowGraphReturn { setChatVariables: React.Dispatch>; } -export const edge_color = '#155EEF'; -const edge_selected_color = '#4DA8FF' export const useWorkflowGraph = ({ containerRef, miniMapRef, @@ -51,9 +45,6 @@ export const useWorkflowGraph = ({ const graphRef = useRef(); const [selectedNode, setSelectedNode] = useState(null); const [zoomLevel, setZoomLevel] = useState(1); - const historyRef = useRef<{ undoStack: string[], redoStack: string[] }>({ undoStack: [], redoStack: [] }); - const [canUndo, setCanUndo] = useState(false); - const [canRedo, setCanRedo] = useState(false); const [isHandMode, setIsHandMode] = useState(true); const [config, setConfig] = useState(null); const [chatVariables, setChatVariables] = useState([]) @@ -338,17 +329,7 @@ export const useWorkflowGraph = ({ port: targetPorts.find((port: any) => port.group === 'left')?.id || 'left' }, connector: { name: 'smooth' }, - attrs: { - line: { - stroke: edge_color, - strokeWidth: 1, - targetMarker: { - name: 'diamond', - width: 4, - height: 4, - }, - }, - }, + ...edgeAttrs // zIndex: loopIterationCount } @@ -368,48 +349,6 @@ export const useWorkflowGraph = ({ }, 200) } } - - const saveState = () => { - if (!graphRef.current) return; - const state = JSON.stringify(graphRef.current.toJSON()); - historyRef.current.undoStack.push(state); - historyRef.current.redoStack = []; - if (historyRef.current.undoStack.length > 50) { - historyRef.current.undoStack.shift(); - } - updateHistoryState(); - }; - - const updateHistoryState = () => { - setCanUndo(historyRef.current.undoStack.length > 1); - setCanRedo(historyRef.current.redoStack.length > 0); - }; - - // 撤销 - const onUndo = () => { - if (!graphRef.current || historyRef.current.undoStack.length === 0) return; - const { undoStack = [], redoStack = [] } = historyRef.current - - const currentState = JSON.stringify(graphRef.current.toJSON()); - const prevState = undoStack[undoStack.length - 2]; - - historyRef.current.redoStack = [...redoStack, currentState] - historyRef.current.undoStack = undoStack.slice(0, undoStack.length - 1) - graphRef.current.fromJSON(JSON.parse(prevState)); - updateHistoryState(); - }; - // 重做 - const onRedo = () => { - if (!graphRef.current || historyRef.current.redoStack.length === 0) return; - const { undoStack = [], redoStack = [] } = historyRef.current - - const nextState = redoStack[redoStack.length - 1]; - - historyRef.current.undoStack = [...undoStack, nextState] - historyRef.current.redoStack = redoStack.slice(0, redoStack.length - 1) - graphRef.current.fromJSON(JSON.parse(nextState)); - updateHistoryState(); - }; // 使用插件 const setupPlugins = () => { if (!graphRef.current || !miniMapRef.current) return; @@ -563,20 +502,6 @@ export const useWorkflowGraph = ({ } return false; }; - // 撤销快捷键事件 - const undoEvent = () => { - if (canUndo) { - onUndo(); - } - return false; - }; - // 重做快捷键事件 - const redoEvent = () => { - if (canRedo) { - onRedo(); - } - return false; - }; // 删除选中的节点和连线事件 const deleteEvent = () => { if (!graphRef.current) return; @@ -677,8 +602,6 @@ export const useWorkflowGraph = ({ background: { color: '#F0F3F8', }, - // width: container.clientWidth || 800, - // height: container.clientHeight || 600, autoResize: true, grid: { visible: true, @@ -694,37 +617,26 @@ export const useWorkflowGraph = ({ enabled: true, }, connecting: { - // router: 'orth', - // router: 'manhattan', connector: { name: 'smooth', args: { radius: 8, }, }, - anchor: 'center', + anchor: 'midSide', connectionPoint: 'anchor', allowBlank: false, + allowLoop: false, allowNode: false, allowEdge: false, + allowPort: true, + allowMulti: true, highlight: true, snap: { radius: 20, }, createEdge() { - return graphRef.current?.createEdge({ - attrs: { - line: { - stroke: edge_color, - strokeWidth: 1, - targetMarker: { - name: 'diamond', - width: 4, - height: 4, - }, - }, - }, - }); + return graphRef.current?.createEdge(edgeAttrs); }, validateConnection({ sourceCell, targetCell, targetMagnet }) { if (!targetMagnet) return false; @@ -823,25 +735,6 @@ export const useWorkflowGraph = ({ graphRef.current.on('scale', scaleEvent); // 监听节点移动事件 graphRef.current.on('node:moved', nodeMoved); - - // 监听画布变化事件 - const events = [ - 'node:added', - 'node:removed', - 'edge:added', - 'edge:removed', - ]; - events.forEach(event => { - graphRef.current!.on(event, () => { - console.log('event', event); - setTimeout(() => saveState(), 50); - }); - }); - - // 监听撤销键盘事件 - graphRef.current.bindKey(['ctrl+z', 'cmd+z'], undoEvent); - // 监听重做键盘事件 - graphRef.current.bindKey(['ctrl+shift+z', 'cmd+shift+z', 'ctrl+y', 'cmd+y'], redoEvent); // 监听复制键盘事件 graphRef.current.bindKey(['ctrl+c', 'cmd+c'], copyEvent); // 监听粘贴键盘事件 @@ -849,11 +742,6 @@ export const useWorkflowGraph = ({ // 删除选中的节点和连线 graphRef.current.bindKey(['ctrl+d', 'cmd+d', 'delete', 'backspace'], deleteEvent); - // 保存初始状态 - setTimeout(() => saveState(), 100); - // init window hook - (window as Window & { __x6_instances__?: Graph[] }).__x6_instances__ = []; - (window as Window & { __x6_instances__?: Graph[] }).__x6_instances__?.push(graphRef.current); }; useEffect(() => { @@ -1066,11 +954,11 @@ export const useWorkflowGraph = ({ }), } saveWorkflowConfig(config.app_id, params as WorkflowConfig) - .then(() => { + .then((res) => { if (flag) { message.success(t('common.saveSuccess')) } - resolve(true) + resolve(res) }).catch(error => { reject(error) }) @@ -1085,12 +973,8 @@ export const useWorkflowGraph = ({ setSelectedNode, zoomLevel, setZoomLevel, - canUndo, - canRedo, isHandMode, setIsHandMode, - onUndo, - onRedo, onDrop, blankClick, deleteEvent,