App.tsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import { useEffect, useMemo, useRef, useState } from "react";
  2. import { TopBar } from "./components/TopBar/TopBar";
  3. import { MainContent } from "./components/MainContent/MainContent";
  4. import { DetailPanel } from "./components/DetailPanel/DetailPanel";
  5. import type { Goal } from "./types/goal";
  6. import type { Edge, Message } from "./types/message";
  7. import { useFlowChartData } from "./components/FlowChart/hooks/useFlowChartData";
  8. import "./styles/global.css";
  9. function App() {
  10. const [selectedTraceId, setSelectedTraceId] = useState<string | null>(null);
  11. const [selectedTraceTitle, setSelectedTraceTitle] = useState("流程图可视化系统");
  12. const [selectedNode, setSelectedNode] = useState<Goal | Message | null>(null);
  13. const [selectedEdge, setSelectedEdge] = useState<Edge | null>(null);
  14. const [rightWidth, setRightWidth] = useState(360);
  15. const [isDragging, setIsDragging] = useState(false);
  16. const [refreshTrigger, setRefreshTrigger] = useState(0);
  17. const [messageRefreshTrigger, setMessageRefreshTrigger] = useState(0);
  18. const bodyRef = useRef<HTMLDivElement | null>(null);
  19. // 获取数据以传递给 DetailPanel
  20. const { msgGroups } = useFlowChartData(selectedTraceId, messageRefreshTrigger);
  21. const handleNodeClick = (node: Goal | Message, edge?: Edge) => {
  22. setSelectedNode(node);
  23. setSelectedEdge(edge || null);
  24. };
  25. const handleCloseDetail = () => {
  26. setSelectedNode(null);
  27. setSelectedEdge(null);
  28. };
  29. const isGoalNode = (node: Goal | Message): node is Goal => "status" in node && "created_at" in node;
  30. // 根据选中的节点获取对应的消息
  31. const selectedMessages = useMemo(() => {
  32. if (selectedNode) {
  33. if (isGoalNode(selectedNode)) {
  34. return msgGroups[selectedNode.id] || [];
  35. }
  36. return [selectedNode as Message];
  37. }
  38. // 如果点击的是边,且该边是主链上的边,通常我们显示源节点的消息(如果需要)
  39. // 但根据用户需求 "边里面的信息,显示对应msgGroup里面的所有的子集的描述"
  40. // 如果 selectedEdge 存在且 selectedNode 为空(虽然目前逻辑是联动),这里可以做处理
  41. // 目前 handleNodeClick 会设置 selectedNode 为 link.source
  42. // 所以 selectedNode.id 就是边的源节点 ID,直接取 msgGroups 即可
  43. return [];
  44. }, [selectedNode, msgGroups]);
  45. useEffect(() => {
  46. if (!isDragging) return;
  47. const handleMove = (event: MouseEvent) => {
  48. const rect = bodyRef.current?.getBoundingClientRect();
  49. if (!rect) return;
  50. const next = rect.right - event.clientX;
  51. const clamped = Math.min(800, Math.max(240, next));
  52. setRightWidth(clamped);
  53. };
  54. const handleUp = () => {
  55. setIsDragging(false);
  56. };
  57. window.addEventListener("mousemove", handleMove);
  58. window.addEventListener("mouseup", handleUp);
  59. return () => {
  60. window.removeEventListener("mousemove", handleMove);
  61. window.removeEventListener("mouseup", handleUp);
  62. };
  63. }, [isDragging]);
  64. return (
  65. <div className="app">
  66. <div className="app-top">
  67. <TopBar
  68. selectedTraceId={selectedTraceId}
  69. selectedNode={selectedNode}
  70. title={selectedTraceTitle}
  71. onTraceSelect={(id, title) => {
  72. setSelectedTraceId(id);
  73. if (title) setSelectedTraceTitle(title);
  74. }}
  75. onTraceCreated={() => setRefreshTrigger((t) => t + 1)}
  76. onMessageInserted={() => setMessageRefreshTrigger((t) => t + 1)}
  77. />
  78. </div>
  79. <div
  80. className="app-body"
  81. ref={bodyRef}
  82. style={{ userSelect: isDragging ? "none" : "auto" }}
  83. >
  84. <div className="app-main">
  85. <MainContent
  86. traceId={selectedTraceId}
  87. onNodeClick={handleNodeClick}
  88. onTraceChange={(id, title) => {
  89. setSelectedTraceId(id);
  90. if (title) setSelectedTraceTitle(title);
  91. }}
  92. refreshTrigger={refreshTrigger}
  93. messageRefreshTrigger={messageRefreshTrigger}
  94. />
  95. </div>
  96. {(selectedNode || selectedEdge) && (
  97. <>
  98. <div
  99. className="app-splitter"
  100. onMouseDown={() => setIsDragging(true)}
  101. role="separator"
  102. aria-orientation="vertical"
  103. aria-valuenow={rightWidth}
  104. aria-valuemin={240}
  105. aria-valuemax={800}
  106. />
  107. <div
  108. className="app-right"
  109. style={{ width: rightWidth }}
  110. >
  111. <DetailPanel
  112. node={selectedNode}
  113. edge={selectedEdge}
  114. messages={selectedMessages as Message[]}
  115. onClose={handleCloseDetail}
  116. />
  117. </div>
  118. </>
  119. )}
  120. </div>
  121. </div>
  122. );
  123. }
  124. export default App;