everydaytok commited on
Commit
cf8b1cc
Β·
verified Β·
1 Parent(s): cc4cc00

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +483 -162
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>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,228 +11,549 @@
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]'
140
- : 'flex-1 py-1 text-slate-500 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>
 
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
  .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
+ .tog { transition:all .15s; }
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; }
23
  </style>
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>
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>
38
+ <span class="text-cyan-400 mx-1">L</span>
39
+ <button class="lbtn" onclick="quickL('lower',-1)">βˆ’</button>
40
+ <span id="b-lower" class="w-3 text-center text-white">3</span>
41
+ <button class="lbtn" onclick="quickL('lower',+1)">+</button>
42
+ </span>
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>
51
 
52
+ <!-- ── PLOTS ────────────────────────────────────────────────────────────────── -->
53
+ <div class="flex-grow flex flex-col min-h-0 overflow-hidden">
54
+ <div id="mesh-plot" class="flex-grow min-h-0"></div>
55
+ <div id="err-plot" style="height:58px" class="flex-shrink-0 border-t border-slate-900"></div>
56
  </div>
57
 
58
+ <!-- ── BOTTOM PANEL ─────────────────────────────────────────────────────────── -->
59
+ <div class="glass flex-shrink-0 border-t border-slate-800" style="height:174px">
60
  <div class="flex border-b border-slate-800 text-[10px]">
61
+ <button onclick="tab('nodes')" id="tab-nodes" class="flex-1 py-1.5 bg-blue-900/40 text-blue-300 font-bold">NODES</button>
62
+ <button onclick="tab('springs')" id="tab-springs" class="flex-1 py-1.5 text-slate-500">SPRINGS</button>
63
+ <button onclick="tab('logs')" id="tab-logs" class="flex-1 py-1.5 text-slate-500">LOGS</button>
64
  </div>
65
  <div class="flex h-full overflow-hidden">
66
+ <div class="w-20 flex-shrink-0 border-r border-slate-800 flex flex-col items-center justify-center p-1 gap-0.5">
67
+ <div class="text-[7px] text-slate-600 uppercase tracking-widest">Tension</div>
68
+ <div id="err-big" class="text-xl font-bold text-red-400 leading-none">0.00</div>
69
+ <div id="pred-val" class="text-[8px] text-slate-500">P:β€”</div>
70
+ <div id="iter-lbl" class="text-[8px] text-slate-700">IT:0</div>
71
  </div>
72
+ <div class="flex-grow overflow-y-auto p-1.5 text-[10px]">
73
  <div id="pane-nodes"></div>
74
+ <div id="pane-springs" class="hidden"></div>
75
+ <div id="pane-logs" class="hidden text-[9px] text-slate-500"></div>
76
  </div>
77
  </div>
78
  </div>
79
 
80
+ <!-- ── DRAWER ───────────────────────────────────────────────────────────────── -->
81
+ <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-[93vh] overflow-y-auto">
82
  <div class="flex justify-between items-center">
83
+ <span class="text-orange-400 font-bold text-sm">ELASTIC MESH LABORATORY</span>
84
  <button onclick="closeDrawer()" class="text-slate-400 text-2xl leading-none">βœ•</button>
85
  </div>
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
 
100
+ <!-- ARCH -->
101
+ <div class="bg-slate-900 rounded p-3 border border-blue-900/50">
102
+ <div class="text-blue-400 text-[9px] font-bold mb-2">NODE ACTIVATION</div>
103
+ <button class="tog on w-full mb-1 py-2 rounded text-[10px] font-bold bg-blue-700"
104
+ onclick="pick('architecture','additive',this,'bg-blue-700')">ADDITIVE Ξ£</button>
105
+ <button class="tog off w-full py-2 rounded text-[10px] font-bold"
106
+ onclick="pick('architecture','multiplicative',this,'bg-blue-700')">MULTIPLICATIVE Ξ </button>
107
+ </div>
108
+
109
+ <!-- ALPHA -->
110
+ <div class="bg-slate-900 rounded p-3 border border-orange-900/50">
111
+ <div class="text-orange-400 text-[9px] font-bold mb-1">BACK-TENSION Ξ±</div>
112
+ <input id="alpha-sl" type="range" min="0" max="100" value="45" step="5"
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>
146
  </div>
147
  </div>
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">
161
+ <option value="housing">Housing β€” AΓ—2.5 + BΓ—1.2</option>
162
+ <option value="subtraction">Subtraction β€” A βˆ’ B</option>
163
+ <option value="multiplication">Multiplication β€” A Γ— B</option>
164
+ <option value="quadratic">Quadratic β€” AΒ² + B</option>
165
+ </select>
166
+ <p id="ds-hint" class="text-[9px] text-slate-500 italic mb-2"></p>
167
+ <div class="flex gap-2">
168
+ <input id="batch-n" type="number" value="30" min="5" max="200"
169
+ class="w-16 bg-black border border-slate-700 p-2 text-white text-sm text-center rounded">
170
+ <button onclick="startBatch()"
171
+ class="flex-1 bg-pink-800 hover:bg-pink-700 py-2 text-xs font-bold rounded">START BATCH</button>
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>
198
+ <div id="expected-lbl" class="text-[9px] text-yellow-400 mb-2 min-h-[14px]"></div>
199
+ <button onclick="runCustom()"
200
+ class="w-full bg-cyan-800 hover:bg-cyan-700 py-2 text-xs font-bold rounded">RUN CUSTOM</button>
201
  </div>
