feat(desktop): 团队视图加「卡通办公室」皮肤(手绘 SVG 贴纸风)

多智能体协调演成办公室:协调者在白板前统筹(白板即思考/综合流),
专家在工位敲键盘,完成打✓、失败挂!、派发走虚线。

风格对齐 ai-office-react(粗描边+平涂+大头身),但用手写 SVG 而非
PixiJS+Spine:零外部素材、无 Spine 运行时授权问题、几 KB 进包。
先试过 three.js 真 3D,低模程序化角色出来是玩具味,已弃并卸干净依赖。

数据与「卡片看板」同源(deriveTeam),换皮不换数据,后端零改动。
动效=状态编码(敲键盘/思考流/辉光),守 prefers-reduced-motion。

顺带修一个真 bug:并发时间轴复盘卡在 running 的旧任务时,未收口工位
会一路量到 Date.now(),跨度爆表(实测 32103562.4s)。抽 teamNow():
直播用此刻、复盘用最后一个事件时间戳;两个皮肤共用,+3 单测。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-07-15 16:25:54 +08:00
parent 0152e7837a
commit 225fee5519
5 changed files with 452 additions and 4 deletions
@@ -2,6 +2,7 @@ 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 { OfficeView } from "../components/OfficeView";
import { Markdown } from "../components/Markdown";
import { ChartView } from "../components/ChartView";
import { extractChartBlocks, hasChart } from "../lib/chartspec";
@@ -33,6 +34,7 @@ export function RunsView({ run }: { run: RunState }) {
const [replay, setReplay] = useState<RunState>(emptyRun);
const [evalRes, setEvalRes] = useState<EvalResult | null>(null);
const [tab, setTab] = useState<DetailTab>("trace");
const [teamSkin, setTeamSkin] = useState<"office" | "board">("office"); // 团队视图皮肤:卡通办公室 / 卡片看板
useEffect(() => {
let alive = true;
@@ -139,7 +141,23 @@ export function RunsView({ run }: { run: RunState }) {
) : activeTab === "trace" ? (
<ExecTrace events={cur.exec} phase={cur.phase} />
) : activeTab === "team" ? (
<TeamView events={cur.exec} output={cur.output} phase={cur.phase} />
<div className="flex h-full min-h-0 flex-col gap-2">
<div className="flex shrink-0 items-center gap-1 self-start rounded-md border border-line p-0.5">
{(["office", "board"] as const).map((k) => (
<button key={k} onClick={() => setTeamSkin(k)}
className={cn("rounded px-2 py-0.5 text-[11px] transition", teamSkin === k ? "bg-ink-800 text-slate-100" : "text-slate-500 hover:text-slate-300")}>
{k === "office" ? "办公室" : "看板"}
</button>
))}
</div>
<div className="min-h-0 flex-1">
{teamSkin === "office" ? (
<OfficeView events={cur.exec} output={cur.output} phase={cur.phase} />
) : (
<TeamView events={cur.exec} output={cur.output} phase={cur.phase} />
)}
</div>
</div>
) : activeTab === "tools" ? (
<ToolCalls run={cur} />
) : (