Pepguy commited on
Commit
6529e4e
·
verified ·
1 Parent(s): 20b49c8

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +431 -126
index.html CHANGED
@@ -1,138 +1,443 @@
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>Topological Engine 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>
10
- body { background: #06090e; color: #cbd5e1; font-family: monospace; overflow: hidden; }
11
- .glass { background: rgba(15,23,42,0.95); border: 1px solid #334155; }
12
- #drawer { transition: transform 0.3s ease-in-out; z-index: 100; }
13
- .drawer-hidden { transform: translateY(100%); }
14
- </style>
 
15
  </head>
16
  <body class="flex flex-col h-screen overflow-hidden">
17
 
18
- <header class="glass p-3 flex justify-between items-center z-10">
19
- <h1 class="text-blue-400 font-bold text-xs uppercase">TOPOLOGICAL ENGINE</h1>
20
- <button onclick="toggleDrawer()" class="bg-blue-600 px-4 py-1 rounded text-white text-xs font-bold">⚙️ SYSTEM DIALS</button>
21
- </header>
 
 
 
 
 
22
 
23
- <aside id="drawer" class="fixed inset-x-0 bottom-0 drawer-hidden glass p-6 flex flex-col gap-5 z-50 max-h-[90vh] overflow-y-auto">
24
- <div class="flex justify-between items-center border-b border-slate-700 pb-2">
25
- <span class="text-orange-400 font-bold">LABORATORY CONTROLS</span>
26
- <button onclick="toggleDrawer()" class="text-slate-400 text-2xl">✕</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  </div>
28
-
29
- <div class="space-y-5">
30
- <!-- ARCHITECTURE -->
31
- <section class="bg-slate-900 border border-blue-900 p-3 rounded">
32
- <h3 class="text-blue-400 font-bold text-[10px] mb-2">1. MESH ARCHITECTURE (THE PHYSICS)</h3>
33
- <select id="architecture" class="w-full bg-black border border-slate-700 p-2 text-white">
34
- <option value="additive">Additive Base[(A*Ka) + (B*Kb) = C]</option>
35
- <option value="multiplicative">Multiplicative Base [(A*Ka) * (B*Kb) = C]</option>
36
- </select>
37
- <select id="mode" class="w-full mt-2 bg-black border border-slate-700 p-2 text-white">
38
- <option value="training">TRAINING PHASE (Learn K Factors)</option>
39
- <option value="inference">INFERENCE PHASE (Predict C)</option>
40
- </select>
41
- <button onclick="applyConfig()" class="w-full bg-blue-700 mt-2 py-2 font-bold rounded">APPLY PHYSICS</button>
42
- </section>
43
-
44
- <!-- DATASET -->
45
- <section class="bg-slate-900 border border-purple-900 p-3 rounded">
46
- <h3 class="text-purple-400 font-bold text-[10px] mb-2">2. DATASET INJECTOR (THE ENVIRONMENT)</h3>
47
- <select id="dataset" class="w-full bg-black border border-slate-700 p-2 text-white">
48
- <option value="housing">Housing (Hidden Law: A*2.5 + B*1.2)</option>
49
- <option value="subtraction">Subtraction (Hidden Law: A*1.0 + B*-1.0)</option>
50
- <option value="multiplication">Factorization (Hidden Law: A * B)</option>
51
- </select>
52
- <button onclick="genData()" class="w-full bg-purple-700 mt-2 py-2 font-bold rounded">START DATA BATCH</button>
53
- </section>
54
-
55
- <button onclick="halt()" class="w-full border border-red-500 text-red-500 py-3 rounded font-bold">HALT ENGINE</button>
56
  </div>
57
- </aside>
58
-
59
- <main class="flex-grow flex flex-col min-h-0">
60
- <div id="plot" class="flex-grow"></div>
61
-
62
- <div class="h-56 glass border-t border-slate-700 p-4 flex flex-col">
63
- <div class="flex justify-between items-center mb-1 border-b border-slate-800 pb-2">
64
- <h4 class="text-blue-300 text-[10px] font-bold tracking-widest">LIVE TOPOLOGY</h4>
65
- <div id="error-val" class="text-sm font-bold text-red-400">TENSION: 0.000</div>
66
- </div>
67
- <div id="nodes-ui" class="space-y-1 text-[11px] font-mono mt-2 flex-grow overflow-y-auto"></div>
 
 
 
68
  </div>
69
- </main>
70
-
71
- <script>
72
- function toggleDrawer() { document.getElementById('drawer').classList.toggle('drawer-hidden'); }
73
-
74
- async function applyConfig() {
75
- await fetch('/config', {
76
- method: 'POST', headers: {'Content-Type': 'application/json'},
77
- body: JSON.stringify({
78
- mode: document.getElementById('mode').value,
79
- architecture: document.getElementById('architecture').value,
80
- dataset: document.getElementById('dataset').value
81
- })
82
- });
83
- toggleDrawer();
84
- }
85
-
86
- async function genData() {
87
- await fetch('/generate', { method: 'POST' });
88
- toggleDrawer();
89
- }
90
-
91
- async function halt() { await fetch('/halt', {method: 'POST'}); toggleDrawer(); }
92
-
93
- setInterval(async () => {
94
- try {
95
- const res = await fetch('/state');
96
- const d = await res.json();
97
-
98
- const err = Math.abs(d.error);
99
- document.getElementById('error-val').innerText = "TENSION: " + d.error.toFixed(4);
100
- document.getElementById('error-val').className = err < 0.05 ? "text-green-400 font-bold" : "text-red-400 font-bold";
101
-
102
- let ui = "";
103
- let px=[], py=[], pz=[], ids=[];
104
- Object.keys(d.nodes).sort().forEach(id => {
105
- const n = d.nodes[id];
106
- let lockStr = n.anchored ? "[ANCHORED]" : "[DRIFTING]";
107
- let colorStr = n.anchored ? "text-red-300" : "text-sky-300";
108
- ui += `<div class="flex justify-between border-b border-slate-900 pb-1">
109
- <span class="${colorStr}">NODE ${id} ${lockStr}: <b class="text-white">${n.x.toFixed(3)}</b></span>
110
- <span class="text-purple-400 font-bold bg-purple-900/20 px-1 rounded">Stiffness (K): ${n.k.toFixed(4)}</span>
111
- </div>`;
112
- px.push(n.x); py.push(n.y); pz.push(0); ids.push(id);
113
- });
114
- document.getElementById('nodes-ui').innerHTML = ui;
115
-
116
- // Triangle Wireframe Connection
117
- const trace = {
118
- type: 'scatter3d', mode: 'lines+markers+text',
119
- x: px.concat([px[0]]), y: py.concat([py[0]]), z: pz.concat([pz[0]]),
120
- text: ids.concat(['']),
121
- marker: {size: 10, color:['#fb923c','#c084fc','#38bdf8']},
122
- line: {color:'#475569', width:3}
123
- };
124
-
125
- const layout = {
126
- margin:{l:0,r:0,t:0,b:0}, paper_bgcolor:'transparent',
127
- scene:{
128
- xaxis:{color:'#475569'}, yaxis:{range:[-3,3], visible:false}, zaxis:{range:[-1,1], visible:false},
129
- camera: {eye:{x:0, y:-1.5, z:0.5}}
130
- }
131
- };
132
- if(!window.plotted){ Plotly.newPlot('plot', [trace], layout, {displayModeBar:false}); window.plotted=true; }
133
- else { Plotly.react('plot', [trace], layout); }
134
- } catch(e){}
135
- }, 150);
136
- </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  </body>
138
  </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.0" />
6
+ <title>Topological Engine 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>
10
+ body { background: #06090e; color: #cbd5e1; font-family: monospace; overflow: hidden; }
11
+ .glass { background: rgba(15,23,42,0.96); border: 1px solid #334155; }
12
+ #drawer { transition: transform 0.3s ease-in-out; z-index: 100; }
13
+ .drawer-hidden { transform: translateY(100%); }
14
+ .panel-title { letter-spacing: 0.18em; text-transform: uppercase; }
15
+ </style>
16
  </head>
17
  <body class="flex flex-col h-screen overflow-hidden">
18
 
19
+ <header class="glass p-3 flex justify-between items-center z-10">
20
+ <div>
21
+ <h1 id="logic-label" class="text-blue-400 font-bold text-xs uppercase">TOPOLOGICAL ENGINE</h1>
22
+ <p id="sub-label" class="text-[10px] text-slate-500 mt-1">Ready</p>
23
+ </div>
24
+ <button onclick="toggleDrawer()" class="bg-blue-600 px-4 py-1 rounded text-white text-xs font-bold">
25
+ ⚙️ SYSTEM DIALS
26
+ </button>
27
+ </header>
28
 
29
+ <aside id="drawer" class="fixed inset-x-0 bottom-0 drawer-hidden glass p-6 flex flex-col gap-5 z-50 max-h-[92vh] overflow-y-auto">
30
+ <div class="flex justify-between items-center border-b border-slate-700 pb-2">
31
+ <span class="text-orange-400 font-bold panel-title text-xs">LABORATORY CONTROLS</span>
32
+ <button onclick="toggleDrawer()" class="text-slate-400 text-2xl">✕</button>
33
+ </div>
34
+
35
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
36
+ <section class="bg-slate-900 border border-blue-900 p-3 rounded">
37
+ <h3 class="text-blue-400 font-bold text-[10px] mb-2 panel-title">1. ARCHITECTURE</h3>
38
+ <select id="architecture" class="w-full bg-black border border-slate-700 p-2 text-white rounded">
39
+ <option value="additive">Additive</option>
40
+ <option value="multiplicative">Multiplicative</option>
41
+ <option value="affine">Affine</option>
42
+ <option value="bilinear">Bilinear</option>
43
+ <option value="gated">Gated</option>
44
+ </select>
45
+
46
+ <select id="coeff_mode" class="w-full mt-2 bg-black border border-slate-700 p-2 text-white rounded">
47
+ <option value="single_k">Single K</option>
48
+ <option value="triple_k">Triple K</option>
49
+ <option value="per_edge_k">Per Edge K</option>
50
+ </select>
51
+
52
+ <select id="topology" class="w-full mt-2 bg-black border border-slate-700 p-2 text-white rounded">
53
+ <option value="single_cell">Single Cell</option>
54
+ <option value="chain">Chain</option>
55
+ <option value="mesh">Mesh</option>
56
+ </select>
57
+
58
+ <select id="mode" class="w-full mt-2 bg-black border border-slate-700 p-2 text-white rounded">
59
+ <option value="training">Training</option>
60
+ <option value="inference">Inference</option>
61
+ </select>
62
+
63
+ <div class="grid grid-cols-2 gap-2 mt-2">
64
+ <input id="num_cells" type="number" min="1" value="4" class="bg-black border border-slate-700 p-2 text-white rounded" />
65
+ <input id="batch_count" type="number" min="1" value="24" class="bg-black border border-slate-700 p-2 text-white rounded" />
66
+ </div>
67
+
68
+ <div class="grid grid-cols-3 gap-2 mt-2">
69
+ <input id="learning_rate" type="number" step="0.001" value="0.01" class="bg-black border border-slate-700 p-2 text-white rounded" />
70
+ <input id="coupling" type="number" step="0.001" value="0.05" class="bg-black border border-slate-700 p-2 text-white rounded" />
71
+ <input id="damping" type="number" step="0.001" value="0.12" class="bg-black border border-slate-700 p-2 text-white rounded" />
72
+ </div>
73
+
74
+ <button onclick="applyConfig()" class="w-full bg-blue-700 mt-3 py-2 font-bold rounded text-white">APPLY PHYSICS</button>
75
+ </section>
76
+
77
+ <section class="bg-slate-900 border border-purple-900 p-3 rounded">
78
+ <h3 class="text-purple-400 font-bold text-[10px] mb-2 panel-title">2. DATASET</h3>
79
+ <select id="dataset_family" class="w-full bg-black border border-slate-700 p-2 text-white rounded">
80
+ <option value="housing">Housing</option>
81
+ <option value="subtraction">Subtraction</option>
82
+ <option value="multiplication">Multiplication</option>
83
+ <option value="symbolic">Symbolic</option>
84
+ <option value="mixed">Mixed</option>
85
+ </select>
86
+
87
+ <button onclick="previewExample()" class="w-full bg-purple-700 mt-3 py-2 font-bold rounded text-white">GENERATE EXAMPLE</button>
88
+ <button onclick="startBatch()" class="w-full bg-indigo-700 mt-2 py-2 font-bold rounded text-white">START BATCH</button>
89
+
90
+ <div class="mt-3 p-3 rounded border border-slate-700 bg-black/40 text-[11px] space-y-1">
91
+ <div class="text-slate-400 panel-title text-[10px]">EXAMPLE PREVIEW</div>
92
+ <div id="example-view" class="text-slate-300">No sample loaded.</div>
93
  </div>
94
+ </section>
95
+
96
+ <section class="bg-slate-900 border border-cyan-900 p-3 rounded">
97
+ <h3 class="text-cyan-400 font-bold text-[10px] mb-2 panel-title">3. CUSTOM INPUTS</h3>
98
+ <div class="grid grid-cols-3 gap-2">
99
+ <input id="custom_a" type="number" step="0.01" value="7" class="bg-black border border-slate-700 p-2 text-white rounded" placeholder="A" />
100
+ <input id="custom_b" type="number" step="0.01" value="8" class="bg-black border border-slate-700 p-2 text-white rounded" placeholder="B" />
101
+ <input id="custom_c" type="number" step="0.01" class="bg-black border border-slate-700 p-2 text-white rounded" placeholder="C target" />
102
+ </div>
103
+ <button onclick="runCustom()" class="w-full bg-cyan-700 mt-3 py-2 font-bold rounded text-white">RUN CUSTOM</button>
104
+ </section>
105
+
106
+ <section class="bg-slate-900 border border-red-900 p-3 rounded flex flex-col justify-between">
107
+ <h3 class="text-red-400 font-bold text-[10px] mb-2 panel-title">4. CONTROL</h3>
108
+ <button onclick="halt()" class="w-full border border-red-500 text-red-400 py-3 rounded font-bold">HALT ENGINE</button>
109
+ <div class="mt-3 text-[11px] text-slate-500">
110
+ Batch count and configuration are independent. The same data family can be tested under different physics.
 
 
 
 
 
 
 
 
 
 
 
111
  </div>
112
+ </section>
113
+ </div>
114
+ </aside>
115
+
116
+ <main class="flex-grow flex flex-col min-h-0">
117
+ <div class="flex flex-col xl:flex-row flex-grow min-h-0">
118
+ <div class="flex-1 min-h-[320px]" id="plot"></div>
119
+ <div class="xl:w-[34rem] w-full glass border-l border-slate-800 p-4 overflow-y-auto">
120
+ <div class="flex justify-between items-center mb-3 border-b border-slate-800 pb-2">
121
+ <div>
122
+ <div class="text-blue-300 text-[10px] font-bold panel-title">LIVE STATUS</div>
123
+ <div id="status-line" class="text-[11px] text-slate-400 mt-1">Waiting...</div>
124
+ </div>
125
+ <div id="error-val" class="text-sm font-bold text-red-400">ERR: 0.000</div>
126
  </div>
127
+
128
+ <div id="summary-box" class="grid grid-cols-2 gap-2 text-[11px] mb-4"></div>
129
+
130
+ <div class="mb-4">
131
+ <div class="text-[10px] text-slate-500 panel-title mb-2">CELL STATE</div>
132
+ <div id="cells-ui" class="space-y-2 text-[11px] font-mono"></div>
133
+ </div>
134
+
135
+ <div class="mb-4">
136
+ <div class="text-[10px] text-slate-500 panel-title mb-2">LOSS HISTORY</div>
137
+ <div id="loss-plot" class="h-56"></div>
138
+ </div>
139
+
140
+ <div>
141
+ <div class="text-[10px] text-slate-500 panel-title mb-2">LOGS</div>
142
+ <div id="logs" class="mt-auto pt-2 border-t border-slate-800 text-[10px] text-slate-500 h-44 overflow-y-auto space-y-1"></div>
143
+ </div>
144
+ </div>
145
+ </div>
146
+ </main>
147
+
148
+ <script>
149
+ const state = {
150
+ plotted: false,
151
+ lossPlotted: false,
152
+ example: null,
153
+ historyLoss: [],
154
+ historyError: []
155
+ };
156
+
157
+ function toggleDrawer() {
158
+ document.getElementById('drawer').classList.toggle('drawer-hidden');
159
+ }
160
+
161
+ function readConfig() {
162
+ return {
163
+ architecture: document.getElementById('architecture').value,
164
+ coeff_mode: document.getElementById('coeff_mode').value,
165
+ topology: document.getElementById('topology').value,
166
+ dataset_family: document.getElementById('dataset_family').value,
167
+ mode: document.getElementById('mode').value,
168
+ num_cells: parseInt(document.getElementById('num_cells').value || '1', 10),
169
+ batch_size: parseInt(document.getElementById('batch_count').value || '24', 10),
170
+ learning_rate: parseFloat(document.getElementById('learning_rate').value || '0.01'),
171
+ coupling: parseFloat(document.getElementById('coupling').value || '0.05'),
172
+ damping: parseFloat(document.getElementById('damping').value || '0.12'),
173
+ };
174
+ }
175
+
176
+ function setHeaderFromConfig(cfg) {
177
+ document.getElementById('logic-label').innerText =
178
+ `TOPOLOGICAL ENGINE | ${cfg.architecture.toUpperCase()} | ${cfg.coeff_mode.toUpperCase()} | ${cfg.topology.toUpperCase()}`;
179
+ document.getElementById('sub-label').innerText =
180
+ `${cfg.dataset_family.toUpperCase()} / ${cfg.mode.toUpperCase()}`;
181
+ }
182
+
183
+ async function applyConfig() {
184
+ const cfg = readConfig();
185
+ setHeaderFromConfig(cfg);
186
+
187
+ await fetch('/config', {
188
+ method: 'POST',
189
+ headers: {'Content-Type': 'application/json'},
190
+ body: JSON.stringify(cfg)
191
+ });
192
+
193
+ await previewExample(false);
194
+ toggleDrawer();
195
+ }
196
+
197
+ async function previewExample(fillInputs = true) {
198
+ const cfg = readConfig();
199
+ const res = await fetch('/example', {
200
+ method: 'POST',
201
+ headers: {'Content-Type': 'application/json'},
202
+ body: JSON.stringify(cfg)
203
+ });
204
+ const sample = await res.json();
205
+ state.example = sample;
206
+
207
+ const ex = document.getElementById('example-view');
208
+ ex.innerHTML = `
209
+ <div class="text-slate-200">a: <b>${sample.a.toFixed(3)}</b></div>
210
+ <div class="text-slate-200">b: <b>${sample.b.toFixed(3)}</b></div>
211
+ <div class="text-slate-200">c: <b>${sample.c.toFixed(3)}</b></div>
212
+ <div class="text-slate-400">label: ${sample.label}</div>
213
+ `;
214
+
215
+ if (fillInputs) {
216
+ document.getElementById('custom_a').value = sample.a.toFixed(3);
217
+ document.getElementById('custom_b').value = sample.b.toFixed(3);
218
+ document.getElementById('custom_c').value = sample.c.toFixed(3);
219
+ }
220
+ }
221
+
222
+ async function startBatch() {
223
+ const cfg = readConfig();
224
+ await fetch('/config', {
225
+ method: 'POST',
226
+ headers: {'Content-Type': 'application/json'},
227
+ body: JSON.stringify(cfg)
228
+ });
229
+ await fetch('/generate_batch', {
230
+ method: 'POST',
231
+ headers: {'Content-Type': 'application/json'},
232
+ body: JSON.stringify({ count: cfg.batch_size })
233
+ });
234
+ toggleDrawer();
235
+ }
236
+
237
+ async function runCustom() {
238
+ const a = document.getElementById('custom_a').value;
239
+ const b = document.getElementById('custom_b').value;
240
+ const c = document.getElementById('custom_c').value;
241
+
242
+ await fetch('/test_custom', {
243
+ method: 'POST',
244
+ headers: {'Content-Type': 'application/json'},
245
+ body: JSON.stringify({ a, b, c: c === '' ? null : c })
246
+ });
247
+ toggleDrawer();
248
+ }
249
+
250
+ async function halt() {
251
+ await fetch('/halt', { method: 'POST' });
252
+ toggleDrawer();
253
+ }
254
+
255
+ function fmt(n) {
256
+ if (n === null || n === undefined || Number.isNaN(n)) return '—';
257
+ return Number(n).toFixed(4);
258
+ }
259
+
260
+ function renderSummary(d) {
261
+ const cfg = d.config;
262
+ const sample = d.current_sample || {};
263
+ const last = d.last_sample || sample;
264
+
265
+ const items = [
266
+ ['Mode', cfg.mode],
267
+ ['Arch', cfg.architecture],
268
+ ['Coeffs', cfg.coeff_mode],
269
+ ['Topology', cfg.topology],
270
+ ['Dataset', cfg.dataset_family],
271
+ ['Batch left', d.batch_remaining],
272
+ ['Iteration', d.iteration],
273
+ ['Sample', last.label || '—'],
274
+ ['Error', fmt(d.current_error)],
275
+ ['Loss', fmt(d.current_loss)],
276
+ ['A', sample.a !== undefined ? fmt(sample.a) : '—'],
277
+ ['B', sample.b !== undefined ? fmt(sample.b) : '—'],
278
+ ];
279
+
280
+ document.getElementById('summary-box').innerHTML = items.map(([k, v]) => `
281
+ <div class="bg-black/40 border border-slate-800 rounded p-2">
282
+ <div class="text-[9px] text-slate-500 panel-title">${k}</div>
283
+ <div class="text-[12px] text-slate-100 mt-1">${v}</div>
284
+ </div>
285
+ `).join('');
286
+ }
287
+
288
+ function renderCells(d) {
289
+ const cells = d.cells || [];
290
+ document.getElementById('cells-ui').innerHTML = cells.map(cell => `
291
+ <div class="border border-slate-800 rounded p-3 bg-black/30">
292
+ <div class="flex justify-between items-center mb-1">
293
+ <div class="text-sky-300 font-bold">CELL ${cell.id} ${cell.anchored ? '[ANCHORED]' : '[DRIFTING]'}</div>
294
+ <div class="text-slate-400">label: ${cell.label || '—'}</div>
295
+ </div>
296
+ <div class="grid grid-cols-2 gap-x-3 gap-y-1">
297
+ <div>A: <span class="text-white">${fmt(cell.a)}</span></div>
298
+ <div>B: <span class="text-white">${fmt(cell.b)}</span></div>
299
+ <div>C: <span class="text-white">${fmt(cell.c)}</span></div>
300
+ <div>T: <span class="text-white">${cell.target !== null ? fmt(cell.target) : '—'}</span></div>
301
+ <div>P: <span class="text-white">${fmt(cell.prediction)}</span></div>
302
+ <div>E: <span class="text-white">${fmt(cell.error)}</span></div>
303
+ <div>Energy: <span class="text-white">${fmt(cell.energy)}</span></div>
304
+ <div>Force: <span class="text-white">${fmt(cell.force)}</span></div>
305
+ </div>
306
+ <div class="mt-2 flex gap-3 text-[10px] text-purple-300">
307
+ <div>k: ${fmt(cell.k)}</div>
308
+ <div>ka: ${fmt(cell.ka)}</div>
309
+ <div>kb: ${fmt(cell.kb)}</div>
310
+ <div>kc: ${fmt(cell.kc)}</div>
311
+ </div>
312
+ </div>
313
+ `).join('');
314
+ }
315
+
316
+ function renderLogs(d) {
317
+ document.getElementById('logs').innerHTML = (d.logs || []).map(l => `<div>${l}</div>`).join('');
318
+ }
319
+
320
+ function renderTopPlot(d) {
321
+ const cells = d.cells || [];
322
+ const xsPred = cells.map(c => c.prediction);
323
+ const ys = cells.map(c => c.id);
324
+ const zsErr = cells.map(c => c.error);
325
+ const labels = cells.map(c => `cell ${c.id}`);
326
+
327
+ const xsTarget = cells.map(c => c.target !== null ? c.target : null).filter(v => v !== null);
328
+ const ysTarget = cells.map(c => c.target !== null ? c.id : null).filter(v => v !== null);
329
+ const zsTarget = cells.map(() => 0).slice(0, xsTarget.length);
330
+
331
+ const trace1 = {
332
+ type: 'scatter3d',
333
+ mode: 'lines+markers+text',
334
+ x: xsPred,
335
+ y: ys,
336
+ z: zsErr,
337
+ text: labels,
338
+ textposition: 'top center',
339
+ marker: { size: 7 },
340
+ line: { width: 3 }
341
+ };
342
+
343
+ const trace2 = {
344
+ type: 'scatter3d',
345
+ mode: 'markers',
346
+ x: xsTarget,
347
+ y: ysTarget,
348
+ z: zsTarget,
349
+ marker: { size: 5, symbol: 'circle' }
350
+ };
351
+
352
+ const layout = {
353
+ margin: { l: 0, r: 0, t: 0, b: 0 },
354
+ paper_bgcolor: 'transparent',
355
+ plot_bgcolor: 'transparent',
356
+ scene: {
357
+ xaxis: { title: 'Prediction / C', color: '#475569', gridcolor: '#1e293b' },
358
+ yaxis: { title: 'Cell index', color: '#475569', gridcolor: '#1e293b' },
359
+ zaxis: { title: 'Error', color: '#475569', gridcolor: '#1e293b' },
360
+ camera: { eye: { x: 0.9, y: -1.8, z: 0.8 } }
361
+ },
362
+ showlegend: false
363
+ };
364
+
365
+ if (!window.topPlotted) {
366
+ Plotly.newPlot('plot', [trace1, trace2], layout, { displayModeBar: false, responsive: true });
367
+ window.topPlotted = true;
368
+ } else {
369
+ Plotly.react('plot', [trace1, trace2], layout, { displayModeBar: false, responsive: true });
370
+ }
371
+ }
372
+
373
+ function renderLossPlot(d) {
374
+ const loss = d.loss_history || [];
375
+ const err = d.error_history || [];
376
+ const x = loss.map((_, i) => i);
377
+
378
+ const lossTrace = {
379
+ type: 'scatter',
380
+ mode: 'lines',
381
+ x: x,
382
+ y: loss,
383
+ name: 'loss'
384
+ };
385
+
386
+ const errTrace = {
387
+ type: 'scatter',
388
+ mode: 'lines',
389
+ x: x,
390
+ y: err,
391
+ name: 'error'
392
+ };
393
+
394
+ const layout = {
395
+ margin: { l: 35, r: 10, t: 10, b: 30 },
396
+ paper_bgcolor: 'transparent',
397
+ plot_bgcolor: 'transparent',
398
+ xaxis: { color: '#94a3b8', gridcolor: '#1e293b' },
399
+ yaxis: { color: '#94a3b8', gridcolor: '#1e293b' },
400
+ showlegend: true,
401
+ legend: { orientation: 'h' }
402
+ };
403
+
404
+ if (!window.lossPlotted) {
405
+ Plotly.newPlot('loss-plot', [lossTrace, errTrace], layout, { displayModeBar: false, responsive: true });
406
+ window.lossPlotted = true;
407
+ } else {
408
+ Plotly.react('loss-plot', [lossTrace, errTrace], layout, { displayModeBar: false, responsive: true });
409
+ }
410
+ }
411
+
412
+ async function tick() {
413
+ try {
414
+ const res = await fetch('/state');
415
+ const d = await res.json();
416
+
417
+ document.getElementById('error-val').innerText = 'ERR: ' + fmt(d.current_error);
418
+ document.getElementById('error-val').className =
419
+ Math.abs(d.current_error) < 0.05 ? 'text-green-400 font-bold' : 'text-red-400 font-bold';
420
+
421
+ document.getElementById('status-line').innerText =
422
+ d.running ? 'Engine running...' : 'Engine idle.';
423
+
424
+ renderSummary(d);
425
+ renderCells(d);
426
+ renderLogs(d);
427
+ renderTopPlot(d);
428
+ renderLossPlot(d);
429
+ } catch (e) {
430
+ document.getElementById('status-line').innerText = 'Disconnected.';
431
+ }
432
+ }
433
+
434
+ // initial example + first config sync
435
+ (async () => {
436
+ const cfg = readConfig();
437
+ setHeaderFromConfig(cfg);
438
+ await previewExample(false);
439
+ setInterval(tick, 180);
440
+ })();
441
+ </script>
442
  </body>
443
  </html>