everydaytok commited on
Commit
338971e
Β·
verified Β·
1 Parent(s): 722604c

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +387 -179
index.html CHANGED
@@ -1,238 +1,446 @@
1
  <!DOCTYPE html>
2
  <html lang="en">
3
  <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Lattice LMS Controller</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>
10
- body { background:#06090e; color:#cbd5e1; font-family:'Courier New',monospace; }
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">CONTROL PANEL</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="10" 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.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/2.5,1)})` : `rgba(6,182,212,${Math.min(Math.abs(K)/2.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.5 + Math.abs(K) }, 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: 14, 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>
 
1
  <!DOCTYPE html>
2
  <html lang="en">
3
  <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <title>Scalar Tri-Mesh v5</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>
10
+ body{background:#06090e;color:#cbd5e1;font-family:'Courier New',monospace}
11
+ .glass{background:rgba(10,18,35,.98);border:1px solid #1e2d40}
12
+ #drawer{transition:transform .3s;z-index:200}
13
+ .closed{transform:translateY(100%)}
14
+ .lb{width:20px;height:20px;background:#1e293b;border:1px solid #334155;border-radius:3px;
15
+ color:#94a3b8;font-weight:bold;font-size:12px;cursor:pointer;
16
+ display:inline-flex;align-items:center;justify-content:center}
17
+ .lb:hover{background:#334155;color:#fff}
18
+ .pt{display:inline-block;padding:1px 5px;border-radius:3px;font-size:9px;font-weight:bold}
19
+ .pt-heavy_a{background:#1e3a2f;color:#4ade80}
20
+ .pt-avg {background:#1e2d4a;color:#60a5fa}
21
+ .pt-diff {background:#3a2a1e;color:#fb923c}
22
+ .pt-heavy_b{background:#3a1e1e;color:#f87171}
23
+ .ood{display:inline-block;padding:0 4px;border-radius:2px;font-size:7px;
24
+ font-weight:bold;background:#7c2d12;color:#fca5a5;margin-left:3px}
25
+ .seen{display:inline-block;padding:0 4px;border-radius:2px;font-size:7px;
26
+ font-weight:bold;background:#14532d;color:#86efac;margin-left:3px}
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 flex justify-between items-center gap-1">
33
  <div class="flex flex-wrap gap-1 items-center text-[8px] font-bold">
34
+ <span id="bMode" class="px-1.5 py-0.5 rounded bg-slate-800 text-slate-400 border border-slate-700">IDLE</span>
35
+ <span id="bType" class="pt pt-avg">β€”</span>
36
+ <span id="bN" class="px-1.5 py-0.5 rounded bg-slate-900 text-slate-600 border border-slate-800">n:1</span>
37
+ <span id="bSpr" class="px-1.5 py-0.5 rounded bg-slate-900 text-slate-600 border border-slate-800">K:β€”</span>
38
+ <div class="flex items-center gap-0.5 px-1.5 py-0.5 rounded bg-slate-800 border border-slate-700">
39
+ <span class="text-slate-500 text-[8px]">n=</span>
40
+ <button class="lb" onclick="chN(-1)">βˆ’</button>
41
+ <span id="nDial" class="text-white w-3 text-center">1</span>
42
+ <button class="lb" onclick="chN(+1)">+</button>
43
+ </div>
44
  </div>
45
  <div class="flex items-center gap-1 flex-shrink-0">
46
+ <span id="qLbl" class="text-[8px] text-slate-600">Q:0</span>
47
+ <div id="dot" class="w-2 h-2 rounded-full bg-slate-700"></div>
48
+ <button onclick="doTrain()" class="text-[9px] bg-purple-800 hover:bg-purple-700 px-2 py-1 rounded font-bold">⚑TRAIN</button>
49
+ <button onclick="doViz()" class="text-[9px] bg-blue-800 hover:bg-blue-700 px-2 py-1 rounded font-bold">πŸ‘VIZ</button>
50
+ <button onclick="doInfer()" class="text-[9px] bg-green-800 hover:bg-green-700 px-2 py-1 rounded font-bold">β–ΆINFER</button>
51
+ <button onclick="doHalt()" class="text-[9px] bg-red-900 hover:bg-red-800 px-2 py-1 rounded font-bold">β– </button>
52
+ <button onclick="openD()" class="text-[9px] bg-slate-700 hover:bg-slate-600 px-2 py-1 rounded font-bold">βš™</button>
53
  </div>
54
  </header>
55
 
56
+ <!-- PLOTS -->
57
+ <div class="flex-grow flex flex-col min-h-0 overflow-hidden">
58
+ <div id="meshPlot" class="flex-grow min-h-0"></div>
59
+ <div id="errPlot" style="height:54px" class="flex-shrink-0 border-t border-slate-900"></div>
60
  </div>
61
 
62
+ <!-- BOTTOM -->
63
+ <div class="glass flex-shrink-0 border-t border-slate-800" style="height:180px">
64
  <div class="flex border-b border-slate-800 text-[10px]">
65
+ <button onclick="tab('nodes')" id="tNodes" class="flex-1 py-1 bg-blue-900/40 text-blue-300 font-bold">NODES</button>
66
+ <button onclick="tab('springs')" id="tSprings" class="flex-1 py-1 text-slate-500">SPRINGS</button>
67
+ <button onclick="tab('acc')" id="tAcc" class="flex-1 py-1 text-slate-500">ACCURACY</button>
68
+ <button onclick="tab('logs')" id="tLogs" class="flex-1 py-1 text-slate-500">LOGS</button>
69
  </div>
70
  <div class="flex h-full overflow-hidden">
71
+ <!-- Tension readout -->
72
+ <div class="w-20 flex-shrink-0 border-r border-slate-800 flex flex-col items-center justify-center p-1 gap-0.5">
73
+ <div class="text-[7px] text-slate-600 uppercase">error</div>
74
+ <div id="eBig" class="text-xl font-bold text-red-400 leading-none">0.00</div>
75
+ <div id="pVal" class="text-[8px] text-slate-500">P:β€”</div>
76
+ <div id="tVal" class="text-[8px] text-slate-600">T:β€”</div>
77
+ <div id="itLbl" class="text-[8px] text-slate-700">IT:0</div>
78
+ <div id="sLbl" class="text-[7px] text-slate-800">S:0</div>
79
  </div>
80
+ <!-- Panes -->
81
+ <div class="flex-grow overflow-y-auto p-1.5 text-[10px]">
82
+ <div id="pNodes"></div>
83
+ <div id="pSprings" class="hidden text-[9px]"></div>
84
+ <div id="pAcc" class="hidden"></div>
85
+ <div id="pLogs" class="hidden text-[9px] text-slate-500 font-mono"></div>
86
  </div>
87
  </div>
88
  </div>
89
 
90
+ <!-- DRAWER -->
91
+ <aside id="drawer" class="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">
92
  <div class="flex justify-between items-center">
93
+ <span class="text-orange-400 font-bold">SCALAR TRI-MESH v5</span>
94
+ <button onclick="closeD()" class="text-slate-400 text-2xl leading-none">βœ•</button>
95
  </div>
96
 
97
+ <div class="bg-slate-900 rounded p-3 border border-blue-900/50 text-[9px] space-y-1">
98
+ <div class="text-blue-400 font-bold mb-1">ARCHITECTURE</div>
99
+ <div>β€’ Nodes: <span class="text-white">scalar (1 float)</span></div>
100
+ <div>β€’ Springs: <span class="text-white">scalar K (signed) per edge</span></div>
101
+ <div>β€’ Topology: <span class="text-white">9-row triangulated hourglass, widths [n,n+1,n+2,n+1,n,…]</span></div>
102
+ <div>β€’ Learning: <span class="text-white">LMS backprop on inter-row springs</span></div>
103
+ <div>β€’ Inference: <span class="text-white">K update at inference time, EWC-protected</span></div>
104
+ </div>
 
105
 
106
+ <!-- Offline training -->
107
+ <div class="bg-slate-900 rounded p-3 border border-purple-900/50">
108
+ <div class="text-purple-400 text-[9px] font-bold mb-2">OFFLINE LMS TRAINING</div>
109
+ <div class="flex gap-2 items-center">
110
+ <label class="text-[9px] text-slate-500 w-16">Epochs</label>
111
+ <input id="epN" type="number" value="5" min="1" max="30"
112
+ class="w-16 bg-black border border-slate-700 p-1 text-white text-sm text-center rounded">
113
+ <button onclick="doTrain()"
114
+ class="flex-1 bg-purple-800 hover:bg-purple-700 py-2 text-xs font-bold rounded">⚑ START</button>
115
  </div>
116
+ <div class="text-[8px] text-slate-600 mt-1">LMS runs at CPU speed. EWC Fisher accumulates automatically.</div>
117
  </div>
118
 
119
+ <!-- Inference -->
120
+ <div class="bg-slate-900 rounded p-3 border border-green-900/50">
121
+ <div class="text-green-400 text-[9px] font-bold mb-2">INFERENCE (heavy_b = OOD test)</div>
122
+ <div class="flex gap-2 items-center">
123
+ <label class="text-[9px] text-slate-500 w-16">N samples</label>
124
+ <input id="infN" type="number" value="400" min="10" max="2500"
125
+ class="w-16 bg-black border border-slate-700 p-1 text-white text-sm text-center rounded">
126
+ <button onclick="doInfer()"
127
+ class="flex-1 bg-green-800 hover:bg-green-700 py-2 text-xs font-bold rounded">β–Ά RUN</button>
 
 
 
128
  </div>
129
+ <div class="text-[8px] text-slate-600 mt-1">Springs update in real-time during inference (EWC on).</div>
130
+ </div>
131
+
132
+ <div class="flex gap-2">
133
+ <button onclick="doReset()"
134
+ class="flex-1 border border-slate-600 text-slate-400 py-2 rounded text-xs font-bold">RESET K</button>
135
+ <button onclick="doHalt(); closeD()"
136
+ class="flex-1 border border-red-700 text-red-500 py-2 rounded text-xs font-bold">HALT</button>
137
  </div>
138
  </aside>
139
 
140
  <script>
141
+ // ── STATE ─────────────────────────────────────────────────────────────────────
142
+ let curN = 1;
143
+ const KCLIP = 12.0;
 
144
 
145
+ function openD() { document.getElementById('drawer').classList.remove('closed'); }
146
+ function closeD() { document.getElementById('drawer').classList.add('closed'); }
 
 
 
147
 
148
+ async function chN(d) {
149
+ curN = Math.max(1, Math.min(8, curN + d));
150
+ document.getElementById('nDial').innerText = curN;
151
+ await fetch('/set_n', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({n:curN})});
152
+ meshReady = false;
153
  }
154
 
155
+ async function doTrain() {
156
+ const ep = parseInt(document.getElementById('epN').value);
157
+ await fetch('/train_offline',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({epochs:ep})});
 
 
 
158
  }
159
+ async function doViz() { await fetch('/train_visual',{method:'POST',headers:{'Content-Type':'application/json'},body:'{}'}); }
160
+ async function doInfer() {
161
+ const n = parseInt(document.getElementById('infN').value);
162
+ await fetch('/infer',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({n})});
163
+ closeD();
164
  }
165
+ async function doHalt() { await fetch('/halt',{method:'POST'}); }
166
+ async function doReset() { await fetch('/reset',{method:'POST'}); meshReady=false; }
167
 
168
+ // ── TABS ──────────────────────────────────────────────────────────────────────
169
  function tab(name) {
170
+ ['nodes','springs','acc','logs'].forEach(t => {
171
+ document.getElementById('p'+t.charAt(0).toUpperCase()+t.slice(1)).classList.toggle('hidden', t!==name);
172
+ const btn = document.getElementById('t'+t.charAt(0).toUpperCase()+t.slice(1));
173
+ btn.className = t===name
174
  ? 'flex-1 py-1 bg-blue-900/40 text-blue-300 font-bold text-[10px]'
175
  : 'flex-1 py-1 text-slate-500 text-[10px]';
176
  });
177
  }
178
 
179
+ // ── VISUALIZATION ─────────────────────────────────────────────────────────────
180
+
181
+ const KIND_COL = {A:'#fb923c', B:'#c084fc', C:'#38bdf8', H:'#4ade80'};
182
+ const KIND_SIZE = {A:18, B:18, C:20, H:9};
183
+
184
+ // Map K value β†’ edge color and width
185
+ function edgeCol(k) {
186
+ const t = Math.min(Math.abs(k)/KCLIP, 1);
187
+ if (k < 0) return [`rgba(30,${Math.round(80+t*120)},${Math.round(140+t*115)},${0.4+t*0.5})`, 0.5+t*2.5];
188
+ return [`rgba(${Math.round(80+t*160)},${Math.round(60+t*120)},20,${0.35+t*0.55})`, 0.5+t*2.5];
189
+ }
190
+
191
+ // Scale node positions so the widest row fits the display
192
+ function scalePos(nodes, margin=4.2) {
193
+ const pxs = Object.values(nodes).map(n=>n.px);
194
+ const pys = Object.values(nodes).map(n=>n.py);
195
+ const maxx = Math.max(...pxs.map(Math.abs)) || 1;
196
+ const sx = margin / maxx;
197
+ return {sx, sy:4.0}; // y already in [-1,1], scale to [-4,4]
198
+ }
199
+
200
+ function buildTraces(nodes, springs, triangles) {
201
+ const traces = [];
202
+ const {sx, sy} = scalePos(nodes);
203
+
204
+ // ── Triangle fills grouped by stress ─────────────────────────────────────
205
+ const buckets = {low:{xs:[],ys:[]}, mid:{xs:[],ys:[]}, high:{xs:[],ys:[]}};
206
+ for (const tri of triangles) {
207
+ const [p0,p1,p2] = tri.pos;
208
+ const s = tri.stress;
209
+ const b = s < 0.05 ? 'low' : s < 0.2 ? 'mid' : 'high';
210
+ buckets[b].xs.push(p0[0]*sx, p1[0]*sx, p2[0]*sx, p0[0]*sx, null);
211
+ buckets[b].ys.push(p0[1]*sy, p1[1]*sy, p2[1]*sy, p0[1]*sy, null);
212
+ }
213
+ const fills = {
214
+ low: ['rgba(56,189,248,0.04)','rgba(56,189,248,0.18)'],
215
+ mid: ['rgba(251,146,60,0.07)','rgba(251,146,60,0.28)'],
216
+ high: ['rgba(239,68,68,0.11)', 'rgba(239,68,68,0.42)'],
217
+ };
218
+ for (const [bk,td] of Object.entries(buckets)) {
219
+ if (!td.xs.length) continue;
220
+ traces.push({
221
+ type:'scatter', mode:'lines',
222
+ x:td.xs, y:td.ys,
223
+ fill:'toself', fillcolor:fills[bk][0],
224
+ line:{color:fills[bk][1], width:0.7},
225
+ hoverinfo:'none', showlegend:false
226
+ });
227
+ }
228
+
229
+ // ── Edges: vertical (colored by K) and horizontal (faint) ────────────────
230
+ const vertX=[], vertY=[], horzX=[], horzY=[];
231
+ for (const [, s] of Object.entries(springs)) {
232
+ const lx = [s.u_px*sx, s.v_px*sx, null];
233
+ const ly = [s.u_py*sy, s.v_py*sy, null];
234
+ if (s.vert) { vertX.push(...lx); vertY.push(...ly); }
235
+ else { horzX.push(...lx); horzY.push(...ly); }
236
+ }
237
+ // Horizontal springs: static faint lines (no per-spring coloring needed)
238
+ if (horzX.length) traces.push({
239
+ type:'scatter', mode:'lines',
240
+ x:horzX, y:horzY,
241
+ line:{color:'rgba(51,65,85,0.5)', width:0.6},
242
+ hoverinfo:'none', showlegend:false
243
+ });
244
+ // Vertical springs: color by K (group into pos/neg buckets for speed)
245
+ const vBuckets = {};
246
+ for (const [, s] of Object.entries(springs)) {
247
+ if (!s.vert) continue;
248
+ const bk = s.k >= 0 ? Math.floor(s.k/3) : `n${Math.floor(-s.k/3)}`;
249
+ if (!vBuckets[bk]) vBuckets[bk] = {k:s.k, xs:[], ys:[]};
250
+ vBuckets[bk].xs.push(s.u_px*sx, s.v_px*sx, null);
251
+ vBuckets[bk].ys.push(s.u_py*sy, s.v_py*sy, null);
252
+ }
253
+ for (const [, b] of Object.entries(vBuckets)) {
254
+ const [col, wd] = edgeCol(b.k);
255
+ traces.push({type:'scatter', mode:'lines', x:b.xs, y:b.ys,
256
+ line:{color:col, width:wd}, hoverinfo:'none', showlegend:false});
257
+ }
258
+
259
+ // ── Nodes ─────────────────────────────────────────────────────────────────
260
+ const ns = Object.entries(nodes);
261
+ traces.push({
262
+ type:'scatter', mode:'markers+text',
263
+ x: ns.map(([,n]) => n.px*sx),
264
+ y: ns.map(([,n]) => n.py*sy),
265
+ text: ns.map(([,n]) => {
266
+ if (n.kind==='H') return '';
267
+ return `${n.kind}\n${n.x.toFixed(2)}`;
268
+ }),
269
+ textposition: ns.map(([,n]) => n.py>0 ? 'top center' : 'bottom center'),
270
+ textfont:{size:8, color: ns.map(([,n])=>KIND_COL[n.kind])},
271
+ marker:{
272
+ size: ns.map(([,n]) => KIND_SIZE[n.kind] + Math.min(n.vel*25, 6)),
273
+ color: ns.map(([,n]) => KIND_COL[n.kind]),
274
+ opacity:ns.map(([,n]) => 0.72 + Math.min(n.vel*2, 0.28)),
275
+ line:{
276
+ width:2,
277
+ color: ns.map(([,n]) => n.anchored ? '#ef4444' : '#22c55e')
278
+ }
279
+ },
280
+ hoverinfo:'none', showlegend:false
281
+ });
282
+
283
+ return traces;
284
+ }
285
+
286
+ const MESH_LAYOUT = {
287
+ margin:{l:4,r:4,t:4,b:4},
288
+ paper_bgcolor:'transparent', plot_bgcolor:'transparent',
289
+ xaxis:{visible:false, range:[-5,5]},
290
+ yaxis:{visible:false, range:[-5,5]},
291
+ showlegend:false,
292
+ };
293
+ const ERR_LAYOUT = {
294
+ margin:{l:26,r:4,t:2,b:8},
295
+ paper_bgcolor:'transparent', plot_bgcolor:'transparent',
296
+ xaxis:{visible:false},
297
+ yaxis:{color:'#334155',gridcolor:'#0f172a',
298
+ zeroline:true,zerolinecolor:'#22c55e',zerolinewidth:1},
299
+ showlegend:false,
300
+ };
301
+
302
+ let meshReady=false, errReady=false, lastW='';
303
+
304
+ // ── POLL ──────────────────────────────────────────────────────────────────────
305
  setInterval(async () => {
306
  try {
307
+ const r = await fetch('/state');
308
+ const d = await r.json();
309
+
310
+ // Sync N dial
311
+ curN = d.n;
312
+ document.getElementById('nDial').innerText = d.n;
313
+
314
+ // Header badges
315
+ document.getElementById('bN').innerText = `n:${d.n}`;
316
+ document.getElementById('bSpr').innerText = `K:${d.n_vert}v+${d.n_springs-d.n_vert}h`;
317
+ document.getElementById('bType').className = `pt pt-${d.cur_type||'avg'}`;
318
+ document.getElementById('bType').innerText = d.cur_type || 'β€”';
319
+ const mc = {training:'bg-yellow-700 text-yellow-100',
320
+ inference:'bg-green-800 text-green-100',
321
+ idle:'bg-slate-800 text-slate-400'}[d.mode]||'bg-slate-800 text-slate-400';
322
+ const bm = document.getElementById('bMode');
323
+ bm.className = `px-1.5 py-0.5 rounded text-[8px] font-bold ${mc}`;
324
+ bm.innerText = d.mode?.toUpperCase()||'IDLE';
325
+ document.getElementById('dot').className = `w-2 h-2 rounded-full ${d.running?'bg-green-400':'bg-slate-700'}`;
326
+ document.getElementById('qLbl').innerText = `Q:${d.q_size}`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
 
328
+ // Tension
329
+ const e = Math.abs(d.error);
330
+ const ec = e<0.025?'text-green-400':e<0.15?'text-yellow-400':'text-red-400';
331
+ document.getElementById('eBig').className = `text-xl font-bold ${ec} leading-none`;
332
+ document.getElementById('eBig').innerText = d.error.toFixed(4);
333
+ document.getElementById('pVal').innerText = `P:${(d.c_pred||[0]).map(v=>v.toFixed(3)).join(',')}`;
334
+ document.getElementById('tVal').innerText = d.c_tgt ? `T:${d.c_tgt.map(v=>v.toFixed(3)).join(',')}` : 'T:β€”';
335
+ document.getElementById('itLbl').innerText = `IT:${d.iter}`;
336
+ document.getElementById('sLbl').innerText = `S:${d.step_cnt}`;
337
+
338
+ // ── Nodes pane ───────────────────────────────────────────────────────────
339
+ // Build layer-by-layer HTML
340
+ let nh = '';
341
+ (d.widths||[]).forEach((w,ri)=>{
342
+ const kinds = Object.values(d.nodes||{}).filter(n=>n.row===ri);
343
+ if (kinds.length===0) return;
344
+ const k0 = kinds[0].kind;
345
+ if (k0==='H' && ri!==1) return; // only show first hidden row + A/B/C
346
+ kinds.forEach(n=>{
347
+ const icon = n.anchored ? '⊠' : 'β—Ž';
348
+ const ic = n.anchored ? 'text-red-400' : 'text-green-500';
349
+ const nc = {A:'text-orange-300',B:'text-purple-300',C:'text-sky-300',H:'text-green-400'}[n.kind];
350
+ nh += `<div class="flex justify-between items-center py-0.5 border-b border-slate-900/50">
351
+ <span class="${ic} text-[9px]">${icon}</span>
352
+ <span class="${nc} font-bold ml-1">${n.kind}[r${n.row}]</span>
353
+ <span class="text-white font-bold">${n.x.toFixed(4)}</span>
354
+ <span class="text-slate-600 text-[8px]">v=${n.vel.toFixed(3)}</span>
355
+ </div>`;
356
  });
357
+ });
358
+ document.getElementById('pNodes').innerHTML = nh || '<div class="text-slate-700">No nodes loaded.</div>';
359
+
360
+ // ── Springs pane ─────────────────────────────────────────────────────────
361
+ let sh = '';
362
+ const sEntries = Object.entries(d.springs||{}).filter(([,s])=>s.vert).slice(0,50);
363
+ for (const [key,s] of sEntries) {
364
+ const kc = s.k<-0.5?'text-blue-300':s.k>3?'text-yellow-200':'text-purple-300';
365
+ const bw = Math.round(Math.min(Math.abs(s.k)/KCLIP,1)*56);
366
+ const bc = s.k<0?'bg-blue-700':'bg-amber-700';
367
+ const fc = s.fish>0.01?'text-orange-400':'text-slate-700';
368
+ sh += `<div class="flex items-center gap-1 py-0.5 border-b border-slate-900/40">
369
+ <div class="flex-grow h-1.5 bg-slate-900 rounded overflow-hidden">
370
+ <div class="${bc} h-full rounded" style="width:${bw}px"></div>
371
+ </div>
372
+ <span class="${kc} font-bold text-[9px] w-12 text-right">${s.k.toFixed(3)}</span>
373
+ <span class="${fc} text-[8px] w-12 text-right">F=${s.fish.toFixed(4)}</span>
374
+ </div>`;
375
+ }
376
+ if (!sh) sh='<div class="text-slate-700">No vertical springs yet.</div>';
377
+ document.getElementById('pSprings').innerHTML = sh;
378
 
379
+ // ── Accuracy pane ─────────────────────────────────────────────────────────
380
+ const acc=d.type_acc||{}, done=d.n_done||0;
381
+ let ah=`<div class="text-[9px] text-slate-500 mb-2">
382
+ Progress: <span class="text-white font-bold">${done}</span> / ${d.test_size||'?'}</div>`;
383
+ if (Object.keys(acc).length) {
384
+ const seen=Object.entries(acc).filter(([,v])=>!v.ood);
385
+ const ood =Object.entries(acc).filter(([,v])=> v.ood);
386
+ const row=([t,v])=>{
387
+ const ac=v.acc>=80?'text-green-400':v.acc>=50?'text-yellow-400':'text-red-400';
388
+ const b=v.ood?'<span class="ood">OOD</span>':'<span class="seen">SEEN</span>';
389
+ return `<tr class="border-b border-slate-900/60">
390
+ <td class="py-0.5"><span class="pt pt-${t}">${t}</span>${b}</td>
391
+ <td class="text-right text-slate-400">${v.n}</td>
392
+ <td class="text-right ${ac} font-bold">${v.acc}%</td>
393
+ <td class="text-right text-slate-400">${v.avg_err}</td>
394
+ <td class="text-right text-slate-600">${v.avg_steps}</td>
395
+ </tr>`;
396
  };
397
+ ah+=`<table class="w-full text-[9px] border-collapse">
398
+ <tr class="text-slate-600 border-b border-slate-800 text-[8px]">
399
+ <th class="text-left">Type</th><th class="text-right">N</th>
400
+ <th class="text-right">Acc%</th><th class="text-right">Avg|e|</th><th class="text-right">Steps</th>
401
+ </tr>${seen.map(row).join('')}
402
+ ${ood.length?`<tr><td colspan="5" class="pt-1 text-[8px] text-slate-600">── OOD (never seen) ──</td></tr>`:'' }
403
+ ${ood.map(row).join('')}</table>`;
404
+ if (done>20) {
405
+ const oa=ood.reduce((s,[,v])=>s+v.acc*v.n,0)/Math.max(ood.reduce((s,[,v])=>s+v.n,0),1);
406
+ const sa=seen.reduce((s,[,v])=>s+v.acc*v.n,0)/Math.max(seen.reduce((s,[,v])=>s+v.n,0),1);
407
+ const vc=oa>=80?'text-green-400':oa>=50?'text-yellow-400':'text-red-400';
408
+ ah+=`<div class="mt-2 p-2 bg-slate-900 rounded text-[9px]">
409
+ <span class="${vc} font-bold">${oa>=80?'βœ“ Generalises OOD':'~ Partial generalisation'}</span>
410
+ <span class="text-slate-500 ml-2">seen:${sa.toFixed(0)}% OOD:${oa.toFixed(0)}%</span>
411
+ </div>`;
412
+ }
413
+ } else ah+='<div class="text-slate-700 text-[9px]">Run inference to see accuracy.</div>';
414
+ document.getElementById('pAcc').innerHTML=ah;
415
 
416
+ // ── Logs pane ─────────────────────────────────────────────────────────────
417
+ document.getElementById('pLogs').innerHTML=
418
+ d.logs.map(l=>`<div class="py-0.5 border-b border-slate-900/40">${l}</div>`).join('');
419
 
420
+ // ── Mesh plot ─────────────────────────────────────────────────────────────
421
+ const wKey = JSON.stringify(d.widths);
422
+ const traces = buildTraces(d.nodes||{}, d.springs||{}, d.triangles||[]);
423
+ if (!meshReady || wKey!==lastW) {
424
+ Plotly.newPlot('meshPlot', traces, MESH_LAYOUT, {displayModeBar:false, responsive:true});
425
+ meshReady=true; lastW=wKey;
426
+ } else {
427
+ Plotly.react('meshPlot', traces, MESH_LAYOUT);
428
+ }
429
 
430
+ // ── Error history ─────────────────────────────────────────────────────────
431
+ const hist=d.history||[];
432
+ const et={type:'scatter',mode:'lines',
433
+ x:hist.map((_,i)=>i),y:hist,
434
+ line:{color:'#f97316',width:1.5},
435
+ fill:'tozeroy',fillcolor:'rgba(249,115,22,0.07)'};
436
+ if (!errReady) {
437
+ Plotly.newPlot('errPlot',[et],ERR_LAYOUT,{displayModeBar:false,responsive:true});
438
+ errReady=true;
439
+ } else {
440
+ Plotly.react('errPlot',[et],ERR_LAYOUT);
441
+ }
442
+ } catch(_){}
443
+ }, 220);
444
  </script>
445
  </body>
446
  </html>