everydaytok commited on
Commit
7518706
Β·
verified Β·
1 Parent(s): 731b39e

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +160 -418
index.html CHANGED
@@ -3,7 +3,7 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Elastic Mesh Lab</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <script src="https://cdn.plot.ly/plotly-2.24.1.min.js"></script>
9
  <style>
@@ -11,224 +11,129 @@
11
  .glass { background:rgba(10,18,35,0.98); border:1px solid #1e2d40; }
12
  #drawer { transition:transform .3s ease; z-index:200; }
13
  .drawer-closed { transform:translateY(100%); }
14
- .lbtn {
15
- width:20px; height:20px; background:#1e293b; border:1px solid #334155;
16
- border-radius:3px; color:#94a3b8; font-weight:bold; font-size:13px;
17
- cursor:pointer; display:inline-flex; align-items:center; justify-content:center;
18
- }
19
- .lbtn:hover { background:#334155; color:#fff; }
20
  .ptype { display:inline-block; padding:1px 5px; border-radius:3px; font-size:9px; font-weight:bold; }
21
- .pt-box_proj { background:#1e3a2f; color:#4ade80; }
22
- .pt-halfspace { background:#1e2d4a; color:#60a5fa; }
23
- .pt-sphere { background:#3a1e3a; color:#e879f9; }
24
- .pt-simplex { background:#3a2a1e; color:#fb923c; }
25
- .pt-elastic { background:#3a1e1e; color:#f87171; }
26
- .pt-unknown { background:#1e1e1e; color:#64748b; }
27
  </style>
28
  </head>
29
  <body class="flex flex-col h-screen overflow-hidden">
30
 
31
- <!-- ── HEADER ──────────────────────────────────────────────────────────────── -->
32
  <header class="glass flex-shrink-0 px-2 py-1.5 flex justify-between items-center gap-1">
33
-
34
- <!-- Mode + topology badges -->
35
  <div class="flex flex-wrap gap-1 items-center text-[8px] font-bold">
36
- <span id="b-mode" class="px-1.5 py-0.5 rounded bg-yellow-900/60 text-yellow-300 border border-yellow-800/60">IDLE</span>
37
- <span id="b-type" class="px-1.5 py-0.5 rounded bg-slate-800 text-slate-400 border border-slate-700">β€”</span>
38
- <span id="b-dim" class="px-1.5 py-0.5 rounded bg-slate-900 text-slate-600 border border-slate-800">D:32</span>
39
-
40
- <!-- Inline topology U / L -->
41
- <span class="px-1.5 py-0.5 rounded bg-green-900/60 text-green-300 border border-green-800/60 flex items-center gap-0.5">
42
- <span class="text-orange-400 mr-0.5">U</span>
43
- <button class="lbtn" onclick="quickL('upper',-1)">βˆ’</button>
44
- <span id="b-upper" class="w-3 text-center text-white">3</span>
45
- <button class="lbtn" onclick="quickL('upper',+1)">+</button>
46
- <span class="text-cyan-400 mx-1">L</span>
47
- <button class="lbtn" onclick="quickL('lower',-1)">βˆ’</button>
48
- <span id="b-lower" class="w-3 text-center text-white">3</span>
49
- <button class="lbtn" onclick="quickL('lower',+1)">+</button>
50
- </span>
51
  </div>
52
-
53
- <!-- Controls -->
54
  <div class="flex items-center gap-1 flex-shrink-0">
55
- <span id="q-lbl" class="text-[8px] text-slate-600">Q:0</span>
56
- <div id="run-dot" class="w-2 h-2 rounded-full bg-slate-700"></div>
57
- <button onclick="doTrainOffline()" class="text-[9px] bg-purple-800 hover:bg-purple-700 px-2 py-1 rounded font-bold">⚑ TRAIN</button>
58
- <button onclick="doTrainVisual()" class="text-[9px] bg-blue-800 hover:bg-blue-700 px-2 py-1 rounded font-bold">πŸ‘ VIZ</button>
59
- <button onclick="doInfer()" class="text-[9px] bg-green-800 hover:bg-green-700 px-2 py-1 rounded font-bold">β–Ά INFER</button>
60
- <button onclick="doHalt()" class="text-[9px] bg-red-900 hover:bg-red-800 px-2 py-1 rounded font-bold">β– </button>
61
- <button onclick="openDrawer()" class="text-[9px] bg-slate-700 hover:bg-slate-600 px-2 py-1 rounded font-bold">βš™</button>
62
  </div>
63
  </header>
64
 
65
- <!-- ── PLOTS ────────────────────────────────────────────────────────────────── -->
66
- <div class="flex-grow flex flex-col min-h-0 overflow-hidden">
67
- <div id="mesh-plot" class="flex-grow min-h-0"></div>
68
- <div id="err-plot" style="height:56px" class="flex-shrink-0 border-t border-slate-900"></div>
69
  </div>
70
 
71
- <!-- ── BOTTOM PANEL ─────────────────────────────────────────────────────────── -->
72
- <div class="glass flex-shrink-0 border-t border-slate-800" style="height:180px">
73
  <div class="flex border-b border-slate-800 text-[10px]">
74
- <button onclick="tab('nodes')" id="tab-nodes" class="flex-1 py-1 bg-blue-900/40 text-blue-300 font-bold">NODES</button>
75
- <button onclick="tab('springs')" id="tab-springs" class="flex-1 py-1 text-slate-500">SPRINGS</button>
76
  <button onclick="tab('accuracy')" id="tab-accuracy" class="flex-1 py-1 text-slate-500">ACCURACY</button>
77
- <button onclick="tab('logs')" id="tab-logs" class="flex-1 py-1 text-slate-500">LOGS</button>
78
  </div>
79
  <div class="flex h-full overflow-hidden">
80
- <!-- Tension meter -->
81
- <div class="w-20 flex-shrink-0 border-r border-slate-800 flex flex-col items-center justify-center p-1 gap-0.5">
82
- <div class="text-[7px] text-slate-600 uppercase tracking-widest">β€–errorβ€–</div>
83
- <div id="err-big" class="text-xl font-bold text-red-400 leading-none">0.00</div>
84
- <div id="pred-val" class="text-[8px] text-slate-500">β€–Cβ€–:β€”</div>
85
- <div id="iter-lbl" class="text-[8px] text-slate-700">IT:0</div>
86
- <div id="step-lbl" class="text-[7px] text-slate-800">S:0</div>
87
  </div>
88
- <!-- Panes -->
89
- <div class="flex-grow overflow-y-auto p-1.5 text-[10px]">
90
  <div id="pane-nodes"></div>
91
- <div id="pane-springs" class="hidden"></div>
92
  <div id="pane-accuracy" class="hidden"></div>
93
- <div id="pane-logs" class="hidden text-[9px] text-slate-500"></div>
94
  </div>
95
  </div>
96
  </div>
97
 
98
- <!-- ── DRAWER ───────────────────────────────────────────────────────────────── -->
99
- <aside id="drawer" class="drawer-closed fixed inset-x-0 bottom-0 glass border-t border-slate-700 p-4 flex flex-col gap-3 max-h-[90vh] overflow-y-auto">
100
  <div class="flex justify-between items-center">
101
- <span class="text-orange-400 font-bold">ELASTIC MESH LABORATORY</span>
102
  <button onclick="closeDrawer()" class="text-slate-400 text-2xl leading-none">βœ•</button>
103
  </div>
104
 
105
- <!-- Architecture info (read-only for now) -->
106
- <div class="bg-slate-900 rounded p-3 border border-blue-900/50 text-[9px]">
107
- <div class="text-blue-400 font-bold mb-2">ARCHITECTURE</div>
108
- <div class="text-slate-400 space-y-1">
109
- <div>β€’ Spring type: <span class="text-white">Matrix K ∈ ℝ^(DΓ—D)</span> per edge</div>
110
- <div>β€’ Update rule: <span class="text-white">Matrix LMS β€” joint optimal step</span></div>
111
- <div>β€’ Inference: <span class="text-white">EWC-regularised online adaptation</span></div>
112
- <div>β€’ Convergence: <span class="text-white">β€–errorβ€– &lt; 0.08 or 400 steps</span></div>
113
- </div>
114
- </div>
115
-
116
- <!-- Topology controls (detailed) -->
117
- <div class="bg-slate-900 rounded p-3 border border-green-900/50">
118
- <div class="text-green-400 text-[9px] font-bold mb-2">TOPOLOGY A β†’ [U] β†’ C ← [L] ← B</div>
119
- <div class="grid grid-cols-2 gap-2">
120
- <div class="bg-slate-800 rounded p-2 text-center border border-orange-900/30">
121
- <div class="text-orange-400 text-[8px] mb-1">UPPER (A-side)</div>
122
- <div class="flex items-center justify-center gap-2">
123
- <button class="lbtn text-base" onclick="quickL('upper',-1)">βˆ’</button>
124
- <span id="dial-upper" class="text-orange-300 font-bold text-2xl">3</span>
125
- <button class="lbtn text-base" onclick="quickL('upper',+1)">+</button>
126
- </div>
127
- </div>
128
- <div class="bg-slate-800 rounded p-2 text-center border border-cyan-900/30">
129
- <div class="text-cyan-400 text-[8px] mb-1">LOWER (B-side)</div>
130
- <div class="flex items-center justify-center gap-2">
131
- <button class="lbtn text-base" onclick="quickL('lower',-1)">βˆ’</button>
132
- <span id="dial-lower" class="text-cyan-300 font-bold text-2xl">3</span>
133
- <button class="lbtn text-base" onclick="quickL('lower',+1)">+</button>
134
- </div>
135
  </div>
136
  </div>
137
- <div id="spring-info" class="text-[8px] text-slate-600 mt-2 text-center"></div>
138
- </div>
139
 
140
- <!-- Training controls -->
141
- <div class="bg-slate-900 rounded p-3 border border-purple-900/50">
142
- <div class="text-purple-400 text-[9px] font-bold mb-2">OFFLINE TRAINING (fast, no display)</div>
143
- <div class="flex gap-2 items-center mb-2">
144
- <label class="text-[9px] text-slate-500 w-16">Epochs</label>
145
- <input id="ep-n" type="number" value="5" min="1" max="50"
146
- class="w-16 bg-black border border-slate-700 p-1 text-white text-sm text-center rounded">
147
- <button onclick="doTrainOffline()"
148
- class="flex-1 bg-purple-800 hover:bg-purple-700 py-2 text-xs font-bold rounded">⚑ START</button>
149
- </div>
150
- <div class="text-[8px] text-slate-600">
151
- Runs full LMS epochs at CPU speed. Fisher info accumulates for EWC.
152
  </div>
153
  </div>
154
 
155
- <!-- Inference controls -->
156
- <div class="bg-slate-900 rounded p-3 border border-green-900/50">
157
- <div class="text-green-400 text-[9px] font-bold mb-2">INFERENCE TEST</div>
158
- <div class="flex gap-2 items-center mb-2">
159
- <label class="text-[9px] text-slate-500 w-16">N samples</label>
160
- <input id="inf-n" type="number" value="200" min="10" max="556"
161
- class="w-16 bg-black border border-slate-700 p-1 text-white text-sm text-center rounded">
162
- <button onclick="doInfer()"
163
- class="flex-1 bg-green-800 hover:bg-green-700 py-2 text-xs font-bold rounded">β–Ά RUN</button>
164
- </div>
165
- <div class="text-[8px] text-slate-600">
166
- Mesh sees A+B only. C_pred vs C_target β†’ accuracy per problem type.
167
  </div>
168
  </div>
169
-
170
- <div class="flex gap-2">
171
- <button onclick="doReset()"
172
- class="flex-1 border border-slate-600 text-slate-400 py-2 rounded text-xs font-bold">RESET SPRINGS</button>
173
- <button onclick="doHalt(); closeDrawer()"
174
- class="flex-1 border border-red-700 text-red-500 py-2 rounded text-xs font-bold">HALT</button>
175
- </div>
176
  </aside>
177
 
178
  <script>
179
- // ── LOCAL STATE ───────────────────────────────────────────────────────────────
180
- const topo = { upper: 3, lower: 3 };
181
-
182
- function syncTopoUI(nu, nl) {
183
- topo.upper = nu; topo.lower = nl;
184
- ['upper','lower'].forEach(k => {
185
- document.getElementById(`b-${k}`).innerText = topo[k];
186
- document.getElementById(`dial-${k}`).innerText = topo[k];
187
- });
188
- const d = 32; // from server dim
189
- const n = nu + nl;
190
- document.getElementById('spring-info').innerText =
191
- `${2*n} spring matrices Γ— DΒ² = ${2*n}Γ—${d*d} = ${2*n*d*d} parameters`;
192
- }
193
-
194
- async function quickL(w, d) {
195
- const r = await fetch('/set_layer', {
196
- method:'POST', headers:{'Content-Type':'application/json'},
197
- body: JSON.stringify({ layer: w, delta: d })
198
- });
199
- const data = await r.json();
200
- syncTopoUI(data.n_upper, data.n_lower);
201
- meshPlotted = false;
202
- }
203
-
204
  function openDrawer() { document.getElementById('drawer').classList.remove('drawer-closed'); }
205
  function closeDrawer() { document.getElementById('drawer').classList.add('drawer-closed'); }
 
206
 
207
- // ── ACTIONS ───────────────────────────────────────────────────────────────────
208
- async function doTrainOffline() {
209
  const ep = parseInt(document.getElementById('ep-n').value);
210
- await fetch('/train_offline', {
211
- method:'POST', headers:{'Content-Type':'application/json'},
212
- body: JSON.stringify({ epochs: ep })
213
- });
214
- }
215
- async function doTrainVisual() {
216
- await fetch('/train_visual', { method:'POST', headers:{'Content-Type':'application/json'}, body:'{}' });
217
  }
 
218
  async function doInfer() {
219
  const n = parseInt(document.getElementById('inf-n').value);
220
- await fetch('/infer', {
221
- method:'POST', headers:{'Content-Type':'application/json'},
222
- body: JSON.stringify({ n })
223
- });
224
  closeDrawer();
225
  }
226
- async function doHalt() { await fetch('/halt', { method:'POST' }); }
227
- async function doReset() { await fetch('/reset', { method:'POST' }); meshPlotted = false; }
228
 
229
- // ── TABS ──────────────────────────────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
230
  function tab(name) {
231
- ['nodes','springs','accuracy','logs'].forEach(t => {
232
  document.getElementById(`pane-${t}`).classList.toggle('hidden', t !== name);
233
  document.getElementById(`tab-${t}`).className = t === name
234
  ? 'flex-1 py-1 bg-blue-900/40 text-blue-300 font-bold text-[10px]'
@@ -236,261 +141,98 @@ function tab(name) {
236
  });
237
  }
238
 
239
- // ── VISUALIZATION ─────────────────────────────────────────────────────────────
240
- // Vertical bilateral hourglass:
241
- // A (top bus) β†’ U1..Un (upper bulge) β†’ C (center waist)
242
- // B (bot bus) β†’ L1..Ln (lower bulge) β†’ C
243
- const Y = { A:4.3, U:2.0, C:0.0, L:-2.0, B:-4.3 };
244
- const SPREAD = { A:0, U:3.6, C:0, L:3.6, B:0 };
245
- const NCOL = id =>
246
- id==='A' ? '#fb923c' : id==='B' ? '#c084fc' : id==='C' ? '#38bdf8' :
247
- id.startsWith('U') ? '#4ade80' : '#67e8f9';
248
-
249
- function nodePos(layers) {
250
- const pos = {};
251
- const ymap = [Y.A, Y.U, Y.C, Y.L, Y.B];
252
- const smap = [SPREAD.A, SPREAD.U, SPREAD.C, SPREAD.L, SPREAD.B];
253
- layers.forEach((layer, li) => {
254
- const n = layer.length;
255
- const s = smap[li] || 0;
256
- layer.forEach((id, i) => {
257
- pos[id] = [n===1 ? 0 : s*(2*i/(n-1)-1), ymap[li]];
258
- });
259
- });
260
- return pos;
261
- }
262
-
263
- function busShapes(pos) {
264
- const sh = [];
265
- const rect = (xc,yc,hw,hh,fill,stroke) => sh.push({
266
- type:'rect', xref:'x', yref:'y',
267
- x0:xc-hw, x1:xc+hw, y0:yc-hh, y1:yc+hh,
268
- fillcolor:fill, line:{color:stroke, width:2}
269
- });
270
- rect(0, Y.A, 2.0, 0.32, 'rgba(251,146,60,0.09)', 'rgba(251,146,60,0.60)');
271
- rect(0, Y.C, 1.6, 0.30, 'rgba(56,189,248,0.09)', 'rgba(56,189,248,0.65)');
272
- rect(0, Y.B, 2.0, 0.32, 'rgba(192,132,252,0.09)','rgba(192,132,252,0.60)');
273
- return sh;
274
- }
275
-
276
- function springColor(frob, mean) {
277
- const t = Math.min(frob / 4, 1);
278
- const r = mean >= 0
279
- ? [Math.round(160+t*90), Math.round(90+t*80), 30]
280
- : [30, Math.round(80+t*100), Math.round(120+t*135)];
281
- return [`rgb(${r[0]},${r[1]},${r[2]})`, 0.8 + t*3.2];
282
- }
283
-
284
- function buildTraces(nodes, springs, layers) {
285
- const pos = nodePos(layers);
286
- const traces = [];
287
-
288
- // Edge traces
289
- for (const [key, s] of Object.entries(springs)) {
290
- const [u, v] = key.split('β†’');
291
- if (!pos[u] || !pos[v]) continue;
292
- const [col, wd] = springColor(s.frob, s.mean);
293
- traces.push({
294
- type:'scatter', mode:'lines',
295
- x:[pos[u][0], pos[v][0]], y:[pos[u][1], pos[v][1]],
296
- line:{color:col, width:wd}, hoverinfo:'none', showlegend:false
297
- });
298
- }
299
-
300
- // Node trace
301
- const allN = layers.flat();
302
- traces.push({
303
- type:'scatter', mode:'markers+text',
304
- x: allN.map(id => pos[id][0]),
305
- y: allN.map(id => pos[id][1]),
306
- text: allN.map(id => {
307
- const n = nodes[id];
308
- return `${id}\nβ€–${n?.norm?.toFixed(2) ?? '?'}β€–`;
309
- }),
310
- textposition: allN.map(id => id==='B' ? 'bottom center' : 'top center'),
311
- textfont:{ size:9, color: allN.map(id => NCOL(id)) },
312
- marker:{
313
- size: allN.map(id => {
314
- const vn = nodes[id]?.vel_norm ?? 0;
315
- const base = ['A','B','C'].includes(id) ? 20 : 13;
316
- return base + Math.min(vn*20, 8);
317
- }),
318
- color: allN.map(id => NCOL(id)),
319
- opacity: allN.map(id => 0.75 + Math.min((nodes[id]?.vel_norm??0)*1.5, 0.25)),
320
- line:{
321
- width:2.5,
322
- color: allN.map(id => nodes[id]?.anchored ? '#ef4444' : '#22c55e')
323
- }
324
- },
325
- hoverinfo:'none', showlegend:false
326
- });
327
-
328
- return traces;
329
- }
330
-
331
- function meshLayout(layers) {
332
- return {
333
- margin:{l:6,r:6,t:6,b:6},
334
- paper_bgcolor:'transparent', plot_bgcolor:'transparent',
335
- xaxis:{visible:false, range:[-5.5, 5.5]},
336
- yaxis:{visible:false, range:[-5.5, 5.5]},
337
- showlegend:false,
338
- shapes: busShapes(nodePos(layers)),
339
- };
340
- }
341
-
342
- const ERR_LAYOUT = {
343
- margin:{l:28,r:5,t:3,b:10},
344
- paper_bgcolor:'transparent', plot_bgcolor:'transparent',
345
- xaxis:{visible:false},
346
- yaxis:{
347
- color:'#334155', gridcolor:'#0f172a',
348
- zeroline:true, zerolinecolor:'#22c55e', zerolinewidth:1
349
- },
350
- showlegend:false,
351
- };
352
-
353
- // Problem type color mapping
354
- const TYPE_COL = {
355
- box_proj: '#4ade80',
356
- halfspace: '#60a5fa',
357
- sphere: '#e879f9',
358
- simplex: '#fb923c',
359
- elastic: '#f87171',
360
- };
361
-
362
- let meshPlotted = false, errPlotted = false, lastLayerKey = '';
363
-
364
- // ── POLL ──────────────────────────────────────────────────────────────────────
365
  setInterval(async () => {
366
  try {
367
- const r = await fetch('/state');
368
- const d = await r.json();
369
-
370
- // Sync topo
371
- syncTopoUI(d.n_upper, d.n_lower);
372
-
373
- // Header badges
374
- const modeColor = {
375
- training: 'bg-yellow-700 text-yellow-100',
376
- inference: 'bg-green-800 text-green-100',
377
- idle: 'bg-slate-800 text-slate-400',
378
- }[d.mode] || 'bg-slate-800 text-slate-400';
379
- document.getElementById('b-mode').className = `px-1.5 py-0.5 rounded text-[8px] font-bold ${modeColor}`;
380
- document.getElementById('b-mode').innerText = d.mode.toUpperCase();
381
- document.getElementById('b-type').innerText = d.current_type || 'β€”';
382
- document.getElementById('b-type').className =
383
- `ptype pt-${d.current_type || 'unknown'}`;
384
- document.getElementById('b-dim').innerText = `D:${d.dim}`;
385
- document.getElementById('run-dot').className =
386
- `w-2 h-2 rounded-full ${d.running ? 'bg-green-400' : 'bg-slate-700'}`;
387
- document.getElementById('q-lbl').innerText = `Q:${d.queue_size}`;
388
-
389
- // Tension meter
390
- const e = d.error;
391
- const col = e < 0.05 ? 'text-green-400' : e < 0.5 ? 'text-yellow-400' : 'text-red-400';
392
- document.getElementById('err-big').className = `text-xl font-bold ${col} leading-none`;
393
- document.getElementById('err-big').innerText = e.toFixed(4);
394
- document.getElementById('pred-val').innerText = `β€–Cβ€–:${d.pred_norm.toFixed(2)}`;
395
- document.getElementById('iter-lbl').innerText = `IT:${d.iter}`;
396
- document.getElementById('step-lbl').innerText = `S:${d.step_count}`;
397
-
398
- // ── Nodes pane ─────────────────────────────────────────────────────────
399
- let nh = '';
400
- (d.layers || []).flat().forEach(id => {
401
- const n = d.nodes[id]; if (!n) return;
402
- const icon = n.anchored ? '<span class="text-red-400">⊠</span>' : '<span class="text-green-500">β—Ž</span>';
403
- const vcol = n.anchored ? 'text-orange-300' : 'text-sky-300';
404
- const head = (n.x_head || []).map(v => v.toFixed(2)).join(' ');
405
- nh += `<div class="py-0.5 border-b border-slate-900/60">
406
- <div class="flex justify-between">
407
- ${icon}<span class="${vcol} ml-1 font-bold">${id}</span>
408
- <span class="text-white">β€–xβ€–=${n.norm}</span>
409
- <span class="text-slate-600 text-[8px]">v=${n.vel_norm.toFixed(3)}</span>
410
- </div>
411
- <div class="text-[8px] text-slate-700 mt-0.5 font-mono">[${head}…]</div>
412
- </div>`;
413
- });
414
- document.getElementById('pane-nodes').innerHTML = nh;
415
-
416
- // ── Springs pane ───────────────────────────────────────────────────────
417
- let sh = '';
418
- for (const [key, s] of Object.entries(d.springs || {})) {
419
- const fc = s.frob > 3 ? 'text-yellow-200' : s.mean < 0 ? 'text-blue-300' : 'text-purple-300';
420
- const fi = s.fish > 0.01 ? 'text-orange-400' : 'text-slate-700';
421
- sh += `<div class="flex justify-between py-0.5 border-b border-slate-900/60 items-center">
422
- <span class="text-slate-500 text-[9px]">${key}</span>
423
- <span class="${fc} font-bold text-[10px]">β€–Kβ€–=${s.frob}</span>
424
- <span class="text-slate-600 text-[8px]">ΞΌ=${s.mean}</span>
425
- <span class="${fi} text-[8px]">F=${s.fish}</span>
426
- </div>`;
427
- }
428
- document.getElementById('pane-springs').innerHTML = sh;
429
-
430
- // ── Accuracy pane ──────────────────────────────────────────────────────
431
- const acc = d.type_acc || {};
432
- const done = d.n_test_done || 0;
433
- const total= d.test_size || 0;
434
- let ah = `<div class="text-[9px] text-slate-500 mb-2">
435
- Test progress: <span class="text-white font-bold">${done}</span> / ${total}
436
- </div>`;
437
-
438
- if (Object.keys(acc).length > 0) {
439
- ah += `<table class="w-full text-[9px] border-collapse">
440
- <tr class="text-slate-600 border-b border-slate-800">
441
- <th class="text-left py-0.5">Type</th>
442
- <th class="text-right py-0.5">N</th>
443
- <th class="text-right py-0.5">Acc%</th>
444
- <th class="text-right py-0.5">Avgβ€–eβ€–</th>
445
- </tr>`;
446
- for (const [t, v] of Object.entries(acc)) {
447
- const aCol = v.acc >= 80 ? 'text-green-400' : v.acc >= 50 ? 'text-yellow-400' : 'text-red-400';
448
- ah += `<tr class="border-b border-slate-900/60">
449
- <td class="py-0.5"><span class="ptype pt-${t}">${t}</span></td>
450
- <td class="text-right text-slate-400">${v.n}</td>
451
- <td class="text-right ${aCol} font-bold">${v.acc}%</td>
452
- <td class="text-right text-slate-400">${v.avg_err}</td>
453
- </tr>`;
454
  }
455
- ah += '</table>';
456
- } else {
457
- ah += '<div class="text-slate-700 text-[9px]">Run inference to see per-type accuracy.</div>';
458
- }
459
- document.getElementById('pane-accuracy').innerHTML = ah;
460
 
461
- // ── Logs pane ──────────────────────────────────────────────────────────
462
- document.getElementById('pane-logs').innerHTML =
463
- d.logs.map(l => `<div class="py-0.5 border-b border-slate-900/40 font-mono">${l}</div>`).join('');
464
-
465
- // ── Mesh plot ──────────────────────────────────────────────────────────
466
- const layerKey = JSON.stringify(d.layers);
467
- const traces = buildTraces(d.nodes, d.springs, d.layers);
468
- const layout = meshLayout(d.layers);
469
- if (!meshPlotted || layerKey !== lastLayerKey) {
470
- Plotly.newPlot('mesh-plot', traces, layout, {displayModeBar:false, responsive:true});
471
- meshPlotted = true;
472
- lastLayerKey = layerKey;
473
- } else {
474
- Plotly.react('mesh-plot', traces, layout);
475
- }
476
-
477
- // ── Error history chart ────────────────────────────────────────────────
478
- const hist = d.history || [];
479
- const eTrace = {
480
- type:'scatter', mode:'lines',
481
- x: hist.map((_,i) => i), y: hist,
482
- line:{color:'#f97316', width:1.5},
483
- fill:'tozeroy', fillcolor:'rgba(249,115,22,0.07)'
484
- };
485
- if (!errPlotted) {
486
- Plotly.newPlot('err-plot',[eTrace], ERR_LAYOUT, {displayModeBar:false, responsive:true});
487
- errPlotted = true;
488
- } else {
489
- Plotly.react('err-plot', [eTrace], ERR_LAYOUT);
490
- }
491
 
492
- } catch(e) { /* silent */ }
493
- }, 220);
494
  </script>
495
  </body>
496
  </html>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Scalar Fabric Logic Mesh</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <script src="https://cdn.plot.ly/plotly-2.24.1.min.js"></script>
9
  <style>
 
11
  .glass { background:rgba(10,18,35,0.98); border:1px solid #1e2d40; }
12
  #drawer { transition:transform .3s ease; z-index:200; }
13
  .drawer-closed { transform:translateY(100%); }
 
 
 
 
 
 
14
  .ptype { display:inline-block; padding:1px 5px; border-radius:3px; font-size:9px; font-weight:bold; }
15
+ .pt-blend { background:#1e3a2f; color:#4ade80; }
16
+ .pt-diff { background:#1e2d4a; color:#60a5fa; }
17
+ .pt-route { background:#3a1e3a; color:#e879f9; }
18
+ .pt-manual{ background:#3a2a1e; color:#fb923c; }
 
 
19
  </style>
20
  </head>
21
  <body class="flex flex-col h-screen overflow-hidden">
22
 
 
23
  <header class="glass flex-shrink-0 px-2 py-1.5 flex justify-between items-center gap-1">
 
 
24
  <div class="flex flex-wrap gap-1 items-center text-[8px] font-bold">
25
+ <span id="b-mode" class="px-1.5 py-0.5 rounded bg-slate-800 text-slate-400 border border-slate-700">IDLE</span>
26
+ <span id="b-type" class="ptype pt-blend">β€”</span>
27
+ <span id="b-dim" class="px-1.5 py-0.5 rounded bg-slate-900 text-slate-600 border border-slate-800">D:8</span>
 
 
 
 
 
 
 
 
 
 
 
 
28
  </div>
 
 
29
  <div class="flex items-center gap-1 flex-shrink-0">
30
+ <span id="q-lbl" class="text-[8px] text-slate-600">Q:0</span>
31
+ <div id="run-dot" class="w-2 h-2 rounded-full bg-slate-700"></div>
32
+ <button onclick="postAct('/halt')" class="text-[9px] bg-red-900 hover:bg-red-800 px-2 py-1 rounded font-bold shadow">β–  HALT</button>
33
+ <button onclick="openDrawer()" class="text-[9px] bg-slate-700 hover:bg-slate-600 px-2 py-1 rounded font-bold shadow">βš™ CONTROLS</button>
 
 
 
34
  </div>
35
  </header>
36
 
37
+ <div class="flex-grow flex flex-col min-h-[30vh] overflow-hidden relative">
38
+ <div id="error-box" class="hidden absolute top-0 w-full bg-red-900 text-white p-2 text-xs font-bold z-50"></div>
39
+ <div id="mesh-plot" class="flex-grow w-full"></div>
40
+ <div id="err-plot" style="height:56px" class="flex-shrink-0 border-t border-slate-900 hidden md:block"></div>
41
  </div>
42
 
43
+ <div class="glass flex-shrink-0 border-t border-slate-800 h-48">
 
44
  <div class="flex border-b border-slate-800 text-[10px]">
45
+ <button onclick="tab('nodes')" id="tab-nodes" class="flex-1 py-1 bg-blue-900/40 text-blue-300 font-bold">NODES (A, C, B)</button>
 
46
  <button onclick="tab('accuracy')" id="tab-accuracy" class="flex-1 py-1 text-slate-500">ACCURACY</button>
47
+ <button onclick="tab('logs')" id="tab-logs" class="flex-1 py-1 text-slate-500">LOGS</button>
48
  </div>
49
  <div class="flex h-full overflow-hidden">
50
+ <div class="w-24 flex-shrink-0 border-r border-slate-800 flex flex-col items-center justify-center p-1 gap-0.5">
51
+ <div class="text-[7px] text-slate-600 uppercase">mean error</div>
52
+ <div id="err-big" class="text-xl font-bold text-red-400 leading-none">0.00</div>
 
 
 
 
53
  </div>
54
+ <div class="flex-grow overflow-y-auto p-1.5 text-[10px] pb-6">
 
55
  <div id="pane-nodes"></div>
 
56
  <div id="pane-accuracy" class="hidden"></div>
57
+ <div id="pane-logs" class="hidden text-[9px] text-slate-500"></div>
58
  </div>
59
  </div>
60
  </div>
61
 
62
+ <aside id="drawer" class="drawer-closed fixed inset-x-0 bottom-0 glass border-t border-slate-700 p-4 flex flex-col gap-3 max-h-[90vh] overflow-y-auto pb-10 shadow-2xl">
 
63
  <div class="flex justify-between items-center">
64
+ <span class="text-orange-400 font-bold">FABRIC CONTROL</span>
65
  <button onclick="closeDrawer()" class="text-slate-400 text-2xl leading-none">βœ•</button>
66
  </div>
67
 
68
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-3">
69
+ <div class="bg-slate-900 rounded p-3 border border-purple-900/50">
70
+ <div class="text-purple-400 text-[9px] font-bold mb-2">OFFLINE TRAINING</div>
71
+ <div class="flex gap-2 items-center">
72
+ <input id="ep-n" type="number" value="15" class="w-16 bg-black border border-slate-700 p-1 text-white text-sm text-center rounded">
73
+ <label class="text-[9px] text-slate-500 flex-1">Epochs</label>
74
+ <button onclick="doTrain()" class="bg-purple-800 hover:bg-purple-700 px-4 py-2 text-xs font-bold rounded">⚑ TRAIN</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  </div>
76
  </div>
 
 
77
 
78
+ <div class="bg-slate-900 rounded p-3 border border-green-900/50">
79
+ <div class="text-green-400 text-[9px] font-bold mb-2">BATCH INFERENCE (EWC)</div>
80
+ <div class="flex gap-2 items-center">
81
+ <input id="inf-n" type="number" value="200" class="w-16 bg-black border border-slate-700 p-1 text-white text-sm text-center rounded">
82
+ <label class="text-[9px] text-slate-500 flex-1">Samples</label>
83
+ <button onclick="doInfer()" class="bg-green-800 hover:bg-green-700 px-4 py-2 text-xs font-bold rounded">β–Ά RUN</button>
84
+ </div>
 
 
 
 
 
85
  </div>
86
  </div>
87
 
88
+ <div class="bg-slate-900 rounded p-3 border border-orange-900/50">
89
+ <div class="text-orange-400 text-[9px] font-bold mb-2">MANUAL INJECTION (D:8)</div>
90
+ <div class="space-y-2">
91
+ <div>
92
+ <label class="text-[8px] text-slate-500 block mb-0.5">Input A</label>
93
+ <input id="man-a" type="text" value="0.9, 0.1, 0.5, 0.2, 0.8, 0.3, 0.7, 0.4" class="w-full bg-black border border-slate-700 p-1.5 text-orange-200 text-xs rounded font-mono">
94
+ </div>
95
+ <div>
96
+ <label class="text-[8px] text-slate-500 block mb-0.5">Input B</label>
97
+ <input id="man-b" type="text" value="0.1, 0.9, 0.5, 0.8, 0.2, 0.7, 0.3, 0.6" class="w-full bg-black border border-slate-700 p-1.5 text-cyan-200 text-xs rounded font-mono">
98
+ </div>
99
+ <button onclick="doManual()" class="w-full bg-orange-900 hover:bg-orange-800 py-2 text-xs font-bold rounded mt-1">INJECT MANUAL ARRAYS</button>
100
  </div>
101
  </div>
 
 
 
 
 
 
 
102
  </aside>
103
 
104
  <script>
105
+ let plotted = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  function openDrawer() { document.getElementById('drawer').classList.remove('drawer-closed'); }
107
  function closeDrawer() { document.getElementById('drawer').classList.add('drawer-closed'); }
108
+ async function postAct(route) { try { await fetch(route, {method:'POST'}); } catch(e) { showError("Connection lost."); } }
109
 
110
+ async function doTrain() {
 
111
  const ep = parseInt(document.getElementById('ep-n').value);
112
+ await fetch('/train', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({epochs:ep})});
113
+ closeDrawer();
 
 
 
 
 
114
  }
115
+
116
  async function doInfer() {
117
  const n = parseInt(document.getElementById('inf-n').value);
118
+ await fetch('/infer', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({n})});
 
 
 
119
  closeDrawer();
120
  }
 
 
121
 
122
+ async function doManual() {
123
+ const a = document.getElementById('man-a').value;
124
+ const b = document.getElementById('man-b').value;
125
+ const res = await fetch('/manual', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({a:a, b:b})});
126
+ const data = await res.json();
127
+ if(!data.ok) alert(data.error); else closeDrawer();
128
+ }
129
+
130
+ function showError(msg) {
131
+ const box = document.getElementById('error-box');
132
+ box.innerText = msg; box.classList.remove('hidden');
133
+ }
134
+
135
  function tab(name) {
136
+ ['nodes','accuracy','logs'].forEach(t => {
137
  document.getElementById(`pane-${t}`).classList.toggle('hidden', t !== name);
138
  document.getElementById(`tab-${t}`).className = t === name
139
  ? 'flex-1 py-1 bg-blue-900/40 text-blue-300 font-bold text-[10px]'
 
141
  });
142
  }
143
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  setInterval(async () => {
145
  try {
146
+ const res = await fetch('/state');
147
+ if (!res.ok) throw new Error("Server error");
148
+ const d = await res.json();
149
+ document.getElementById('error-box').classList.add('hidden');
150
+
151
+ const mcolor = {train:'bg-yellow-700 text-yellow-100', infer:'bg-green-800 text-green-100', manual:'bg-orange-800 text-orange-100'}[d.mode] || 'bg-slate-800';
152
+ document.getElementById('b-mode').className = `px-1.5 py-0.5 rounded text-[8px] font-bold ${mcolor}`;
153
+ document.getElementById('b-mode').innerText = (d.mode||'IDLE').toUpperCase();
154
+ document.getElementById('b-type').className = `ptype pt-${d.current_type}`;
155
+ document.getElementById('b-type').innerText = d.current_type;
156
+ document.getElementById('run-dot').className = `w-2 h-2 rounded-full ${d.running?'bg-green-400':'bg-slate-700'}`;
157
+ document.getElementById('q-lbl').innerText = `Q:${d.queue_size}`;
158
+
159
+ const col = d.error < 0.05 ? 'text-green-400' : d.error < 0.15 ? 'text-yellow-400' : 'text-red-400';
160
+ document.getElementById('err-big').className = `text-xl font-bold ${col} leading-none`;
161
+ document.getElementById('err-big').innerText = d.error.toFixed(4);
162
+
163
+ document.getElementById('pane-logs').innerHTML = d.logs.map(l => `<div>${l}</div>`).join('');
164
+
165
+ let ah = "";
166
+ if(Object.keys(d.type_acc).length > 0) {
167
+ ah += `<table class="w-full text-left border-collapse">
168
+ <tr class="text-slate-600 border-b border-slate-800"><th>Type</th><th>N</th><th>Avg Err</th></tr>`;
169
+ for(const [t, v] of Object.entries(d.type_acc)) {
170
+ ah += `<tr class="border-b border-slate-900/60">
171
+ <td class="py-1 text-orange-300 font-bold">${t}</td>
172
+ <td class="text-slate-400">${v.n}</td>
173
+ <td class="text-white">${v.avg_err}</td>
174
+ </tr>`;
175
+ }
176
+ ah += `</table>`;
177
+ } else { ah = "No inference data yet. Run Batch Inference."; }
178
+ document.getElementById('pane-accuracy').innerHTML = ah;
179
+
180
+ let nh = "";
181
+ ['A','C','B'].forEach(kind => {
182
+ const k_nodes = Object.values(d.nodes).filter(n => n.kind === kind).sort((a,b)=>a.col-b.col);
183
+ if(k_nodes.length === 0) return;
184
+ const vals = k_nodes.map(n => n.x.toFixed(2)).join(', ');
185
+ const color = kind==='A'?'text-orange-400':kind==='B'?'text-purple-400':'text-sky-400';
186
+ nh += `<div class="mb-1"><span class="${color} font-bold w-4 inline-block">${kind}:</span><span class="font-mono text-slate-300 tracking-wider">${vals}</span></div>`;
187
+ });
188
+ document.getElementById('pane-nodes').innerHTML = nh;
189
+
190
+ const traces = [];
191
+ const nodes = Object.values(d.nodes);
192
+
193
+ for(const [key, K] of Object.entries(d.springs)) {
194
+ const parts = key.split('|');
195
+ if (parts.length !== 2) continue;
196
+ if(!d.nodes[parts[0]] || !d.nodes[parts[1]]) continue;
197
+
198
+ const pu = d.nodes[parts[0]].pos, pv = d.nodes[parts[1]].pos;
199
+ // Stiff/Positive = Amber, Repulsive/Negative = Cyan
200
+ const color = K > 0 ? `rgba(217,119,6,${Math.min(K/1.5,1)})` : `rgba(6,182,212,${Math.min(Math.abs(K)/1.5,1)})`;
201
+ traces.push({
202
+ type:'scatter', mode:'lines', x:[pu[0], pv[0]], y:[pu[1], pv[1]],
203
+ line:{ color: color, width: 1.0 + Math.abs(K)*2.0 }, hoverinfo:'none', showlegend:false
204
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  }
 
 
 
 
 
206
 
207
+ traces.push({
208
+ type:'scatter', mode:'markers+text',
209
+ x: nodes.map(n => n.pos[0]), y: nodes.map(n => n.pos[1]),
210
+ text: nodes.map(n => n.x.toFixed(2)),
211
+ textposition: 'top center', textfont: {size: 10, color: '#9ca3af'},
212
+ marker: {
213
+ size: 16, color: nodes.map(n => n.x), colorscale: 'Viridis',
214
+ line: {color: nodes.map(n => ['A','B','C'].includes(n.kind)?'#ef4444':'#171717'), width: 2}
215
+ },
216
+ showlegend:false
217
+ });
218
+
219
+ const layout = {
220
+ margin:{l:5,r:5,t:5,b:5}, paper_bgcolor:'transparent', plot_bgcolor:'transparent',
221
+ xaxis:{visible:false}, yaxis:{visible:false, scaleanchor:'x'},
222
+ };
223
+
224
+ if(!plotted) { Plotly.newPlot('mesh-plot', traces, layout, {responsive:true}); plotted = true; }
225
+ else { Plotly.react('mesh-plot', traces, layout); }
226
+
227
+ if(document.getElementById('err-plot').offsetWidth > 0 && d.hist.length > 0) {
228
+ Plotly.react('err-plot', [{y: d.hist, type:'scatter', line:{color:'#22c55e'}}], {
229
+ margin:{l:30,r:10,t:10,b:15}, paper_bgcolor:'transparent', plot_bgcolor:'transparent',
230
+ xaxis:{visible:false}, yaxis:{gridcolor:'#262626'}
231
+ });
232
+ }
 
 
 
 
233
 
234
+ } catch (e) { showError("UI Error: " + e.message); }
235
+ }, 250);
236
  </script>
237
  </body>
238
  </html>