/** * browserLLM.ts * Infrastruttura per AI locale browser-side (WebGPU / transformers.js). * * STATO: nessun modello locale è integrato (WebLLM/transformers.js richiedono * ~3–4 GB di download, non adatti a iPhone). browserGenerate() ora lancia un * errore esplicito (Gap 1 fix S483) invece di simulare una risposta falsa. * providerChain.ts cattura l'errore (non-AbortError) e cade sull'esaurimento * provider, mostrando un messaggio onesto all'utente. * * Compatibile con: ProviderIndicator · consensusMode · providerChain */ import type { ChatMessage } from "./types"; export type BrowserLLMPhase = | { phase: "idle" } | { phase: "loading"; progress: number; text: string } | { phase: "ready"; model: string } | { phase: "generating"; model: string } | { phase: "error"; message: string } | { phase: "unsupported"; reason: string }; export interface BrowserModel { id: string; name: string; desc: string; } export const BROWSER_MODELS: BrowserModel[] = [ { id: "local-small", name: "Locale piccolo", desc: "Fallback offline leggero", }, ]; let currentPhase: BrowserLLMPhase = { phase: "idle" }; const listeners = new Set<(s: BrowserLLMPhase) => void>(); function emit(status: BrowserLLMPhase) { currentPhase = status; listeners.forEach(fn => fn(status)); } export function onBrowserLLMStatus( cb: (status: BrowserLLMPhase) => void, ): () => void { listeners.add(cb); cb(currentPhase); return () => { listeners.delete(cb); }; } export function isWebGPUSupported(): boolean { return typeof navigator !== "undefined" && "gpu" in navigator; } let ready = false; export async function loadBrowserModel( _modelId = "local-small", ): Promise { // Nessun modello locale integrato — segnala unsupported senza simulare loading. emit({ phase: "unsupported", reason: "Nessun modello locale disponibile. Usa i provider cloud.", }); } export function isModelReady(): boolean { return ready; } /** * Gap 1 fix (S483): invece di restituire un echo formattato della domanda * dell'utente (che sembrava una risposta AI ma non lo era), lancia un errore * esplicito. providerChain.ts cattura questo errore (non-AbortError) e * prosegue verso il messaggio di esaurimento provider, che è onesto e utile. */ export async function browserGenerate( _messages: ChatMessage[], _onChunk?: (chunk: string) => void, signal?: AbortSignal, ): Promise { if (signal?.aborted) { throw new DOMException("Operazione interrotta", "AbortError"); } emit({ phase: "unsupported", reason: "Nessun modello LLM locale integrato." }); throw new Error( "BrowserLLM_UNAVAILABLE: nessun modello LLM locale è integrato. " + "Tutti i provider cloud sono temporaneamente esauriti.", ); } export function unloadBrowserModel(): void { ready = false; emit({ phase: "idle", }); }