import { useEffect, useState, useCallback } from 'react'; import { PageParseAPI, getAirgapMode, setAirgapMode, subscribeToJobs, API_BASE_URL } from './services/api'; import type { Task, PageRecord } from './services/api'; import { FileUploader } from './components/FileUploader'; import { CodeEditor } from './components/CodeEditor'; import { TaskList } from './components/TaskList'; import { TelemetryPanel } from './components/TelemetryPanel'; import { PageHistory } from './components/PageHistory'; import { PageDetails } from './components/PageDetails'; import { ChatPanel } from './components/ChatPanel'; import { ScanFace, Upload, Database, History, MessageSquare, Code, Cpu, Wifi, WifiOff, Sun, Moon, Settings } from 'lucide-react'; type View = 'upload' | 'tasks' | 'history' | 'chat' | 'code'; function App() { const [tasks, setTasks] = useState([]); const [pages, setPages] = useState([]); const [isAirgapped, setIsAirgapped] = useState(getAirgapMode()); const [serverOnline, setServerOnline] = useState(false); const [isProcessing, setIsProcessing] = useState(false); const [selectedPage, setSelectedPage] = useState(null); const [activeView, setActiveView] = useState('upload'); const [isDarkMode, setIsDarkMode] = useState(() => { const saved = localStorage.getItem('pageparse_theme'); if (saved) return saved === 'dark'; return window.matchMedia('(prefers-color-scheme: dark)').matches; }); const [showSettings, setShowSettings] = useState(false); const [webhookUrl, setWebhookUrl] = useState(''); const [webhookConfigured, setWebhookConfigured] = useState(false); const loadData = useCallback(async () => { try { const [allTasks, allPages] = await Promise.all([ PageParseAPI.getTasks(), PageParseAPI.getPages(), ]); setTasks(allTasks); setPages(allPages); if (selectedPage) { const updated = allPages.find(p => p.id === selectedPage.id); if (updated) setSelectedPage(updated); } } catch (err) { console.error('Error loading data:', err); } }, [selectedPage]); const checkConnectivity = useCallback(async () => { if (isAirgapped) { setServerOnline(false); return; } setServerOnline(await PageParseAPI.checkServerConnection()); }, [isAirgapped]); useEffect(() => { const unsub = subscribeToJobs((jobs) => { const processing = jobs.some(j => j.status !== 'saved' && j.status !== 'failed'); setIsProcessing(processing); if (jobs.some(j => j.status === 'saved')) loadData(); }); return () => unsub(); }, [loadData]); useEffect(() => { checkConnectivity(); loadData(); const interval = setInterval(() => { if (!isAirgapped) checkConnectivity(); }, 10000); return () => clearInterval(interval); }, [isAirgapped, checkConnectivity, loadData]); useEffect(() => { document.documentElement.setAttribute('data-theme', isDarkMode ? 'dark' : 'light'); localStorage.setItem('pageparse_theme', isDarkMode ? 'dark' : 'light'); }, [isDarkMode]); useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement || e.target instanceof HTMLSelectElement) return; if (e.ctrlKey || e.metaKey) return; switch (e.key) { case '1': setActiveView('upload'); break; case '2': setActiveView('tasks'); break; case '3': setActiveView('history'); break; case '4': setActiveView('chat'); break; case '5': setActiveView('code'); break; case 't': case 'T': setIsDarkMode(prev => !prev); break; case '?': setShowSettings(prev => !prev); break; case 'Escape': setShowSettings(false); break; } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, []); useEffect(() => { if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/static/sw.js').catch(() => {}); } }, []); const handleAirgapChange = () => { const newVal = !isAirgapped; setIsAirgapped(newVal); setAirgapMode(newVal); }; const handleWebhookConfig = async () => { try { await fetch(`${API_BASE_URL}/webhook`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: webhookUrl, secret: '' }), }); setWebhookConfigured(true); setTimeout(() => setShowSettings(false), 1500); } catch (err) { console.error(err); } }; const navItems: { view: View; icon: typeof Upload; label: string; shortcut: string }[] = [ { view: 'upload', icon: Upload, label: 'Ingest', shortcut: '1' }, { view: 'tasks', icon: Database, label: 'Records', shortcut: '2' }, { view: 'history', icon: History, label: 'History', shortcut: '3' }, { view: 'chat', icon: MessageSquare, label: 'AI Chat', shortcut: '4' }, { view: 'code', icon: Code, label: 'Code', shortcut: '5' }, ]; if (selectedPage) { return ( setSelectedPage(null)} onPageChanged={loadData} /> ); } return (
{/* SIDEBAR */} {/* MAIN */}

{activeView === 'upload' && 'Ingestion Portal'} {activeView === 'tasks' && `Records (${tasks.length})`} {activeView === 'history' && `History (${pages.length})`} {activeView === 'chat' && 'AI Assistant'} {activeView === 'code' && 'Code Paste & Analyze'}

{isProcessing ? 'Inference Active' : 'Idle'}
{activeView === 'upload' && (
setIsProcessing(true)} onJobFinished={() => { setTimeout(loadData, 500); }} isAirgapped={isAirgapped} />
)} {activeView === 'tasks' && ( )} {activeView === 'history' && ( )} {activeView === 'chat' && (

PageParse AI Chat

)} {activeView === 'code' && ( )}
{/* SETTINGS MODAL */} {showSettings && (
setShowSettings(false)}>
e.stopPropagation()}>

Settings

setWebhookUrl(e.target.value)} placeholder="https://hooks.example.com/pageparse" />
{webhookConfigured &&
Webhook configured!
}
Ingest view1
Records view2
History view3
AI Chat view4
Code view5
Toggle themeT
Settings?
Close modalEsc
)}
); } export default App;