import { useState, useEffect, useCallback, lazy, Suspense } from 'react'; import { Navbar } from './components/Navbar'; import { IdentityPanel } from './components/IdentityPanel'; import { TokenManagerDialog } from './components/TokenManagerDialog'; import { ThemeProvider } from './lib/theme'; import { getUuid, setUuid, apiBind, setActiveApiToken } from './api/client'; import { ACPDirectView } from './components/ACPDirectView'; import { useTokens } from './hooks/useTokens'; const Dashboard = lazy(() => import('./pages/Dashboard').then(m => ({ default: m.Dashboard }))); const SessionDetail = lazy(() => import('./pages/SessionDetail').then(m => ({ default: m.SessionDetail }))); export default function App() { const [currentSessionId, setCurrentSessionId] = useState(null); const [identityOpen, setIdentityOpen] = useState(false); const [tokenDialogOpen, setTokenDialogOpen] = useState(false); const [acpDirect, setAcpDirect] = useState<{ url: string; token: string } | null>(null); const { tokens, activeTokenId, activeLabel, activeTokenValue, setActiveTokenId, addToken, removeToken, updateToken } = useTokens(); // Sync active token to API client useEffect(() => { setActiveApiToken(activeTokenValue); }, [activeTokenValue]); const handleSetActiveToken = useCallback( (id: string) => { setActiveTokenId(id); }, [setActiveTokenId], ); // Simple hash-based router const parseRoute = useCallback(() => { // Ensure UUID exists getUuid(); const path = window.location.pathname; // Check for UUID import from QR scan (?uuid=xxx) const params = new URLSearchParams(window.location.search); const importUuid = params.get('uuid'); if (importUuid) { setUuid(importUuid); const url = new URL(window.location.href); url.searchParams.delete('uuid'); window.history.replaceState(null, '', url); } // Check for ACP direct connection (?acp=1) const acpParam = params.get('acp'); if (acpParam === '1') { const stored = sessionStorage.getItem('acp_connection'); if (stored) { try { const acpData = JSON.parse(stored); if (acpData.url && acpData.token) { setAcpDirect({ url: acpData.url, token: acpData.token }); sessionStorage.removeItem('acp_connection'); // Clean URL const url = new URL(window.location.href); url.searchParams.delete('acp'); window.history.replaceState(null, '', url); return; } } catch { sessionStorage.removeItem('acp_connection'); } } } // Check for CLI session bind (?sid=xxx) — bind session to current UUID const sid = params.get('sid'); if (sid) { const url = new URL(window.location.href); url.searchParams.delete('sid'); window.history.replaceState(null, '', `/code/${sid}`); setCurrentSessionId(sid); // Bind this session to the current user's UUID for ownership apiBind(sid).catch((err: unknown) => { console.warn('Failed to bind session:', err); }); return; } // Path-based routing: /code/session_xxx → session detail const match = path.match(/^\/code\/([^/]+)/); if (match && match[1]) { setCurrentSessionId(match[1]); } else { setCurrentSessionId(null); } }, []); useEffect(() => { parseRoute(); window.addEventListener('popstate', parseRoute); return () => window.removeEventListener('popstate', parseRoute); }, [parseRoute]); const navigateToSession = useCallback((sessionId: string) => { window.history.pushState(null, '', `/code/${sessionId}`); setCurrentSessionId(sessionId); }, []); const navigateToDashboard = useCallback(() => { window.history.pushState(null, '', '/code/'); setCurrentSessionId(null); setAcpDirect(null); }, []); return (
setIdentityOpen(true)} onTokenClick={() => setTokenDialogOpen(true)} activeTokenLabel={currentSessionId ? undefined : activeLabel} sessionTitle={currentSessionId || (acpDirect ? 'ACP' : undefined)} onBack={currentSessionId || acpDirect ? navigateToDashboard : undefined} /> Loading...
}> {acpDirect ? ( ) : currentSessionId ? ( ) : (
)} setIdentityOpen(false)} /> setTokenDialogOpen(false)} tokens={tokens} activeTokenId={activeTokenId} onSetActive={handleSetActiveToken} onAdd={addToken} onRemove={removeToken} onUpdate={updateToken} />
); }