返回 DeepSeek-Reasonix
fork-targets.tsx
根目录 / desktop / frontend / bench / fork-targets.tsx
1 // Browser fixture for the transcript's turn-fork entry. It renders the real
2 // Transcript with the props the app shell passes and drives the real
3 // create-only binding, so a browser run shows exactly the states a user sees.
4 import { useCallback, useLayoutEffect, useState } from "react";
5 import { createRoot } from "react-dom/client";
6 import { Transcript } from "../src/components/Transcript";
7 import { LocaleProvider, useI18n } from "../src/lib/i18n";
8 import { app } from "../src/lib/bridge";
9 import { forkCreateFailureText, type ForkTargetSetView, type ForkTargetView } from "../src/lib/forkTargets";
10 import type { Item } from "../src/lib/useController";
11 import "../src/styles.css";
12
13 // One persisted turn record: the boundary a fork may cut at, addressed by the
14 // message identity of the turn's final answer.
15 type TurnRecord = { turnId: string; user: string; answer: string; messageId?: string; open?: boolean };
16 type SourceName = "completed" | "open" | "recordless";
17
18 const SOURCES: Record<SourceName, { records: TurnRecord[]; verifiable: boolean; running: boolean }> = {
19 completed: {
20 records: [
21 { turnId: "turn-1", user: "Explain the retry policy", answer: "The retry policy backs off exponentially.", messageId: "a1" },
22 { turnId: "turn-2", user: "Now summarize it", answer: "It retries with exponential backoff and a cap.", messageId: "a2" },
23 ],
24 verifiable: true,
25 running: false,
26 },
27 open: {
28 records: [
29 { turnId: "turn-1", user: "Explain the retry policy", answer: "The retry policy backs off exponentially.", messageId: "a1" },
30 { turnId: "turn-2", user: "Now summarize it", answer: "Summarizing the policy while the turn runs…", open: true },
31 ],
32 verifiable: true,
33 running: true,
34 },
35 recordless: {
36 records: [{ turnId: "turn-1", user: "Legacy question", answer: "A legacy answer with no turn record." }],
37 verifiable: false,
38 running: false,
39 },
40 };
41
42 // The host derives one target per persisted turn; a turn that has not closed has
43 // no boundary, and a recordless source proves none at all.
44 function targetsOf(source: SourceName, records: TurnRecord[]): ForkTargetSetView {
45 return {
46 sourceSessionId: `bench-${source}`, sessionGeneration: 1,
47 targets: records.map((record, index) => record.open || !record.messageId
48 ? { sourceSessionId: `bench-${source}`, sessionGeneration: 1, turnId: record.turnId, boundarySequence: 0, turnNumber: index + 1, status: record.open ? "in_progress" : "committed", available: false, reason: "turn_open" }
49 : { sourceSessionId: `bench-${source}`, sessionGeneration: 1, turnId: record.turnId, boundarySequence: (index + 1) * 3, turnNumber: index + 1, status: "committed", messageId: record.messageId, available: true }),
50 verifiable: SOURCES[source].verifiable,
51 };
52 }
53
54 // Items carry the identity the transcript keys on: `m:<messageId>` when the
55 // record keeps one, the positional history key when it does not.
56 function itemsOf(records: TurnRecord[]): Item[] {
57 return records.flatMap((record): Item[] => [
58 { kind: "user", id: `u:${record.turnId}`, text: record.user, checkpointTurn: Number(record.turnId.split("-")[1]) },
59 { kind: "assistant", id: record.messageId ? `m:${record.messageId}` : `h:${record.turnId}`, text: record.answer, reasoning: "", streaming: Boolean(record.open) },
60 ]);
61 }
62
63 declare global {
64 interface Window {
65 forkFixture: {
66 source(name: SourceName): void;
67 locale(locale: "en" | "zh"): void;
68 calls(): Array<{ turnId: string; operationId: string }>;
69 notice(): string;
70 };
71 }
72 }
73
74 function Fixture() {
75 const [source, setSource] = useState<SourceName>("completed");
76 const [calls, setCalls] = useState<Array<{ turnId: string; operationId: string }>>([]);
77 const [notice, setNotice] = useState("");
78 const { t, setPref } = useI18n();
79 const { records, verifiable, running } = SOURCES[source];
80
81 // The same create-only binding the app shell clicks through: the host returns
82 // an operation id, and every refusal is surfaced as the user's notice.
83 const onFork = useCallback(async (target: ForkTargetView) => {
84 setNotice("");
85 try {
86 const created = await app.CreateForkForTab("bench-fork-tab", target);
87 setCalls((current) => [...current, { turnId: target.turnId, operationId: created.operationId ?? "" }]);
88 if (created?.opened) {
89 if (created.operationId) await app.AcknowledgeForkOperation("bench-fork-tab", created.operationId);
90 return;
91 }
92 setNotice(created?.sessionId
93 ? t("chat.branchRecoverChild", { session: created.sessionId })
94 : t("chat.branchFailedDetail", { detail: created?.error ?? "" }));
95 } catch (error) {
96 setNotice(forkCreateFailureText(error));
97 }
98 }, [t]);
99
100 useLayoutEffect(() => {
101 window.forkFixture = {
102 source: (name) => { setSource(name); setCalls([]); setNotice(""); },
103 locale: (locale) => setPref(locale),
104 calls: () => calls,
105 notice: () => notice,
106 };
107 }, [calls, notice, setPref]);
108
109 return <div style={{ height: "40vh", display: "flex", flexDirection: "column", background: "var(--bg)" }}>
110 <Transcript items={itemsOf(records)} geometrySessionKey={`fixture-${source}`} running={running} tabId="bench-fork-tab"
111 onPrompt={() => {}} onFork={(target) => void onFork(target)} forkTargets={targetsOf(source, records)} forkBlocked={null} />
112 {notice && <div role="status" data-fork-notice style={{ flex: "none", padding: "4px 16px", color: "var(--text)" }}>{notice}</div>}
113 <div data-fork-calls hidden>{calls.map((call, index) => <span key={index} data-fork-call data-turn={call.turnId}>{call.operationId}</span>)}</div>
114 </div>;
115 }
116
117 createRoot(document.getElementById("root")!).render(<LocaleProvider><Fixture /></LocaleProvider>);
118
118 lines Plain Text