返回 DeepSeek-Reasonix
uiPerfScenarios.ts
根目录 / desktop / frontend / src / lib / uiPerfScenarios.ts
1 // UI performance scenarios: the workloads the streaming pipeline must stay
2 // smooth under — not just "a normal chat answer". Deterministic generators let
3 // the simulated harness (ui-perf-scenarios.test.ts) assert count budgets in
4 // CI, and the same specs are the contract for the future real-browser
5 // (Playwright) driver, which owns the wall-clock/fps side.
6
7 export type ScenarioDriver = "simulated" | "browser";
8
9 export interface UIPerfScenario {
10 id: string;
11 title: string;
12 chunksPerSec: number;
13 reasoningChars: number;
14 reasoningVisible: boolean;
15 textChars: number;
16 paragraphs: number;
17 codeFences: number;
18 // browser-driver scenarios need a real input pipeline (typing, wheel,
19 // selection, tool-card interaction) or a real DOM/session to be meaningful.
20 driver: ScenarioDriver;
21 notes?: string;
22 }
23
24 export const UI_PERF_SCENARIOS: UIPerfScenario[] = [
25 {
26 id: "UI-PERF-01",
27 title: "normal answer: 2KB markdown prose",
28 chunksPerSec: 80,
29 reasoningChars: 0,
30 reasoningVisible: false,
31 textChars: 2_000,
32 paragraphs: 8,
33 codeFences: 0,
34 driver: "simulated",
35 },
36 {
37 id: "UI-PERF-02",
38 title: "DeepSeek reasoning: 16KB reasoning + 8KB answer",
39 chunksPerSec: 150,
40 reasoningChars: 16_000,
41 reasoningVisible: true,
42 textChars: 8_000,
43 paragraphs: 16,
44 codeFences: 0,
45 driver: "simulated",
46 },
47 {
48 id: "UI-PERF-03",
49 title: "code heavy: 10 fences in 20KB markdown",
50 chunksPerSec: 120,
51 reasoningChars: 0,
52 reasoningVisible: false,
53 textChars: 20_000,
54 paragraphs: 10,
55 codeFences: 10,
56 driver: "simulated",
57 },
58 {
59 id: "UI-PERF-04",
60 title: "interaction under streaming: typing, wheel, selection, tool cards",
61 chunksPerSec: 150,
62 reasoningChars: 8_000,
63 reasoningVisible: true,
64 textChars: 8_000,
65 paragraphs: 12,
66 codeFences: 2,
67 driver: "browser",
68 notes: "The point is input latency while streaming, not stream fps; needs the real input pipeline.",
69 },
70 {
71 id: "UI-PERF-05",
72 title: "long session: 100 turns, 30 tool calls, diffs and code",
73 chunksPerSec: 100,
74 reasoningChars: 0,
75 reasoningVisible: false,
76 textChars: 4_000,
77 paragraphs: 10,
78 codeFences: 2,
79 driver: "simulated",
80 notes: "Per-delta cost must not scale with transcript length; DOM/heap steady state belongs to the browser driver.",
81 },
82 {
83 id: "UI-PERF-06",
84 title: "background agent: tab A streams while tab B is active",
85 chunksPerSec: 150,
86 reasoningChars: 4_000,
87 reasoningVisible: false,
88 textChars: 4_000,
89 paragraphs: 8,
90 codeFences: 0,
91 driver: "simulated",
92 },
93 ];
94
95 export interface ScenarioChunk {
96 kind: "text" | "reasoning";
97 delta: string;
98 }
99
100 function fill(prefix: string, target: number, sentence: string): string {
101 let out = prefix;
102 while (out.length < target) out += sentence;
103 return out.slice(0, target);
104 }
105
106 // generateScenarioChunks produces the scenario's full stream as fixed-size
107 // deltas: reasoning first, then answer text with the requested paragraph and
108 // fence structure. Content is deterministic filler — budgets are about shape
109 // (blocks, fences, rates), never about wording.
110 export function generateScenarioChunks(spec: UIPerfScenario, chunkChars = 12): ScenarioChunk[] {
111 const chunks: ScenarioChunk[] = [];
112 const emit = (kind: "text" | "reasoning", content: string) => {
113 for (let i = 0; i < content.length; i += chunkChars) {
114 chunks.push({ kind, delta: content.slice(i, i + chunkChars) });
115 }
116 };
117 if (spec.reasoningChars > 0) {
118 emit("reasoning", fill("thinking: ", spec.reasoningChars, "considering the next step carefully. "));
119 }
120 const blocks: string[] = [];
121 const proseTarget = Math.max(1, Math.floor((spec.textChars * (spec.codeFences > 0 ? 0.5 : 1)) / spec.paragraphs));
122 for (let p = 0; p < spec.paragraphs; p += 1) {
123 blocks.push(fill(`Paragraph ${p} with **bold** and \`code\`. `, proseTarget, "More explanatory prose follows here. "));
124 }
125 if (spec.codeFences > 0) {
126 const codeTarget = Math.max(24, Math.floor((spec.textChars * 0.5) / spec.codeFences));
127 for (let f = 0; f < spec.codeFences; f += 1) {
128 const body = fill(`function block${f}() {\n`, codeTarget, ` compute(${f});\n`);
129 blocks.push(`\`\`\`js\n${body}\n\`\`\``);
130 }
131 }
132 emit("text", blocks.join("\n\n") + "\n\n");
133 return chunks;
134 }
135
135 lines TYPESCRIPT