# 流程图可视化系统需求文档(React 版本) ## 一、项目概述 基于 design.html 的现有实现,开发一个新的流程图可视化系统。支持节点和边的交互式展示,提供清晰的层级结构和详细信息面板。 **设计原则**: - 采用组件化架构,提高代码复用性 - 清晰的目录结构,便于团队协作 ## 二、技术栈 - **UI 框架**: React 18+ / Vue 3+ - **构建工具**: Vite / Webpack - **数据可视化**: D3.js 或 React+SVG - **数据通信**: Axios (HTTP) + WebSocket API - **状态管理**: Zustand / Pinia / Redux Toolkit - **样式方案**: CSS Modules / Styled Components / SCSS **架构原则**: - **组件化**: 每个功能模块独立组件 - **关注点分离**: 组件、样式、API 请求必须分别在不同文件中实现 - **类型安全**: 推荐使用 TypeScript - **模块化**: 清晰的文件组织结构 ## 三、整体框架结构 ### 3.1 布局组成 ``` ┌─────────────────────────────────────────────────────┐ │ 顶部导航栏 │ ├─────────────────────────────────┬───────────────────┤ │ │ │ │ │ │ │ 主体内容区域 │ 右侧详情面板 │ │ (流程图可视化) │ │ │ │ │ │ │ │ └─────────────────────────────────┴───────────────────┘ ``` ### 3.2 组件层级 - **App** (根组件) - **TopBar** (顶部导航栏) - Title (标题) - FilterBar (筛选条件) - **MainContent** (主体内容区域) - FlowChart (流程图组件) - **DetailPanel** (右侧详情面板) - NodeDetail (节点详情) - EdgeDetail (边详情) ## 四、功能需求详述 ### 4.1 顶部导航栏 (TopBar) **实现文件**: - 组件: `src/components/TopBar/TopBar.tsx` - 样式: `src/components/TopBar/TopBar.module.css` - 类型: `src/components/TopBar/types.ts` **内容**: 1. **标题区域** - 显示当前选中 Trace 的 task 名称 - 可配置的图标和文字 2. **筛选条件区域** - 使用 UI 组件库的表单组件(Select、Input 等) - 筛选项包括但不限于: - 状态筛选(running/completed/failed) - Trace 选择下拉框 - 刷新按钮 - 筛选条件变化时触发数据重新加载 **数据结构**: ```typescript interface FilterConfig { status?: "running" | "completed" | "failed"; traceId?: string; } ``` ### 4.2 主体内容区域 (MainContent) **实现文件**: - 组件: `src/components/MainContent/MainContent.tsx` - 样式: `src/components/MainContent/MainContent.module.css` - 子组件: `src/components/FlowChart/FlowChart.tsx` **功能**: 流程图可视化展示 #### 4.2.1 节点交互 **交互行为**: - 点击节点时,在主体内容区域高亮显示从 root 到该节点的完整路径 - 同时显示该节点最近的边的内容 **视觉效果**: - 路径上的节点和边高亮显示 - 非路径部分半透明或灰化 - 平滑的动画过渡 **数据需求**: ```typescript interface Node { id: string; label: string; type: string; parentId?: string; metadata: Record; } interface PathInfo { nodes: Node[]; edges: Edge[]; nearestEdge?: Edge; } ``` #### 4.2.2 边交互 **交互行为**: - 点击边时,在右侧详情面板显示该边的所有内容 - 内容以层级结构展示,支持展开/收起 **数据需求**: ```typescript interface Edge { id: string; source: string; target: string; label: string; content: EdgeContent; } interface EdgeContent { id: string; title: string; children?: EdgeContent[]; data: Record; } ``` ### 4.3 右侧详情面板 (DetailPanel) **实现文件**: - 组件: `src/components/DetailPanel/DetailPanel.tsx` - 样式: `src/components/DetailPanel/DetailPanel.module.css` - 子组件: - `src/components/DetailPanel/NodeDetail.tsx` - `src/components/DetailPanel/EdgeDetail.tsx` **显示模式**: 1. **节点详情模式** - 显示节点的基本信息 - 显示节点的元数据 - 显示相关的边信息 2. **边详情模式** - 显示边的层级内容 - 使用树形或折叠面板组件展示层级结构 - 支持展开/收起操作 - 支持搜索和筛选 **数据结构**: ```typescript interface DetailPanelState { type: "node" | "edge" | null; data: Node | Edge | null; } ``` ## 五、数据交互规范 ### 5.1 HTTP 接口 **Base URL**: `http://localhost:8000` #### 5.1.1 获取 Trace 列表 **接口**: `GET /api/traces?status=running&limit=20` **用途**: 获取 trace_id 列表,顶部 title 显示选中 trace 的 task **查询参数**: | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `status` | string | 否 | 过滤状态:`running` / `completed` / `failed` | | `mode` | string | 否 | 过滤模式:`call` / `agent` | | `limit` | int | 否 | 返回数量(默认 50,最大 100)| **响应数据**: ```typescript interface TraceListResponse { traces: Array<{ trace_id: string; mode: string; task: string; // 任务描述,用于顶部标题显示 status: string; total_messages: number; total_tokens: number; total_cost: number; current_goal_id: string; created_at: string; }>; total: number; } ``` #### 5.1.2 获取 GoalTree 数据(渲染流程节点) **接口**: `GET /api/traces/{trace_id}` **用途**: 获取完整的 GoalTree 数据,根据 `goal_tree.goals` 渲染流程节点 **响应数据**: ```typescript interface TraceDetailResponse { trace_id: string; mode: string; task: string; status: string; total_messages: number; total_tokens: number; total_cost: number; created_at: string; completed_at: string | null; goal_tree: { mission: string; current_id: string | null; goals: Array; }; branches: Record; } ``` #### 5.1.3 获取 Messages 数据(渲染边的详细内容) **接口**: `GET /api/traces/{trace_id}/messages?goal_id={goal_id}` **用途**: 获取指定 Goal 关联的所有 Messages,用于渲染流程节点之间的边的详细执行内容 **响应数据**: ```typescript interface MessagesResponse { messages: Array; total: number; } ``` ### 5.2 WebSocket 实时通信 **连接地址**: `ws://localhost:8000/api/traces/{trace_id}/watch?since_event_id=0` **事件类型**: - `connected` - 连接成功 - `goal_added` - 新增节点 - `goal_updated` - 节点状态变化 - `message_added` - 新消息 - `trace_completed` - 任务完成 - `branch_started` - 分支开始 - `branch_completed` - 分支完成 ## 六、项目文件组织结构 ### 6.1 整体目录结构(React 版本) ``` src/ ├── components/ # 组件目录 │ ├── TopBar/ │ │ ├── TopBar.tsx # 顶部导航栏组件 │ │ ├── TopBar.module.css # 组件样式 │ │ └── types.ts # 类型定义 │ ├── MainContent/ │ │ ├── MainContent.tsx │ │ └── MainContent.module.css │ ├── FlowChart/ │ │ ├── FlowChart.tsx │ │ ├── FlowChart.module.css │ │ └── hooks/ │ │ └── useFlowChart.ts │ ├── DetailPanel/ │ │ ├── DetailPanel.tsx │ │ ├── DetailPanel.module.css │ │ ├── NodeDetail.tsx │ │ └── EdgeDetail.tsx │ └── common/ # 通用组件 │ ├── Loading.tsx │ └── ErrorBoundary.tsx │ ├── api/ # API 请求层(接口独立文件夹) │ ├── client.ts # HTTP 客户端配置 │ ├── websocket.ts # WebSocket 连接管理 │ ├── traceApi.ts # Trace 相关接口 │ ├── goalApi.ts # Goal 相关接口 │ └── messageApi.ts # Message 相关接口 │ ├── styles/ # 全局样式 │ ├── variables.css # CSS 变量 │ ├── reset.css # 样式重置 │ ├── global.css # 全局样式 │ └── themes/ # 主题 │ ├── light.css │ └── dark.css │ ├── hooks/ # 自定义 Hooks │ ├── useTrace.ts │ ├── useWebSocket.ts │ └── useTheme.ts │ ├── store/ # 状态管理 │ ├── index.ts │ ├── traceStore.ts │ └── uiStore.ts │ ├── utils/ # 工具函数 │ ├── dagGenerator.ts │ ├── pathCalculator.ts │ └── formatters.ts │ ├── types/ # TypeScript 类型定义 │ ├── trace.ts │ ├── goal.ts │ └── message.ts │ ├── App.tsx # 应用根组件 ├── main.tsx # 应用入口 └── vite-env.d.ts # Vite 类型声明 ``` ### 6.2 关键文件说明 **1. API 层(src/api/)** `src/api/traceApi.ts` - Trace 相关接口 ```typescript import { client } from "./client"; import type { TraceListResponse, TraceDetailResponse } from "@/types/trace"; export const traceApi = { // 获取 Trace 列表 async fetchTraces(params?: { status?: string; limit?: number }): Promise { const response = await client.get("/api/traces", { params }); return response.data; }, // 获取 Trace 详情 async fetchTraceDetail(traceId: string): Promise { const response = await client.get(`/api/traces/${traceId}`); return response.data; }, }; ``` `src/api/messageApi.ts` - Message 相关接口 ```typescript import { client } from "./client"; import type { MessagesResponse } from "@/types/message"; export const messageApi = { // 获取 Goal 的 Messages async fetchMessages(traceId: string, goalId: string): Promise { const response = await client.get(`/api/traces/${traceId}/messages`, { params: { goal_id: goalId } }); return response.data; }, }; ``` `src/api/client.ts` - HTTP 客户端配置 ```typescript import axios from "axios"; export const client = axios.create({ baseURL: "http://localhost:8000", timeout: 10000, headers: { "Content-Type": "application/json", }, }); // 请求拦截器 client.interceptors.request.use( (config) => { // 可以在这里添加 token 等 return config; }, (error) => { return Promise.reject(error); }, ); // 响应拦截器 client.interceptors.response.use( (response) => { return response; }, (error) => { console.error("API Error:", error); return Promise.reject(error); }, ); ``` **2. 组件层(src/components/)** `src/components/TopBar/TopBar.tsx` - 顶部导航栏组件 ```typescript import React, { useEffect, useState } from 'react'; import { traceApi } from '@/api/traceApi'; import styles from './TopBar.module.css'; interface TopBarProps { onTraceSelect: (traceId: string) => void; } export const TopBar: React.FC = ({ onTraceSelect }) => { const [traces, setTraces] = useState([]); const [selectedTraceId, setSelectedTraceId] = useState(''); const [title, setTitle] = useState('流程图可视化系统'); useEffect(() => { loadTraces(); }, []); const loadTraces = async () => { try { const data = await traceApi.fetchTraces({ status: 'running', limit: 20 }); setTraces(data.traces); } catch (error) { console.error('Failed to load traces:', error); } }; const handleTraceChange = (traceId: string) => { setSelectedTraceId(traceId); const trace = traces.find(t => t.trace_id === traceId); if (trace) { setTitle(trace.task); onTraceSelect(traceId); } }; return (

