nexaroa 0.0.111__py3-none-any.whl

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.
Files changed (78) hide show
  1. neuroshard/__init__.py +93 -0
  2. neuroshard/__main__.py +4 -0
  3. neuroshard/cli.py +466 -0
  4. neuroshard/core/__init__.py +92 -0
  5. neuroshard/core/consensus/verifier.py +252 -0
  6. neuroshard/core/crypto/__init__.py +20 -0
  7. neuroshard/core/crypto/ecdsa.py +392 -0
  8. neuroshard/core/economics/__init__.py +52 -0
  9. neuroshard/core/economics/constants.py +387 -0
  10. neuroshard/core/economics/ledger.py +2111 -0
  11. neuroshard/core/economics/market.py +975 -0
  12. neuroshard/core/economics/wallet.py +168 -0
  13. neuroshard/core/governance/__init__.py +74 -0
  14. neuroshard/core/governance/proposal.py +561 -0
  15. neuroshard/core/governance/registry.py +545 -0
  16. neuroshard/core/governance/versioning.py +332 -0
  17. neuroshard/core/governance/voting.py +453 -0
  18. neuroshard/core/model/__init__.py +30 -0
  19. neuroshard/core/model/dynamic.py +4186 -0
  20. neuroshard/core/model/llm.py +905 -0
  21. neuroshard/core/model/registry.py +164 -0
  22. neuroshard/core/model/scaler.py +387 -0
  23. neuroshard/core/model/tokenizer.py +568 -0
  24. neuroshard/core/network/__init__.py +56 -0
  25. neuroshard/core/network/connection_pool.py +72 -0
  26. neuroshard/core/network/dht.py +130 -0
  27. neuroshard/core/network/dht_plan.py +55 -0
  28. neuroshard/core/network/dht_proof_store.py +516 -0
  29. neuroshard/core/network/dht_protocol.py +261 -0
  30. neuroshard/core/network/dht_service.py +506 -0
  31. neuroshard/core/network/encrypted_channel.py +141 -0
  32. neuroshard/core/network/nat.py +201 -0
  33. neuroshard/core/network/nat_traversal.py +695 -0
  34. neuroshard/core/network/p2p.py +929 -0
  35. neuroshard/core/network/p2p_data.py +150 -0
  36. neuroshard/core/swarm/__init__.py +106 -0
  37. neuroshard/core/swarm/aggregation.py +729 -0
  38. neuroshard/core/swarm/buffers.py +643 -0
  39. neuroshard/core/swarm/checkpoint.py +709 -0
  40. neuroshard/core/swarm/compute.py +624 -0
  41. neuroshard/core/swarm/diloco.py +844 -0
  42. neuroshard/core/swarm/factory.py +1288 -0
  43. neuroshard/core/swarm/heartbeat.py +669 -0
  44. neuroshard/core/swarm/logger.py +487 -0
  45. neuroshard/core/swarm/router.py +658 -0
  46. neuroshard/core/swarm/service.py +640 -0
  47. neuroshard/core/training/__init__.py +29 -0
  48. neuroshard/core/training/checkpoint.py +600 -0
  49. neuroshard/core/training/distributed.py +1602 -0
  50. neuroshard/core/training/global_tracker.py +617 -0
  51. neuroshard/core/training/production.py +276 -0
  52. neuroshard/governance_cli.py +729 -0
  53. neuroshard/grpc_server.py +895 -0
  54. neuroshard/runner.py +3223 -0
  55. neuroshard/sdk/__init__.py +92 -0
  56. neuroshard/sdk/client.py +990 -0
  57. neuroshard/sdk/errors.py +101 -0
  58. neuroshard/sdk/types.py +282 -0
  59. neuroshard/tracker/__init__.py +0 -0
  60. neuroshard/tracker/server.py +864 -0
  61. neuroshard/ui/__init__.py +0 -0
  62. neuroshard/ui/app.py +102 -0
  63. neuroshard/ui/templates/index.html +1052 -0
  64. neuroshard/utils/__init__.py +0 -0
  65. neuroshard/utils/autostart.py +81 -0
  66. neuroshard/utils/hardware.py +121 -0
  67. neuroshard/utils/serialization.py +90 -0
  68. neuroshard/version.py +1 -0
  69. nexaroa-0.0.111.dist-info/METADATA +283 -0
  70. nexaroa-0.0.111.dist-info/RECORD +78 -0
  71. nexaroa-0.0.111.dist-info/WHEEL +5 -0
  72. nexaroa-0.0.111.dist-info/entry_points.txt +4 -0
  73. nexaroa-0.0.111.dist-info/licenses/LICENSE +190 -0
  74. nexaroa-0.0.111.dist-info/top_level.txt +2 -0
  75. protos/__init__.py +0 -0
  76. protos/neuroshard.proto +651 -0
  77. protos/neuroshard_pb2.py +160 -0
  78. protos/neuroshard_pb2_grpc.py +1298 -0
