import React, { useCallback, useEffect } from 'react'; import { applyNodeChanges, applyEdgeChanges, addEdge, reconnectEdge, Node, Edge, } from '@xyflow/react'; import { Message } from '@arco-design/web-react'; import { localNodeData } from '@/pages/flowEditor/sideBar/config/localNodeData'; import { useAlignmentGuidelines } from '@/hooks/useAlignmentGuidelines'; import LoopNode from '@/components/FlowEditor/node/loopNode/LoopNode'; import { updateCanvasDataMap } from '@/store/ideContainer'; import { projectFlowHandle } from '@/pages/flowEditor/utils/projectFlowHandle'; import { appFLowHandle } from '@/pages/flowEditor/utils/appFlowhandle'; import { getCurrentAppKey } from '@/features/workflow/runtime/flowRuntime'; import { ensureNodeTypeRegistered, resolveNodeComponent, } from '@/features/workflow/registry/nodeRegistry'; import { dispatchFlowSnapshot, dispatchFlowSnapshotAsync, } from '@/features/workflow/operations/snapshot'; import { createLoopGroupEdge, createLoopInsertConnectionEdges, createLoopNodePair, } from '@/features/workflow/operations/loopOperations'; import { buildInsertedNodeEdges, resolveInsertedNodeHandles, } from '@/features/workflow/operations/edgeOperations'; import { buildWorkflowConnectionEdge, validateWorkflowConnection, } from '@/features/workflow/operations/connectionOperations'; import { buildCopiedFlowData, buildPastedFlowData, buildPastedSingleNode, } from '@/features/workflow/operations/clipboardOperations'; import { isLoopBoundaryNode, removeNodesAndConnectedEdges, resolveDeletedNodesWithLoopPairs, } from '@/features/workflow/operations/deleteOperations'; import { shouldPersistCanvas, shouldUseCachedCanvas, } from '@/features/workflow/operations/canvasCache'; import { buildRuntimeNode, resolveNodeDefinition, } from '@/features/workflow/operations/nodeOnboarding'; import { saveWorkflowData } from '@/features/workflow/persistence/flowPersistence'; import { pauseWorkflow, rerunWorkflow, runWorkflow, } from '@/features/workflow/runtime/runtimeActions'; import { Dispatch } from 'redux'; import store from '@/store'; export const useFlowCallbacks = ( nodes: Node[], setNodes: React.Dispatch>, edges: Edge[], setEdges: React.Dispatch>, useDefault: boolean, reactFlowInstance: any, canvasDataMap: any, dispatch: Dispatch, updateCanvasDataMapDebounced: ( dispatch: Dispatch, canvasDataMap: any, id: string, nodes: Node[], edges: Edge[] ) => void, initialData: any, historyTimeoutRef: React.MutableRefObject, setHistoryInitialized: React.Dispatch>, editingNode: Node | null, setEditingNode: React.Dispatch>, setIsEditModalOpen: React.Dispatch>, edgeForNodeAdd: Edge | null, setEdgeForNodeAdd: React.Dispatch>, positionForNodeAdd: { x: number; y: number } | null, setPositionForNodeAdd: React.Dispatch< React.SetStateAction<{ x: number; y: number } | null> >, setIsDelete: React.Dispatch> ) => { const { getGuidelines, clearGuidelines } = useAlignmentGuidelines(); // 辅助函数:获取当前应用/子流程的唯一标识符 const getCurrentFlowAppKey = useCallback(() => { const { currentAppData } = store.getState().ideContainer; return getCurrentAppKey(currentAppData) || initialData?.appId; }, [initialData]); // region 画布操作 // 节点变更处理,添加防抖机制 const onNodesChange = useCallback( (changes: any) => { // 深度克隆节点数组以避免修改冻结的对象 const clonedNodes = JSON.parse(JSON.stringify(nodes)); const newNodes = applyNodeChanges(changes, clonedNodes); setNodes(newNodes); // 如果需要在节点变化时执行某些操作,可以在这里添加 // 只有当变化是节点位置变化时才不立即记录历史 const isPositionChange = changes.some( (change: any) => change.type === 'position' && change.dragging === false ); // 如果是位置变化结束或者不是位置变化,则记录历史 if ( isPositionChange || !changes.some((change: any) => change.type === 'position') ) { // 清除之前的定时器 if (historyTimeoutRef.current) { clearTimeout(historyTimeoutRef.current); } // 设置新的定时器,延迟记录历史记录 historyTimeoutRef.current = dispatchFlowSnapshotAsync( { nodes: [...newNodes], edges: [...edges] }, 100 ); } }, [nodes, edges] ); // 边变更处理 const onEdgesChange = useCallback( (changes: any) => { const newEdges = applyEdgeChanges(changes, edges); setEdges(newEdges); // 如果需要在边变化时执行某些操作,可以在这里添加 // 边的变化立即记录历史 dispatchFlowSnapshot({ nodes: [...nodes], edges: [...newEdges] }); }, [edges, nodes] ); // 节点删除处理 const onNodesDelete = useCallback((deletedNodes: any) => { setIsDelete(true); }, []); // 边连接处理 const onConnect = useCallback( (params: any) => { const validation = validateWorkflowConnection(nodes, params); if (!validation.isValid) { if (validation.message) console.warn(validation.message); return; } setEdges((edgesSnapshot: Edge[]) => { const result = buildWorkflowConnectionEdge( nodes, params, validation.lineType || 'data' ); if (!result?.edge) { if (result?.message) { console.warn(result.message); Message.warning(result.message); } return edgesSnapshot; } const newEdges = addEdge(result.edge, edgesSnapshot); dispatchFlowSnapshotAsync({ nodes: [...nodes], edges: [...newEdges] }); return newEdges; }); }, [nodes] ); // 边重新连接处理 const onReconnect = useCallback( (oldEdge: Edge, newConnection: any) => { const validation = validateWorkflowConnection(nodes, newConnection); if (!validation.isValid) { if (validation.message) console.warn(validation.message); return; } setEdges((els) => reconnectEdge(oldEdge, newConnection, els)); }, [nodes] ); // 拖动处理 const onDragOver = useCallback((event: React.DragEvent) => { event.preventDefault(); event.dataTransfer.dropEffect = 'move'; }, []); const getNodeDefinition = useCallback( (nodeType: string, fallbackNode?: any) => resolveNodeDefinition(localNodeData, nodeType, fallbackNode), [] ); const clearAddNodeContext = useCallback(() => { setEdgeForNodeAdd(null); setPositionForNodeAdd(null); }, []); // 添加循环节点及其开始和结束节点 const addLoopNodeWithStartEnd = useCallback( (position: { x: number; y: number }) => { const { loopStartNode, loopEndNode } = createLoopNodePair(position); // 创建连接边(连接循环开始和结束节点的顶部连接点) const newEdges = [createLoopGroupEdge(loopStartNode.id, loopEndNode.id)]; // 将未定义的节点动态追加进nodeTypes ensureNodeTypeRegistered('LOOP', '循环', LoopNode); setNodes((nds: Node[]) => { const newNodes = [...nds, loopStartNode, loopEndNode]; // 添加节点后记录历史 dispatchFlowSnapshotAsync({ nodes: [...newNodes], edges: [...edges, ...newEdges], }); return newNodes; }); setEdges((eds: Edge[]) => { const updatedEdges = [...eds, ...newEdges]; // 添加边后记录历史 dispatchFlowSnapshotAsync({ nodes: [...nodes, loopStartNode, loopEndNode], edges: [...updatedEdges], }); return updatedEdges; }); }, [nodes, edges] ); const addNodeOnCanvas = useCallback( ( nodeType: string, nodeDefinition: any, position: { x: number; y: number } ) => { if (nodeType === 'LOOP') { addLoopNodeWithStartEnd(position); return; } const { eventList } = store.getState().ideContainer; const newNode = buildRuntimeNode({ nodeType, nodeDefinition, position, eventList, eventIdMode: 'eventIdOptional', }); setNodes((nds: Node[]) => { const newNodes = nds.concat(newNode); dispatchFlowSnapshotAsync({ nodes: [...newNodes], edges: [...edges] }); return newNodes; }); }, [addLoopNodeWithStartEnd, edges] ); // 侧边栏节点实例 const onDrop = useCallback( (event: React.DragEvent) => { event.preventDefault(); if (!reactFlowInstance) return; const callBack = event.dataTransfer.getData('application/reactflow'); const nodeData = JSON.parse(callBack); if (typeof nodeData.nodeType === 'undefined' || !nodeData.nodeType) { return; } const position = reactFlowInstance.screenToFlowPosition({ x: event.clientX, y: event.clientY, }); const nodeDefinition = getNodeDefinition(nodeData.nodeType, nodeData); if (!nodeDefinition) return; addNodeOnCanvas(nodeData.nodeType, nodeDefinition, position); }, [reactFlowInstance, getNodeDefinition, addNodeOnCanvas] ); // 节点拖拽处理 const onNodeDrag = useCallback( (_: any, node: Node) => { // 获取对齐线 getGuidelines(node, nodes); }, [nodes, getGuidelines] ); // 节点拖拽结束处理 const onNodeDragStop = useCallback(() => { // 清除对齐线 clearGuidelines(); }, [clearGuidelines]); // endregion // region 画布数据处理 // 初始化画布数据 const initializeCanvasData = useCallback(() => { const appKey = getCurrentFlowAppKey(); const cachedCanvas = appKey ? canvasDataMap[appKey] : null; if (shouldUseCachedCanvas({ cachedCanvas, initialData, useDefault })) { const { edges, nodes } = cachedCanvas; setNodes(nodes); setEdges(edges); } else { // 首次进入 if (useDefault) projectFlowHandle( initialData, useDefault, setNodes, setEdges, dispatch, canvasDataMap ); else appFLowHandle(initialData, useDefault, setNodes, setEdges, dispatch); } // 标记历史记录已初始化 setHistoryInitialized(true); }, [initialData, useDefault, canvasDataMap, getCurrentFlowAppKey]); // 实时更新 canvasDataMap const updateCanvasDataMapEffect = useCallback(() => { const appKey = getCurrentFlowAppKey(); const { appRuntimeData } = store.getState().ideContainer; const isCurrentAppRunning = Boolean( appKey && appRuntimeData[appKey]?.isRunning ); if ( appKey && shouldPersistCanvas({ nodes, edges, isRunning: isCurrentAppRunning }) ) { updateCanvasDataMapDebounced( dispatch, canvasDataMap, appKey, nodes, edges ); } // 清理函数,在组件卸载时取消防抖 return () => { // 取消防抖函数 }; }, [nodes, edges, dispatch, canvasDataMap, getCurrentFlowAppKey]); // 关闭编辑弹窗 const closeEditModal = useCallback(() => { setIsEditModalOpen(false); setEditingNode(null); }, []); // 保存节点编辑 const saveNodeEdit = useCallback( (updatedData: any) => { console.log('updatedData:', updatedData); const updatedNodes = nodes.map((node) => { if (node.id === editingNode?.id) { return { ...node, data: { ...node.data, ...updatedData }, }; } return node; }); setNodes(updatedNodes); closeEditModal(); }, [nodes, editingNode, closeEditModal] ); // 编辑节点 const editNode = useCallback((node: Node) => { setEditingNode(node); setIsEditModalOpen(true); }, []); // 编辑边 const editEdge = useCallback((edge: Edge) => { // 这里可以实现边编辑逻辑 console.log('编辑边:', edge); }, []); // 复制节点(支持多节点和多边) const copyNode = useCallback( (node: Node) => { const copiedData = buildCopiedFlowData( node, nodes, edges, initialData?.appId ); if (!copiedData) { console.warn('没有可复制的节点(开始和结束节点不能复制)'); return; } localStorage.setItem('copiedFlowData', JSON.stringify(copiedData)); console.log( `已复制 ${copiedData.nodes.length} 个节点和 ${copiedData.edges.length} 条边` ); }, [nodes, edges, initialData?.appId] ); // 粘贴节点(支持多节点和多边) const pasteNode = useCallback( (position: { x: number; y: number }) => { // 尝试读取新格式的复制数据(多节点+边) const copiedFlowDataStr = localStorage.getItem('copiedFlowData'); // 兼容旧格式的单节点复制 const copiedNodeStr = localStorage.getItem('copiedNode'); if (!copiedFlowDataStr && !copiedNodeStr) { console.warn('没有找到复制的节点数据'); return; } try { // 处理新格式(多节点+边) if (copiedFlowDataStr) { const copiedData = JSON.parse(copiedFlowDataStr); // 检查是否为同一应用 if ( copiedData.appId && initialData?.appId && copiedData.appId !== initialData.appId ) { console.warn('不能在不同应用之间粘贴节点'); return; } const pastedData = buildPastedFlowData(copiedData, position); if (!pastedData) { console.warn('没有可粘贴的节点'); return; } pastedData.nodes.forEach((newNode: Node) => { ensureNodeTypeRegistered( newNode.type, (newNode.data?.title as string) || newNode.type, resolveNodeComponent(newNode.type) ); }); // 更新节点和边 setNodes((nds: Node[]) => { const updatedNodes = [...nds, ...pastedData.nodes]; // 添加节点后记录历史 dispatchFlowSnapshotAsync({ nodes: [...updatedNodes], edges: [...edges, ...pastedData.edges], }); return updatedNodes; }); setEdges((eds: Edge[]) => { const updatedEdges = [...eds, ...pastedData.edges]; return updatedEdges; }); console.log( `已粘贴 ${pastedData.nodes.length} 个节点和 ${pastedData.edges.length} 条边` ); } // 处理旧格式(单节点)- 保持向后兼容 else if (copiedNodeStr) { const copiedNode = JSON.parse(copiedNodeStr); // 检查是否为同一应用 if ( copiedNode.appId && initialData?.appId && copiedNode.appId !== initialData.appId ) { console.warn('不能在不同应用之间粘贴节点'); return; } // 创建新节点,更新ID和位置 const newNode = buildPastedSingleNode(copiedNode, position); // 特殊处理循环节点 if (copiedNode.type === 'LOOP') { setNodes((nds: Node[]) => { const newNodes = [...nds, newNode]; dispatchFlowSnapshotAsync({ nodes: [...newNodes], edges: [...edges], }); return newNodes; }); return; } // 注册节点类型 ensureNodeTypeRegistered( newNode.type, (newNode.data?.title as string) || newNode.type, resolveNodeComponent(newNode.type) ); setNodes((nds: Node[]) => { const newNodes = [...nds, newNode]; dispatchFlowSnapshotAsync({ nodes: [...newNodes], edges: [...edges], }); return newNodes; }); } } catch (error) { console.error('粘贴节点时出错:', error); } }, [edges, initialData?.appId, nodes] ); // endregion // region 节点/边操作 // 删除节点函数 const deleteNode = useCallback( (node: Node) => { // 在应用编排模式下(useDefault为false)不允许删除节点 if (!useDefault) { console.warn('在应用编排模式下不允许删除节点'); return; } // 开始和结束节点不允许删除 if (node.type === 'start' || node.type === 'end') { console.warn('开始和结束节点不允许删除'); return; } const nodesToRemove = isLoopBoundaryNode(node) ? resolveDeletedNodesWithLoopPairs([node], nodes) : [node]; const nextGraph = removeNodesAndConnectedEdges( nodes, edges, nodesToRemove ); setNodes(nextGraph.nodes); setEdges(nextGraph.edges); // 删除节点后记录历史 setTimeout(() => { dispatchFlowSnapshot({ nodes: [...nextGraph.nodes], edges: [...nextGraph.edges], }); }, 0); }, [nodes, edges, useDefault] ); // 删除边函数 const deleteEdge = useCallback( (edge: Edge) => { // 获取当前应用的运行状态 const { appRuntimeData, currentAppData } = store.getState().ideContainer; const currentAppIsRunning = currentAppData?.id && appRuntimeData[currentAppData.id] ? appRuntimeData[currentAppData.id].isRunning : false; // 在运行时禁止删除边 if (currentAppIsRunning) { console.warn('在运行时不允许删除边'); return; } setEdges((eds: Edge[]) => eds.filter((e) => e.id !== edge.id)); // 删除边后记录历史 setTimeout(() => { dispatchFlowSnapshot({ nodes: [...nodes], edges: [...edges.filter((e) => e.id !== edge.id)], }); }, 0); }, [nodes, edges] ); const getEdgeMidpointPosition = useCallback( (sourceId: string, targetId: string) => { const sourceNode = nodes.find((n) => n.id === sourceId); const targetNode = nodes.find((n) => n.id === targetId); if (!sourceNode || !targetNode) return null; return { x: (sourceNode.position.x + targetNode.position.x) / 2, y: (sourceNode.position.y + targetNode.position.y) / 2, }; }, [nodes] ); // 在边上添加节点的具体实现 const addNodeOnEdge = useCallback( (nodeType: string, node: any) => { if (!edgeForNodeAdd || !reactFlowInstance) return; // 查找节点定义 const nodeDefinition = getNodeDefinition(nodeType, node); if (!nodeDefinition) return; // 特殊处理循环节点,添加开始和结束节点 if (nodeType === 'LOOP') { const position = getEdgeMidpointPosition( edgeForNodeAdd.source, edgeForNodeAdd.target ); if (!position) return; const { loopStartNode, loopEndNode } = createLoopNodePair(position); // 创建连接边(连接循环开始和结束节点的顶部连接点) const groupEdge = createLoopGroupEdge(loopStartNode.id, loopEndNode.id); // 创建连接原有节点到循环开始节点,以及循环结束节点到目标节点的边 const connectionEdges = createLoopInsertConnectionEdges({ sourceId: edgeForNodeAdd.source, sourceHandle: edgeForNodeAdd.sourceHandle, targetId: edgeForNodeAdd.target, targetHandle: edgeForNodeAdd.targetHandle, loopStartId: loopStartNode.id, loopEndId: loopEndNode.id, }); // 将未定义的节点动态追加进nodeTypes ensureNodeTypeRegistered('LOOP', '循环', LoopNode); // 更新节点和边 setNodes((nds: Node[]) => { const newNodes = [...nds, loopStartNode, loopEndNode]; // 添加节点后记录历史 dispatchFlowSnapshotAsync({ nodes: [...newNodes], edges: [...edges, groupEdge, ...connectionEdges], }); return newNodes; }); setEdges((eds: Edge[]) => { // 删除原来的边,添加新的边 const updatedEdges = [ ...eds.filter((e) => e.id !== edgeForNodeAdd.id), groupEdge, ...connectionEdges, ]; // 添加边后记录历史 dispatchFlowSnapshotAsync({ nodes: [...nodes, loopStartNode, loopEndNode], edges: [...updatedEdges], }); return updatedEdges; }); // 关闭菜单 clearAddNodeContext(); return; } const position = getEdgeMidpointPosition( edgeForNodeAdd.source, edgeForNodeAdd.target ); if (!position) return; const { eventList } = store.getState().ideContainer; const newNode = buildRuntimeNode({ nodeType, nodeDefinition, position, eventList, eventIdMode: 'id', }); // 添加新节点 setNodes((nds: Node[]) => [...nds, newNode]); // 删除旧边 setEdges((eds: Edge[]) => eds.filter((e) => e.id !== edgeForNodeAdd.id)); const { sourceHandle, targetHandle } = resolveInsertedNodeHandles(newNode); const newEdges = buildInsertedNodeEdges({ baseEdges: edges, removedEdgeId: edgeForNodeAdd.id, sourceId: edgeForNodeAdd.source, sourceHandle: edgeForNodeAdd.sourceHandle, targetId: edgeForNodeAdd.target, targetHandle: edgeForNodeAdd.targetHandle, insertedNodeId: newNode.id, insertedNodeSourceHandle: sourceHandle, insertedNodeTargetHandle: targetHandle, }); setEdges(newEdges); // 关闭菜单 clearAddNodeContext(); // 添加节点后记录历史 dispatchFlowSnapshotAsync({ nodes: [...nodes, newNode], edges: [...newEdges], }); }, [ edgeForNodeAdd, reactFlowInstance, edges, addLoopNodeWithStartEnd, getEdgeMidpointPosition, nodes, clearAddNodeContext, ] ); // 在画布上添加节点 const addNodeOnPane = useCallback( (nodeType: string, position: { x: number; y: number }, node?: any) => { if (!reactFlowInstance) return; // 查找节点定义 const nodeDefinition = getNodeDefinition(nodeType, node); if (!nodeDefinition) return; addNodeOnCanvas(nodeType, nodeDefinition, position); }, [reactFlowInstance, getNodeDefinition, addNodeOnCanvas] ); // 处理添加节点的统一方法 const handleAddNode = useCallback( (nodeType: string, node: any) => { // 如果是通过边添加节点 if (edgeForNodeAdd) { addNodeOnEdge(nodeType, node); } // 如果是通过画布添加节点 else if (positionForNodeAdd) { addNodeOnPane(nodeType, positionForNodeAdd, node); } // 清除状态 clearAddNodeContext(); }, [ edgeForNodeAdd, positionForNodeAdd, addNodeOnEdge, addNodeOnPane, clearAddNodeContext, ] ); // endregion const saveFlowDataToServer = useCallback(async () => { await saveWorkflowData({ nodes, edges, useDefault, initialData, canvasDataMap, dispatch, setNodes, setEdges, }); }, [nodes, edges, useDefault, initialData, canvasDataMap, dispatch]); // 运行处理函数 const handleRun = useCallback( async (running: boolean) => { await runWorkflow({ running, dispatch, getCurrentFlowAppKey, setEdges, }); }, [getCurrentFlowAppKey] ); // 暂停/恢复应用 const handlePause = useCallback( async (isPaused: boolean) => { await pauseWorkflow({ isPaused, dispatch, getCurrentFlowAppKey }); }, [getCurrentFlowAppKey] ); // 重跑应用 const handleReRun = useCallback(async () => { await rerunWorkflow({ dispatch, getCurrentFlowAppKey }); }, [getCurrentFlowAppKey]); return { // Event handlers onNodesChange, onEdgesChange, onConnect, onReconnect, onDragOver, onDrop, onNodeDrag, onNodeDragStop, onNodesDelete, // Menu handlers closeEditModal, saveNodeEdit, deleteNode, deleteEdge, editNode, editEdge, copyNode, pasteNode, // 添加粘贴节点功能 // Node operations addNodeOnEdge, addNodeOnPane, handleAddNode, // Initialization initializeCanvasData, updateCanvasDataMapEffect, // Actions saveFlowDataToServer, handleRun, handlePause, handleReRun, }; }; export default useFlowCallbacks;