AUDIT / src /store /uiStore.ts
Arypulka98's picture
feat(audit): deploy full backend cluster node (part 4)
641b62c verified
Raw
History Blame Contribute Delete
20.9 kB
/**
* 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 });
}