202
+
203
+ <button onclick="halt()"
204
+ class="w-full border border-red-700 text-red-500 py-2 rounded text-xs font-bold">HALT ENGINE</button>
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 => {
248
+ b.classList.add('off'); b.classList.remove('on', cls);
249
+ });
250
+ btn.classList.remove('off'); btn.classList.add('on', cls);
251
+ }
252
+
253
+ function openDrawer() { document.getElementById('drawer').classList.remove('drawer-closed'); refreshDS(); }
254
  function closeDrawer() { document.getElementById('drawer').classList.add('drawer-closed'); }
 
255
 
256
+ async function applyConfig() {
257
+ const ds = document.getElementById('ds-sel').value;
258
+ const alpha = parseFloat(document.getElementById('alpha-sl').value) / 100;
259
+ await fetch('/config', {
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();
270
+ meshPlotted = false;
271
  closeDrawer();
272
  }
273
 
274
+ async function startBatch() {
275
+ await fetch('/generate', {
276
+ method:'POST', headers:{'Content-Type':'application/json'},
277
+ body: JSON.stringify({ count: parseInt(document.getElementById('batch-n').value) })
278
+ });
279
  closeDrawer();
280
  }
281
 
282
+ async function runCustom() {
283
+ const a = document.getElementById('ca').value;
284
+ const b = document.getElementById('cb').value;
285
+ const c = document.getElementById('cc').value;
286
+ await fetch('/run_custom', {
287
+ method:'POST', headers:{'Content-Type':'application/json'},
288
+ body: JSON.stringify({ a, b, c: c !== '' ? c : null })
289
+ });
290
+ closeDrawer();
291
+ }
292
+
293
+ async function halt() {
294
+ await fetch('/halt', { method:'POST' });
295
+ closeDrawer();
296
  }
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() {
307
+ const ds = document.getElementById('ds-sel').value, m = DS[ds];
308
+ document.getElementById('ds-hint').innerText = m.hint;
309
+ document.getElementById('ds-examples').innerHTML = 'e.g. ' +
310
+ m.ex.map(e => `<span class="cursor-pointer text-cyan-500 underline"
311
+ onclick="fillC(${e.a},${e.b})">A=${e.a} B=${e.b}β†’${m.fn(e.a,e.b)}</span>`).join(' ');
312
+ updateExpected();
313
+ }
314
+
315
+ function fillC(a, b) {
316
+ document.getElementById('ca').value = a;
317
+ document.getElementById('cb').value = b;
318
+ document.getElementById('cc').value = '';
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();
331
+ updateSpringCount();
332
+
333
+ // ── TABS ──────────────────────────────────────────────────────────────────────
334
  function tab(name) {
335
+ ['nodes','springs','logs'].forEach(t => {
336
  document.getElementById(`pane-${t}`).classList.toggle('hidden', t !== name);
337
  document.getElementById(`tab-${t}`).className = t === name
338
+ ? 'flex-1 py-1.5 bg-blue-900/40 text-blue-300 font-bold text-[10px]'
339
+ : 'flex-1 py-1.5 text-slate-500 text-[10px]';
340
+ });
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
404
+ for (const [key, k] of Object.entries(springs)) {
405
+ const [u, v] = key.split('β†’');
406
+ if (!pos[u] || !pos[v]) continue;
407
+ const [col, wd] = springColor(k);
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
  });
452
+
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
+
476
+ let meshPlotted = false, errPlotted = false, lastLayerKey = '';
477
+
478
+ // ── POLL ──────────────────────────────────────────────────────────────────────
479
  setInterval(async () => {
480
  try {
481
+ const r = await fetch('/state');
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}`;
493
+ document.getElementById('iter-lbl').innerText = `IT:${d.iter}`;
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 pane — ordered 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
516
+ let sh = '';
517
+ for (const [key, k] of Object.entries(d.springs)) {
518
+ const kc = k < 0 ? 'text-blue-300' : k > 4 ? 'text-yellow-200' : 'text-purple-300';
519
+ sh += `<div class="flex justify-between py-0.5 border-b border-slate-900">
520
+ <span class="text-slate-500 text-[9px]">${key}</span>
521
+ <span class="${kc} font-bold text-[10px]">${k.toFixed(4)}</span></div>`;
522
+ }
523
+ document.getElementById('pane-springs').innerHTML = sh;
524
+
525
+ // Logs
526
+ document.getElementById('pane-logs').innerHTML =
527
+ d.logs.map(l => `<div class="py-0.5 border-b border-slate-900/50">${l}</div>`).join('');
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);
538
+ }
539
+
540
+ // Error chart
541
+ const hist = d.history;
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 */ }
556
+ }, 200);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
557
  </script>
558
  </body>
559
  </html>