返回 DeepSeek-Reasonix
useComposerInboxRefresh.ts
根目录 / desktop / frontend / src / lib / useComposerInboxRefresh.ts
1 import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
2 import type { InboxSnapshotLike } from "./composerInboxQueue";
3 import { app } from "./bridge";
4 import {
5 guidanceFromInboxSnapshot,
6 hydrateEmptyGuidancePreviews,
7 inboxSnapshotBelongsToScope,
8 localGuidanceFallback,
9 mergeGuidanceSnapshot,
10 } from "./composerInboxQueue";
11 import { onInboxChanged } from "./inboxEvents";
12 import type { PendingGuidance } from "../components/ComposerGuidanceShelf";
13
14 export function useComposerInboxRefresh(
15 tabId: string | undefined,
16 draftKey: string,
17 guidanceDraftKey: string,
18 inboxSessionKey: string,
19 previewKey: string,
20 retryNonce: number,
21 running: boolean,
22 applyQueue: (items: PendingGuidance[]) => void,
23 collapse: () => void,
24 bump: () => void,
25 runtimeRevision?: number,
26 ) {
27 const inboxSessionKeyRef = useRef(inboxSessionKey);
28 const [snapshot, setSnapshot] = useState<InboxSnapshotLike>();
29 const appliedRevisionRef = useRef<{ scope: string; revision: number }>({ scope: inboxSessionKey, revision: -1 });
30 const clearQueue = useCallback(() => applyQueue([]), [applyQueue]);
31 useLayoutEffect(() => {
32 if (inboxSessionKeyRef.current === inboxSessionKey) return;
33 inboxSessionKeyRef.current = inboxSessionKey;
34 appliedRevisionRef.current = { scope: inboxSessionKey, revision: -1 };
35 setSnapshot(undefined);
36 collapse();
37 clearQueue();
38 }, [draftKey, inboxSessionKey, clearQueue, collapse]);
39 const acceptSnapshot = useCallback((snap: InboxSnapshotLike) => {
40 if (inboxSessionKeyRef.current !== inboxSessionKey || !inboxSnapshotBelongsToScope(snap.sessionPath, inboxSessionKey)) return false;
41 const revision = Number(snap.revision ?? -1);
42 if (!Number.isFinite(revision) || (appliedRevisionRef.current.scope === inboxSessionKey && revision < appliedRevisionRef.current.revision)) return false;
43 appliedRevisionRef.current = { scope: inboxSessionKey, revision };
44 setSnapshot(snap);
45 applyQueue(mergeGuidanceSnapshot(guidanceFromInboxSnapshot(snap), localGuidanceFallback(previewKey)));
46 return true;
47 }, [inboxSessionKey, applyQueue, previewKey]);
48 useEffect(() => onInboxChanged((changed) => {
49 if (changed.tabId && tabId && changed.tabId !== tabId) return;
50 if (!inboxSnapshotBelongsToScope(changed.sessionPath, inboxSessionKey)) return;
51 if (changed.revision !== undefined
52 && appliedRevisionRef.current.scope === inboxSessionKey
53 && changed.revision <= appliedRevisionRef.current.revision) return;
54 bump();
55 }), [tabId, inboxSessionKey, bump]);
56 useEffect(() => {
57 if (guidanceDraftKey !== draftKey) return;
58 let live = true;
59 const fallback = localGuidanceFallback(previewKey);
60 if (typeof app.InboxSnapshot !== "function") {
61 applyQueue(fallback);
62 return;
63 }
64 void app.InboxSnapshot(tabId || "").then((snap) => {
65 if (!live || !inboxSnapshotBelongsToScope(snap?.sessionPath, inboxSessionKey)) return;
66 if (!acceptSnapshot(snap)) return;
67 const revision = Number(snap?.revision ?? -1);
68 const durable = guidanceFromInboxSnapshot(snap);
69 applyQueue(mergeGuidanceSnapshot(durable, fallback));
70 void hydrateEmptyGuidancePreviews(durable, (id) => app.ReadInboxItem(tabId || "", id)).then((hydrated) => {
71 if (live && appliedRevisionRef.current.revision === revision && hydrated.some((item) => item.text.trim())) {
72 applyQueue(mergeGuidanceSnapshot(hydrated, fallback));
73 }
74 });
75 }).catch(() => {
76 // A transport failure must not erase the last authoritative queue.
77 if (live && appliedRevisionRef.current.revision < 0) applyQueue(localGuidanceFallback(previewKey));
78 });
79 return () => { live = false; };
80 }, [draftKey, guidanceDraftKey, previewKey, running, tabId, retryNonce, inboxSessionKey, applyQueue, runtimeRevision, acceptSnapshot]);
81 return { snapshot, acceptSnapshot };
82 }
83
83 lines TYPESCRIPT