{title}

); }; ``` **3. 自定义 Hooks(src/hooks/)** `src/hooks/useTrace.ts` - Trace 数据管理 Hook ```typescript import { useState, useEffect } from "react"; import { traceApi } from "@/api/traceApi"; import type { TraceDetailResponse } from "@/types/trace"; export const useTrace = (traceId: string | null) => { const [trace, setTrace] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); useEffect(() => { if (!traceId) return; const loadTrace = async () => { setLoading(true); setError(null); try { const data = await traceApi.fetchTraceDetail(traceId); setTrace(data); } catch (err) { setError(err as Error); } finally { setLoading(false); } }; loadTrace(); }, [traceId]); return { trace, loading, error }; }; ``` `src/hooks/useWebSocket.ts` - WebSocket 连接 Hook ```typescript import { useEffect, useRef, useState } from "react"; interface UseWebSocketOptions { onMessage?: (data: any) => void; onError?: (error: Event) => void; onClose?: () => void; } export const useWebSocket = (traceId: string | null, options: UseWebSocketOptions = {}) => { const wsRef = useRef(null); const [connected, setConnected] = useState(false); useEffect(() => { if (!traceId) return; const url = `ws://localhost:8000/api/traces/${traceId}/watch?since_event_id=0`; const ws = new WebSocket(url); ws.onopen = () => { console.log("WebSocket connected"); setConnected(true); }; ws.onmessage = (event) => { const data = JSON.parse(event.data); options.onMessage?.(data); }; ws.onerror = (error) => { console.error("WebSocket error:", error); options.onError?.(error); }; ws.onclose = () => { console.log("WebSocket closed"); setConnected(false); options.onClose?.(); }; wsRef.current = ws; return () => { ws.close(); }; }, [traceId]); return { connected, ws: wsRef.current }; }; ``` **4. 应用入口(src/App.tsx)** ```typescript import React, { useState } from 'react'; import { TopBar } from './components/TopBar/TopBar'; import { MainContent } from './components/MainContent/MainContent'; import { DetailPanel } from './components/DetailPanel/DetailPanel'; import { useTrace } from './hooks/useTrace'; import { useWebSocket } from './hooks/useWebSocket'; import './styles/global.css'; function App() { const [selectedTraceId, setSelectedTraceId] = useState(null); const [selectedNode, setSelectedNode] = useState(null); const [selectedEdge, setSelectedEdge] = useState(null); const { trace, loading } = useTrace(selectedTraceId); useWebSocket(selectedTraceId, { onMessage: (data) => { console.log('WebSocket message:', data); // 处理实时更新 } }); return (
{(selectedNode || selectedEdge) && ( { setSelectedNode(null); setSelectedEdge(null); }} /> )}
); } export default App; ``` ### 6.3 模块化原则 **关注点分离**: - ✅ API 请求逻辑 → `src/api/` 目录 - ✅ 组件 UI 结构 → `src/components/` 目录 - ✅ 样式定义 → `src/styles/` 目录(全局)+ 组件内 `.module.css` - ✅ 主题配置 → `src/styles/themes/` 目录 - ✅ 业务逻辑 → `src/hooks/` 和 `src/store/` 目录 - ✅ 类型定义 → `src/types/` 目录 **禁止做法**: - ❌ 在组件文件中直接写 API 请求(应导入 api 层函数) - ❌ 在组件文件中写大量内联样式(应使用 CSS Modules) - ❌ 在样式文件中混入业务逻辑 - ❌ 主题样式和组件样式混在一起 **推荐做法**: - ✅ 使用 TypeScript 提供类型安全 - ✅ 使用自定义 Hooks 封装业务逻辑 - ✅ 使用 CSS Modules 避免样式冲突 - ✅ 使用状态管理库管理全局状态 - ✅ API 请求统一通过 `src/api/` 层调用 ## 七、样式规范 ### 7.1 CSS 变量 `src/styles/variables.css` ```css :root { /* 颜色 */ --color-primary: #0070f3; --color-success: #00c853; --color-warning: #ff9800; --color-error: #f44336; /* 背景色 */ --bg-primary: #ffffff; --bg-secondary: #f5f5f5; /* 文字颜色 */ --text-primary: #333333; --text-secondary: #666666; /* 间距 */ --spacing-xs: 4px; --spacing-sm: 8px; --spacing-md: 16px; --spacing-lg: 24px; /* 字体 */ --font-size-sm: 12px; --font-size-md: 14px; --font-size-lg: 16px; } ``` ### 7.2 组件样式 使用 CSS Modules 避免样式冲突: `src/components/TopBar/TopBar.module.css` ```css .topbar { height: 60px; background: var(--bg-primary); border-bottom: 1px solid var(--border-color); display: flex; align-items: center; justify-content: space-between; padding: 0 var(--spacing-lg); } .title h1 { font-size: var(--font-size-lg); font-weight: 600; color: var(--text-primary); margin: 0; } .filters { display: flex; gap: var(--spacing-md); } ``` ## 八、开发优先级 ### P0 (核心功能) - [ ] 基础框架搭建(TopBar + MainContent + DetailPanel) - [ ] 流程图基础渲染 - [ ] 节点点击交互 - [ ] HTTP 数据获取 ### P1 (重要功能) - [ ] 边点击交互 - [ ] 层级内容展开/收起 - [ ] 筛选功能 - [ ] WebSocket 实时更新 ### P2 (优化功能) - [ ] 动画效果优化 - [ ] 性能优化 - [ ] 主题切换 - [ ] 响应式布局 ## 九、技术实现建议 ### 9.1 状态管理 使用 Zustand 进行状态管理: ```typescript import create from "zustand"; interface AppState { currentTraceId: string | null; trace: TraceDetailResponse | null; selectedNode: Node | null; selectedEdge: Edge | null; setCurrentTraceId: (id: string) => void; setTrace: (trace: TraceDetailResponse) => void; setSelectedNode: (node: Node | null) => void; setSelectedEdge: (edge: Edge | null) => void; } export const useAppStore = create((set) => ({ currentTraceId: null, trace: null, selectedNode: null, selectedEdge: null, setCurrentTraceId: (id) => set({ currentTraceId: id }), setTrace: (trace) => set({ trace }), setSelectedNode: (node) => set({ selectedNode: node }), setSelectedEdge: (edge) => set({ selectedEdge: edge }), })); ``` ### 9.2 性能优化 1. **React.memo**: 对不经常变化的组件使用 memo 2. **useMemo/useCallback**: 缓存计算结果和回调函数 3. **虚拟滚动**: 大量节点时使用虚拟滚动 4. **懒加载**: 按需加载组件和数据 ## 十、测试要点 1. **单元测试**: 使用 Vitest 测试组件和工具函数 2. **集成测试**: 测试组件间的交互 3. **E2E 测试**: 使用 Playwright 测试完整流程 4. **性能测试**: 测试大数据量下的渲染性能 ## 十一、部署说明 1. 使用 Vite 构建生产版本:`npm run build` 2. 配置环境变量(API 地址、WebSocket 地址等) 3. 部署到静态服务器或 CDN 4. 配置 WebSocket 代理(如果需要) ## 十二、FlowChart 核心实现指南 ### 12.1 渲染方案选择:React + D3 Hybrid **推荐方案**: 使用 React 控制 SVG 渲染,D3.js 仅用于布局计算 **技术架构**: - **D3.js**: 仅用于数据处理和布局计算(`d3.tree()`, `d3.hierarchy()`) - **React**: 完全控制 SVG DOM 渲染和更新 - **优势**: - 避免 D3 直接操作 DOM 与 React 虚拟 DOM 冲突 - 充分利用 React 的状态管理和组件化 - 更易于维护和测试 - 完美支持 WebSocket 实时更新 ### 12.2 FlowChart 组件实现 `src/components/FlowChart/FlowChart.tsx` ```typescript import React, { useMemo, useRef, useEffect, useState } from 'react'; import * as d3 from 'd3'; import styles from './FlowChart.module.css'; import type { Goal } from '@/types/goal'; interface FlowChartProps { goals: Goal[]; onNodeClick?: (node: Goal) => void; onEdgeClick?: (edge: Edge) => void; } interface LayoutNode extends d3.HierarchyPointNode { x: number; y: number; } export const FlowChart: React.FC = ({ goals, onNodeClick, onEdgeClick }) => { const svgRef = useRef(null); const [dimensions, setDimensions] = useState({ width: 1200, height: 800 }); const [selectedNodeId, setSelectedNodeId] = useState(null); // 使用 D3 计算布局 const layoutData = useMemo(() => { if (!goals || goals.length === 0) return null; // 构建层级数据结构 const root = buildHierarchy(goals); // 使用 d3.tree() 计算布局 const treeLayout = d3.tree() .size([dimensions.height - 100, dimensions.width - 200]) .separation((a, b) => (a.parent === b.parent ? 1.2 : 1.5)); const treeData = treeLayout(root); return { nodes: treeData.descendants() as LayoutNode[], links: treeData.links() }; }, [goals, dimensions]); // 处理节点点击 const handleNodeClick = (node: LayoutNode) => { setSelectedNodeId(node.data.id); onNodeClick?.(node.data); }; // 处理边点击 const handleEdgeClick = (link: any) => { const edge = { id: `${link.source.data.id}-${link.target.data.id}`, source: link.source.data.id, target: link.target.data.id, data: link.target.data }; onEdgeClick?.(edge); }; if (!layoutData) return
Loading...
; return (
{/* 定义箭头标记 */} {/* 渲染连接线(边) */} {layoutData.links.map((link, index) => ( handleEdgeClick(link)} /> ))} {/* 渲染节点 */} {layoutData.nodes.map((node) => ( handleNodeClick(node)} /> ))}
); }; // 构建层级数据结构 function buildHierarchy(goals: Goal[]): d3.HierarchyNode { const goalsMap = new Map(goals.map(g => [g.id, g])); const root = goals.find(g => !g.parent_id); if (!root) throw new Error('No root goal found'); function buildTree(goal: Goal): any { const children = goals .filter(g => g.parent_id === goal.id) .map(child => buildTree(child)); return { ...goal, children: children.length > 0 ? children : undefined }; } return d3.hierarchy(buildTree(root)); } ``` ### 12.3 箭头样式实现(基于 design.png) `src/components/FlowChart/ArrowMarkers.tsx` ```typescript import React from 'react'; export const ArrowMarkers: React.FC = () => { return ( <> {/* 默认箭头 - 蓝色 */} {/* 成功状态箭头 - 绿色 */} {/* 失败状态箭头 - 红色 */} {/* 运行中状态箭头 - 橙色 */} {/* 选中状态箭头 - 高亮蓝色 */} ); }; ``` ### 12.4 边(Edge)组件实现 - Bezier 曲线 `src/components/FlowChart/Edge.tsx` ```typescript import React from 'react'; import styles from './Edge.module.css'; interface EdgeProps { link: any; selected: boolean; onClick: () => void; } export const Edge: React.FC = ({ link, selected, onClick }) => { const { source, target } = link; // 计算 Bezier 曲线路径(垂直布局) const createPath = () => { const sourceX = source.y; const sourceY = source.x; const targetX = target.y; const targetY = target.x; // 使用三次 Bezier 曲线创建平滑连接 const midY = (sourceY + targetY) / 2; return `M${sourceX},${sourceY} C${sourceX},${midY} ${targetX},${midY} ${targetX},${targetY}`; }; // 根据目标节点状态选择箭头样式 const getMarkerUrl = () => { if (selected) return 'url(#arrow-selected)'; const status = target.data.status; switch (status) { case 'completed': return 'url(#arrow-success)'; case 'failed': return 'url(#arrow-error)'; case 'running': return 'url(#arrow-running)'; default: return 'url(#arrow-default)'; } }; // 根据状态选择线条颜色 const getStrokeColor = () => { if (selected) return '#0070f3'; const status = target.data.status; switch (status) { case 'completed': return '#00c853'; case 'failed': return '#f44336'; case 'running': return '#ff9800'; default: return '#4e79a7'; } }; return ( {/* 透明的宽路径用于点击检测 */} {/* 实际显示的路径 */} ); }; ``` ### 12.5 节点(Node)组件实现 `src/components/FlowChart/Node.tsx` ```typescript import React from 'react'; import styles from './Node.module.css'; interface NodeProps { node: any; selected: boolean; onClick: () => void; } export const Node: React.FC = ({ node, selected, onClick }) => { const { x, y, data } = node; // 根据状态选择节点颜色 const getNodeColor = () => { switch (data.status) { case 'completed': return '#e8f5e9'; case 'failed': return '#ffebee'; case 'running': return '#fff3e0'; default: return '#e3f2fd'; } }; const getBorderColor = () => { if (selected) return '#0070f3'; switch (data.status) { case 'completed': return '#00c853'; case 'failed': return '#f44336'; case 'running': return '#ff9800'; default: return '#4e79a7'; } }; return ( {/* 节点背景 */} {/* 节点文本 */} {truncateText(data.description || data.id, 12)} {/* 状态指示器 */} {data.status === 'running' && ( )} ); }; function truncateText(text: string, maxLength: number): string { return text.length > maxLength ? text.slice(0, maxLength) + '...' : text; } ``` ### 12.6 WebSocket 实时更新实现 `src/components/FlowChart/useFlowChartData.ts` ```typescript import { useState, useEffect, useCallback } from "react"; import { useWebSocket } from "@/hooks/useWebSocket"; import type { Goal } from "@/types/goal"; export const useFlowChartData = (traceId: string | null) => { const [goals, setGoals] = useState([]); // 处理 WebSocket 消息 const handleWebSocketMessage = useCallback((event: any) => { const { type, data } = event; switch (type) { case "goal_added": // 新增节点 - 动态添加到图中 setGoals((prev) => [...prev, data.goal]); break; case "goal_updated": // 更新节点状态 - 触发重新渲染 setGoals((prev) => prev.map((g) => (g.id === data.goal.id ? { ...g, ...data.goal } : g))); break; case "message_added": // 新消息添加 - 可能需要更新边的数据 // 这里可以触发边的高亮或动画效果 console.log("New message:", data.message); break; case "trace_completed": // 任务完成 - 可以显示完成状态 console.log("Trace completed"); break; default: console.log("Unknown event type:", type); } }, []); // 建立 WebSocket 连接 useWebSocket(traceId, { onMessage: handleWebSocketMessage, }); return { goals, setGoals }; }; ``` ### 12.7 样式定义 `src/components/FlowChart/FlowChart.module.css` ```css .container { width: 100%; height: 100%; overflow: auto; background: #fafafa; } .svg { display: block; margin: 0 auto; } .links { pointer-events: none; } .links path { pointer-events: stroke; } .nodes { pointer-events: all; } /* 边的样式 */ .edge path { transition: stroke-width 0.3s ease, opacity 0.3s ease; } .edge.selected path { stroke-width: 3px; opacity: 1; } /* 节点的样式 */ .node rect { transition: all 0.3s ease; } .node.selected rect { filter: drop-shadow(0 4px 8px rgba(0, 112, 243, 0.3)); } /* 运行中状态的脉冲动画 */ @keyframes pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.5; transform: scale(1.2); } } .pulse { animation: pulse 2s ease-in-out infinite; } ``` ### 12.8 完整使用示例 `src/components/MainContent/MainContent.tsx` ```typescript import React, { useEffect } from 'react'; import { FlowChart } from '../FlowChart/FlowChart'; import { useFlowChartData } from '../FlowChart/useFlowChartData'; import { traceApi } from '@/api/traceApi'; import styles from './MainContent.module.css'; interface MainContentProps { traceId: string | null; onNodeClick?: (node: any) => void; onEdgeClick?: (edge: any) => void; } export const MainContent: React.FC = ({ traceId, onNodeClick, onEdgeClick }) => { const { goals, setGoals } = useFlowChartData(traceId); // 初始加载数据 useEffect(() => { if (!traceId) return; const loadInitialData = async () => { try { const trace = await traceApi.fetchTraceDetail(traceId); setGoals(trace.goal_tree.goals); } catch (error) { console.error('Failed to load trace data:', error); } }; loadInitialData(); }, [traceId]); if (!traceId) { return (

请选择一个 Trace 查看流程图

); } return (
); }; ``` ### 12.9 关键技术要点总结 **1. React + D3 Hybrid 模式**: - D3 仅用于布局计算(`d3.tree()`, `d3.hierarchy()`) - React 完全控制 SVG 渲染 - 避免 DOM 操作冲突 **2. 实时更新策略**: - WebSocket 接收事件 → 更新 React state - State 变化 → 触发 useMemo 重新计算布局 - 布局变化 → React 自动重新渲染 SVG **3. 箭头样式设计**: - 使用 SVG `` 定义箭头 - 根据节点状态动态选择箭头颜色 - Bezier 曲线创建平滑连接线 - 支持选中状态的高亮效果 **4. 性能优化**: - 使用 `useMemo` 缓存布局计算结果 - 使用 `useCallback` 避免不必要的重新渲染 - 透明宽路径提升点击体验 - CSS transition 实现平滑动画 **5. 交互体验**: - 节点/边点击高亮 - 状态颜色编码(绿色=完成,红色=失败,橙色=运行中) - 运行中节点的脉冲动画 - 选中状态的阴影效果