Spaces:
Runtime error
Runtime error
Upload folder using huggingface_hub
Browse files- frontend/src/app/dashboard/page.tsx +22 -22
- frontend/src/app/favicon.ico +0 -0
- frontend/src/app/icon.svg +8 -0
- frontend/src/app/layout.tsx +10 -10
- frontend/src/app/mlops/page.tsx +20 -20
- frontend/src/app/page.tsx +17 -17
- frontend/src/app/simulator/page.tsx +19 -21
frontend/src/app/dashboard/page.tsx
CHANGED
|
@@ -23,18 +23,18 @@ export default function Dashboard() {
|
|
| 23 |
const { data, stream, status } = useTelemetry();
|
| 24 |
|
| 25 |
return (
|
| 26 |
-
<main className="
|
| 27 |
{/* Top Nav (Mechanical) */}
|
| 28 |
|
| 29 |
<motion.div
|
| 30 |
variants={staggerContainer}
|
| 31 |
initial="hidden"
|
| 32 |
animate="show"
|
| 33 |
-
className="max-w-
|
| 34 |
>
|
| 35 |
<motion.header variants={fadeUp} className="mb-12 border-b-4 border-ink pb-8 flex justify-between items-end">
|
| 36 |
<div>
|
| 37 |
-
<div className="text-[12px] text-hazard font-bold tracking-
|
| 38 |
<motion.div
|
| 39 |
animate={{ opacity: [1, 0.2, 1] }}
|
| 40 |
transition={{ duration: 2, repeat: Infinity, ease: "linear" }}
|
|
@@ -47,7 +47,7 @@ export default function Dashboard() {
|
|
| 47 |
</h1>
|
| 48 |
</div>
|
| 49 |
<div className="text-right hidden md:block">
|
| 50 |
-
<div className={`text-[14px] font-bold uppercase tracking-
|
| 51 |
STATUS: {status === 'reconnecting' ? "RECONNECTING..." : (data ? "CONNECTED" : "AWAITING SIGNAL")}
|
| 52 |
</div>
|
| 53 |
</div>
|
|
@@ -59,7 +59,7 @@ export default function Dashboard() {
|
|
| 59 |
{/* Main Status Panel */}
|
| 60 |
<motion.div variants={fadeUp} className="bg-canvas-cream flex flex-col">
|
| 61 |
<div className="border-b-2 border-ink p-4 flex justify-between items-center bg-white">
|
| 62 |
-
<h2 className="text-[14px] font-bold tracking-
|
| 63 |
{data?.prediction ? (
|
| 64 |
(() => {
|
| 65 |
const getStatusStyles = (label: string) => {
|
|
@@ -77,14 +77,14 @@ export default function Dashboard() {
|
|
| 77 |
initial={{ scale: 0.95, opacity: 0 }}
|
| 78 |
animate={{ scale: 1, opacity: 1 }}
|
| 79 |
transition={{ type: "spring", stiffness: 300, damping: 20 }}
|
| 80 |
-
className={`px-4 py-2 text-[14px] font-bold uppercase tracking-
|
| 81 |
>
|
| 82 |
{data.prediction.is_at_risk ? '[!]' : '[OK]'} {data.prediction.label} ({(data.prediction.confidence * 100).toFixed(1)}%)
|
| 83 |
</motion.div>
|
| 84 |
);
|
| 85 |
})()
|
| 86 |
) : (
|
| 87 |
-
<div className="px-4 py-2 text-[14px] font-bold tracking-
|
| 88 |
INITIALIZING...
|
| 89 |
</div>
|
| 90 |
)}
|
|
@@ -98,18 +98,18 @@ export default function Dashboard() {
|
|
| 98 |
</motion.div>
|
| 99 |
|
| 100 |
{/* 2-Column Mini Graphs */}
|
| 101 |
-
<motion.div variants={fadeUp} className="grid grid-cols-1 lg:grid-cols-2 gap-px bg-ink border-b-2 border-ink h-
|
| 102 |
-
<div className="bg-white p-3 flex flex-col group"><div className="text-[11px] font-bold text-slate tracking-
|
| 103 |
-
<div className="bg-white p-3 flex flex-col group"><div className="text-[11px] font-bold text-slate tracking-
|
| 104 |
</motion.div>
|
| 105 |
|
| 106 |
{/* Diagnostic Probabilities */}
|
| 107 |
-
<motion.div variants={fadeUp} className="bg-white flex flex-col min-h-
|
| 108 |
<div className="border-b-2 border-ink p-3 px-6 flex justify-between items-center bg-canvas-cream">
|
| 109 |
-
<h2 className="text-[12px] font-bold tracking-
|
| 110 |
-
<div className="text-[11px] font-bold tracking-
|
| 111 |
</div>
|
| 112 |
-
<div className="
|
| 113 |
{data?.prediction?.disease_probs ? (
|
| 114 |
Object.entries(data.prediction.disease_probs)
|
| 115 |
.sort((a, b) => (b[1] as number) - (a[1] as number))
|
|
@@ -124,7 +124,7 @@ export default function Dashboard() {
|
|
| 124 |
transition={{ duration: 0.5, delay: i * 0.1, ease: easeFluid }}
|
| 125 |
className="flex flex-col gap-2"
|
| 126 |
>
|
| 127 |
-
<div className="flex justify-between text-[12px] font-bold uppercase tracking-
|
| 128 |
<span className={isRisk ? "text-hazard drop-shadow-[0_0_2px_rgba(230,25,25,0.5)]" : "text-ink"}>{disease}</span>
|
| 129 |
<span className="tabular-nums">{pct}%</span>
|
| 130 |
</div>
|
|
@@ -142,7 +142,7 @@ export default function Dashboard() {
|
|
| 142 |
)
|
| 143 |
})
|
| 144 |
) : (
|
| 145 |
-
<div className="h-full flex items-center justify-center text-[12px] font-bold text-slate tracking-
|
| 146 |
AWAITING CLASSIFICATION DATA...
|
| 147 |
</div>
|
| 148 |
)}
|
|
@@ -159,7 +159,7 @@ export default function Dashboard() {
|
|
| 159 |
function VitalCard({ label, value, unit }: { label: string, value: string | number | null | undefined, unit: string }) {
|
| 160 |
return (
|
| 161 |
<motion.div variants={fadeUp} className="bg-canvas-cream p-8 flex flex-col hover:bg-white transition-colors cursor-default overflow-hidden group">
|
| 162 |
-
<div className="text-[11px] font-bold text-slate tracking-
|
| 163 |
{label}
|
| 164 |
</div>
|
| 165 |
<div>
|
|
@@ -171,7 +171,7 @@ function VitalCard({ label, value, unit }: { label: string, value: string | numb
|
|
| 171 |
>
|
| 172 |
{value !== null && value !== undefined ? value : "---"}
|
| 173 |
</motion.div>
|
| 174 |
-
<div className="text-[12px] font-bold tracking-
|
| 175 |
[{unit}]
|
| 176 |
</div>
|
| 177 |
</div>
|
|
@@ -195,9 +195,9 @@ function TelemetryGraph({ stream, min, max, color, fill }: { stream: number[], m
|
|
| 195 |
const lastY = lastPoint ? lastPoint.split(',')[1] : 100;
|
| 196 |
|
| 197 |
return (
|
| 198 |
-
<div className="w-full h-full
|
| 199 |
{/* Blueprint Grid lines */}
|
| 200 |
-
<div className="
|
| 201 |
|
| 202 |
{/* Signal trace */}
|
| 203 |
<div className="absolute inset-0 z-10 overflow-hidden">
|
|
@@ -232,7 +232,7 @@ function TelemetryGraph({ stream, min, max, color, fill }: { stream: number[], m
|
|
| 232 |
|
| 233 |
{/* Sweeping Radar Line */}
|
| 234 |
<motion.div
|
| 235 |
-
className="
|
| 236 |
style={{ backgroundColor: color, boxShadow: `0 0 12px 2px ${color}` }}
|
| 237 |
initial={{ left: "0%" }}
|
| 238 |
animate={{ left: "100%" }}
|
|
@@ -240,7 +240,7 @@ function TelemetryGraph({ stream, min, max, color, fill }: { stream: number[], m
|
|
| 240 |
/>
|
| 241 |
|
| 242 |
{/* Terminal Scanline Overlay */}
|
| 243 |
-
<div className="
|
| 244 |
</div>
|
| 245 |
);
|
| 246 |
}
|
|
|
|
| 23 |
const { data, stream, status } = useTelemetry();
|
| 24 |
|
| 25 |
return (
|
| 26 |
+
<main className="max-h-dvh bg-canvas-cream flex flex-col font-mono selection:bg-hazard selection:text-white pb-16">
|
| 27 |
{/* Top Nav (Mechanical) */}
|
| 28 |
|
| 29 |
<motion.div
|
| 30 |
variants={staggerContainer}
|
| 31 |
initial="hidden"
|
| 32 |
animate="show"
|
| 33 |
+
className="max-w-350 mx-auto w-full px-8 py-16 flex-1 flex flex-col"
|
| 34 |
>
|
| 35 |
<motion.header variants={fadeUp} className="mb-12 border-b-4 border-ink pb-8 flex justify-between items-end">
|
| 36 |
<div>
|
| 37 |
+
<div className="text-[12px] text-hazard font-bold tracking-widest mb-4 flex items-center gap-2">
|
| 38 |
<motion.div
|
| 39 |
animate={{ opacity: [1, 0.2, 1] }}
|
| 40 |
transition={{ duration: 2, repeat: Infinity, ease: "linear" }}
|
|
|
|
| 47 |
</h1>
|
| 48 |
</div>
|
| 49 |
<div className="text-right hidden md:block">
|
| 50 |
+
<div className={`text-[14px] font-bold uppercase tracking-wider border-2 border-ink px-4 py-2 text-canvas-cream ${status === 'reconnecting' ? 'bg-hazard animate-pulse' : 'bg-ink'}`}>
|
| 51 |
STATUS: {status === 'reconnecting' ? "RECONNECTING..." : (data ? "CONNECTED" : "AWAITING SIGNAL")}
|
| 52 |
</div>
|
| 53 |
</div>
|
|
|
|
| 59 |
{/* Main Status Panel */}
|
| 60 |
<motion.div variants={fadeUp} className="bg-canvas-cream flex flex-col">
|
| 61 |
<div className="border-b-2 border-ink p-4 flex justify-between items-center bg-white">
|
| 62 |
+
<h2 className="text-[14px] font-bold tracking-wider">{'< LIVE_VITALS >'}</h2>
|
| 63 |
{data?.prediction ? (
|
| 64 |
(() => {
|
| 65 |
const getStatusStyles = (label: string) => {
|
|
|
|
| 77 |
initial={{ scale: 0.95, opacity: 0 }}
|
| 78 |
animate={{ scale: 1, opacity: 1 }}
|
| 79 |
transition={{ type: "spring", stiffness: 300, damping: 20 }}
|
| 80 |
+
className={`px-4 py-2 text-[14px] font-bold uppercase tracking-wider border-2 ${getStatusStyles(data.prediction.label)}`}
|
| 81 |
>
|
| 82 |
{data.prediction.is_at_risk ? '[!]' : '[OK]'} {data.prediction.label} ({(data.prediction.confidence * 100).toFixed(1)}%)
|
| 83 |
</motion.div>
|
| 84 |
);
|
| 85 |
})()
|
| 86 |
) : (
|
| 87 |
+
<div className="px-4 py-2 text-[14px] font-bold tracking-wider border-2 border-ink text-slate animate-pulse">
|
| 88 |
INITIALIZING...
|
| 89 |
</div>
|
| 90 |
)}
|
|
|
|
| 98 |
</motion.div>
|
| 99 |
|
| 100 |
{/* 2-Column Mini Graphs */}
|
| 101 |
+
<motion.div variants={fadeUp} className="grid grid-cols-1 lg:grid-cols-2 gap-px bg-ink border-b-2 border-ink h-40">
|
| 102 |
+
<div className="bg-white p-3 flex flex-col group"><div className="text-[11px] font-bold text-slate tracking-widest mb-2 group-hover:text-hazard transition-colors">HR_STREAM</div><div className="flex-1 min-h-0 relative"><TelemetryGraph stream={stream.hr} min={60} max={150} color="var(--color-ink)" fill="rgba(5,5,5,0.05)" /></div></div>
|
| 103 |
+
<div className="bg-white p-3 flex flex-col group"><div className="text-[11px] font-bold text-slate tracking-widest mb-2 group-hover:text-hazard transition-colors">TEMP_STREAM</div><div className="flex-1 min-h-0 relative"><TelemetryGraph stream={stream.temp} min={36.0} max={39.0} color="var(--color-ink)" fill="rgba(5,5,5,0.05)" /></div></div>
|
| 104 |
</motion.div>
|
| 105 |
|
| 106 |
{/* Diagnostic Probabilities */}
|
| 107 |
+
<motion.div variants={fadeUp} className="bg-white flex flex-col min-h-70">
|
| 108 |
<div className="border-b-2 border-ink p-3 px-6 flex justify-between items-center bg-canvas-cream">
|
| 109 |
+
<h2 className="text-[12px] font-bold tracking-widest uppercase">{'< DIAGNOSTIC_PROBABILITIES >'}</h2>
|
| 110 |
+
<div className="text-[11px] font-bold tracking-wider text-hazard uppercase">CLASS / LIKELIHOOD</div>
|
| 111 |
</div>
|
| 112 |
+
<div className="grow p-6 bg-white flex flex-col justify-center gap-6">
|
| 113 |
{data?.prediction?.disease_probs ? (
|
| 114 |
Object.entries(data.prediction.disease_probs)
|
| 115 |
.sort((a, b) => (b[1] as number) - (a[1] as number))
|
|
|
|
| 124 |
transition={{ duration: 0.5, delay: i * 0.1, ease: easeFluid }}
|
| 125 |
className="flex flex-col gap-2"
|
| 126 |
>
|
| 127 |
+
<div className="flex justify-between text-[12px] font-bold uppercase tracking-widest">
|
| 128 |
<span className={isRisk ? "text-hazard drop-shadow-[0_0_2px_rgba(230,25,25,0.5)]" : "text-ink"}>{disease}</span>
|
| 129 |
<span className="tabular-nums">{pct}%</span>
|
| 130 |
</div>
|
|
|
|
| 142 |
)
|
| 143 |
})
|
| 144 |
) : (
|
| 145 |
+
<div className="h-full flex items-center justify-center text-[12px] font-bold text-slate tracking-widest animate-pulse">
|
| 146 |
AWAITING CLASSIFICATION DATA...
|
| 147 |
</div>
|
| 148 |
)}
|
|
|
|
| 159 |
function VitalCard({ label, value, unit }: { label: string, value: string | number | null | undefined, unit: string }) {
|
| 160 |
return (
|
| 161 |
<motion.div variants={fadeUp} className="bg-canvas-cream p-8 flex flex-col hover:bg-white transition-colors cursor-default overflow-hidden group">
|
| 162 |
+
<div className="text-[11px] font-bold text-slate tracking-widest border-b border-ink/20 pb-2 mb-4 group-hover:text-hazard transition-colors">
|
| 163 |
{label}
|
| 164 |
</div>
|
| 165 |
<div>
|
|
|
|
| 171 |
>
|
| 172 |
{value !== null && value !== undefined ? value : "---"}
|
| 173 |
</motion.div>
|
| 174 |
+
<div className="text-[12px] font-bold tracking-wider text-hazard">
|
| 175 |
[{unit}]
|
| 176 |
</div>
|
| 177 |
</div>
|
|
|
|
| 195 |
const lastY = lastPoint ? lastPoint.split(',')[1] : 100;
|
| 196 |
|
| 197 |
return (
|
| 198 |
+
<div className="w-full h-full relative inset-0 border-2 border-ink overflow-hidden bg-white group ">
|
| 199 |
{/* Blueprint Grid lines */}
|
| 200 |
+
<div className="relative inset-0 z-0 opacity-[0.1]" style={{ backgroundImage: 'linear-gradient(var(--ink-black) 1px, transparent 1px), linear-gradient(90deg, var(--ink-black) 1px, transparent 1px)', backgroundSize: '20px 20px' }}></div>
|
| 201 |
|
| 202 |
{/* Signal trace */}
|
| 203 |
<div className="absolute inset-0 z-10 overflow-hidden">
|
|
|
|
| 232 |
|
| 233 |
{/* Sweeping Radar Line */}
|
| 234 |
<motion.div
|
| 235 |
+
className="relative top-0 bottom-0 w-0.5 z-20 opacity-40 pointer-events-none"
|
| 236 |
style={{ backgroundColor: color, boxShadow: `0 0 12px 2px ${color}` }}
|
| 237 |
initial={{ left: "0%" }}
|
| 238 |
animate={{ left: "100%" }}
|
|
|
|
| 240 |
/>
|
| 241 |
|
| 242 |
{/* Terminal Scanline Overlay */}
|
| 243 |
+
<div className="relative inset-0 pointer-events-none z-30 opacity-40 bg-[linear-gradient(transparent_50%,rgba(0,0,0,0.2)_50%)] bg-size-[100%_4px]"></div>
|
| 244 |
</div>
|
| 245 |
);
|
| 246 |
}
|
frontend/src/app/favicon.ico
DELETED
|
Binary file (25.9 kB)
|
|
|
frontend/src/app/icon.svg
ADDED
|
|
frontend/src/app/layout.tsx
CHANGED
|
@@ -16,7 +16,7 @@ const geistMono = Geist_Mono({
|
|
| 16 |
import Link from "next/link";
|
| 17 |
|
| 18 |
export const metadata: Metadata = {
|
| 19 |
-
title: "
|
| 20 |
description: "Cardiovascular monitoring dashboard",
|
| 21 |
};
|
| 22 |
|
|
@@ -30,11 +30,11 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|
| 30 |
<TelemetryProvider>
|
| 31 |
{/* Universal Nav */}
|
| 32 |
<nav className="flex items-center px-4 md:px-8 py-4 border-b-2 border-ink bg-canvas-cream sticky top-0 z-50 overflow-x-auto">
|
| 33 |
-
<div className="max-w-
|
| 34 |
<Link href="/" className="font-sans text-[18px] font-black tracking-[-0.04em] uppercase hover:text-hazard transition-colors">
|
| 35 |
[ HEARTFLOW_OS ]
|
| 36 |
</Link>
|
| 37 |
-
<div className="flex gap-4 md:gap-8 items-center text-[12px] md:text-[13px] font-bold tracking-
|
| 38 |
<Link href="/dashboard" className="text-ink hover:text-hazard transition-colors">SYS.MONITOR</Link>
|
| 39 |
<Link href="/simulator" className="text-ink hover:text-hazard transition-colors">AI.SIMULATOR</Link>
|
| 40 |
<Link href="/history" className="text-ink hover:text-hazard transition-colors">DATA.LOG</Link>
|
|
@@ -43,22 +43,22 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|
| 43 |
</div>
|
| 44 |
</div>
|
| 45 |
</nav>
|
| 46 |
-
|
| 47 |
<div className="flex-1 flex flex-col">
|
| 48 |
{children}
|
| 49 |
</div>
|
| 50 |
</TelemetryProvider>
|
| 51 |
-
|
| 52 |
{/* Universal Footer */}
|
| 53 |
<footer className="w-full border-t-2 border-ink bg-white py-6 px-8 mt-auto z-50">
|
| 54 |
-
<div className="max-w-
|
| 55 |
-
<div className="text-[12px] font-bold tracking-
|
| 56 |
© 2026 HEARTFLOW_OS. TELEMETRY_SYS.
|
| 57 |
</div>
|
| 58 |
<div className="flex flex-wrap items-center justify-center gap-4 md:gap-8">
|
| 59 |
-
<Link href="/architecture" className="text-[12px] font-bold tracking-
|
| 60 |
-
<a href={`${process.env.NEXT_PUBLIC_API_URL || "https://idkhowtocodefr-tinyml-backend.hf.space"}/docs`} target="_blank" rel="noopener noreferrer" className="text-[12px] font-bold tracking-
|
| 61 |
-
<a href="https://github.com/IDKHowToCodeFR/HEARTFLOW_OS" target="_blank" rel="noopener noreferrer" className="text-[12px] font-bold tracking-
|
| 62 |
</div>
|
| 63 |
</div>
|
| 64 |
</footer>
|
|
|
|
| 16 |
import Link from "next/link";
|
| 17 |
|
| 18 |
export const metadata: Metadata = {
|
| 19 |
+
title: "HEARTFLOW_OS",
|
| 20 |
description: "Cardiovascular monitoring dashboard",
|
| 21 |
};
|
| 22 |
|
|
|
|
| 30 |
<TelemetryProvider>
|
| 31 |
{/* Universal Nav */}
|
| 32 |
<nav className="flex items-center px-4 md:px-8 py-4 border-b-2 border-ink bg-canvas-cream sticky top-0 z-50 overflow-x-auto">
|
| 33 |
+
<div className="max-w-350 mx-auto w-full flex flex-col md:flex-row justify-between items-center gap-4 md:gap-0">
|
| 34 |
<Link href="/" className="font-sans text-[18px] font-black tracking-[-0.04em] uppercase hover:text-hazard transition-colors">
|
| 35 |
[ HEARTFLOW_OS ]
|
| 36 |
</Link>
|
| 37 |
+
<div className="flex gap-4 md:gap-8 items-center text-[12px] md:text-[13px] font-bold tracking-wider uppercase flex-wrap justify-center">
|
| 38 |
<Link href="/dashboard" className="text-ink hover:text-hazard transition-colors">SYS.MONITOR</Link>
|
| 39 |
<Link href="/simulator" className="text-ink hover:text-hazard transition-colors">AI.SIMULATOR</Link>
|
| 40 |
<Link href="/history" className="text-ink hover:text-hazard transition-colors">DATA.LOG</Link>
|
|
|
|
| 43 |
</div>
|
| 44 |
</div>
|
| 45 |
</nav>
|
| 46 |
+
|
| 47 |
<div className="flex-1 flex flex-col">
|
| 48 |
{children}
|
| 49 |
</div>
|
| 50 |
</TelemetryProvider>
|
| 51 |
+
|
| 52 |
{/* Universal Footer */}
|
| 53 |
<footer className="w-full border-t-2 border-ink bg-white py-6 px-8 mt-auto z-50">
|
| 54 |
+
<div className="max-w-350 mx-auto flex flex-col md:flex-row items-center justify-between gap-4">
|
| 55 |
+
<div className="text-[12px] font-bold tracking-wider uppercase text-ink text-center md:text-left">
|
| 56 |
© 2026 HEARTFLOW_OS. TELEMETRY_SYS.
|
| 57 |
</div>
|
| 58 |
<div className="flex flex-wrap items-center justify-center gap-4 md:gap-8">
|
| 59 |
+
<Link href="/architecture" className="text-[12px] font-bold tracking-wider uppercase text-ink hover:text-hazard transition-colors">ARCHITECTURE.MD</Link>
|
| 60 |
+
<a href={`${process.env.NEXT_PUBLIC_API_URL || "https://idkhowtocodefr-tinyml-backend.hf.space"}/docs`} target="_blank" rel="noopener noreferrer" className="text-[12px] font-bold tracking-wider uppercase text-ink hover:text-hazard transition-colors">API_DOCS</a>
|
| 61 |
+
<a href="https://github.com/IDKHowToCodeFR/HEARTFLOW_OS" target="_blank" rel="noopener noreferrer" className="text-[12px] font-bold tracking-wider uppercase text-ink hover:text-hazard transition-colors">GITHUB_REPO</a>
|
| 62 |
</div>
|
| 63 |
</div>
|
| 64 |
</footer>
|
frontend/src/app/mlops/page.tsx
CHANGED
|
@@ -27,7 +27,7 @@ export default function MLOps() {
|
|
| 27 |
const handleUpload = async () => {
|
| 28 |
if (!file) return;
|
| 29 |
setStatus("uploading");
|
| 30 |
-
|
| 31 |
const formData = new FormData();
|
| 32 |
formData.append("file", file);
|
| 33 |
|
|
@@ -37,7 +37,7 @@ export default function MLOps() {
|
|
| 37 |
body: formData,
|
| 38 |
});
|
| 39 |
const data = await res.json();
|
| 40 |
-
|
| 41 |
if (data.error) {
|
| 42 |
setStatus("error");
|
| 43 |
setMessage(data.error);
|
|
@@ -52,18 +52,18 @@ export default function MLOps() {
|
|
| 52 |
};
|
| 53 |
|
| 54 |
return (
|
| 55 |
-
<main className="min-h-
|
| 56 |
{/* Top Nav (Mechanical) */}
|
| 57 |
|
| 58 |
-
<motion.div
|
| 59 |
variants={staggerContainer}
|
| 60 |
initial="hidden"
|
| 61 |
animate="show"
|
| 62 |
-
className="max-w-
|
| 63 |
>
|
| 64 |
<motion.header variants={fadeUp} className="mb-12 border-b-4 border-ink pb-8 flex justify-between items-end">
|
| 65 |
<div>
|
| 66 |
-
<div className="text-[12px] text-hazard font-bold tracking-
|
| 67 |
/// PIPELINE_CONTROL
|
| 68 |
</div>
|
| 69 |
<h1 className="text-[clamp(3rem,6vw,6rem)]">
|
|
@@ -74,18 +74,18 @@ export default function MLOps() {
|
|
| 74 |
|
| 75 |
<motion.div variants={fadeUp} className="border-2 border-ink bg-white p-8">
|
| 76 |
<div className="border-b-2 border-ink pb-4 mb-8">
|
| 77 |
-
<h2 className="text-[16px] font-bold tracking-
|
| 78 |
<p className="text-[13px] text-slate uppercase mt-2">Append telemetry data and rebuild the edge ensemble.</p>
|
| 79 |
</div>
|
| 80 |
|
| 81 |
<div className="flex flex-col gap-6">
|
| 82 |
-
<label
|
| 83 |
-
htmlFor="dropzone-file"
|
| 84 |
className="flex flex-col items-center justify-center w-full h-48 border-2 border-dashed border-ink bg-canvas-cream hover:bg-hazard hover:text-white transition-colors cursor-pointer group relative overflow-hidden"
|
| 85 |
>
|
| 86 |
-
<div className="absolute inset-0 pointer-events-none opacity-20 bg-[linear-gradient(45deg,var(--ink-black)_25%,transparent_25%,transparent_50%,var(--ink-black)_50%,var(--ink-black)_75%,transparent_75%,transparent)] bg-[
|
| 87 |
<div className="flex flex-col items-center justify-center pt-5 pb-6 relative z-10">
|
| 88 |
-
<motion.div
|
| 89 |
initial={{ scale: 0.9, opacity: 0 }}
|
| 90 |
animate={{ scale: 1, opacity: 1 }}
|
| 91 |
key={file ? 'selected' : 'unselected'}
|
|
@@ -98,10 +98,10 @@ export default function MLOps() {
|
|
| 98 |
</p>
|
| 99 |
<p className="text-[12px] uppercase opacity-70">REQUIRES MATCHING SCHEMA</p>
|
| 100 |
</div>
|
| 101 |
-
<input
|
| 102 |
-
id="dropzone-file"
|
| 103 |
-
type="file"
|
| 104 |
-
className="sr-only"
|
| 105 |
accept=".csv"
|
| 106 |
onChange={(e) => setFile(e.target.files?.[0] || null)}
|
| 107 |
aria-label="Upload CSV Dataset"
|
|
@@ -109,23 +109,23 @@ export default function MLOps() {
|
|
| 109 |
</label>
|
| 110 |
|
| 111 |
<div className="flex justify-between items-center border-t-2 border-ink pt-6 mt-2" aria-live="polite">
|
| 112 |
-
<motion.div
|
| 113 |
key={status}
|
| 114 |
initial={{ opacity: 0, x: -10 }}
|
| 115 |
animate={{ opacity: 1, x: 0 }}
|
| 116 |
-
className="text-[13px] font-bold uppercase tracking-
|
| 117 |
>
|
| 118 |
{status === "uploading" && <span className="text-slate">PROCESSING...</span>}
|
| 119 |
{status === "error" && <span className="text-hazard">ERR: {message}</span>}
|
| 120 |
{status === "success" && <span className="text-ink bg-canvas-cream px-2 py-1 border border-ink">SYS: {message}</span>}
|
| 121 |
</motion.div>
|
| 122 |
-
|
| 123 |
-
<motion.button
|
| 124 |
whileHover={{ scale: 1.02, backgroundColor: "var(--color-hazard)", borderColor: "var(--color-hazard)" }}
|
| 125 |
whileTap={{ scale: 0.98 }}
|
| 126 |
onClick={handleUpload}
|
| 127 |
disabled={!file || status === "uploading"}
|
| 128 |
-
className="inline-flex items-center justify-center h-12 px-8 bg-ink text-white text-[14px] font-bold uppercase tracking-
|
| 129 |
>
|
| 130 |
EXECUTE >>
|
| 131 |
</motion.button>
|
|
|
|
| 27 |
const handleUpload = async () => {
|
| 28 |
if (!file) return;
|
| 29 |
setStatus("uploading");
|
| 30 |
+
|
| 31 |
const formData = new FormData();
|
| 32 |
formData.append("file", file);
|
| 33 |
|
|
|
|
| 37 |
body: formData,
|
| 38 |
});
|
| 39 |
const data = await res.json();
|
| 40 |
+
|
| 41 |
if (data.error) {
|
| 42 |
setStatus("error");
|
| 43 |
setMessage(data.error);
|
|
|
|
| 52 |
};
|
| 53 |
|
| 54 |
return (
|
| 55 |
+
<main className="min-h-dvh bg-canvas-cream flex flex-col font-mono selection:bg-hazard selection:text-white">
|
| 56 |
{/* Top Nav (Mechanical) */}
|
| 57 |
|
| 58 |
+
<motion.div
|
| 59 |
variants={staggerContainer}
|
| 60 |
initial="hidden"
|
| 61 |
animate="show"
|
| 62 |
+
className="max-w-250 mx-auto w-full px-8 py-16 flex-1 flex flex-col"
|
| 63 |
>
|
| 64 |
<motion.header variants={fadeUp} className="mb-12 border-b-4 border-ink pb-8 flex justify-between items-end">
|
| 65 |
<div>
|
| 66 |
+
<div className="text-[12px] text-hazard font-bold tracking-widest mb-4">
|
| 67 |
/// PIPELINE_CONTROL
|
| 68 |
</div>
|
| 69 |
<h1 className="text-[clamp(3rem,6vw,6rem)]">
|
|
|
|
| 74 |
|
| 75 |
<motion.div variants={fadeUp} className="border-2 border-ink bg-white p-8">
|
| 76 |
<div className="border-b-2 border-ink pb-4 mb-8">
|
| 77 |
+
<h2 className="text-[16px] font-bold tracking-wider uppercase text-ink">{'< BATCH_RETRAIN >'}</h2>
|
| 78 |
<p className="text-[13px] text-slate uppercase mt-2">Append telemetry data and rebuild the edge ensemble.</p>
|
| 79 |
</div>
|
| 80 |
|
| 81 |
<div className="flex flex-col gap-6">
|
| 82 |
+
<label
|
| 83 |
+
htmlFor="dropzone-file"
|
| 84 |
className="flex flex-col items-center justify-center w-full h-48 border-2 border-dashed border-ink bg-canvas-cream hover:bg-hazard hover:text-white transition-colors cursor-pointer group relative overflow-hidden"
|
| 85 |
>
|
| 86 |
+
<div className="absolute inset-0 pointer-events-none opacity-20 bg-[linear-gradient(45deg,var(--ink-black)_25%,transparent_25%,transparent_50%,var(--ink-black)_50%,var(--ink-black)_75%,transparent_75%,transparent)] bg-size-[20px_20px]" />
|
| 87 |
<div className="flex flex-col items-center justify-center pt-5 pb-6 relative z-10">
|
| 88 |
+
<motion.div
|
| 89 |
initial={{ scale: 0.9, opacity: 0 }}
|
| 90 |
animate={{ scale: 1, opacity: 1 }}
|
| 91 |
key={file ? 'selected' : 'unselected'}
|
|
|
|
| 98 |
</p>
|
| 99 |
<p className="text-[12px] uppercase opacity-70">REQUIRES MATCHING SCHEMA</p>
|
| 100 |
</div>
|
| 101 |
+
<input
|
| 102 |
+
id="dropzone-file"
|
| 103 |
+
type="file"
|
| 104 |
+
className="sr-only"
|
| 105 |
accept=".csv"
|
| 106 |
onChange={(e) => setFile(e.target.files?.[0] || null)}
|
| 107 |
aria-label="Upload CSV Dataset"
|
|
|
|
| 109 |
</label>
|
| 110 |
|
| 111 |
<div className="flex justify-between items-center border-t-2 border-ink pt-6 mt-2" aria-live="polite">
|
| 112 |
+
<motion.div
|
| 113 |
key={status}
|
| 114 |
initial={{ opacity: 0, x: -10 }}
|
| 115 |
animate={{ opacity: 1, x: 0 }}
|
| 116 |
+
className="text-[13px] font-bold uppercase tracking-wider"
|
| 117 |
>
|
| 118 |
{status === "uploading" && <span className="text-slate">PROCESSING...</span>}
|
| 119 |
{status === "error" && <span className="text-hazard">ERR: {message}</span>}
|
| 120 |
{status === "success" && <span className="text-ink bg-canvas-cream px-2 py-1 border border-ink">SYS: {message}</span>}
|
| 121 |
</motion.div>
|
| 122 |
+
|
| 123 |
+
<motion.button
|
| 124 |
whileHover={{ scale: 1.02, backgroundColor: "var(--color-hazard)", borderColor: "var(--color-hazard)" }}
|
| 125 |
whileTap={{ scale: 0.98 }}
|
| 126 |
onClick={handleUpload}
|
| 127 |
disabled={!file || status === "uploading"}
|
| 128 |
+
className="inline-flex items-center justify-center h-12 px-8 bg-ink text-white text-[14px] font-bold uppercase tracking-widest border-2 border-ink disabled:opacity-50 disabled:pointer-events-none"
|
| 129 |
>
|
| 130 |
EXECUTE >>
|
| 131 |
</motion.button>
|
frontend/src/app/page.tsx
CHANGED
|
@@ -5,7 +5,7 @@ import Link from "next/link";
|
|
| 5 |
|
| 6 |
export default function Home() {
|
| 7 |
const easeFluid: [number, number, number, number] = [0.16, 1, 0.3, 1];
|
| 8 |
-
|
| 9 |
const staggerContainer = {
|
| 10 |
hidden: { opacity: 0 },
|
| 11 |
show: {
|
|
@@ -20,18 +20,18 @@ export default function Home() {
|
|
| 20 |
};
|
| 21 |
|
| 22 |
return (
|
| 23 |
-
<main className="min-h-
|
| 24 |
{/* Hero Section */}
|
| 25 |
-
<section className="max-w-
|
| 26 |
-
<motion.div
|
| 27 |
variants={staggerContainer}
|
| 28 |
initial="hidden"
|
| 29 |
animate="show"
|
| 30 |
className="lg:col-span-7 flex flex-col justify-center"
|
| 31 |
>
|
| 32 |
-
<motion.div variants={fadeUp} className="text-[12px] text-hazard font-bold tracking-
|
| 33 |
-
<span>/// MODEL_V2.
|
| 34 |
-
<motion.span
|
| 35 |
initial={{ scaleX: 0 }}
|
| 36 |
animate={{ scaleX: 1 }}
|
| 37 |
transition={{ duration: 1, ease: easeFluid, delay: 0.4 }}
|
|
@@ -39,7 +39,7 @@ export default function Home() {
|
|
| 39 |
></motion.span>
|
| 40 |
<span>INT8_QUANTIZED</span>
|
| 41 |
</motion.div>
|
| 42 |
-
|
| 43 |
<motion.h1 variants={fadeUp} className="text-[clamp(3rem,8vw,8rem)] mb-8 leading-tight">
|
| 44 |
CLINICAL<br />
|
| 45 |
INFERENCE<br />
|
|
@@ -51,31 +51,31 @@ export default function Home() {
|
|
| 51 |
</motion.p>
|
| 52 |
|
| 53 |
<motion.div variants={fadeUp} className="flex flex-wrap items-center gap-4">
|
| 54 |
-
<Link href="/dashboard"
|
| 55 |
-
<motion.
|
| 56 |
whileHover={{ scale: 1.02, backgroundColor: "var(--color-hazard)", borderColor: "var(--color-hazard)" }}
|
| 57 |
whileTap={{ scale: 0.98 }}
|
| 58 |
transition={{ duration: 0.2, ease: "easeOut" }}
|
| 59 |
-
className="inline-flex items-center justify-center h-12 px-8 bg-ink text-white text-[14px] font-bold uppercase tracking-
|
| 60 |
>
|
| 61 |
[ INITIATE_STREAM ]
|
| 62 |
-
</motion.
|
| 63 |
</Link>
|
| 64 |
-
<Link href="/edge"
|
| 65 |
-
<motion.
|
| 66 |
whileHover={{ scale: 1.02, backgroundColor: "var(--color-ink)", color: "var(--color-canvas-cream)" }}
|
| 67 |
whileTap={{ scale: 0.98 }}
|
| 68 |
transition={{ duration: 0.2, ease: "easeOut" }}
|
| 69 |
-
className="inline-flex items-center justify-center h-12 px-8 bg-transparent text-ink text-[14px] font-bold uppercase tracking-
|
| 70 |
>
|
| 71 |
C_COMPILER >>
|
| 72 |
-
</motion.
|
| 73 |
</Link>
|
| 74 |
</motion.div>
|
| 75 |
</motion.div>
|
| 76 |
|
| 77 |
{/* Feature Grid */}
|
| 78 |
-
<motion.div
|
| 79 |
variants={staggerContainer}
|
| 80 |
initial="hidden"
|
| 81 |
animate="show"
|
|
|
|
| 5 |
|
| 6 |
export default function Home() {
|
| 7 |
const easeFluid: [number, number, number, number] = [0.16, 1, 0.3, 1];
|
| 8 |
+
|
| 9 |
const staggerContainer = {
|
| 10 |
hidden: { opacity: 0 },
|
| 11 |
show: {
|
|
|
|
| 20 |
};
|
| 21 |
|
| 22 |
return (
|
| 23 |
+
<main className="min-h-dvh bg-canvas-cream flex flex-col font-mono selection:bg-hazard selection:text-white">
|
| 24 |
{/* Hero Section */}
|
| 25 |
+
<section className="max-w-350 mx-auto w-full px-8 pt-16 md:pt-32 pb-24 grid grid-cols-1 lg:grid-cols-12 gap-16 flex-1 overflow-hidden">
|
| 26 |
+
<motion.div
|
| 27 |
variants={staggerContainer}
|
| 28 |
initial="hidden"
|
| 29 |
animate="show"
|
| 30 |
className="lg:col-span-7 flex flex-col justify-center"
|
| 31 |
>
|
| 32 |
+
<motion.div variants={fadeUp} className="text-[12px] text-hazard font-bold tracking-widest mb-6 flex items-center gap-4">
|
| 33 |
+
<span>/// MODEL_V2.4 </span>
|
| 34 |
+
<motion.span
|
| 35 |
initial={{ scaleX: 0 }}
|
| 36 |
animate={{ scaleX: 1 }}
|
| 37 |
transition={{ duration: 1, ease: easeFluid, delay: 0.4 }}
|
|
|
|
| 39 |
></motion.span>
|
| 40 |
<span>INT8_QUANTIZED</span>
|
| 41 |
</motion.div>
|
| 42 |
+
|
| 43 |
<motion.h1 variants={fadeUp} className="text-[clamp(3rem,8vw,8rem)] mb-8 leading-tight">
|
| 44 |
CLINICAL<br />
|
| 45 |
INFERENCE<br />
|
|
|
|
| 51 |
</motion.p>
|
| 52 |
|
| 53 |
<motion.div variants={fadeUp} className="flex flex-wrap items-center gap-4">
|
| 54 |
+
<Link href="/dashboard" className="block w-full md:w-auto">
|
| 55 |
+
<motion.div
|
| 56 |
whileHover={{ scale: 1.02, backgroundColor: "var(--color-hazard)", borderColor: "var(--color-hazard)" }}
|
| 57 |
whileTap={{ scale: 0.98 }}
|
| 58 |
transition={{ duration: 0.2, ease: "easeOut" }}
|
| 59 |
+
className="inline-flex items-center justify-center h-12 px-8 bg-ink text-white text-[14px] font-bold uppercase tracking-widest border-2 border-ink w-full"
|
| 60 |
>
|
| 61 |
[ INITIATE_STREAM ]
|
| 62 |
+
</motion.div>
|
| 63 |
</Link>
|
| 64 |
+
<Link href="/edge" className="block w-full md:w-auto mt-2 md:mt-0">
|
| 65 |
+
<motion.div
|
| 66 |
whileHover={{ scale: 1.02, backgroundColor: "var(--color-ink)", color: "var(--color-canvas-cream)" }}
|
| 67 |
whileTap={{ scale: 0.98 }}
|
| 68 |
transition={{ duration: 0.2, ease: "easeOut" }}
|
| 69 |
+
className="inline-flex items-center justify-center h-12 px-8 bg-transparent text-ink text-[14px] font-bold uppercase tracking-widest border-2 border-ink w-full"
|
| 70 |
>
|
| 71 |
C_COMPILER >>
|
| 72 |
+
</motion.div>
|
| 73 |
</Link>
|
| 74 |
</motion.div>
|
| 75 |
</motion.div>
|
| 76 |
|
| 77 |
{/* Feature Grid */}
|
| 78 |
+
<motion.div
|
| 79 |
variants={staggerContainer}
|
| 80 |
initial="hidden"
|
| 81 |
animate="show"
|
frontend/src/app/simulator/page.tsx
CHANGED
|
@@ -65,18 +65,18 @@ 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"
|
| 75 |
-
className="max-w-
|
| 76 |
>
|
| 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-
|
| 80 |
/// EXPLAINABLE_AI_MODULE
|
| 81 |
</div>
|
| 82 |
<h1 className="text-[clamp(3rem,6vw,6rem)]">
|
|
@@ -84,7 +84,7 @@ export default function Analysis() {
|
|
| 84 |
</h1>
|
| 85 |
</div>
|
| 86 |
<div className="text-right hidden md:block max-w-sm">
|
| 87 |
-
<div className="text-[14px] font-bold uppercase tracking-
|
| 88 |
MANUAL OVERRIDE. INPUT CUSTOM TELEMETRY TO TEST ENSEMBLE CLASSIFIER AND VIEW SHAPLEY FEATURE IMPORTANCE VECTORS.
|
| 89 |
</div>
|
| 90 |
</div>
|
|
@@ -95,10 +95,10 @@ export default function Analysis() {
|
|
| 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-
|
| 99 |
</div>
|
| 100 |
|
| 101 |
-
<div className="p-8 flex flex-col gap-8
|
| 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})} />
|
|
@@ -117,14 +117,13 @@ export default function Analysis() {
|
|
| 117 |
</motion.button>
|
| 118 |
|
| 119 |
{/* Terminal Readout */}
|
| 120 |
-
|
| 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
|
| 127 |
-
<pre className="text-[12px] font-mono whitespace-pre-wrap
|
| 128 |
{`{
|
| 129 |
"Heart_Rate": ${formData.Heart_Rate},
|
| 130 |
"SpO2_Level": ${formData.SpO2_Level},
|
|
@@ -136,16 +135,15 @@ export default function Analysis() {
|
|
| 136 |
</pre>
|
| 137 |
</div>
|
| 138 |
</div>
|
| 139 |
-
</div>
|
| 140 |
</motion.div>
|
| 141 |
|
| 142 |
{/* Results Panel */}
|
| 143 |
<motion.div variants={fadeUp} className="lg:col-span-7 bg-canvas-cream flex flex-col overflow-hidden">
|
| 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-
|
| 146 |
</div>
|
| 147 |
|
| 148 |
-
<div className="
|
| 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">
|
|
@@ -156,7 +154,7 @@ export default function Analysis() {
|
|
| 156 |
transition={{ duration: 0.6, ease: easeFluid }}
|
| 157 |
>
|
| 158 |
<div className="mb-12">
|
| 159 |
-
<div className="text-[11px] font-bold text-slate tracking-
|
| 160 |
|
| 161 |
{(() => {
|
| 162 |
const getStatusStyles = (label: string) => {
|
|
@@ -171,10 +169,10 @@ export default function Analysis() {
|
|
| 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 break-words">
|
| 175 |
{prediction.prediction ? `[!]_${prediction.prediction_label.replace(" ", "_")}` : 'NORMAL'}
|
| 176 |
</div>
|
| 177 |
-
<div className="text-[14px] font-bold tracking-
|
| 178 |
CONFIDENCE: {(prediction.probability * 100).toFixed(1)}%
|
| 179 |
</div>
|
| 180 |
</div>
|
|
@@ -183,7 +181,7 @@ export default function Analysis() {
|
|
| 183 |
|
| 184 |
{/* Ensemble Diagnostics */}
|
| 185 |
{prediction.model_outputs && (
|
| 186 |
-
<div className="border-2 border-ink bg-white flex flex-col text-[12px] font-bold uppercase tracking-
|
| 187 |
<div className="border-b-2 border-ink px-4 py-2 bg-ink text-canvas-cream flex justify-between">
|
| 188 |
<span>{'< ENSEMBLE_STACK_DIAGNOSTICS >'}</span>
|
| 189 |
<span className="text-hazard">WEIGHTED_VOTING</span>
|
|
@@ -214,8 +212,8 @@ export default function Analysis() {
|
|
| 214 |
)}
|
| 215 |
</div>
|
| 216 |
|
| 217 |
-
<div className="flex-1 border-2 border-ink bg-white p-8 flex flex-col min-h-
|
| 218 |
-
<div className="text-[11px] font-bold text-slate tracking-
|
| 219 |
<div className="flex-1">
|
| 220 |
<ShapChart patientData={submittedData} />
|
| 221 |
</div>
|
|
@@ -223,7 +221,7 @@ export default function Analysis() {
|
|
| 223 |
</motion.div>
|
| 224 |
) : (
|
| 225 |
<div className="h-full flex items-center justify-center">
|
| 226 |
-
<div className="text-slate text-[14px] font-bold tracking-
|
| 227 |
{loading ? 'CALCULATING_SHAP_VALUES...' : 'AWAITING_INPUT...'}
|
| 228 |
</div>
|
| 229 |
</div>
|
|
@@ -242,7 +240,7 @@ function InputControl({ label, value, min, max, step = 1, onChange }: any) {
|
|
| 242 |
return (
|
| 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-
|
| 246 |
<motion.span
|
| 247 |
key={value}
|
| 248 |
initial={{ opacity: 0.5, y: -5 }}
|
|
|
|
| 65 |
};
|
| 66 |
|
| 67 |
return (
|
| 68 |
+
<main className="max-h-dvh 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"
|
| 75 |
+
className="max-w-350 mx-auto w-full px-8 py-16 flex-1 flex flex-col"
|
| 76 |
>
|
| 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)]">
|
|
|
|
| 84 |
</h1>
|
| 85 |
</div>
|
| 86 |
<div className="text-right hidden md:block max-w-sm">
|
| 87 |
+
<div className="text-[14px] font-bold uppercase tracking-wider border-2 border-ink px-4 py-2 bg-ink text-canvas-cream text-left">
|
| 88 |
MANUAL OVERRIDE. INPUT CUSTOM TELEMETRY TO TEST ENSEMBLE CLASSIFIER AND VIEW SHAPLEY FEATURE IMPORTANCE VECTORS.
|
| 89 |
</div>
|
| 90 |
</div>
|
|
|
|
| 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})} />
|
|
|
|
| 117 |
</motion.button>
|
| 118 |
|
| 119 |
{/* Terminal Readout */}
|
| 120 |
+
<div className="relative 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" />
|
|
|
|
| 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},
|
|
|
|
| 135 |
</pre>
|
| 136 |
</div>
|
| 137 |
</div>
|
|
|
|
| 138 |
</motion.div>
|
| 139 |
|
| 140 |
{/* Results Panel */}
|
| 141 |
<motion.div variants={fadeUp} className="lg:col-span-7 bg-canvas-cream flex flex-col overflow-hidden">
|
| 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">
|
|
|
|
| 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) => {
|
|
|
|
| 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">
|
| 173 |
{prediction.prediction ? `[!]_${prediction.prediction_label.replace(" ", "_")}` : 'NORMAL'}
|
| 174 |
</div>
|
| 175 |
+
<div className="text-[14px] font-bold tracking-widest">
|
| 176 |
CONFIDENCE: {(prediction.probability * 100).toFixed(1)}%
|
| 177 |
</div>
|
| 178 |
</div>
|
|
|
|
| 181 |
|
| 182 |
{/* Ensemble Diagnostics */}
|
| 183 |
{prediction.model_outputs && (
|
| 184 |
+
<div className="border-2 border-ink bg-white flex flex-col text-[12px] font-bold uppercase tracking-wider">
|
| 185 |
<div className="border-b-2 border-ink px-4 py-2 bg-ink text-canvas-cream flex justify-between">
|
| 186 |
<span>{'< ENSEMBLE_STACK_DIAGNOSTICS >'}</span>
|
| 187 |
<span className="text-hazard">WEIGHTED_VOTING</span>
|
|
|
|
| 212 |
)}
|
| 213 |
</div>
|
| 214 |
|
| 215 |
+
<div className="flex-1 border-2 border-ink bg-white p-8 flex flex-col min-h-75">
|
| 216 |
+
<div className="text-[11px] font-bold text-slate tracking-widest mb-6 border-b-2 border-ink pb-2">SHAPLEY_ADDITIVE_EXPLANATIONS</div>
|
| 217 |
<div className="flex-1">
|
| 218 |
<ShapChart patientData={submittedData} />
|
| 219 |
</div>
|
|
|
|
| 221 |
</motion.div>
|
| 222 |
) : (
|
| 223 |
<div className="h-full flex items-center justify-center">
|
| 224 |
+
<div className="text-slate text-[14px] font-bold tracking-widest uppercase border-2 border-slate px-6 py-4 animate-pulse">
|
| 225 |
{loading ? 'CALCULATING_SHAP_VALUES...' : 'AWAITING_INPUT...'}
|
| 226 |
</div>
|
| 227 |
</div>
|
|
|
|
| 240 |
return (
|
| 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 }}
|