File size: 13,716 Bytes
4cea933
 
 
604d53b
 
 
4cea933
 
 
604d53b
 
 
4cea933
 
604d53b
 
 
 
 
 
4cea933
 
 
604d53b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4cea933
604d53b
 
 
 
 
4cea933
 
 
 
 
604d53b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4cea933
 
 
 
 
 
604d53b
4cea933
 
 
 
 
 
604d53b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4cea933
604d53b
 
 
 
 
4cea933
604d53b
 
 
4cea933
604d53b
 
4cea933
 
604d53b
 
 
 
 
 
 
 
4cea933
 
604d53b
 
 
 
 
 
 
 
 
 
 
4cea933
 
604d53b
 
 
 
 
4cea933
604d53b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4cea933
 
 
604d53b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4cea933
604d53b
 
 
 
 
 
4cea933
604d53b
 
 
 
 
 
 
 
 
 
4cea933
604d53b
4cea933
604d53b
4cea933
604d53b
 
 
 
 
 
 
 
 
4cea933
604d53b
 
 
4cea933
604d53b
 
 
 
 
 
 
 
 
 
 
 
 
4cea933
 
604d53b
 
 
 
 
 
 
 
 
 
 
4cea933
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
"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>
  );
}