everydaytok commited on
Commit
8165cb4
·
verified ·
1 Parent(s): 1f87580

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +59 -111
index.html CHANGED
@@ -24,7 +24,6 @@
24
  </head>
25
  <body class="flex flex-col h-screen overflow-hidden">
26
 
27
- <!-- ── HEADER ──────────────────────────────────────────────────────────────── -->
28
  <header class="glass flex-shrink-0 px-2 py-1.5 flex justify-between items-center">
29
  <div class="flex flex-wrap gap-1 text-[8px] font-bold items-center">
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>
@@ -34,7 +33,6 @@
34
  <span id="b-cred" class="px-1.5 py-0.5 rounded bg-fuchsia-900/60 text-fuchsia-300 border border-fuchsia-800/60">CREDIT</span>
35
  <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>
36
 
37
- <!-- D · U · L quick buttons -->
38
  <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">
39
  <span class="text-yellow-300 mr-0.5">D</span>
40
  <button class="lbtn" onclick="quickL('inputs',-1)">−</button>
@@ -50,7 +48,6 @@
50
  <button class="lbtn" onclick="quickL('lower',+1)">+</button>
51
  </span>
52
 
53
- <!-- STIFFNESS INDICATOR -->
54
  <div class="flex items-center gap-1.5 px-2 border-l border-slate-700 ml-2" title="Percentage of springs actively changing">
55
  <span class="text-[7px] text-slate-500 uppercase tracking-wider">Learning</span>
56
  <div class="w-16 bg-slate-900 rounded-sm h-2 overflow-hidden border border-slate-700">
@@ -67,13 +64,11 @@
67
  </div>
68
  </header>
69
 
70
- <!-- ── PLOTS ────────────────────────────────────────────────────────────────── -->
71
  <div class="flex-grow flex flex-col min-h-0 overflow-hidden">
72
  <div id="mesh-plot" class="flex-grow min-h-0"></div>
73
  <div id="err-plot" style="height:58px" class="flex-shrink-0 border-t border-slate-900"></div>
74
  </div>
75
 
76
- <!-- ── BOTTOM PANEL ─────────────────────────────────────────────────────────── -->
77
  <div class="glass flex-shrink-0 border-t border-slate-800" style="height:174px">
78
  <div class="flex border-b border-slate-800 text-[10px]">
79
  <button onclick="tab('nodes')" id="tab-nodes" class="flex-1 py-1.5 bg-blue-900/40 text-blue-300 font-bold">UNITS</button>
@@ -104,18 +99,14 @@
104
 
105
  <div class="grid grid-cols-2 gap-2">
106
 
107
- <!-- MODE -->
108
  <div class="col-span-2 bg-slate-900 rounded p-3 border border-yellow-900/50">
109
  <div class="text-yellow-400 text-[9px] font-bold mb-1">EXECUTION MODE</div>
110
  <div class="flex gap-2">
111
- <button class="tog on flex-1 py-2 rounded text-xs font-bold bg-yellow-700"
112
- onclick="setMode('training',this,'bg-yellow-700')">TRAINING</button>
113
- <button class="tog off flex-1 py-2 rounded text-xs font-bold"
114
- onclick="setMode('inference',this,'bg-yellow-700')">INFERENCE</button>
115
  </div>
116
  </div>
117
 
118
- <!-- STACK TOPOLOGY -->
119
  <div class="col-span-2 bg-slate-900 rounded p-3 border border-emerald-900/50">
120
  <div class="text-emerald-400 text-[9px] font-bold mb-2">ARCHITECTURE TOPOLOGY (Dimensionality)</div>
121
  <div class="flex gap-2 items-center mb-1">
@@ -133,7 +124,6 @@
133
  <div id="topo-hint" class="text-[8px] text-emerald-500 italic mt-1 text-center">Flat mode: D independent variables.</div>
134
  </div>
135
 
136
- <!-- CREDIT ASSIGNMENT -->
137
  <div class="col-span-2 bg-slate-900 rounded p-3 border border-fuchsia-900/50">
138
  <div class="text-fuchsia-400 text-[9px] font-bold mb-2">TRAINING BEHAVIOR (How errors fix springs)</div>
139
  <select id="cfg-cred" class="w-full bg-black border border-slate-700 p-2 text-white text-xs rounded">
@@ -144,24 +134,18 @@
144
  <div class="text-[8px] text-slate-500 mt-1 italic text-center">Controls how the model learns when stacking multiple hourglasses.</div>
145
  </div>
146
 
147
- <!-- ARCH & ALPHA -->
148
  <div class="bg-slate-900 rounded p-3 border border-blue-900/50">
149
  <div class="text-blue-400 text-[9px] font-bold mb-2">NODE ACTIVATION</div>
150
- <button class="tog on w-full mb-1 py-2 rounded text-[10px] font-bold bg-blue-700"
151
- onclick="pick('architecture','additive',this,'bg-blue-700')">ADDITIVE Σ</button>
152
- <button class="tog off w-full py-2 rounded text-[10px] font-bold"
153
- onclick="pick('architecture','multiplicative',this,'bg-blue-700')">MULTIPLICATIVE Π</button>
154
  </div>
