AUDIT / src /hooks /useWorkspacePanel.ts
Arypulka98's picture
feat(audit): deploy full backend cluster node (part 2)
cc11e77 verified
Raw
History Blame Contribute Delete
9.57 kB
/**
* 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,
};
}