Spaces:
Configuration error
Configuration error
File size: 9,572 Bytes
cc11e77 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 | /**
* 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,
};
}
|