import { useState, useEffect, useCallback } from 'react'; import { Activity } from 'lucide-react'; import Welcome from './Welcome'; import Tutorial from './Tutorial'; import { PatientPanel } from './PatientPanel'; import { AgentPanel } from './AgentPanel'; import { BriefPanel } from './BriefPanel'; import { ApiKeyModal } from './ApiKeyModal'; import ConsultPanel from './ConsultPanel'; const API = import.meta.env.VITE_API_BASE || ''; const INITIAL_STATUSES = { triage: 'idle', contraindication: 'idle', labs: 'idle', differentials: 'idle', action_protocol: 'idle', }; // Staggered agent activation for visual drama const AGENT_ORDER = ['triage', 'contraindication', 'labs', 'differentials', 'action_protocol']; export default function App() { const [showWelcome, setShowWelcome] = useState(true); const [showTutorial, setShowTutorial] = useState(false); const [showApiModal, setShowApiModal] = useState(false); const [patients, setPatients] = useState([]); const [patientsLoading, setPatientsLoading] = useState(false); const [selected, setSelected] = useState(null); const [running, setRunning] = useState(false); const [agentStatuses, setAgentStatuses] = useState(INITIAL_STATUSES); const [agentResults, setAgentResults] = useState({}); const [briefData, setBriefData] = useState(null); const [activeTab, setActiveTab] = useState('orchestrate'); const [focus, setFocus] = useState('General'); // Load patients const fetchPatients = useCallback(async () => { setPatientsLoading(true); try { const res = await fetch(`${API}/patients`); const data = await res.json(); setPatients(data.patients || []); } catch { setPatients([]); } finally { setPatientsLoading(false); } }, []); useEffect(() => { fetchPatients(); }, [fetchPatients]); const handleSelectPatient = (patient) => { setSelected(patient); setBriefData(null); setAgentStatuses(INITIAL_STATUSES); setAgentResults({}); setActiveTab('orchestrate'); }; // Upload patient JSON/PDF (Multi-file support) const handleUpload = async (e) => { const fileList = Array.from(e.target.files); if (fileList.length === 0) return; const form = new FormData(); fileList.forEach(f => form.append('files', f)); // 'files' is plural now try { const geminiKey = localStorage.getItem('gemini_key'); const headers = geminiKey ? { 'x-gemini-key': geminiKey } : {}; const res = await fetch(`${API}/patients/upload`, { method: 'POST', body: form, headers }); if (!res.ok) { const err = await res.json(); alert(`Upload failed: ${err.detail || 'Internal Server Error'}`); } else { fetchPatients(); } } catch (err) { alert(`Network error during upload: ${err.message}`); } e.target.value = ''; }; // Run 5-agent orchestration with staggered UI animation const runOrchestration = async (targetPatient = null) => { // If targetPatient is an event object from a click handler, ignore it const isEvent = targetPatient && targetPatient.nativeEvent; const p = (targetPatient && !isEvent) ? targetPatient : selected; if (!p || running) return; setRunning(true); setBriefData(null); setAgentResults({}); // Light up agents one by one with a delay for UX drama const activateAgents = async () => { for (let i = 0; i < AGENT_ORDER.length; i++) { const key = AGENT_ORDER[i]; setAgentStatuses(prev => ({ ...prev, [key]: 'working' })); await new Promise(r => setTimeout(r, 180)); } }; activateAgents(); // fire and don't await — let it animate while API call runs try { const geminiKey = localStorage.getItem('gemini_key'); const headers = { 'Content-Type': 'application/json' }; if (geminiKey) headers['x-gemini-key'] = geminiKey; const res = await fetch(`${API}/orchestrate/${p.id}`, { method: 'POST', headers }); if (!res.ok) throw new Error('API error'); const data = await res.json(); // Mark all complete with slight stagger for (let i = 0; i < AGENT_ORDER.length; i++) { const key = AGENT_ORDER[i]; await new Promise(r => setTimeout(r, 120)); setAgentStatuses(prev => ({ ...prev, [key]: 'complete' })); setAgentResults(prev => ({ ...prev, [key]: data.agents?.[key] })); } setBriefData(data); // Update the patient risk in sidebar from triage if (data.agents?.triage?.severity) { const sev = data.agents.triage.severity; const risk = sev <= 2 ? 'critical' : sev === 3 ? 'moderate' : 'stable'; setSelected(prev => prev ? { ...prev, risk_level: risk } : prev); } } catch { AGENT_ORDER.forEach(key => { setAgentStatuses(prev => ({ ...prev, [key]: 'error' })); }); } finally { setRunning(false); } }; if (showWelcome) return { setShowWelcome(false); setShowTutorial(true); }} />; if (showTutorial) return setShowTutorial(false)} />; return (
{/* Header */}
CodeBlue Context EMERGENCY INTELLIGENCE
● System Online {patients.length} patients
{/* 3-column layout */}
{/* Left: Patient List */}
{/* Center: Tabs + Content */}
{activeTab === 'orchestrate' ? ( ) : ( )}
{/* Right: Emergency Brief */}
{showApiModal && setShowApiModal(false)} />}
); }