Team Ai
Apppublic

IDKHowToCodeFr/tinyml-backend

sourceHugging Faceupdated 2d agoView on Hugging Face
1likes
page.tsx247 linesDownload Raw Back to dashboard
1"use client";2 3import { motion } from "framer-motion";4import Link from "next/link";5import { useTelemetry } from "@/context/TelemetryContext";6 7const easeFluid: [number, number, number, number] = [0.16, 1, 0.3, 1];8 9const staggerContainer = {10  hidden: { opacity: 0 },11  show: {12    opacity: 1,13    transition: { staggerChildren: 0.1, delayChildren: 0.1 }14  }15};16 17const fadeUp = {18  hidden: { opacity: 0, y: 20 },19  show: { opacity: 1, y: 0, transition: { duration: 0.8, ease: easeFluid } }20};21 22export default function Dashboard() {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 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" }}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 />TELEMETRY47            </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>54        </motion.header>55 56        {/* Dashboard Grid - Brutalist Tables */}57        <motion.div variants={staggerContainer} className="grid grid-cols-1 bg-ink border-2 border-ink" aria-live="polite">58          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) => {66                    const normalized = (label || "").toLowerCase();67                    if (normalized === 'healthy' || normalized === 'normal') return "bg-[#d1fae5] text-[#065f46] border-[#065f46]";68                    if (normalized.includes('heart')) return "bg-[#fef08a] text-[#854d0e] border-[#854d0e]";69                    if (normalized.includes('asthma')) return "bg-[#e0f2fe] text-[#075985] border-[#075985]";70                    if (normalized.includes('hypertension')) return "bg-[#f3e8ff] text-[#6b21a8] border-[#6b21a8]";71                    if (normalized.includes('diabetes')) return "bg-[#ffedd5] text-[#9a3412] border-[#9a3412]";72                    return "bg-hazard text-white border-hazard"; 73                  };74                  return (75                    <motion.div 76                      key={data.prediction.label}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              )}91            </div>92 93            <motion.div variants={staggerContainer} className="grid grid-cols-2 lg:grid-cols-4 gap-px bg-ink border-b-2 border-ink">94              <VitalCard label="HEART_RATE" value={data?.sensor_data?.Heart_Rate ? Math.round(data.sensor_data.Heart_Rate) : "---"} unit="BPM" />95              <VitalCard label="O2_SATURATION" value={data?.sensor_data?.SpO2_Level ? Math.round(data.sensor_data.SpO2_Level) : "---"} unit="%" />96              <VitalCard label="BLOOD_PRESSURE" value={data?.sensor_data ? `${Math.round(data.sensor_data.Systolic_BP)}/${Math.round(data.sensor_data.Diastolic_BP)}` : null} unit="MMHG" />97              <VitalCard label="CORE_TEMP" value={data?.sensor_data?.Body_Temp ? data.sensor_data.Body_Temp.toFixed(1) : "---"} unit="°C" />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))116                     .map(([disease, prob], i) => {117                        const pct = ((prob as number) * 100).toFixed(1);118                        const isRisk = (prob as number) > 0.4 && disease.toLowerCase() !== 'normal' && disease.toLowerCase() !== 'healthy';119                        return (120                          <motion.div 121                            key={disease} 122                            initial={{ opacity: 0, x: -20 }}123                            animate={{ opacity: 1, x: 0 }}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>131                            <div className="h-6 bg-canvas-cream border-2 border-ink overflow-hidden relative">132                              {/* Blueprint tick marks */}133                              <div className="absolute inset-0 z-0 opacity-20" style={{ backgroundImage: 'linear-gradient(90deg, var(--ink-black) 1px, transparent 1px)', backgroundSize: '10% 100%' }}></div>134                              <motion.div 135                                className={`h-full relative z-10 ${isRisk ? 'bg-hazard shadow-[0_0_8px_var(--hazard-red)]' : 'bg-ink'}`}136                                initial={{ width: "0%" }}137                                animate={{ width: `${pct}%` }}138                                transition={{ duration: 0.8, ease: easeFluid }}139                              />140                            </div>141                          </motion.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                )}149              </div>150            </motion.div>151 152          </motion.div>153        </motion.div>154      </motion.div>155    </main>156  );157}158 159function 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>166        <motion.div 167          key={String(value)}168          initial={{ opacity: 0.5, y: 5 }}169          animate={{ opacity: 1, y: 0 }}170          className="font-sans text-[clamp(40px,3.5vw,60px)] font-black tracking-tighter tabular-nums leading-none mb-1 break-all"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>178    </motion.div>179  );180}181 182function TelemetryGraph({ stream, min, max, color, fill }: { stream: number[], min: number, max: number, color: string, fill: string }) {183  const range = max - min;184  185  const points = stream.map((val, i) => {186    const x = (i / (stream.length - 1)) * 100;187    const y = Math.max(0, Math.min(100, 100 - (((val - min) / range) * 100)));188    return `${x},${y}`;189  });190 191  const firstY = points[0] ? points[0].split(',')[1] : 100;192  const pathD = `M 0,100 L 0,${firstY} L ${points.join(" L ")} L 100,100 Z`;193  const lineD = `M 0,${firstY} L ${points.join(" L ")}`;194  const lastPoint = points[points.length - 1];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">204        <svg className="w-full h-full block" viewBox="0 0 100 100" preserveAspectRatio="none">205          {/* Fill Area */}206          <path 207            d={pathD} 208            fill={fill} 209            className="transition-all duration-300 ease-linear"210          />211          {/* Stroke Line (Glow) */}212          <path 213            d={lineD} 214            fill="none" 215            stroke={color} 216            strokeWidth="1.5" 217            vectorEffect="non-scaling-stroke"218            className="transition-all duration-300 ease-linear"219            style={{ filter: `drop-shadow(0 0 4px ${color})` }}220          />221          {/* Leading Dot */}222          <circle 223            cx="100" 224            cy={lastY} 225            r="1.5" 226            fill={color} 227            className="transition-all duration-300 ease-linear"228            style={{ filter: `drop-shadow(0 0 4px ${color})` }}229          />230        </svg>231      </div>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%" }}239        transition={{ duration: 1.5, ease: "linear", repeat: Infinity }}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}247