返回 DeepSeek-Reasonix
chatMountedOrder.ts
根目录 / desktop / frontend / src / lib / chatMountedOrder.ts
1 /**
2 * The committed subset of a progressively mounted chat order.
3 *
4 * History data may arrive before its DOM nodes are revealed. Consumers such as
5 * the turn navigator subscribe here so every advertised target already exists
6 * in the document. This is ephemeral view state and is discarded with the
7 * active chat session.
8 */
9 export class ChatMountedOrder {
10 private order: readonly string[] = [];
11 private listeners = new Set<() => void>();
12
13 getSnapshot = (): readonly string[] => this.order;
14
15 subscribe = (listener: () => void): (() => void) => {
16 this.listeners.add(listener);
17 return () => this.listeners.delete(listener);
18 };
19
20 publish(next: readonly string[]): void {
21 if (this.order.length === next.length && this.order.every((key, index) => key === next[index])) return;
22 this.order = next;
23 // A synchronous external-store notification may unsubscribe and resubscribe
24 // while React renders. Iterate a snapshot so the new subscription cannot be
25 // visited again by the same publication.
26 for (const listener of [...this.listeners]) listener();
27 }
28
29 dispose(): void {
30 this.order = [];
31 this.listeners.clear();
32 }
33 }
34
35 export const CHAT_HISTORY_MOUNT_BATCH = 24;
36
37 export function reconcileMountedOrder(current: readonly string[], order: readonly string[]): readonly string[] {
38 if (!current.length) return order;
39 const start = order.indexOf(current[0]);
40 const contiguous = start >= 0 && current.every((key, index) => order[start + index] === key);
41 if (!contiguous) return order;
42 const suffix = order.slice(start + current.length);
43 return suffix.length ? [...current, ...suffix] : current;
44 }
45
46 /** Add the nearest leading history nodes while keeping the mounted suffix intact. */
47 export function revealEarlierMountedOrder(current: readonly string[], order: readonly string[]): readonly string[] {
48 if (!current.length) return order;
49 const start = order.indexOf(current[0]);
50 const contiguous = start >= 0 && current.every((key, index) => order[start + index] === key);
51 if (!contiguous) return order;
52 const chunkStart = Math.max(0, start - CHAT_HISTORY_MOUNT_BATCH);
53 const suffix = order.slice(start + current.length);
54 if (chunkStart === start && !suffix.length) return current;
55 return [...order.slice(chunkStart, start), ...current, ...suffix];
56 }
57
57 lines TYPESCRIPT