everydaytok commited on
Commit
3b5dc6e
Β·
verified Β·
1 Parent(s): 92ee031

Update index.html

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