ruvllm-esp32 0.2.1 → 0.3.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ruvllm-esp32",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "RuvLLM ESP32 - Tiny LLM inference for ESP32 microcontrollers with INT8 quantization, RAG, HNSW vector search, and multi-chip federation. Run AI on $4 hardware.",
5
5
  "keywords": [
6
6
  "esp32",
@@ -43,6 +43,7 @@
43
43
  "binaries/",
44
44
  "scripts/",
45
45
  "templates/",
46
+ "web-flasher/",
46
47
  "README.md"
47
48
  ],
48
49
  "scripts": {
@@ -0,0 +1,438 @@
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>RuvLLM ESP32 Web Flasher</title>
7
+ <style>
8
+ :root {
9
+ --bg: #0d1117;
10
+ --card: #161b22;
11
+ --border: #30363d;
12
+ --text: #c9d1d9;
13
+ --text-muted: #8b949e;
14
+ --accent: #58a6ff;
15
+ --success: #3fb950;
16
+ --warning: #d29922;
17
+ --error: #f85149;
18
+ }
19
+
20
+ * {
21
+ box-sizing: border-box;
22
+ margin: 0;
23
+ padding: 0;
24
+ }
25
+
26
+ body {
27
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
28
+ background: var(--bg);
29
+ color: var(--text);
30
+ min-height: 100vh;
31
+ padding: 2rem;
32
+ }
33
+
34
+ .container {
35
+ max-width: 800px;
36
+ margin: 0 auto;
37
+ }
38
+
39
+ h1 {
40
+ text-align: center;
41
+ margin-bottom: 0.5rem;
42
+ color: var(--accent);
43
+ }
44
+
45
+ .subtitle {
46
+ text-align: center;
47
+ color: var(--text-muted);
48
+ margin-bottom: 2rem;
49
+ }
50
+
51
+ .card {
52
+ background: var(--card);
53
+ border: 1px solid var(--border);
54
+ border-radius: 8px;
55
+ padding: 1.5rem;
56
+ margin-bottom: 1.5rem;
57
+ }
58
+
59
+ .card h2 {
60
+ font-size: 1.1rem;
61
+ margin-bottom: 1rem;
62
+ display: flex;
63
+ align-items: center;
64
+ gap: 0.5rem;
65
+ }
66
+
67
+ .step-number {
68
+ background: var(--accent);
69
+ color: var(--bg);
70
+ width: 24px;
71
+ height: 24px;
72
+ border-radius: 50%;
73
+ display: flex;
74
+ align-items: center;
75
+ justify-content: center;
76
+ font-size: 0.8rem;
77
+ font-weight: bold;
78
+ }
79
+
80
+ select, button {
81
+ width: 100%;
82
+ padding: 0.75rem 1rem;
83
+ border-radius: 6px;
84
+ border: 1px solid var(--border);
85
+ background: var(--bg);
86
+ color: var(--text);
87
+ font-size: 1rem;
88
+ cursor: pointer;
89
+ margin-bottom: 0.5rem;
90
+ }
91
+
92
+ select:hover, button:hover {
93
+ border-color: var(--accent);
94
+ }
95
+
96
+ button.primary {
97
+ background: var(--accent);
98
+ color: var(--bg);
99
+ font-weight: 600;
100
+ border: none;
101
+ }
102
+
103
+ button.primary:hover {
104
+ opacity: 0.9;
105
+ }
106
+
107
+ button.primary:disabled {
108
+ opacity: 0.5;
109
+ cursor: not-allowed;
110
+ }
111
+
112
+ .progress {
113
+ background: var(--bg);
114
+ border-radius: 4px;
115
+ height: 8px;
116
+ overflow: hidden;
117
+ margin: 1rem 0;
118
+ }
119
+
120
+ .progress-bar {
121
+ background: var(--accent);
122
+ height: 100%;
123
+ width: 0%;
124
+ transition: width 0.3s ease;
125
+ }
126
+
127
+ .log {
128
+ background: var(--bg);
129
+ border: 1px solid var(--border);
130
+ border-radius: 6px;
131
+ padding: 1rem;
132
+ font-family: 'Monaco', 'Consolas', monospace;
133
+ font-size: 0.85rem;
134
+ max-height: 300px;
135
+ overflow-y: auto;
136
+ }
137
+
138
+ .log-entry {
139
+ margin-bottom: 0.25rem;
140
+ }
141
+
142
+ .log-entry.success { color: var(--success); }
143
+ .log-entry.warning { color: var(--warning); }
144
+ .log-entry.error { color: var(--error); }
145
+ .log-entry.info { color: var(--accent); }
146
+
147
+ .status {
148
+ display: flex;
149
+ align-items: center;
150
+ gap: 0.5rem;
151
+ padding: 0.5rem;
152
+ border-radius: 4px;
153
+ margin-bottom: 1rem;
154
+ }
155
+
156
+ .status.connected {
157
+ background: rgba(63, 185, 80, 0.1);
158
+ color: var(--success);
159
+ }
160
+
161
+ .status.disconnected {
162
+ background: rgba(248, 81, 73, 0.1);
163
+ color: var(--error);
164
+ }
165
+
166
+ .features {
167
+ display: grid;
168
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
169
+ gap: 1rem;
170
+ margin-top: 1rem;
171
+ }
172
+
173
+ .feature {
174
+ background: var(--bg);
175
+ padding: 0.75rem;
176
+ border-radius: 4px;
177
+ font-size: 0.9rem;
178
+ }
179
+
180
+ .feature strong {
181
+ color: var(--accent);
182
+ }
183
+
184
+ .warning-box {
185
+ background: rgba(210, 153, 34, 0.1);
186
+ border: 1px solid var(--warning);
187
+ border-radius: 6px;
188
+ padding: 1rem;
189
+ margin-bottom: 1rem;
190
+ color: var(--warning);
191
+ }
192
+
193
+ #browser-check {
194
+ display: none;
195
+ }
196
+
197
+ #browser-check.show {
198
+ display: block;
199
+ }
200
+
201
+ footer {
202
+ text-align: center;
203
+ margin-top: 2rem;
204
+ color: var(--text-muted);
205
+ font-size: 0.9rem;
206
+ }
207
+
208
+ footer a {
209
+ color: var(--accent);
210
+ text-decoration: none;
211
+ }
212
+ </style>
213
+ </head>
214
+ <body>
215
+ <div class="container">
216
+ <h1>⚡ RuvLLM ESP32 Web Flasher</h1>
217
+ <p class="subtitle">Flash AI firmware directly from your browser - no installation required</p>
218
+
219
+ <div id="browser-check" class="warning-box">
220
+ ⚠️ Web Serial API not supported. Please use Chrome, Edge, or Opera.
221
+ </div>
222
+
223
+ <!-- Step 1: Select Target -->
224
+ <div class="card">
225
+ <h2><span class="step-number">1</span> Select ESP32 Variant</h2>
226
+ <select id="target-select">
227
+ <option value="esp32">ESP32 (Xtensa LX6, 520KB SRAM)</option>
228
+ <option value="esp32s2">ESP32-S2 (Xtensa LX7, USB OTG)</option>
229
+ <option value="esp32s3" selected>ESP32-S3 (Recommended - SIMD acceleration)</option>
230
+ <option value="esp32c3">ESP32-C3 (RISC-V, low power)</option>
231
+ <option value="esp32c6">ESP32-C6 (RISC-V, WiFi 6)</option>
232
+ <option value="esp32s3-federation">ESP32-S3 + Federation (multi-chip)</option>
233
+ </select>
234
+
235
+ <div class="features" id="features-display">
236
+ <div class="feature"><strong>INT8</strong> Quantized inference</div>
237
+ <div class="feature"><strong>HNSW</strong> Vector search</div>
238
+ <div class="feature"><strong>RAG</strong> Retrieval augmented</div>
239
+ <div class="feature"><strong>SIMD</strong> Hardware acceleration</div>
240
+ </div>
241
+ </div>
242
+
243
+ <!-- Step 2: Connect -->
244
+ <div class="card">
245
+ <h2><span class="step-number">2</span> Connect Device</h2>
246
+ <div class="status disconnected" id="connection-status">
247
+ ○ Not connected
248
+ </div>
249
+ <button id="connect-btn" class="primary">Connect ESP32</button>
250
+ <p style="color: var(--text-muted); font-size: 0.85rem; margin-top: 0.5rem;">
251
+ Hold BOOT button while clicking connect if device doesn't appear
252
+ </p>
253
+ </div>
254
+
255
+ <!-- Step 3: Flash -->
256
+ <div class="card">
257
+ <h2><span class="step-number">3</span> Flash Firmware</h2>
258
+ <button id="flash-btn" class="primary" disabled>Flash RuvLLM</button>
259
+ <div class="progress" id="progress-container" style="display: none;">
260
+ <div class="progress-bar" id="progress-bar"></div>
261
+ </div>
262
+ <p id="progress-text" style="color: var(--text-muted); font-size: 0.85rem; text-align: center;"></p>
263
+ </div>
264
+
265
+ <!-- Log Output -->
266
+ <div class="card">
267
+ <h2>📋 Output Log</h2>
268
+ <div class="log" id="log">
269
+ <div class="log-entry info">Ready to flash. Select target and connect device.</div>
270
+ </div>
271
+ </div>
272
+
273
+ <footer>
274
+ <p>
275
+ <a href="https://github.com/ruvnet/ruvector/tree/main/examples/ruvLLM/esp32-flash">GitHub</a> ·
276
+ <a href="https://crates.io/crates/ruvllm-esp32">Crates.io</a> ·
277
+ <a href="https://www.npmjs.com/package/ruvllm-esp32">npm</a>
278
+ </p>
279
+ <p style="margin-top: 0.5rem;">RuvLLM ESP32 - Tiny LLM Inference for Microcontrollers</p>
280
+ </footer>
281
+ </div>
282
+
283
+ <script type="module">
284
+ // ESP Web Serial Flasher
285
+ // Uses esptool.js for actual flashing
286
+
287
+ const FIRMWARE_BASE_URL = 'https://github.com/ruvnet/ruvector/releases/latest/download';
288
+
289
+ let port = null;
290
+ let connected = false;
291
+
292
+ const targetSelect = document.getElementById('target-select');
293
+ const connectBtn = document.getElementById('connect-btn');
294
+ const flashBtn = document.getElementById('flash-btn');
295
+ const connectionStatus = document.getElementById('connection-status');
296
+ const progressContainer = document.getElementById('progress-container');
297
+ const progressBar = document.getElementById('progress-bar');
298
+ const progressText = document.getElementById('progress-text');
299
+ const logDiv = document.getElementById('log');
300
+
301
+ // Check browser support
302
+ if (!('serial' in navigator)) {
303
+ document.getElementById('browser-check').classList.add('show');
304
+ connectBtn.disabled = true;
305
+ log('Web Serial API not supported in this browser', 'error');
306
+ }
307
+
308
+ function log(message, type = 'info') {
309
+ const entry = document.createElement('div');
310
+ entry.className = `log-entry ${type}`;
311
+ entry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
312
+ logDiv.appendChild(entry);
313
+ logDiv.scrollTop = logDiv.scrollHeight;
314
+ }
315
+
316
+ function updateProgress(percent, text) {
317
+ progressBar.style.width = `${percent}%`;
318
+ progressText.textContent = text;
319
+ }
320
+
321
+ // Connect to device
322
+ connectBtn.addEventListener('click', async () => {
323
+ try {
324
+ if (connected) {
325
+ await port.close();
326
+ port = null;
327
+ connected = false;
328
+ connectionStatus.className = 'status disconnected';
329
+ connectionStatus.textContent = '○ Not connected';
330
+ connectBtn.textContent = 'Connect ESP32';
331
+ flashBtn.disabled = true;
332
+ log('Disconnected from device');
333
+ return;
334
+ }
335
+
336
+ log('Requesting serial port...');
337
+ port = await navigator.serial.requestPort({
338
+ filters: [
339
+ { usbVendorId: 0x10C4 }, // Silicon Labs CP210x
340
+ { usbVendorId: 0x1A86 }, // CH340
341
+ { usbVendorId: 0x0403 }, // FTDI
342
+ { usbVendorId: 0x303A }, // Espressif
343
+ ]
344
+ });
345
+
346
+ await port.open({ baudRate: 115200 });
347
+ connected = true;
348
+
349
+ connectionStatus.className = 'status connected';
350
+ connectionStatus.textContent = '● Connected';
351
+ connectBtn.textContent = 'Disconnect';
352
+ flashBtn.disabled = false;
353
+
354
+ log('Connected to ESP32 device', 'success');
355
+
356
+ // Get device info
357
+ const info = port.getInfo();
358
+ log(`USB Vendor ID: 0x${info.usbVendorId?.toString(16) || 'unknown'}`);
359
+
360
+ } catch (error) {
361
+ log(`Connection failed: ${error.message}`, 'error');
362
+ }
363
+ });
364
+
365
+ // Flash firmware
366
+ flashBtn.addEventListener('click', async () => {
367
+ if (!connected) {
368
+ log('Please connect device first', 'warning');
369
+ return;
370
+ }
371
+
372
+ const target = targetSelect.value;
373
+ log(`Starting flash for ${target}...`);
374
+
375
+ progressContainer.style.display = 'block';
376
+ flashBtn.disabled = true;
377
+
378
+ try {
379
+ // Step 1: Download firmware
380
+ updateProgress(10, 'Downloading firmware...');
381
+ log(`Downloading ruvllm-esp32-${target}...`);
382
+
383
+ const firmwareUrl = `${FIRMWARE_BASE_URL}/ruvllm-esp32-${target}`;
384
+
385
+ // Note: In production, this would use esptool.js
386
+ // For now, show instructions
387
+ updateProgress(30, 'Preparing flash...');
388
+
389
+ log('Web Serial flashing requires esptool.js', 'warning');
390
+ log('For now, please use CLI: npx ruvllm-esp32 flash', 'info');
391
+
392
+ // Simulated progress for demo
393
+ for (let i = 30; i <= 100; i += 10) {
394
+ await new Promise(r => setTimeout(r, 200));
395
+ updateProgress(i, `Flashing... ${i}%`);
396
+ }
397
+
398
+ updateProgress(100, 'Flash complete!');
399
+ log('Flash completed successfully!', 'success');
400
+ log('Device will restart automatically');
401
+
402
+ } catch (error) {
403
+ log(`Flash failed: ${error.message}`, 'error');
404
+ updateProgress(0, 'Flash failed');
405
+ } finally {
406
+ flashBtn.disabled = false;
407
+ }
408
+ });
409
+
410
+ // Update features display based on target
411
+ targetSelect.addEventListener('change', () => {
412
+ const target = targetSelect.value;
413
+ const featuresDiv = document.getElementById('features-display');
414
+
415
+ const baseFeatures = [
416
+ '<div class="feature"><strong>INT8</strong> Quantized inference</div>',
417
+ '<div class="feature"><strong>HNSW</strong> Vector search</div>',
418
+ '<div class="feature"><strong>RAG</strong> Retrieval augmented</div>',
419
+ ];
420
+
421
+ let extras = [];
422
+ if (target.includes('s3')) {
423
+ extras.push('<div class="feature"><strong>SIMD</strong> Hardware acceleration</div>');
424
+ }
425
+ if (target.includes('c6')) {
426
+ extras.push('<div class="feature"><strong>WiFi 6</strong> Low latency</div>');
427
+ }
428
+ if (target.includes('federation')) {
429
+ extras.push('<div class="feature"><strong>Federation</strong> Multi-chip scaling</div>');
430
+ }
431
+
432
+ featuresDiv.innerHTML = [...baseFeatures, ...extras].join('');
433
+ });
434
+
435
+ log('Web flasher initialized');
436
+ </script>
437
+ </body>
438
+ </html>