Team Ai
Apppublic

IDKHowToCodeFr/tinyml-backend

sourceHugging Faceupdated 2d agoView on Hugging Face
1likes
page.tsx269 linesDownload Raw Back to simulator
1"use client";2 3import { motion } from "framer-motion";4import Link from "next/link";5import { useState, useEffect } from "react";6import ShapChart from "@/components/ShapChart";7 8const easeFluid: [number, number, number, number] = [0.16, 1, 0.3, 1];9 10const staggerContainer = {11  hidden: { opacity: 0 },12  show: {13    opacity: 1,14    transition: { staggerChildren: 0.1, delayChildren: 0.1 }15  }16};17 18const fadeUp = {19  hidden: { opacity: 0, y: 20 },20  show: { opacity: 1, y: 0, transition: { duration: 0.8, ease: easeFluid } }21};22 23export default function Analysis() {24  const [formData, setFormData] = useState({25    Heart_Rate: 75,26    SpO2_Level: 98,27    Systolic_BP: 120,28    Diastolic_BP: 80,29    Body_Temp: 37.030  });31 32  useEffect(() => {33    setFormData({34      Heart_Rate: Math.floor(Math.random() * (140 - 60 + 1)) + 60,35      SpO2_Level: Math.floor(Math.random() * (100 - 90 + 1)) + 90,36      Systolic_BP: Math.floor(Math.random() * (160 - 90 + 1)) + 90,37      Diastolic_BP: Math.floor(Math.random() * (100 - 60 + 1)) + 60,38      Body_Temp: Number((Math.random() * (39.5 - 36.0) + 36.0).toFixed(1))39    });40  }, []);41 42  const [submittedData, setSubmittedData] = useState<any>(null);43  const [prediction, setPrediction] = useState<any>(null);44  const [loading, setLoading] = useState(false);45 46  const handlePredict = async () => {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",54        headers: { "Content-Type": "application/json" },55        body: JSON.stringify(formData)56      });57      const data = await res.json();58      setPrediction(data);59      setSubmittedData(formData);60    } catch (err) {61      console.error(err);62    } finally {63      setLoading(false);64    }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.div72        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)]">83              MODEL<br />SIMULATOR84            </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>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.button109                onClick={handlePredict}110                disabled={loading}111                whileHover={{ y: 2, x: 2, boxShadow: "4px 4px 0px 0px rgba(0,0,0,1)" }}112                whileTap={{ y: 6, x: 6, boxShadow: "none" }}113                transition={{ duration: 0.1 }}114                className="mt-6 border-[3px] border-ink bg-hazard text-white py-6 font-bold text-[18px] tracking-[0.2em] shadow-[6px_6px_0px_0px_rgba(0,0,0,1)] uppercase disabled:opacity-50 disabled:cursor-not-allowed"115              >116                {loading ? 'PROCESSING_REQUEST...' : '[ EXECUTE_INFERENCE ]'}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},132  "Diastolic_BP": ${formData.Diastolic_BP},133  "Body_Temp": ${formData.Body_Temp.toFixed(1)}134}`}135                    <span className="inline-block w-2 h-4 bg-[#4AF626] animate-pulse ml-1 align-middle" />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-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.div154                    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();164                          if (normalized === 'healthy' || normalized === 'normal') return "bg-[#d1fae5] text-[#065f46] border-[#065f46]";165                          if (normalized.includes('heart')) return "bg-[#fef08a] text-[#854d0e] border-[#854d0e]";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">175                              {prediction.prediction ? `[!]_${prediction.prediction_label.replace(" ", "_")}` : 'NORMAL'}176                            </div>177                            <div className="text-[14px] font-bold tracking-widest">178                              CONFIDENCE: {(prediction.probability * 100).toFixed(1)}%179                            </div>180                          </div>181                        );182                      })()}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-wider">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>190                          </div>191                          <div className="divide-y-2 divide-ink">192                            {Object.keys(prediction.model_outputs).map((modelName, index) => (193                              <motion.div194                                key={modelName}195                                initial={{ opacity: 0, x: 20 }}196                                animate={{ opacity: 1, x: 0 }}197                                transition={{ duration: 0.4, delay: index * 0.1, ease: "easeOut" }}198                                className="flex justify-between items-center p-3 hover:bg-canvas-cream transition-colors"199                              >200                                <div className="w-[20%] text-slate">[{modelName}]</div>201                                <div className={`w-[40%] ${prediction.model_outputs[modelName] !== 'Healthy' ? 'text-hazard' : ''}`}>202                                  {prediction.model_outputs[modelName] !== 'Healthy' ? '[!] ' + prediction.model_outputs[modelName] : '[OK] HEALTHY'}203                                </div>204                                <div className="w-[20%] text-right">205                                  CONF: {(prediction.model_probs[modelName] * 100).toFixed(1)}%206                                </div>207                                <div className="w-[20%] text-right text-slate">208                                  W: {prediction.weights[modelName].toFixed(2)}209                                </div>210                              </motion.div>211                            ))}212                          </div>213                        </div>214                      )}215                    </div>216 217                    <div className="flex-1 border-2 border-ink bg-white p-8 flex flex-col min-h-75">218                      <div className="text-[11px] font-bold text-slate tracking-widest mb-6 border-b-2 border-ink pb-2">SHAPLEY_ADDITIVE_EXPLANATIONS</div>219                      <div className="flex-1">220                        <ShapChart patientData={submittedData} />221                      </div>222                    </div>223                  </motion.div>224                ) : (225                  <div className="h-full flex items-center justify-center">226                    <div className="text-slate text-[14px] font-bold tracking-widest uppercase border-2 border-slate px-6 py-4 animate-pulse">227                      {loading ? 'CALCULATING_SHAP_VALUES...' : 'AWAITING_INPUT...'}228                    </div>229                  </div>230                )}231              </div>232            </div>233          </motion.div>234 235        </motion.div>236      </motion.div>237    </main>238  );239}240 241function 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-widest group-hover:text-ink transition-colors">{label}</label>246        <motion.span247          key={value}248          initial={{ opacity: 0.5, y: -5 }}249          animate={{ opacity: 1, y: 0 }}250          className="font-sans text-[32px] font-black leading-none tabular-nums tracking-tighter"251        >252          {value}253        </motion.span>254      </div>255      <div className="relative border-2 border-ink p-1 bg-white">256        <input257          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        />265      </div>266    </div>267  );268}269