返回 DeepSeek-Reasonix
chat-transcript.tsx
根目录 / desktop / frontend / bench / chat-transcript.tsx
1 import { useLayoutEffect, useMemo, useRef, useState } from "react";
2 import { createRoot } from "react-dom/client";
3 import { Transcript } from "../src/components/Transcript";
4 import { LocaleProvider } from "../src/lib/i18n";
5 import { ToastProvider } from "../src/lib/toast";
6 import { getMarkdownWorkerClient } from "../src/lib/markdownWorkerClient";
7 import { Composer } from "../src/components/Composer";
8 import { canonicalMessage } from "../src/lib/canonicalTranscriptBackend";
9 import { historyMessagesToItems } from "../src/lib/historyItems";
10 import type { ControllerLiveStore, Item, LiveStream, State } from "../src/lib/useController";
11 import { initialState, reducer } from "../src/lib/useController";
12 import type { RuntimeState } from "../src/lib/runtimeStateStore";
13 import "../src/styles.css";
14
15 function makeTurns(count: number, start = 0): Item[] {
16 return Array.from({ length: count }, (_, position): Item[] => { const index = start + position; return [
17 { kind: "user", id: `u${index}`, text: `Question ${index + 1}: Explain the implementation`, checkpointTurn: index + 1 },
18 { kind: "tool", id: `tool${index}`, name: "read_file", args: JSON.stringify({ path: "src/example.ts" }), output: "Evidence\n".repeat(1200), readOnly: true, status: "done", subject: "src/example.ts" },
19 { kind: "assistant", id: `a${index}`, text: `### Answer ${index + 1}\n\nA stable paragraph with **formatted text** and a [reference](https://example.com).\n\n- Keep the reader's position.\n- Update only the changed node.\n\n中文内容用于验证字体与换行。` + (index % 12 === 0 ? "\n\n| Item | Result |\n|---|---|\n| Test | Passed |\n\n$x^2 + y^2$" : "") + (index % 40 === 0 ? "\n\n```ts\n" + Array.from({length: 210}, (_, n) => `const value${n} = ${n};`).join("\n") + "\n```" : ""), reasoning: "Inspect the relevant code before making a change.", streaming: false,
20 createdAt: new Date(2026, 8, 12, 12, 34).getTime(), turnDurationMs: 29_000, tokensPerSecond: 183,
21 turnUsage: { totalTokens: 185_225, uncachedInputTokens: 26_278, cacheReadTokens: 155_520, outputTokens: 3_427, reasoningTokens: 1_909, routes: ["deepseek-official/deepseek-flash"] } },
22 ]; }).flat();
23 }
24 function weatherTurn(): Item[] {
25 const end = makeTurns(1)[2];
26 return [
27 { kind: "user", id: "weather-user", text: "查一下今天上海的天气", checkpointTurn: 1 },
28 { kind: "assistant", id: "weather-thought", text: "", reasoning: "先查询实时天气,再交叉核对来源。", streaming: false },
29 { kind: "tool", id: "weather-search", name: "web_search", args: JSON.stringify({ query: "上海今天天气 实时温度" }), output: "Weather sources", searchSources: [{ title: "天气数据(测试来源)", url: "https://example.com/weather" }], status: "done" },
30 { kind: "tool", id: "weather-error", name: "web_fetch", args: JSON.stringify({ url: "https://example.com/weather" }), error: "DNS lookup failed", status: "error" },
31 { kind: "assistant", id: "weather-progress", text: "一个来源暂时无法访问,正在核对另一来源。", reasoning: "切换到另一来源。", streaming: false },
32 { kind: "notice", id: "weather-permission", level: "info", title: "已记录决策", text: "permission saved to /tmp/weather-fixture/reasonix.toml: curl --max-time 20 https://example.com/weather" },
33 { kind: "tool", id: "weather-bash", name: "bash", args: JSON.stringify({ command: "printf '25.5°C 晴\\n'", description: "获取并核对上海天气" }), output: "25.5°C 晴\n", status: "done",
34 execution: { kind: "shell", shell: "bash", state: "completed", exitCode: 0, supportsAndAnd: true } },
35 { kind: "tool", id: "weather-write-html", name: "write_file", args: JSON.stringify({ path: "output/shanghai-weather.html" }), output: "wrote output/shanghai-weather.html", readOnly: false, status: "done", fileDiff: { diff: "", added: 4, removed: 0 } },
36 { kind: "tool", id: "weather-write-notes", name: "write_file", args: JSON.stringify({ path: "output/weather-notes.md" }), output: "wrote output/weather-notes.md", readOnly: false, status: "done", fileDiff: { diff: "", added: 1, removed: 1 } },
37 { kind: "tool", id: "weather-present", name: "present", args: JSON.stringify({ files: [{ path: "output/shanghai-weather.html", description: "交互式天气报告" }, { path: "output/weather-notes.md", description: "数据来源与说明" }] }), output: "Presented output/shanghai-weather.html\nPresented output/weather-notes.md", status: "done",
38 presentedFiles: [{ path: "output/shanghai-weather.html", description: "交互式天气报告" }, { path: "output/weather-notes.md", description: "数据来源与说明" }] },
39 { kind: "notice", id: "weather-result", level: "info", text: "Turn complete", completionSummary: {
40 preset: "balanced", verdict: "complete", mutations: 2, changed_files: 2, checks_passed: 1, checks_failed: 0,
41 checks_suppressed: 0, review: "passed", constraint_degraded: false,
42 receipt: { verdict: "complete", diff: { id: "weather-diff", turn: 0, coverage: "complete", added: 5, removed: 1, reasons: [], files: [
43 { path: "output/shanghai-weather.html", kind: "create", added: 4, removed: 0 },
44 { path: "output/weather-notes.md", kind: "create", added: 1, removed: 1 },
45 ] } },
46 } },
47 { ...end, id: "weather-final", text: "今天上海天气如下。以下为界面回放测试数据。\n\n## 上海 · 今日实况\n\n| 项目 | 数值 |\n|---|---|\n| 天气 | 晴 ☀️ |\n| 气温 | **25.5 °C** |\n| 湿度 | 66% |\n\n**全天**:多云转晴,23~30 °C。", reasoning: "" } as Item,
48 ];
49 }
50 declare global { interface Window { chatFixture: { steer(order: "record-first" | "event-first", count: number): void; unappliedSteer(order: "record-first" | "event-first", count: number): void; maintenance(stage: "start" | "refresh" | "completed" | "unknown"): void; authored(): void; toolAliasRegression(): void; backgroundLaunch(): void; backgroundOutputHistory(): void; weather(): void; replace(count: number): void; reset(count: number): void; older(): void; tick(index: number): void; settle(): void; switchSession(): void; prepend(): void; ready: number; pending(): number } } }
51 function Fixture() {
52 const [items, setItems] = useState(() => new URLSearchParams(window.location.search).has("deliverables") ? weatherTurn() : makeTurns(20));
53 const [session, setSession] = useState(0);
54 const [running, setRunning] = useState(false);
55 const [ready, setReady] = useState(0);
56 const itemsRef = useRef(items);
57 const maintenanceState = useRef(initialState);
58 itemsRef.current = items;
59 const liveRef = useRef<LiveStream>();
60 const liveListeners = useRef(new Set<() => void>());
61 // Production stream deltas bypass the full controller tree through this
62 // store. Keep the benchmark on that path so input timing measures the UI
63 // users run instead of rebuilding the complete history fixture per token.
64 const liveStore = useMemo<ControllerLiveStore>(() => ({
65 subscribe: (_tabId, listener) => {
66 liveListeners.current.add(listener);
67 return () => { liveListeners.current.delete(listener); };
68 },
69 getSnapshot: () => liveRef.current,
70 }), []);
71 const publishLive = () => liveListeners.current.forEach(listener => listener());
72 const clearLive = () => { liveRef.current = undefined; publishLive(); };
73 useLayoutEffect(() => {
74 window.chatFixture = {
75 ready, pending: () => getMarkdownWorkerClient().stats().pending,
76 steer: (order, count) => {
77 let state: State = { ...initialState, transcriptProtocol: 2 };
78 const text = "对比源码信息应该更准一些";
79 const formal: Item[] = [];
80 for (let i = 0; i < count; i++) {
81 const messageId = `steer-${i}`;
82 const event = { type: "event" as const, e: { kind: "steer" as const, messageId, itemId: `inbox-${i}`, text } };
83 if (order === "event-first") state = reducer(state, event);
84 formal.push(...historyMessagesToItems([{ role: "notice", messageId, recordId: `m:${messageId}`, content: `↪ ${text}` }], "steer").items);
85 state = reducer(state, { type: "transcript_records", confirmedUsers: [], projection: {
86 items: [...formal], removeIds: [], startTurn: 1, endTurn: 1, totalTurns: 1, hasOlder: false, hasNewer: false,
87 revision: i + 1, revisionKnown: true, digest: "steer",
88 } });
89 state = reducer(state, event);
90 }
91 clearLive(); setItems(state.items); setRunning(false); setSession(value => value + 1);
92 },
93 unappliedSteer: (order, count) => {
94 let state: State = { ...initialState, transcriptProtocol: 2 };
95 const text = "Guidance was not applied:\n对比源码信息应该更准一些";
96 const formal: Item[] = [];
97 for (let i = 0; i < count; i++) {
98 const messageId = `unapplied-${i}`;
99 const event = { type: "event" as const, e: { kind: "notice" as const, code: "unapplied_steer", level: "warn" as const, messageId, text } };
100 if (order === "event-first") state = reducer(state, event);
101 formal.push(...historyMessagesToItems([{ role: "notice", messageId, recordId: `m:${messageId}:notice:0`,
102 code: "unapplied_steer", level: "warn", content: text }], "unapplied").items);
103 state = reducer(state, { type: "transcript_records", confirmedUsers: [], projection: {
104 items: [...formal], removeIds: [], startTurn: 1, endTurn: 1, totalTurns: 1, hasOlder: false, hasNewer: false,
105 revision: i + 1, revisionKnown: true, digest: "unapplied",
106 } });
107 state = reducer(state, event);
108 }
109 clearLive(); setItems(state.items); setRunning(false); setSession(value => value + 1);
110 },
111 maintenance: stage => {
112 const op = { operationId: "bench-maintenance", kind: "compact", status: "running", activity: "running", operationRevision: 1, runtimeEpoch: "bench-runtime" };
113 const idle: RuntimeState = { schemaVersion: 1, projectionEpoch: "bench-projection", runtimeEpoch: "bench-runtime", activityRevision: 1, revision: 1,
114 phase: "idle", running: false, turnId: "", turnStatus: "", turnEventSeq: 0, pendingPrompt: false, cancelRequested: false, cancellable: false, backgroundJobs: 0, activity: "" };
115 let state = maintenanceState.current;
116 if (stage === "start" || stage === "unknown") {
117 state = reducer({ ...initialState, items: makeTurns(1) }, { type: "runtime_snapshot", snapshot: idle });
118 state = reducer(state, { type: "event", e: { kind: "session_operation", sessionOperation: { ...op, ...(stage === "unknown" ? { status: "future_state", activity: "" } : {}) } } });
119 clearLive(); setSession(value => value + 1);
120 } else if (stage === "refresh") {
121 const message = canonicalMessage({ messageId: `maintenance:${op.operationId}`, position: 3, version: 1, role: "compaction" }, { role: "compaction", content: JSON.stringify(op) });
122 state = reducer(state, { type: "history", messages: [{ role: "user", messageId: "u0", content: "Question 1" }, { role: "assistant", messageId: "a0", content: "Existing answer" }, message] });
123 state = reducer(state, { type: "runtime_snapshot", snapshot: { ...idle, revision: 2, phase: "executing", running: true, maintenance: op } });
124 } else {
125 state = reducer(state, { type: "event", e: { kind: "session_operation", sessionOperation: { ...op, status: "completed", activity: "finalizing", operationRevision: 2, summary: "Durable compression summary", inputTokens: 1200, resultTokens: 400, applied: true } } });
126 }
127 maintenanceState.current = state; setItems(state.items); setRunning(false);
128 },
129 authored: () => {
130 const messages = [
131 { role: "user", origin: "host", content: '<session-context version="1">private environment</session-context>' },
132 { role: "user", origin: "user", content: '<response-language>internal policy</response-language>你是谁', raw_content: "你是谁" },
133 { role: "user", content: '<capability-route>legacy internal route</capability-route>旧会话问题' },
134 { role: "user", origin: "user", raw_content: '<response-language>用户引用的 XML</response-language>' },
135 { role: "assistant", content: "我是 Reasonix。" },
136 ].map((raw, i) => canonicalMessage({ messageId: `authored-${i}`, position: i, version: 1, role: raw.role }, raw));
137 clearLive(); setItems(historyMessagesToItems(messages, "authored").items); setRunning(false); setSession(value => value + 1);
138 },
139 weather: () => { clearLive(); setItems(weatherTurn()); setRunning(false); setSession(value => value + 1); },
140 backgroundLaunch: () => {
141 clearLive(); setItems([
142 { kind: "user", id: "launch-user", text: "Run the build" },
143 ...["pwsh", "bash"].map((name): Item => ({ kind: "tool", id: `launch-${name}`, name, args: "{}", status: "done", output: "Background job started", execution: { state: "background_started", shell: name } })),
144 { kind: "assistant", id: "launch-final", text: "Build started.", streaming: false },
145 ]); setRunning(false); setSession(value => value + 1);
146 },
147 backgroundOutputHistory: () => {
148 const calls = ["running", "failed", "cancelled", "completed"].map(state => ({
149 id: `poll-${state}`, name: "job_output", arguments: "{}",
150 resultObservation: { state: "completed" as const, messageId: `result-${state}`, version: 1 },
151 }));
152 const projected = historyMessagesToItems([
153 { role: "user", content: "Check the background jobs" },
154 { role: "assistant", content: "", toolCalls: calls },
155 ...calls.map(call => ({ role: "tool", messageId: call.resultObservation.messageId, toolName: call.name,
156 toolCallId: call.id, content: "Output retrieved", execution: { state: call.id.slice("poll-".length) } })),
157 { role: "assistant", content: "Output checked." },
158 ], "output-history");
159 clearLive(); setItems(projected.items); setRunning(false); setSession(value => value + 1);
160 },
161 toolAliasRegression: () => {
162 const user: Item = { kind: "user", id: "alias-user", text: "创建文件", checkpointTurn: 1 };
163 const tool: Item = { kind: "tool", id: "alias-call", name: "bash", args: "printf ok", readOnly: false, status: "done", output: "ok",
164 execution: { kind: "shell", shell: "bash", state: "completed", exitCode: 0, supportsAndAnd: true } };
165 const final: Item = { kind: "assistant", id: "alias-final", text: "已完成。", reasoning: "", streaming: false };
166 const projected = reducer({ ...initialState, items: [user, final, tool, { ...tool }] }, { type: "transcript_records", confirmedUsers: [], projection: {
167 items: [user, tool, final], removeIds: [], startTurn: 1, endTurn: 1, totalTurns: 1, hasOlder: false, hasNewer: false,
168 revision: 1, revisionKnown: true, digest: "alias-regression",
169 } });
170 clearLive(); setItems(projected.items); setRunning(false); setSession(value => value + 1);
171 },
172 replace: count => { clearLive(); setItems(makeTurns(count)); setRunning(false); setReady(value => value + 1); },
173 reset: count => { clearLive(); setItems(makeTurns(Math.min(60, count), Math.max(0, count - 60))); setRunning(false); setSession(value => value + 1); setReady(value => value + 1); },
174 older: () => setItems(previous => { const start = Number(previous[0].id.slice(1)); return [...makeTurns(Math.min(60, start), Math.max(0, start - 60)), ...previous]; }),
175 tick: index => {
176 const assistant = [...itemsRef.current].reverse().find((item): item is Extract<Item, { kind: "assistant" }> => item.kind === "assistant");
177 if (!assistant) return;
178 liveRef.current = { id: assistant.id, text: `Live answer\n\n${"Stable paragraph.\n\n".repeat(index + 1)}`,
179 reasoning: `thinking ${index}`, reasoningComplete: false };
180 setRunning(true);
181 publishLive();
182 },
183 settle: () => {
184 const live = liveRef.current;
185 liveRef.current = undefined;
186 setRunning(false);
187 if (live) setItems(previous => previous.map(item => item.kind === "assistant" && item.id === live.id
188 ? { ...item, text: live.text, reasoning: live.reasoning, reasoningComplete: live.reasoningComplete, streaming: false } : item));
189 publishLive();
190 },
191 switchSession: () => setSession(value => value + 1),
192 prepend: () => setItems(previous => [{ kind: "user", id: `older${previous.length}`, text: "An older question" }, { kind: "assistant", id: `older-answer${previous.length}`, text: "Earlier context\n\n".repeat(20), reasoning: "", streaming: false }, ...previous]),
193 };
194 }, [ready]);
195 return <div style={{ height: "100vh", display: "flex", flexDirection: "column", background: "var(--bg)" }}>
196 {new URLSearchParams(window.location.search).has("deliverables") && <div role="note" style={{ padding: "8px 16px", color: "var(--fg-dim)" }}>组件回放 · 示例数据。此页未连接桌面文件与 Diff 服务;完整交互请在桌面应用中验证。</div>}
197 <Transcript items={items} liveStore={liveStore} tabId="chat-bench" geometrySessionKey={`fixture-${session}`} running={running} onPrompt={() => {}}
198 onFork={() => {}}
199 hasOlderHistory={/^u[1-9]\d*$/.test(items[0]?.id ?? "")} onLoadOlderHistory={() => { window.chatFixture.older(); return true; }} />
200 <div style={{ flex: "none", maxHeight: "40vh", padding: 16 }}><Composer running={running} collaborationMode="normal" toolApprovalMode="ask" modelLabel="DeepSeek" tabId="chat-bench"
201 onSend={() => {}} onCancel={async () => ({ discardedItemIds: [] })} onCycleMode={() => {}} onSetMode={() => {}}
202 onSetCollaborationMode={() => {}} onSetToolApprovalMode={() => {}} onToggleYoloApprovalMode={() => {}}
203 onClearGoal={() => {}} onPauseGoal={() => {}} onResumeGoal={() => {}} onSwitchModel={() => true} onSetEffort={() => {}} />
204 </div>
205 </div>;
206 }
207 createRoot(document.getElementById("root")!).render(<LocaleProvider><ToastProvider><Fixture /></ToastProvider></LocaleProvider>);
208
208 lines Plain Text