import { useEffect, useMemo, useRef, useState } from "react"; import { TopBar } from "./components/TopBar/TopBar"; import { MainContent } from "./components/MainContent/MainContent"; import { DetailPanel } from "./components/DetailPanel/DetailPanel"; import type { Goal } from "./types/goal"; import type { Edge, Message } from "./types/message"; import { useFlowChartData } from "./components/FlowChart/hooks/useFlowChartData"; import "./styles/global.css"; function App() { const [selectedTraceId, setSelectedTraceId] = useState(null); const [selectedTraceTitle, setSelectedTraceTitle] = useState("流程图可视化系统"); const [selectedNode, setSelectedNode] = useState(null); const [selectedEdge, setSelectedEdge] = useState(null); const [rightWidth, setRightWidth] = useState(360); const [isDragging, setIsDragging] = useState(false); const [refreshTrigger, setRefreshTrigger] = useState(0); const [messageRefreshTrigger, setMessageRefreshTrigger] = useState(0); const bodyRef = useRef(null); // 获取数据以传递给 DetailPanel const { msgGroups } = useFlowChartData(selectedTraceId, messageRefreshTrigger); const handleNodeClick = (node: Goal | Message, edge?: Edge) => { setSelectedNode(node); setSelectedEdge(edge || null); }; const handleCloseDetail = () => { setSelectedNode(null); setSelectedEdge(null); }; const isGoalNode = (node: Goal | Message): node is Goal => "status" in node && "created_at" in node; // 根据选中的节点获取对应的消息 const selectedMessages = useMemo(() => { if (selectedNode) { if (isGoalNode(selectedNode)) { return msgGroups[selectedNode.id] || []; } return [selectedNode as Message]; } // 如果点击的是边,且该边是主链上的边,通常我们显示源节点的消息(如果需要) // 但根据用户需求 "边里面的信息,显示对应msgGroup里面的所有的子集的描述" // 如果 selectedEdge 存在且 selectedNode 为空(虽然目前逻辑是联动),这里可以做处理 // 目前 handleNodeClick 会设置 selectedNode 为 link.source // 所以 selectedNode.id 就是边的源节点 ID,直接取 msgGroups 即可 return []; }, [selectedNode, msgGroups]); useEffect(() => { if (!isDragging) return; const handleMove = (event: MouseEvent) => { const rect = bodyRef.current?.getBoundingClientRect(); if (!rect) return; const next = rect.right - event.clientX; const clamped = Math.min(800, Math.max(240, next)); setRightWidth(clamped); }; const handleUp = () => { setIsDragging(false); }; window.addEventListener("mousemove", handleMove); window.addEventListener("mouseup", handleUp); return () => { window.removeEventListener("mousemove", handleMove); window.removeEventListener("mouseup", handleUp); }; }, [isDragging]); return (
{ setSelectedTraceId(id); if (title) setSelectedTraceTitle(title); }} onTraceCreated={() => setRefreshTrigger((t) => t + 1)} onMessageInserted={() => setMessageRefreshTrigger((t) => t + 1)} />
{ setSelectedTraceId(id); if (title) setSelectedTraceTitle(title); }} refreshTrigger={refreshTrigger} messageRefreshTrigger={messageRefreshTrigger} />
{(selectedNode || selectedEdge) && ( <>
setIsDragging(true)} role="separator" aria-orientation="vertical" aria-valuenow={rightWidth} aria-valuemin={240} aria-valuemax={800} />
)}
); } export default App;