@@ -0,0 +1,1052 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="dark">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>NeuroShard Node Dashboard</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet">
11
+ <script>
12
+ tailwind.config = {
13
+ darkMode: 'class',
14
+ theme: {
15
+ extend: {
16
+ colors: {
17
+ neuro: {
18
+ 50: '#e6fff9',
19
+ 100: '#b3ffed',
20
+ 200: '#80ffe0',
21
+ 300: '#4dffd4',
22
+ 400: '#1affc7',
23
+ 500: '#00d4aa',
24
+ 600: '#00a888',
25
+ 700: '#007d66',
26
+ 800: '#005244',
27
+ 900: '#002922',
28
+ },
29
+ dark: {
30
+ 50: '#f7f7f8',
31
+ 100: '#eeeef0',
32
+ 200: '#d9d9de',
33
+ 300: '#b8b8c1',
34
+ 400: '#91919f',
35
+ 500: '#737384',
36
+ 600: '#5d5d6c',
37
+ 700: '#4c4c58',
38
+ 800: '#1a1a1f',
39
+ 900: '#0f0f12',
40
+ 950: '#09090b',
41
+ }
42
+ },
43
+ fontFamily: {
44
+ sans: ['Space Grotesk', 'system-ui', 'sans-serif'],
45
+ mono: ['JetBrains Mono', 'Menlo', 'monospace'],
46
+ }
47
+ }
48
+ }
49
+ }
50
+ </script>
51
+ <style>
52
+ /* Custom scrollbar */
53
+ ::-webkit-scrollbar { width: 8px; height: 8px; }
54
+ ::-webkit-scrollbar-track { background: #1a1a1f; }
55
+ ::-webkit-scrollbar-thumb { background: #4c4c58; border-radius: 4px; }
56
+ ::-webkit-scrollbar-thumb:hover { background: #5d5d6c; }
57
+
58
+ /* Animations */
59
+ @keyframes pulse-glow {
60
+ 0%, 100% { box-shadow: 0 0 0 0 rgba(0, 212, 170, 0.4); }
61
+ 50% { box-shadow: 0 0 0 8px rgba(0, 212, 170, 0); }
62
+ }
63
+ .pulse-glow { animation: pulse-glow 2s infinite; }
64
+
65
+ @keyframes slide-up {
66
+ from { opacity: 0; transform: translateY(10px); }
67
+ to { opacity: 1; transform: translateY(0); }
68
+ }
69
+ .animate-slide-up { animation: slide-up 0.3s ease-out; }
70
+
71
+ /* Log colors */
72
+ .log-error { color: #ef4444; }
73
+ .log-warning { color: #f59e0b; }
74
+ .log-success { color: #22c55e; }
75
+ .log-neuro { color: #00d4aa; }
76
+ .log-training { color: #a855f7; }
77
+ .log-info { color: #94a3b8; }
78
+ .log-timestamp { color: #64748b; }
79
+ </style>
80
+ </head>
81
+ <body class="bg-dark-950 text-gray-100 font-sans h-screen overflow-hidden">
82
+ <div class="flex h-screen">
83
+ <!-- Sidebar -->
84
+ <aside class="w-72 bg-dark-900 border-r border-dark-700 flex flex-col overflow-y-auto">
85
+ <!-- Logo -->
86
+ <div class="p-6 border-b border-dark-700">
87
+ <div class="flex items-center gap-3">
88
+ <div class="w-10 h-10 rounded-xl bg-gradient-to-br from-neuro-400 to-neuro-600 flex items-center justify-center">
89
+ <span class="text-dark-950 font-bold text-xl">N</span>
90
+ </div>
91
+ <div>
92
+ <h1 class="font-bold text-lg tracking-tight">NEUROSHARD</h1>
93
+ <p class="text-xs text-dark-400" id="version">v0.1.0</p>
94
+ </div>
95
+ </div>
96
+ </div>
97
+
98
+ <!-- Status -->
99
+ <div class="p-4">
100
+ <div class="bg-dark-800 rounded-xl p-4">
101
+ <div class="flex items-center gap-2">
102
+ <div id="status-dot" class="w-2.5 h-2.5 rounded-full bg-dark-500"></div>
103
+ <span id="status-text" class="font-semibold text-dark-400">CONNECTING</span>
104
+ </div>
105
+ <p id="status-detail" class="text-sm text-dark-500 mt-2">Initializing node...</p>
106
+ </div>
107
+ </div>
108
+
109
+ <!-- Balance -->
110
+ <div class="px-4 pb-4">
111
+ <div class="bg-dark-800 rounded-xl p-4">
112
+ <p class="text-xs text-dark-500 uppercase tracking-wider">NEURO Balance (Local)</p>
113
+ <p id="balance" class="text-3xl font-bold text-neuro-500 mt-1">0.0000</p>
114
+ <p class="text-xs text-dark-500 mt-2">
115
+ Earned: <span id="total-earned" class="text-neuro-400">0.0000</span>
116
+ </p>
117
+ <p id="balance-note" class="text-xs text-dark-600 mt-2 hidden">
118
+ <span class="text-yellow-500">⚠</span> Solo proofs pending network confirmation
119
+ </p>
120
+ </div>
121
+ </div>
122
+
123
+ <!-- System Resources -->
124
+ <div class="px-4 pb-4">
125
+ <p class="text-xs text-dark-500 uppercase tracking-wider mb-3 px-1">System Resources</p>
126
+
127
+ <!-- CPU -->
128
+ <div class="bg-dark-800 rounded-xl p-3 mb-2">
129
+ <div class="flex justify-between text-sm mb-2">
130
+ <span class="text-dark-400">CPU Usage</span>
131
+ <span id="cpu-percent" class="font-mono font-medium">0%</span>
132
+ </div>
133
+ <div class="h-2 bg-dark-700 rounded-full overflow-hidden">
134
+ <div id="cpu-bar" class="h-full bg-neuro-500 rounded-full transition-all duration-500" style="width: 0%"></div>
135
+ </div>
136
+ </div>
137
+
138
+ <!-- RAM -->
139
+ <div class="bg-dark-800 rounded-xl p-3">
140
+ <div class="flex justify-between text-sm mb-2">
141
+ <span class="text-dark-400">Memory</span>
142
+ <span id="ram-percent" class="font-mono font-medium text-xs">0 / 0 GB</span>
143
+ </div>
144
+ <div class="h-2 bg-dark-700 rounded-full overflow-hidden">
145
+ <div id="ram-bar" class="h-full bg-neuro-500 rounded-full transition-all duration-500" style="width: 0%"></div>
146
+ </div>
147
+ </div>
148
+ </div>
149
+
150
+ <!-- DiLoCo Progress -->
151
+ <div class="px-4 pb-4" id="diloco-section" style="display: none;">
152
+ <div class="bg-dark-800 rounded-xl p-3">
153
+ <div class="flex justify-between text-sm mb-2">
154
+ <span class="text-dark-400">DiLoCo Sync</span>
155
+ <span id="diloco-progress" class="font-mono font-medium text-xs">0/500</span>
156
+ </div>
157
+ <div class="h-2 bg-dark-700 rounded-full overflow-hidden">
158
+ <div id="diloco-bar" class="h-full bg-purple-500 rounded-full transition-all duration-500" style="width: 0%"></div>
159
+ </div>
160
+ <p class="text-xs text-dark-500 mt-2">Outer step: <span id="diloco-outer">0</span></p>
161
+ </div>
162
+ </div>
163
+
164
+ <!-- Spacer -->
165
+ <div class="flex-1"></div>
166
+
167
+ <!-- Stop Button -->
168
+ <div class="px-4 pb-4">
169
+ <button onclick="stopNode()" id="stop-btn" class="w-full px-4 py-3 bg-red-600/20 hover:bg-red-600/30 border border-red-600/50 rounded-xl text-red-400 font-medium transition-colors flex items-center justify-center gap-2">
170
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
171
+ <rect x="6" y="6" width="12" height="12" rx="2" stroke-width="2"/>
172
+ </svg>
173
+ Stop Node
174
+ </button>
175
+ </div>
176
+
177
+ <!-- Footer -->
178
+ <div class="p-4 border-t border-dark-700">
179
+ <p class="text-xs text-dark-500 text-center">Decentralized AI Network</p>
180
+ <p class="text-xs text-dark-600 text-center mt-1">
181
+ <a href="https://neuroshard.com" target="_blank" class="hover:text-neuro-500 transition-colors">neuroshard.com</a>
182
+ </p>
183
+ </div>
184
+ </aside>
185
+
186
+ <!-- Main Content -->
187
+ <main class="flex-1 flex flex-col min-h-0 overflow-hidden">
188
+ <!-- Tab Navigation -->
189
+ <nav class="bg-dark-900 border-b border-dark-700 px-6 flex-shrink-0">
190
+ <div class="flex gap-1">
191
+ <button onclick="showTab('dashboard')" id="tab-dashboard" class="tab-btn px-5 py-4 text-sm font-medium border-b-2 border-neuro-500 text-neuro-500">
192
+ Dashboard
193
+ </button>
194
+ <button onclick="showTab('logs')" id="tab-logs" class="tab-btn px-5 py-4 text-sm font-medium border-b-2 border-transparent text-dark-400 hover:text-dark-200 transition-colors">
195
+ Logs
196
+ </button>
197
+ <button onclick="showTab('settings')" id="tab-settings" class="tab-btn px-5 py-4 text-sm font-medium border-b-2 border-transparent text-dark-400 hover:text-dark-200 transition-colors">
198
+ Settings
199
+ </button>
200
+ </div>
201
+ </nav>
202
+
203
+ <!-- Tab Content -->
204
+ <div id="tab-content-area" class="flex-1 flex flex-col overflow-hidden">
205
+ <!-- Dashboard Tab -->
206
+ <div id="content-dashboard" class="p-6 animate-slide-up overflow-auto flex-1">
207
+ <!-- Stats Grid -->
208
+ <div class="grid grid-cols-4 gap-4 mb-6">
209
+ <!-- Role -->
210
+ <div class="bg-dark-800 rounded-xl p-4">
211
+ <p class="text-xs text-dark-500 uppercase tracking-wider">Role</p>
212
+ <p id="stat-role" class="text-2xl font-bold mt-1">-</p>
213
+ </div>
214
+ <!-- Layers -->
215
+ <div class="bg-dark-800 rounded-xl p-4">
216
+ <p class="text-xs text-dark-500 uppercase tracking-wider">Layers</p>
217
+ <p id="stat-layers" class="text-2xl font-bold mt-1">0</p>
218
+ </div>
219
+ <!-- Parameters -->
220
+ <div class="bg-dark-800 rounded-xl p-4">
221
+ <p class="text-xs text-dark-500 uppercase tracking-wider">Parameters</p>
222
+ <p id="stat-params" class="text-2xl font-bold mt-1">0M</p>
223
+ </div>
224
+ <!-- Device -->
225
+ <div class="bg-dark-800 rounded-xl p-4">
226
+ <p class="text-xs text-dark-500 uppercase tracking-wider">Device</p>
227
+ <p id="stat-device" class="text-2xl font-bold mt-1">CPU</p>
228
+ </div>
229
+ </div>
230
+
231
+ <!-- Training Stats -->
232
+ <div class="grid grid-cols-4 gap-4 mb-6">
233
+ <!-- Tokens -->
234
+ <div class="bg-dark-800 rounded-xl p-4">
235
+ <p class="text-xs text-dark-500 uppercase tracking-wider">Tokens Processed</p>
236
+ <p id="stat-tokens" class="text-2xl font-bold mt-1">0</p>
237
+ </div>
238
+ <!-- Training Rounds -->
239
+ <div class="bg-dark-800 rounded-xl p-4">
240
+ <p class="text-xs text-dark-500 uppercase tracking-wider">Training Rounds</p>
241
+ <p id="stat-training" class="text-2xl font-bold mt-1">0</p>
242
+ </div>
243
+ <!-- Loss -->
244
+ <div class="bg-dark-800 rounded-xl p-4">
245
+ <p class="text-xs text-dark-500 uppercase tracking-wider">Current Loss</p>
246
+ <p id="stat-loss" class="text-2xl font-bold mt-1">∞</p>
247
+ </div>
248
+ <!-- Earned -->
249
+ <div class="bg-dark-800 rounded-xl p-4">
250
+ <p class="text-xs text-dark-500 uppercase tracking-wider">Total Earned</p>
251
+ <p id="stat-earned" class="text-2xl font-bold text-neuro-500 mt-1">0.00</p>
252
+ </div>
253
+ </div>
254
+
255
+ <!-- Network Info -->
256
+ <div class="bg-dark-800 rounded-xl p-6">
257
+ <h3 class="font-semibold mb-4">Network Information</h3>
258
+
259
+ <div class="space-y-3">
260
+ <div class="flex items-center justify-between py-2 border-b border-dark-700">
261
+ <span class="text-dark-400">Wallet ID</span>
262
+ <span id="wallet-id" class="font-mono text-neuro-500">Not connected</span>
263
+ </div>
264
+ <div class="flex items-center justify-between py-2 border-b border-dark-700">
265
+ <span class="text-dark-400">Instance</span>
266
+ <span id="instance-id" class="font-mono text-sm text-dark-400">-</span>
267
+ </div>
268
+ <div class="flex items-center justify-between py-2 border-b border-dark-700">
269
+ <span class="text-dark-400">Inference Price</span>
270
+ <span id="inference-price" class="font-mono text-sm text-blue-400">-</span>
271
+ </div>
272
+ <div class="flex items-center justify-between py-2">
273
+ <span class="text-dark-400">Tracker</span>
274
+ <span id="tracker-status" class="text-dark-500">Disconnected</span>
275
+ </div>
276
+ </div>
277
+ </div>
278
+
279
+ <!-- Swarm Status -->
280
+ <div class="bg-dark-800 rounded-xl p-6 mt-6" id="swarm-section">
281
+ <h3 class="font-semibold mb-4">Swarm Architecture</h3>
282
+
283
+ <div class="grid grid-cols-3 gap-4">
284
+ <div class="bg-dark-700 rounded-lg p-3">
285
+ <p class="text-xs text-dark-500 uppercase">Inbound Buffer</p>
286
+ <p id="swarm-inbound" class="text-lg font-mono mt-1">0%</p>
287
+ </div>
288
+ <div class="bg-dark-700 rounded-lg p-3">
289
+ <p class="text-xs text-dark-500 uppercase">Outbound Buffer</p>
290
+ <p id="swarm-outbound" class="text-lg font-mono mt-1">0%</p>
291
+ </div>
292
+ <div class="bg-dark-700 rounded-lg p-3">
293
+ <p class="text-xs text-dark-500 uppercase">Known Peers</p>
294
+ <p id="swarm-peers" class="text-lg font-mono mt-1">0</p>
295
+ </div>
296
+ </div>
297
+ </div>
298
+ </div>
299
+
300
+ <!-- Logs Tab -->
301
+ <div id="content-logs" class="hidden flex flex-col flex-1 min-h-0">
302
+ <!-- Filter Bar -->
303
+ <div class="bg-dark-900 border-b border-dark-700 px-6 py-3 flex items-center gap-4 flex-shrink-0">
304
+ <span class="text-sm font-medium text-dark-400">Filter:</span>
305
+ <label class="flex items-center gap-2 cursor-pointer">
306
+ <input type="checkbox" id="filter-all" checked class="rounded bg-dark-700 border-dark-600 text-neuro-500 focus:ring-neuro-500" onchange="applyLogFilter()">
307
+ <span class="text-sm">All</span>
308
+ </label>
309
+ <label class="flex items-center gap-2 cursor-pointer">
310
+ <input type="checkbox" id="filter-neuro" checked class="rounded bg-dark-700 border-dark-600 text-neuro-500 focus:ring-neuro-500" onchange="applyLogFilter()">
311
+ <span class="text-sm text-neuro-500">Rewards</span>
312
+ </label>
313
+ <label class="flex items-center gap-2 cursor-pointer">
314
+ <input type="checkbox" id="filter-training" checked class="rounded bg-dark-700 border-dark-600 text-neuro-500 focus:ring-neuro-500" onchange="applyLogFilter()">
315
+ <span class="text-sm text-purple-400">Training</span>
316
+ </label>
317
+ <label class="flex items-center gap-2 cursor-pointer">
318
+ <input type="checkbox" id="filter-error" checked class="rounded bg-dark-700 border-dark-600 text-neuro-500 focus:ring-neuro-500" onchange="applyLogFilter()">
319
+ <span class="text-sm text-red-400">Errors</span>
320
+ </label>
321
+ <div class="flex-1"></div>
322
+ <button onclick="clearLogs()" class="px-3 py-1.5 text-sm bg-dark-700 hover:bg-dark-600 rounded transition-colors">
323
+ Clear Logs
324
+ </button>
325
+ </div>
326
+
327
+ <!-- Log Output -->
328
+ <div id="log-container" class="flex-1 overflow-y-auto min-h-0 bg-dark-950 p-4 font-mono text-sm">
329
+ <div id="log-output" class="space-y-1">
330
+ <p class="log-info">[00:00:00] NeuroShard Dashboard initialized</p>
331
+ <p class="log-info">[00:00:00] Connecting to node...</p>
332
+ </div>
333
+ </div>
334
+ </div>
335
+
336
+ <!-- Settings Tab -->
337
+ <div id="content-settings" class="hidden p-6 animate-slide-up overflow-auto flex-1">
338
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
339
+ <!-- Left Column -->
340
+ <div class="space-y-6">
341
+ <!-- Node Info -->
342
+ <div class="bg-dark-800 rounded-xl p-6">
343
+ <h3 class="font-semibold mb-4 flex items-center gap-2">
344
+ <svg class="w-5 h-5 text-neuro-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
345
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"/>
346
+ </svg>
347
+ Node Configuration
348
+ </h3>
349
+ <p class="text-sm text-dark-400 mb-4">
350
+ Current node settings. Restart to change.
351
+ </p>
352
+
353
+ <div class="space-y-4">
354
+ <div class="grid grid-cols-2 gap-4">
355
+ <div>
356
+ <label class="text-xs text-dark-500 uppercase tracking-wider">Port</label>
357
+ <p id="setting-port" class="text-lg font-mono text-neuro-400 mt-1">8000</p>
358
+ </div>
359
+ <div>
360
+ <label class="text-xs text-dark-500 uppercase tracking-wider">Wallet ID</label>
361
+ <p id="setting-wallet-id" class="text-sm font-mono text-dark-300 mt-1 truncate">-</p>
362
+ </div>
363
+ </div>
364
+ <div>
365
+ <label class="text-xs text-dark-500 uppercase tracking-wider">Tracker</label>
366
+ <p id="setting-tracker" class="text-sm font-mono text-dark-400 mt-1 truncate">https://neuroshard.com/api/tracker</p>
367
+ </div>
368
+ </div>
369
+ </div>
370
+
371
+ <!-- Resource Limits -->
372
+ <div class="bg-dark-800 rounded-xl p-6">
373
+ <h3 class="font-semibold mb-4 flex items-center gap-2">
374
+ <svg class="w-5 h-5 text-neuro-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
375
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4"/>
376
+ </svg>
377
+ Resource Limits
378
+ </h3>
379
+ <p class="text-sm text-dark-400 mb-4">
380
+ Adjust CPU and memory limits. Changes take effect immediately.
381
+ </p>
382
+
383
+ <div class="space-y-6">
384
+ <!-- CPU Slider -->
385
+ <div>
386
+ <div class="flex justify-between mb-2">
387
+ <label class="text-sm text-dark-400">Max CPU Threads</label>
388
+ <span id="cpu-limit-value" class="text-sm font-mono text-neuro-400">4</span>
389
+ </div>
390
+ <input id="cpu-limit-slider" type="range" min="1" max="16" value="4"
391
+ class="w-full h-2 bg-dark-700 rounded-lg appearance-none cursor-pointer accent-neuro-500"
392
+ oninput="updateCpuLimit(this.value)">
393
+ </div>
394
+
395
+ <!-- Memory Slider -->
396
+ <div>
397
+ <div class="flex justify-between mb-2">
398
+ <label class="text-sm text-dark-400">Max Memory</label>
399
+ <span id="mem-limit-value" class="text-sm font-mono text-neuro-400">4096 MB</span>
400
+ </div>
401
+ <input id="mem-limit-slider" type="range" min="512" max="65536" value="4096" step="512"
402
+ class="w-full h-2 bg-dark-700 rounded-lg appearance-none cursor-pointer accent-neuro-500"
403
+ oninput="updateMemLimit(this.value)">
404
+ <p class="text-xs text-dark-500 mt-1">Memory for model layers (more = more layers = more rewards)</p>
405
+ </div>
406
+
407
+ <!-- Storage Slider -->
408
+ <div>
409
+ <div class="flex justify-between mb-2">
410
+ <label class="text-sm text-dark-400">Training Data Storage</label>
411
+ <span id="storage-limit-value" class="text-sm font-mono text-neuro-400">100 MB</span>
412
+ </div>
413
+ <input id="storage-limit-slider" type="range" min="50" max="10000" value="100" step="50"
414
+ class="w-full h-2 bg-dark-700 rounded-lg appearance-none cursor-pointer accent-neuro-500"
415
+ oninput="updateStorageLimit(this.value)">
416
+ <p class="text-xs text-dark-500 mt-1">Disk space for training data shards (~10 shards per 100MB)</p>
417
+ </div>
418
+ </div>
419
+
420
+ <button onclick="applyThrottle()" class="mt-6 w-full px-4 py-2.5 bg-neuro-600 hover:bg-neuro-500 rounded-lg text-dark-950 font-medium transition-colors">
421
+ Apply Changes
422
+ </button>
423
+ </div>
424
+ </div>
425
+
426
+ <!-- Right Column -->
427
+ <div class="space-y-6">
428
+ <!-- Network Info -->
429
+ <div class="bg-dark-800 rounded-xl p-6">
430
+ <h3 class="font-semibold mb-4 flex items-center gap-2">
431
+ <svg class="w-5 h-5 text-neuro-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
432
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9"/>
433
+ </svg>
434
+ Network Status
435
+ </h3>
436
+
437
+ <div class="grid grid-cols-2 gap-4">
438
+ <div class="bg-dark-900 rounded-lg p-4">
439
+ <p class="text-xs text-dark-500 uppercase tracking-wider">Peers</p>
440
+ <p id="setting-peers" class="text-2xl font-bold text-neuro-400 mt-1">0</p>
441
+ </div>
442
+ <div class="bg-dark-900 rounded-lg p-4">
443
+ <p class="text-xs text-dark-500 uppercase tracking-wider">Layers</p>
444
+ <p id="setting-layers" class="text-2xl font-bold text-neuro-400 mt-1">0</p>
445
+ </div>
446
+ <div class="bg-dark-900 rounded-lg p-4">
447
+ <p class="text-xs text-dark-500 uppercase tracking-wider">Training</p>
448
+ <p id="setting-training" class="text-lg font-semibold mt-1">-</p>
449
+ </div>
450
+ <div class="bg-dark-900 rounded-lg p-4">
451
+ <p class="text-xs text-dark-500 uppercase tracking-wider">Device</p>
452
+ <p id="setting-device" class="text-lg font-semibold mt-1">CPU</p>
453
+ </div>
454
+ </div>
455
+ </div>
456
+
457
+ <!-- CLI Reference -->
458
+ <div class="bg-dark-800 rounded-xl p-6">
459
+ <h3 class="font-semibold mb-4 flex items-center gap-2">
460
+ <svg class="w-5 h-5 text-neuro-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
461
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
462
+ </svg>
463
+ CLI Quick Reference
464
+ </h3>
465
+ <pre class="bg-dark-950 p-4 rounded-lg text-xs overflow-x-auto font-mono text-neuro-400 leading-relaxed"><code># Basic
466
+ neuroshard-node --token YOUR_TOKEN
467
+
468
+ # Custom port
469
+ neuroshard-node --port 9000 --token YOUR_TOKEN
470
+
471
+ # Resource limits
472
+ neuroshard-node --token YOUR_TOKEN \
473
+ --memory 8192 --cpu-threads 8
474
+
475
+ # Inference only
476
+ neuroshard-node --token YOUR_TOKEN --no-training
477
+
478
+ # Headless mode
479
+ neuroshard-node --token YOUR_TOKEN --headless</code></pre>
480
+ </div>
481
+ </div>
482
+ </div>
483
+ </div>
484
+ </div>
485
+ </main>
486
+ </div>
487
+
488
+ <script>
489
+ // State
490
+ let logs = [];
491
+ let nodeId = '';
492
+ let lastLogId = 0; // Track last fetched log ID for polling
493
+ let clientLogIdCounter = -1; // Negative IDs for client-side logs (won't conflict with backend)
494
+
495
+ // Tab switching
496
+ function showTab(tabName) {
497
+ // Hide all content
498
+ document.querySelectorAll('[id^="content-"]').forEach(el => el.classList.add('hidden'));
499
+ // Remove active state from all tabs
500
+ document.querySelectorAll('.tab-btn').forEach(el => {
501
+ el.classList.remove('border-neuro-500', 'text-neuro-500');
502
+ el.classList.add('border-transparent', 'text-dark-400');
503
+ });
504
+
505
+ // Show selected content
506
+ document.getElementById(`content-${tabName}`).classList.remove('hidden');
507
+ // Set active tab
508
+ const tab = document.getElementById(`tab-${tabName}`);
509
+ tab.classList.remove('border-transparent', 'text-dark-400');
510
+ tab.classList.add('border-neuro-500', 'text-neuro-500');
511
+ }
512
+
513
+ // Log management
514
+ function addLog(message, type = 'info') {
515
+ const now = new Date();
516
+ const timestamp = now.toLocaleTimeString('en-US', { hour12: false });
517
+ const epoch = now.getTime(); // Milliseconds since Unix epoch
518
+
519
+ logs.push({
520
+ id: clientLogIdCounter--,
521
+ epoch,
522
+ timestamp,
523
+ message,
524
+ type
525
+ });
526
+
527
+ // Keep last 500 logs
528
+ if (logs.length > 500) logs = logs.slice(-300);
529
+
530
+ renderLogs();
531
+ }
532
+
533
+ let isFirstLoad = true;
534
+
535
+ function renderLogs() {
536
+ const output = document.getElementById('log-output');
537
+ const scrollContainer = document.getElementById('log-container');
538
+ if (!output || !scrollContainer) return;
539
+
540
+ const showAll = document.getElementById('filter-all').checked;
541
+ const showNeuro = document.getElementById('filter-neuro').checked;
542
+ const showTraining = document.getElementById('filter-training').checked;
543
+ const showError = document.getElementById('filter-error').checked;
544
+
545
+ // Check if user is at the bottom BEFORE updating (within 100px tolerance)
546
+ // Also check if scrollHeight is equal to clientHeight (no scrollbar yet)
547
+ const isAtBottom = (scrollContainer.scrollHeight - scrollContainer.scrollTop - scrollContainer.clientHeight) < 100 ||
548
+ (scrollContainer.scrollHeight <= scrollContainer.clientHeight);
549
+
550
+ // Sort logs by epoch timestamp (ascending - oldest first)
551
+ const sortedLogs = [...logs].sort((a, b) => a.epoch - b.epoch);
552
+
553
+ let html = '';
554
+ sortedLogs.forEach(log => {
555
+ let show = showAll;
556
+ if (!showAll) {
557
+ if (log.type === 'neuro' && showNeuro) show = true;
558
+ if (log.type === 'training' && showTraining) show = true;
559
+ if (log.type === 'error' && showError) show = true;
560
+ if (log.type === 'info') show = true;
561
+ }
562
+
563
+ if (show) {
564
+ html += `<p class="log-${log.type}"><span class="log-timestamp">[${log.timestamp}]</span> ${escapeHtml(log.message)}</p>`;
565
+ }
566
+ });
567
+
568
+ output.innerHTML = html || '<p class="text-dark-500">No logs to display</p>';
569
+
570
+ // Auto-scroll to bottom of the scrollable container
571
+ if (isFirstLoad || isAtBottom) {
572
+ requestAnimationFrame(() => {
573
+ scrollContainer.scrollTop = scrollContainer.scrollHeight;
574
+ });
575
+ if (isFirstLoad && logs.length > 0) isFirstLoad = false;
576
+ }
577
+ }
578
+
579
+ function escapeHtml(text) {
580
+ const div = document.createElement('div');
581
+ div.textContent = text;
582
+ return div.innerHTML;
583
+ }
584
+
585
+ function applyLogFilter() {
586
+ renderLogs();
587
+ }
588
+
589
+ function clearLogs() {
590
+ logs = [];
591
+ // Don't reset lastLogId - we still want to poll only new logs from backend
592
+ addLog('Logs cleared', 'info');
593
+ }
594
+
595
+ // Resource limit updates
596
+ function updateCpuLimit(value) {
597
+ document.getElementById('cpu-limit-value').textContent = value;
598
+ }
599
+
600
+ function updateMemLimit(value) {
601
+ document.getElementById('mem-limit-value').textContent = value + ' MB';
602
+ }
603
+
604
+ function updateStorageLimit(value) {
605
+ document.getElementById('storage-limit-value').textContent = value + ' MB';
606
+ }
607
+
608
+ async function applyThrottle() {
609
+ const cpu = document.getElementById('cpu-limit-slider').value;
610
+ const mem = document.getElementById('mem-limit-slider').value;
611
+ const storage = document.getElementById('storage-limit-slider').value;
612
+
613
+ try {
614
+ const resp = await fetch('/api/throttle', {
615
+ method: 'POST',
616
+ headers: { 'Content-Type': 'application/json' },
617
+ body: JSON.stringify({
618
+ cpu_threads: parseInt(cpu),
619
+ memory_mb: parseInt(mem),
620
+ storage_mb: parseInt(storage)
621
+ })
622
+ });
623
+
624
+ if (resp.ok) {
625
+ addLog(`Settings updated: ${cpu} threads, ${mem}MB memory, ${storage}MB storage`, 'success');
626
+ } else {
627
+ addLog('Failed to update settings', 'error');
628
+ }
629
+ } catch (e) {
630
+ addLog('Error updating settings: ' + e.message, 'error');
631
+ }
632
+ }
633
+
634
+ // Stop node gracefully
635
+ async function stopNode() {
636
+ if (!confirm('Are you sure you want to stop the node? Your checkpoint will be saved.')) {
637
+ return;
638
+ }
639
+
640
+ addLog('Stopping node...', 'warning');
641
+
642
+ try {
643
+ const resp = await fetch('/api/shutdown', {
644
+ method: 'POST',
645
+ headers: { 'Content-Type': 'application/json' }
646
+ });
647
+
648
+ if (resp.ok) {
649
+ const data = await resp.json();
650
+ addLog(data.message, 'success');
651
+
652
+ // Update UI to show stopping state
653
+ setStatus('STOPPING', 'yellow', 'Saving checkpoint...');
654
+ document.getElementById('stop-btn').disabled = true;
655
+ document.getElementById('stop-btn').classList.add('opacity-50', 'cursor-not-allowed');
656
+
657
+ // Show message that browser tab can be closed
658
+ setTimeout(() => {
659
+ setStatus('STOPPED', 'red', 'Node stopped. You can close this tab.');
660
+ }, 2000);
661
+ } else {
662
+ addLog('Failed to stop node', 'error');
663
+ }
664
+ } catch (e) {
665
+ addLog('Error stopping node: ' + e.message, 'error');
666
+ }
667
+ }
668
+
669
+ // Status helpers
670
+ function setStatus(status, color, detail) {
671
+ const dot = document.getElementById('status-dot');
672
+ const text = document.getElementById('status-text');
673
+ const detailEl = document.getElementById('status-detail');
674
+
675
+ dot.className = 'w-2.5 h-2.5 rounded-full';
676
+
677
+ if (color === 'green') {
678
+ dot.classList.add('bg-green-500', 'pulse-glow');
679
+ text.classList.remove('text-dark-400', 'text-yellow-500', 'text-red-500');
680
+ text.classList.add('text-green-500');
681
+ } else if (color === 'yellow') {
682
+ dot.classList.add('bg-yellow-500');
683
+ text.classList.remove('text-dark-400', 'text-green-500', 'text-red-500');
684
+ text.classList.add('text-yellow-500');
685
+ } else if (color === 'red') {
686
+ dot.classList.add('bg-red-500');
687
+ text.classList.remove('text-dark-400', 'text-green-500', 'text-yellow-500');
688
+ text.classList.add('text-red-500');
689
+ } else {
690
+ dot.classList.add('bg-dark-500');
691
+ text.classList.remove('text-green-500', 'text-yellow-500', 'text-red-500');
692
+ text.classList.add('text-dark-400');
693
+ }
694
+
695
+ text.textContent = status;
696
+ detailEl.textContent = detail;
697
+ }
698
+
699
+ function setProgressBarColor(bar, percent) {
700
+ bar.classList.remove('bg-neuro-500', 'bg-yellow-500', 'bg-red-500');
701
+ if (percent > 85) {
702
+ bar.classList.add('bg-red-500');
703
+ } else if (percent > 70) {
704
+ bar.classList.add('bg-yellow-500');
705
+ } else {
706
+ bar.classList.add('bg-neuro-500');
707
+ }
708
+ }
709
+
710
+ // Format numbers
711
+ function formatNumber(num) {
712
+ if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M';
713
+ if (num >= 1000) return (num / 1000).toFixed(1) + 'K';
714
+ return num.toString();
715
+ }
716
+
717
+ // Track connection state
718
+ let isConnected = false;
719
+ let lastTotalEarned = 0;
720
+
721
+ // Fetch and update stats
722
+ async function updateStats() {
723
+ try {
724
+ const resp = await fetch('/api/stats');
725
+ if (!resp.ok) throw new Error('Stats unavailable');
726
+
727
+ const data = await resp.json();
728
+
729
+ // Log first successful connection
730
+ if (!isConnected) {
731
+ isConnected = true;
732
+ addLog('Connected to node successfully', 'success');
733
+ }
734
+
735
+ // Update status
736
+ const training = data.training_enabled;
737
+ const status = data.training_status || 'idle';
738
+
739
+ if (status === 'running') {
740
+ setStatus('TRAINING', 'green', `${data.my_layers?.length || 0} layers • ${(data.my_params_m || 0).toFixed(0)}M params`);
741
+ } else if (training) {
742
+ setStatus('ONLINE', 'green', `${data.my_layers?.length || 0} layers • Ready to train`);
743
+ } else {
744
+ setStatus('INFERENCE', 'yellow', 'Training disabled');
745
+ }
746
+
747
+ // Update tracker status
748
+ document.getElementById('tracker-status').textContent = 'Connected';
749
+ document.getElementById('tracker-status').className = 'text-green-500';
750
+
751
+ // Update version
752
+ if (data.version) {
753
+ document.getElementById('version').textContent = 'v' + data.version;
754
+ }
755
+
756
+ // Update stats
757
+ document.getElementById('stat-role').textContent = data.role || 'Worker';
758
+ document.getElementById('stat-layers').textContent = data.my_layers?.length || 0;
759
+ document.getElementById('stat-params').textContent = (data.my_params_m || 0).toFixed(0) + 'M';
760
+ document.getElementById('stat-device').textContent = (data.device || 'cpu').toUpperCase();
761
+
762
+ // Device color
763
+ const deviceEl = document.getElementById('stat-device');
764
+ if (data.device === 'cuda' || data.device === 'mps') {
765
+ deviceEl.classList.add('text-green-500');
766
+ } else {
767
+ deviceEl.classList.remove('text-green-500');
768
+ }
769
+
770
+ // Training stats
771
+ document.getElementById('stat-tokens').textContent = formatNumber(data.token_count || 0);
772
+ document.getElementById('stat-training').textContent = data.training_rounds || 0;
773
+
774
+ // Update settings page values
775
+ const settingsLayers = document.getElementById('setting-layers');
776
+ const settingsPeers = document.getElementById('setting-peers');
777
+ const settingsTraining = document.getElementById('setting-training');
778
+ const settingsDevice = document.getElementById('setting-device');
779
+
780
+ if (settingsLayers) settingsLayers.textContent = data.my_layers?.length || 0;
781
+ if (settingsPeers) settingsPeers.textContent = data.peer_count || 0;
782
+ if (settingsTraining) {
783
+ settingsTraining.textContent = training ? 'Enabled' : 'Disabled';
784
+ settingsTraining.className = training ? 'text-lg font-semibold mt-1 text-green-500' : 'text-lg font-semibold mt-1 text-dark-400';
785
+ }
786
+ if (settingsDevice) {
787
+ const dev = (data.device || 'cpu').toUpperCase();
788
+ settingsDevice.textContent = dev;
789
+ settingsDevice.className = (data.device === 'cuda' || data.device === 'mps')
790
+ ? 'text-lg font-semibold mt-1 text-green-500'
791
+ : 'text-lg font-semibold mt-1 text-dark-400';
792
+ }
793
+
794
+ // Update instance ID in dashboard
795
+ if (data.instance_id) {
796
+ const instanceEl = document.getElementById('instance-id');
797
+ if (instanceEl) instanceEl.textContent = data.instance_id.substring(0, 12) + '...';
798
+ }
799
+
800
+ const loss = data.current_loss;
801
+ document.getElementById('stat-loss').textContent = loss && loss < Infinity ? loss.toFixed(4) : '∞';
802
+
803
+ // DiLoCo progress
804
+ if (data.diloco) {
805
+ document.getElementById('diloco-section').style.display = 'block';
806
+ const inner = data.diloco.inner_step || 0;
807
+ const total = data.diloco.inner_total || 500;
808
+ const progress = (inner / total * 100).toFixed(0);
809
+ document.getElementById('diloco-progress').textContent = `${inner}/${total}`;
810
+ document.getElementById('diloco-bar').style.width = progress + '%';
811
+ document.getElementById('diloco-outer').textContent = data.diloco.outer_step || 0;
812
+ } else {
813
+ document.getElementById('diloco-section').style.display = 'none';
814
+ }
815
+
816
+ // Initialize sliders with actual config values (once)
817
+ if (data.config && !window.slidersInitialized) {
818
+ window.slidersInitialized = true;
819
+
820
+ // CPU threads
821
+ if (data.config.cpu_threads) {
822
+ const cpuSlider = document.getElementById('cpu-limit-slider');
823
+ if (cpuSlider) {
824
+ cpuSlider.value = data.config.cpu_threads;
825
+ document.getElementById('cpu-limit-value').textContent = data.config.cpu_threads;
826
+ }
827
+ }
828
+
829
+ // Memory
830
+ if (data.config.memory_mb) {
831
+ const memSlider = document.getElementById('mem-limit-slider');
832
+ if (memSlider) {
833
+ memSlider.value = data.config.memory_mb;
834
+ document.getElementById('mem-limit-value').textContent = data.config.memory_mb + ' MB';
835
+ }
836
+ }
837
+
838
+ // Storage
839
+ if (data.config.storage_mb) {
840
+ const storageSlider = document.getElementById('storage-limit-slider');
841
+ if (storageSlider) {
842
+ storageSlider.value = data.config.storage_mb;
843
+ document.getElementById('storage-limit-value').textContent = data.config.storage_mb + ' MB';
844
+ }
845
+ }
846
+
847
+ addLog(`Config loaded: CPU=${data.config.cpu_threads || 'auto'}, Memory=${data.config.memory_mb || 'auto'}MB, Storage=${data.config.storage_mb || 100}MB`, 'info');
848
+ }
849
+
850
+ } catch (e) {
851
+ setStatus('OFFLINE', 'red', 'Cannot connect to node');
852
+ document.getElementById('tracker-status').textContent = 'Disconnected';
853
+ document.getElementById('tracker-status').className = 'text-dark-500';
854
+ }
855
+ }
856
+
857
+ // Track peer count for balance warning (-1 = not yet fetched)
858
+ let lastKnownPeerCount = -1;
859
+
860
+ // Fetch balance
861
+ async function updateBalance() {
862
+ try {
863
+ const resp = await fetch('/api/neuro');
864
+ if (!resp.ok) return;
865
+
866
+ const data = await resp.json();
867
+
868
+ const totalEarned = data.total_earned || 0;
869
+ const balance = data.balance || 0;
870
+
871
+ document.getElementById('balance').textContent = balance.toFixed(4);
872
+ document.getElementById('total-earned').textContent = totalEarned.toFixed(4);
873
+ document.getElementById('stat-earned').textContent = totalEarned.toFixed(4);
874
+
875
+ // Show warning if earning while solo (proofs won't be network-confirmed)
876
+ // Only show if we've actually fetched peer count (lastKnownPeerCount >= 0)
877
+ const balanceNote = document.getElementById('balance-note');
878
+ if (balanceNote) {
879
+ // Show warning if we have balance but no/few peers AND we've checked
880
+ if (totalEarned > 0 && lastKnownPeerCount === 0) {
881
+ balanceNote.classList.remove('hidden');
882
+ } else {
883
+ balanceNote.classList.add('hidden');
884
+ }
885
+ }
886
+
887
+ // Detect new earnings and log them
888
+ if (lastTotalEarned > 0 && totalEarned > lastTotalEarned) {
889
+ const earned = totalEarned - lastTotalEarned;
890
+ addLog(`Earned +${earned.toFixed(6)} NEURO (PoNW reward)`, 'neuro');
891
+
892
+ // Only warn if we've confirmed no peers (not just haven't checked yet)
893
+ if (lastKnownPeerCount === 0) {
894
+ addLog('⚠ Earning while solo - proofs need peers to be network-confirmed', 'warning');
895
+ }
896
+ }
897
+ lastTotalEarned = totalEarned;
898
+
899
+ // Update wallet and node IDs
900
+ if (data.wallet_id) {
901
+ document.getElementById('wallet-id').textContent = data.wallet_id.substring(0, 16) + '...';
902
+ // Also update settings page
903
+ const settingWallet = document.getElementById('setting-wallet-id');
904
+ if (settingWallet) settingWallet.textContent = data.wallet_id.substring(0, 24) + '...';
905
+ }
906
+ if (data.node_id) {
907
+ nodeId = data.node_id;
908
+ }
909
+
910
+ } catch (e) {
911
+ // Silent fail
912
+ }
913
+ }
914
+
915
+ // Fetch swarm status
916
+ async function updateSwarm() {
917
+ try {
918
+ const resp = await fetch('/api/swarm');
919
+ if (!resp.ok) return;
920
+
921
+ const data = await resp.json();
922
+
923
+ document.getElementById('swarm-inbound').textContent = ((data.inbound_fill_rate || 0) * 100).toFixed(0) + '%';
924
+ document.getElementById('swarm-outbound').textContent = ((data.outbound_fill_rate || 0) * 100).toFixed(0) + '%';
925
+ document.getElementById('swarm-peers').textContent = data.known_peers || 0;
926
+
927
+ // Track peer count for balance warning
928
+ lastKnownPeerCount = data.known_peers || 0;
929
+
930
+ } catch (e) {
931
+ // Silent fail
932
+ }
933
+ }
934
+
935
+ // Fetch market data for inference price
936
+ async function updateMarket() {
937
+ try {
938
+ const resp = await fetch('/api/market');
939
+ if (!resp.ok) return;
940
+
941
+ const data = await resp.json();
942
+
943
+ // Update inference price
944
+ const priceEl = document.getElementById('inference-price');
945
+ if (priceEl && data.current_price !== undefined) {
946
+ const price = data.current_price;
947
+ priceEl.textContent = price.toFixed(6) + ' NEURO/tok';
948
+ }
949
+
950
+ } catch (e) {
951
+ // Silent fail - market may not be available
952
+ }
953
+ }
954
+
955
+ // Fetch system resources
956
+ async function updateSystem() {
957
+ try {
958
+ const resp = await fetch('/api/stats');
959
+ if (!resp.ok) return;
960
+
961
+ const data = await resp.json();
962
+ const system = data.system || {};
963
+
964
+ // CPU - actual system usage
965
+ const cpuPercent = Math.round(system.cpu_percent || 0);
966
+ document.getElementById('cpu-percent').textContent = cpuPercent + '%';
967
+ document.getElementById('cpu-bar').style.width = cpuPercent + '%';
968
+ setProgressBarColor(document.getElementById('cpu-bar'), cpuPercent);
969
+
970
+ // RAM - actual usage with total
971
+ const ramUsed = system.ram_used_gb || 0;
972
+ const ramTotal = system.ram_total_gb || 0;
973
+ const ramPercent = system.ram_percent || 0;
974
+ document.getElementById('ram-percent').textContent = `${ramUsed.toFixed(1)} / ${ramTotal.toFixed(1)} GB`;
975
+
976
+ // Update RAM progress bar
977
+ const ramBar = document.getElementById('ram-bar');
978
+ if (ramBar) {
979
+ ramBar.style.width = ramPercent + '%';
980
+ setProgressBarColor(ramBar, ramPercent);
981
+ }
982
+
983
+ } catch (e) {
984
+ // Silent fail
985
+ }
986
+ }
987
+
988
+ // Fetch backend logs
989
+ async function updateLogs() {
990
+ try {
991
+ const resp = await fetch(`/api/logs?since_id=${lastLogId}&limit=100`);
992
+ if (!resp.ok) return;
993
+
994
+ const data = await resp.json();
995
+
996
+ // Add new logs from backend
997
+ if (data.logs && data.logs.length > 0) {
998
+ data.logs.forEach(log => {
999
+ // Avoid duplicates by checking log ID
1000
+ const isDuplicate = logs.some(existing => existing.id === log.id);
1001
+
1002
+ if (!isDuplicate) {
1003
+ logs.push({
1004
+ id: log.id,
1005
+ epoch: log.epoch,
1006
+ timestamp: log.timestamp,
1007
+ message: log.message,
1008
+ type: log.type || 'info'
1009
+ });
1010
+ }
1011
+ });
1012
+
1013
+ // Keep last 500 logs
1014
+ if (logs.length > 500) logs = logs.slice(-300);
1015
+
1016
+ renderLogs();
1017
+ }
1018
+
1019
+ // Update ID for next poll
1020
+ lastLogId = data.latest_id || lastLogId;
1021
+
1022
+ } catch (e) {
1023
+ // Silent fail
1024
+ }
1025
+ }
1026
+
1027
+ // Initial setup
1028
+ addLog('Dashboard loaded', 'info');
1029
+ addLog('Connecting to node...', 'info');
1030
+
1031
+ // Start update loops
1032
+ updateStats();
1033
+ updateBalance();
1034
+ updateSwarm();
1035
+ updateSystem();
1036
+ updateMarket(); // Fetch market prices
1037
+ updateLogs(); // Fetch initial logs
1038
+
1039
+ setInterval(updateStats, 3000);
1040
+ setInterval(updateBalance, 5000); // Check for new earnings every 5s
1041
+ setInterval(updateSwarm, 5000);
1042
+ setInterval(updateSystem, 5000);
1043
+ setInterval(updateMarket, 10000); // Market prices every 10s
1044
+ setInterval(updateLogs, 2000); // Poll logs every 2s
1045
+
1046
+ // Get current URL port for settings
1047
+ const port = window.location.port || '8000';
1048
+ const settingPort = document.getElementById('setting-port');
1049
+ if (settingPort) settingPort.textContent = port;
1050
+ </script>
1051
+ </body>
1052
+ </html>