Spaces:
Sleeping
Sleeping
| /** | |
| * 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<SystemAlert, "id" | "at">) => 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<UIStoreState>((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<string, keyof typeof s> = { | |
| 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<typeof s> = {}; | |
| for (const key of Object.values(OVERLAY_FLAGS)) { | |
| (closeAll as Record<string, unknown>)[key] = false; | |
| } | |
| const flag = OVERLAY_FLAGS[id as string]; | |
| return { ...closeAll, activeOverlayPanel: id, ...(flag ? { [flag]: true } : {}) }; | |
| }), | |
| closeOverlayPanel: () => set(s => { | |
| const OVERLAY_FLAGS: Record<string, string> = { | |
| 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<typeof s> = { activeOverlayPanel: null }; | |
| if (s.activeOverlayPanel) { | |
| const flag = OVERLAY_FLAGS[s.activeOverlayPanel]; | |
| if (flag) (patch as Record<string, unknown>)[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<typeof setTimeout> | 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 }); | |
| } | |