everydaytok commited on
Commit
20b2d83
·
verified ·
1 Parent(s): 88e9e88

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +229 -152
index.html CHANGED
@@ -15,8 +15,8 @@
15
  .tog.on { color:#fff; }
16
  .tog.off { background:#1e293b !important; color:#64748b; }
17
  .lbtn {
18
- width:22px; height:22px; background:#1e293b; border:1px solid #334155;
19
- border-radius:4px; color:#94a3b8; font-weight:bold; font-size:14px;
20
  cursor:pointer; display:inline-flex; align-items:center; justify-content:center;
21
  }
22
  .lbtn:hover { background:#334155; color:#fff; }
@@ -30,8 +30,13 @@
30
  <span id="b-mode" class="px-1.5 py-0.5 rounded bg-yellow-900/60 text-yellow-300 border border-yellow-800/60">TRAIN</span>
31
  <span id="b-arch" class="px-1.5 py-0.5 rounded bg-blue-900/60 text-blue-300 border border-blue-800/60">ADDIT</span>
32
  <span id="b-alpha" class="px-1.5 py-0.5 rounded bg-orange-900/60 text-orange-300 border border-orange-800/60">α:0.45</span>
 
33
  <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">
34
- <span class="text-orange-400 mr-0.5">U</span>
 
 
 
 
35
  <button class="lbtn" onclick="quickL('upper',-1)">−</button>
36
  <span id="b-upper" class="w-3 text-center text-white">3</span>
37
  <button class="lbtn" onclick="quickL('upper',+1)">+</button>
@@ -43,8 +48,8 @@
43
  <span id="b-data" class="px-1.5 py-0.5 rounded bg-pink-900/60 text-pink-300 border border-pink-800/60">HOUSNG</span>
44
  </div>
45
  <div class="flex items-center gap-2 ml-1">
46
- <span id="q-lbl" class="text-[8px] text-slate-600">Q:0</span>
47
- <div id="run-dot" class="w-2 h-2 rounded-full bg-slate-700"></div>
48
  <button onclick="openDrawer()" class="text-[10px] bg-blue-700 hover:bg-blue-600 px-2 py-1 rounded font-bold">⚙ DIALS</button>
49
  </div>
50
  </header>
@@ -86,14 +91,17 @@
86
 
87
  <div class="grid grid-cols-2 gap-2">
88
 
89
- <!-- MODE -->
90
  <div class="col-span-2 bg-slate-900 rounded p-3 border border-yellow-900/50">
91
- <div class="text-yellow-400 text-[9px] font-bold mb-2">EXECUTION MODE</div>
 
 
 
92
  <div class="flex gap-2">
93
  <button class="tog on flex-1 py-2 rounded text-xs font-bold bg-yellow-700"
94
- onclick="pick('mode','training',this,'bg-yellow-700')">TRAINING</button>
95
  <button class="tog off flex-1 py-2 rounded text-xs font-bold"
96
- onclick="pick('mode','inference',this,'bg-yellow-700')">INFERENCE</button>
97
  </div>
98
  </div>
99
 
@@ -113,33 +121,41 @@
113
  class="w-full accent-orange-500 mt-1"
114
  oninput="document.getElementById('alpha-val').innerText=(this.value/100).toFixed(2)">
115
  <div class="text-center text-orange-300 font-bold text-xl mt-0.5" id="alpha-val">0.45</div>
116
- <div class="text-[8px] text-slate-600 text-center mt-0.5">elastic coupling A↕B</div>
117
  </div>
118
 
119
  <!-- TOPOLOGY -->
120
  <div class="col-span-2 bg-slate-900 rounded p-3 border border-green-900/50">
121
- <div class="text-green-400 text-[9px] font-bold mb-3">
122
- HOURGLASS TOPOLOGY
123
- <span class="text-slate-500 font-normal ml-1">A → [upper] → C ← [lower] ← B</span>
124
  </div>
125
- <div class="grid grid-cols-2 gap-3">
126
- <div class="bg-slate-800 rounded p-3 text-center border border-orange-900/40">
127
- <div class="text-orange-400 text-[8px] mb-1">UPPER BULGE (A-side)</div>
128
- <div class="flex items-center justify-center gap-3">
129
- <button class="lbtn text-base" onclick="chL('upper',-1)">−</button>
130
- <span id="dial-upper" class="text-orange-300 font-bold text-3xl w-8 text-center">3</span>
131
- <button class="lbtn text-base" onclick="chL('upper',+1)">+</button>
 
 
 
 
 
 
 
 
 
132
  </div>
133
- <div class="text-[8px] text-slate-600 mt-1">U nodes</div>
134
  </div>
135
- <div class="bg-slate-800 rounded p-3 text-center border border-cyan-900/40">
136
- <div class="text-cyan-400 text-[8px] mb-1">LOWER BULGE (B-side)</div>
137
- <div class="flex items-center justify-center gap-3">
138
- <button class="lbtn text-base" onclick="chL('lower',-1)">−</button>
139
- <span id="dial-lower" class="text-cyan-300 font-bold text-3xl w-8 text-center">3</span>
140
- <button class="lbtn text-base" onclick="chL('lower',+1)">+</button>
141
  </div>
142
- <div class="text-[8px] text-slate-600 mt-1">L nodes</div>
143
  </div>
144
  </div>
145
  <div id="spring-count" class="text-center text-[8px] text-slate-600 mt-2"></div>
@@ -148,13 +164,13 @@
148
 
149
  <button onclick="applyConfig()"
150
  class="w-full bg-white text-black py-3 rounded font-bold text-sm hover:bg-slate-200">
151
- APPLY CONFIG &amp; RESET MESH
152
  </button>
153
 
154
  <!-- DATASET -->
155
  <div class="bg-slate-900 rounded p-3 border border-pink-900/50">
156
  <div class="text-pink-400 text-[9px] font-bold mb-2">DATASET
157
- <span class="text-slate-600 font-normal ml-1">(hidden from mesh)</span>
158
  </div>
159
  <select id="ds-sel" onchange="refreshDS()"
160
  class="w-full bg-black border border-slate-700 p-2 text-white text-xs rounded mb-2">
@@ -172,26 +188,28 @@
172
  </div>
173
  </div>
174
 
175
- <!-- CUSTOM -->
176
  <div class="bg-slate-900 rounded p-3 border border-cyan-900/50">
177
- <div class="text-cyan-400 text-[9px] font-bold mb-1">CUSTOM INPUT</div>
 
 
178
  <div id="ds-examples" class="text-[9px] text-slate-500 mb-2"></div>
179
  <div class="grid grid-cols-3 gap-2 mb-2">
180
  <div>
181
  <div class="text-[8px] text-slate-600 mb-1">A (top)</div>
182
- <input id="ca" type="number" value="5" step="0.1"
183
  class="w-full bg-black border border-slate-700 p-2 text-white text-sm text-center rounded"
184
  oninput="updateExpected()">
185
  </div>
186
  <div>
187
  <div class="text-[8px] text-slate-600 mb-1">B (bottom)</div>
188
- <input id="cb" type="number" value="3" step="0.1"
189
  class="w-full bg-black border border-slate-700 p-2 text-white text-sm text-center rounded"
190
  oninput="updateExpected()">
191
  </div>
192
  <div>
193
  <div class="text-[8px] text-slate-600 mb-1">C target</div>
194
- <input id="cc" type="number" placeholder="auto" step="0.1"
195
  class="w-full bg-black border border-slate-700 p-2 text-white text-sm text-center rounded">
196
  </div>
197
  </div>
@@ -205,43 +223,64 @@
205
  </aside>
206
 
207
  <script>
208
- // ── STATE ──────────────────────────────────────────────────────────────────────
209
  const cfg = { mode: 'training', architecture: 'additive' };
210
- const topo = { upper: 3, lower: 3 };
211
 
212
- function springCount() {
213
- const { upper: u, lower: l } = topo;
214
- return `Springs: ${u} (A→U) + ${u} (U→C) + ${l} (B→L) + ${l} (L→C) = ${2*u + 2*l} total`;
215
- }
216
  function updateSpringCount() {
217
- document.getElementById('spring-count').innerText = springCount();
 
 
 
 
 
 
 
 
218
  }
219
 
220
  function chL(w, d) {
221
- topo[w] = Math.max(1, Math.min(8, topo[w] + d));
 
222
  document.getElementById(`dial-${w}`).innerText = topo[w];
223
  updateSpringCount();
224
  }
225
 
226
  async function quickL(w, d) {
227
- const res = await fetch('/set_layer', {
228
- method: 'POST', headers: {'Content-Type':'application/json'},
229
  body: JSON.stringify({ layer: w, delta: d })
230
  });
231
  const data = await res.json();
232
- topo.upper = data.n_upper; topo.lower = data.n_lower;
233
- syncTopoUI();
234
  meshPlotted = false;
235
  }
236
 
237
- function syncTopoUI() {
238
- ['upper','lower'].forEach(k => {
239
- document.getElementById(`b-${k}`).innerText = topo[k];
240
- document.getElementById(`dial-${k}`).innerText = topo[k];
241
- });
 
 
 
242
  updateSpringCount();
243
  }
244
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
245
  function pick(key, val, btn, cls) {
246
  cfg[key] = val;
247
  btn.parentElement.querySelectorAll('.tog').forEach(b => {
@@ -260,10 +299,9 @@ async function applyConfig() {
260
  method:'POST', headers:{'Content-Type':'application/json'},
261
  body: JSON.stringify({
262
  ...cfg, dataset: ds, back_alpha: alpha,
263
- n_upper: topo.upper, n_lower: topo.lower
264
  })
265
  });
266
- document.getElementById('b-mode').innerText = cfg.mode === 'training' ? 'TRAIN' : 'INFER';
267
  document.getElementById('b-arch').innerText = cfg.architecture.slice(0,5).toUpperCase();
268
  document.getElementById('b-alpha').innerText = `α:${alpha.toFixed(2)}`;
269
  document.getElementById('b-data').innerText = ds.slice(0,6).toUpperCase();
@@ -297,10 +335,10 @@ async function halt() {
297
 
298
  // ── DATASET META ──────────────────────────────────────────────────────────────
299
  const DS = {
300
- housing: { hint:'A×2.5+B×1.2', fn:(a,b)=>(a*2.5+b*1.2).toFixed(3), ex:[{a:4,b:2},{a:6,b:3}] },
301
- subtraction: { hint:'A−B', fn:(a,b)=>(a-b).toFixed(3), ex:[{a:8,b:3},{a:5,b:5}] },
302
- multiplication:{ hint:'A×B', fn:(a,b)=>(a*b).toFixed(3), ex:[{a:6,b:7},{a:3,b:9}] },
303
- quadratic: { hint:'A²+B', fn:(a,b)=>(a*a+b).toFixed(3), ex:[{a:3,b:2},{a:4,b:5}] },
304
  };
305
 
306
  function refreshDS() {
@@ -319,12 +357,29 @@ function fillC(a, b) {
319
  updateExpected();
320
  }
321
 
 
 
 
 
322
  function updateExpected() {
323
- const a = document.getElementById('ca').value, b = document.getElementById('cb').value;
 
324
  const ds = document.getElementById('ds-sel').value;
325
- if (a && b && DS[ds])
326
- document.getElementById('expected-lbl').innerText =
327
- `Ground truth: ${DS[ds].fn(parseFloat(a), parseFloat(b))}`;
 
 
 
 
 
 
 
 
 
 
 
 
328
  }
329
 
330
  refreshDS();
@@ -341,63 +396,66 @@ function tab(name) {
341
  }
342
 
343
  // ── VISUALIZATION ─────────────────────────────────────────────────────────────
344
- //
345
- // Vertical bilateral hourglass — exact sketch match:
346
- //
347
- // ┌─────────┐ ← A bus (top rectangle, orange)
348
- // └─────────┘
349
- // / U1 U2 U3 \ ← upper bulge (fan-out from A)
350
- // / \
351
- // └──────C──────┘ ← C waist (center rectangle, cyan)
352
- // \ /
353
- // \ L1 L2 L3 / ← lower bulge (fan-in to B)
354
- // ┌─────────┐ ← B bus (bottom rectangle, purple)
355
- // └─────────┘
356
- //
357
- // Y axis: A=+4.4, upper bulge=+2.0, C=0, lower bulge=-2.0, B=-4.4
358
-
359
- const Y = { A: 4.4, U: 2.1, C: 0.0, L: -2.1, B: -4.4 };
360
-
361
- function layerX(ids, spread) {
362
- const n = ids.length;
363
- return ids.map((_, i) => n === 1 ? 0 : spread * (2*i/(n-1) - 1));
364
- }
365
 
366
- function buildPos(layers) {
367
- // layers = [['A'], ['U1'..'Un'], ['C'], ['L1'..'Ln'], ['B']]
368
  const pos = {};
369
- const spreads = [0, 3.8, 0, 3.8, 0];
370
- const ys = [Y.A, Y.U, Y.C, Y.L, Y.B];
 
 
 
 
 
 
371
  layers.forEach((layer, li) => {
372
- const xs = layerX(layer, spreads[li]);
373
- layer.forEach((id, i) => { pos[id] = [xs[i], ys[li]]; });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
374
  });
375
  return pos;
376
  }
377
 
378
- // Bus rectangle shapes
379
- function busShapes(pos) {
380
- const sh = [];
381
- const rect = (xc, yc, hw, hh, fill, stroke) => sh.push({
382
- type: 'rect', xref: 'x', yref: 'y',
383
- x0: xc-hw, x1: xc+hw, y0: yc-hh, y1: yc+hh,
384
- fillcolor: fill, line: { color: stroke, width: 2 }
 
385
  });
386
- rect(0, Y.A, 2.2, 0.35, 'rgba(251,146,60,0.10)', 'rgba(251,146,60,0.65)'); // A bus
387
- rect(0, Y.C, 1.8, 0.32, 'rgba(56,189,248,0.10)', 'rgba(56,189,248,0.70)'); // C waist
388
- rect(0, Y.B, 2.2, 0.35, 'rgba(192,132,252,0.10)','rgba(192,132,252,0.65)'); // B bus
 
389
  return sh;
390
  }
391
 
392
- // Spring color: positive = amber warmth, negative = cool blue
393
  function springColor(k) {
394
  const t = Math.min(Math.abs(k) / 6, 1);
395
- if (k >= 0) return [`rgb(${Math.round(180+t*70)},${Math.round(100+t*80)},30)`, 1.0 + t*3.5];
396
- return [`rgb(30,${Math.round(80+t*100)},${Math.round(140+t*115)})`, 1.0 + t*3.5];
397
  }
398
 
399
- function buildTraces(nodes, springs, layers) {
400
- const pos = buildPos(layers);
401
  const traces = [];
402
 
403
  // Edges
@@ -408,44 +466,41 @@ function buildTraces(nodes, springs, layers) {
408
  traces.push({
409
  type:'scatter', mode:'lines',
410
  x:[pos[u][0], pos[v][0]], y:[pos[u][1], pos[v][1]],
411
- line:{ color: col, width: wd },
412
  hoverinfo:'none', showlegend:false
413
  });
414
  }
415
 
416
- // Nodes
417
  const allN = layers.flat();
418
- const nodeCol = id => {
419
- if (id === 'A') return '#fb923c';
420
- if (id === 'B') return '#c084fc';
421
- if (id === 'C') return '#38bdf8';
422
- if (id.startsWith('U')) return '#4ade80';
423
- return '#67e8f9';
424
  };
 
425
 
426
  traces.push({
427
  type:'scatter', mode:'markers+text',
428
- x: allN.map(id => pos[id][0]),
429
- y: allN.map(id => pos[id][1]),
430
  text: allN.map(id => {
431
- const xv = Number(nodes[id]?.x ?? 0).toFixed(2);
432
- return `${id}\n${xv}`;
433
- }),
434
- textposition: allN.map(id => {
435
- if (id === 'A') return 'top center';
436
- if (id === 'B') return 'bottom center';
437
- return 'top center';
438
  }),
439
- textfont:{ size:9, color: allN.map(id => nodeCol(id)) },
 
440
  marker:{
441
  size: allN.map(id => {
442
- const v = Math.abs(nodes[id]?.vel ?? 0);
443
- const base = ['A','B','C'].includes(id) ? 20 : 13;
444
- return base + Math.min(v * 30, 9);
445
  }),
446
- color: allN.map(id => nodeCol(id)),
447
- opacity: allN.map(id => 0.75 + Math.min(Math.abs(nodes[id]?.vel ?? 0) * 1.8, 0.25)),
448
- line:{ width:2.5, color: allN.map(id => nodes[id]?.anchored ? '#ef4444' : '#22c55e') }
 
 
 
449
  },
450
  hoverinfo:'none', showlegend:false
451
  });
@@ -453,23 +508,24 @@ function buildTraces(nodes, springs, layers) {
453
  return traces;
454
  }
455
 
456
- function meshLayout(layers) {
457
- const pos = buildPos(layers);
 
458
  return {
459
- margin:{ l:8, r:8, t:8, b:8 },
460
  paper_bgcolor:'transparent', plot_bgcolor:'transparent',
461
- xaxis:{ visible:false, range:[-5.5, 5.5] },
462
- yaxis:{ visible:false, range:[-5.5, 5.5] },
463
  showlegend:false,
464
- shapes: busShapes(pos),
465
  };
466
  }
467
 
468
  const ERR_LAYOUT = {
469
- margin:{ l:30, r:6, t:3, b:12 },
470
  paper_bgcolor:'transparent', plot_bgcolor:'transparent',
471
- xaxis:{ visible:false },
472
- yaxis:{ color:'#334155', gridcolor:'#0f172a', zeroline:true, zerolinecolor:'#22c55e', zerolinewidth:1 },
473
  showlegend:false,
474
  };
475
 
@@ -482,11 +538,17 @@ setInterval(async () => {
482
  const d = await r.json();
483
 
484
  // Sync topo
485
- topo.upper = d.n_upper; topo.lower = d.n_lower;
486
- syncTopoUI();
487
  document.getElementById('b-alpha').innerText = `α:${d.back_alpha.toFixed(2)}`;
 
 
 
 
 
 
 
 
488
 
489
- // Header
490
  document.getElementById('run-dot').className =
491
  `w-2 h-2 rounded-full ${d.running ? 'bg-green-400' : 'bg-slate-700'}`;
492
  document.getElementById('q-lbl').innerText = `Q:${d.queue_size}`;
@@ -494,22 +556,37 @@ setInterval(async () => {
494
 
495
  // Tension
496
  const e = Math.abs(d.error);
497
- const col = e < 0.05 ? 'text-green-400' : e < 2 ? 'text-yellow-400' : 'text-red-400';
498
  document.getElementById('err-big').className = `text-xl font-bold ${col} leading-none`;
499
  document.getElementById('err-big').innerText = d.error.toFixed(4);
500
- document.getElementById('pred-val').innerText = `P:${d.prediction.toFixed(3)}`;
501
 
502
- // Nodes paneordered top→bottom
 
 
 
 
 
 
503
  const order = d.layers.flat();
504
  let nh = '';
505
  order.forEach(id => {
506
  const n = d.nodes[id]; if (!n) return;
 
507
  const icon = n.anchored ? '<span class="text-red-400">⊠</span>' : '<span class="text-green-500">◎</span>';
508
- const vel = `<span class="text-slate-700 text-[8px] ml-1">v:${(n.vel||0).toFixed(3)}</span>`;
509
  nh += `<div class="flex justify-between items-center py-0.5 border-b border-slate-900">
510
- ${icon}<span class="ml-1 text-sky-300">${id}</span>
511
- <span class="text-white font-bold">${Number(n.x).toFixed(4)}</span>${vel}</div>`;
 
 
512
  });
 
 
 
 
 
 
 
513
  document.getElementById('pane-nodes').innerHTML = nh;
514
 
515
  // Springs pane
@@ -528,10 +605,10 @@ setInterval(async () => {
528
 
529
  // Mesh plot
530
  const layerKey = JSON.stringify(d.layers);
531
- const traces = buildTraces(d.nodes, d.springs, d.layers);
532
- const layout = meshLayout(d.layers);
533
  if (!meshPlotted || layerKey !== lastLayerKey) {
534
- Plotly.newPlot('mesh-plot', traces, layout, { displayModeBar:false, responsive:true });
535
  meshPlotted = true; lastLayerKey = layerKey;
536
  } else {
537
  Plotly.react('mesh-plot', traces, layout);
@@ -542,14 +619,14 @@ setInterval(async () => {
542
  const eTrace = {
543
  type:'scatter', mode:'lines',
544
  x: hist.map((_,i)=>i), y: hist,
545
- line:{ color:'#f97316', width:1.5 },
546
  fill:'tozeroy', fillcolor:'rgba(249,115,22,0.07)'
547
  };
548
  if (!errPlotted) {
549
- Plotly.newPlot('err-plot', [eTrace], ERR_LAYOUT, { displayModeBar:false, responsive:true });
550
  errPlotted = true;
551
  } else {
552
- Plotly.react('err-plot', [eTrace], ERR_LAYOUT);
553
  }
554
 
555
  } catch(e) { /* silent */ }
 
15
  .tog.on { color:#fff; }
16
  .tog.off { background:#1e293b !important; color:#64748b; }
17
  .lbtn {
18
+ width:20px; height:20px; background:#1e293b; border:1px solid #334155;
19
+ border-radius:3px; color:#94a3b8; font-weight:bold; font-size:12px;
20
  cursor:pointer; display:inline-flex; align-items:center; justify-content:center;
21
  }
22
  .lbtn:hover { background:#334155; color:#fff; }
 
30
  <span id="b-mode" class="px-1.5 py-0.5 rounded bg-yellow-900/60 text-yellow-300 border border-yellow-800/60">TRAIN</span>
31
  <span id="b-arch" class="px-1.5 py-0.5 rounded bg-blue-900/60 text-blue-300 border border-blue-800/60">ADDIT</span>
32
  <span id="b-alpha" class="px-1.5 py-0.5 rounded bg-orange-900/60 text-orange-300 border border-orange-800/60">α:0.45</span>
33
+ <!-- Topology inline controls: D · U · L -->
34
  <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">
35
+ <span class="text-yellow-300 mr-0.5">D</span>
36
+ <button class="lbtn" onclick="quickL('inputs',-1)">−</button>
37
+ <span id="b-inputs" class="w-3 text-center text-white">1</span>
38
+ <button class="lbtn" onclick="quickL('inputs',+1)">+</button>
39
+ <span class="text-orange-400 mx-1">U</span>
40
  <button class="lbtn" onclick="quickL('upper',-1)">−</button>
41
  <span id="b-upper" class="w-3 text-center text-white">3</span>
42
  <button class="lbtn" onclick="quickL('upper',+1)">+</button>
 
48
  <span id="b-data" class="px-1.5 py-0.5 rounded bg-pink-900/60 text-pink-300 border border-pink-800/60">HOUSNG</span>
49
  </div>
50
  <div class="flex items-center gap-2 ml-1">
51
+ <span id="q-lbl" class="text-[8px] text-slate-600">Q:0</span>
52
+ <div id="run-dot" class="w-2 h-2 rounded-full bg-slate-700"></div>
53
  <button onclick="openDrawer()" class="text-[10px] bg-blue-700 hover:bg-blue-600 px-2 py-1 rounded font-bold">⚙ DIALS</button>
54
  </div>
55
  </header>
 
91
 
92
  <div class="grid grid-cols-2 gap-2">
93
 
94
+ <!-- MODE — immediate, no mesh rebuild -->
95
  <div class="col-span-2 bg-slate-900 rounded p-3 border border-yellow-900/50">
96
+ <div class="text-yellow-400 text-[9px] font-bold mb-1">
97
+ EXECUTION MODE
98
+ <span class="text-slate-600 font-normal ml-1">switches instantly, springs kept</span>
99
+ </div>
100
  <div class="flex gap-2">
101
  <button class="tog on flex-1 py-2 rounded text-xs font-bold bg-yellow-700"
102
+ onclick="setMode('training',this,'bg-yellow-700')">TRAINING</button>
103
  <button class="tog off flex-1 py-2 rounded text-xs font-bold"
104
+ onclick="setMode('inference',this,'bg-yellow-700')">INFERENCE</button>
105
  </div>
106
  </div>
107
 
 
121
  class="w-full accent-orange-500 mt-1"
122
  oninput="document.getElementById('alpha-val').innerText=(this.value/100).toFixed(2)">
123
  <div class="text-center text-orange-300 font-bold text-xl mt-0.5" id="alpha-val">0.45</div>
 
124
  </div>
125
 
126
  <!-- TOPOLOGY -->
127
  <div class="col-span-2 bg-slate-900 rounded p-3 border border-green-900/50">
128
+ <div class="text-green-400 text-[9px] font-bold mb-2">
129
+ TOPOLOGY
130
+ <span class="text-slate-500 font-normal ml-1">A[D] → [D×U] → C[D] ← [D×L] ← B[D]</span>
131
  </div>
132
+ <div class="grid grid-cols-3 gap-2">
133
+ <div class="bg-slate-800 rounded p-2 text-center border border-yellow-900/40">
134
+ <div class="text-yellow-400 text-[8px] mb-1">DIMENSIONS (D)</div>
135
+ <div class="flex items-center justify-center gap-2">
136
+ <button class="lbtn text-sm" onclick="chL('inputs',-1)">−</button>
137
+ <span id="dial-inputs" class="text-yellow-300 font-bold text-2xl w-8 text-center">1</span>
138
+ <button class="lbtn text-sm" onclick="chL('inputs',+1)">+</button>
139
+ </div>
140
+ <div class="text-[7px] text-slate-600 mt-1">max 8</div>
141
+ </div>
142
+ <div class="bg-slate-800 rounded p-2 text-center border border-orange-900/40">
143
+ <div class="text-orange-400 text-[8px] mb-1">UPPER (A-side)</div>
144
+ <div class="flex items-center justify-center gap-2">
145
+ <button class="lbtn text-sm" onclick="chL('upper',-1)">−</button>
146
+ <span id="dial-upper" class="text-orange-300 font-bold text-2xl w-8 text-center">3</span>
147
+ <button class="lbtn text-sm" onclick="chL('upper',+1)">+</button>
148
  </div>
149
+ <div class="text-[7px] text-slate-600 mt-1">per dim</div>
150
  </div>
151
+ <div class="bg-slate-800 rounded p-2 text-center border border-cyan-900/40">
152
+ <div class="text-cyan-400 text-[8px] mb-1">LOWER (B-side)</div>
153
+ <div class="flex items-center justify-center gap-2">
154
+ <button class="lbtn text-sm" onclick="chL('lower',-1)">−</button>
155
+ <span id="dial-lower" class="text-cyan-300 font-bold text-2xl w-8 text-center">3</span>
156
+ <button class="lbtn text-sm" onclick="chL('lower',+1)">+</button>
157
  </div>
158
+ <div class="text-[7px] text-slate-600 mt-1">per dim</div>
159
  </div>
160
  </div>
161
  <div id="spring-count" class="text-center text-[8px] text-slate-600 mt-2"></div>
 
164
 
165
  <button onclick="applyConfig()"
166
  class="w-full bg-white text-black py-3 rounded font-bold text-sm hover:bg-slate-200">
167
+ APPLY &amp; REBUILD TOPOLOGY
168
  </button>
169
 
170
  <!-- DATASET -->
171
  <div class="bg-slate-900 rounded p-3 border border-pink-900/50">
172
  <div class="text-pink-400 text-[9px] font-bold mb-2">DATASET
173
+ <span class="text-slate-600 font-normal ml-1">(applied per dimension)</span>
174
  </div>
175
  <select id="ds-sel" onchange="refreshDS()"
176
  class="w-full bg-black border border-slate-700 p-2 text-white text-xs rounded mb-2">
 
188
  </div>
189
  </div>
190
 
191
+ <!-- CUSTOM INPUT — accepts comma-separated for n>1 -->
192
  <div class="bg-slate-900 rounded p-3 border border-cyan-900/50">
193
+ <div class="text-cyan-400 text-[9px] font-bold mb-1">CUSTOM INPUT
194
+ <span id="custom-dim-hint" class="text-slate-600 font-normal ml-1">(D=1: single value)</span>
195
+ </div>
196
  <div id="ds-examples" class="text-[9px] text-slate-500 mb-2"></div>
197
  <div class="grid grid-cols-3 gap-2 mb-2">
198
  <div>
199
  <div class="text-[8px] text-slate-600 mb-1">A (top)</div>
200
+ <input id="ca" type="text" value="5"
201
  class="w-full bg-black border border-slate-700 p-2 text-white text-sm text-center rounded"
202
  oninput="updateExpected()">
203
  </div>
204
  <div>
205
  <div class="text-[8px] text-slate-600 mb-1">B (bottom)</div>
206
+ <input id="cb" type="text" value="3"
207
  class="w-full bg-black border border-slate-700 p-2 text-white text-sm text-center rounded"
208
  oninput="updateExpected()">
209
  </div>
210
  <div>
211
  <div class="text-[8px] text-slate-600 mb-1">C target</div>
212
+ <input id="cc" type="text" placeholder="auto"
213
  class="w-full bg-black border border-slate-700 p-2 text-white text-sm text-center rounded">
214
  </div>
215
  </div>
 
223
  </aside>
224
 
225
  <script>
226
+ // ── LOCAL STATE ────────────────────────────────────────────────────────────────
227
  const cfg = { mode: 'training', architecture: 'additive' };
228
+ const topo = { inputs: 1, upper: 3, lower: 3 };
229
 
 
 
 
 
230
  function updateSpringCount() {
231
+ const { inputs: n, upper: u, lower: l } = topo;
232
+ const total = n * (2*u + 2*l);
233
+ document.getElementById('spring-count').innerText =
234
+ `D=${n}: ${n}×(${u}+${u}+${l}+${l}) = ${total} springs total`;
235
+ // Update custom input placeholder hints
236
+ const hint = n === 1
237
+ ? '(D=1: single value)'
238
+ : `(D=${n}: comma-separated, e.g. 5,3,7)`;
239
+ document.getElementById('custom-dim-hint').innerText = hint;
240
  }
241
 
242
  function chL(w, d) {
243
+ const lim = { inputs:[1,8], upper:[1,16], lower:[1,16] };
244
+ topo[w] = Math.max(lim[w][0], Math.min(lim[w][1], topo[w]+d));
245
  document.getElementById(`dial-${w}`).innerText = topo[w];
246
  updateSpringCount();
247
  }
248
 
249
  async function quickL(w, d) {
250
+ const res = await fetch('/set_layer', {
251
+ method:'POST', headers:{'Content-Type':'application/json'},
252
  body: JSON.stringify({ layer: w, delta: d })
253
  });
254
  const data = await res.json();
255
+ syncTopoUI(data.n_inputs, data.n_upper, data.n_lower);
 
256
  meshPlotted = false;
257
  }
258
 
259
+ function syncTopoUI(ni, nu, nl) {
260
+ topo.inputs = ni; topo.upper = nu; topo.lower = nl;
261
+ document.getElementById('b-inputs').innerText = ni;
262
+ document.getElementById('b-upper').innerText = nu;
263
+ document.getElementById('b-lower').innerText = nl;
264
+ document.getElementById('dial-inputs').innerText = ni;
265
+ document.getElementById('dial-upper').innerText = nu;
266
+ document.getElementById('dial-lower').innerText = nl;
267
  updateSpringCount();
268
  }
269
 
270
+ // Mode toggle — immediate, no mesh rebuild
271
+ async function setMode(m, btn, cls) {
272
+ cfg.mode = m;
273
+ btn.parentElement.querySelectorAll('.tog').forEach(b => {
274
+ b.classList.add('off'); b.classList.remove('on', cls);
275
+ });
276
+ btn.classList.remove('off'); btn.classList.add('on', cls);
277
+ await fetch('/set_mode', {
278
+ method:'POST', headers:{'Content-Type':'application/json'},
279
+ body: JSON.stringify({ mode: m })
280
+ });
281
+ document.getElementById('b-mode').innerText = m === 'training' ? 'TRAIN' : 'INFER';
282
+ }
283
+
284
  function pick(key, val, btn, cls) {
285
  cfg[key] = val;
286
  btn.parentElement.querySelectorAll('.tog').forEach(b => {
 
299
  method:'POST', headers:{'Content-Type':'application/json'},
300
  body: JSON.stringify({
301
  ...cfg, dataset: ds, back_alpha: alpha,
302
+ n_inputs: topo.inputs, n_upper: topo.upper, n_lower: topo.lower
303
  })
304
  });
 
305
  document.getElementById('b-arch').innerText = cfg.architecture.slice(0,5).toUpperCase();
306
  document.getElementById('b-alpha').innerText = `α:${alpha.toFixed(2)}`;
307
  document.getElementById('b-data').innerText = ds.slice(0,6).toUpperCase();
 
335
 
336
  // ── DATASET META ──────────────────────────────────────────────────────────────
337
  const DS = {
338
+ housing: { hint:'A×2.5+B×1.2 per dimension', fn:(a,b)=>(a*2.5+b*1.2).toFixed(3), ex:[{a:4,b:2},{a:6,b:3}] },
339
+ subtraction: { hint:'A−B per dimension', fn:(a,b)=>(a-b).toFixed(3), ex:[{a:8,b:3},{a:5,b:5}] },
340
+ multiplication:{ hint:'A×B per dimension', fn:(a,b)=>(a*b).toFixed(3), ex:[{a:6,b:7},{a:3,b:9}] },
341
+ quadratic: { hint:'A²+B per dimension', fn:(a,b)=>(a*a+b).toFixed(3), ex:[{a:3,b:2},{a:4,b:5}] },
342
  };
343
 
344
  function refreshDS() {
 
357
  updateExpected();
358
  }
359
 
360
+ function parseInputVals(str) {
361
+ return String(str).split(',').map(s => parseFloat(s.trim())).filter(x => !isNaN(x));
362
+ }
363
+
364
  function updateExpected() {
365
+ const a = document.getElementById('ca').value;
366
+ const b = document.getElementById('cb').value;
367
  const ds = document.getElementById('ds-sel').value;
368
+ if (!a || !b || !DS[ds]) return;
369
+ const fn = DS[ds].fn;
370
+ const aVals = parseInputVals(a);
371
+ const bVals = parseInputVals(b);
372
+ if (!aVals.length || !bVals.length) return;
373
+ const n = Math.max(topo.inputs, 1);
374
+ const results = [];
375
+ for (let i = 0; i < n; i++) {
376
+ const av = aVals[i % aVals.length];
377
+ const bv = bVals[i % bVals.length];
378
+ results.push(fn(av, bv));
379
+ }
380
+ document.getElementById('expected-lbl').innerText =
381
+ n === 1 ? `Ground truth: ${results[0]}`
382
+ : `Ground truth: [${results.join(', ')}]`;
383
  }
384
 
385
  refreshDS();
 
396
  }
397
 
398
  // ── VISUALIZATION ─────────────────────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
399
 
400
+ function buildPos(layers, n_inputs, n_upper, n_lower) {
 
401
  const pos = {};
402
+ const Y = [4.4, 2.1, 0.0, -2.1, -4.4];
403
+
404
+ // Column spacing — for n=1 centered, for n>1 evenly spread
405
+ const COL_W = n_inputs === 1 ? 0 : Math.min(9.0 / (n_inputs - 1), 4.2);
406
+ const halfSpan = COL_W * (n_inputs - 1) / 2;
407
+ // Within-column spread for bulge nodes
408
+ const bulgeSprd = n_inputs === 1 ? 3.8 : Math.min(COL_W * 0.58, 2.0);
409
+
410
  layers.forEach((layer, li) => {
411
+ const y = Y[li];
412
+ layer.forEach(nid => {
413
+ const kind = nid[0]; // A U C L B
414
+ let dim = 1;
415
+ if (kind === 'A' || kind === 'B' || kind === 'C') {
416
+ dim = parseInt(nid.slice(1));
417
+ } else {
418
+ dim = parseInt(nid.slice(1).split('_')[0]);
419
+ }
420
+ const cx = n_inputs === 1 ? 0 : -halfSpan + (dim - 1) * COL_W;
421
+
422
+ if (kind === 'A' || kind === 'B' || kind === 'C') {
423
+ pos[nid] = [cx, y];
424
+ } else {
425
+ const j = parseInt(nid.slice(1).split('_')[1]);
426
+ const total = kind === 'U' ? n_upper : n_lower;
427
+ const t = total === 1 ? 0 : (2*(j-1)/(total-1) - 1);
428
+ pos[nid] = [cx + bulgeSprd * t, y];
429
+ }
430
+ });
431
  });
432
  return pos;
433
  }
434
 
435
+ function busShapes(pos, n_inputs) {
436
+ const sh = [];
437
+ const mg = n_inputs === 1 ? 1.4 : 0.8;
438
+ const xs = k => Object.entries(pos).filter(([id])=>id[0]===k).map(([,v])=>v[0]);
439
+ const rect = (mn, mx, yc, hh, fill, stroke) => sh.push({
440
+ type:'rect', xref:'x', yref:'y',
441
+ x0:mn-mg, x1:mx+mg, y0:yc-hh, y1:yc+hh,
442
+ fillcolor:fill, line:{color:stroke, width:2}
443
  });
444
+ const aXs = xs('A'), cXs = xs('C'), bXs = xs('B');
445
+ rect(Math.min(...aXs), Math.max(...aXs), 4.4, 0.35, 'rgba(251,146,60,0.10)', 'rgba(251,146,60,0.65)');
446
+ rect(Math.min(...cXs), Math.max(...cXs), 0.0, 0.32, 'rgba(56,189,248,0.10)', 'rgba(56,189,248,0.70)');
447
+ rect(Math.min(...bXs), Math.max(...bXs), -4.4, 0.35, 'rgba(192,132,252,0.10)','rgba(192,132,252,0.65)');
448
  return sh;
449
  }
450
 
 
451
  function springColor(k) {
452
  const t = Math.min(Math.abs(k) / 6, 1);
453
+ if (k >= 0) return [`rgb(${Math.round(180+t*70)},${Math.round(100+t*80)},30)`, 1.0+t*3.5];
454
+ return [`rgb(30,${Math.round(80+t*100)},${Math.round(140+t*115)})`, 1.0+t*3.5];
455
  }
456
 
457
+ function buildTraces(nodes, springs, layers, n_inputs, n_upper, n_lower) {
458
+ const pos = buildPos(layers, n_inputs, n_upper, n_lower);
459
  const traces = [];
460
 
461
  // Edges
 
466
  traces.push({
467
  type:'scatter', mode:'lines',
468
  x:[pos[u][0], pos[v][0]], y:[pos[u][1], pos[v][1]],
469
+ line:{color:col, width:wd},
470
  hoverinfo:'none', showlegend:false
471
  });
472
  }
473
 
474
+ // Nodes — show labels only for A/B/C; hidden nodes are plain dots
475
  const allN = layers.flat();
476
+ const NCOL = id => {
477
+ const k = id[0];
478
+ return k==='A'?'#fb923c': k==='B'?'#c084fc': k==='C'?'#38bdf8':
479
+ k==='U'?'#4ade80': '#67e8f9';
 
 
480
  };
481
+ const isIO = id => 'ABC'.includes(id[0]);
482
 
483
  traces.push({
484
  type:'scatter', mode:'markers+text',
485
+ x: allN.map(id => pos[id]?.[0] ?? 0),
486
+ y: allN.map(id => pos[id]?.[1] ?? 0),
487
  text: allN.map(id => {
488
+ if (!isIO(id)) return '';
489
+ return `${id}\n${Number(nodes[id]?.x ?? 0).toFixed(2)}`;
 
 
 
 
 
490
  }),
491
+ textposition: allN.map(id => id[0]==='B' ? 'bottom center' : 'top center'),
492
+ textfont:{ size:9, color: allN.map(id => NCOL(id)) },
493
  marker:{
494
  size: allN.map(id => {
495
+ const v = Math.abs(nodes[id]?.vel ?? 0);
496
+ return (isIO(id) ? 18 : 10) + Math.min(v*30, 8);
 
497
  }),
498
+ color: allN.map(id => NCOL(id)),
499
+ opacity: allN.map(id => 0.75 + Math.min(Math.abs(nodes[id]?.vel??0)*1.8, 0.25)),
500
+ line:{
501
+ width:2.5,
502
+ color: allN.map(id => nodes[id]?.anchored ? '#ef4444' : '#22c55e')
503
+ }
504
  },
505
  hoverinfo:'none', showlegend:false
506
  });
 
508
  return traces;
509
  }
510
 
511
+ function meshLayout(layers, n_inputs, n_upper, n_lower) {
512
+ const pos = buildPos(layers, n_inputs, n_upper, n_lower);
513
+ const xMax = Math.max(5.5, n_inputs * 2.8);
514
  return {
515
+ margin:{l:8,r:8,t:8,b:8},
516
  paper_bgcolor:'transparent', plot_bgcolor:'transparent',
517
+ xaxis:{visible:false, range:[-xMax, xMax]},
518
+ yaxis:{visible:false, range:[-5.5, 5.5]},
519
  showlegend:false,
520
+ shapes: busShapes(pos, n_inputs),
521
  };
522
  }
523
 
524
  const ERR_LAYOUT = {
525
+ margin:{l:30,r:6,t:3,b:12},
526
  paper_bgcolor:'transparent', plot_bgcolor:'transparent',
527
+ xaxis:{visible:false},
528
+ yaxis:{color:'#334155', gridcolor:'#0f172a', zeroline:true, zerolinecolor:'#22c55e', zerolinewidth:1},
529
  showlegend:false,
530
  };
531
 
 
538
  const d = await r.json();
539
 
540
  // Sync topo
541
+ syncTopoUI(d.n_inputs, d.n_upper, d.n_lower);
 
542
  document.getElementById('b-alpha').innerText = `α:${d.back_alpha.toFixed(2)}`;
543
+ document.getElementById('b-data').innerText = (d.dataset_type||'').slice(0,6).toUpperCase();
544
+
545
+ // Mode badge — reflect actual server state
546
+ const mBadge = document.getElementById('b-mode');
547
+ mBadge.innerText = d.mode === 'training' ? 'TRAIN' : 'INFER';
548
+ mBadge.className = d.mode === 'training'
549
+ ? 'px-1.5 py-0.5 rounded bg-yellow-900/60 text-yellow-300 border border-yellow-800/60 text-[8px] font-bold'
550
+ : 'px-1.5 py-0.5 rounded bg-green-900/60 text-green-300 border border-green-800/60 text-[8px] font-bold';
551
 
 
552
  document.getElementById('run-dot').className =
553
  `w-2 h-2 rounded-full ${d.running ? 'bg-green-400' : 'bg-slate-700'}`;
554
  document.getElementById('q-lbl').innerText = `Q:${d.queue_size}`;
 
556
 
557
  // Tension
558
  const e = Math.abs(d.error);
559
+ const col = e < 0.02 ? 'text-green-400' : e < 2 ? 'text-yellow-400' : 'text-red-400';
560
  document.getElementById('err-big').className = `text-xl font-bold ${col} leading-none`;
561
  document.getElementById('err-big').innerText = d.error.toFixed(4);
 
562
 
563
+ // Prediction displayshow all C values for multi-dim
564
+ const preds = d.predictions || [d.prediction];
565
+ document.getElementById('pred-val').innerText =
566
+ preds.length === 1 ? `P:${preds[0].toFixed(3)}`
567
+ : `P:[${preds.map(v=>v.toFixed(2)).join(',')}]`;
568
+
569
+ // Nodes pane — A/B/C nodes first, then a summary of hidden
570
  const order = d.layers.flat();
571
  let nh = '';
572
  order.forEach(id => {
573
  const n = d.nodes[id]; if (!n) return;
574
+ if (!'ABC'.includes(id[0])) return; // skip hidden in main list
575
  const icon = n.anchored ? '<span class="text-red-400">⊠</span>' : '<span class="text-green-500">◎</span>';
576
+ const COLS = {A:'#fb923c',B:'#c084fc',C:'#38bdf8'};
577
  nh += `<div class="flex justify-between items-center py-0.5 border-b border-slate-900">
578
+ ${icon}<span class="ml-1 font-bold" style="color:${COLS[id[0]]}">${id}</span>
579
+ <span class="text-white font-bold">${Number(n.x).toFixed(4)}</span>
580
+ <span class="text-slate-700 text-[8px]">v:${(n.vel||0).toFixed(3)}</span>
581
+ </div>`;
582
  });
583
+ // Hidden nodes summary
584
+ const hids = order.filter(id => 'UL'.includes(id[0]));
585
+ if (hids.length) {
586
+ const avgV = hids.reduce((s,id) => s + Math.abs(d.nodes[id]?.vel||0), 0) / hids.length;
587
+ nh += `<div class="text-[8px] text-slate-600 py-0.5 border-b border-slate-900">
588
+ ${hids.length} hidden — avg|vel|=${avgV.toFixed(3)}</div>`;
589
+ }
590
  document.getElementById('pane-nodes').innerHTML = nh;
591
 
592
  // Springs pane
 
605
 
606
  // Mesh plot
607
  const layerKey = JSON.stringify(d.layers);
608
+ const traces = buildTraces(d.nodes, d.springs, d.layers, d.n_inputs, d.n_upper, d.n_lower);
609
+ const layout = meshLayout(d.layers, d.n_inputs, d.n_upper, d.n_lower);
610
  if (!meshPlotted || layerKey !== lastLayerKey) {
611
+ Plotly.newPlot('mesh-plot', traces, layout, {displayModeBar:false, responsive:true});
612
  meshPlotted = true; lastLayerKey = layerKey;
613
  } else {
614
  Plotly.react('mesh-plot', traces, layout);
 
619
  const eTrace = {
620
  type:'scatter', mode:'lines',
621
  x: hist.map((_,i)=>i), y: hist,
622
+ line:{color:'#f97316',width:1.5},
623
  fill:'tozeroy', fillcolor:'rgba(249,115,22,0.07)'
624
  };
625
  if (!errPlotted) {
626
+ Plotly.newPlot('err-plot',[eTrace],ERR_LAYOUT,{displayModeBar:false,responsive:true});
627
  errPlotted = true;
628
  } else {
629
+ Plotly.react('err-plot',[eTrace],ERR_LAYOUT);
630
  }
631
 
632
  } catch(e) { /* silent */ }