entroplain 0.1.0 → 0.2.0

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.
@@ -0,0 +1,368 @@
1
+ """
2
+ Real-time entropy visualization dashboard.
3
+
4
+ Run with: entroplain-dashboard --port 8765
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
+ # HTML template for the dashboard
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
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
37
+ background: #0a0a0a;
38
+ color: #e0e0e0;
39
+ padding: 20px;
40
+ }
41
+ .container { max-width: 1200px; margin: 0 auto; }
42
+ h1 {
43
+ font-size: 24px;
44
+ margin-bottom: 20px;
45
+ color: #4ade80;
46
+ }
47
+ .grid {
48
+ display: grid;
49
+ grid-template-columns: 1fr 300px;
50
+ gap: 20px;
51
+ }
52
+ .chart-container {
53
+ background: #1a1a1a;
54
+ border-radius: 8px;
55
+ padding: 20px;
56
+ }
57
+ .stats-panel {
58
+ display: flex;
59
+ flex-direction: column;
60
+ gap: 15px;
61
+ }
62
+ .stat-card {
63
+ background: #1a1a1a;
64
+ border-radius: 8px;
65
+ padding: 15px;
66
+ }
67
+ .stat-label {
68
+ font-size: 12px;
69
+ color: #888;
70
+ text-transform: uppercase;
71
+ letter-spacing: 0.05em;
72
+ }
73
+ .stat-value {
74
+ font-size: 32px;
75
+ font-weight: 600;
76
+ color: #fff;
77
+ margin-top: 5px;
78
+ }
79
+ .stat-value.savings { color: #4ade80; }
80
+ .stat-value.cost { color: #fbbf24; }
81
+ .valleys { color: #60a5fa; }
82
+ .status-badge {
83
+ display: inline-block;
84
+ padding: 4px 12px;
85
+ border-radius: 12px;
86
+ font-size: 12px;
87
+ font-weight: 500;
88
+ }
89
+ .status-active { background: #22c55e; color: #000; }
90
+ .status-idle { background: #374151; color: #888; }
91
+ .status-exited { background: #f59e0b; color: #000; }
92
+ #status { margin-top: 10px; }
93
+ .legend {
94
+ display: flex;
95
+ gap: 20px;
96
+ margin-top: 15px;
97
+ font-size: 12px;
98
+ }
99
+ .legend-item {
100
+ display: flex;
101
+ align-items: center;
102
+ gap: 5px;
103
+ }
104
+ .legend-dot {
105
+ width: 10px;
106
+ height: 10px;
107
+ border-radius: 50%;
108
+ }
109
+ .dot-entropy { background: #60a5fa; }
110
+ .dot-valley { background: #f59e0b; }
111
+ .dot-threshold { background: #ef4444; }
112
+ </style>
113
+ </head>
114
+ <body>
115
+ <div class="container">
116
+ <h1>🎯 Entroplain Dashboard</h1>
117
+
118
+ <div class="grid">
119
+ <div class="chart-container">
120
+ <canvas id="entropyChart" height="300"></canvas>
121
+ <div class="legend">
122
+ <div class="legend-item">
123
+ <div class="legend-dot dot-entropy"></div>
124
+ <span>Entropy</span>
125
+ </div>
126
+ <div class="legend-item">
127
+ <div class="legend-dot dot-valley"></div>
128
+ <span>Valley Detected</span>
129
+ </div>
130
+ <div class="legend-item">
131
+ <div class="legend-dot dot-threshold"></div>
132
+ <span>Threshold (0.15)</span>
133
+ </div>
134
+ </div>
135
+ </div>
136
+
137
+ <div class="stats-panel">
138
+ <div class="stat-card">
139
+ <div class="stat-label">Status</div>
140
+ <div id="status">
141
+ <span class="status-badge status-idle">Idle</span>
142
+ </div>
143
+ </div>
144
+
145
+ <div class="stat-card">
146
+ <div class="stat-label">Tokens Generated</div>
147
+ <div class="stat-value" id="tokens">0</div>
148
+ </div>
149
+
150
+ <div class="stat-card">
151
+ <div class="stat-label">Valleys Detected</div>
152
+ <div class="stat-value valleys" id="valleys">0</div>
153
+ </div>
154
+
155
+ <div class="stat-card">
156
+ <div class="stat-label">Current Entropy</div>
157
+ <div class="stat-value" id="currentEntropy">-</div>
158
+ </div>
159
+
160
+ <div class="stat-card">
161
+ <div class="stat-label">Mean Entropy</div>
162
+ <div class="stat-value" id="meanEntropy">-</div>
163
+ </div>
164
+
165
+ <div class="stat-card">
166
+ <div class="stat-label">Tokens Saved</div>
167
+ <div class="stat-value savings" id="saved">0%</div>
168
+ </div>
169
+
170
+ <div class="stat-card">
171
+ <div class="stat-label">Cost Saved</div>
172
+ <div class="stat-value cost" id="costSaved">$0.00</div>
173
+ </div>
174
+ </div>
175
+ </div>
176
+ </div>
177
+
178
+ <script>
179
+ const ctx = document.getElementById('entropyChart').getContext('2d');
180
+
181
+ const chart = new Chart(ctx, {
182
+ type: 'line',
183
+ data: {
184
+ labels: [],
185
+ datasets: [
186
+ {
187
+ label: 'Entropy',
188
+ data: [],
189
+ borderColor: '#60a5fa',
190
+ backgroundColor: 'rgba(96, 165, 250, 0.1)',
191
+ fill: true,
192
+ tension: 0.3,
193
+ pointRadius: 0,
194
+ },
195
+ {
196
+ label: 'Threshold',
197
+ data: [],
198
+ borderColor: '#ef4444',
199
+ borderDash: [5, 5],
200
+ pointRadius: 0,
201
+ fill: false,
202
+ },
203
+ {
204
+ label: 'Valleys',
205
+ data: [],
206
+ borderColor: '#f59e0b',
207
+ pointBackgroundColor: '#f59e0b',
208
+ pointRadius: 6,
209
+ showLine: false,
210
+ }
211
+ ]
212
+ },
213
+ options: {
214
+ responsive: true,
215
+ maintainAspectRatio: false,
216
+ animation: { duration: 0 },
217
+ scales: {
218
+ x: {
219
+ title: { display: true, text: 'Tokens', color: '#888' },
220
+ grid: { color: '#333' },
221
+ ticks: { color: '#888' }
222
+ },
223
+ y: {
224
+ title: { display: true, text: 'Entropy (bits)', color: '#888' },
225
+ min: 0,
226
+ max: 1,
227
+ grid: { color: '#333' },
228
+ ticks: { color: '#888' }
229
+ }
230
+ },
231
+ plugins: {
232
+ legend: { display: false }
233
+ }
234
+ }
235
+ });
236
+
237
+ const ws = new WebSocket(`ws://${location.host}/ws`);
238
+
239
+ ws.onmessage = (event) => {
240
+ const data = JSON.parse(event.data);
241
+ updateChart(data);
242
+ updateStats(data);
243
+ };
244
+
245
+ function updateChart(data) {
246
+ const labels = data.trajectory.map((_, i) => i);
247
+ const entropies = data.trajectory.map(p => p.entropy);
248
+ const threshold = data.trajectory.map(() => data.threshold || 0.15);
249
+
250
+ // Mark valleys
251
+ const valleyPoints = data.trajectory.map(p =>
252
+ p.is_valley ? p.entropy : null
253
+ );
254
+
255
+ chart.data.labels = labels;
256
+ chart.data.datasets[0].data = entropies;
257
+ chart.data.datasets[1].data = threshold;
258
+ chart.data.datasets[2].data = valleyPoints;
259
+ chart.update();
260
+ }
261
+
262
+ function updateStats(data) {
263
+ document.getElementById('tokens').textContent = data.token_count;
264
+ document.getElementById('valleys').textContent = data.valley_count;
265
+ document.getElementById('currentEntropy').textContent =
266
+ data.current_entropy ? data.current_entropy.toFixed(3) : '-';
267
+ document.getElementById('meanEntropy').textContent =
268
+ data.mean_entropy ? data.mean_entropy.toFixed(3) : '-';
269
+
270
+ // Calculate savings
271
+ if (data.exited_early) {
272
+ const savedPct = Math.round((data.tokens_saved / data.tokens_total) * 100);
273
+ document.getElementById('saved').textContent = savedPct + '%';
274
+ document.getElementById('costSaved').textContent = '$' + data.cost_saved.toFixed(4);
275
+ }
276
+
277
+ // Update status
278
+ const statusEl = document.getElementById('status');
279
+ if (data.exited_early) {
280
+ statusEl.innerHTML = '<span class="status-badge status-exited">Exited Early</span>';
281
+ } else if (data.active) {
282
+ statusEl.innerHTML = '<span class="status-badge status-active">Active</span>';
283
+ } else {
284
+ statusEl.innerHTML = '<span class="status-badge status-idle">Idle</span>';
285
+ }
286
+ }
287
+ </script>
288
+ </body>
289
+ </html>
290
+ """
291
+
292
+
293
+ class Dashboard:
294
+ """Real-time dashboard server."""
295
+
296
+ def __init__(self, config: DashboardConfig):
297
+ self.config = config
298
+ self.app = FastAPI(title="Entroplain Dashboard")
299
+ self._websocket_clients: List[WebSocket] = []
300
+ self._current_data: Dict[str, Any] = {
301
+ "trajectory": [],
302
+ "token_count": 0,
303
+ "valley_count": 0,
304
+ "current_entropy": 0,
305
+ "mean_entropy": 0,
306
+ "active": False,
307
+ "exited_early": False,
308
+ }
309
+ self._setup_routes()
310
+
311
+ def _setup_routes(self):
312
+ @self.app.get("/")
313
+ async def root():
314
+ return HTMLResponse(content=DASHBOARD_HTML)
315
+
316
+ @self.app.websocket("/ws")
317
+ async def websocket_endpoint(websocket: WebSocket):
318
+ await websocket.accept()
319
+ self._websocket_clients.append(websocket)
320
+ try:
321
+ while True:
322
+ # Keep connection alive
323
+ data = await websocket.receive_text()
324
+ except Exception:
325
+ self._websocket_clients.remove(websocket)
326
+
327
+ async def broadcast_update(self, data: Dict[str, Any]):
328
+ """Broadcast entropy data to all connected clients."""
329
+ self._current_data = data
330
+ for client in self._websocket_clients:
331
+ try:
332
+ await client.send_json(data)
333
+ except Exception:
334
+ self._websocket_clients.remove(client)
335
+
336
+ def run(self):
337
+ """Start the dashboard server."""
338
+ uvicorn.run(self.app, host="0.0.0.0", port=self.config.port)
339
+
340
+
341
+ def main():
342
+ """CLI entry point for the dashboard."""
343
+ import argparse
344
+
345
+ parser = argparse.ArgumentParser(description="Entroplain Dashboard")
346
+ parser.add_argument("--port", type=int, default=8050, help="Dashboard port")
347
+ parser.add_argument("--proxy-port", type=int, default=8765, help="Proxy port to monitor")
348
+ args = parser.parse_args()
349
+
350
+ config = DashboardConfig(port=args.port, proxy_port=args.proxy_port)
351
+ dashboard = Dashboard(config)
352
+
353
+ print(f"""
354
+ ==============================================================
355
+ ENTROPPLAIN DASHBOARD
356
+ ==============================================================
357
+ Dashboard: http://localhost:{args.port}
358
+ Monitoring proxy on port {args.proxy_port}
359
+ ==============================================================
360
+ Open the dashboard to see real-time entropy visualization
361
+ ==============================================================
362
+ """)
363
+
364
+ dashboard.run()
365
+
366
+
367
+ if __name__ == "__main__":
368
+ main()