import { memo, useState, useRef, useEffect } from "react"; import { getLocalBackendStatus } from "@/lib/providerChain"; import { Z_INDEX } from "@/lib/zindex"; interface Props { onClose: () => void; onAvailable: () => void; } const STEPS = [ { n: 1, title: "Installa Ollama", desc: "Scarica e installa il runtime AI locale (gratuito, open source)", cmd: null as string | null, link: { label: "Scarica Ollama →", url: "https://ollama.com/download" }, hint: "Disponibile per macOS, Linux, Windows", }, { n: 2, title: "Avvia Ollama", desc: "Apri il Terminale ed esegui", cmd: "ollama serve", link: null, hint: "Lascia il terminale aperto", }, { n: 3, title: "Scarica un modello AI", desc: "Scegli un modello (la prima volta scarica il file)", cmd: "ollama pull qwen2.5-coder:7b", link: null, hint: "~4GB — oppure phi4-mini (~2GB) per Mac con poca RAM", }, { n: 4, title: "Avvia il backend", desc: "Nella cartella backend/ del progetto esegui", cmd: "bash start.sh", link: null, hint: "Installa dipendenze Python e avvia il server locale su porta 8000", }, ]; const BackendSetupModal = memo(function BackendSetupModal({ onClose, onAvailable }: Props) { const [currentStep, setCurrentStep] = useState(0); const [checking, setChecking] = useState(false); const [detected, setDetected] = useState(false); const [copied, setCopied] = useState(null); const pollRef = useRef | null>(null); // [S47-bugfix] Stable ref for onAvailable — avoids useEffect re-running every render // (inline arrow functions from parent create new references each render) const onAvailableRef = useRef(onAvailable); onAvailableRef.current = onAvailable; // Poll for backend while modal is open — empty deps, stable via ref useEffect(() => { let mounted = true; pollRef.current = setInterval(async () => { try { const s = await getLocalBackendStatus(); if (!mounted) return; if (s.available) { setDetected(true); clearInterval(pollRef.current!); setTimeout(() => onAvailableRef.current(), 1500); } } catch { /* non-fatal — riprova al prossimo tick */ } }, 4000); return () => { mounted = false; clearInterval(pollRef.current!); }; }, []); // ← stable: onAvailable always current via ref const checkNow = async () => { setChecking(true); const s = await getLocalBackendStatus(); setChecking(false); if (s.available) { setDetected(true); setTimeout(() => onAvailableRef.current(), 1200); } }; const copyCmd = (cmd: string, idx: number) => { navigator.clipboard.writeText(cmd).catch(() => {}); setCopied(idx); setTimeout(() => setCopied(null), 2000); }; if (detected) { return (

Backend locale rilevato!

L'AI locale è attiva. Le prossime chat useranno Ollama invece del cloud.

); } return (
{ if (e.target === e.currentTarget) onClose(); }} >
{/* Header */}

Attiva AI locale

Ollama · privato · gratuito · nessun cloud

{/* Steps */}

Segui questi passaggi una sola volta — poi l'app lo rileva automaticamente.

{/* Step tabs */}
{STEPS.map((s, i) => (
{/* Current step */} {(() => { const step = STEPS[currentStep]; return (
{step.n}
{step.title}

{step.desc}

{step.cmd && (
{step.cmd}
)} {step.link && ( {step.link.label} )}

{step.hint}

); })()} {/* Navigation */}
{currentStep < STEPS.length - 1 && ( )}
{/* Auto-detect notice */}
Rilevamento automatico ogni 4 secondi — la finestra si chiuderà da sola
); } ); export default BackendSetupModal;