Spaces:
Sleeping
Sleeping
Download frontend/src/BriefPanel.jsx from anil988/CodeBlue-Context: direct link, hf CLI and curl.
- Browser
- Download file 15.1 kB
-
https://huggingface.co/spaces/anil988/CodeBlue-Context/resolve/main/frontend/src/BriefPanel.jsx
- Command line
-
hf download hf://spaces/anil988/CodeBlue-Context/frontend/src/BriefPanel.jsx
-
curl -L -o BriefPanel.jsx https://huggingface.co/spaces/anil988/CodeBlue-Context/resolve/main/frontend/src/BriefPanel.jsx
15.1 kB
| // BriefPanel.jsx — Right column: Emergency Brief + Treatment Checker | |
| import { useState } from 'react'; | |
| import { ShieldAlert, FlaskConical, Stethoscope, ListChecks, AlertTriangle, Clock, Send, CheckCircle, XCircle, AlertCircle, Zap, FileText, Activity, Shield, User } from 'lucide-react'; | |
| const API_BASE = import.meta.env.VITE_API_BASE || ''; | |
| const renderText = (val) => { | |
| if (val === null || val === undefined) return ''; | |
| if (typeof val === 'string') return val; | |
| if (typeof val === 'object') return JSON.stringify(val); | |
| return String(val); | |
| }; | |
| function Section({ title, icon, children, accent }) { | |
| return ( | |
| <div className="fade-up" style={{ | |
| marginBottom: 14, borderRadius: 10, | |
| background: 'var(--bg-surface)', | |
| border: `1px solid ${accent ? accent + '30' : 'var(--border-subtle)'}`, | |
| overflow: 'hidden', | |
| }}> | |
| <div style={{ | |
| padding: '8px 14px', display: 'flex', alignItems: 'center', gap: 8, | |
| borderBottom: '1px solid var(--border-subtle)', | |
| background: accent ? accent + '08' : 'transparent', | |
| }}> | |
| <span style={{ color: accent || 'var(--text-secondary)' }}>{icon}</span> | |
| <span style={{ fontSize: 11, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.06em', color: accent || 'var(--text-secondary)' }}> | |
| {title} | |
| </span> | |
| </div> | |
| <div style={{ padding: '10px 14px' }}>{children}</div> | |
| </div> | |
| ); | |
| } | |
| function VerdictBadge({ verdict }) { | |
| const cls = `badge verdict-${(verdict || 'not_evaluated').toLowerCase()}`; | |
| const labels = { SAFE: '✓ SAFE', CAUTION: '⚠ CAUTION', CONTRAINDICATED: '✕ CONTRAINDICATED', NOT_EVALUATED: '? NOT EVALUATED' }; | |
| return <span className={cls}>{labels[verdict] || verdict}</span>; | |
| } | |
| function TreatmentChecker({ patientId }) { | |
| const [input, setInput] = useState(''); | |
| const [loading, setLoading] = useState(false); | |
| const [result, setResult] = useState(null); | |
| const check = async () => { | |
| if (!input.trim() || !patientId) return; | |
| setLoading(true); setResult(null); | |
| 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_BASE}/check-treatment/${patientId}`, { | |
| method: 'POST', | |
| headers, | |
| body: JSON.stringify({ treatment: input.trim() }), | |
| }); | |
| const data = await res.json(); | |
| setResult(data.result); | |
| } catch { | |
| setResult({ treatment_verdict: 'NOT_EVALUATED', verdict_reason: 'Safety service unavailable.' }); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| const accent = result?.treatment_verdict === 'SAFE' ? 'var(--green-safe)' | |
| : result?.treatment_verdict === 'CONTRAINDICATED' ? 'var(--red-critical)' | |
| : result?.treatment_verdict === 'CAUTION' ? 'var(--amber-warning)' | |
| : 'var(--border-default)'; | |
| return ( | |
| <div style={{ | |
| padding: 14, borderRadius: 10, background: 'var(--bg-surface)', | |
| border: '1px solid var(--border-default)', | |
| }}> | |
| <div style={{ fontSize: 11, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.06em', color: 'var(--purple-ai)', marginBottom: 10, display: 'flex', alignItems: 'center', gap: 6 }}> | |
| <ShieldAlert size={12} /> Treatment Safety Check | |
| </div> | |
| <div style={{ display: 'flex', gap: 8 }}> | |
| <input | |
| className="input-dark" | |
| placeholder='e.g. "morphine 4mg IV"' | |
| value={input} | |
| onChange={e => setInput(e.target.value)} | |
| onKeyDown={e => e.key === 'Enter' && check()} | |
| /> | |
| <button className="btn-primary" onClick={check} disabled={loading || !input.trim() || !patientId} style={{ flexShrink: 0, padding: '9px 14px' }}> | |
| {loading ? <div className="spinner"/> : <Send size={13}/>} | |
| </button> | |
| </div> | |
| {result && ( | |
| <div className="fade-up" style={{ marginTop: 12, padding: 12, borderRadius: 8, border: `1px solid ${accent}40`, background: accent + '08' }}> | |
| <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}> | |
| <VerdictBadge verdict={result.treatment_verdict} /> | |
| </div> | |
| <p style={{ fontSize: 12, color: 'var(--text-primary)', lineHeight: 1.6, marginBottom: 8 }}>{result.verdict_reason}</p> | |
| {(result.allergy_risks || []).map((r, i) => ( | |
| <p key={i} style={{ fontSize: 11, color: 'var(--red-critical)', marginBottom: 3 }}>⚠ Allergy: {r.substance} — {r.risk}</p> | |
| ))} | |
| {(result.drug_interactions || []).map((d, i) => ( | |
| <p key={i} style={{ fontSize: 11, color: 'var(--amber-warning)', marginBottom: 3 }}>💊 Interaction: {d.drug_a} + {d.drug_b} → {d.interaction}</p> | |
| ))} | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } | |
| export function BriefPanel({ data, patient }) { | |
| if (!data) { | |
| return ( | |
| <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}> | |
| <div style={{ padding: '14px 16px', borderBottom: '1px solid var(--border-subtle)' }}> | |
| <span style={{ fontWeight: 700, fontSize: 13, color: 'var(--text-secondary)', textTransform: 'uppercase', letterSpacing: '0.05em' }}> | |
| Emergency Brief | |
| </span> | |
| </div> | |
| <div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: 30, gap: 12, color: 'var(--text-tertiary)', textAlign: 'center' }}> | |
| <AlertCircle size={36} style={{ opacity: 0.4 }} /> | |
| <p style={{ fontSize: 13 }}>Run emergency snapshot to generate brief</p> | |
| {patient && <TreatmentChecker patientId={patient.id} />} | |
| </div> | |
| </div> | |
| ); | |
| } | |
| const { agents, meta } = data; | |
| const triage = agents?.triage || {}; | |
| const action = agents?.action_protocol || {}; | |
| // Build collective reasoning log | |
| const collectiveReasoning = [ | |
| ...(triage.agent_reasoning || []).map(r => ({ agent: 'Triage', text: r })), | |
| ...(agents?.contraindication?.agent_reasoning || []).map(r => ({ agent: 'Guardian', text: r })), | |
| ...(agents?.labs?.agent_reasoning || []).map(r => ({ agent: 'Pathologist', text: r })), | |
| ...(agents?.differentials?.agent_reasoning || []).map(r => ({ agent: 'Resident', text: r })), | |
| ...(action.agent_reasoning || []).map(r => ({ agent: 'Attending', text: r })), | |
| ]; | |
| return ( | |
| <div style={{ height: '100%', display: 'flex', flexDirection: 'column', background: 'var(--bg-base)' }}> | |
| <div style={{ padding: '14px 16px', borderBottom: '1px solid var(--border-subtle)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> | |
| <span style={{ fontWeight: 800, fontSize: 13, letterSpacing: '0.05em', color: 'var(--text-primary)', textTransform: 'uppercase' }}> | |
| Emergency Brief | |
| </span> | |
| <div style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 10, color: 'var(--text-tertiary)' }}> | |
| <Clock size={10} /> {meta?.processing_ms}ms | |
| {meta?.gemini_enabled && <span style={{ color: 'var(--purple-ai)', fontWeight: 700 }}>· AI ACTIVE</span>} | |
| </div> | |
| </div> | |
| <div style={{ flex: 1, overflowY: 'auto', padding: '20px 24px' }}> | |
| {/* Patient Hero Header */} | |
| <div style={{ | |
| marginBottom: 20, paddingBottom: 16, borderBottom: '1px solid var(--border-subtle)', | |
| display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' | |
| }}> | |
| <div> | |
| <div style={{ fontSize: 28, fontWeight: 900, color: 'var(--text-primary)', letterSpacing: '-0.03em', marginBottom: 4 }}> | |
| {patient?.display_name || 'Anonymous Patient'} | |
| </div> | |
| <div style={{ fontSize: 13, color: 'var(--text-secondary)', fontWeight: 600, display: 'flex', alignItems: 'center', gap: 8 }}> | |
| <div style={{ display: 'flex', alignItems: 'center', gap: 4, background: 'var(--border-subtle)', padding: '2px 8px', borderRadius: 4 }}> | |
| <User size={12} /> | |
| <span>{patient?.age || '??'}y {patient?.gender || 'Unknown'}</span> | |
| </div> | |
| <span style={{ fontSize: 11, color: 'var(--text-tertiary)', fontFamily: 'var(--font-mono)' }}>{patient?.id}</span> | |
| {(!patient?.age || patient?.age === '??') && ( | |
| <span style={{ fontSize: 10, color: 'var(--amber-warning)', background: 'var(--amber-bg)', padding: '2px 8px', borderRadius: 4 }}> | |
| Demographics Unverified | |
| </span> | |
| )} | |
| </div> | |
| </div> | |
| <div className={`badge badge-${patient?.risk_level || 'stable'}`} style={{ padding: '6px 12px' }}> | |
| {patient?.risk_level?.toUpperCase()} | |
| </div> | |
| </div> | |
| {/* Primary Clinical Directive */} | |
| {action.primary_directive && ( | |
| <div className="fade-up directive-banner" style={{ | |
| padding: '20px', borderRadius: 16, marginBottom: 24, | |
| boxShadow: 'var(--shadow-lg)', | |
| position: 'relative', overflow: 'hidden' | |
| }}> | |
| <div style={{ fontSize: 10, fontWeight: 900, textTransform: 'uppercase', opacity: 0.9, letterSpacing: '0.15em', marginBottom: 10, display: 'flex', alignItems: 'center', gap: 6 }}> | |
| <Zap size={12} fill="white" /> IMMEDIATE CLINICAL DIRECTIVE | |
| </div> | |
| <div style={{ fontSize: 20, fontWeight: 900, lineHeight: 1.25, letterSpacing: '-0.01em' }}> | |
| {renderText(action.primary_directive)} | |
| </div> | |
| </div> | |
| )} | |
| {/* Immediate Critical Risks (Condition-First) */} | |
| {triage.critical_risks?.length > 0 && ( | |
| <SectionBlock title="Physiological Threats" icon={<ShieldAlert size={14}/>} accent="var(--red-critical)"> | |
| <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}> | |
| {triage.critical_risks.map((r, i) => ( | |
| <div key={i} className="pulse-critical" style={{ | |
| background: 'var(--red-bg)', border: '1px solid var(--red-glow)', | |
| borderRadius: 12, padding: '12px 16px' | |
| }}> | |
| <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}> | |
| <AlertTriangle size={14} color="var(--red-critical)" /> | |
| <span style={{ fontWeight: 800, fontSize: 13, color: 'var(--red-critical)' }}> | |
| {renderText(r.finding || r.condition)} | |
| </span> | |
| </div> | |
| <div style={{ fontSize: 12, color: 'var(--text-secondary)', marginLeft: 22, lineHeight: 1.4 }}> | |
| <span style={{ color: 'var(--text-tertiary)' }}>Risk:</span> <span style={{ fontWeight: 700, color: 'var(--text-primary)' }}>{renderText(r.risk)}</span> | |
| <br/> | |
| <span style={{ fontSize: 11, opacity: 0.7 }}>Mechanism: {renderText(r.danger)}</span> | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| </SectionBlock> | |
| )} | |
| {/* Action Mapping */} | |
| {action.action_mapping?.length > 0 && ( | |
| <SectionBlock title="Action Mapping" icon={<Zap size={14}/>} accent="var(--blue-accent)"> | |
| <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}> | |
| {action.action_mapping.map((m, i) => ( | |
| <div key={i} style={{ display: 'flex', gap: 14, alignItems: 'flex-start' }}> | |
| <div style={{ | |
| width: 22, height: 22, borderRadius: '6px', background: 'var(--blue-accent)', | |
| display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', | |
| fontSize: 11, fontWeight: 900, flexShrink: 0, marginTop: 1 | |
| }}> | |
| {i + 1} | |
| </div> | |
| <div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}> | |
| <div style={{ fontSize: 13, fontWeight: 700, color: 'var(--text-primary)' }}>{renderText(m.action)}</div> | |
| <div style={{ fontSize: 11, color: 'var(--text-tertiary)', fontWeight: 500 }}>Objective: {renderText(m.condition)}</div> | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| </SectionBlock> | |
| )} | |
| {/* Clinical Synthesis */} | |
| {action.clinical_summary && ( | |
| <SectionBlock title="Clinical Synthesis" icon={<FileText size={14}/>} accent="var(--purple-ai)"> | |
| <div style={{ | |
| fontSize: 12.5, lineHeight: 1.6, color: 'var(--text-secondary)', | |
| background: 'var(--purple-bg)', padding: '14px 18px', borderRadius: 12, border: '1px solid var(--border-subtle)', | |
| boxShadow: 'var(--shadow-sm)' | |
| }}> | |
| "{renderText(action.clinical_summary)}" | |
| </div> | |
| </SectionBlock> | |
| )} | |
| {/* Safety Checker */} | |
| <div style={{ marginTop: 24 }}> | |
| <TreatmentChecker patientId={patient?.id} /> | |
| </div> | |
| {/* Reasoning Pipeline */} | |
| {collectiveReasoning.length > 0 && ( | |
| <div style={{ marginTop: 32, paddingTop: 20, borderTop: '1px solid var(--border-subtle)' }}> | |
| <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}> | |
| <div style={{ fontSize: 10, fontWeight: 800, color: 'var(--text-tertiary)', textTransform: 'uppercase', display: 'flex', alignItems: 'center', gap: 6 }}> | |
| <Activity size={10} /> Agent Reasoning Pipeline | |
| </div> | |
| <div style={{ fontSize: 9, fontWeight: 700, color: 'var(--green-safe)', background: 'var(--green-bg)', padding: '2px 6px', borderRadius: 4 }}> | |
| CERTIFIED SYSTEM LOGIC | |
| </div> | |
| </div> | |
| <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}> | |
| {collectiveReasoning.map((r, i) => ( | |
| <div key={i} style={{ fontSize: 10.5, color: 'var(--text-secondary)', display: 'flex', gap: 12 }}> | |
| <span style={{ fontWeight: 800, color: 'var(--purple-ai)', minWidth: 65, textTransform: 'uppercase', fontSize: 9, letterSpacing: '0.02em' }}>{r.agent}</span> | |
| <span style={{ lineHeight: 1.5, opacity: 0.8 }}>{renderText(r.text)}</span> | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| ); | |
| } | |
| function SectionBlock({ title, icon, children, accent = 'var(--blue-accent)' }) { | |
| return ( | |
| <div style={{ marginBottom: 24 }}> | |
| <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12, borderBottom: `1px solid ${accent}20`, paddingBottom: 6 }}> | |
| <div style={{ color: accent }}>{icon}</div> | |
| <span style={{ fontSize: 11, fontWeight: 900, textTransform: 'uppercase', letterSpacing: '0.08em', color: 'var(--text-primary)' }}>{title}</span> | |
| </div> | |
| {children} | |
| </div> | |
| ); | |
| } | |