Spaces:
Runtime error
Runtime error
Download frontend/src/app/edge/page.tsx from IDKHowToCodeFr/tinyml-backend: direct link, hf CLI and curl.
- Browser
- Download file 13.7 kB
-
https://huggingface.co/spaces/IDKHowToCodeFr/tinyml-backend/resolve/main/frontend/src/app/edge/page.tsx
- Command line
-
hf download hf://spaces/IDKHowToCodeFr/tinyml-backend/frontend/src/app/edge/page.tsx
-
curl -L -o page.tsx https://huggingface.co/spaces/IDKHowToCodeFr/tinyml-backend/resolve/main/frontend/src/app/edge/page.tsx
13.7 kB
| "use client"; | |
| import { useState } from "react"; | |
| import { Cpu, Zap, Terminal } from "lucide-react"; | |
| import { generateCHeader } from "@/lib/edge/c_exporter"; | |
| import GeneratedCHeader from "@/components/GeneratedCHeader"; | |
| export default function EdgeDeployment() { | |
| const [model, setModel] = useState("rf"); | |
| const [targetMcu, setTargetMcu] = useState("esp32"); | |
| const [quantize, setQuantize] = useState(true); | |
| const [code, setCode] = useState<string | null>(null); | |
| const [loading, setLoading] = useState(false); | |
| const [error, setError] = useState(""); | |
| const [copied, setCopied] = useState(false); | |
| const handleExport = async (overrideModel?: string, overrideQuantize?: boolean, overrideMcu?: string) => { | |
| const activeModel = overrideModel ?? model; | |
| const activeQuantize = overrideQuantize !== undefined ? overrideQuantize : quantize; | |
| const activeMcu = overrideMcu ?? targetMcu; | |
| setLoading(true); | |
| setError(""); | |
| try { | |
| const apiUrl = process.env.NEXT_PUBLIC_API_URL; | |
| if (apiUrl) { | |
| const res = await fetch(`${apiUrl}/export_tinyml?model_name=${activeModel}&quantize=${activeQuantize}`); | |
| if (res.ok) { | |
| const data = await res.json(); | |
| if (!data.error && (data.code || data.c_code)) { | |
| setCode(data.code || data.c_code); | |
| setLoading(false); | |
| return; | |
| } | |
| } | |
| } | |
| } catch { | |
| // Cloud API unreachable, seamlessly compile on the client | |
| } | |
| // Client-side Zero-Cloud TinyML Transpiler | |
| try { | |
| const cCode = generateCHeader(activeModel, activeQuantize, activeMcu); | |
| setCode(cCode); | |
| } catch (err: unknown) { | |
| const msg = err instanceof Error ? err.message : String(err); | |
| setError(msg || "Failed to generate C header"); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| const handleSelectModel = (val: string) => { | |
| setModel(val); | |
| }; | |
| const handleSelectMcu = (val: string) => { | |
| setTargetMcu(val); | |
| }; | |
| const handleToggleQuantize = () => { | |
| setQuantize((prev) => !prev); | |
| }; | |
| const handleCopy = () => { | |
| if (!code) return; | |
| navigator.clipboard.writeText(code); | |
| setCopied(true); | |
| setTimeout(() => setCopied(false), 2000); | |
| }; | |
| const handleDownload = () => { | |
| if (!code) return; | |
| const blob = new Blob([code], { type: "text/x-csrc" }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement("a"); | |
| a.href = url; | |
| a.download = `model_${model}_${quantize ? "int8" : "fp32"}.h`; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| URL.revokeObjectURL(url); | |
| }; | |
| // Dynamic hardware budget estimates based on selected topology | |
| const getSramEstimate = () => { | |
| if (model === "rf") return quantize ? "28 Bytes" : "112 Bytes"; | |
| if (model === "svm") return quantize ? "32 Bytes" : "200 Bytes"; | |
| if (model === "small_nn") return quantize ? "48 Bytes" : "168 Bytes"; | |
| if (model === "knn") return quantize ? "64 Bytes" : "240 Bytes"; | |
| return quantize ? "24 Bytes" : "96 Bytes"; | |
| }; | |
| const getFlashEstimate = () => { | |
| if (model === "rf") return quantize ? "~ 1.6 KB" : "~ 4.2 KB"; | |
| if (model === "svm") return quantize ? "~ 0.9 KB" : "~ 2.1 KB"; | |
| if (model === "small_nn") return quantize ? "~ 1.2 KB" : "~ 2.8 KB"; | |
| if (model === "knn") return quantize ? "~ 1.8 KB" : "~ 3.4 KB"; | |
| return quantize ? "~ 0.6 KB" : "~ 1.5 KB"; | |
| }; | |
| return ( | |
| <main className="min-h-dvh bg-canvas-cream flex flex-col font-mono selection:bg-hazard selection:text-white pb-12"> | |
| <div className="max-w-350 mx-auto w-full px-4 sm:px-8 py-6 md:py-8 flex-1 flex flex-col"> | |
| {/* Header */} | |
| <header className="mb-6 border-b-4 border-ink pb-6 flex flex-col md:flex-row justify-between md:items-end gap-4"> | |
| <div> | |
| <div className="text-[12px] text-hazard font-bold tracking-widest mb-3 flex items-center gap-2 uppercase"> | |
| <Terminal size={14} /> | |
| C99 EMBEDDED TRANSPILER 路 ZERO-MALLOC ABI | |
| </div> | |
| <h1 className="text-[clamp(2.5rem,5vw,5rem)] leading-[0.9]"> | |
| EDGE<br />COMPILER | |
| </h1> | |
| </div> | |
| <div className="flex flex-col items-start md:items-end gap-2 text-right"> | |
| <div className="text-[12px] font-bold uppercase tracking-wider border-2 border-ink px-4 py-2 bg-ink text-white"> | |
| {`TARGET: ${targetMcu.toUpperCase()} 路 ${quantize ? "INT8 FIXED-POINT" : "FP32 SINGLE-PRECISION"}`} | |
| </div> | |
| <div className="text-[11px] text-slate font-bold uppercase tracking-wider"> | |
| ABI: int predict(const float *features) 路 Zero-Malloc | |
| </div> | |
| </div> | |
| </header> | |
| {/* 2-Column Responsive Workspace Grid (Fixed Size on Desktop: lg:h-145) */} | |
| <div className="crosshair-corner grid grid-cols-1 lg:grid-cols-12 gap-px bg-ink border-2 border-ink lg:h-145"> | |
| {/* Config Panel - 6 Columns (Fixed Size, Zero Scroll) */} | |
| <div className="lg:col-span-6 bg-canvas-cream flex flex-col h-full min-h-0 justify-between overflow-hidden"> | |
| {/* Header */} | |
| <div className="h-13 px-4 sm:px-5 border-b-2 border-ink bg-white flex justify-between items-center shrink-0"> | |
| <h2 className="text-[13px] sm:text-[14px] font-bold tracking-wider uppercase flex items-center gap-2"> | |
| <Cpu size={15} /> | |
| Target Configuration | |
| </h2> | |
| <span className="tech-tag text-[9px]">TINYML C99</span> | |
| </div> | |
| {/* Target Architecture Selection (2x2 Silicon Grid) */} | |
| <div className="p-3 sm:p-3.5 bg-canvas-cream border-b-2 border-ink flex flex-col gap-1.5 shrink-0"> | |
| <div className="text-[10px] font-bold tracking-widest text-slate uppercase flex justify-between items-center"> | |
| <span className="text-ink">Target Silicon Profile</span> | |
| <span className="text-hazard text-[9px] font-bold uppercase">{targetMcu} active</span> | |
| </div> | |
| <div className="grid grid-cols-2 gap-2"> | |
| {[ | |
| { id: "esp32", name: "ESP32-S3", clock: "240 MHz", arch: "Xtensa LX7 路 SIMD", mem: "512 KB" }, | |
| { id: "arm", name: "Cortex-M4F", clock: "168 MHz", arch: "ARMv7E-M 路 CMSIS", mem: "FPU Unit" }, | |
| { id: "rp2040", name: "RP2040", clock: "133 MHz", arch: "Dual M0+ 路 ALU", mem: "264 KB" }, | |
| { id: "nrf52", name: "nRF52840", clock: "64 MHz", arch: "Cortex-M4 路 BLE", mem: "256 KB" }, | |
| ].map((chip) => { | |
| const isSelected = targetMcu === chip.id; | |
| return ( | |
| <button | |
| key={chip.id} | |
| type="button" | |
| onClick={() => handleSelectMcu(chip.id)} | |
| className={`btn-tactile border-2 border-ink p-2 text-left transition-colors flex flex-col justify-between cursor-pointer ${ | |
| isSelected | |
| ? "bg-ink text-white font-bold" | |
| : "bg-white text-ink hover:bg-canvas-cream" | |
| }`} | |
| > | |
| <div className="flex justify-between items-center text-[11px] font-black uppercase tracking-wider mb-0.5"> | |
| <span>{chip.name}</span> | |
| <span | |
| className={`text-[8px] font-bold px-1.5 py-0.2 border ${ | |
| isSelected | |
| ? "bg-hazard text-white border-hazard" | |
| : "bg-whisper text-ink border-ink/40" | |
| }`} | |
| > | |
| {chip.clock} | |
| </span> | |
| </div> | |
| <div className="flex justify-between items-center text-[9px] font-mono tabular-nums"> | |
| <span className={`truncate max-w-27.5 ${isSelected ? "text-white/75" : "text-slate"}`}>{chip.arch}</span> | |
| <span className={`font-bold shrink-0 ${isSelected ? "text-hazard" : "text-slate"}`}>{chip.mem}</span> | |
| </div> | |
| </button> | |
| ); | |
| })} | |
| </div> | |
| </div> | |
| {/* Config Controls (Middle Section, Flex-1, Fits with zero scroll) */} | |
| <div className="p-3 sm:p-3.5 flex flex-col gap-2.5 bg-white flex-1 min-h-0 justify-between"> | |
| {/* Model Architecture Selector - 5 Ensemble Models */} | |
| <div className="flex flex-col gap-1"> | |
| <div className="text-[10px] font-bold uppercase tracking-widest text-slate flex justify-between items-center"> | |
| <span className="text-ink">Ensemble Classifier Topology</span> | |
| <span className="text-[9px] text-slate/80">5 Estimators</span> | |
| </div> | |
| <div className="grid grid-cols-5 gap-1.5"> | |
| {[ | |
| { id: "rf", label: "RF", sub: "30 Trees" }, | |
| { id: "svm", label: "SVM", sub: "Linear" }, | |
| { id: "logreg", label: "LogReg", sub: "Logits" }, | |
| { id: "small_nn", label: "MLP", sub: "16-8 Net" }, | |
| { id: "knn", label: "KNN", sub: "5-NN" }, | |
| ].map((m) => { | |
| const isSelected = model === m.id; | |
| return ( | |
| <button | |
| key={m.id} | |
| type="button" | |
| onClick={() => handleSelectModel(m.id)} | |
| className={`btn-tactile border-2 border-ink py-1.5 px-0.5 text-center transition-colors cursor-pointer ${ | |
| isSelected | |
| ? "bg-ink text-white font-bold" | |
| : "bg-canvas-cream text-ink hover:bg-white" | |
| }`} | |
| > | |
| <div className="text-[10px] font-black uppercase tracking-wider leading-tight">{m.label}</div> | |
| <div className={`text-[7.5px] uppercase tracking-wider mt-0.5 ${isSelected ? "text-white/75" : "text-slate"}`}> | |
| {m.sub} | |
| </div> | |
| </button> | |
| ); | |
| })} | |
| </div> | |
| </div> | |
| {/* Quantization Toggle */} | |
| <div className="border-2 border-ink p-2.5 bg-canvas-cream flex items-center justify-between"> | |
| <div> | |
| <div className="text-[11px] font-bold uppercase tracking-wider text-ink flex items-center gap-1.5"> | |
| <Zap size={12} className="text-hazard shrink-0" /> | |
| INT8 Fixed-Point Quantization | |
| </div> | |
| <div className="text-[8.5px] text-slate uppercase mt-0.5"> | |
| {quantize ? "75% SRAM reduction 路 Integer ALU bitshifts 路 0 FPU cycles" : "Standard FP32 IEEE floating point representation"} | |
| </div> | |
| </div> | |
| <button | |
| type="button" | |
| onClick={handleToggleQuantize} | |
| className={`btn-tactile border-2 border-ink px-2.5 py-1 text-[8.5px] font-bold uppercase tracking-wider transition-colors shrink-0 ml-2 cursor-pointer ${ | |
| quantize ? "bg-hazard text-white" : "bg-white text-ink hover:bg-canvas-cream" | |
| }`} | |
| > | |
| {quantize ? "Enabled" : "Disabled"} | |
| </button> | |
| </div> | |
| {/* Hardware Budget 3-Cell Telemetry Strip */} | |
| <div className="border-2 border-ink bg-canvas-cream p-2 text-[9px] font-bold tracking-wider uppercase grid grid-cols-3 gap-2 text-center divide-x-2 divide-ink/20"> | |
| <div> | |
| <div className="text-[8px] text-slate">STATIC SRAM</div> | |
| <div className="tabular-nums text-ink text-[10.5px] font-black">{getSramEstimate()}</div> | |
| </div> | |
| <div> | |
| <div className="text-[8px] text-slate">FLASH ROM</div> | |
| <div className="tabular-nums text-ink text-[10.5px] font-black">{getFlashEstimate()}</div> | |
| </div> | |
| <div> | |
| <div className="text-[8px] text-slate">HEAP ALLOC</div> | |
| <div className="text-hazard tabular-nums text-[10.5px] font-black">0 BYTES</div> | |
| </div> | |
| </div> | |
| </div> | |
| {/* Compile CTA - Directly connected with no gap */} | |
| <div className="p-3.5 sm:p-4 bg-white border-t-2 border-ink shrink-0"> | |
| <button | |
| type="button" | |
| onClick={() => handleExport()} | |
| disabled={loading} | |
| className="btn-tactile-hazard py-2.5 font-bold text-[13px] tracking-[0.2em] uppercase flex items-center justify-center gap-2 w-full cursor-pointer disabled:cursor-not-allowed" | |
| > | |
| {loading ? "Compiling C99 Header..." : code ? "Recompile C Header" : "Compile C Header"} | |
| </button> | |
| </div> | |
| </div> | |
| {/* Reusable Generated C Header Section - 6 Columns */} | |
| <GeneratedCHeader | |
| code={code} | |
| loading={loading} | |
| error={error} | |
| onCopy={handleCopy} | |
| onDownload={handleDownload} | |
| copied={copied} | |
| className="lg:col-span-6 h-full min-h-0" | |
| /> | |
| </div> | |
| </div> | |
| </main> | |
| ); | |
| } | |