Spaces:
Sleeping
Sleeping
Hackathon Builder
Emergency Intelligence Upgrade: Multi-file synthesis, demographic preservation, and senior physician-grade reasoning
3024450 Download frontend/src/App.jsx from anil988/CodeBlue-Context: direct link, hf CLI and curl.
- Browser
- Download file 10.8 kB
-
https://huggingface.co/spaces/anil988/CodeBlue-Context/resolve/main/frontend/src/App.jsx
- Command line
-
hf download hf://spaces/anil988/CodeBlue-Context/frontend/src/App.jsx
-
curl -L -o App.jsx https://huggingface.co/spaces/anil988/CodeBlue-Context/resolve/main/frontend/src/App.jsx
10.8 kB
| 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 <Welcome onStart={() => { setShowWelcome(false); setShowTutorial(true); }} />; | |
| if (showTutorial) return <Tutorial onComplete={() => setShowTutorial(false)} />; | |
| return ( | |
| <div style={{ display: 'flex', flexDirection: 'column', height: '100vh', overflow: 'hidden' }}> | |
| {/* Header */} | |
| <header style={{ | |
| display: 'flex', alignItems: 'center', justifyContent: 'space-between', | |
| padding: '0 20px', height: 48, flexShrink: 0, | |
| background: 'var(--bg-surface)', borderBottom: '1px solid var(--border-subtle)', | |
| zIndex: 10, | |
| }}> | |
| <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}> | |
| <div style={{ animation: 'heartbeat 1.4s infinite', color: 'var(--red-critical)' }}> | |
| <Activity size={20} /> | |
| </div> | |
| <span style={{ fontWeight: 800, fontSize: 15, letterSpacing: '-0.02em' }}>CodeBlue Context</span> | |
| <span style={{ | |
| fontSize: 10, fontWeight: 600, padding: '2px 8px', borderRadius: 99, | |
| background: 'rgba(255,45,85,0.15)', color: 'var(--red-critical)', | |
| border: '1px solid rgba(255,45,85,0.3)', letterSpacing: '0.05em', | |
| }}> | |
| EMERGENCY INTELLIGENCE | |
| </span> | |
| </div> | |
| <div style={{ display: 'flex', alignItems: 'center', gap: 16, fontSize: 11, color: 'var(--text-tertiary)' }}> | |
| <button | |
| onClick={() => setShowApiModal(true)} | |
| className="btn-ghost" | |
| style={{ color: 'var(--purple-ai)', borderColor: 'rgba(191,90,242,0.3)', padding: '6px 12px' }} | |
| > | |
| 🔑 Provide API Key | |
| </button> | |
| <button | |
| className="btn-demo" | |
| onClick={() => { | |
| const demoPatient = patients.find(p => p.id === 'CB-001') || patients[0]; | |
| if (demoPatient) { | |
| handleSelectPatient(demoPatient); | |
| setTimeout(() => runOrchestration(demoPatient), 50); // slight delay to let UI select it | |
| } | |
| }} | |
| style={{ | |
| padding: '6px 14px', borderRadius: 6, fontWeight: 700, cursor: 'pointer', | |
| background: 'var(--blue-accent)', color: '#fff', border: 'none', | |
| transition: 'all 0.2s', | |
| display: 'flex', alignItems: 'center', gap: '6px' | |
| }} | |
| onMouseOver={(e) => e.currentTarget.style.filter = 'brightness(1.1)'} | |
| onMouseOut={(e) => e.currentTarget.style.filter = 'brightness(1)'} | |
| > | |
| <Activity size={14} /> Run Live Demo | |
| </button> | |
| <span> | |
| <span style={{ color: 'var(--green-safe)', fontWeight: 700 }}>●</span> System Online | |
| </span> | |
| <span>{patients.length} patients</span> | |
| </div> | |
| </header> | |
| {/* 3-column layout */} | |
| <div style={{ flex: 1, display: 'grid', gridTemplateColumns: '240px 420px 1fr', overflow: 'hidden' }}> | |
| {/* Left: Patient List */} | |
| <div style={{ borderRight: '1px solid var(--border-subtle)', overflow: 'hidden', display: 'flex', flexDirection: 'column' }}> | |
| <PatientPanel | |
| patients={patients} | |
| selected={selected} | |
| onSelect={handleSelectPatient} | |
| onUpload={handleUpload} | |
| loading={patientsLoading} | |
| /> | |
| </div> | |
| {/* Center: Tabs + Content */} | |
| <div style={{ borderRight: '1px solid var(--border-subtle)', overflow: 'hidden', display: 'flex', flexDirection: 'column' }}> | |
| <div style={{ display: 'flex', background: 'var(--bg-base)', padding: '4px 4px 0', borderBottom: '1px solid var(--border-subtle)' }}> | |
| <button | |
| onClick={() => setActiveTab('orchestrate')} | |
| style={{ | |
| flex: 1, padding: '10px', fontSize: 11, fontWeight: 700, cursor: 'pointer', border: 'none', | |
| background: activeTab === 'orchestrate' ? 'var(--bg-surface)' : 'transparent', | |
| color: activeTab === 'orchestrate' ? 'var(--blue-accent)' : 'var(--text-secondary)', | |
| borderRadius: '8px 8px 0 0', borderBottom: activeTab === 'orchestrate' ? '2px solid var(--blue-accent)' : 'none' | |
| }} | |
| > | |
| ORCHESTRATION | |
| </button> | |
| <button | |
| onClick={() => setActiveTab('consult')} | |
| style={{ | |
| flex: 1, padding: '10px', fontSize: 11, fontWeight: 700, cursor: 'pointer', border: 'none', | |
| background: activeTab === 'consult' ? 'var(--bg-surface)' : 'transparent', | |
| color: activeTab === 'consult' ? 'var(--purple-ai)' : 'var(--text-secondary)', | |
| borderRadius: '8px 8px 0 0', borderBottom: activeTab === 'consult' ? '2px solid var(--purple-ai)' : 'none' | |
| }} | |
| > | |
| ADVANCED CONSULT | |
| </button> | |
| </div> | |
| <div style={{ flex: 1, overflow: 'hidden' }}> | |
| {activeTab === 'orchestrate' ? ( | |
| <AgentPanel | |
| patient={selected} | |
| agentStatuses={agentStatuses} | |
| agentResults={agentResults} | |
| onRun={runOrchestration} | |
| running={running} | |
| focus={focus} | |
| setFocus={setFocus} | |
| /> | |
| ) : ( | |
| <ConsultPanel | |
| patientId={selected?.id} | |
| previousResults={agentResults} | |
| /> | |
| )} | |
| </div> | |
| </div> | |
| {/* Right: Emergency Brief */} | |
| <div style={{ overflow: 'hidden', display: 'flex', flexDirection: 'column' }}> | |
| <BriefPanel data={briefData} patient={selected} /> | |
| </div> | |
| </div> | |
| {showApiModal && <ApiKeyModal onClose={() => setShowApiModal(false)} />} | |
| </div> | |
| ); | |
| } |