import { useEffect, useRef } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import Sidebar from '../sidebar/view/Sidebar'; import MainContent from '../main-content/view/MainContent'; import { useWebSocket } from '../../contexts/WebSocketContext'; import { useDeviceSettings } from '../../hooks/useDeviceSettings'; import { useSessionProtection } from '../../hooks/useSessionProtection'; import { useProjectsState } from '../../hooks/useProjectsState'; import MobileNav from './MobileNav'; export default function AppContent() { const navigate = useNavigate(); const { sessionId } = useParams<{ sessionId?: string }>(); const { t } = useTranslation('common'); const { isMobile } = useDeviceSettings({ trackPWA: false }); const { ws, sendMessage, latestMessage, isConnected } = useWebSocket(); const wasConnectedRef = useRef(false); const { activeSessions, processingSessions, markSessionAsActive, markSessionAsInactive, markSessionAsProcessing, markSessionAsNotProcessing, replaceTemporarySession, } = useSessionProtection(); const { selectedProject, selectedSession, activeTab, sidebarOpen, isLoadingProjects, isInputFocused, externalMessageUpdate, setActiveTab, setSidebarOpen, setIsInputFocused, setShowSettings, openSettings, refreshProjectsSilently, sidebarSharedProps, } = useProjectsState({ sessionId, navigate, latestMessage, isMobile, activeSessions, }); useEffect(() => { // Expose a non-blocking refresh for chat/session flows. // Full loading refreshes are still available through direct fetchProjects calls. window.refreshProjects = refreshProjectsSilently; return () => { if (window.refreshProjects === refreshProjectsSilently) { delete window.refreshProjects; } }; }, [refreshProjectsSilently]); useEffect(() => { window.openSettings = openSettings; return () => { if (window.openSettings === openSettings) { delete window.openSettings; } }; }, [openSettings]); useEffect(() => { if (typeof navigator === 'undefined' || !('serviceWorker' in navigator)) { return undefined; } const handleServiceWorkerMessage = (event: MessageEvent) => { const message = event.data; if (!message || message.type !== 'notification:navigate') { return; } if (typeof message.provider === 'string' && message.provider.trim()) { localStorage.setItem('selected-provider', message.provider); } setActiveTab('chat'); setSidebarOpen(false); void refreshProjectsSilently(); if (typeof message.sessionId === 'string' && message.sessionId) { navigate(`/session/${message.sessionId}`); return; } navigate('/'); }; navigator.serviceWorker.addEventListener('message', handleServiceWorkerMessage); return () => { navigator.serviceWorker.removeEventListener('message', handleServiceWorkerMessage); }; }, [navigate, refreshProjectsSilently, setActiveTab, setSidebarOpen]); // Permission recovery: query pending permissions on WebSocket reconnect or session change useEffect(() => { const isReconnect = isConnected && !wasConnectedRef.current; if (isReconnect) { wasConnectedRef.current = true; } else if (!isConnected) { wasConnectedRef.current = false; } if (isConnected && selectedSession?.id) { sendMessage({ type: 'get-pending-permissions', sessionId: selectedSession.id }); } }, [isConnected, selectedSession?.id, sendMessage]); return (
{!isMobile ? (
) : (
)}
setSidebarOpen(true)} isLoading={isLoadingProjects} onInputFocusChange={setIsInputFocused} onSessionActive={markSessionAsActive} onSessionInactive={markSessionAsInactive} onSessionProcessing={markSessionAsProcessing} onSessionNotProcessing={markSessionAsNotProcessing} processingSessions={processingSessions} onReplaceTemporarySession={replaceTemporarySession} onNavigateToSession={(targetSessionId: string) => navigate(`/session/${targetSessionId}`)} onShowSettings={() => setShowSettings(true)} externalMessageUpdate={externalMessageUpdate} />
{isMobile && ( )}
); }