155
 
156
  <div class="bg-slate-900 rounded p-3 border border-orange-900/50">
157
  <div class="text-orange-400 text-[9px] font-bold mb-1">BACK-TENSION α</div>
158
- <input id="alpha-sl" type="range" min="0" max="100" value="45" step="5"
159
- class="w-full accent-orange-500 mt-1"
160
- oninput="document.getElementById('alpha-val').innerText=(this.value/100).toFixed(2)">
161
  <div class="text-center text-orange-300 font-bold text-xl mt-0.5" id="alpha-val">0.45</div>
162
  </div>
163
 
164
- <!-- H-BULGES -->
165
  <div class="col-span-2 bg-slate-900 rounded p-2 text-center border border-slate-700 grid grid-cols-2 gap-2">
166
  <div class="bg-slate-800 rounded p-2 text-center border border-orange-900/40">
167
  <div class="text-orange-400 text-[8px] mb-1">UPPER ROW (U)</div>
@@ -182,14 +166,11 @@
182
  </div>
183
  </div>
184
 
185
- <button onclick="applyConfig()"
186
- class="w-full bg-white text-black py-3 rounded font-bold text-sm hover:bg-slate-200">
187
  APPLY &amp; REBUILD TOPOLOGY
188
  </button>
189
 
190
- <!-- DATASET & CUSTOM -->
191
  <div class="grid grid-cols-2 gap-2 mt-2">
192
- <!-- BATCH GEN -->
193
  <div class="bg-slate-900 rounded p-3 border border-pink-900/50">
194
  <div class="text-pink-400 text-[9px] font-bold mb-2">DATASET</div>
195
  <select id="ds-sel" onchange="refreshDS()" class="w-full bg-black border border-slate-700 p-2 text-white text-xs rounded mb-2">
@@ -204,7 +185,6 @@
204
  </div>
205
  </div>
206
 
207
- <!-- CUSTOM INPUT VALIDATOR -->
208
  <div class="bg-slate-900 rounded p-3 border border-cyan-900/50 flex flex-col">
209
  <div class="text-cyan-400 text-[9px] font-bold mb-1">CUSTOM INPUT PREVIEW</div>
210
  <div id="ds-examples" class="text-[9px] text-slate-500 mb-2">e.g. click me</div>
@@ -215,7 +195,6 @@
215
  <input id="cc" type="text" placeholder="auto" class="w-full bg-black border border-slate-700 p-1.5 text-white text-xs text-center rounded" title="Optional Target C">
216
  </div>
217
 
218
- <!-- Crucial Realtime Validator Text -->
219
  <div id="expected-lbl" class="text-[9px] text-yellow-400 font-bold mb-2 min-h-[14px] text-center flex-grow flex items-center justify-center">
220
  Ground truth: waiting...
221
  </div>
@@ -228,11 +207,9 @@
228
  </aside>
229
 
230
  <script>
231
- // ── STATE ──────────────────────────────────────────────────────────────────────
232
  const cfg = { mode: 'training', architecture: 'additive' };
233
  const topo = { inputs: 1, upper: 3, lower: 3, stack: 0 };
234
 
235
- // ── DATASET LOGIC FOR PREVIEW ────────────────────────────────────────────────
236
  const DS = {
237
  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}] },
238
  subtraction: { hint:'A−B', fn:(a,b)=>(a-b).toFixed(3), ex:[{a:8,b:3},{a:5,b:5}] },
@@ -240,21 +217,26 @@ const DS = {
240
  quadratic: { hint:'A²+B', fn:(a,b)=>(a*a+b).toFixed(3), ex:[{a:3,b:2},{a:4,b:5}] },
241
  };
242
 
243
- function refreshDS() {
244
  const ds = document.getElementById('ds-sel').value;
245
  const m = DS[ds];
246
  document.getElementById('ds-examples').innerHTML = 'e.g. ' +
247
  m.ex.map(e => `<span class="cursor-pointer text-cyan-500 underline"
248
  onclick="fillC('${e.a}','${e.b}')">A=${e.a} B=${e.b}</span>`).join(' ');
249
  updateExpected();
 
 
 
 
 
 
250
  }
251
 
