/** * uiStore.ts — Zustand store per lo stato UI globale * * Centralizza: panel visibility, modal flags, tab attivo, sistema di alert. * Evita prop drilling per stati UI che attraversano molti componenti. * * Checklist 7.1 — unified state architecture (uiStore) * S74 — SystemAlert queue per autodiagnosi + badge impostazioni + toast stack * S136 — Aggiunto: isMobile, showFileExplorer, showInputTools, hasToken, * memPulse, memoryCount, connectorCount — estratti da MainWorkspace */ import { create } from "zustand"; import { getGroqToken, getGeminiToken, getHuggingFaceToken as getHFToken, getOpenRouterToken, getOpenAIToken, getTogetherToken, } from "@/lib/apiKeys"; function _checkAnyToken(): boolean { return !!(getGroqToken() || getGeminiToken() || getHFToken() || getOpenRouterToken() || getOpenAIToken() || getTogetherToken()); } export type AppTab = "chat" | "editor" | "run" | "more" | "preview" | "terminal"; // ─── OverlayPanelId — pannelli floating mutuamente esclusivi ───────────────── export type OverlayPanelId = | "settings" | "memory" | "files" | "connectors" | "diagnostics" | "taskHistory" | "snapshotPanel" | "runtimeConsole" | "pythonVars" | "fileExplorer" | "telemetry" | "workspacePicker" | "adminTasks" | "quickOpen" | "gitSync" | "deployPanel" | "problems" | "dbBrowser" | "secretsPanel" | "agentPresence"; /** * Mappa OverlayPanelId → chiave booleana nello stato per backward-compat. * Dichiarata dopo UIStoreState, usata nell'implementazione. */ // ─── SystemAlert — singola notifica nel toast stack ─────────────────────────── export interface SystemAlert { id: string; title: string; message: string; severity: "info" | "warn" | "error"; at: number; autoDismiss?: number; // ms — undefined = manuale source: string; // "backend", "provider-groq", "storage", ecc. } interface UIStoreState { // Unified overlay panel (Sprint-1) — mutuamente esclusivo activeOverlayPanel: OverlayPanelId | null; // Panel visibility sidebarOpen: boolean; showSettings: boolean; showMemory: boolean; showFiles: boolean; showConnectors: boolean; showDiagnostics: boolean; // RuntimeDiagnosticsPanel showTaskHistory: boolean; // [Sessione 9] TaskHistoryPanel showSnapshotPanel: boolean; // [Sessione 10] SnapshotPanel showRuntimeConsole: boolean; // [Sessione 22] Runtime Console (Shift+L) showPythonVars: boolean; // [S71] Pannello Variabili Python (Shift+P) showFileExplorer: boolean; // [S119] VFS FileExplorer — S136 showInputTools: boolean; // [S85-UX] input tools popover — S136 showTelemetry: boolean; // [S310] TelemetryDashboard showWorkspacePicker:boolean; // [S311] WorkspacePicker showAdminTasks: boolean; // [S370] AdminTasksPanel showFileTree: boolean; // [S611] FileTreeSidebar persistente in editor tab showQuickOpen: boolean; // [S617] Quick Open palette (Ctrl+P / Cmd+P) showPreviewSplit: boolean; // S647 showGitSync: boolean; // S743 showDeployPanel: boolean; // S744 showProblems: boolean; // S745 showDbBrowser: boolean; // S746: split orizzontale code|preview nell'editor tab showSecretsPanel: boolean; // S748: vault segreti Railway showAgentPresence: boolean; // GAP-UI: pannello agenti connessi live showNewProject: boolean; // P1-1: NewProjectPanel migrato da MainWorkspace useState // [S78] Workspace-first layout workspaceMode: 'chat' | 'workspace'; chatPanelOpen: boolean; // Tab attivo activeTab: AppTab; // [S136] Responsive + auth gate (estratti da MainWorkspace) isMobile: boolean; hasToken: boolean; // [S136] Badge / pulse state (estratti da MainWorkspace) memoryCount: number; connectorCount: number; memPulse: boolean; // Preferenza verbosità passi agente (S354-UX) agentCompactSteps: boolean; toggleAgentCompactSteps: () => void; // Toast / banner legacy (mantenuto per compatibilità con healthOrchestrator) bannerMessage: string | null; bannerSeverity: "info" | "warn" | "error"; // [S74] Sistema di alert — badge + toast stack systemAlerts: SystemAlert[]; // Actions — panel toggle setSidebarOpen: (v: boolean) => void; toggleSidebar: () => void; setShowSettings: (v: boolean) => void; setShowMemory: (v: boolean) => void; setShowFiles: (v: boolean) => void; setShowConnectors: (v: boolean) => void; setShowDiagnostics: (v: boolean) => void; toggleDiagnostics: () => void; setShowTaskHistory: (v: boolean) => void; toggleTaskHistory: () => void; setShowSnapshotPanel: (v: boolean) => void; toggleSnapshotPanel: () => void; setShowRuntimeConsole:(v: boolean) => void; toggleRuntimeConsole: () => void; setShowPythonVars: (v: boolean) => void; togglePythonVars: () => void; setShowFileExplorer: (v: boolean) => void; // S136 toggleTelemetry: () => void; // S310 toggleWorkspacePicker:() => void; // S311 toggleAdminTasks: () => void; // S370 toggleFileTree: () => void; // S611 setShowQuickOpen: (v: boolean) => void; // S617 toggleQuickOpen: () => void; // S617 toggleFileExplorer: () => void; // S136 setShowPreviewSplit: (v: boolean) => void; // S647 togglePreviewSplit: () => void; // S647 toggleGitSync: () => void; // S743 toggleDeployPanel: () => void; // S744 toggleProblems: () => void; // S745 toggleDbBrowser: () => void; // S746 toggleSecretsPanel: () => void; // S748 toggleAgentPresence: () => void; // GAP-UI setShowNewProject: (v: boolean) => void; // P1-1 setShowInputTools: (v: boolean | ((prev: boolean) => boolean)) => void; // S136 // [S78] Workspace mode setWorkspaceMode: (mode: 'chat' | 'workspace') => void; toggleWorkspaceMode:() => void; setChatPanelOpen: (v: boolean) => void; toggleChatPanel: () => void; // Actions — tab setActiveTab: (tab: AppTab) => void; // Actions — responsive + auth (S136) setIsMobile: (v: boolean) => void; setHasToken: (v: boolean) => void; // Actions — badge (S136) setMemoryCount: (v: number) => void; setConnectorCount: (v: number) => void; setMemPulse: (v: boolean) => void; // Actions — banner legacy showBanner: (msg: string, severity?: "info" | "warn" | "error") => void; clearBanner: () => void; // Actions — [S74] alert queue addAlert: (alert: Omit) => string; // ritorna id removeAlert: (id: string) => void; clearAlerts: () => void; // Chiude tutti i panel aperti openOverlayPanel: (id: OverlayPanelId) => void; // apre pannello, chiude altri closeOverlayPanel: () => void; // chiude pannello attivo closeAllPanels: () => void; } let _alertSeq = 0; export const useUIStore = create((set) => ({ activeOverlayPanel: null, sidebarOpen: false, showSettings: false, showMemory: false, showFiles: false, showConnectors: false, showDiagnostics: false, showTaskHistory: false, showSnapshotPanel: false, showRuntimeConsole: false, showPythonVars: false, showFileExplorer: false, // S136 showTelemetry: false, showWorkspacePicker: false, showAdminTasks: false, // S370 showFileTree: true, // S611: visibile di default nell'editor tab showQuickOpen: false, // S617: Quick Open palette (Ctrl+P / Cmd+P) showPreviewSplit: false, // S647: split view code|preview showGitSync: false, // S743 showDeployPanel: false, // S744 showProblems: false, // S745 showDbBrowser: false, // S746 showSecretsPanel: false, // S748 showAgentPresence: false, // GAP-UI showNewProject: false, // P1-1 showInputTools: false, // S136 workspaceMode: 'chat', chatPanelOpen: false, activeTab: "chat", isMobile: typeof window !== "undefined" ? window.innerWidth < 768 : false, // S136 hasToken: _checkAnyToken(), // S239 — init sincrono, nessun flash TokenSetup memoryCount: 0, // S136 connectorCount: 0, // S136 memPulse: false, // S136 agentCompactSteps: false, bannerMessage: null, bannerSeverity: "info", systemAlerts: [], openOverlayPanel: (id) => set(s => { // Chiude tutti i flag boolean overlay, poi apre solo quello richiesto const OVERLAY_FLAGS: Record = { settings:"showSettings", memory:"showMemory", files:"showFiles", connectors:"showConnectors", diagnostics:"showDiagnostics", taskHistory:"showTaskHistory", snapshotPanel:"showSnapshotPanel", runtimeConsole:"showRuntimeConsole", pythonVars:"showPythonVars", fileExplorer:"showFileExplorer", telemetry:"showTelemetry", workspacePicker:"showWorkspacePicker", adminTasks:"showAdminTasks", quickOpen:"showQuickOpen", gitSync:"showGitSync", deployPanel:"showDeployPanel", problems:"showProblems", dbBrowser:"showDbBrowser", secretsPanel:"showSecretsPanel", agentPresence:"showAgentPresence", }; const closeAll: Partial = {}; for (const key of Object.values(OVERLAY_FLAGS)) { (closeAll as Record)[key] = false; } const flag = OVERLAY_FLAGS[id as string]; return { ...closeAll, activeOverlayPanel: id, ...(flag ? { [flag]: true } : {}) }; }), closeOverlayPanel: () => set(s => { const OVERLAY_FLAGS: Record = { settings:"showSettings", memory:"showMemory", files:"showFiles", connectors:"showConnectors", diagnostics:"showDiagnostics", taskHistory:"showTaskHistory", snapshotPanel:"showSnapshotPanel", runtimeConsole:"showRuntimeConsole", pythonVars:"showPythonVars", fileExplorer:"showFileExplorer", telemetry:"showTelemetry", workspacePicker:"showWorkspacePicker", adminTasks:"showAdminTasks", quickOpen:"showQuickOpen", gitSync:"showGitSync", deployPanel:"showDeployPanel", problems:"showProblems", dbBrowser:"showDbBrowser", secretsPanel:"showSecretsPanel", agentPresence:"showAgentPresence", }; const patch: Partial = { activeOverlayPanel: null }; if (s.activeOverlayPanel) { const flag = OVERLAY_FLAGS[s.activeOverlayPanel]; if (flag) (patch as Record)[flag] = false; } return patch; }), setSidebarOpen: (v) => set({ sidebarOpen: v }), toggleSidebar: () => set(s => ({ sidebarOpen: !s.sidebarOpen })), setShowSettings: (v) => set({ showSettings: v }), setShowMemory: (v) => set({ showMemory: v }), setShowFiles: (v) => set({ showFiles: v }), setShowConnectors: (v) => set({ showConnectors: v }), setShowDiagnostics: (v) => set({ showDiagnostics: v }), toggleDiagnostics: () => set(s => ({ showDiagnostics: !s.showDiagnostics })), setShowTaskHistory: (v) => set({ showTaskHistory: v }), toggleTaskHistory: () => set(s => ({ showTaskHistory: !s.showTaskHistory })), setShowSnapshotPanel: (v) => set({ showSnapshotPanel: v }), toggleSnapshotPanel: () => set(s => ({ showSnapshotPanel: !s.showSnapshotPanel })), setShowRuntimeConsole:(v) => set({ showRuntimeConsole: v }), toggleRuntimeConsole: () => set(s => ({ showRuntimeConsole: !s.showRuntimeConsole })), setShowPythonVars: (v) => set({ showPythonVars: v }), togglePythonVars: () => set(s => ({ showPythonVars: !s.showPythonVars })), setShowFileExplorer: (v) => set({ showFileExplorer: v }), // S136 toggleFileExplorer: () => set(s => ({ showFileExplorer: !s.showFileExplorer })), // S136 toggleTelemetry: () => set(s => ({ showTelemetry: !s.showTelemetry })), toggleWorkspacePicker:() => set(s => ({ showWorkspacePicker: !s.showWorkspacePicker })), toggleAdminTasks: () => set(s => ({ showAdminTasks: !s.showAdminTasks })), // S370 toggleFileTree: () => set(s => ({ showFileTree: !s.showFileTree })), // S611 setShowQuickOpen: (v) => set({ showQuickOpen: v }), // S617 toggleQuickOpen: () => set(s => ({ showQuickOpen: !s.showQuickOpen })), // S617 setShowPreviewSplit: (v) => set({ showPreviewSplit: v }), // S647 togglePreviewSplit: () => set(s => ({ showPreviewSplit: !s.showPreviewSplit })),// S647 toggleGitSync: () => set(s => ({ showGitSync: !s.showGitSync })), // S743 toggleDeployPanel: () => set(s => ({ showDeployPanel: !s.showDeployPanel })), // S744 toggleProblems: () => set(s => ({ showProblems: !s.showProblems })), // S745 toggleDbBrowser: () => set(s => ({ showDbBrowser: !s.showDbBrowser })), // S746 toggleSecretsPanel: () => set(s => ({ showSecretsPanel: !s.showSecretsPanel })), // S748 toggleAgentPresence: () => set(s => ({ showAgentPresence: !s.showAgentPresence })), // GAP-UI setShowNewProject: (v) => set({ showNewProject: v }), // P1-1 setShowInputTools: (v) => set(s => ({ // S136 showInputTools: typeof v === "function" ? v(s.showInputTools) : v, })), setWorkspaceMode: (mode) => set(s => ({ workspaceMode: mode, chatPanelOpen: mode === 'workspace', activeTab: mode === 'workspace' && s.activeTab === 'chat' ? 'editor' : s.activeTab, })), toggleWorkspaceMode: () => set(s => { const next = s.workspaceMode === 'chat' ? 'workspace' : 'chat'; return { workspaceMode: next, chatPanelOpen: next === 'workspace', activeTab: next === 'workspace' && s.activeTab === 'chat' ? 'editor' : s.activeTab, }; }), setChatPanelOpen: (v) => set({ chatPanelOpen: v }), toggleChatPanel: () => set(s => ({ chatPanelOpen: !s.chatPanelOpen })), setActiveTab: (tab) => set({ activeTab: tab }), setIsMobile: (v) => set({ isMobile: v }), // S136 setHasToken: (v) => set({ hasToken: v }), // S136 setMemoryCount: (v) => set({ memoryCount: v }), // S136 setConnectorCount: (v) => set({ connectorCount: v }), // S136 setMemPulse: (v) => set({ memPulse: v }), // S136 toggleAgentCompactSteps: () => set(s => ({ agentCompactSteps: !s.agentCompactSteps })), showBanner: (msg, severity = "info") => set({ bannerMessage: msg, bannerSeverity: severity }), clearBanner: () => set({ bannerMessage: null }), addAlert: (alert) => { const id = `alert-${Date.now()}-${_alertSeq++}`; const entry: SystemAlert = { ...alert, id, at: Date.now() }; set(s => ({ systemAlerts: [...s.systemAlerts, entry] })); return id; }, removeAlert: (id) => set(s => ({ systemAlerts: s.systemAlerts.filter(a => a.id !== id) })), clearAlerts: () => set({ systemAlerts: [] }), closeAllPanels: () => set({ // FIX13: completo — include tutti i panel activeOverlayPanel: null, sidebarOpen: false, showSettings: false, showMemory: false, showFiles: false, showConnectors: false, showDiagnostics: false, // FIX13 showTaskHistory: false, // FIX13 showSnapshotPanel: false, // FIX13 showRuntimeConsole: false, // FIX13 showPythonVars: false, // FIX13 showTelemetry: false, // FIX13 showWorkspacePicker: false, // FIX13 showAdminTasks: false, // FIX13 showFileExplorer: false, // FIX13 showInputTools: false, // FIX13 showQuickOpen: false, // FIX13 showPreviewSplit: false, // S647 showGitSync: false, // S743 showDeployPanel: false, // S744 showProblems: false, // S745 showDbBrowser: false, // S746 showSecretsPanel: false, // S748 showAgentPresence: false, // GAP-UI showNewProject: false, // P1-1 }), })); // ─── Selectors ──────────────────────────────────────────────────────────────── export const selectActiveOverlayPanel = (s: UIStoreState) => s.activeOverlayPanel; export const selectActiveTab = (s: UIStoreState) => s.activeTab; export const selectSidebarOpen = (s: UIStoreState) => s.sidebarOpen; export const selectShowDiagnostics = (s: UIStoreState) => s.showDiagnostics; export const selectShowTaskHistory = (s: UIStoreState) => s.showTaskHistory; export const selectShowSnapshotPanel = (s: UIStoreState) => s.showSnapshotPanel; export const selectShowRuntimeConsole = (s: UIStoreState) => s.showRuntimeConsole; export const selectShowPythonVars = (s: UIStoreState) => s.showPythonVars; export const selectShowFileExplorer = (s: UIStoreState) => s.showFileExplorer; // S136 export const selectShowInputTools = (s: UIStoreState) => s.showInputTools; // S136 export const selectIsMobile = (s: UIStoreState) => s.isMobile; // S136 export const selectHasToken = (s: UIStoreState) => s.hasToken; // S136 export const selectMemoryCount = (s: UIStoreState) => s.memoryCount; // S136 export const selectConnectorCount = (s: UIStoreState) => s.connectorCount; // S136 export const selectMemPulse = (s: UIStoreState) => s.memPulse; // S136 export const selectBannerMessage = (s: UIStoreState) => s.bannerMessage; export const selectBannerSeverity = (s: UIStoreState) => s.bannerSeverity; export const selectAlertCount = (s: UIStoreState) => s.systemAlerts.filter(a => a.severity !== "info").length; export const selectWorkspaceMode = (s: UIStoreState) => s.workspaceMode; export const selectChatPanelOpen = (s: UIStoreState) => s.chatPanelOpen; export const selectShowTelemetry = (s: UIStoreState) => s.showTelemetry; // S310 export const selectShowWorkspacePicker = (s: UIStoreState) => s.showWorkspacePicker; // S311 export const selectShowAdminTasks = (s: UIStoreState) => s.showAdminTasks; // S370 export const selectShowFileTree = (s: UIStoreState) => s.showFileTree; // S611 export const selectShowQuickOpen = (s: UIStoreState) => s.showQuickOpen; // S617 export const selectShowPreviewSplit = (s: UIStoreState) => s.showPreviewSplit; // S647 export const selectShowGitSync = (s: UIStoreState) => s.showGitSync; // S743 export const selectShowDeployPanel = (s: UIStoreState) => s.showDeployPanel; // S744 export const selectShowProblems = (s: UIStoreState) => s.showProblems; // S745 export const selectShowDbBrowser = (s: UIStoreState) => s.showDbBrowser; // S746 export const selectShowSecretsPanel = (s: UIStoreState) => s.showSecretsPanel; // S748 export const selectShowAgentPresence = (s: UIStoreState) => s.showAgentPresence; // GAP-UI export const selectShowNewProject = (s: UIStoreState) => s.showNewProject; // P1-1 export const selectAgentCompactSteps = (s: UIStoreState) => s.agentCompactSteps; // S354-UX // ─── Reactive isMobile — aggiornato su resize / orientationchange ───────────── // S-MOBILE-FIX: isMobile era uno snapshot statico preso all'init. Questo listener // aggiorna lo store ogni volta che l'utente ruota il device o ridimensiona la // finestra, così la UI risponde correttamente senza reload. if (typeof window !== "undefined") { const _MOBILE_BP = 768; let _resizeTimer: ReturnType | null = null; function _syncIsMobile(): void { const next = window.innerWidth < _MOBILE_BP; if (useUIStore.getState().isMobile !== next) { useUIStore.getState().setIsMobile(next); } } function _onResize(): void { if (_resizeTimer !== null) clearTimeout(_resizeTimer); _resizeTimer = setTimeout(_syncIsMobile, 100); // debounce 100ms } // resize: debounced (può sparare centinaia di volte al secondo) window.addEventListener("resize", _onResize, { passive: true }); // orientationchange: immediato (evento raro, nessun debounce necessario) window.addEventListener("orientationchange", _syncIsMobile, { passive: true }); }