Spaces:
Runtime error
Runtime error
Upload folder using huggingface_hub
Browse files
frontend/src/app/architecture/page.tsx
CHANGED
|
@@ -118,9 +118,9 @@ const fadeUp = {
|
|
| 118 |
|
| 119 |
export default function Architecture() {
|
| 120 |
return (
|
| 121 |
-
<main className="min-h-
|
| 122 |
|
| 123 |
-
<div className="max-w-
|
| 124 |
|
| 125 |
{/* HEADER BLOCK */}
|
| 126 |
<motion.header
|
|
@@ -136,7 +136,7 @@ export default function Architecture() {
|
|
| 136 |
PIPELINE<br />TOPOLOGY
|
| 137 |
</h1>
|
| 138 |
</div>
|
| 139 |
-
<div className="text-[12px] text-slate font-bold tracking-
|
| 140 |
REF: HFL-OS-ARCH-01<br/>
|
| 141 |
REV: 2.6.4<br/>
|
| 142 |
STATUS: ACTIVE
|
|
@@ -146,7 +146,7 @@ export default function Architecture() {
|
|
| 146 |
{/* BLUEPRINT GRID */}
|
| 147 |
<motion.div
|
| 148 |
initial="hidden" animate="show" variants={fadeUp}
|
| 149 |
-
className="w-full bg-ink grid grid-cols-1 md:grid-cols-3 gap-
|
| 150 |
>
|
| 151 |
{/* COLUMN 1 */}
|
| 152 |
<div className="bg-canvas-cream flex flex-col justify-between p-8 relative group">
|
|
@@ -219,11 +219,11 @@ export default function Architecture() {
|
|
| 219 |
<div className="text-[10px] text-hazard font-bold tracking-[0.15em] mb-2 uppercase">SPEC_01</div>
|
| 220 |
<h2 className="text-[24px] font-sans font-black tracking-[-0.02em] uppercase text-ink leading-tight">DATA SANITIZATION & FEATURES</h2>
|
| 221 |
</div>
|
| 222 |
-
<div className="md:col-span-3 bg-ink p-
|
| 223 |
-
<div className="
|
| 224 |
|
| 225 |
<div className="bg-canvas-cream p-8">
|
| 226 |
-
<h3 className="text-[12px] font-bold text-ink tracking-
|
| 227 |
<p className="text-[13px] text-slate leading-relaxed uppercase mb-6">
|
| 228 |
Raw telemetry is hostile. The ingestion script aggressively drops redundant alert flags to prevent target-leakage. Broken unicode encodings from edge sensors are patched on the fly.
|
| 229 |
</p>
|
|
@@ -235,7 +235,7 @@ export default function Architecture() {
|
|
| 235 |
</div>
|
| 236 |
|
| 237 |
<div className="bg-canvas-cream p-8">
|
| 238 |
-
<h3 className="text-[12px] font-bold text-ink tracking-
|
| 239 |
<p className="text-[13px] text-slate leading-relaxed uppercase mb-6">
|
| 240 |
Missing sensor drops are repaired using a fitted <code className="bg-ink text-white px-1">SimpleImputer</code>. We inject a non-linear hint directly into the AST: <code className="bg-ink text-white px-1">Risk_Severity</code>. All floats are strictly normalized.
|
| 241 |
</p>
|
|
@@ -264,7 +264,7 @@ export default function Architecture() {
|
|
| 264 |
When a clinician uploads a new batch dataset, the API spawns a background thread. It retrains all 5 models, calculates the weighted F1-Score against a 20% holdout, and checks <code className="bg-ink text-white px-1">registry.json</code>. If the new score degrades, the batch is destroyed. If it improves, the active `.pkl` weights are hot-swapped globally without dropping WebSocket streams.
|
| 265 |
</p>
|
| 266 |
|
| 267 |
-
<div className="inline-flex flex-col md:flex-row border-2 border-ink bg-ink gap-
|
| 268 |
<div className="bg-canvas-cream text-ink px-6 py-4 font-bold text-[12px] flex items-center justify-center">
|
| 269 |
F1 SCORE > ACTIVE_V1
|
| 270 |
</div>
|
|
|
|
| 118 |
|
| 119 |
export default function Architecture() {
|
| 120 |
return (
|
| 121 |
+
<main className="min-h-dvh bg-canvas-cream flex flex-col font-mono selection:bg-hazard selection:text-white pb-16 overflow-x-hidden">
|
| 122 |
|
| 123 |
+
<div className="max-w-350 mx-auto w-full px-4 md:px-8 py-16 flex-1 flex flex-col">
|
| 124 |
|
| 125 |
{/* HEADER BLOCK */}
|
| 126 |
<motion.header
|
|
|
|
| 136 |
PIPELINE<br />TOPOLOGY
|
| 137 |
</h1>
|
| 138 |
</div>
|
| 139 |
+
<div className="text-[12px] text-slate font-bold tracking-widest uppercase text-right max-w-xs">
|
| 140 |
REF: HFL-OS-ARCH-01<br/>
|
| 141 |
REV: 2.6.4<br/>
|
| 142 |
STATUS: ACTIVE
|
|
|
|
| 146 |
{/* BLUEPRINT GRID */}
|
| 147 |
<motion.div
|
| 148 |
initial="hidden" animate="show" variants={fadeUp}
|
| 149 |
+
className="w-full bg-ink grid grid-cols-1 md:grid-cols-3 gap-0.5 p-0.5 mb-16 shadow-[16px_16px_0px_0px_rgba(230,25,25,1)]"
|
| 150 |
>
|
| 151 |
{/* COLUMN 1 */}
|
| 152 |
<div className="bg-canvas-cream flex flex-col justify-between p-8 relative group">
|
|
|
|
| 219 |
<div className="text-[10px] text-hazard font-bold tracking-[0.15em] mb-2 uppercase">SPEC_01</div>
|
| 220 |
<h2 className="text-[24px] font-sans font-black tracking-[-0.02em] uppercase text-ink leading-tight">DATA SANITIZATION & FEATURES</h2>
|
| 221 |
</div>
|
| 222 |
+
<div className="md:col-span-3 bg-ink p-0.5">
|
| 223 |
+
<div className="grid grid-cols-1 md:grid-cols-2 h-full gap-0.5 bg-ink">
|
| 224 |
|
| 225 |
<div className="bg-canvas-cream p-8">
|
| 226 |
+
<h3 className="text-[12px] font-bold text-ink tracking-widest mb-4 uppercase border-b-2 border-ink pb-2">/// Pipeline_Execution</h3>
|
| 227 |
<p className="text-[13px] text-slate leading-relaxed uppercase mb-6">
|
| 228 |
Raw telemetry is hostile. The ingestion script aggressively drops redundant alert flags to prevent target-leakage. Broken unicode encodings from edge sensors are patched on the fly.
|
| 229 |
</p>
|
|
|
|
| 235 |
</div>
|
| 236 |
|
| 237 |
<div className="bg-canvas-cream p-8">
|
| 238 |
+
<h3 className="text-[12px] font-bold text-ink tracking-widest mb-4 uppercase border-b-2 border-ink pb-2">/// Feature_Synthesis</h3>
|
| 239 |
<p className="text-[13px] text-slate leading-relaxed uppercase mb-6">
|
| 240 |
Missing sensor drops are repaired using a fitted <code className="bg-ink text-white px-1">SimpleImputer</code>. We inject a non-linear hint directly into the AST: <code className="bg-ink text-white px-1">Risk_Severity</code>. All floats are strictly normalized.
|
| 241 |
</p>
|
|
|
|
| 264 |
When a clinician uploads a new batch dataset, the API spawns a background thread. It retrains all 5 models, calculates the weighted F1-Score against a 20% holdout, and checks <code className="bg-ink text-white px-1">registry.json</code>. If the new score degrades, the batch is destroyed. If it improves, the active `.pkl` weights are hot-swapped globally without dropping WebSocket streams.
|
| 265 |
</p>
|
| 266 |
|
| 267 |
+
<div className="inline-flex flex-col md:flex-row border-2 border-ink bg-ink gap-0.5 relative z-10">
|
| 268 |
<div className="bg-canvas-cream text-ink px-6 py-4 font-bold text-[12px] flex items-center justify-center">
|
| 269 |
F1 SCORE > ACTIVE_V1
|
| 270 |
</div>
|
frontend/src/app/dashboard/page.tsx
CHANGED
|
@@ -23,7 +23,7 @@ export default function Dashboard() {
|
|
| 23 |
const { data, stream, status } = useTelemetry();
|
| 24 |
|
| 25 |
return (
|
| 26 |
-
<main className="
|
| 27 |
{/* Top Nav (Mechanical) */}
|
| 28 |
|
| 29 |
<motion.div
|
|
@@ -40,7 +40,7 @@ export default function Dashboard() {
|
|
| 40 |
transition={{ duration: 2, repeat: Infinity, ease: "linear" }}
|
| 41 |
className="w-2 h-2 rounded-full bg-hazard"
|
| 42 |
/>
|
| 43 |
-
/// TELEMETRY_STREAM_ACTIVE
|
| 44 |
</div>
|
| 45 |
<h1 className="text-[clamp(3rem,6vw,6rem)] leading-[0.9]">
|
| 46 |
PATIENT<br />TELEMETRY
|
|
|
|
| 23 |
const { data, stream, status } = useTelemetry();
|
| 24 |
|
| 25 |
return (
|
| 26 |
+
<main className="w-full flex-1 bg-canvas-cream flex flex-col font-mono selection:bg-hazard selection:text-white pb-16">
|
| 27 |
{/* Top Nav (Mechanical) */}
|
| 28 |
|
| 29 |
<motion.div
|
|
|
|
| 40 |
transition={{ duration: 2, repeat: Infinity, ease: "linear" }}
|
| 41 |
className="w-2 h-2 rounded-full bg-hazard"
|
| 42 |
/>
|
| 43 |
+
{"/// TELEMETRY_STREAM_ACTIVE"}
|
| 44 |
</div>
|
| 45 |
<h1 className="text-[clamp(3rem,6vw,6rem)] leading-[0.9]">
|
| 46 |
PATIENT<br />TELEMETRY
|
frontend/src/app/simulator/page.tsx
CHANGED
|
@@ -47,7 +47,7 @@ export default function Analysis() {
|
|
| 47 |
setLoading(true);
|
| 48 |
setPrediction(null);
|
| 49 |
setSubmittedData(null);
|
| 50 |
-
|
| 51 |
try {
|
| 52 |
const res = await fetch((process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000") + "/predict", {
|
| 53 |
method: "POST",
|
|
@@ -65,10 +65,10 @@ export default function Analysis() {
|
|
| 65 |
};
|
| 66 |
|
| 67 |
return (
|
| 68 |
-
<main className="
|
| 69 |
{/* Top Nav (Mechanical) */}
|
| 70 |
|
| 71 |
-
<motion.div
|
| 72 |
variants={staggerContainer}
|
| 73 |
initial="hidden"
|
| 74 |
animate="show"
|
|
@@ -77,7 +77,7 @@ export default function Analysis() {
|
|
| 77 |
<motion.header variants={fadeUp} className="mb-12 border-b-4 border-ink pb-8 flex justify-between items-end">
|
| 78 |
<div>
|
| 79 |
<div className="text-[12px] text-hazard font-bold tracking-widest mb-4">
|
| 80 |
-
/// EXPLAINABLE_AI_MODULE
|
| 81 |
</div>
|
| 82 |
<h1 className="text-[clamp(3rem,6vw,6rem)]">
|
| 83 |
MODEL<br />SIMULATOR
|
|
@@ -91,21 +91,21 @@ export default function Analysis() {
|
|
| 91 |
</motion.header>
|
| 92 |
|
| 93 |
<motion.div variants={staggerContainer} className="grid grid-cols-1 lg:grid-cols-12 gap-px bg-ink border-2 border-ink">
|
| 94 |
-
|
| 95 |
{/* Input Panel */}
|
| 96 |
<motion.div variants={fadeUp} className="lg:col-span-5 bg-canvas-cream flex flex-col">
|
| 97 |
<div className="border-b-2 border-ink p-4 bg-white flex justify-between items-center">
|
| 98 |
<h2 className="text-[14px] font-bold tracking-wider">{'< MANUAL_INPUT_MATRIX >'}</h2>
|
| 99 |
</div>
|
| 100 |
-
|
| 101 |
<div className="p-8 flex flex-col gap-8 grow">
|
| 102 |
-
<InputControl label="HEART_RATE (BPM)" value={formData.Heart_Rate} min={40} max={200} onChange={(v: number) => setFormData({...formData, Heart_Rate: v})} />
|
| 103 |
-
<InputControl label="SPO2_LEVEL (%)" value={formData.SpO2_Level} min={70} max={100} onChange={(v: number) => setFormData({...formData, SpO2_Level: v})} />
|
| 104 |
-
<InputControl label="SYSTOLIC_BP (MMHG)" value={formData.Systolic_BP} min={80} max={220} onChange={(v: number) => setFormData({...formData, Systolic_BP: v})} />
|
| 105 |
-
<InputControl label="DIASTOLIC_BP (MMHG)" value={formData.Diastolic_BP} min={40} max={140} onChange={(v: number) => setFormData({...formData, Diastolic_BP: v})} />
|
| 106 |
-
<InputControl label="BODY_TEMP (°C)" value={formData.Body_Temp} min={30} max={42} step={0.1} onChange={(v: number) => setFormData({...formData, Body_Temp: v})} />
|
| 107 |
-
|
| 108 |
-
<motion.button
|
| 109 |
onClick={handlePredict}
|
| 110 |
disabled={loading}
|
| 111 |
whileHover={{ y: 2, x: 2, boxShadow: "4px 4px 0px 0px rgba(0,0,0,1)" }}
|
|
@@ -117,14 +117,15 @@ export default function Analysis() {
|
|
| 117 |
</motion.button>
|
| 118 |
|
| 119 |
{/* Terminal Readout */}
|
| 120 |
-
|
|
|
|
| 121 |
<div className="text-[10px] font-bold tracking-[0.2em] text-ink bg-white border-b-[3px] border-ink px-4 py-3 flex justify-between uppercase">
|
| 122 |
<span>RAW_PAYLOAD_PREVIEW</span>
|
| 123 |
-
<span>{JSON.stringify(formData).length} BYTES // SYS_READY</span>
|
| 124 |
</div>
|
| 125 |
<div className="p-6 grow flex flex-col relative z-0">
|
| 126 |
<pre className="text-[12px] font-mono whitespace-pre-wrap grow overflow-auto leading-relaxed">
|
| 127 |
-
{`{
|
| 128 |
"Heart_Rate": ${formData.Heart_Rate},
|
| 129 |
"SpO2_Level": ${formData.SpO2_Level},
|
| 130 |
"Systolic_BP": ${formData.Systolic_BP},
|
|
@@ -135,6 +136,7 @@ export default function Analysis() {
|
|
| 135 |
</pre>
|
| 136 |
</div>
|
| 137 |
</div>
|
|
|
|
| 138 |
</motion.div>
|
| 139 |
|
| 140 |
{/* Results Panel */}
|
|
@@ -142,20 +144,20 @@ export default function Analysis() {
|
|
| 142 |
<div className="border-b-2 border-ink p-4 bg-white flex justify-between items-center">
|
| 143 |
<h2 className="text-[14px] font-bold tracking-wider">{'< INFERENCE_RESULTS_&_XAI >'}</h2>
|
| 144 |
</div>
|
| 145 |
-
|
| 146 |
<div className="grow flex flex-col relative bg-white">
|
| 147 |
<div className="absolute inset-0 z-0 opacity-10 pointer-events-none" style={{ backgroundImage: 'linear-gradient(var(--ink-black) 1px, transparent 1px), linear-gradient(90deg, var(--ink-black) 1px, transparent 1px)', backgroundSize: '40px 40px' }}></div>
|
| 148 |
-
|
| 149 |
<div className="z-10 p-8 flex flex-col h-full">
|
| 150 |
{prediction ? (
|
| 151 |
-
<motion.div
|
| 152 |
initial={{ opacity: 0, scale: 0.95 }}
|
| 153 |
animate={{ opacity: 1, scale: 1 }}
|
| 154 |
transition={{ duration: 0.6, ease: easeFluid }}
|
| 155 |
>
|
| 156 |
<div className="mb-12">
|
| 157 |
<div className="text-[11px] font-bold text-slate tracking-widest mb-4">MODEL_OUTPUT</div>
|
| 158 |
-
|
| 159 |
{(() => {
|
| 160 |
const getStatusStyles = (label: string) => {
|
| 161 |
const normalized = (label || "").toLowerCase();
|
|
@@ -164,9 +166,9 @@ export default function Analysis() {
|
|
| 164 |
if (normalized.includes('asthma')) return "bg-[#e0f2fe] text-[#075985] border-[#075985]";
|
| 165 |
if (normalized.includes('hypertension')) return "bg-[#f3e8ff] text-[#6b21a8] border-[#6b21a8]";
|
| 166 |
if (normalized.includes('diabetes')) return "bg-[#ffedd5] text-[#9a3412] border-[#9a3412]";
|
| 167 |
-
return "bg-hazard text-white border-hazard";
|
| 168 |
};
|
| 169 |
-
|
| 170 |
return (
|
| 171 |
<div className={`border-2 p-6 flex flex-col mb-4 ${getStatusStyles(prediction.prediction ? prediction.prediction_label : 'Healthy')}`}>
|
| 172 |
<div className="font-sans text-[clamp(28px,3vw,56px)] font-black tracking-tighter leading-none mb-2 uppercase wrap-break-words">
|
|
@@ -188,7 +190,7 @@ export default function Analysis() {
|
|
| 188 |
</div>
|
| 189 |
<div className="divide-y-2 divide-ink">
|
| 190 |
{Object.keys(prediction.model_outputs).map((modelName, index) => (
|
| 191 |
-
<motion.div
|
| 192 |
key={modelName}
|
| 193 |
initial={{ opacity: 0, x: 20 }}
|
| 194 |
animate={{ opacity: 1, x: 0 }}
|
|
@@ -229,7 +231,7 @@ export default function Analysis() {
|
|
| 229 |
</div>
|
| 230 |
</div>
|
| 231 |
</motion.div>
|
| 232 |
-
|
| 233 |
</motion.div>
|
| 234 |
</motion.div>
|
| 235 |
</main>
|
|
@@ -241,7 +243,7 @@ function InputControl({ label, value, min, max, step = 1, onChange }: any) {
|
|
| 241 |
<div className="flex flex-col gap-3 group">
|
| 242 |
<div className="flex justify-between items-end">
|
| 243 |
<label className="text-[13px] font-bold text-slate tracking-widest group-hover:text-ink transition-colors">{label}</label>
|
| 244 |
-
<motion.span
|
| 245 |
key={value}
|
| 246 |
initial={{ opacity: 0.5, y: -5 }}
|
| 247 |
animate={{ opacity: 1, y: 0 }}
|
|
@@ -251,12 +253,12 @@ function InputControl({ label, value, min, max, step = 1, onChange }: any) {
|
|
| 251 |
</motion.span>
|
| 252 |
</div>
|
| 253 |
<div className="relative border-2 border-ink p-1 bg-white">
|
| 254 |
-
<input
|
| 255 |
-
type="range"
|
| 256 |
-
min={min}
|
| 257 |
-
max={max}
|
| 258 |
step={step}
|
| 259 |
-
value={value}
|
| 260 |
onChange={(e) => onChange(Number(e.target.value))}
|
| 261 |
className="w-full appearance-none h-4 bg-ink/10 outline-none [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-6 [&::-webkit-slider-thumb]:h-8 [&::-webkit-slider-thumb]:bg-ink [&::-webkit-slider-thumb]:cursor-pointer relative z-10 block"
|
| 262 |
/>
|
|
|
|
| 47 |
setLoading(true);
|
| 48 |
setPrediction(null);
|
| 49 |
setSubmittedData(null);
|
| 50 |
+
|
| 51 |
try {
|
| 52 |
const res = await fetch((process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000") + "/predict", {
|
| 53 |
method: "POST",
|
|
|
|
| 65 |
};
|
| 66 |
|
| 67 |
return (
|
| 68 |
+
<main className="w-full flex-1 bg-canvas-cream flex flex-col font-mono selection:bg-hazard selection:text-white pb-16">
|
| 69 |
{/* Top Nav (Mechanical) */}
|
| 70 |
|
| 71 |
+
<motion.div
|
| 72 |
variants={staggerContainer}
|
| 73 |
initial="hidden"
|
| 74 |
animate="show"
|
|
|
|
| 77 |
<motion.header variants={fadeUp} className="mb-12 border-b-4 border-ink pb-8 flex justify-between items-end">
|
| 78 |
<div>
|
| 79 |
<div className="text-[12px] text-hazard font-bold tracking-widest mb-4">
|
| 80 |
+
{"/// EXPLAINABLE_AI_MODULE"}
|
| 81 |
</div>
|
| 82 |
<h1 className="text-[clamp(3rem,6vw,6rem)]">
|
| 83 |
MODEL<br />SIMULATOR
|
|
|
|
| 91 |
</motion.header>
|
| 92 |
|
| 93 |
<motion.div variants={staggerContainer} className="grid grid-cols-1 lg:grid-cols-12 gap-px bg-ink border-2 border-ink">
|
| 94 |
+
|
| 95 |
{/* Input Panel */}
|
| 96 |
<motion.div variants={fadeUp} className="lg:col-span-5 bg-canvas-cream flex flex-col">
|
| 97 |
<div className="border-b-2 border-ink p-4 bg-white flex justify-between items-center">
|
| 98 |
<h2 className="text-[14px] font-bold tracking-wider">{'< MANUAL_INPUT_MATRIX >'}</h2>
|
| 99 |
</div>
|
| 100 |
+
|
| 101 |
<div className="p-8 flex flex-col gap-8 grow">
|
| 102 |
+
<InputControl label="HEART_RATE (BPM)" value={formData.Heart_Rate} min={40} max={200} onChange={(v: number) => setFormData({ ...formData, Heart_Rate: v })} />
|
| 103 |
+
<InputControl label="SPO2_LEVEL (%)" value={formData.SpO2_Level} min={70} max={100} onChange={(v: number) => setFormData({ ...formData, SpO2_Level: v })} />
|
| 104 |
+
<InputControl label="SYSTOLIC_BP (MMHG)" value={formData.Systolic_BP} min={80} max={220} onChange={(v: number) => setFormData({ ...formData, Systolic_BP: v })} />
|
| 105 |
+
<InputControl label="DIASTOLIC_BP (MMHG)" value={formData.Diastolic_BP} min={40} max={140} onChange={(v: number) => setFormData({ ...formData, Diastolic_BP: v })} />
|
| 106 |
+
<InputControl label="BODY_TEMP (°C)" value={formData.Body_Temp} min={30} max={42} step={0.1} onChange={(v: number) => setFormData({ ...formData, Body_Temp: v })} />
|
| 107 |
+
|
| 108 |
+
<motion.button
|
| 109 |
onClick={handlePredict}
|
| 110 |
disabled={loading}
|
| 111 |
whileHover={{ y: 2, x: 2, boxShadow: "4px 4px 0px 0px rgba(0,0,0,1)" }}
|
|
|
|
| 117 |
</motion.button>
|
| 118 |
|
| 119 |
{/* Terminal Readout */}
|
| 120 |
+
<div className="mt-8 grow border-[3px] border-ink bg-ink text-[#4AF626] p-0 flex flex-col min-h-37.5 shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] relative overflow-hidden group">
|
| 121 |
+
<div className="absolute inset-0 pointer-events-none opacity-10 bg-[repeating-linear-gradient(0deg,transparent,transparent_2px,rgba(255,255,255,0.1)_2px,rgba(255,255,255,0.1)_4px)] z-10" />
|
| 122 |
<div className="text-[10px] font-bold tracking-[0.2em] text-ink bg-white border-b-[3px] border-ink px-4 py-3 flex justify-between uppercase">
|
| 123 |
<span>RAW_PAYLOAD_PREVIEW</span>
|
| 124 |
+
<span>{JSON.stringify(formData).length} BYTES {"// SYS_READY"}</span>
|
| 125 |
</div>
|
| 126 |
<div className="p-6 grow flex flex-col relative z-0">
|
| 127 |
<pre className="text-[12px] font-mono whitespace-pre-wrap grow overflow-auto leading-relaxed">
|
| 128 |
+
{`{
|
| 129 |
"Heart_Rate": ${formData.Heart_Rate},
|
| 130 |
"SpO2_Level": ${formData.SpO2_Level},
|
| 131 |
"Systolic_BP": ${formData.Systolic_BP},
|
|
|
|
| 136 |
</pre>
|
| 137 |
</div>
|
| 138 |
</div>
|
| 139 |
+
</div>
|
| 140 |
</motion.div>
|
| 141 |
|
| 142 |
{/* Results Panel */}
|
|
|
|
| 144 |
<div className="border-b-2 border-ink p-4 bg-white flex justify-between items-center">
|
| 145 |
<h2 className="text-[14px] font-bold tracking-wider">{'< INFERENCE_RESULTS_&_XAI >'}</h2>
|
| 146 |
</div>
|
| 147 |
+
|
| 148 |
<div className="grow flex flex-col relative bg-white">
|
| 149 |
<div className="absolute inset-0 z-0 opacity-10 pointer-events-none" style={{ backgroundImage: 'linear-gradient(var(--ink-black) 1px, transparent 1px), linear-gradient(90deg, var(--ink-black) 1px, transparent 1px)', backgroundSize: '40px 40px' }}></div>
|
| 150 |
+
|
| 151 |
<div className="z-10 p-8 flex flex-col h-full">
|
| 152 |
{prediction ? (
|
| 153 |
+
<motion.div
|
| 154 |
initial={{ opacity: 0, scale: 0.95 }}
|
| 155 |
animate={{ opacity: 1, scale: 1 }}
|
| 156 |
transition={{ duration: 0.6, ease: easeFluid }}
|
| 157 |
>
|
| 158 |
<div className="mb-12">
|
| 159 |
<div className="text-[11px] font-bold text-slate tracking-widest mb-4">MODEL_OUTPUT</div>
|
| 160 |
+
|
| 161 |
{(() => {
|
| 162 |
const getStatusStyles = (label: string) => {
|
| 163 |
const normalized = (label || "").toLowerCase();
|
|
|
|
| 166 |
if (normalized.includes('asthma')) return "bg-[#e0f2fe] text-[#075985] border-[#075985]";
|
| 167 |
if (normalized.includes('hypertension')) return "bg-[#f3e8ff] text-[#6b21a8] border-[#6b21a8]";
|
| 168 |
if (normalized.includes('diabetes')) return "bg-[#ffedd5] text-[#9a3412] border-[#9a3412]";
|
| 169 |
+
return "bg-hazard text-white border-hazard";
|
| 170 |
};
|
| 171 |
+
|
| 172 |
return (
|
| 173 |
<div className={`border-2 p-6 flex flex-col mb-4 ${getStatusStyles(prediction.prediction ? prediction.prediction_label : 'Healthy')}`}>
|
| 174 |
<div className="font-sans text-[clamp(28px,3vw,56px)] font-black tracking-tighter leading-none mb-2 uppercase wrap-break-words">
|
|
|
|
| 190 |
</div>
|
| 191 |
<div className="divide-y-2 divide-ink">
|
| 192 |
{Object.keys(prediction.model_outputs).map((modelName, index) => (
|
| 193 |
+
<motion.div
|
| 194 |
key={modelName}
|
| 195 |
initial={{ opacity: 0, x: 20 }}
|
| 196 |
animate={{ opacity: 1, x: 0 }}
|
|
|
|
| 231 |
</div>
|
| 232 |
</div>
|
| 233 |
</motion.div>
|
| 234 |
+
|
| 235 |
</motion.div>
|
| 236 |
</motion.div>
|
| 237 |
</main>
|
|
|
|
| 243 |
<div className="flex flex-col gap-3 group">
|
| 244 |
<div className="flex justify-between items-end">
|
| 245 |
<label className="text-[13px] font-bold text-slate tracking-widest group-hover:text-ink transition-colors">{label}</label>
|
| 246 |
+
<motion.span
|
| 247 |
key={value}
|
| 248 |
initial={{ opacity: 0.5, y: -5 }}
|
| 249 |
animate={{ opacity: 1, y: 0 }}
|
|
|
|
| 253 |
</motion.span>
|
| 254 |
</div>
|
| 255 |
<div className="relative border-2 border-ink p-1 bg-white">
|
| 256 |
+
<input
|
| 257 |
+
type="range"
|
| 258 |
+
min={min}
|
| 259 |
+
max={max}
|
| 260 |
step={step}
|
| 261 |
+
value={value}
|
| 262 |
onChange={(e) => onChange(Number(e.target.value))}
|
| 263 |
className="w-full appearance-none h-4 bg-ink/10 outline-none [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-6 [&::-webkit-slider-thumb]:h-8 [&::-webkit-slider-thumb]:bg-ink [&::-webkit-slider-thumb]:cursor-pointer relative z-10 block"
|
| 264 |
/>
|