252
  function fillC(a, b) {
253
- const isStacked = parseInt(document.getElementById('cfg-stack').value) > 0;
254
- const n = isStacked ? Math.pow(2, parseInt(document.getElementById('cfg-stack').value)) : parseInt(document.getElementById('cfg-inputs').value);
255
 
256
  if (n > 1) {
257
- // Fill with comma separated values if n > 1
258
  document.getElementById('ca').value = Array(n).fill(a).join(',');
259
  document.getElementById('cb').value = Array(n).fill(b).join(',');
260
  } else {
@@ -276,46 +258,39 @@ function updateExpected() {
276
  const lbl = document.getElementById('expected-lbl');
277
 
278
  if (!a || !b || !DS[ds]) {
279
- lbl.innerText = "Enter A and B values";
280
- return;
281
  }
282
 
283
  const fn = DS[ds].fn;
284
  const av = parseVals(a), bv = parseVals(b);
285
  if (!av.length || !bv.length) {
286
- lbl.innerText = "Invalid numbers";
287
- return;
288
  }
289
 
290
  const stackLv = parseInt(document.getElementById('cfg-stack').value);
291
  const isStacked = stackLv > 0;
292
- // If stacked, D is fixed to 2^stack. Otherwise use D dial.
293
  const n = isStacked ? Math.pow(2, stackLv) : parseInt(document.getElementById('cfg-inputs').value);
294
 
295
  if (isStacked) {
296
- // For stacked, the entire system converges to a single output target
297
- // which in your python is evaluated on the first item in the list
298
  const res = fn(av[0], bv[0]);
299
- lbl.innerText = `Final Stacked Target: ${res}`;
300
  } else {
301
- // For flat, it evaluates N independent outputs
302
  const results = Array.from({length:n}, (_,i) => fn(av[i%av.length], bv[i%bv.length]));
303
- lbl.innerText = n === 1 ? `Target Ground truth: ${results[0]}` : `Targets: [${results.join(', ')}]`;
304
  }
305
  }
306
 
307
- // ── UI TOPOLOGY TOGGLES ──────────────────────────────────────────────────────
308
  function updateTopoUI() {
309
  const stack = parseInt(document.getElementById('cfg-stack').value);
310
  const dimInput = document.getElementById('cfg-inputs');
311
  const hint = document.getElementById('topo-hint');
312
 
313
  if (stack > 0) {
314
- const requiredDims = Math.pow(2, stack);
315
- dimInput.value = requiredDims;
316
  dimInput.disabled = true;
317
  dimInput.className = "w-full bg-slate-800 border border-slate-700 text-center text-slate-500 rounded text-sm p-0.5 cursor-not-allowed";
318
- hint.innerText = `Stacked mode requires exactly ${requiredDims} comma-separated inputs per batch.`;
319
  } else {
320
  dimInput.disabled = false;
321
  dimInput.className = "w-full bg-black border border-slate-700 text-center text-white rounded text-sm p-0.5";
@@ -360,10 +335,7 @@ async function setMode(m, btn, cls) {
360
  b.classList.add('off'); b.classList.remove('on', cls);
361
  });
362
  btn.classList.remove('off'); btn.classList.add('on', cls);
363
- await fetch('/set_mode', {
364
- method:'POST', headers:{'Content-Type':'application/json'},
365
- body: JSON.stringify({ mode: m })
366
- });
367
  document.getElementById('b-mode').innerText = m === 'training' ? 'TRAIN' : 'INFER';
368
  }
369
 
@@ -378,7 +350,6 @@ function pick(key, val, btn, cls) {
378
  function openDrawer() { document.getElementById('drawer').classList.remove('drawer-closed'); refreshDS(); }
379
  function closeDrawer() { document.getElementById('drawer').classList.add('drawer-closed'); }
380
 
381
- // Initialize defaults on load
382
  refreshDS();
383
 
384
  async function applyConfig() {
@@ -386,9 +357,7 @@ async function applyConfig() {
386
  let cred = document.getElementById('cfg-cred').value;
387
  const alpha = parseFloat(document.getElementById('alpha-sl').value) / 100;
388
 
389
- // Map simplified dropdown to backend booleans
390
- let rev = false;
391
- let ind = false;
392
  if (cred === 'reverse') { rev = true; cred = 'elastic_backprop'; }
393
  if (cred === 'independent') { ind = true; cred = 'independent'; }
394
 
@@ -424,13 +393,29 @@ async function startBatch() {
424
  }
425
 
426
  async function runCustom() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
427
  await fetch('/run_custom', {
428
  method:'POST', headers:{'Content-Type':'application/json'},
429
- body: JSON.stringify({
430
- a: document.getElementById('ca').value,
431
- b: document.getElementById('cb').value,
432
- c: document.getElementById('cc').value || null
433
- })
434
  });
435
  closeDrawer();
436
  }
@@ -449,7 +434,7 @@ function tab(name) {
449
  });
450
  }
451
 
452
- // ── VISUALIZATION (Horizontal Flow Graph) ────────────────────────────────────
453
 
454
  function springColor(k) {
455
  const t = Math.min(Math.abs(k) / 6, 1);
@@ -460,24 +445,17 @@ function springColor(k) {
460
  function buildTraces(d) {
461
  const pos = {};
462
  const traces = [];
463
-
464
- const W_SPREAD = 5.5;
465
- const H_SPREAD = 4.0;
466
- const HG_W = 2.0;
467
- const HG_H = 1.5;
468
 
469
  d.topology.forEach((level, lv) => {
470
  const baseX = lv * W_SPREAD;
471
  const N = level.length;
472
-
473
  level.forEach((uid, i) => {
474
  const baseY = (i - (N - 1) / 2) * -H_SPREAD;
475
  const u = d.units[uid];
476
-
477
  pos[`${uid}_A`] = [baseX - (HG_W/2), baseY + HG_H];
478
  pos[`${uid}_B`] = [baseX - (HG_W/2), baseY - HG_H];
479
  pos[`${uid}_C`] = [baseX + (HG_W/2), baseY];
480
-
481
  for(let j=1; j<=u.n_upper; j++) {
482
  const ty = u.n_upper === 1 ? 0 : (j-1)/(u.n_upper-1) - 0.5;
483
  pos[`${uid}_U${j}`] = [baseX, baseY + (HG_H*0.7) - ty * 1.5];
@@ -489,18 +467,14 @@ function buildTraces(d) {
489
  });
490
  });
491
 
492
- // Wiring between levels
493
  d.connections.forEach(conn => {
494
  const p1 = pos[`${conn.from_uid}_C`];
495
  const p2 = pos[`${conn.to_uid}_${conn.to_port}`];
496
  if(p1 && p2) {
497
  const midX = (p1[0] + p2[0]) / 2;
498
  traces.push({
499
- type:'scatter', mode:'lines',
500
- x: [p1[0], midX, midX, p2[0]],
501
- y: [p1[1], p1[1], p2[1], p2[1]],
502
- line:{color:'rgba(255,255,255,0.15)', width:2, shape:'spline'},
503
- hoverinfo:'none'
504
  });
505
  }
506
  });
@@ -513,24 +487,17 @@ function buildTraces(d) {
513
  const p1 = pos[`${uid}_${n1}`], p2 = pos[`${uid}_${n2}`];
514
  if(p1 && p2) {
515
  const [col, wd] = springColor(k);
516
- traces.push({
517
- type:'scatter', mode:'lines', x:[p1[0], p2[0]], y:[p1[1], p2[1]],
518
- line:{color:col, width:wd}, hoverinfo:'none'
519
- });
520
  }
521
  });
522
-
523
  allN.push({ id:`${uid}_A`, x:pos[`${uid}_A`][0], y:pos[`${uid}_A`][1], v:u.a_val, t:'A' });
524
  allN.push({ id:`${uid}_B`, x:pos[`${uid}_B`][0], y:pos[`${uid}_B`][1], v:u.b_val, t:'B' });
525
  allN.push({ id:`${uid}_C`, x:pos[`${uid}_C`][0], y:pos[`${uid}_C`][1], v:u.c_val, t:'C' });
526
-
527
  for(let j=1; j<=u.n_upper; j++) {
528
- const nd = u.nodes[`U${j}`];
529
- allN.push({ id:`${uid}_U${j}`, x:pos[`${uid}_U${j}`][0], y:pos[`${uid}_U${j}`][1], vel:nd.vel, t:'U' });
530
  }
531
  for(let j=1; j<=u.n_lower; j++) {
532
- const nd = u.nodes[`L${j}`];
533
- allN.push({ id:`${uid}_L${j}`, x:pos[`${uid}_L${j}`][0], y:pos[`${uid}_L${j}`][1], vel:nd.vel, t:'L' });
534
  }
535
  });
536
 
@@ -549,27 +516,15 @@ function buildTraces(d) {
549
  hoverinfo:'none'
550
  });
551
 
552
- const N_Lvs = d.topology.length;
553
- const N_Max = d.topology[0].length;
554
- const xMax = (N_Lvs - 1) * W_SPREAD + (HG_W*1.5);
555
- const xMin = -(HG_W*1.5);
556
- const yMax = Math.max(5, (N_Max/2) * H_SPREAD + 1);
557
 
558
  return { traces, layout: {
559
  margin:{l:8,r:8,t:8,b:8}, paper_bgcolor:'transparent', plot_bgcolor:'transparent',
560
- xaxis:{visible:false, range:[xMin, xMax]}, yaxis:{visible:false, range:[-yMax, yMax]},
561
- showlegend:false
562
  }};
563
  }
564
 
565
- const ERR_LAYOUT = {
566
- margin:{l:30,r:6,t:3,b:12},
567
- paper_bgcolor:'transparent', plot_bgcolor:'transparent',
568
- xaxis:{visible:false},
569
- yaxis:{color:'#334155', gridcolor:'#0f172a', zeroline:true, zerolinecolor:'#22c55e', zerolinewidth:1},
570
- showlegend:false,
571
- };
572
-
573
  let meshPlotted = false, errPlotted = false, lastLayerKey = '';
574
 
575
  // ── POLL ──────────────────────────────────────────────────────────────────────
@@ -579,7 +534,6 @@ setInterval(async () => {
579
  const d = await r.json();
580
 
581
  syncTopoUI(d);
582
-
583
  let credBadge = d.credit_mode.replace('_','').slice(0,7).toUpperCase();
584
  if (d.reverse_mode) credBadge = 'REVERSE';
585
  if (d.individual_train) credBadge = 'INDEPND';
@@ -639,8 +593,7 @@ setInterval(async () => {
639
  });
640
  document.getElementById('pane-springs').innerHTML = sh;
641
 
642
- document.getElementById('pane-logs').innerHTML =
643
- d.logs.map(l => `<div class="py-0.5 border-b border-slate-900/50">${l}</div>`).join('');
644
 
645
  const layerKey = JSON.stringify(d.topology) + d.n_upper + d.n_lower;
646
  const { traces, layout } = buildTraces(d);
@@ -653,17 +606,12 @@ setInterval(async () => {
653
  }
654
 
655
  const hist = d.history;
656
- const eTrace = {
657
- type:'scatter', mode:'lines',
658
- x: hist.map((_,i)=>i), y: hist,
659
- line:{color:'#f97316',width:1.5},
660
- fill:'tozeroy', fillcolor:'rgba(249,115,22,0.07)'
661
- };
662
  if (!errPlotted) {
663
- Plotly.newPlot('err-plot',[eTrace],ERR_LAYOUT,{displayModeBar:false,responsive:true});
664
  errPlotted = true;
665
  } else {
666
- Plotly.react('err-plot',[eTrace],ERR_LAYOUT);
667
  }
668
 
669
  } catch(e) { }
 
24
  </head>
25
  <body class="flex flex-col h-screen overflow-hidden">
26
 
 
27
  <header class="glass flex-shrink-0 px-2 py-1.5 flex justify-between items-center">
28
  <div class="flex flex-wrap gap-1 text-[8px] font-bold items-center">
29
  <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>
 
33
  <span id="b-cred" class="px-1.5 py-0.5 rounded bg-fuchsia-900/60 text-fuchsia-300 border border-fuchsia-800/60">CREDIT</span>
34
  <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>
35
 
 
36
  <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">
37
  <span class="text-yellow-300 mr-0.5">D</span>
38
  <button class="lbtn" onclick="quickL('inputs',-1)">−</button>
 
48
  <button class="lbtn" onclick="quickL('lower',+1)">+</button>
49
  </span>
50
 
 
51
  <div class="flex items-center gap-1.5 px-2 border-l border-slate-700 ml-2" title="Percentage of springs actively changing">
52
  <span class="text-[7px] text-slate-500 uppercase tracking-wider">Learning</span>
53
  <div class="w-16 bg-slate-900 rounded-sm h-2 overflow-hidden border border-slate-700">
 
64
  </div>
65
  </header>
66
 
 
67
  <div class="flex-grow flex flex-col min-h-0 overflow-hidden">
68
  <div id="mesh-plot" class="flex-grow min-h-0"></div>
69
  <div id="err-plot" style="height:58px" class="flex-shrink-0 border-t border-slate-900"></div>
70
  </div>
71
 
 
72
  <div class="glass flex-shrink-0 border-t border-slate-800" style="height:174px">
73
  <div class="flex border-b border-slate-800 text-[10px]">
74
  <button onclick="tab('nodes')" id="tab-nodes" class="flex-1 py-1.5 bg-blue-900/40 text-blue-300 font-bold">UNITS</button>
 
99
 
100
  <div class="grid grid-cols-2 gap-2">
101
 
 
102
  <div class="col-span-2 bg-slate-900 rounded p-3 border border-yellow-900/50">
103
  <div class="text-yellow-400 text-[9px] font-bold mb-1">EXECUTION MODE</div>
104
  <div class="flex gap-2">
105
+ <button class="tog on flex-1 py-2 rounded text-xs font-bold bg-yellow-700" onclick="setMode('training',this,'bg-yellow-700')">TRAINING</button>
106
+ <button class="tog off flex-1 py-2 rounded text-xs font-bold" onclick="setMode('inference',this,'bg-yellow-700')">INFERENCE</button>
 
 
107
  </div>
108
  </div>
109
 
 
110
  <div class="col-span-2 bg-slate-900 rounded p-3 border border-emerald-900/50">
111
  <div class="text-emerald-400 text-[9px] font-bold mb-2">ARCHITECTURE TOPOLOGY (Dimensionality)</div>
112
  <div class="flex gap-2 items-center mb-1">
 
124
  <div id="topo-hint" class="text-[8px] text-emerald-500 italic mt-1 text-center">Flat mode: D independent variables.</div>
125
  </div>
126
 
 
127
  <div class="col-span-2 bg-slate-900 rounded p-3 border border-fuchsia-900/50">
128
  <div class="text-fuchsia-400 text-[9px] font-bold mb-2">TRAINING BEHAVIOR (How errors fix springs)</div>
129
  <select id="cfg-cred" class="w-full bg-black border border-slate-700 p-2 text-white text-xs rounded">
 
134
  <div class="text-[8px] text-slate-500 mt-1 italic text-center">Controls how the model learns when stacking multiple hourglasses.</div>
135
  </div>
136
 
 
137
  <div class="bg-slate-900 rounded p-3 border border-blue-900/50">
138
  <div class="text-blue-400 text-[9px] font-bold mb-2">NODE ACTIVATION</div>
139
+ <button class="tog on w-full mb-1 py-2 rounded text-[10px] font-bold bg-blue-700" onclick="pick('architecture','additive',this,'bg-blue-700')">ADDITIVE Σ</button>
140
+ <button class="tog off w-full py-2 rounded text-[10px] font-bold" onclick="pick('architecture','multiplicative',this,'bg-blue-700')">MULTIPLICATIVE Π</button>
 
 
141
  </div>
142
 
143
  <div class="bg-slate-900 rounded p-3 border border-orange-900/50">
144
  <div class="text-orange-400 text-[9px] font-bold mb-1">BACK-TENSION α</div>
145
+ <input id="alpha-sl" type="range" min="0" max="100" value="45" step="5" class="w-full accent-orange-500 mt-1" oninput="document.getElementById('alpha-val').innerText=(this.value/100).toFixed(2)">
 
 
146
  <div class="text-center text-orange-300 font-bold text-xl mt-0.5" id="alpha-val">0.45</div>
147
  </div>
148
 
 
149
  <div class="col-span-2 bg-slate-900 rounded p-2 text-center border border-slate-700 grid grid-cols-2 gap-2">
150
  <div class="bg-slate-800 rounded p-2 text-center border border-orange-900/40">
151
  <div class="text-orange-400 text-[8px] mb-1">UPPER ROW (U)</div>
 
166
  </div>
167
  </div>
168
 
169
+ <button onclick="applyConfig()" class="w-full bg-white text-black py-3 rounded font-bold text-sm hover:bg-slate-200 mt-2">
 
170
  APPLY &amp; REBUILD TOPOLOGY
171
  </button>
172
 
 
173
  <div class="grid grid-cols-2 gap-2 mt-2">
 
174
  <div class="bg-slate-900 rounded p-3 border border-pink-900/50">
175
  <div class="text-pink-400 text-[9px] font-bold mb-2">DATASET</div>
176
  <select id="ds-sel" onchange="refreshDS()" class="w-full bg-black border border-slate-700 p-2 text-white text-xs rounded mb-2">
 
185
  </div>
186
  </div>
187
 
 
188
  <div class="bg-slate-900 rounded p-3 border border-cyan-900/50 flex flex-col">
189
  <div class="text-cyan-400 text-[9px] font-bold mb-1">CUSTOM INPUT PREVIEW</div>
190
  <div id="ds-examples" class="text-[9px] text-slate-500 mb-2">e.g. click me</div>
 
195
  <input id="cc" type="text" placeholder="auto" class="w-full bg-black border border-slate-700 p-1.5 text-white text-xs text-center rounded" title="Optional Target C">
196
  </div>
197
 
 
198
  <div id="expected-lbl" class="text-[9px] text-yellow-400 font-bold mb-2 min-h-[14px] text-center flex-grow flex items-center justify-center">
199
  Ground truth: waiting...
200
  </div>
 
207
  </aside>
208
 
209
  <script>
 
210
  const cfg = { mode: 'training', architecture: 'additive' };
211
  const topo = { inputs: 1, upper: 3, lower: 3, stack: 0 };
212
 
 
213
  const DS = {
214
  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}] },
215
  subtraction: { hint:'A−B', fn:(a,b)=>(a-b).toFixed(3), ex:[{a:8,b:3},{a:5,b:5}] },
 
217
  quadratic: { hint:'A²+B', fn:(a,b)=>(a*a+b).toFixed(3), ex:[{a:3,b:2},{a:4,b:5}] },
218
  };
219
 
220
+ async function refreshDS() {
221
  const ds = document.getElementById('ds-sel').value;
222
  const m = DS[ds];
223
  document.getElementById('ds-examples').innerHTML = 'e.g. ' +
224
  m.ex.map(e => `<span class="cursor-pointer text-cyan-500 underline"
225
  onclick="fillC('${e.a}','${e.b}')">A=${e.a} B=${e.b}</span>`).join(' ');
226
  updateExpected();
227
+
228
+ // Quietly sync backend dataset immediately so custom runs perfectly match UI
229
+ await fetch('/config', {
230
+ method:'POST', headers:{'Content-Type':'application/json'},
231
+ body: JSON.stringify({ dataset: ds })
232
+ });
233
  }
234
 
235
  function fillC(a, b) {
236
+ const stackLv = parseInt(document.getElementById('cfg-stack').value);
237
+ const n = stackLv > 0 ? Math.pow(2, stackLv) : parseInt(document.getElementById('cfg-inputs').value);
238
 
239
  if (n > 1) {
 
240
  document.getElementById('ca').value = Array(n).fill(a).join(',');
241
  document.getElementById('cb').value = Array(n).fill(b).join(',');
242
  } else {
 
258
  const lbl = document.getElementById('expected-lbl');
259
 
260
  if (!a || !b || !DS[ds]) {
261
+ lbl.innerText = "Enter A and B values"; return;
 
262
  }
263
 
264
  const fn = DS[ds].fn;
265
  const av = parseVals(a), bv = parseVals(b);
266
  if (!av.length || !bv.length) {
267
+ lbl.innerText = "Invalid numbers"; return;
 
268
  }
269
 
270
  const stackLv = parseInt(document.getElementById('cfg-stack').value);
271
  const isStacked = stackLv > 0;
 
272
  const n = isStacked ? Math.pow(2, stackLv) : parseInt(document.getElementById('cfg-inputs').value);
273
 
274
  if (isStacked) {
 
 
275
  const res = fn(av[0], bv[0]);
276
+ lbl.innerText = `Final Target: ${res}`;
277
  } else {
 
278
  const results = Array.from({length:n}, (_,i) => fn(av[i%av.length], bv[i%bv.length]));
279
+ lbl.innerText = n === 1 ? `Target: ${results[0]}` : `Targets: [${results.join(', ')}]`;
280
  }
281
  }
282
 
 
283
  function updateTopoUI() {
284
  const stack = parseInt(document.getElementById('cfg-stack').value);
285
  const dimInput = document.getElementById('cfg-inputs');
286
  const hint = document.getElementById('topo-hint');
287
 
288
  if (stack > 0) {
289
+ const req = Math.pow(2, stack);
290
+ dimInput.value = req;
291
  dimInput.disabled = true;
292
  dimInput.className = "w-full bg-slate-800 border border-slate-700 text-center text-slate-500 rounded text-sm p-0.5 cursor-not-allowed";
293
+ hint.innerText = `Stacked mode requires ${req} comma-separated inputs per batch.`;
294
  } else {
295
  dimInput.disabled = false;
296
  dimInput.className = "w-full bg-black border border-slate-700 text-center text-white rounded text-sm p-0.5";
 
335
  b.classList.add('off'); b.classList.remove('on', cls);
336
  });
337
  btn.classList.remove('off'); btn.classList.add('on', cls);
338
+ await fetch('/set_mode', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ mode: m }) });
 
 
 
