Spaces:
Sleeping
Sleeping
| /** | |
| * useWorkspacePanel.ts — S-WP1: Pannelli UI, tastiera, responsive, badge | |
| * | |
| * Estratto da MainWorkspace.tsx (S-WP1): | |
| * - Tutti i subscriber UIStore (tab, sidebar, panel toggles, hasToken, badges…) | |
| * - tabsVisited (lazy-mount tracking) | |
| * - Effetto responsive (isMobile resize listener) | |
| * - Effetto keyboard shortcuts (Escape, Cmd+K, Ctrl+P, Shift+H/S/L/P/A) | |
| * - Effetto visualViewport (--vvh + scroll-jump prevention) | |
| * - Effetto memory badge pulse | |
| * - Effetto refresh badge counts when panels close | |
| * - Boot effect: hasToken | |
| * | |
| * Rimane in MainWorkspace: ConversationStore, WorkspaceStore, boot side-effects | |
| * (prewarm, hydrate, agentLoop, GH, SSE), selfDiag, VFS, JSX. | |
| */ | |
| import { useState, useRef, useEffect } from "react"; | |
| import { useIsLandscape } from "@/hooks/use-mobile"; | |
| import { useUIStore, selectAlertCount } from "@/store/uiStore"; | |
| import { useConversationStore } from "@/store/conversationStore"; | |
| import { useWorkspaceStore } from "@/store/workspaceStore"; | |
| import { agentMemory } from "@/lib/agentMemory"; | |
| import { getConnectors } from "@/lib/connectors"; | |
| import { getAnyToken } from "@/lib/messageBuilders"; | |
| import { onTokenInvalid } from "@/lib/providerStatusEmitter"; // TOKEN-FIX-5: 401 → reset hasToken | |
| // ───────────────────────────────────────────────────────────────────────────── | |
| interface UseWorkspacePanelOptions { | |
| /** AbortController ref from useConversationManager — Escape key aborts stream */ | |
| abortRef: React.RefObject<AbortController | null>; | |
| } | |
| export function useWorkspacePanel({ abortRef }: UseWorkspacePanelOptions) { | |
| // ── UIStore — tab ───────────────────────────────────────────────────────── | |
| const tab = useUIStore(s => s.activeTab); | |
| const setTab = useUIStore(s => s.setActiveTab); | |
| // ── Lazy mount: tab visitati — FileEditor e TerminalPanel montati solo dopo primo accesso | |
| const [tabsVisited, setTabsVisited] = useState<Set<string>>(new Set(["chat"])); | |
| useEffect(() => { | |
| setTabsVisited(prev => { const s = new Set([...prev]); s.add(tab); return s; }); | |
| }, [tab]); | |
| // ── UIStore — sidebar + settings ───────────────────────────────────────── | |
| const sidebarOpen = useUIStore(s => s.sidebarOpen); | |
| const setSidebarOpen = useUIStore(s => s.setSidebarOpen); | |
| const toggleSidebar = useUIStore(s => s.toggleSidebar); | |
| const showSettings = useUIStore(s => s.showSettings); | |
| const setShowSettings = useUIStore(s => s.setShowSettings); | |
| // ── UIStore — panel toggles ─────────────────────────────────────────────── | |
| const toggleTaskHistory = useUIStore(s => s.toggleTaskHistory); | |
| const toggleSnapshotPanel = useUIStore(s => s.toggleSnapshotPanel); | |
| const toggleRuntimeConsole = useUIStore(s => s.toggleRuntimeConsole); | |
| const togglePythonVars = useUIStore(s => s.togglePythonVars); | |
| const showConnectors = useUIStore(s => s.showConnectors); | |
| const showMemory = useUIStore(s => s.showMemory); | |
| const toggleAdminTasks = useUIStore(s => s.toggleAdminTasks); // S370 | |
| const toggleQuickOpen = useUIStore(s => s.toggleQuickOpen); // S617 | |
| // ── UIStore — layout ────────────────────────────────────────────────────── | |
| const workspaceMode = useUIStore(s => s.workspaceMode); | |
| const chatPanelOpen = useUIStore(s => s.chatPanelOpen); | |
| const isMobile = useUIStore(s => s.isMobile); | |
| const isLandscape = useIsLandscape(); // S464: landscape mobile layout | |
| // ── UIStore — auth + badges ─────────────────────────────────────────────── | |
| const hasToken = useUIStore(s => s.hasToken); | |
| const setHasToken = useUIStore(s => s.setHasToken); | |
| const setMemPulse = useUIStore(s => s.setMemPulse); | |
| const setMemoryCount = useUIStore(s => s.setMemoryCount); | |
| const setConnectorCount = useUIStore(s => s.setConnectorCount); | |
| const systemAlerts = useUIStore(s => s.systemAlerts); | |
| const alertCount = useUIStore(selectAlertCount); | |
| const addAlert = useUIStore(s => s.addAlert); | |
| // ── Internal ref: memory badge count tracking ───────────────────────────── | |
| const prevMemCountRef = useRef<number>(agentMemory.list().length); | |
| // ── Boot: hasToken ──────────────────────────────────────────────────────── | |
| useEffect(() => { setHasToken(!!getAnyToken()); }, [setHasToken]); | |
| // ── TOKEN-FIX-5: 401 token-invalid → reset hasToken → TokenSetup schermata ── | |
| // emitTokenInvalid() è chiamato da providerFallbackChain/providerTryCore/agentLoop | |
| // quando un provider risponde 401 (token scaduto o non valido). Prima non aveva | |
| // subscriber → 401 silenzioso, utente vedeva solo "Tutti i provider non hanno risposto" | |
| // senza capire il motivo. Ora resetta hasToken → mostra TokenSetup per aggiornare. | |
| useEffect(() => onTokenInvalid(() => { | |
| setHasToken(false); | |
| }), [setHasToken]); | |
| // ── Keyboard shortcuts ──────────────────────────────────────────────────── | |
| useEffect(() => { | |
| const h = (e: KeyboardEvent) => { | |
| // Escape: ferma streaming | |
| if (e.key === "Escape") { | |
| if (abortRef.current) { e.preventDefault(); abortRef.current.abort(); } | |
| return; | |
| } | |
| // Cmd+K (Mac) / Ctrl+K (Win/Linux): nuova conversazione / Command Palette | |
| if (e.key === "k" && (e.metaKey || e.ctrlKey)) { | |
| e.preventDefault(); | |
| useConversationStore.getState().newConv(); | |
| return; | |
| } | |
| // S626: Ctrl+Shift+T / Cmd+Shift+T — riapri ultimo tab chiuso (VS Code standard) | |
| if (e.key === "t" && (e.metaKey || e.ctrlKey) && e.shiftKey) { | |
| e.preventDefault(); | |
| useWorkspaceStore.getState().reopenLastClosed(); | |
| return; | |
| } | |
| // S617/S650: Ctrl+P / Cmd+P — Quick Open palette | |
| if (e.key === "p" && (e.metaKey || e.ctrlKey) && !e.shiftKey) { | |
| e.preventDefault(); | |
| toggleQuickOpen(); | |
| return; | |
| } | |
| if (!e.shiftKey) return; | |
| const tag = (e.target as HTMLElement)?.tagName; | |
| if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || (e.target as HTMLElement)?.isContentEditable) return; | |
| if (e.key === "H") { e.preventDefault(); toggleTaskHistory(); } | |
| if (e.key === "S") { e.preventDefault(); toggleSnapshotPanel(); } | |
| if (e.key === "L") { e.preventDefault(); toggleRuntimeConsole(); } | |
| if (e.key === "P") { e.preventDefault(); togglePythonVars(); } | |
| if (e.key === "A") { e.preventDefault(); toggleAdminTasks(); } // S370 | |
| }; | |
| window.addEventListener("keydown", h); | |
| return () => window.removeEventListener("keydown", h); | |
| }, [abortRef, toggleTaskHistory, toggleSnapshotPanel, toggleRuntimeConsole, togglePythonVars, toggleAdminTasks, toggleQuickOpen]); | |
| // ── visualViewport: --vvh + blocca scroll-jump ──────────────────────────── | |
| useEffect(() => { | |
| const vv = (window as Window & { visualViewport?: VisualViewport }).visualViewport; | |
| if (!vv) return; | |
| const onWinScroll = () => { if (window.scrollY !== 0) window.scrollTo(0, 0); }; | |
| window.addEventListener("scroll", onWinScroll, { passive: true }); | |
| return () => { | |
| window.removeEventListener("scroll", onWinScroll); | |
| }; | |
| }, []); | |
| // ── Memory badge pulse ──────────────────────────────────────────────────── | |
| useEffect(() => { | |
| const unsub = agentMemory.subscribe(() => { | |
| const newCount = agentMemory.list().length; | |
| if (newCount > prevMemCountRef.current) setMemPulse(true); | |
| prevMemCountRef.current = newCount; | |
| }); | |
| return unsub; | |
| }, [setMemPulse]); | |
| // ── Refresh badge counts when panels close ──────────────────────────────── | |
| useEffect(() => { | |
| if (!showMemory) setMemoryCount(agentMemory.list().length); | |
| if (!showConnectors) setConnectorCount(getConnectors().length); | |
| }, [showMemory, showConnectors, setMemoryCount, setConnectorCount]); | |
| return { | |
| tab, setTab, tabsVisited, | |
| sidebarOpen, setSidebarOpen, toggleSidebar, | |
| showSettings, setShowSettings, | |
| toggleTaskHistory, toggleSnapshotPanel, toggleRuntimeConsole, | |
| togglePythonVars, toggleAdminTasks, toggleQuickOpen, | |
| showConnectors, showMemory, | |
| workspaceMode, chatPanelOpen, | |
| isMobile, isLandscape, | |
| hasToken, setHasToken, | |
| systemAlerts, alertCount, addAlert, | |
| setMemPulse, setMemoryCount, setConnectorCount, | |
| }; | |
| } | |