173 lines
5.6 KiB
TypeScript
173 lines
5.6 KiB
TypeScript
// 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>
|
||
);
|
||
}
|