339
  document.getElementById('b-mode').innerText = m === 'training' ? 'TRAIN' : 'INFER';
340
  }
341
 
 
350
  function openDrawer() { document.getElementById('drawer').classList.remove('drawer-closed'); refreshDS(); }
351
  function closeDrawer() { document.getElementById('drawer').classList.add('drawer-closed'); }
352
 
 
353
  refreshDS();
354
 
355
  async function applyConfig() {
 
357
  let cred = document.getElementById('cfg-cred').value;
358
  const alpha = parseFloat(document.getElementById('alpha-sl').value) / 100;
359
 
360
+ let rev = false, ind = false;
 
 
361
  if (cred === 'reverse') { rev = true; cred = 'elastic_backprop'; }
362
  if (cred === 'independent') { ind = true; cred = 'independent'; }
363
 
 
393
  }
394
 
395
  async function runCustom() {
396
+ const a = document.getElementById('ca').value;
397
+ const b = document.getElementById('cb').value;
398
+ let c = document.getElementById('cc').value;
399
+
400
+ // STRICT UI SYNC: Calculate exact C array so Backend math is strictly overriden to match UI.
401
+ if (!c) {
402
+ const ds = document.getElementById('ds-sel').value;
403
+ const fn = DS[ds].fn;
404
+ const av = parseVals(a), bv = parseVals(b);
405
+ if (av.length && bv.length) {
406
+ const stackLv = parseInt(document.getElementById('cfg-stack').value);
407
+ if (stackLv > 0) {
408
+ c = fn(av[0], bv[0]);
409
+ } else {
410
+ const n = parseInt(document.getElementById('cfg-inputs').value);
411
+ c = Array.from({length:n}, (_,i) => fn(av[i%av.length], bv[i%bv.length])).join(',');
412
+ }
413
+ }
414
+ }
415
+
416
  await fetch('/run_custom', {
417
  method:'POST', headers:{'Content-Type':'application/json'},
418
+ body: JSON.stringify({ a, b, c })
 
 
 
 
419
  });
420
  closeDrawer();
421
  }
 
