PluginProbe
ZIP AI – AI Website Builder & AI Agent (Beta) / 0.0.4
ZIP AI – AI Website Builder & AI Agent (Beta) v0.0.4
0.0.10 0.0.9 trunk 0.0.4 0.0.5 0.0.6 0.0.7 0.0.8
zip-ai / src / hooks / useReloadRecovery.js

useReloadRecovery.js in ZIP AI – AI Website Builder & AI Agent (Beta) 0.0.4, at src/hooks/useReloadRecovery.js

65 lines 2.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * useReloadRecovery — Phase 4.2
3 *
4 * On component mount (and whenever the session_id changes), fetch the
5 * most recent brain-authored TurnState snapshot for this session from
6 * Laravel and hydrate `useChatExecutionStore.turnSnapshot`. Restores:
7 * - TurnTerminalBanner (Phase 4.3) if the last turn had errors /
8 * was unresolved / timed out / etc.
9 * - Tool-card terminal states via reconcileToolCallsFromSnapshot.
10 *
11 * Silent on failure: reload recovery is best-effort. If the request
12 * errors (network, auth, 404), the UI continues with whatever live
13 * state it has. A failed recovery should never break the chat.
14 *
15 * Why this exists: before Phase 4.2, a browser refresh after a turn
16 * ended wiped the in-memory `turnSnapshot`, so the banner vanished
17 * and tool-card terminal states reverted to whatever the delta-driven
18 * store reassembled (often wrong). With this hook, the durable copy
19 * in Laravel DB is the restore source.
20 */
21
22 import { useEffect } from 'react';
23 import { sessionLastTurn } from '../services/api';
24 import { useChatExecutionStore } from '../store/useChatExecutionStore';
25
26 /**
27 * @param {string|null|undefined} sessionId Current chat session identifier.
28 * When null/empty, the hook is a no-op (no session, nothing to restore).
29 */
30 export function useReloadRecovery(sessionId) {
31 const applyTurnSnapshot = useChatExecutionStore((s) => s.applyTurnSnapshot);
32 const reconcileToolCallsFromSnapshot = useChatExecutionStore((s) => s.reconcileToolCallsFromSnapshot);
33
34 useEffect(() => {
35 if (!sessionId) return;
36 let cancelled = false;
37
38 (async () => {
39 try {
40 const resp = await sessionLastTurn(sessionId);
41 if (cancelled) return;
42
43 const snapshot = resp?.snapshot;
44 if (!snapshot) return;
45
46 applyTurnSnapshot(snapshot);
47
48 // At terminal time brain's tool_calls are authoritative —
49 // reconcile any store cards that survived from delta-driven
50 // replay so they match brain's final status.
51 if (snapshot.terminal && Array.isArray(snapshot.tool_calls)) {
52 reconcileToolCallsFromSnapshot(snapshot.tool_calls);
53 }
54 } catch (err) {
55 // Best-effort — don't break the chat on recovery failure.
56 // eslint-disable-next-line no-console
57 }
58 })();
59
60 return () => {
61 cancelled = true;
62 };
63 }, [sessionId, applyTurnSnapshot, reconcileToolCallsFromSnapshot]);
64 }
65