| 1 | import { useCallback, useEffect, useLayoutEffect, useRef } from "react"; |
| 2 | import { app } from "./bridge"; |
| 3 | import { |
| 4 | guidanceFromInboxSnapshot, |
| 5 | hydrateEmptyGuidancePreviews, |
| 6 | inboxSnapshotBelongsToScope, |
| 7 | localGuidanceFallback, |
| 8 | mergeGuidanceSnapshot, |
| 9 | } from "./composerInboxQueue"; |
| 10 | import { onInboxChanged } from "./inboxEvents"; |
| 11 | import type { PendingGuidance } from "../components/ComposerGuidanceShelf"; |
| 12 | |
| 13 | export function useComposerInboxRefresh( |
| 14 | tabId: string | undefined, |
| 15 | draftKey: string, |
| 16 | guidanceDraftKey: string, |
| 17 | inboxSessionKey: string, |
| 18 | previewKey: string, |
| 19 | retryNonce: number, |
| 20 | running: boolean, |
| 21 | applyQueue: (items: PendingGuidance[]) => void, |
| 22 | collapse: () => void, |
| 23 | bump: () => void, |
| 24 | runtimeRevision?: number, |
| 25 | ) { |
| 26 | const inboxSessionKeyRef = useRef(inboxSessionKey); |
| 27 | const appliedRevisionRef = useRef<{ scope: string; revision: number }>({ scope: inboxSessionKey, revision: -1 }); |
| 28 | const clearQueue = useCallback(() => applyQueue([]), [applyQueue]); |
| 29 | useLayoutEffect(() => { |
| 30 | if (inboxSessionKeyRef.current === inboxSessionKey) return; |
| 31 | inboxSessionKeyRef.current = inboxSessionKey; |
| 32 | appliedRevisionRef.current = { scope: inboxSessionKey, revision: -1 }; |
| 33 | clearQueue(); |
| 34 | }, [draftKey, inboxSessionKey, clearQueue]); |
| 35 | useEffect(() => onInboxChanged((changed) => { |
| 36 | if (changed.tabId && tabId && changed.tabId !== tabId) return; |
| 37 | if (!inboxSnapshotBelongsToScope(changed.sessionPath, inboxSessionKey)) return; |
| 38 | if (changed.revision !== undefined |
| 39 | && appliedRevisionRef.current.scope === inboxSessionKey |
| 40 | && changed.revision <= appliedRevisionRef.current.revision) return; |
| 41 | bump(); |
| 42 | }), [tabId, inboxSessionKey, bump]); |
| 43 | useEffect(() => { |
| 44 | if (guidanceDraftKey !== draftKey) return; |
| 45 | let live = true; |
| 46 | const fallback = localGuidanceFallback(previewKey); |
| 47 | if (typeof app.InboxSnapshot !== "function") { |
| 48 | applyQueue(fallback); |
| 49 | collapse(); |
| 50 | return; |
| 51 | } |
| 52 | void app.InboxSnapshot(tabId || "").then((snap) => { |
| 53 | if (!live || !inboxSnapshotBelongsToScope(snap?.sessionPath, inboxSessionKey)) return; |
| 54 | const rawRevision = Number(snap?.revision ?? -1); |
| 55 | const revision = Number.isFinite(rawRevision) ? rawRevision : -1; |
| 56 | if (appliedRevisionRef.current.scope === inboxSessionKey && revision < appliedRevisionRef.current.revision) return; |
| 57 | appliedRevisionRef.current = { scope: inboxSessionKey, revision }; |
| 58 | const durable = guidanceFromInboxSnapshot(snap); |
| 59 | applyQueue(mergeGuidanceSnapshot(durable, fallback)); |
| 60 | collapse(); |
| 61 | void hydrateEmptyGuidancePreviews(durable, (id) => app.ReadInboxItem(tabId || "", id)).then((hydrated) => { |
| 62 | if (live && hydrated.some((item) => item.text.trim())) { |
| 63 | applyQueue(mergeGuidanceSnapshot(hydrated, fallback)); |
| 64 | } |
| 65 | }); |
| 66 | }).catch(() => { |
| 67 | if (live) applyQueue(localGuidanceFallback(previewKey)); |
| 68 | }); |
| 69 | return () => { live = false; }; |
| 70 | }, [draftKey, guidanceDraftKey, previewKey, running, tabId, retryNonce, inboxSessionKey, applyQueue, collapse, runtimeRevision]); |
| 71 | } |
| 72 |