"use client"; import { useState, useEffect, useRef } from "react"; import { startWorkflow, streamWorkflow, screenPatient, getGraphPatients } from "@/lib/api"; import { ClipboardCheck, CheckCircle, XCircle, AlertCircle, Loader2, ChevronRight, GitBranch } from "lucide-react"; import { clsx } from "clsx"; const FALLBACK_PATIENTS = ["P001", "P002", "P003", "P004", "P005"]; const QUICK_TRIALS = [ { id: "NCT04889131", label: "NCT04889131 – HER2+ Breast Cancer" }, { id: "NCT05123456", label: "NCT05123456 – Immunotherapy Combo" }, { id: "NCT05456789", label: "NCT05456789 – BRCA2 Prostate" }, { id: "NCT06112233", label: "NCT06112233 – EGFR NSCLC" }, { id: "NCT05334455", label: "NCT05334455 – MSI-H Colorectal" }, ]; const WORKFLOW_STATES = [ "PENDING", "INGESTING", "PARSING_PROTOCOL", "MATCHING", "SCORING", "RECRUITING", "COMPLETED", ]; function StateTracker({ events }: { events: any[] }) { const stateSet = new Set(events.map((e: any) => e.state)); return (
{WORKFLOW_STATES.map((state, i) => { const done = stateSet.has(state); const isCurrent = events[events.length - 1]?.state === state; return (
{state} {i < WORKFLOW_STATES.length - 1 && }
); })}
); } function ScoreBar({ score }: { score: number }) { const pct = Math.round(score * 100); const color = pct >= 80 ? "bg-emerald-500" : pct >= 60 ? "bg-amber-500" : "bg-red-500"; return (
= 80 ? "text-emerald-600" : pct >= 60 ? "text-amber-600" : "text-red-600")}> {pct}%
); } function CriterionRow({ criterion, met, triggered, confidence, note }: any) { const pass = met === true || triggered === false; const fail = met === false || triggered === true; return (
{pass ? : fail ? : }
{criterion}
{note &&
{note}
}
{confidence && ( {confidence} )}
); } function MatchPath({ path }: { path: any[] }) { if (!path?.length) return null; return (
Graph Match Path
{path.map((node: any, i: number) => ( {node.from?.split(":")[1] ?? node.from} —[{node.rel}]→ {node.to?.split(":")[1] ?? node.to} {node.note && ({node.note})} ))}
); } export default function ScreeningPage() { const [patientId, setPatientId] = useState("P001"); const [nctId, setNctId] = useState(QUICK_TRIALS[0].id); const [customNct, setCustomNct] = useState(""); const [useCustom, setUseCustom] = useState(false); const [loading, setLoading] = useState(false); const [mode, setMode] = useState<"screen" | "workflow">("screen"); const [result, setResult] = useState(null); const [workflowResult, setWorkflowResult] = useState(null); const [streamEvents, setStreamEvents] = useState([]); const [streamDone, setStreamDone] = useState(false); const [error, setError] = useState(""); const [graphPatients, setGraphPatients] = useState([]); const cleanupRef = useRef<(() => void) | null>(null); useEffect(() => { getGraphPatients(undefined, 500).then((d) => setGraphPatients(d.patients)).catch(() => {}); return () => { cleanupRef.current?.(); }; }, []); const effectiveNct = useCustom ? customNct : nctId; const handleScreen = async () => { if (!patientId.trim()) { setError("Patient ID is required"); return; } if (!effectiveNct.trim()) { setError("NCT ID is required"); return; } setLoading(true); setError(""); setResult(null); try { const data = await screenPatient(patientId.trim(), effectiveNct.trim()); setResult(data); } catch (e: any) { setError(e.message); } setLoading(false); }; const handleWorkflow = async () => { if (!patientId.trim()) { setError("Patient ID is required"); return; } cleanupRef.current?.(); setLoading(true); setError(""); setWorkflowResult(null); setStreamEvents([]); setStreamDone(false); try { const { workflow_id } = await startWorkflow(patientId.trim()); const stop = streamWorkflow( workflow_id, (evt) => setStreamEvents((prev) => [...prev, evt]), () => { setStreamDone(true); setLoading(false); }, ); cleanupRef.current = stop; } catch (e: any) { setError(e.message); setLoading(false); } }; return (

Patient Screening

AI-powered eligibility assessment using FHIR R4 patient data and A2A orchestration

{/* Mode tabs */}
{(["screen", "workflow"] as const).map((m) => ( ))}
setPatientId(e.target.value)} placeholder="e.g. P_C50_0001 or P001" className="w-full border border-slate-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500" /> {(graphPatients.length > 0 ? graphPatients : FALLBACK_PATIENTS.map((id) => ({ id }))).map((p: any) => ( ))}
{mode === "screen" && (
{ const val = e.target.value; const quick = QUICK_TRIALS.find((t) => t.id === val); if (quick) { setUseCustom(false); setNctId(val); } else { setUseCustom(true); setCustomNct(val); } }} placeholder="e.g. NCT04889131" className="w-full border border-slate-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500" /> {QUICK_TRIALS.map((t) => )}
)}
{error && (
{error}
)} {/* Single screen result */} {result && (

Eligibility Score

{result.eligible ? "ELIGIBLE" : "NOT ELIGIBLE"}
{result.summary && (

{result.summary}

)} {result.risk_flags?.length > 0 && (
{result.risk_flags.map((f: string, i: number) => ( {f} ))}
)}
{result.inclusion_results?.length > 0 && (

Inclusion Criteria

{result.inclusion_results.map((c: any, i: number) => )}
)} {result.exclusion_results?.length > 0 && (

Exclusion Criteria

{result.exclusion_results.map((c: any, i: number) => )}
)} {result.match_path?.length > 0 && (

Graph Explainability

)}
)} {/* Streaming A2A workflow */} {(streamEvents.length > 0 || loading) && mode === "workflow" && (

A2A Pipeline — Live

{!streamDone && } {streamDone && Complete}
{streamEvents.map((evt: any, i: number) => (
{evt.state} {evt.message} {evt.data?.eligible_count !== undefined && ( {evt.data.eligible_count} eligible )}
))}
{/* Final summary when complete */} {streamDone && (() => { const final = streamEvents[streamEvents.length - 1]; if (!final || !["COMPLETED", "FAILED"].includes(final.state)) return null; return (

{final.state === "COMPLETED" ? "Pipeline Complete" : "Pipeline Failed"}

{final.state === "COMPLETED" && (
{final.eligible_trials ?? 0}
Eligible Trials
{final.total_evaluated ?? 0}
Evaluated
{final.recruitment_records ?? 0}
Outreach Generated
)} {final.error && (

{final.error}

)}
); })()}
)}
); }