feat(desktop): 多智能体「团队」视图(运行·观测新增,轨迹仍默认)

把多智能体协调画成「协调者居中 + 专家工位环绕」,让最难读的一段——并行派发与综合
——变得一眼可读。执行轨迹保持默认视图;「团队」tab 只在多智能体运行时出现,
普通任务不塞多余 tab。

- lib/run.ts 加 isMultiAgent()(据 coordinator: 节点判定) + deriveTeam():
  从现有执行事件流派生协调者/工位模型。与 deriveNodes 的区别是保留 start/end 时间戳,
  才能画并发时间轴;并解析专家收尾 detail("简报 X → Y")还原简报与产出。
- components/TeamView.tsx:径向布局(工位按角度均分环绕,>6 个降级为网格)、
  协调者实时思考流(接 token 流 + 闪烁光标)、并发时间轴(重叠一眼可见)。
  动效只标活跃态(派发链路流动/工位脉冲)——是状态编码不是装饰,带 prefers-reduced-motion 守卫。
  专家(kind=agent)与工具(kind=tool)图标区分。
- 零后端改动:数据全部来自现有 exec 事件(coordinator:/agent:/tool:)。

补 9 个单测覆盖派生逻辑(简报解析/失败态/时间窗/lead 不混入工位)。
实机经回放路径验证渲染(径向布局/简报/并发条/tab 条件出现)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-07-15 15:30:12 +08:00
parent 2d5b72930a
commit 0152e7837a
4 changed files with 370 additions and 6 deletions
@@ -1,14 +1,15 @@
import { useCallback, useEffect, useState } from "react";
import { Activity, FileText, History, Users, Wrench } from "lucide-react";
import { ExecTrace } from "../components/ExecTrace";
import { TeamView } from "../components/TeamView";
import { Markdown } from "../components/Markdown";
import { ChartView } from "../components/ChartView";
import { extractChartBlocks, hasChart } from "../lib/chartspec";
import { deriveNodes, emptyRun, type RunState } from "../lib/run";
import { deriveNodes, isMultiAgent, emptyRun, type RunState } from "../lib/run";
import { listRuns, taskEval, runReplay, type RunSummary, type EvalResult } from "../lib/api";
import { Tabs, Panel, Dot, Badge, EmptyState, cn, type TabDef } from "../ui";
type DetailTab = "trace" | "tools" | "eval";
type DetailTab = "trace" | "team" | "tools" | "eval";
const STATUS_DOT: Record<string, "success" | "danger" | "warn" | "running" | "neutral"> = {
done: "success", failed: "danger", timeout: "danger", rejected: "danger",
@@ -68,8 +69,13 @@ export function RunsView({ run }: { run: RunState }) {
const nodes = deriveNodes(cur.exec);
// 工具调用面板纳入专家派发:MCP 工具(kind=tool)与多智能体协调里的子智能体派发(kind=agent)都是「调用」。
const calls = nodes.filter((n) => n.kind === "tool" || n.kind === "agent");
// 「团队」视图只在多智能体协调的运行里出现(普通任务轨迹已足够,不塞多余 tab)。
const multi = isMultiAgent(cur.exec);
// 切到非多智能体运行时,「团队」tab 会消失——此时停在它上面要回落轨迹,否则内容悬空。
const activeTab: DetailTab = !multi && tab === "team" ? "trace" : tab;
const tabs: TabDef<DetailTab>[] = [
{ key: "trace", label: "执行轨迹", count: nodes.length },
...(multi ? ([{ key: "team", label: "团队" }] as TabDef<DetailTab>[]) : []),
{ key: "tools", label: "工具/专家", count: calls.length },
{ key: "eval", label: "评测" },
];
@@ -124,15 +130,17 @@ export function RunsView({ run }: { run: RunState }) {
{/* 中:tab 切换 轨迹/工具/评测 + 状态指示 */}
<div className="flex min-h-0 flex-col rounded-lg border border-line bg-ink-900">
<div className="flex items-center border-b border-line px-2">
<Tabs tabs={tabs} value={tab} onChange={setTab} />
<Tabs tabs={tabs} value={activeTab} onChange={setTab} />
<span className={cn("ml-auto pr-2 text-[11px]", statusCls)}>{statusText}</span>
</div>
<div className="min-h-0 flex-1 overflow-auto p-4">
{empty ? (
<EmptyState icon={Activity} title="选择一次运行" desc="左侧点选历史运行即可回放轨迹;或在编排/报告页发起新运行。" />
) : tab === "trace" ? (
) : activeTab === "trace" ? (
<ExecTrace events={cur.exec} phase={cur.phase} />
) : tab === "tools" ? (
) : activeTab === "team" ? (
<TeamView events={cur.exec} output={cur.output} phase={cur.phase} />
) : activeTab === "tools" ? (
<ToolCalls run={cur} />
) : (
<EvalView ev={evalRes} />