| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149 |
- 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 { Terminal } from "./components/Terminal/Terminal";
- 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<string | null>(null);
- const [selectedTraceTitle, setSelectedTraceTitle] = useState("流程图可视化系统");
- const [selectedNode, setSelectedNode] = useState<Goal | Message | null>(null);
- const [selectedEdge, setSelectedEdge] = useState<Edge | null>(null);
- const [rightWidth, setRightWidth] = useState(360);
- const [isDragging, setIsDragging] = useState(false);
- const [showTerminal, setShowTerminal] = useState(false);
- const [refreshTrigger, setRefreshTrigger] = useState(0);
- const [messageRefreshTrigger, setMessageRefreshTrigger] = useState(0);
- const bodyRef = useRef<HTMLDivElement | null>(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 (
- <div className="app">
- <div className="app-top">
- <TopBar
- selectedTraceId={selectedTraceId}
- selectedNode={selectedNode}
- title={selectedTraceTitle}
- onTraceSelect={(id, title) => {
- setSelectedTraceId(id);
- if (title) setSelectedTraceTitle(title);
- }}
- onTraceCreated={() => setRefreshTrigger((t) => t + 1)}
- onMessageInserted={() => setMessageRefreshTrigger((t) => t + 1)}
- />
- </div>
- <div
- className="app-body"
- ref={bodyRef}
- style={{ userSelect: isDragging ? "none" : "auto" }}
- >
- <div className="app-main">
- <MainContent
- traceId={selectedTraceId}
- onNodeClick={handleNodeClick}
- onTraceChange={(id, title) => {
- setSelectedTraceId(id);
- if (title) setSelectedTraceTitle(title);
- }}
- refreshTrigger={refreshTrigger}
- messageRefreshTrigger={messageRefreshTrigger}
- />
- </div>
- {(selectedNode || selectedEdge) && (
- <>
- <div
- className="app-splitter"
- onMouseDown={() => setIsDragging(true)}
- role="separator"
- aria-orientation="vertical"
- aria-valuenow={rightWidth}
- aria-valuemin={240}
- aria-valuemax={800}
- />
- <div
- className="app-right"
- style={{ width: rightWidth }}
- >
- <DetailPanel
- node={selectedNode}
- edge={selectedEdge}
- messages={selectedMessages as Message[]}
- onClose={handleCloseDetail}
- />
- </div>
- </>
- )}
- </div>
- {showTerminal && (
- <div className="app-terminal-float">
- <Terminal onClose={() => setShowTerminal(false)} />
- </div>
- )}
- {!showTerminal && (
- <button
- className="app-terminal-toggle"
- onClick={() => setShowTerminal(true)}
- title="打开控制台"
- >
- ▶ 控制台
- </button>
- )}
- </div>
- );
- }
- export default App;
|