434
  });
435
  }
436
 
437
+ // ── VISUALIZATION ────────────────────────────────────────────────────────────
438
 
439
  function springColor(k) {
440
  const t = Math.min(Math.abs(k) / 6, 1);
 
445
  function buildTraces(d) {
446
  const pos = {};
447
  const traces = [];
448
+ const W_SPREAD = 5.5, H_SPREAD = 4.0, HG_W = 2.0, HG_H = 1.5;
 
 
 
 
449
 
450
  d.topology.forEach((level, lv) => {
451
  const baseX = lv * W_SPREAD;
452
  const N = level.length;
 
453
  level.forEach((uid, i) => {
454
  const baseY = (i - (N - 1) / 2) * -H_SPREAD;
455
  const u = d.units[uid];
 
456
  pos[`${uid}_A`] = [baseX - (HG_W/2), baseY + HG_H];
457
  pos[`${uid}_B`] = [baseX - (HG_W/2), baseY - HG_H];
458
  pos[`${uid}_C`] = [baseX + (HG_W/2), baseY];
 
459
  for(let j=1; j<=u.n_upper; j++) {
460
  const ty = u.n_upper === 1 ? 0 : (j-1)/(u.n_upper-1) - 0.5;
461
  pos[`${uid}_U${j}`] = [baseX, baseY + (HG_H*0.7) - ty * 1.5];
 
467
  });
468
  });
469
 
 
470
  d.connections.forEach(conn => {
471
  const p1 = pos[`${conn.from_uid}_C`];
472
  const p2 = pos[`${conn.to_uid}_${conn.to_port}`];
473
  if(p1 && p2) {
474
  const midX = (p1[0] + p2[0]) / 2;
475
  traces.push({
476
+ type:'scatter', mode:'lines', x: [p1[0], midX, midX, p2[0]], y: [p1[1], p1[1], p2[1], p2[1]],
477
+ line:{color:'rgba(255,255,255,0.15)', width:2, shape:'spline'}, hoverinfo:'none'
 
 
 
478
  });
479
  }
480
  });
 
487
  const p1 = pos[`${uid}_${n1}`], p2 = pos[`${uid}_${n2}`];
488
  if(p1 && p2) {
489
  const [col, wd] = springColor(k);
490
+ traces.push({ type:'scatter', mode:'lines', x:[p1[0], p2[0]], y:[p1[1], p2[1]], line:{color:col, width:wd}, hoverinfo:'none' });
 
 
 
491
  }
492
  });
 
493
  allN.push({ id:`${uid}_A`, x:pos[`${uid}_A`][0], y:pos[`${uid}_A`][1], v:u.a_val, t:'A' });
494
  allN.push({ id:`${uid}_B`, x:pos[`${uid}_B`][0], y:pos[`${uid}_B`][1], v:u.b_val, t:'B' });
495
  allN.push({ id:`${uid}_C`, x:pos[`${uid}_C`][0], y:pos[`${uid}_C`][1], v:u.c_val, t:'C' });
 
496
  for(let j=1; j<=u.n_upper; j++) {
497
+ allN.push({ id:`${uid}_U${j}`, x:pos[`${uid}_U${j}`][0], y:pos[`${uid}_U${j}`][1], vel:u.nodes[`U${j}`].vel, t:'U' });
 
498
  }
499
  for(let j=1; j<=u.n_lower; j++) {
500
+ allN.push({ id:`${uid}_L${j}`, x:pos[`${uid}_L${j}`][0], y:pos[`${uid}_L${j}`][1], vel:u.nodes[`L${j}`].vel, t:'L' });
 
501
  }
502
  });
