/** * DebugFixBanner.tsx β€” Live Auto-Debug Toast (S60) * * Mostra all'utente cosa sta facendo il loop agentLoop β†’ debugLoop in tempo reale. * Appare automaticamente quando un tool run_code / execute_shell fallisce e * l'agente tenta di auto-ripararlo. * * Stati: * fixing β†’ pillola arancio animata "πŸ”§ Auto-debug in corso…" * fixed β†’ pillola verde "βœ… Codice corretto automaticamente!" (sparisce dopo 4s) * failed β†’ pillola rossa "❌ Auto-debug: impossibile correggere" (sparisce dopo 3s) * * iPhone-safe: solo CSS transitions, zero Worker/SharedArrayBuffer. * Non mostra StreamChunk β€” filtra solo live_debug_* events. */ import { memo, useState, useEffect, useCallback, useRef } from "react"; import type * as React from "react"; // FIX11: React namespace types import { Wrench, CheckCircle, XCircle } from "lucide-react"; import { useAnyEvent } from "@/core/events/useEventStream"; import type { RuntimeEvent } from "@/core/events/EventTypes"; import { Z_INDEX } from "@/lib/zindex"; // ─── Tipi stato ─────────────────────────────────────────────────────────────── type DebugState = | { phase: "idle" } | { phase: "fixing"; taskId: string; attempt: number; toolName: string } | { phase: "fixed"; taskId: string; attempts: number; snippet?: string } | { phase: "failed"; taskId: string; reason: string }; // ─── Colori e icone per fase ────────────────────────────────────────────────── function stateStyle(phase: DebugState["phase"]): React.CSSProperties { if (phase === "fixing") return { background: "linear-gradient(135deg, rgba(251,146,60,0.18), rgba(245,158,11,0.12))", border: "1px solid rgba(251,146,60,0.4)", color: "#fb923c", }; if (phase === "fixed") return { background: "linear-gradient(135deg, rgba(74,222,128,0.18), rgba(59,130,246,0.12))", border: "1px solid rgba(74,222,128,0.4)", color: "#4ade80", }; return { background: "linear-gradient(135deg, rgba(248,113,113,0.18), rgba(239,68,68,0.10))", border: "1px solid rgba(248,113,113,0.35)", color: "#f87171", }; } // ─── Pillola principale ─────────────────────────────────────────────────────── function DebugPill({ state }: { state: Exclude }) { const { phase } = state; const style = stateStyle(phase); const icon = phase === "fixing" ? : phase === "fixed" ? : ; const label = phase === "fixing" ? `Auto-debug ${state.toolName}: tentativo ${state.attempt}…` : phase === "fixed" ? `Codice corretto in ${state.attempts} tentativ${state.attempts === 1 ? "o" : "i"}` : `Auto-debug non riuscito`; const sub = phase === "fixed" && (state as { phase: "fixed"; snippet?: string }).snippet ? (state as { phase: "fixed"; snippet?: string }).snippet!.slice(0, 80) : phase === "failed" ? (state as { phase: "failed"; reason: string }).reason : ""; return (
{/* icona */} {icon} {/* testo */}
{label} {sub && ( {sub} )}
); } // ─── Banner principale ──────────────────────────────────────────────────────── const DebugFixBanner = memo(function DebugFixBanner() { const [state, setState] = useState({ phase: "idle" }); const hideTimer = useRef | null>(null); const clearHide = () => { if (hideTimer.current !== null) { clearTimeout(hideTimer.current); hideTimer.current = null; } }; const scheduleHide = useCallback((ms: number) => { clearHide(); hideTimer.current = setTimeout(() => setState({ phase: "idle" }), ms); }, []); useEffect(() => () => clearHide(), []); useAnyEvent(useCallback((ev: RuntimeEvent) => { if (ev.kind === "live_debug_start") { clearHide(); setState({ phase: "fixing", taskId: ev.taskId, attempt: ev.attempt, toolName: ev.toolName }); } else if (ev.kind === "live_debug_fix") { setState(prev => prev.phase === "fixing" ? { ...prev, attempt: ev.attempt } : prev ); } else if (ev.kind === "live_debug_success") { setState({ phase: "fixed", taskId: ev.taskId, attempts: ev.attempts }); scheduleHide(4500); } else if (ev.kind === "live_debug_fail") { setState({ phase: "failed", taskId: ev.taskId, reason: ev.reason }); scheduleHide(3000); } }, [scheduleHide])); if (state.phase === "idle") return null; return ( <> {/* Keyframe per l'icona rotante */}
); } ); export default DebugFixBanner;