Просмотр исходного кода

Merge remote-tracking branch 'refs/remotes/origin/main'

Talegorithm 1 неделя назад
Родитель
Сommit
1925f20340

+ 39 - 1
frontend/react-template/package-lock.json

@@ -13,7 +13,8 @@
         "axios": "^1.6.0",
         "axios": "^1.6.0",
         "d3": "^7.8.5",
         "d3": "^7.8.5",
         "react": "^18.2.0",
         "react": "^18.2.0",
-        "react-dom": "^18.2.0"
+        "react-dom": "^18.2.0",
+        "react-markdown": "^10.1.0"
       },
       },
       "devDependencies": {
       "devDependencies": {
         "@tailwindcss/postcss": "^4.0.0",
         "@tailwindcss/postcss": "^4.0.0",
@@ -5099,6 +5100,16 @@
         "url": "https://opencollective.com/unified"
         "url": "https://opencollective.com/unified"
       }
       }
     },
     },
+    "node_modules/html-url-attributes": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmmirror.com/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
+      "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==",
+      "license": "MIT",
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
     "node_modules/iconv-lite": {
     "node_modules/iconv-lite": {
       "version": "0.6.3",
       "version": "0.6.3",
       "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz",
       "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz",
@@ -7458,6 +7469,33 @@
       "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
       "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
       "license": "MIT"
       "license": "MIT"
     },
     },
