返回 DeepSeek-Reasonix
transcriptContentRead.ts
根目录 / desktop / frontend / src / lib / transcriptContentRead.ts
1 import { resolvedHistoryField } from "./canonicalTranscriptBackend";
2 import { applyResolvedField, type TranscriptRecord } from "./transcriptRecordProjection";
3 import { recordBytes } from "./transcriptRecordBytes";
4 import type { SessionTranscript } from "./transcriptStoreTypes";
5 import type { HistoryContentChunk, HistoryContentRef } from "./types";
6
7 interface ContentReadOwner {
8 locate(entryId: string): { session: SessionTranscript; entryId: string } | undefined;
9 resident(session: SessionTranscript): boolean;
10 read(ref: HistoryContentRef, index: number): Promise<HistoryContentChunk>;
11 publish(session: SessionTranscript, record: TranscriptRecord): void;
12 }
13
14 /** Resolve against one resident owner, with one handoff to its replacement cut. */
15 export async function readTranscriptContent(
16 owner: ContentReadOwner, alias: string, field: string,
17 previous?: SessionTranscript, handoff = true,
18 ): Promise<string | undefined> {
19 const location = owner.locate(alias);
20 if (!location || (previous && previous !== location.session)) return undefined;
21 const { session, entryId } = location;
22 const generation = session.generation;
23 const settlement = session.generationSettlement;
24 if (settlement?.generation === generation) await settlement.promise;
25 if (!owner.resident(session) || generation !== session.generation) return undefined;
26 const rec = session.byId.get(entryId);
27 if (!rec) return undefined;
28 if (rec.resolved?.[field] !== undefined) return rec.resolved[field];
29 const ref = rec.refs.find(candidate => candidate.field === field || candidate.field === "canonicalMessage");
30 if (!ref) return undefined;
31 const pendingKey = JSON.stringify([entryId, field]);
32 const pending = session.pendingContent.get(pendingKey);
33 if (pending?.generation === generation) return pending.promise;
34 const request = (async (): Promise<string | undefined> => {
35 let data = "";
36 for (let index = 0; index < Math.max(1, ref.chunks); index++) {
37 const chunk = await owner.read(ref, index);
38 // Closing/evicting is terminal, including a reopened tab with the same ID.
39 if (!owner.resident(session)) return undefined;
40 if (session.generation !== generation) {
41 return handoff ? readTranscriptContent(owner, alias, field, session, false) : undefined;
42 }
43 if (session.byId.get(entryId) !== rec) return undefined;
44 if (chunk.stale) {
45 rec.staleRefs = { ...rec.staleRefs, [field]: true };
46 return undefined;
47 }
48 data += chunk.data ?? "";
49 if (chunk.done) break;
50 }
51 if (!applyResolvedField(rec, ref, data)) return undefined;
52 const previousBytes = rec.bytes;
53 rec.bytes = recordBytes(rec.message);
54 session.bodyBytes += rec.bytes - previousBytes;
55 const value = ref.field === "canonicalMessage" ? resolvedHistoryField(rec.message, field) : data;
56 if (value === undefined) return undefined;
57 rec.resolved = { ...rec.resolved, [field]: value };
58 owner.publish(session, rec);
59 return value;
60 })();
61 const entry = { generation, promise: request };
62 const release = () => {
63 if (session.pendingContent.get(pendingKey) === entry) session.pendingContent.delete(pendingKey);
64 };
65 session.pendingContent.set(pendingKey, entry);
66 void request.then(release, release);
67 return request;
68 }
69
69 lines TYPESCRIPT