返回 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 } from "../src/lib/useController";
11 import "../src/styles.css";
12
13 function makeTurns(count: number, start = 0): Item[] {
14 return Array.from({ length: count }, (_, position): Item[] => { const index = start + position; return [
15 { kind: "user", id: `u${index}`, text: `Question ${index + 1}: Explain the implementation`, checkpointTurn: index + 1 },
16 { 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" },
17 { 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,
18 createdAt: new Date(2026, 8, 12, 12, 34).getTime(), turnDurationMs: 29_000, tokensPerSecond: 183,
19 turnUsage: { totalTokens: 185_225, uncachedInputTokens: 26_278, cacheReadTokens: 155_520, outputTokens: 3_427, reasoningTokens: 1_909, routes: ["deepseek-official/deepseek-flash"] } },
20 ]; }).flat();
21 }
22 function weatherTurn(): Item[] {
23 const end = makeTurns(1)[2];
24 return [
25 { kind: "user", id: "weather-user", text: "查一下今天上海的天气", checkpointTurn: 1 },
26 { kind: "assistant", id: "weather-thought", text: "", reasoning: "先查询实时天气,再交叉核对来源。", streaming: false },
27 { kind: "tool", id: "weather-search", name: "web_search", args: JSON.stringify({ query: "上海今天天气 实时温度" }), output: "Weather sources", searchSources: [{ title: "天气数据(测试来源)", url: "https://example.com/weather" }], status: "done" },
28 { kind: "tool", id: "weather-error", name: "web_fetch", args: JSON.stringify({ url: "https://example.com/weather" }), error: "DNS lookup failed", status: "error" },
29 { kind: "assistant", id: "weather-progress", text: "一个来源暂时无法访问,正在核对另一来源。", reasoning: "切换到另一来源。", streaming: false },
30 { 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" },
31 { kind: "tool", id: "weather-bash", name: "bash", args: JSON.stringify({ command: "printf '25.5°C 晴\\n'", description: "获取并核对上海天气" }), output: "25.5°C 晴\n", status: "done",
32 execution: { kind: "shell", shell: "bash", state: "completed", exitCode: 0, supportsAndAnd: true } },
33 { 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",
34 presentedFiles: [{ path: "output/shanghai-weather.html", description: "交互式天气报告" }, { path: "output/weather-notes.md", description: "数据来源与说明" }] },
35 { ...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,
36 ];
37 }
38 declare global { interface Window { chatFixture: { authored(): 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 } } }
39 function Fixture() {
40 const [items, setItems] = useState(() => makeTurns(20));
41 const [session, setSession] = useState(0);
42 const [running, setRunning] = useState(false);
43 const [ready, setReady] = useState(0);
44 const itemsRef = useRef(items);
45 itemsRef.current = items;
46 const liveRef = useRef<LiveStream>();
47 const liveListeners = useRef(new Set<() => void>());
48 // Production stream deltas bypass the full controller tree through this
49 // store. Keep the benchmark on that path so input timing measures the UI
50 // users run instead of rebuilding the complete history fixture per token.
51 const liveStore = useMemo<ControllerLiveStore>(() => ({
52 subscribe: (_tabId, listener) => {
53 liveListeners.current.add(listener);
54 return () => { liveListeners.current.delete(listener); };
55 },
56 getSnapshot: () => liveRef.current,
57 }), []);
58 const publishLive = () => liveListeners.current.forEach(listener => listener());
59 const clearLive = () => { liveRef.current = undefined; publishLive(); };
60 useLayoutEffect(() => {
61 window.chatFixture = {
62 ready, pending: () => getMarkdownWorkerClient().stats().pending,
63 authored: () => {
64 const messages = [
65 { role: "user", origin: "host", content: '<session-context version="1">private environment</session-context>' },
66 { role: "user", origin: "user", content: '<response-language>internal policy</response-language>你是谁', raw_content: "你是谁" },
67 { role: "user", content: '<capability-route>legacy internal route</capability-route>旧会话问题' },
68 { role: "user", origin: "user", raw_content: '<response-language>用户引用的 XML</response-language>' },
69 { role: "assistant", content: "我是 Reasonix。" },
70 ].map((raw, i) => canonicalMessage({ messageId: `authored-${i}`, position: i, version: 1, role: raw.role }, raw));
71 clearLive(); setItems(historyMessagesToItems(messages, "authored").items); setRunning(false); setSession(value => value + 1);
72 },
73 weather: () => { clearLive(); setItems(weatherTurn()); setRunning(false); setSession(value => value + 1); },
74 replace: count => { clearLive(); setItems(makeTurns(count)); setRunning(false); setReady(value => value + 1); },
75 reset: count => { clearLive(); setItems(makeTurns(Math.min(60, count), Math.max(0, count - 60))); setRunning(false); setSession(value => value + 1); setReady(value => value + 1); },
76 older: () => setItems(previous => { const start = Number(previous[0].id.slice(1)); return [...makeTurns(Math.min(60, start), Math.max(0, start - 60)), ...previous]; }),
77 tick: index => {
78 const assistant = [...itemsRef.current].reverse().find((item): item is Extract<Item, { kind: "assistant" }> => item.kind === "assistant");
79 if (!assistant) return;
80 liveRef.current = { id: assistant.id, text: `Live answer\n\n${"Stable paragraph.\n\n".repeat(index + 1)}`,
81 reasoning: `thinking ${index}`, reasoningComplete: false };
82 setRunning(true);
83 publishLive();
84 },
85 settle: () => {
86 const live = liveRef.current;
87 liveRef.current = undefined;
88 setRunning(false);
89 if (live) setItems(previous => previous.map(item => item.kind === "assistant" && item.id === live.id
90 ? { ...item, text: live.text, reasoning: live.reasoning, reasoningComplete: live.reasoningComplete, streaming: false } : item));
91 publishLive();
92 },
93 switchSession: () => setSession(value => value + 1),
94 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]),
95 };
96 }, [ready]);
97 return <div style={{ height: "100vh", display: "flex", flexDirection: "column", background: "var(--bg)" }}>
98 <Transcript items={items} liveStore={liveStore} tabId="chat-bench" geometrySessionKey={`fixture-${session}`} running={running} onPrompt={() => {}}
99 onFork={() => {}} hasOlderHistory={items[0]?.id !== "u0"} onLoadOlderHistory={() => { window.chatFixture.older(); return true; }} />
100 <div style={{ flex: "none", maxHeight: "40vh", padding: 16 }}><Composer running={running} collaborationMode="normal" toolApprovalMode="ask" modelLabel="DeepSeek" tabId="chat-bench"
101 onSend={() => {}} onCancel={async () => ({ discardedItemIds: [] })} onCycleMode={() => {}} onSetMode={() => {}}
102 onSetCollaborationMode={() => {}} onSetToolApprovalMode={() => {}} onToggleYoloApprovalMode={() => {}}
103 onClearGoal={() => {}} onPauseGoal={() => {}} onResumeGoal={() => {}} onSwitchModel={() => true} onSetEffort={() => {}} />
104 </div>
105 </div>;
106 }
107 createRoot(document.getElementById("root")!).render(<LocaleProvider><ToastProvider><Fixture /></ToastProvider></LocaleProvider>);
108
108 lines Plain Text