503
 
 
516
  hoverinfo:'none'
517
  });
518
 
519
+ const xMax = (d.topology.length - 1) * W_SPREAD + (HG_W*1.5);
520
+ const yMax = Math.max(5, (d.topology[0].length/2) * H_SPREAD + 1);
 
 
 
521
 
522
  return { traces, layout: {
523
  margin:{l:8,r:8,t:8,b:8}, paper_bgcolor:'transparent', plot_bgcolor:'transparent',
524
+ xaxis:{visible:false, range:[-(HG_W*1.5), xMax]}, yaxis:{visible:false, range:[-yMax, yMax]}, showlegend:false
 
525
  }};
526
  }
527
 
 
 
 
 
 
 
 
 
528
  let meshPlotted = false, errPlotted = false, lastLayerKey = '';
529
 
530
  // ── POLL ──────────────────────────────────────────────────────────────────────
 
534
  const d = await r.json();
535
 
536
  syncTopoUI(d);
 
537
  let credBadge = d.credit_mode.replace('_','').slice(0,7).toUpperCase();
538
  if (d.reverse_mode) credBadge = 'REVERSE';
539
  if (d.individual_train) credBadge = 'INDEPND';
 
593
  });
594
  document.getElementById('pane-springs').innerHTML = sh;
595
 
