Spaces:
Sleeping
Sleeping
Download frontend/src/AgentPanel.jsx from anil988/CodeBlue-Context: direct link, hf CLI and curl.
- Browser
- Download file 13.5 kB
-
https://huggingface.co/spaces/anil988/CodeBlue-Context/resolve/main/frontend/src/AgentPanel.jsx
- Command line
-
hf download hf://spaces/anil988/CodeBlue-Context/frontend/src/AgentPanel.jsx
-
curl -L -o AgentPanel.jsx https://huggingface.co/spaces/anil988/CodeBlue-Context/resolve/main/frontend/src/AgentPanel.jsx
13.5 kB
| // AgentPanel.jsx β Center orchestration column | |
| import { useState } from 'react'; | |
| import { ShieldAlert, FlaskConical, Stethoscope, ListChecks, Zap, CheckCircle, Loader, Circle, ChevronDown, ChevronUp } from 'lucide-react'; | |
| const AGENTS = [ | |
| { key: 'triage', label: 'Triage Agent', icon: <Zap size={14}/>, desc: 'Severity classification & chief complaint' }, | |
| { key: 'contraindication', label: 'Contraindication Guardian', icon: <ShieldAlert size={14}/>, desc: 'Drug interactions & allergy risks' }, | |
| { key: 'labs', label: 'Lab Interpreter', icon: <FlaskConical size={14}/>, desc: 'Critical lab findings & patterns' }, | |
| { key: 'differentials', label: 'Differential Diagnosis', icon: <Stethoscope size={14}/>, desc: 'Top 3 differentials from evidence' }, | |
| { key: 'action_protocol', label: 'Action Protocol', icon: <ListChecks size={14}/>, desc: 'Immediate ordered steps & disposition' }, | |
| ]; | |
| const THINKING_MESSAGES = { | |
| triage: "Analyzing vitals and identifying life-threats...", | |
| contraindication: "Scanning history for high-risk medication overlaps...", | |
| labs: "Interpreting metabolic panels and blood gases...", | |
| differentials: "Cross-referencing symptoms with clinical evidence...", | |
| action_protocol: "Synthesizing final emergency stabilization steps...", | |
| }; | |
| function StatusIcon({ status }) { | |
| if (status === 'working') return <div className="spinner" />; | |
| if (status === 'complete') return <CheckCircle size={15} color="var(--green-safe)" />; | |
| if (status === 'error') return <Circle size={15} color="var(--red-critical)" />; | |
| return <Circle size={15} color="var(--text-tertiary)" />; | |
| } | |
| function AgentRow({ agent, status, result, expanded, onToggle }) { | |
| const cls = status === 'idle' ? 'agent-idle' | |
| : status === 'working' ? 'agent-working' | |
| : status === 'complete' ? 'agent-complete' | |
| : 'agent-error'; | |
| const hasResult = result && status === 'complete'; | |
| return ( | |
| <div className={`card ${cls}`} style={{ marginBottom: 8, transition: 'all 0.3s' }}> | |
| <div | |
| onClick={hasResult ? onToggle : undefined} | |
| style={{ | |
| display: 'flex', alignItems: 'center', gap: 10, padding: '10px 14px', | |
| cursor: hasResult ? 'pointer' : 'default', | |
| }} | |
| > | |
| <div style={{ | |
| width: 28, height: 28, borderRadius: 7, | |
| background: 'rgba(255,255,255,0.04)', | |
| display: 'flex', alignItems: 'center', justifyContent: 'center', | |
| color: status === 'complete' ? 'var(--green-safe)' : status === 'working' ? 'var(--blue-accent)' : 'var(--text-tertiary)', | |
| }}> | |
| {agent.icon} | |
| </div> | |
| <div style={{ flex: 1, minWidth: 0 }}> | |
| <div style={{ fontSize: 12, fontWeight: 600, color: status === 'idle' ? 'var(--text-secondary)' : 'var(--text-primary)' }}> | |
| {agent.label} | |
| </div> | |
| <div style={{ fontSize: 10, color: status === 'working' ? 'var(--blue-accent)' : 'var(--text-tertiary)', fontWeight: status === 'working' ? 600 : 400 }}> | |
| {status === 'working' ? THINKING_MESSAGES[agent.key] : agent.desc} | |
| </div> | |
| </div> | |
| <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}> | |
| <StatusIcon status={status} /> | |
| {hasResult && (expanded ? <ChevronUp size={12} color="var(--text-tertiary)"/> : <ChevronDown size={12} color="var(--text-tertiary)"/>)} | |
| </div> | |
| </div> | |
| {hasResult && expanded && ( | |
| <div style={{ padding: '0 14px 12px', borderTop: '1px solid var(--border-subtle)', paddingTop: 10 }} className="fade-in"> | |
| <AgentResultPreview agentKey={agent.key} result={result} /> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } | |
| function AgentResultPreview({ agentKey, result }) { | |
| const style = { fontSize: 11, color: 'var(--text-secondary)', lineHeight: 1.6 }; | |
| const tag = (text, color) => ( | |
| <span style={{ background: color + '20', color, border: `1px solid ${color}40`, borderRadius: 4, padding: '1px 6px', fontSize: 10, fontWeight: 700, marginRight: 4 }}> | |
| {text} | |
| </span> | |
| ); | |
| if (agentKey === 'triage') return ( | |
| <div style={style}> | |
| {tag(`ESI ${result.severity}: ${result.severity_label}`, | |
| result.severity <= 2 ? 'var(--red-critical)' : result.severity === 3 ? 'var(--amber-warning)' : 'var(--green-safe)')} | |
| <p style={{ marginTop: 6 }}>{result.chief_complaint}</p> | |
| {(result.time_critical_flags || []).map((f, i) => <p key={i}>β‘ {f}</p>)} | |
| {result.agent_reasoning && ( | |
| <div style={{ marginTop: 8, padding: 8, background: 'var(--bg-base)', borderRadius: 6, borderLeft: '3px solid var(--blue-accent)' }}> | |
| <p style={{ fontSize: 10, fontWeight: 700, marginBottom: 4, color: 'var(--text-primary)' }}>AGENT REASONING</p> | |
| {result.agent_reasoning.map((r, i) => <p key={i} style={{ fontSize: 10 }}>β’ {r}</p>)} | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| if (agentKey === 'contraindication') return ( | |
| <div style={style}> | |
| {tag(result.treatment_verdict, | |
| result.treatment_verdict === 'SAFE' ? 'var(--green-safe)' | |
| : result.treatment_verdict === 'CONTRAINDICATED' ? 'var(--red-critical)' | |
| : 'var(--amber-warning)')} | |
| <p style={{ marginTop: 6 }}>{result.verdict_reason}</p> | |
| {(result.allergy_risks || []).slice(0, 2).map((r, i) => ( | |
| <p key={i} style={{ color: 'var(--red-critical)' }}>β Allergy: {r.substance} β {r.risk}</p> | |
| ))} | |
| {(result.drug_interactions || []).slice(0, 2).map((d, i) => ( | |
| <p key={i} style={{ color: 'var(--amber-warning)' }}>π {d.drug_a} + {d.drug_b}: {d.interaction}</p> | |
| ))} | |
| {result.agent_reasoning && ( | |
| <div style={{ marginTop: 8, padding: 8, background: 'var(--bg-base)', borderRadius: 6, borderLeft: '3px solid var(--blue-accent)' }}> | |
| <p style={{ fontSize: 10, fontWeight: 700, marginBottom: 4, color: 'var(--text-primary)' }}>AGENT REASONING</p> | |
| {result.agent_reasoning.map((r, i) => <p key={i} style={{ fontSize: 10 }}>β’ {r}</p>)} | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| if (agentKey === 'labs') return ( | |
| <div style={style}> | |
| {(result.critical_labs || []).map((l, i) => ( | |
| <p key={i} style={{ color: 'var(--red-critical)' }}>π΄ {l.name}: {l.value} β {l.significance}</p> | |
| ))} | |
| {(result.abnormal_labs || []).map((l, i) => ( | |
| <p key={i} style={{ color: 'var(--amber-warning)' }}>π‘ {l.name}: {l.value} β {l.significance}</p> | |
| ))} | |
| {(result.immediate_concerns || []).slice(0, 2).map((c, i) => <p key={i}>β {c}</p>)} | |
| {result.agent_reasoning && ( | |
| <div style={{ marginTop: 8, padding: 8, background: 'var(--bg-base)', borderRadius: 6, borderLeft: '3px solid var(--blue-accent)' }}> | |
| <p style={{ fontSize: 10, fontWeight: 700, marginBottom: 4, color: 'var(--text-primary)' }}>AGENT REASONING</p> | |
| {result.agent_reasoning.map((r, i) => <p key={i} style={{ fontSize: 10 }}>β’ {r}</p>)} | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| if (agentKey === 'differentials') return ( | |
| <div style={style}> | |
| {(result.differentials || []).map((d, i) => ( | |
| <div key={i} style={{ marginBottom: 6 }}> | |
| {tag(d.likelihood, d.likelihood === 'HIGH' ? 'var(--red-critical)' : d.likelihood === 'MODERATE' ? 'var(--amber-warning)' : 'var(--text-secondary)')} | |
| <span style={{ fontWeight: 600, color: 'var(--text-primary)' }}>{d.diagnosis}</span> | |
| {d.must_rule_out && <span style={{ color: 'var(--red-critical)', fontSize: 10, marginLeft: 6 }}>MUST RULE OUT</span>} | |
| </div> | |
| ))} | |
| {result.agent_reasoning && ( | |
| <div style={{ marginTop: 8, padding: 8, background: 'var(--bg-base)', borderRadius: 6, borderLeft: '3px solid var(--blue-accent)' }}> | |
| <p style={{ fontSize: 10, fontWeight: 700, marginBottom: 4, color: 'var(--text-primary)' }}>AGENT REASONING</p> | |
| {result.agent_reasoning.map((r, i) => <p key={i} style={{ fontSize: 10 }}>β’ {r}</p>)} | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| if (agentKey === 'action_protocol') return ( | |
| <div style={style}> | |
| {(result.immediate_actions || []).map((a, i) => <p key={i}>{i + 1}. {a}</p>)} | |
| {result.disposition && ( | |
| <p style={{ marginTop: 6, fontWeight: 700, color: 'var(--blue-accent)' }}> | |
| Disposition β {result.disposition} | |
| </p> | |
| )} | |
| {result.agent_reasoning && ( | |
| <div style={{ marginTop: 8, padding: 8, background: 'var(--bg-base)', borderRadius: 6, borderLeft: '3px solid var(--blue-accent)' }}> | |
| <p style={{ fontSize: 10, fontWeight: 700, marginBottom: 4, color: 'var(--text-primary)' }}>AGENT REASONING</p> | |
| {result.agent_reasoning.map((r, i) => <p key={i} style={{ fontSize: 10 }}>β’ {r}</p>)} | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| return null; | |
| } | |
| export function AgentPanel({ agentStatuses, agentResults, onRun, patient, running, focus, setFocus }) { | |
| const [expanded, setExpanded] = useState({}); | |
| const toggle = key => setExpanded(e => ({ ...e, [key]: !e[key] })); | |
| const doneCount = AGENTS.filter(a => agentStatuses[a.key] === 'complete').length; | |
| return ( | |
| <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}> | |
| <div style={{ padding: '14px 16px', borderBottom: '1px solid var(--border-subtle)' }}> | |
| <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> | |
| <span style={{ fontWeight: 700, fontSize: 13, color: 'var(--text-secondary)', textTransform: 'uppercase', letterSpacing: '0.05em' }}> | |
| Agent Orchestration | |
| </span> | |
| {running && ( | |
| <span style={{ fontSize: 11, color: 'var(--blue-accent)', display: 'flex', alignItems: 'center', gap: 5 }}> | |
| <div className="spinner" style={{ width: 12, height: 12 }} /> {doneCount}/5 complete | |
| </span> | |
| )} | |
| </div> | |
| </div> | |
| <div style={{ padding: '12px 14px', flex: 1, overflowY: 'auto' }}> | |
| {!patient && ( | |
| <div style={{ textAlign: 'center', padding: '40px 20px', color: 'var(--text-tertiary)', fontSize: 13 }}> | |
| Select a patient to run emergency analysis | |
| </div> | |
| )} | |
| {patient && ( | |
| <> | |
| {/* Clinical Focus Selector */} | |
| <div style={{ marginBottom: 14 }}> | |
| <p style={{ fontSize: 9, fontWeight: 800, color: 'var(--text-tertiary)', textTransform: 'uppercase', marginBottom: 6 }}>Clinical Focus</p> | |
| <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}> | |
| {['General', 'Cardiac', 'Trauma', 'Respiratory', 'Neuro'].map(f => ( | |
| <div | |
| key={f} | |
| onClick={() => !running && setFocus?.(f)} | |
| style={{ | |
| padding: '4px 10px', borderRadius: 20, fontSize: 10, fontWeight: 700, cursor: 'pointer', | |
| background: focus === f ? 'var(--blue-accent)' : 'var(--bg-surface)', | |
| color: focus === f ? '#fff' : 'var(--text-secondary)', | |
| border: focus === f ? 'none' : '1px solid var(--border-subtle)', | |
| transition: 'all 0.2s' | |
| }} | |
| > | |
| {f} | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| {/* Agent rows */} | |
| {AGENTS.map(a => ( | |
| <AgentRow | |
| key={a.key} agent={a} | |
| status={agentStatuses[a.key] || 'idle'} | |
| result={agentResults[a.key]} | |
| expanded={expanded[a.key]} | |
| onToggle={() => toggle(a.key)} | |
| /> | |
| ))} | |
| {/* Run button */} | |
| <button className="btn-danger pulse-on-hover" style={{ marginTop: 10, width: '100%' }} onClick={onRun} disabled={running}> | |
| {running | |
| ? <><div className="spinner" /> AI Agents Orchestrating...</> | |
| : <><Zap size={15} /> Analyze Patient Record</>} | |
| </button> | |
| {/* Collaboration Log */} | |
| {running || Object.keys(agentResults).length > 0 ? ( | |
| <div style={{ marginTop: 20, borderTop: '1px solid var(--border-subtle)', paddingTop: 16 }}> | |
| <p style={{ fontSize: 10, fontWeight: 700, color: 'var(--text-tertiary)', textTransform: 'uppercase', marginBottom: 10, letterSpacing: '0.05em' }}> | |
| Collaboration Thread | |
| </p> | |
| <div style={{ | |
| background: 'var(--bg-surface)', padding: 12, borderRadius: 8, border: '1px solid var(--border-subtle)', | |
| fontFamily: 'var(--font-mono)', fontSize: 10, lineHeight: 1.5, maxHeight: 120, overflowY: 'auto' | |
| }}> | |
| {AGENTS.filter(a => agentResults[a.key] || agentStatuses[a.key] === 'working').map((a, i) => ( | |
| <div key={i} style={{ marginBottom: 6 }} className="fade-in"> | |
| <span style={{ color: 'var(--blue-accent)', fontWeight: 700 }}>[{a.label}]: </span> | |
| <span style={{ color: 'var(--text-secondary)' }}> | |
| {agentStatuses[a.key] === 'working' ? 'Transmitting findings...' : (agentResults[a.key]?.agent_reasoning?.[0] || 'Processed record successfully.')} | |
| </span> | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| ) : null} | |
| </> | |
| )} | |
| </div> | |
| </div> | |
| ); | |
| } | |