2025-05-01 13:05:26 +08:00

173 lines
5.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// File: frontend/app/workflow/page.tsx
// Description: 工作流编辑器页面,使用 React Flow
'use client'; // React Flow 需要客户端渲染
import React, { useState, useCallback, useMemo } from 'react';
import ReactFlow, {
ReactFlowProvider, // Provider 包裹应用
MiniMap, // 小地图
Controls, // 控制按钮 (缩放, 适应视图)
Background, // 背景网格
useNodesState, // Hook 管理节点状态
useEdgesState, // Hook 管理边状态
addEdge, // Helper 函数添加边
Node, // 节点类型
Edge, // 边类型
Connection, // 连接类型
Position, // 用于 Handle 定位
MarkerType, // 用于边箭头类型
NodeChange, // 节点变化类型
EdgeChange, // 边变化类型
applyNodeChanges, // 应用节点变化
applyEdgeChanges,
BackgroundVariant, // 应用边变化
} from 'reactflow';
// 引入 React Flow 的 CSS 样式
import 'reactflow/dist/style.css';
// --- 初始节点和边数据 (示例) ---
const initialNodes: Node[] = [
{
id: '1',
type: 'input', // React Flow 内建输入类型节点
data: { label: '开始节点' },
position: { x: 250, y: 5 },
sourcePosition: Position.Bottom, // Handle (连接点) 位置
},
{
id: '2',
// type: 'default', // 默认类型节点
data: { label: '处理节点 A' },
position: { x: 100, y: 100 },
sourcePosition: Position.Bottom,
targetPosition: Position.Top,
},
{
id: '3',
type: 'output', // React Flow 内建输出类型节点
data: { label: '结束节点' },
position: { x: 400, y: 100 },
targetPosition: Position.Top,
},
{
id: '4',
type: 'default', // 自定义节点类型后续添加
data: { label: '处理节点 B' },
position: { x: 250, y: 200 },
sourcePosition: Position.Bottom,
targetPosition: Position.Top,
},
];
const initialEdges: Edge[] = [
{
id: 'e1-2',
source: '1', // 源节点 ID
target: '2', // 目标节点 ID
label: '数据流 1', // 边标签 (可选)
markerEnd: { type: MarkerType.ArrowClosed }, // 箭头样式
},
{
id: 'e1-3',
source: '1',
target: '3',
label: '数据流 2',
markerEnd: { type: MarkerType.ArrowClosed },
},
{
id: 'e2-4',
source: '2',
target: '4',
animated: true, // 动画边 (可选)
label: '处理 A -> B',
markerEnd: { type: MarkerType.ArrowClosed },
},
];
// --- 工作流页面组件 ---
function WorkflowEditor() {
// 使用 React Flow 提供的 Hooks 管理节点和边的状态
const [nodes, setNodes] = useState<Node[]>(initialNodes);
const [edges, setEdges] = useState<Edge[]>(initialEdges);
// 当节点拖动、选择等交互发生时,更新节点状态
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) => {
// 创建新边
const newEdge = {
...connection,
id: `e-${connection.source}-${connection.target}-${Math.random()}`, // 简单生成 ID
markerEnd: { type: MarkerType.ArrowClosed }, // 添加箭头
// animated: true, // 可以给新连接添加动画
};
setEdges((eds) => addEdge(newEdge, eds));
console.log('连接成功:', connection);
},
[setEdges]
);
// --- (可选) 自定义节点类型 ---
// const nodeTypes = useMemo(() => ({
// customInput: CustomInputNode, // 示例
// llmNode: LLMNode, // 示例
// }), []);
return (
<div className="flex flex-col h-full bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-hidden">
<h1 className="text-xl font-semibold p-4 border-b dark:border-gray-700 text-gray-800 dark:text-gray-200">
</h1>
{/* 设置 React Flow 画布容器的高度 */}
<div style={{ height: 'calc(100% - 65px)' }} className="flex-1"> {/* 减去标题栏高度 */}
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
// nodeTypes={nodeTypes} // 注册自定义节点类型
fitView // 初始加载时适应视图
className="bg-gray-50 dark:bg-gray-900" // 设置画布背景色
>
{/* 添加控件 */}
<Controls />
<MiniMap nodeStrokeWidth={3} zoomable pannable />
<Background gap={16} color="#ccc" variant={BackgroundVariant.Dots} /> {/* 使用点状背景 */}
{/* TODO: 添加侧边栏用于拖放节点 */}
{/* <div className="absolute left-4 top-20 z-10 bg-white dark:bg-gray-700 p-4 rounded shadow">
<p>节点面板</p>
<button className="p-2 border rounded mt-2 block">拖拽节点 A</button>
<button className="p-2 border rounded mt-2 block">拖拽节点 B</button>
</div> */}
</ReactFlow>
</div>
</div>
);
}
// --- 主页面组件,包含 Provider ---
export default function WorkflowPage() {
return (
// ReactFlowProvider 需要包裹使用 useReactFlow hook 的组件
// 如果 WorkflowEditor 内部需要使用 useReactFlow则 Provider 必须在外层
<ReactFlowProvider>
<WorkflowEditor />
</ReactFlowProvider>
);
}