596
+ document.getElementById('pane-logs').innerHTML = d.logs.map(l => `<div class="py-0.5 border-b border-slate-900/50">${l}</div>`).join('');
 
597
 
598
  const layerKey = JSON.stringify(d.topology) + d.n_upper + d.n_lower;
599
  const { traces, layout } = buildTraces(d);
 
606
  }
607
 
608
  const hist = d.history;
609
+ const eTrace = { type:'scatter', mode:'lines', x: hist.map((_,i)=>i), y: hist, line:{color:'#f97316',width:1.5}, fill:'tozeroy', fillcolor:'rgba(249,115,22,0.07)' };
 
 
 
 
 
610
  if (!errPlotted) {
611
+ Plotly.newPlot('err-plot',[eTrace],{margin:{l:30,r:6,t:3,b:12}, paper_bgcolor:'transparent', plot_bgcolor:'transparent', xaxis:{visible:false}, yaxis:{color:'#334155', gridcolor:'#0f172a', zeroline:true, zerolinecolor:'#22c55e', zerolinewidth:1}, showlegend:false},{displayModeBar:false,responsive:true});
612
  errPlotted = true;
613
  } else {
614
+ Plotly.react('err-plot',[eTrace],{margin:{l:30,r:6,t:3,b:12}, paper_bgcolor:'transparent', plot_bgcolor:'transparent', xaxis:{visible:false}, yaxis:{color:'#334155', gridcolor:'#0f172a', zeroline:true, zerolinecolor:'#22c55e', zerolinewidth:1}, showlegend:false});
615
  }
616
 
617
  } catch(e) { }