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

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

Talegorithm 3 недель назад
Родитель
Сommit
dbb7955f71

+ 9 - 1
agent/tools/builtin/browser/baseClass.py

@@ -68,7 +68,15 @@ from agent.tools.builtin.browser.sync_mysql_help import mysql
 # 导入 browser-use 的核心类
 from browser_use import BrowserSession, BrowserProfile
 from browser_use.tools.service import Tools
-from browser_use.tools.views import ReadContentAction
+try:
+    from browser_use.tools.views import ReadContentAction  # type: ignore
+except Exception:
+    from pydantic import BaseModel
+
+    class ReadContentAction(BaseModel):
+        goal: str
+        source: str = "page"
+        context: str = ""
 from browser_use.agent.views import ActionResult
 from browser_use.filesystem.file_system import FileSystem
 

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

@@ -30,7 +30,9 @@ export type SubTraceEntry = { id: string; mission?: string };
 const EMPTY_GOALS: Goal[] = [];
 
 export const FlowChart: FC<FlowChartProps> = ({ goals, msgGroups = {}, onNodeClick, onSubTraceClick }) => {
-  console.log("%c [ goals---2 ]-33", "font-size:13px; background:pink; color:#bf2c9f;", goals);
+  goals = goals.filter((g) => !g.parent_id);
+
+  console.log("%c [ FlowChart-goals ]-33", "font-size:13px; background:pink; color:#bf2c9f;", goals);
   // 确保 goals 中包含 END 节点
   const displayGoals = useMemo(() => {
     if (!goals) return [];
@@ -348,6 +350,83 @@ export const FlowChart: FC<FlowChartProps> = ({ goals, msgGroups = {}, onNodeCli
     return links;
   }, [displayGoals, handleSubTraceClick, layoutData, normalizeSubTraceEntries]);
 
+  const subGoalsOverlay = useMemo(() => {
+    if (!layoutData)
+      return [] as Array<{
+        parentId: string;
+        nodes: Array<{ x: number; y: number; fullText: string; shortText: string }>;
+        paths: Array<{ d: string }>;
+      }>;
+
+    const nodeMap = new Map(layoutData.nodes.map((node) => [node.data.id, node]));
+
+    const truncateMiddle = (text: string, limit: number) => {
+      if (text.length <= limit) return text;
+      const half = Math.floor((limit - 3) / 2);
+      return `${text.slice(0, half)}...${text.slice(-half)}`;
+    };
+
+    const flattenSubGoals = (items: Goal[] | undefined): Goal[] => {
+      if (!items || items.length === 0) return [];
+      const result: Goal[] = [];
+      items.forEach((item) => {
+        result.push(item);
+        const children = flattenSubGoals(item.sub_goals);
+        if (children.length > 0) result.push(...children);
+      });
+      return result;
+    };
+
+    const buildSegment = (sx: number, sy: number, tx: number, ty: number) => {
+      const controlX = (sx + tx) / 2 - 40;
+      const controlY = Math.max(sy, ty) - 30;
+      return `M${sx},${sy} Q${controlX},${controlY} ${tx},${ty}`;
+    };
+
+    const overlays: Array<{
+      parentId: string;
+      nodes: Array<{ x: number; y: number; fullText: string; shortText: string }>;
+      paths: Array<{ d: string }>;
+    }> = [];
+
+    displayGoals.forEach((goal) => {
+      const anchorNode = nodeMap.get(goal.id);
+      if (!anchorNode) return;
+
+      const subGoals = flattenSubGoals(goal.sub_goals);
+      if (subGoals.length === 0) return;
+
+      const anchorX = anchorNode.x;
+      const anchorY = anchorNode.y;
+      const count = subGoals.length;
+      const sides = count + 2;
+      const radius = Math.max(90, 44 + count * 16 + Math.max(0, count - 4) * 6);
+      const centerX = anchorX;
+      const centerY = anchorY + radius;
+      const angleStep = (Math.PI * 2) / sides;
+
+      const nodes = subGoals.map((subGoal, index) => {
+        const angle = -Math.PI / 2 - angleStep * (index + 1);
+        const fullText = subGoal.description || subGoal.id;
+        return {
+          x: centerX + Math.cos(angle) * radius,
+          y: centerY + Math.sin(angle) * radius,
+          fullText,
+          shortText: truncateMiddle(fullText, 10),
+        };
+      });
+
+      const nodeHalfH = 5;
+      const paths = nodes.map((node) => ({
+        d: buildSegment(anchorX, anchorY + nodeHalfH, node.x, node.y),
+      }));
+
+      overlays.push({ parentId: goal.id, nodes, paths });
+    });
+
+    return overlays;
+  }, [displayGoals, layoutData]);
+
   // 当前选中节点的消息链
   const selectedMessages = useMemo(() => {
     if (selectedSubTrace) return subTraceMessages || [];
@@ -472,8 +551,9 @@ export const FlowChart: FC<FlowChartProps> = ({ goals, msgGroups = {}, onNodeCli
           <g transform={`translate(${layoutData.margin.left},${layoutData.margin.top})`}>
             {(() => {
               const visibleNodes = layoutData.nodes.filter((n) => n.data.id !== VIRTUAL_ROOT_ID);
-              const xs = visibleNodes.map((n) => n.x);
-              const ys = visibleNodes.map((n) => n.y);
+              const overlayPoints = subGoalsOverlay.flatMap((overlay) => overlay.nodes);
+              const xs = [...visibleNodes.map((n) => n.x), ...overlayPoints.map((n) => n.x)];
+              const ys = [...visibleNodes.map((n) => n.y), ...overlayPoints.map((n) => n.y)];
               const nodeHalfW = 70;
               const nodeHalfH = 26;
               const minX = Math.min(...xs) - nodeHalfW;
@@ -584,6 +664,45 @@ export const FlowChart: FC<FlowChartProps> = ({ goals, msgGroups = {}, onNodeCli
                       ))}
                     </g>
                   )}
+                  {subGoalsOverlay.length > 0 && (
+                    <g>
+                      {subGoalsOverlay.flatMap((overlay) =>
+                        overlay.paths.map((p, idx) => (
+                          <path
+                            key={`subgoals-path-${overlay.parentId}-${idx}`}
+                            d={p.d}
+                            fill="none"
+                            stroke="#7aa0d6"
+                            strokeWidth={1.5}
+                            strokeDasharray="6,6"
+                            markerEnd="url(#arrow-default)"
+                            opacity={0.7}
+                            strokeLinecap="round"
+                          />
+                        )),
+                      )}
+                      {subGoalsOverlay.flatMap((overlay) =>
+                        overlay.nodes.map((node, idx) => (
+                          <g
+                            key={`subgoals-node-${overlay.parentId}-${idx}`}
+                            transform={`translate(${node.x},${node.y})`}
+                          >
+                            <Tooltip content={node.fullText}>
+                              <text
+                                x={0}
+                                y={16}
+                                fontSize={12}
+                                fill="#333"
+                                textAnchor="middle"
+                              >
+                                {node.shortText}
+                              </text>
+                            </Tooltip>
+                          </g>
+                        )),
+                      )}
+                    </g>
+                  )}
                   {messageOverlay && (
                     <g>
                       {messageOverlay.paths.map((p, idx) => (

+ 30 - 3
frontend/react-template/src/components/FlowChart/hooks/useFlowChartData.ts

@@ -14,6 +14,32 @@ const isMessage = (value: unknown): value is Message =>
   isRecord(value) &&
   (typeof value.id === "string" || typeof (value as { message_id?: string }).message_id === "string");
 
+const buildSubGoals = (flatGoals: Goal[]): Goal[] => {
+  const nodeMap = new Map<string, Goal>();
+  flatGoals.forEach((goal) => {
+    nodeMap.set(goal.id, { ...goal, sub_goals: [] });
+  });
+
+  flatGoals.forEach((goal) => {
+    const parentId = typeof goal.parent_id === "string" && goal.parent_id ? goal.parent_id : undefined;
+    if (!parentId) return;
+    const parent = nodeMap.get(parentId);
+    const child = nodeMap.get(goal.id);
+    if (!parent || !child) return;
+    if (!Array.isArray(parent.sub_goals)) parent.sub_goals = [];
+    parent.sub_goals.push(child);
+  });
+
+  return flatGoals.map((goal) => {
+    const node = nodeMap.get(goal.id);
+    if (!node) return goal;
+    if (Array.isArray(node.sub_goals) && node.sub_goals.length === 0) {
+      delete node.sub_goals;
+    }
+    return node;
+  });
+};
+
 // FlowChart 专用数据 Hook:处理实时事件并聚合消息组
 export const useFlowChartData = (traceId: string | null, initialGoals: Goal[]) => {
   const [goals, setGoals] = useState<Goal[]>(initialGoals);
@@ -83,7 +109,7 @@ export const useFlowChartData = (traceId: string | null, initialGoals: Goal[]) =
         const goalList = isRecord(goalTree) ? goalTree.goals : undefined;
         const nextGoals = Array.isArray(goalList) ? (goalList as Goal[]) : [];
         setGoals((prev) => {
-          return nextGoals.map((ng) => {
+          const mergedFlat = nextGoals.map((ng) => {
             const existing = prev.find((p) => p.id === ng.id);
             if (!existing) return ng;
             const merged: Goal = { ...existing, ...ng };
@@ -95,6 +121,7 @@ export const useFlowChartData = (traceId: string | null, initialGoals: Goal[]) =
             }
             return merged;
           });
+          return buildSubGoals(mergedFlat);
         });
         return;
       }
@@ -116,10 +143,10 @@ export const useFlowChartData = (traceId: string | null, initialGoals: Goal[]) =
               merged.agent_call_mode = existing.agent_call_mode;
             }
             next[idx] = merged;
-            return next;
+            return buildSubGoals(next);
           }
           next.push(goal as Goal);
-          return next;
+          return buildSubGoals(next);
         });
         return;
       }

+ 1 - 0
frontend/react-template/src/components/MainContent/MainContent.tsx

@@ -16,6 +16,7 @@ export const MainContent: FC<MainContentProps> = ({ traceId, onNodeClick }) => {
   const { trace, loading } = useTrace(traceId);
   const initialGoals = useMemo(() => trace?.goal_tree?.goals ?? [], [trace]);
   const { goals, connected, msgGroups } = useFlowChartData(traceId, initialGoals);
+  console.log("%c [ goals ]-19", "font-size:13px; background:pink; color:#bf2c9f;", goals);
 
   if (!traceId && !loading) {
     return (

+ 1 - 0
frontend/react-template/src/types/goal.ts

@@ -10,6 +10,7 @@ export interface Goal {
   metadata?: Record<string, unknown>;
   agent_call_mode?: string;
   sub_trace_ids?: Array<string | { trace_id: string; mission?: string }>;
+  sub_goals?: Array<Goal>;
 }
 
 export interface BranchContext {