Spaces:
Sleeping
Sleeping
File size: 2,737 Bytes
cc11e77 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | /**
* useAgentFileChanged.ts — Fix D: watch mode preview signal listener
*
* Ascolta il CustomEvent "agent:file-changed" emesso da toolExecutor.ts
* ogni volta che apply_patch / write_file / create_file / iterate_file
* completano con successo (tool non-errore).
*
* Design:
* - Safari-safe: CustomEvent + addEventListener (nessun BroadcastChannel)
* - Zero-cost se enabled=false: nessun listener registrato
* - Debounce: batch di scritture rapide consecutive → un solo callback
* - Stable callback ref: il callback può aggiornarsi senza re-subscribe
*
* Emitter (toolExecutor.ts Fix D):
* window.dispatchEvent(new CustomEvent("agent:file-changed", { detail: { tool, t } }))
*
* @module useAgentFileChanged
*/
import { useEffect, useRef } from "react";
export interface AgentFileChangedDetail {
/** Nome del tool che ha modificato il file (apply_patch, write_file, …) */
tool: string;
/** Timestamp Unix ms dell'evento */
t: number;
}
/**
* Registra un listener per "agent:file-changed" con debounce.
*
* @param onChanged — callback stabile: chiamata al massimo ogni `debounceMs`
* @param debounceMs — debounce tra eventi ravvicinati (default 300ms)
* @param enabled — false = nessun listener (default true)
*
* @example
* // In PreviewSandbox: refresh automatico quando l'agente scrive un file
* useAgentFileChanged(
* useCallback(() => { if (html) runtime.refresh("auto"); }, [html, runtime]),
* );
*/
export function useAgentFileChanged(
onChanged: (detail: AgentFileChangedDetail) => void,
debounceMs = 300,
enabled = true,
): void {
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
// Stable callback ref — aggiornato ogni render senza rifare il subscribe
const callbackRef = useRef(onChanged);
useEffect(() => { callbackRef.current = onChanged; });
useEffect(() => {
if (!enabled || typeof window === "undefined") return;
const handler = (e: Event) => {
const detail = (e as CustomEvent<AgentFileChangedDetail>).detail
?? { tool: "unknown", t: Date.now() };
// Debounce: cancella il timer precedente e rischedula
if (timerRef.current !== null) clearTimeout(timerRef.current);
timerRef.current = setTimeout(() => {
timerRef.current = null;
callbackRef.current(detail);
}, debounceMs);
};
window.addEventListener("agent:file-changed", handler);
return () => {
window.removeEventListener("agent:file-changed", handler);
if (timerRef.current !== null) {
clearTimeout(timerRef.current);
timerRef.current = null;
}
};
}, [enabled, debounceMs]); // callbackRef è stabile — non serve in deps
}
|