+    "node_modules/react-markdown": {
+      "version": "10.1.0",
+      "resolved": "https://registry.npmmirror.com/react-markdown/-/react-markdown-10.1.0.tgz",
+      "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/hast": "^3.0.0",
+        "@types/mdast": "^4.0.0",
+        "devlop": "^1.0.0",
+        "hast-util-to-jsx-runtime": "^2.0.0",
+        "html-url-attributes": "^3.0.0",
+        "mdast-util-to-hast": "^13.0.0",
+        "remark-parse": "^11.0.0",
+        "remark-rehype": "^11.0.0",
+        "unified": "^11.0.0",
+        "unist-util-visit": "^5.0.0",
+        "vfile": "^6.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      },
+      "peerDependencies": {
+        "@types/react": ">=18",
+        "react": ">=18"
+      }
+    },
     "node_modules/react-refresh": {
     "node_modules/react-refresh": {
       "version": "0.17.0",
       "version": "0.17.0",
       "resolved": "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.17.0.tgz",
       "resolved": "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.17.0.tgz",

+ 7 - 6
frontend/react-template/package.json

@@ -12,18 +12,19 @@
   "dependencies": {
   "dependencies": {
     "@douyinfe/semi-icons": "^2.56.0",
     "@douyinfe/semi-icons": "^2.56.0",
     "@douyinfe/semi-ui": "^2.56.0",
     "@douyinfe/semi-ui": "^2.56.0",
+    "axios": "^1.6.0",
+    "d3": "^7.8.5",
     "react": "^18.2.0",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
     "react-dom": "^18.2.0",
-    "axios": "^1.6.0",
-    "d3": "^7.8.5"
+    "react-markdown": "^10.1.0"
   },
   },
   "devDependencies": {
   "devDependencies": {
-    "@types/react": "^18.2.43",
-    "@types/react-dom": "^18.2.17",
-    "@types/d3": "^7.4.3",
-    "@types/node": "^20.11.5",
     "@tailwindcss/postcss": "^4.0.0",
     "@tailwindcss/postcss": "^4.0.0",
     "@tailwindcss/vite": "^4.0.0",
     "@tailwindcss/vite": "^4.0.0",
+    "@types/d3": "^7.4.3",
+    "@types/node": "^20.11.5",
+    "@types/react": "^18.2.43",
+    "@types/react-dom": "^18.2.17",
     "@typescript-eslint/eslint-plugin": "^6.14.0",
     "@typescript-eslint/eslint-plugin": "^6.14.0",
     "@typescript-eslint/parser": "^6.14.0",
     "@typescript-eslint/parser": "^6.14.0",
     "@vitejs/plugin-react": "^4.2.1",
     "@vitejs/plugin-react": "^4.2.1",

+ 10 - 2
frontend/react-template/src/App.tsx

@@ -10,6 +10,7 @@ import "./styles/global.css";
 
 
 function App() {
 function App() {
   const [selectedTraceId, setSelectedTraceId] = useState<string | null>(null);
   const [selectedTraceId, setSelectedTraceId] = useState<string | null>(null);
+  const [selectedTraceTitle, setSelectedTraceTitle] = useState("流程图可视化系统");
   const [selectedNode, setSelectedNode] = useState<Goal | Message | null>(null);
   const [selectedNode, setSelectedNode] = useState<Goal | Message | null>(null);
   const [selectedEdge, setSelectedEdge] = useState<Edge | null>(null);
   const [selectedEdge, setSelectedEdge] = useState<Edge | null>(null);
   const [rightWidth, setRightWidth] = useState(360);
   const [rightWidth, setRightWidth] = useState(360);
@@ -82,14 +83,21 @@ function App() {
           <TopBar
           <TopBar
             selectedTraceId={selectedTraceId}
             selectedTraceId={selectedTraceId}
             selectedNode={selectedNode}
             selectedNode={selectedNode}
-            onTraceSelect={setSelectedTraceId}
+            title={selectedTraceTitle}
+            onTraceSelect={(id, title) => {
+              setSelectedTraceId(id);
+              if (title) setSelectedTraceTitle(title);
+            }}
             onTraceCreated={() => setRefreshTrigger((t) => t + 1)}
             onTraceCreated={() => setRefreshTrigger((t) => t + 1)}
             onMessageInserted={() => setMessageRefreshTrigger((t) => t + 1)}
             onMessageInserted={() => setMessageRefreshTrigger((t) => t + 1)}
           />
           />
           <MainContent
           <MainContent
             traceId={selectedTraceId}
             traceId={selectedTraceId}
             onNodeClick={handleNodeClick}
             onNodeClick={handleNodeClick}
-            onTraceChange={setSelectedTraceId}
+            onTraceChange={(id, title) => {
+              setSelectedTraceId(id);
+              if (title) setSelectedTraceTitle(title);
+            }}
             refreshTrigger={refreshTrigger}
             refreshTrigger={refreshTrigger}
             messageRefreshTrigger={messageRefreshTrigger}
             messageRefreshTrigger={messageRefreshTrigger}
           />
           />

+ 1 - 1
frontend/react-template/src/api/traceApi.ts

@@ -44,7 +44,7 @@ export const traceApi = {
       method: "POST",
       method: "POST",
     });
     });
   },
   },
-  reflectTrace(traceId: string, data?: { focus?: string }) {
+  reflectTrace(traceId: string, data?: { focus?: string | null }) {
     return request<void>(`/api/traces/${traceId}/reflect`, {
     return request<void>(`/api/traces/${traceId}/reflect`, {
       method: "POST",
       method: "POST",
       data,
       data,

+ 2 - 3
frontend/react-template/src/components/FlowChart/FlowChart.tsx

@@ -940,13 +940,12 @@ const FlowChartComponent: ForwardRefRenderFunction<FlowChartRef, FlowChartProps>
                         height={50}
                         height={50}
                       >
                       >
                         <div
                         <div
-                          className="w-full h-full flex items-center justify-center text-xs text-center leading-[1.2] px-1 box-border line-clamp-3"
+                          className="w-full h-full overflow-hidden flex items-center justify-center"
                           style={{
                           style={{
                             color: textColor,
                             color: textColor,
-                            WebkitBoxPack: "center", // 垂直居中
                           }}
                           }}
                         >
                         >
-                          {text}
+                          <text className="text-xs line-clamp-3 px-1">{text}</text>
                         </div>
                         </div>
                       </foreignObject>
                       </foreignObject>
                     </Tooltip>
                     </Tooltip>

+ 6 - 3
frontend/react-template/src/components/MainContent/MainContent.tsx

@@ -14,7 +14,7 @@ import styles from "./MainContent.module.css";
 interface MainContentProps {
 interface MainContentProps {
   traceId: string | null;
   traceId: string | null;
   onNodeClick?: (node: Goal | Message, edge?: Edge) => void;
   onNodeClick?: (node: Goal | Message, edge?: Edge) => void;
-  onTraceChange?: (traceId: string) => void;
+  onTraceChange?: (traceId: string, title?: string) => void;
   refreshTrigger?: number;
   refreshTrigger?: number;
   messageRefreshTrigger?: number;
   messageRefreshTrigger?: number;
 }
 }
@@ -92,11 +92,14 @@ export const MainContent: FC<MainContentProps> = ({
         <div className={styles.headerRight}>
         <div className={styles.headerRight}>
           <Select
           <Select
             value={traceId}
             value={traceId}
-            onChange={(value: unknown) => onTraceChange?.(value as string)}
+            onChange={(value: unknown) => {
+              const trace = traceList.find((t) => t.trace_id === value);
+              onTraceChange?.(value as string, trace?.task || trace?.trace_id);
+            }}
             style={{ width: 200 }}
             style={{ width: 200 }}
             placeholder="选择 Trace"
             placeholder="选择 Trace"
             optionList={traceList.map((t) => ({
             optionList={traceList.map((t) => ({
-              label: t.task || t.trace_id,
+              label: t.task?.length > 15 ? `${t.task.slice(0, 15)}...` : t.task || t.trace_id,
               value: t.trace_id,
               value: t.trace_id,
             }))}
             }))}
           />
           />

+ 10 - 1
frontend/react-template/src/components/TopBar/TopBar.module.css

@@ -7,12 +7,21 @@
   justify-content: space-between;
   justify-content: space-between;
   padding: 0 var(--spacing-lg, 24px);
   padding: 0 var(--spacing-lg, 24px);
 }
 }
+.title {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  flex: 1;
+}
 
 
 .title h1 {
 .title h1 {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
   font-size: var(--font-size-lg, 16px);
   font-size: var(--font-size-lg, 16px);
   font-weight: 600;
   font-weight: 600;
   color: var(--text-primary, #333333);
   color: var(--text-primary, #333333);
-  margin: 0;
+  margin: 0 20px 0 0;
 }
 }
 
 
 .filters {
 .filters {

+ 33 - 15
frontend/react-template/src/components/TopBar/TopBar.tsx

@@ -1,5 +1,6 @@
 import { useCallback, useEffect, useState, useRef } from "react";
 import { useCallback, useEffect, useState, useRef } from "react";
 import type { FC } from "react";
 import type { FC } from "react";
+import ReactMarkdown from "react-markdown";
 import { Modal, Form, Toast } from "@douyinfe/semi-ui";
 import { Modal, Form, Toast } from "@douyinfe/semi-ui";
 import { traceApi } from "../../api/traceApi";
 import { traceApi } from "../../api/traceApi";
 import type { Goal } from "../../types/goal";
 import type { Goal } from "../../types/goal";
@@ -9,7 +10,8 @@ import styles from "./TopBar.module.css";
 interface TopBarProps {
 interface TopBarProps {
   selectedTraceId: string | null;
   selectedTraceId: string | null;
   selectedNode: Goal | Message | null;
   selectedNode: Goal | Message | null;
-  onTraceSelect: (traceId: string) => void;
+  title: string;
+  onTraceSelect: (traceId: string, title?: string) => void;
   onTraceCreated?: () => void;
   onTraceCreated?: () => void;
   onMessageInserted?: () => void;
   onMessageInserted?: () => void;
 }
 }
@@ -17,11 +19,11 @@ interface TopBarProps {
 export const TopBar: FC<TopBarProps> = ({
 export const TopBar: FC<TopBarProps> = ({
   selectedTraceId,
   selectedTraceId,
   selectedNode,
   selectedNode,
+  title,
   onTraceSelect,
   onTraceSelect,
   onTraceCreated,
   onTraceCreated,
   onMessageInserted,
   onMessageInserted,
 }) => {
 }) => {
-  const [title, setTitle] = useState("流程图可视化系统");
   const [isModalVisible, setIsModalVisible] = useState(false);
   const [isModalVisible, setIsModalVisible] = useState(false);
   const [isInsertModalVisible, setIsInsertModalVisible] = useState(false);
   const [isInsertModalVisible, setIsInsertModalVisible] = useState(false);
   const [isReflectModalVisible, setIsReflectModalVisible] = useState(false);
   const [isReflectModalVisible, setIsReflectModalVisible] = useState(false);
@@ -46,20 +48,20 @@ export const TopBar: FC<TopBarProps> = ({
           status: status || undefined,
           status: status || undefined,
           limit: 20,
           limit: 20,
         });
         });
-        const firstTrace = data.traces[0];
-        const traceId = firstTrace?.parent_trace_id || firstTrace.trace_id;
-        if (firstTrace) {
-          setTitle(firstTrace.task);
-          onTraceSelect(traceId);
-        } else {
-          setTitle("流程图可视化系统");
-          onTraceSelect("");
+        // 初始加载时不自动选择,或者如果 selectedTraceId 为空才选择第一个
+        if (!selectedTraceId && data.traces.length > 0) {
+          const firstTrace = data.traces[0];
+          const traceId = firstTrace?.parent_trace_id || firstTrace.trace_id;
+          onTraceSelect(traceId, firstTrace.task);
+        } else if (data.traces.length === 0) {
+          onTraceSelect("", "流程图可视化系统");
         }
         }
       } catch (error) {
       } catch (error) {
         console.error("Failed to load traces:", error);
         console.error("Failed to load traces:", error);
+        Toast.error("加载任务列表失败");
       }
       }
     },
     },
-    [onTraceSelect],
+    [onTraceSelect, selectedTraceId],
   );
   );
 
 
   useEffect(() => {
   useEffect(() => {
@@ -188,7 +190,7 @@ export const TopBar: FC<TopBarProps> = ({
     const values = reflectFormApiRef.current?.getValues();
     const values = reflectFormApiRef.current?.getValues();
     const focus = values?.reflect_focus?.trim();
     const focus = values?.reflect_focus?.trim();
     try {
     try {
-      await traceApi.reflectTrace(selectedTraceId, focus ? { focus } : undefined);
+      await traceApi.reflectTrace(selectedTraceId, focus ? { focus } : {});
       Toast.success("已触发反思");
       Toast.success("已触发反思");
       setIsReflectModalVisible(false);
       setIsReflectModalVisible(false);
     } catch (error) {
     } catch (error) {
@@ -200,7 +202,17 @@ export const TopBar: FC<TopBarProps> = ({
   const handleExperience = async () => {
   const handleExperience = async () => {
     try {
     try {
       const content = await traceApi.getExperiences();
       const content = await traceApi.getExperiences();
-      setExperienceContent(typeof content === "string" ? content : JSON.stringify(content, null, 2));
+      // 尝试解析 JSON 格式
+      let displayContent = typeof content === "string" ? content : JSON.stringify(content, null, 2);
+      try {
+        const parsed = typeof content === "string" ? JSON.parse(content) : content;
+        if (parsed && typeof parsed === "object" && "content" in parsed) {
+          displayContent = parsed.content;
+        }
+      } catch (e) {
+        // 解析失败则使用原始字符串
+      }
+      setExperienceContent(displayContent);
       setIsExperienceModalVisible(true);
       setIsExperienceModalVisible(true);
     } catch (error) {
     } catch (error) {
       console.error("Failed to get experiences:", error);
       console.error("Failed to get experiences:", error);
@@ -210,8 +222,12 @@ export const TopBar: FC<TopBarProps> = ({
 
 
   return (
   return (
     <header className={styles.topbar}>
     <header className={styles.topbar}>
-      <div className={styles.title}>
+      <div
+        className={styles.title}
+        title={title}
+      >
         <h1>{title}</h1>
         <h1>{title}</h1>
+        {/* <h1>{title.length > 50 ? `${title.slice(0, 50)}...` : title}</h1> */}
       </div>
       </div>
       <div className={styles.filters}>
       <div className={styles.filters}>
         <button
         <button
@@ -314,7 +330,9 @@ export const TopBar: FC<TopBarProps> = ({
         style={{ width: 800 }}
         style={{ width: 800 }}
         bodyStyle={{ maxHeight: "70vh", overflow: "auto" }}
         bodyStyle={{ maxHeight: "70vh", overflow: "auto" }}
       >
       >
-        <pre style={{ whiteSpace: "pre-wrap", wordWrap: "break-word" }}>{experienceContent || "暂无经验数据"}</pre>
+        <div style={{ whiteSpace: "pre-wrap", wordWrap: "break-word" }}>
+          {experienceContent ? <ReactMarkdown>{experienceContent}</ReactMarkdown> : "暂无经验数据"}
+        </div>
       </Modal>
       </Modal>
     </header>
     </header>
   );
   );