// File: frontend/app/workflow/page.tsx // Description: 工作流编辑器页面,添加侧边栏用于拖放节点 'use client'; import React, { useState, useCallback, useRef, DragEvent } from 'react'; // 添加 useRef, DragEvent import ReactFlow, { ReactFlowProvider, MiniMap, Controls, Background, useNodesState, useEdgesState, addEdge, Node, Edge, Connection, Position, MarkerType, NodeChange, EdgeChange, applyNodeChanges, applyEdgeChanges, useReactFlow, // 导入 useReactFlow hook XYPosition, BackgroundVariant, // 导入 XYPosition 类型 } from 'reactflow'; import { MessageSquareText, BrainCircuit, Database, LogOut, Play } from 'lucide-react'; // 导入一些节点图标 import 'reactflow/dist/style.css'; // --- 初始节点和边数据 (可以清空或保留示例) --- const initialNodes: Node[] = [ // { id: '1', type: 'inputNode', data: { label: '开始' }, position: { x: 100, y: 100 } }, ]; const initialEdges: Edge[] = []; // --- 定义可拖拽的节点类型 --- const nodeTypesForPalette = [ { type: 'inputNode', label: '输入', icon: MessageSquareText, defaultData: { text: '用户输入...' } }, { type: 'llmNode', label: 'LLM 调用', icon: BrainCircuit, defaultData: { model: 'gpt-3.5-turbo', prompt: '...' } }, { type: 'ragNode', label: 'RAG 查询', icon: Database, defaultData: { query: '...', knowledgeBase: 'default' } }, { type: 'outputNode', label: '输出', icon: LogOut, defaultData: { result: null } }, { type: 'startNode', label: '开始流程', icon: Play, defaultData: {} }, // 添加一个开始节点类型 ]; // --- 侧边栏组件 --- const Sidebar = () => { // 拖拽开始时设置数据 const onDragStart = (event: DragEvent, nodeType: string, defaultData: any) => { // 将节点类型和默认数据存储在 dataTransfer 中 const nodeInfo = JSON.stringify({ nodeType, defaultData }); event.dataTransfer.setData('application/reactflow', nodeInfo); event.dataTransfer.effectAllowed = 'move'; console.log(`Drag Start: ${nodeType}`); }; return ( ); }; // --- 工作流编辑器组件 --- function WorkflowEditor() { const reactFlowWrapper = useRef(null); // Ref 指向 React Flow 容器 const [nodes, setNodes] = useState(initialNodes); const [edges, setEdges] = useState(initialEdges); const { project } = useReactFlow(); // 使用 hook 获取 project 方法 const onNodesChange = useCallback( (changes: NodeChange[]) => setNodes((nds) => applyNodeChanges(changes, nds)), [setNodes] ); const onEdgesChange = useCallback( (changes: EdgeChange[]) => setEdges((eds) => applyEdgeChanges(changes, eds)), [setEdges] ); const onConnect = useCallback( (connection: Connection) => setEdges((eds) => addEdge({ ...connection, markerEnd: { type: MarkerType.ArrowClosed } }, eds)), [setEdges] ); // 处理画布上的拖拽悬停事件 const onDragOver = useCallback((event: DragEvent) => { event.preventDefault(); // 必须阻止默认行为才能触发 onDrop event.dataTransfer.dropEffect = 'move'; // 设置放置效果 }, []); // 处理画布上的放置事件 const onDrop = useCallback( (event: DragEvent) => { event.preventDefault(); // 阻止默认行为(如打开文件) if (!reactFlowWrapper.current) { return; } // 从 dataTransfer 中获取节点信息 const nodeInfoString = event.dataTransfer.getData('application/reactflow'); if (!nodeInfoString) { console.warn("No reactflow data found in dataTransfer"); return; } let nodeInfo; try { nodeInfo = JSON.parse(nodeInfoString); } catch (error) { console.error("Failed to parse node info from dataTransfer", error); return; } const { nodeType, defaultData } = nodeInfo; if (!nodeType) { console.warn("Node type not found in dataTransfer"); return; } // 获取鼠标相对于 React Flow 画布的位置 // 需要计算鼠标位置相对于 reactFlowWrapper 的偏移量 const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect(); const position = project({ x: event.clientX - reactFlowBounds.left, y: event.clientY - reactFlowBounds.top, }); // 创建新节点 const newNode: Node = { id: `${nodeType}-${Date.now()}`, // 使用更唯一的 ID type: nodeType, // 设置节点类型 position, data: { label: `${nodeType} node`, ...defaultData }, // 合并默认数据 // 根据节点类型设置 Handle 位置 (可选,也可以在自定义节点内部定义) sourcePosition: Position.Bottom, targetPosition: Position.Top, }; console.log('Node dropped:', newNode); // 将新节点添加到状态中 setNodes((nds) => nds.concat(newNode)); }, [project, setNodes] // 依赖 project 方法和 setNodes ); // --- (可选) 注册自定义节点类型 --- // const nodeTypes = useMemo(() => ({ // inputNode: CustomInputNode, // llmNode: LLMNode, // ragNode: RAGNode, // outputNode: CustomOutputNode, // startNode: StartNode, // }), []); return ( 工作流编辑器 {/* 主容器使用 Flexbox */} {/* 侧边栏 */} {/* React Flow 画布容器 */} {/* 添加 ref */} ); } // --- 主页面组件,包含 Provider --- export default function WorkflowPage() { return ( ); }