MainContent.tsx 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. import { useRef, useState, useEffect } from "react";
  2. import type { FC } from "react";
  3. import { Select } from "@douyinfe/semi-ui";
  4. import { FlowChart } from "../FlowChart/FlowChart";
  5. import type { FlowChartRef } from "../FlowChart/FlowChart";
  6. import { useFlowChartData } from "../FlowChart/hooks/useFlowChartData";
  7. import { traceApi } from "../../api/traceApi";
  8. import type { Goal } from "../../types/goal";
  9. import type { Edge, Message } from "../../types/message";
  10. import type { TraceListItem } from "../../types/trace";
  11. import styles from "./MainContent.module.css";
  12. interface MainContentProps {
  13. traceId: string | null;
  14. onNodeClick?: (node: Goal | Message, edge?: Edge) => void;
  15. onTraceChange?: (traceId: string, title?: string) => void;
  16. refreshTrigger?: number;
  17. messageRefreshTrigger?: number;
  18. }
  19. export const MainContent: FC<MainContentProps> = ({
  20. traceId,
  21. onNodeClick,
  22. onTraceChange,
  23. refreshTrigger,
  24. messageRefreshTrigger,
  25. }) => {
  26. const flowChartRef = useRef<FlowChartRef>(null);
  27. const [isAllExpanded, setIsAllExpanded] = useState(true);
  28. const [traceList, setTraceList] = useState<TraceListItem[]>([]);
  29. const [cachedGoals, setCachedGoals] = useState<Goal[]>([]);
  30. const [cachedMsgGroups, setCachedMsgGroups] = useState<Record<string, Message[]>>({});
  31. const [cachedInvalidBranches, setCachedInvalidBranches] = useState<Message[][]>([]);
  32. const { goals, connected, msgGroups, reloading, invalidBranches } = useFlowChartData(traceId, messageRefreshTrigger);
  33. console.log("%c [ msgGroups ]-34", "font-size:13px; background:pink; color:#bf2c9f;", msgGroups);
  34. const displayGoals = goals.length > 0 ? goals : cachedGoals;
  35. const displayMsgGroups = Object.keys(msgGroups).length > 0 ? msgGroups : cachedMsgGroups;
  36. const displayInvalidBranches =
  37. invalidBranches && invalidBranches.length > 0 ? invalidBranches : cachedInvalidBranches;
  38. useEffect(() => {
  39. const fetchTraces = async () => {
  40. try {
  41. const data = await traceApi.fetchTraces({ limit: 100 });
  42. setTraceList(data.traces);
  43. } catch (error) {
  44. console.error("Failed to load traces:", error);
  45. }
  46. };
  47. fetchTraces();
  48. }, [refreshTrigger]);
  49. useEffect(() => {
  50. // 移除 reload 调用,因为 useFlowChartData 内部会监听 messageRefreshTrigger 并重新加载
  51. }, [messageRefreshTrigger]);
  52. useEffect(() => {
  53. if (goals.length > 0) {
  54. setCachedGoals(goals);
  55. }
  56. }, [goals]);
  57. useEffect(() => {
  58. if (Object.keys(msgGroups).length > 0) {
  59. setCachedMsgGroups(msgGroups);
  60. }
  61. }, [msgGroups]);
  62. useEffect(() => {
  63. if (invalidBranches && invalidBranches.length > 0) {
  64. setCachedInvalidBranches(invalidBranches);
  65. }
  66. }, [invalidBranches]);
  67. useEffect(() => {
  68. setCachedGoals([]);
  69. setCachedMsgGroups({});
  70. setCachedInvalidBranches([]);
  71. }, [traceId]);
  72. if (!traceId && !reloading) {
  73. return (
  74. <div className={styles.main}>
  75. <div className={styles.header}>
  76. <div className={styles.title}>暂无 Trace</div>
  77. <div className={styles.status}>未连接</div>
  78. </div>
  79. <div className={styles.content}>
  80. <div className={styles.empty}>暂无可展示的数据</div>
  81. </div>
  82. </div>
  83. );
  84. }
  85. return (
  86. <div className={styles.main}>
  87. <div className={styles.header}>
  88. <div className={styles.title}>{connected ? "WebSocket 已连接" : "WebSocket 未连接"}</div>
  89. <div className={styles.headerRight}>
  90. <Select
  91. value={traceId}
  92. onChange={(value: unknown) => {
  93. const trace = traceList.find((t) => t.trace_id === value);
  94. onTraceChange?.(value as string, trace?.task || trace?.trace_id);
  95. }}
  96. style={{ width: 200 }}
  97. placeholder="选择 Trace"
  98. optionList={traceList.map((t) => ({
  99. label: t.task?.length > 15 ? `${t.task.slice(0, 15)}...` : t.task || t.trace_id,
  100. value: t.trace_id,
  101. }))}
  102. />
  103. {/* <div className={styles.status}>{connected ? "WebSocket 已连接" : "WebSocket 未连接"}</div> */}
  104. {/* <div className={styles.legend}>
  105. <div className={styles.legendItem}>
  106. <span
  107. className={styles.legendDot}
  108. style={{ background: "#00c853" }}
  109. />
  110. 已完成
  111. </div>
  112. <div className={styles.legendItem}>
  113. <span
  114. className={styles.legendDot}
  115. style={{ background: "#f44336" }}
  116. />
  117. 失败
  118. </div>
  119. <div className={styles.legendItem}>
  120. <span
  121. className={styles.legendDot}
  122. style={{ background: "#ff9800" }}
  123. />
  124. 运行中
  125. </div>
  126. <div className={styles.legendItem}>
  127. <span
  128. className={styles.legendDot}
  129. style={{ background: "#4e79a7" }}
  130. />
  131. 默认
  132. </div>
  133. </div> */}
  134. <div className={styles.buttons}>
  135. <button
  136. className={styles.btn}
  137. onClick={() => {
  138. if (isAllExpanded) {
  139. flowChartRef.current?.collapseAll();
  140. } else {
  141. flowChartRef.current?.expandAll();
  142. }
  143. setIsAllExpanded(!isAllExpanded);
  144. }}
  145. >
  146. {isAllExpanded ? "全折叠" : "全展开"}
  147. </button>
  148. </div>
  149. </div>
  150. </div>
  151. <div className={styles.content}>
  152. {reloading ? (
  153. <div className={styles.loading}>加载中...</div>
  154. ) : displayGoals.length === 0 ? (
  155. <div className={styles.empty}>暂无数据</div>
  156. ) : (
  157. <FlowChart
  158. ref={flowChartRef}
  159. goals={displayGoals}
  160. msgGroups={displayMsgGroups}
  161. invalidBranches={displayInvalidBranches}
  162. onNodeClick={onNodeClick}
  163. onSubTraceClick={(_parentGoal, entry) => {
  164. onTraceChange?.(entry.id, entry.mission || entry.id);
  165. }}
  166. />
  167. )}
  168. </div>
  169. </div>
  170. );
  171. };