entroplain 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DEPLOY.md +41 -0
- package/README.md +478 -476
- package/dist/entroplain-0.2.2-py3-none-any.whl +0 -0
- package/dist/entroplain-0.2.2.tar.gz +0 -0
- package/dist/entroplain-0.2.3-py3-none-any.whl +0 -0
- package/dist/entroplain-0.2.3.tar.gz +0 -0
- package/docs/AGENT_USAGE.md +178 -178
- package/docs/USAGE.md +302 -302
- package/entroplain/__init__.py +5 -3
- package/entroplain/cost_tracker.py +231 -231
- package/entroplain/dashboard.py +480 -368
- package/entroplain/monitor.py +390 -390
- package/entroplain/providers.py +626 -626
- package/entroplain/proxy.py +561 -349
- package/entroplain/shared_state.py +72 -0
- package/package.json +47 -46
- package/pyproject.toml +1 -1
- package/scripts/setup.bat +89 -0
- package/scripts/setup.sh +98 -0
- package/test_nvidia.py +56 -56
- package/test_proxy.py +16 -16
- package/vercel.json +6 -0
- package/website/README.md +14 -0
- package/website/app/globals.css +88 -0
- package/website/app/layout.tsx +34 -0
- package/website/app/page.tsx +537 -0
- package/website/package-lock.json +520 -0
- package/website/package.json +25 -0
- package/website/tsconfig.json +40 -0
- package/website/vercel.json +3 -0
- package/dist/entroplain-0.2.0-py3-none-any.whl +0 -0
- package/dist/entroplain-0.2.0.tar.gz +0 -0
package/entroplain/dashboard.py
CHANGED
|
@@ -1,368 +1,480 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Real-time entropy visualization dashboard.
|
|
3
|
-
|
|
4
|
-
Run with: entroplain-dashboard --port
|
|
5
|
-
Then open: http://localhost:8050
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import asyncio
|
|
9
|
-
import json
|
|
10
|
-
from datetime import datetime
|
|
11
|
-
from typing import Dict, List, Any, Optional
|
|
12
|
-
from dataclasses import dataclass, field
|
|
13
|
-
from fastapi import FastAPI, WebSocket
|
|
14
|
-
from fastapi.responses import HTMLResponse
|
|
15
|
-
import uvicorn
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@dataclass
|
|
19
|
-
class DashboardConfig:
|
|
20
|
-
"""Configuration for the dashboard."""
|
|
21
|
-
port: int = 8050
|
|
22
|
-
proxy_port: int = 8765
|
|
23
|
-
update_interval_ms: int = 100
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
#
|
|
27
|
-
DASHBOARD_HTML = """
|
|
28
|
-
<!DOCTYPE html>
|
|
29
|
-
<html>
|
|
30
|
-
<head>
|
|
31
|
-
<title>Entroplain Dashboard</title>
|
|
32
|
-
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
33
|
-
<style>
|
|
34
|
-
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
35
|
-
body {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
display:
|
|
59
|
-
|
|
60
|
-
gap: 15px;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
font-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
.
|
|
91
|
-
.
|
|
92
|
-
|
|
93
|
-
.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
<
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
1
|
+
"""
|
|
2
|
+
Real-time entropy visualization dashboard.
|
|
3
|
+
|
|
4
|
+
Run with: entroplain-dashboard --port 8050
|
|
5
|
+
Then open: http://localhost:8050
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import json
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from typing import Dict, List, Any, Optional
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
|
14
|
+
from fastapi.responses import HTMLResponse
|
|
15
|
+
import uvicorn
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class DashboardConfig:
|
|
20
|
+
"""Configuration for the dashboard."""
|
|
21
|
+
port: int = 8050
|
|
22
|
+
proxy_port: int = 8765
|
|
23
|
+
update_interval_ms: int = 100
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Fixed-height dashboard HTML
|
|
27
|
+
DASHBOARD_HTML = """
|
|
28
|
+
<!DOCTYPE html>
|
|
29
|
+
<html>
|
|
30
|
+
<head>
|
|
31
|
+
<title>Entroplain Dashboard</title>
|
|
32
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
33
|
+
<style>
|
|
34
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
35
|
+
html, body {
|
|
36
|
+
height: 100%;
|
|
37
|
+
overflow: hidden;
|
|
38
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
39
|
+
background: #0a0a0a;
|
|
40
|
+
color: #e0e0e0;
|
|
41
|
+
}
|
|
42
|
+
.app {
|
|
43
|
+
height: 100vh;
|
|
44
|
+
display: flex;
|
|
45
|
+
flex-direction: column;
|
|
46
|
+
padding: 20px;
|
|
47
|
+
max-width: 1400px;
|
|
48
|
+
margin: 0 auto;
|
|
49
|
+
}
|
|
50
|
+
h1 {
|
|
51
|
+
font-size: 20px;
|
|
52
|
+
margin-bottom: 15px;
|
|
53
|
+
color: #4ade80;
|
|
54
|
+
flex-shrink: 0;
|
|
55
|
+
}
|
|
56
|
+
.main-grid {
|
|
57
|
+
flex: 1;
|
|
58
|
+
display: grid;
|
|
59
|
+
grid-template-columns: 1fr 280px;
|
|
60
|
+
gap: 15px;
|
|
61
|
+
min-height: 0;
|
|
62
|
+
}
|
|
63
|
+
.chart-container {
|
|
64
|
+
background: #1a1a1a;
|
|
65
|
+
border-radius: 8px;
|
|
66
|
+
padding: 15px;
|
|
67
|
+
display: flex;
|
|
68
|
+
flex-direction: column;
|
|
69
|
+
min-height: 0;
|
|
70
|
+
}
|
|
71
|
+
.chart-wrapper {
|
|
72
|
+
flex: 1;
|
|
73
|
+
position: relative;
|
|
74
|
+
min-height: 0;
|
|
75
|
+
}
|
|
76
|
+
.chart-wrapper canvas {
|
|
77
|
+
position: absolute;
|
|
78
|
+
top: 0;
|
|
79
|
+
left: 0;
|
|
80
|
+
width: 100%;
|
|
81
|
+
height: 100%;
|
|
82
|
+
}
|
|
83
|
+
.legend {
|
|
84
|
+
display: flex;
|
|
85
|
+
gap: 15px;
|
|
86
|
+
margin-top: 10px;
|
|
87
|
+
font-size: 11px;
|
|
88
|
+
flex-shrink: 0;
|
|
89
|
+
}
|
|
90
|
+
.legend-item { display: flex; align-items: center; gap: 5px; }
|
|
91
|
+
.legend-dot { width: 8px; height: 8px; border-radius: 50%; }
|
|
92
|
+
.dot-entropy { background: #60a5fa; }
|
|
93
|
+
.dot-valley { background: #f59e0b; }
|
|
94
|
+
.dot-threshold { background: #ef4444; }
|
|
95
|
+
|
|
96
|
+
.stats-panel {
|
|
97
|
+
display: flex;
|
|
98
|
+
flex-direction: column;
|
|
99
|
+
gap: 10px;
|
|
100
|
+
overflow-y: auto;
|
|
101
|
+
}
|
|
102
|
+
.stat-card {
|
|
103
|
+
background: #1a1a1a;
|
|
104
|
+
border-radius: 8px;
|
|
105
|
+
padding: 12px;
|
|
106
|
+
flex-shrink: 0;
|
|
107
|
+
}
|
|
108
|
+
.stat-label {
|
|
109
|
+
font-size: 10px;
|
|
110
|
+
color: #888;
|
|
111
|
+
text-transform: uppercase;
|
|
112
|
+
letter-spacing: 0.05em;
|
|
113
|
+
}
|
|
114
|
+
.stat-value {
|
|
115
|
+
font-size: 24px;
|
|
116
|
+
font-weight: 600;
|
|
117
|
+
color: #fff;
|
|
118
|
+
margin-top: 4px;
|
|
119
|
+
}
|
|
120
|
+
.stat-value.savings { color: #4ade80; }
|
|
121
|
+
.stat-value.cost { color: #fbbf24; }
|
|
122
|
+
.stat-value.valleys { color: #60a5fa; }
|
|
123
|
+
|
|
124
|
+
.status-badge {
|
|
125
|
+
display: inline-block;
|
|
126
|
+
padding: 3px 10px;
|
|
127
|
+
border-radius: 10px;
|
|
128
|
+
font-size: 11px;
|
|
129
|
+
font-weight: 500;
|
|
130
|
+
}
|
|
131
|
+
.status-active { background: #22c55e; color: #000; }
|
|
132
|
+
.status-idle { background: #374151; color: #888; }
|
|
133
|
+
.status-exited { background: #f59e0b; color: #000; }
|
|
134
|
+
|
|
135
|
+
.connection-status {
|
|
136
|
+
padding: 8px 12px;
|
|
137
|
+
border-radius: 6px;
|
|
138
|
+
font-size: 11px;
|
|
139
|
+
margin-bottom: 10px;
|
|
140
|
+
}
|
|
141
|
+
.connected { background: #166534; color: #4ade80; }
|
|
142
|
+
.disconnected { background: #7f1d1d; color: #fca5a5; }
|
|
143
|
+
|
|
144
|
+
.waiting-message {
|
|
145
|
+
display: flex;
|
|
146
|
+
flex-direction: column;
|
|
147
|
+
align-items: center;
|
|
148
|
+
justify-content: center;
|
|
149
|
+
height: 100%;
|
|
150
|
+
color: #666;
|
|
151
|
+
text-align: center;
|
|
152
|
+
}
|
|
153
|
+
.waiting-message h2 { font-size: 16px; margin-bottom: 8px; color: #888; }
|
|
154
|
+
.waiting-message p { font-size: 12px; }
|
|
155
|
+
</style>
|
|
156
|
+
</head>
|
|
157
|
+
<body>
|
|
158
|
+
<div class="app">
|
|
159
|
+
<h1>🎯 Entroplain Dashboard</h1>
|
|
160
|
+
|
|
161
|
+
<div class="main-grid">
|
|
162
|
+
<div class="chart-container">
|
|
163
|
+
<div id="connectionStatus" class="connection-status disconnected">
|
|
164
|
+
Connecting...
|
|
165
|
+
</div>
|
|
166
|
+
<div class="chart-wrapper">
|
|
167
|
+
<canvas id="entropyChart"></canvas>
|
|
168
|
+
<div id="waitingMessage" class="waiting-message">
|
|
169
|
+
<h2>Waiting for data...</h2>
|
|
170
|
+
<p>Make a request through the proxy to see entropy visualization</p>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
<div class="legend">
|
|
174
|
+
<div class="legend-item"><div class="legend-dot dot-entropy"></div><span>Entropy</span></div>
|
|
175
|
+
<div class="legend-item"><div class="legend-dot dot-valley"></div><span>Valley</span></div>
|
|
176
|
+
<div class="legend-item"><div class="legend-dot dot-threshold"></div><span>Threshold</span></div>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
<div class="stats-panel">
|
|
181
|
+
<div class="stat-card">
|
|
182
|
+
<div class="stat-label">Status</div>
|
|
183
|
+
<div class="stat-value" id="status"><span class="status-badge status-idle">Idle</span></div>
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
<div class="stat-card">
|
|
187
|
+
<div class="stat-label">Tokens Generated</div>
|
|
188
|
+
<div class="stat-value" id="tokens">0</div>
|
|
189
|
+
</div>
|
|
190
|
+
|
|
191
|
+
<div class="stat-card">
|
|
192
|
+
<div class="stat-label">Valleys Detected</div>
|
|
193
|
+
<div class="stat-value valleys" id="valleys">0</div>
|
|
194
|
+
</div>
|
|
195
|
+
|
|
196
|
+
<div class="stat-card">
|
|
197
|
+
<div class="stat-label">Current Entropy</div>
|
|
198
|
+
<div class="stat-value" id="currentEntropy">-</div>
|
|
199
|
+
</div>
|
|
200
|
+
|
|
201
|
+
<div class="stat-card">
|
|
202
|
+
<div class="stat-label">Mean Entropy</div>
|
|
203
|
+
<div class="stat-value" id="meanEntropy">-</div>
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
<div class="stat-card">
|
|
207
|
+
<div class="stat-label">Tokens Saved</div>
|
|
208
|
+
<div class="stat-value savings" id="saved">-</div>
|
|
209
|
+
</div>
|
|
210
|
+
|
|
211
|
+
<div class="stat-card">
|
|
212
|
+
<div class="stat-label">Cost Saved</div>
|
|
213
|
+
<div class="stat-value cost" id="costSaved">-</div>
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
|
|
219
|
+
<script>
|
|
220
|
+
let chart = null;
|
|
221
|
+
let hasData = false;
|
|
222
|
+
const maxDataPoints = 200;
|
|
223
|
+
|
|
224
|
+
function initChart() {
|
|
225
|
+
const ctx = document.getElementById('entropyChart').getContext('2d');
|
|
226
|
+
|
|
227
|
+
chart = new Chart(ctx, {
|
|
228
|
+
type: 'line',
|
|
229
|
+
data: {
|
|
230
|
+
labels: [],
|
|
231
|
+
datasets: [
|
|
232
|
+
{
|
|
233
|
+
label: 'Entropy',
|
|
234
|
+
data: [],
|
|
235
|
+
borderColor: '#60a5fa',
|
|
236
|
+
backgroundColor: 'rgba(96, 165, 250, 0.1)',
|
|
237
|
+
fill: true,
|
|
238
|
+
tension: 0.3,
|
|
239
|
+
pointRadius: 0,
|
|
240
|
+
pointHoverRadius: 4,
|
|
241
|
+
borderWidth: 2,
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
label: 'Threshold',
|
|
245
|
+
data: [],
|
|
246
|
+
borderColor: '#ef4444',
|
|
247
|
+
borderDash: [5, 5],
|
|
248
|
+
pointRadius: 0,
|
|
249
|
+
fill: false,
|
|
250
|
+
borderWidth: 1,
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
label: 'Valleys',
|
|
254
|
+
data: [],
|
|
255
|
+
borderColor: '#f59e0b',
|
|
256
|
+
pointBackgroundColor: '#f59e0b',
|
|
257
|
+
pointRadius: 5,
|
|
258
|
+
pointHoverRadius: 7,
|
|
259
|
+
showLine: false,
|
|
260
|
+
}
|
|
261
|
+
]
|
|
262
|
+
},
|
|
263
|
+
options: {
|
|
264
|
+
responsive: true,
|
|
265
|
+
maintainAspectRatio: false,
|
|
266
|
+
animation: { duration: 0 },
|
|
267
|
+
interaction: { intersect: false, mode: 'index' },
|
|
268
|
+
scales: {
|
|
269
|
+
x: {
|
|
270
|
+
title: { display: true, text: 'Tokens', color: '#888', font: { size: 11 } },
|
|
271
|
+
grid: { color: '#222' },
|
|
272
|
+
ticks: { color: '#666', maxTicksLimit: 10 }
|
|
273
|
+
},
|
|
274
|
+
y: {
|
|
275
|
+
title: { display: true, text: 'Entropy (bits)', color: '#888', font: { size: 11 } },
|
|
276
|
+
min: 0,
|
|
277
|
+
max: 1,
|
|
278
|
+
grid: { color: '#222' },
|
|
279
|
+
ticks: { color: '#666' }
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
plugins: {
|
|
283
|
+
legend: { display: false },
|
|
284
|
+
tooltip: {
|
|
285
|
+
backgroundColor: '#1a1a1a',
|
|
286
|
+
titleColor: '#fff',
|
|
287
|
+
bodyColor: '#888',
|
|
288
|
+
borderColor: '#333',
|
|
289
|
+
borderWidth: 1,
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function updateChart(data) {
|
|
297
|
+
if (!chart) return;
|
|
298
|
+
|
|
299
|
+
if (!hasData && data.trajectory && data.trajectory.length > 0) {
|
|
300
|
+
hasData = true;
|
|
301
|
+
document.getElementById('waitingMessage').style.display = 'none';
|
|
302
|
+
document.getElementById('connectionStatus').className = 'connection-status connected';
|
|
303
|
+
document.getElementById('connectionStatus').textContent = 'Connected - Live';
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (!data.trajectory || data.trajectory.length === 0) return;
|
|
307
|
+
|
|
308
|
+
// Limit data points for performance
|
|
309
|
+
let trajectory = data.trajectory;
|
|
310
|
+
if (trajectory.length > maxDataPoints) {
|
|
311
|
+
trajectory = trajectory.slice(-maxDataPoints);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const labels = trajectory.map((_, i) => i);
|
|
315
|
+
const entropies = trajectory.map(p => p.entropy);
|
|
316
|
+
const threshold = trajectory.map(() => 0.15); // Default threshold
|
|
317
|
+
const valleyPoints = trajectory.map(p => p.is_valley ? p.entropy : null);
|
|
318
|
+
|
|
319
|
+
chart.data.labels = labels;
|
|
320
|
+
chart.data.datasets[0].data = entropies;
|
|
321
|
+
chart.data.datasets[1].data = threshold;
|
|
322
|
+
chart.data.datasets[2].data = valleyPoints;
|
|
323
|
+
chart.update('none');
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function updateStats(data) {
|
|
327
|
+
document.getElementById('tokens').textContent = data.token_count || 0;
|
|
328
|
+
document.getElementById('valleys').textContent = data.valley_count || 0;
|
|
329
|
+
|
|
330
|
+
if (data.current_entropy !== undefined) {
|
|
331
|
+
document.getElementById('currentEntropy').textContent = data.current_entropy.toFixed(3);
|
|
332
|
+
}
|
|
333
|
+
if (data.mean_entropy !== undefined) {
|
|
334
|
+
document.getElementById('meanEntropy').textContent = data.mean_entropy.toFixed(3);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (data.exited_early) {
|
|
338
|
+
document.getElementById('status').innerHTML = '<span class="status-badge status-exited">Exited Early</span>';
|
|
339
|
+
if (data.tokens_saved && data.tokens_total) {
|
|
340
|
+
const savedPct = Math.round((data.tokens_saved / data.tokens_total) * 100);
|
|
341
|
+
document.getElementById('saved').textContent = savedPct + '%';
|
|
342
|
+
}
|
|
343
|
+
if (data.cost_saved) {
|
|
344
|
+
document.getElementById('costSaved').textContent = '$' + data.cost_saved.toFixed(4);
|
|
345
|
+
}
|
|
346
|
+
} else if (data.active) {
|
|
347
|
+
document.getElementById('status').innerHTML = '<span class="status-badge status-active">Active</span>';
|
|
348
|
+
document.getElementById('saved').textContent = '-';
|
|
349
|
+
document.getElementById('costSaved').textContent = '-';
|
|
350
|
+
} else {
|
|
351
|
+
document.getElementById('status').innerHTML = '<span class="status-badge status-idle">Idle</span>';
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Initialize on load
|
|
356
|
+
initChart();
|
|
357
|
+
|
|
358
|
+
// WebSocket connection
|
|
359
|
+
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
360
|
+
const ws = new WebSocket(`${protocol}//${location.host}/ws`);
|
|
361
|
+
|
|
362
|
+
ws.onopen = () => {
|
|
363
|
+
document.getElementById('connectionStatus').className = 'connection-status connected';
|
|
364
|
+
document.getElementById('connectionStatus').textContent = 'Connected - Waiting for data';
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
ws.onclose = () => {
|
|
368
|
+
document.getElementById('connectionStatus').className = 'connection-status disconnected';
|
|
369
|
+
document.getElementById('connectionStatus').textContent = 'Disconnected - Reconnecting...';
|
|
370
|
+
setTimeout(() => location.reload(), 3000);
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
ws.onerror = () => {
|
|
374
|
+
document.getElementById('connectionStatus').className = 'connection-status disconnected';
|
|
375
|
+
document.getElementById('connectionStatus').textContent = 'Connection error';
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
ws.onmessage = (event) => {
|
|
379
|
+
try {
|
|
380
|
+
const data = JSON.parse(event.data);
|
|
381
|
+
updateChart(data);
|
|
382
|
+
updateStats(data);
|
|
383
|
+
} catch (e) {
|
|
384
|
+
console.error('Parse error:', e);
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
</script>
|
|
388
|
+
</body>
|
|
389
|
+
</html>
|
|
390
|
+
"""
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
class Dashboard:
|
|
394
|
+
"""Real-time dashboard server."""
|
|
395
|
+
|
|
396
|
+
def __init__(self, config: DashboardConfig):
|
|
397
|
+
self.config = config
|
|
398
|
+
self.app = FastAPI(title="Entroplain Dashboard")
|
|
399
|
+
self._websocket_clients: List[WebSocket] = []
|
|
400
|
+
self._current_data: Dict[str, Any] = {
|
|
401
|
+
"trajectory": [],
|
|
402
|
+
"token_count": 0,
|
|
403
|
+
"valley_count": 0,
|
|
404
|
+
"current_entropy": 0,
|
|
405
|
+
"mean_entropy": 0,
|
|
406
|
+
"active": False,
|
|
407
|
+
"exited_early": False,
|
|
408
|
+
}
|
|
409
|
+
self._setup_routes()
|
|
410
|
+
|
|
411
|
+
def _setup_routes(self):
|
|
412
|
+
@self.app.get("/")
|
|
413
|
+
async def root():
|
|
414
|
+
return HTMLResponse(content=DASHBOARD_HTML)
|
|
415
|
+
|
|
416
|
+
@self.app.websocket("/ws")
|
|
417
|
+
async def websocket_endpoint(websocket: WebSocket):
|
|
418
|
+
await websocket.accept()
|
|
419
|
+
self._websocket_clients.append(websocket)
|
|
420
|
+
try:
|
|
421
|
+
# Send initial state
|
|
422
|
+
await websocket.send_json(self._current_data)
|
|
423
|
+
# Keep connection alive
|
|
424
|
+
while True:
|
|
425
|
+
data = await websocket.receive_text()
|
|
426
|
+
# Echo back current state on any message
|
|
427
|
+
await websocket.send_json(self._current_data)
|
|
428
|
+
except WebSocketDisconnect:
|
|
429
|
+
if websocket in self._websocket_clients:
|
|
430
|
+
self._websocket_clients.remove(websocket)
|
|
431
|
+
except Exception:
|
|
432
|
+
if websocket in self._websocket_clients:
|
|
433
|
+
self._websocket_clients.remove(websocket)
|
|
434
|
+
|
|
435
|
+
async def broadcast_update(self, data: Dict[str, Any]):
|
|
436
|
+
"""Broadcast entropy data to all connected clients."""
|
|
437
|
+
self._current_data = data
|
|
438
|
+
dead_clients = []
|
|
439
|
+
for client in self._websocket_clients:
|
|
440
|
+
try:
|
|
441
|
+
await client.send_json(data)
|
|
442
|
+
except Exception:
|
|
443
|
+
dead_clients.append(client)
|
|
444
|
+
for client in dead_clients:
|
|
445
|
+
if client in self._websocket_clients:
|
|
446
|
+
self._websocket_clients.remove(client)
|
|
447
|
+
|
|
448
|
+
def run(self):
|
|
449
|
+
"""Start the dashboard server."""
|
|
450
|
+
uvicorn.run(self.app, host="0.0.0.0", port=self.config.port)
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def main():
|
|
454
|
+
"""CLI entry point for the dashboard."""
|
|
455
|
+
import argparse
|
|
456
|
+
|
|
457
|
+
parser = argparse.ArgumentParser(description="Entroplain Dashboard")
|
|
458
|
+
parser.add_argument("--port", type=int, default=8050, help="Dashboard port")
|
|
459
|
+
parser.add_argument("--proxy-port", type=int, default=8765, help="Proxy port to monitor")
|
|
460
|
+
args = parser.parse_args()
|
|
461
|
+
|
|
462
|
+
config = DashboardConfig(port=args.port, proxy_port=args.proxy_port)
|
|
463
|
+
dashboard = Dashboard(config)
|
|
464
|
+
|
|
465
|
+
print(f"""
|
|
466
|
+
==============================================================
|
|
467
|
+
ENTROPPLAIN DASHBOARD
|
|
468
|
+
==============================================================
|
|
469
|
+
Dashboard: http://localhost:{args.port}
|
|
470
|
+
Monitoring proxy on port {args.proxy_port}
|
|
471
|
+
==============================================================
|
|
472
|
+
Open the dashboard to see real-time entropy visualization
|
|
473
|
+
==============================================================
|
|
474
|
+
""")
|
|
475
|
+
|
|
476
|
+
dashboard.run()
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
if __name__ == "__main__":
|
|
480
|
+
main()
|