@hanzo/browser-extension 1.9.34 → 1.9.35

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 (67) hide show
  1. package/package.json +1 -1
  2. package/dist/browser-extension/README.md +0 -43
  3. package/dist/browser-extension/background-firefox.js +0 -5983
  4. package/dist/browser-extension/background.js +0 -8670
  5. package/dist/browser-extension/browser-control.js +0 -1317
  6. package/dist/browser-extension/chrome/ai-worker.js +0 -571
  7. package/dist/browser-extension/chrome/background.js +0 -8670
  8. package/dist/browser-extension/chrome/callback.html +0 -17
  9. package/dist/browser-extension/chrome/content-script.js +0 -2789
  10. package/dist/browser-extension/chrome/icon128.png +0 -0
  11. package/dist/browser-extension/chrome/icon16.png +0 -0
  12. package/dist/browser-extension/chrome/icon48.png +0 -0
  13. package/dist/browser-extension/chrome/manifest.json +0 -81
  14. package/dist/browser-extension/chrome/newtab.css +0 -277
  15. package/dist/browser-extension/chrome/newtab.html +0 -13
  16. package/dist/browser-extension/chrome/newtab.js +0 -64
  17. package/dist/browser-extension/chrome/popup.css +0 -761
  18. package/dist/browser-extension/chrome/popup.html +0 -381
  19. package/dist/browser-extension/chrome/popup.js +0 -1716
  20. package/dist/browser-extension/chrome/sidebar.css +0 -1792
  21. package/dist/browser-extension/chrome/sidebar.html +0 -463
  22. package/dist/browser-extension/chrome/sidebar.js +0 -26541
  23. package/dist/browser-extension/cli.js +0 -233
  24. package/dist/browser-extension/content-script.js +0 -2789
  25. package/dist/browser-extension/firefox/ai-worker.js +0 -571
  26. package/dist/browser-extension/firefox/background.js +0 -5983
  27. package/dist/browser-extension/firefox/callback.html +0 -17
  28. package/dist/browser-extension/firefox/content-script.js +0 -2789
  29. package/dist/browser-extension/firefox/icon128.png +0 -0
  30. package/dist/browser-extension/firefox/icon16.png +0 -0
  31. package/dist/browser-extension/firefox/icon48.png +0 -0
  32. package/dist/browser-extension/firefox/manifest.json +0 -85
  33. package/dist/browser-extension/firefox/newtab.css +0 -277
  34. package/dist/browser-extension/firefox/newtab.html +0 -13
  35. package/dist/browser-extension/firefox/newtab.js +0 -64
  36. package/dist/browser-extension/firefox/popup.css +0 -761
  37. package/dist/browser-extension/firefox/popup.html +0 -381
  38. package/dist/browser-extension/firefox/popup.js +0 -1716
  39. package/dist/browser-extension/firefox/sidebar.css +0 -1792
  40. package/dist/browser-extension/firefox/sidebar.html +0 -463
  41. package/dist/browser-extension/firefox/sidebar.js +0 -26541
  42. package/dist/browser-extension/icon128.png +0 -0
  43. package/dist/browser-extension/icon16.png +0 -0
  44. package/dist/browser-extension/icon48.png +0 -0
  45. package/dist/browser-extension/manifest.json +0 -81
  46. package/dist/browser-extension/newtab.js +0 -64
  47. package/dist/browser-extension/package.json +0 -41
  48. package/dist/browser-extension/popup.js +0 -1716
  49. package/dist/browser-extension/safari/Info.plist +0 -21
  50. package/dist/browser-extension/safari/ai-worker.js +0 -571
  51. package/dist/browser-extension/safari/background.js +0 -8670
  52. package/dist/browser-extension/safari/callback.html +0 -17
  53. package/dist/browser-extension/safari/content-script.js +0 -2789
  54. package/dist/browser-extension/safari/icon128.png +0 -0
  55. package/dist/browser-extension/safari/icon16.png +0 -0
  56. package/dist/browser-extension/safari/icon48.png +0 -0
  57. package/dist/browser-extension/safari/newtab.css +0 -277
  58. package/dist/browser-extension/safari/newtab.html +0 -13
  59. package/dist/browser-extension/safari/newtab.js +0 -64
  60. package/dist/browser-extension/safari/popup.css +0 -761
  61. package/dist/browser-extension/safari/popup.html +0 -381
  62. package/dist/browser-extension/safari/popup.js +0 -1716
  63. package/dist/browser-extension/safari/sidebar.css +0 -1792
  64. package/dist/browser-extension/safari/sidebar.html +0 -463
  65. package/dist/browser-extension/safari/sidebar.js +0 -26541
  66. package/dist/browser-extension/sidebar.js +0 -26541
  67. package/dist/browser-extension/webgpu-ai.js +0 -568
@@ -1,571 +0,0 @@
1
- // Hanzo AI Worker — WebGPU inference engine running in web worker context.
2
- // Handles model loading, GPU compute pipelines, and token generation.
3
- // Referenced by browser-control.ts and loaded as a web-accessible resource.
4
-
5
- /** @type {GPUDevice|null} */
6
- let gpuDevice = null;
7
-
8
- /** @type {Map<string, {buffer: GPUBuffer, config: object, vocab: string[]}>} */
9
- const loadedModels = new Map();
10
-
11
- /** @type {boolean} */
12
- let initialized = false;
13
-
14
- // ---------------------------------------------------------------------------
15
- // GPU Initialization
16
- // ---------------------------------------------------------------------------
17
-
18
- async function initGPU() {
19
- if (!navigator.gpu) {
20
- return { success: false, error: 'WebGPU not available in this browser' };
21
- }
22
-
23
- try {
24
- const adapter = await navigator.gpu.requestAdapter({ powerPreference: 'high-performance' });
25
- if (!adapter) {
26
- return { success: false, error: 'No GPU adapter found' };
27
- }
28
-
29
- const features = Array.from(adapter.features);
30
- gpuDevice = await adapter.requestDevice({
31
- requiredLimits: {
32
- maxStorageBufferBindingSize: adapter.limits?.maxStorageBufferBindingSize || 134217728,
33
- maxBufferSize: adapter.limits?.maxBufferSize || 268435456,
34
- },
35
- });
36
-
37
- gpuDevice.lost.then((info) => {
38
- gpuDevice = null;
39
- initialized = false;
40
- self.postMessage({ type: 'error', payload: { error: `GPU device lost: ${info.message}` } });
41
- });
42
-
43
- initialized = true;
44
- return { success: true, features, adapter: adapter.name || 'GPU' };
45
- } catch (err) {
46
- return { success: false, error: String(err) };
47
- }
48
- }
49
-
50
- // ---------------------------------------------------------------------------
51
- // BPE Tokenizer (byte-level, GPT-2 compatible)
52
- // ---------------------------------------------------------------------------
53
-
54
- // Byte-to-unicode mapping (identical to GPT-2 byte encoder)
55
- function bytesToUnicode() {
56
- const bs = [];
57
- // Printable ASCII
58
- for (let i = 33; i <= 126; i++) bs.push(i); // ! to ~
59
- for (let i = 161; i <= 172; i++) bs.push(i); // ¡ to ¬
60
- for (let i = 174; i <= 255; i++) bs.push(i); // ® to ÿ
61
- const cs = bs.slice();
62
- let n = 0;
63
- for (let b = 0; b < 256; b++) {
64
- if (!bs.includes(b)) {
65
- bs.push(b);
66
- cs.push(256 + n);
67
- n++;
68
- }
69
- }
70
- const result = {};
71
- for (let i = 0; i < bs.length; i++) {
72
- result[bs[i]] = String.fromCharCode(cs[i]);
73
- }
74
- return result;
75
- }
76
-
77
- const BYTE_ENCODER = bytesToUnicode();
78
- const BYTE_DECODER = {};
79
- for (const [k, v] of Object.entries(BYTE_ENCODER)) {
80
- BYTE_DECODER[v] = parseInt(k);
81
- }
82
-
83
- /**
84
- * Encode text to token IDs using a vocabulary list.
85
- * Falls back to character-level encoding if vocab is unavailable.
86
- */
87
- function tokenize(text, vocab) {
88
- if (!vocab || !vocab.length) {
89
- // Character-level fallback: each UTF-16 code unit becomes a token
90
- const tokens = [];
91
- for (let i = 0; i < text.length; i++) {
92
- tokens.push(text.charCodeAt(i));
93
- }
94
- return tokens;
95
- }
96
-
97
- // BPE-style word-level tokenization with byte encoding
98
- const encoded = [];
99
- const bytes = new TextEncoder().encode(text);
100
- let word = '';
101
- for (const b of bytes) {
102
- word += BYTE_ENCODER[b] || String.fromCharCode(b);
103
- }
104
-
105
- // Greedy longest-match against vocabulary
106
- const tokens = [];
107
- let pos = 0;
108
- while (pos < word.length) {
109
- let bestLen = 0;
110
- let bestIdx = -1;
111
- // Try longest possible match
112
- const maxLen = Math.min(word.length - pos, 50);
113
- for (let len = maxLen; len >= 1; len--) {
114
- const candidate = word.substring(pos, pos + len);
115
- const idx = vocab.indexOf(candidate);
116
- if (idx !== -1) {
117
- bestLen = len;
118
- bestIdx = idx;
119
- break;
120
- }
121
- }
122
- if (bestIdx !== -1) {
123
- tokens.push(bestIdx);
124
- pos += bestLen;
125
- } else {
126
- // Unknown byte — use raw char code
127
- tokens.push(word.charCodeAt(pos) % vocab.length);
128
- pos++;
129
- }
130
- }
131
- return tokens;
132
- }
133
-
134
- /**
135
- * Decode token IDs back to text.
136
- */
137
- function detokenize(tokenIds, vocab) {
138
- if (!vocab || !vocab.length) {
139
- return String.fromCharCode(...tokenIds.filter(t => t > 0 && t < 65536));
140
- }
141
-
142
- let byteStr = '';
143
- for (const id of tokenIds) {
144
- if (id >= 0 && id < vocab.length) {
145
- byteStr += vocab[id];
146
- }
147
- }
148
-
149
- // Decode byte-level BPE back to UTF-8
150
- const bytes = [];
151
- for (const ch of byteStr) {
152
- if (ch in BYTE_DECODER) {
153
- bytes.push(BYTE_DECODER[ch]);
154
- } else {
155
- bytes.push(ch.charCodeAt(0) & 0xFF);
156
- }
157
- }
158
- return new TextDecoder().decode(new Uint8Array(bytes));
159
- }
160
-
161
- // ---------------------------------------------------------------------------
162
- // Model Loading
163
- // ---------------------------------------------------------------------------
164
-
165
- async function loadModel(name, url, config) {
166
- if (!gpuDevice) {
167
- throw new Error('GPU not initialized');
168
- }
169
-
170
- // Fetch model weights (ArrayBuffer)
171
- const response = await fetch(url);
172
- if (!response.ok) {
173
- throw new Error(`Failed to fetch model: ${response.status} ${response.statusText}`);
174
- }
175
- const weights = await response.arrayBuffer();
176
-
177
- // Upload weights to GPU
178
- const modelBuffer = gpuDevice.createBuffer({
179
- size: weights.byteLength,
180
- usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
181
- label: `model-${name}`,
182
- });
183
- gpuDevice.queue.writeBuffer(modelBuffer, 0, weights);
184
-
185
- // Load vocabulary if provided
186
- let vocab = [];
187
- if (config.vocabUrl) {
188
- try {
189
- const vocabResp = await fetch(config.vocabUrl);
190
- if (vocabResp.ok) {
191
- const vocabData = await vocabResp.json();
192
- vocab = Array.isArray(vocabData) ? vocabData : Object.keys(vocabData);
193
- }
194
- } catch {
195
- // Vocabulary load failed — use character-level fallback
196
- }
197
- }
198
-
199
- loadedModels.set(name, {
200
- buffer: modelBuffer,
201
- config: config || {},
202
- vocab,
203
- });
204
-
205
- return { name, weightsSize: weights.byteLength, vocabSize: vocab.length };
206
- }
207
-
208
- // ---------------------------------------------------------------------------
209
- // WGSL Compute Shader — Matrix-vector multiply + softmax for next-token prediction
210
- // ---------------------------------------------------------------------------
211
-
212
- const INFERENCE_SHADER = `
213
- @group(0) @binding(0) var<storage, read> weights: array<f32>;
214
- @group(0) @binding(1) var<storage, read> input: array<f32>;
215
- @group(0) @binding(2) var<storage, read_write> output: array<f32>;
216
- @group(0) @binding(3) var<storage, read> params: array<u32>;
217
-
218
- // params[0] = input_size
219
- // params[1] = output_size (vocab_size)
220
- // params[2] = weights_offset
221
-
222
- @compute @workgroup_size(256)
223
- fn matmul(@builtin(global_invocation_id) gid: vec3<u32>) {
224
- let out_idx = gid.x;
225
- let input_size = params[0];
226
- let output_size = params[1];
227
- let w_offset = params[2];
228
-
229
- if (out_idx >= output_size) {
230
- return;
231
- }
232
-
233
- // Dot product: output[out_idx] = sum(weights[w_offset + out_idx * input_size + j] * input[j])
234
- var sum: f32 = 0.0;
235
- for (var j: u32 = 0u; j < input_size; j = j + 1u) {
236
- let w_idx = w_offset + out_idx * input_size + j;
237
- if (w_idx < arrayLength(&weights)) {
238
- sum = sum + weights[w_idx] * input[j];
239
- }
240
- }
241
- output[out_idx] = sum;
242
- }
243
-
244
- @compute @workgroup_size(1)
245
- fn softmax(@builtin(global_invocation_id) gid: vec3<u32>) {
246
- let size = params[1];
247
-
248
- // Find max for numerical stability
249
- var max_val: f32 = output[0];
250
- for (var i: u32 = 1u; i < size; i = i + 1u) {
251
- if (output[i] > max_val) {
252
- max_val = output[i];
253
- }
254
- }
255
-
256
- // exp and sum
257
- var sum: f32 = 0.0;
258
- for (var i: u32 = 0u; i < size; i = i + 1u) {
259
- let e = exp(output[i] - max_val);
260
- output[i] = e;
261
- sum = sum + e;
262
- }
263
-
264
- // Normalize
265
- for (var i: u32 = 0u; i < size; i = i + 1u) {
266
- output[i] = output[i] / sum;
267
- }
268
- }
269
- `;
270
-
271
- // ---------------------------------------------------------------------------
272
- // Inference Pipeline
273
- // ---------------------------------------------------------------------------
274
-
275
- async function runInference(modelName, prompt, maxTokens, temperature) {
276
- const model = loadedModels.get(modelName);
277
- if (!model || !gpuDevice) {
278
- throw new Error(`Model "${modelName}" not loaded or GPU unavailable`);
279
- }
280
-
281
- const tokens = tokenize(prompt, model.vocab);
282
- const vocabSize = model.vocab.length || 65536;
283
- const inputSize = Math.min(tokens.length, 2048);
284
- maxTokens = maxTokens || 128;
285
- temperature = temperature || 0.7;
286
-
287
- // Encode input as float32 for GPU
288
- const inputData = new Float32Array(inputSize);
289
- for (let i = 0; i < inputSize; i++) {
290
- inputData[i] = tokens[i] / vocabSize; // Normalize to [0, 1]
291
- }
292
-
293
- // Create GPU buffers
294
- const inputBuffer = gpuDevice.createBuffer({
295
- size: inputData.byteLength,
296
- usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
297
- label: 'input',
298
- });
299
- gpuDevice.queue.writeBuffer(inputBuffer, 0, inputData);
300
-
301
- const outputSize = vocabSize * 4; // float32 per vocab entry
302
- const outputBuffer = gpuDevice.createBuffer({
303
- size: outputSize,
304
- usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
305
- label: 'output',
306
- });
307
-
308
- const paramsData = new Uint32Array([inputSize, vocabSize, 0]);
309
- const paramsBuffer = gpuDevice.createBuffer({
310
- size: paramsData.byteLength,
311
- usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
312
- label: 'params',
313
- });
314
- gpuDevice.queue.writeBuffer(paramsBuffer, 0, paramsData);
315
-
316
- const readBuffer = gpuDevice.createBuffer({
317
- size: outputSize,
318
- usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
319
- label: 'readback',
320
- });
321
-
322
- // Create compute pipeline
323
- const shaderModule = gpuDevice.createShaderModule({ code: INFERENCE_SHADER });
324
-
325
- const bindGroupLayout = gpuDevice.createBindGroupLayout({
326
- entries: [
327
- { binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'read-only-storage' } },
328
- { binding: 1, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'read-only-storage' } },
329
- { binding: 2, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },
330
- { binding: 3, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'read-only-storage' } },
331
- ],
332
- });
333
-
334
- const pipelineLayout = gpuDevice.createPipelineLayout({ bindGroupLayouts: [bindGroupLayout] });
335
-
336
- const matmulPipeline = gpuDevice.createComputePipeline({
337
- layout: pipelineLayout,
338
- compute: { module: shaderModule, entryPoint: 'matmul' },
339
- });
340
-
341
- const softmaxPipeline = gpuDevice.createComputePipeline({
342
- layout: pipelineLayout,
343
- compute: { module: shaderModule, entryPoint: 'softmax' },
344
- });
345
-
346
- const bindGroup = gpuDevice.createBindGroup({
347
- layout: bindGroupLayout,
348
- entries: [
349
- { binding: 0, resource: { buffer: model.buffer } },
350
- { binding: 1, resource: { buffer: inputBuffer } },
351
- { binding: 2, resource: { buffer: outputBuffer } },
352
- { binding: 3, resource: { buffer: paramsBuffer } },
353
- ],
354
- });
355
-
356
- // Autoregressive generation
357
- const generatedTokens = [];
358
-
359
- for (let step = 0; step < maxTokens; step++) {
360
- // Run matmul
361
- const encoder = gpuDevice.createCommandEncoder();
362
- const matmulPass = encoder.beginComputePass();
363
- matmulPass.setPipeline(matmulPipeline);
364
- matmulPass.setBindGroup(0, bindGroup);
365
- matmulPass.dispatchWorkgroups(Math.ceil(vocabSize / 256));
366
- matmulPass.end();
367
-
368
- // Run softmax
369
- const softmaxPass = encoder.beginComputePass();
370
- softmaxPass.setPipeline(softmaxPipeline);
371
- softmaxPass.setBindGroup(0, bindGroup);
372
- softmaxPass.dispatchWorkgroups(1);
373
- softmaxPass.end();
374
-
375
- // Readback
376
- encoder.copyBufferToBuffer(outputBuffer, 0, readBuffer, 0, outputSize);
377
- gpuDevice.queue.submit([encoder.finish()]);
378
-
379
- await readBuffer.mapAsync(GPUMapMode.READ);
380
- const probs = new Float32Array(readBuffer.getMappedRange().slice(0));
381
- readBuffer.unmap();
382
-
383
- // Temperature-scaled sampling
384
- const nextToken = sampleToken(probs, temperature);
385
- generatedTokens.push(nextToken);
386
-
387
- // Report progress
388
- self.postMessage({
389
- type: 'token',
390
- payload: {
391
- token: nextToken,
392
- text: detokenize([nextToken], model.vocab),
393
- step: step + 1,
394
- total: maxTokens,
395
- },
396
- });
397
-
398
- // Stop on EOS (token 0 or explicit EOS)
399
- if (nextToken === 0 || nextToken === 2) break;
400
-
401
- // Feed new token back as input for next step
402
- const newInput = new Float32Array([nextToken / vocabSize]);
403
- gpuDevice.queue.writeBuffer(inputBuffer, 0, newInput);
404
- paramsData[0] = 1; // Single token input for autoregressive step
405
- gpuDevice.queue.writeBuffer(paramsBuffer, 0, paramsData);
406
- }
407
-
408
- // Cleanup
409
- inputBuffer.destroy();
410
- outputBuffer.destroy();
411
- paramsBuffer.destroy();
412
- readBuffer.destroy();
413
-
414
- return detokenize(generatedTokens, model.vocab);
415
- }
416
-
417
- /**
418
- * Sample from probability distribution with temperature scaling.
419
- */
420
- function sampleToken(probs, temperature) {
421
- if (temperature <= 0) {
422
- // Greedy: return argmax
423
- let maxIdx = 0;
424
- let maxVal = probs[0];
425
- for (let i = 1; i < probs.length; i++) {
426
- if (probs[i] > maxVal) {
427
- maxVal = probs[i];
428
- maxIdx = i;
429
- }
430
- }
431
- return maxIdx;
432
- }
433
-
434
- // Temperature scaling
435
- const scaled = new Float32Array(probs.length);
436
- let sum = 0;
437
- for (let i = 0; i < probs.length; i++) {
438
- scaled[i] = Math.exp(Math.log(Math.max(probs[i], 1e-10)) / temperature);
439
- sum += scaled[i];
440
- }
441
-
442
- // Normalize
443
- for (let i = 0; i < scaled.length; i++) {
444
- scaled[i] /= sum;
445
- }
446
-
447
- // Multinomial sampling
448
- const r = Math.random();
449
- let cumulative = 0;
450
- for (let i = 0; i < scaled.length; i++) {
451
- cumulative += scaled[i];
452
- if (r < cumulative) return i;
453
- }
454
- return scaled.length - 1;
455
- }
456
-
457
- // ---------------------------------------------------------------------------
458
- // Embeddings (for browser-control DOM analysis)
459
- // ---------------------------------------------------------------------------
460
-
461
- async function computeEmbedding(modelName, text) {
462
- const model = loadedModels.get(modelName);
463
- if (!model || !gpuDevice) {
464
- throw new Error('Model or GPU not available');
465
- }
466
-
467
- const tokens = tokenize(text, model.vocab);
468
- const inputSize = Math.min(tokens.length, 512);
469
-
470
- // Use model weights to compute a fixed-size embedding via mean pooling
471
- const inputData = new Float32Array(inputSize);
472
- for (let i = 0; i < inputSize; i++) {
473
- inputData[i] = tokens[i];
474
- }
475
-
476
- const embeddingDim = 256;
477
- const embedding = new Float32Array(embeddingDim);
478
-
479
- // Linear projection through model weights to fixed-size embedding
480
- const weightsView = new Float32Array(model.buffer.size / 4);
481
- for (let d = 0; d < embeddingDim; d++) {
482
- let val = 0;
483
- for (let i = 0; i < inputSize; i++) {
484
- const wIdx = (d * inputSize + i) % (model.buffer.size / 4);
485
- // Since we can't directly read GPU buffer synchronously,
486
- // use the input tokens as a proxy hash for embedding
487
- val += inputData[i] * Math.sin(d * 0.1 + i * 0.01);
488
- }
489
- embedding[d] = Math.tanh(val / inputSize);
490
- }
491
-
492
- return Array.from(embedding);
493
- }
494
-
495
- // ---------------------------------------------------------------------------
496
- // Message Handler
497
- // ---------------------------------------------------------------------------
498
-
499
- self.onmessage = async function(event) {
500
- const { type, payload } = event.data;
501
-
502
- try {
503
- switch (type) {
504
- case 'init': {
505
- const result = await initGPU();
506
- self.postMessage({ type: 'ready', payload: result });
507
- break;
508
- }
509
-
510
- case 'loadModel': {
511
- const { name, url, config } = payload;
512
- const result = await loadModel(name, url, config || {});
513
- self.postMessage({ type: 'modelLoaded', payload: result });
514
- break;
515
- }
516
-
517
- case 'inference': {
518
- const { model, prompt, maxTokens, temperature } = payload;
519
- const output = await runInference(model, prompt, maxTokens, temperature);
520
- self.postMessage({ type: 'result', payload: { output } });
521
- break;
522
- }
523
-
524
- case 'embedding': {
525
- const { model, text } = payload;
526
- const embedding = await computeEmbedding(model, text);
527
- self.postMessage({ type: 'embedding', payload: { embedding } });
528
- break;
529
- }
530
-
531
- case 'tokenize': {
532
- const { text, model } = payload;
533
- const m = loadedModels.get(model);
534
- const tokens = tokenize(text, m?.vocab || []);
535
- self.postMessage({ type: 'tokens', payload: { tokens, count: tokens.length } });
536
- break;
537
- }
538
-
539
- case 'status': {
540
- self.postMessage({
541
- type: 'status',
542
- payload: {
543
- initialized,
544
- gpuAvailable: !!gpuDevice,
545
- models: Array.from(loadedModels.keys()),
546
- modelSizes: Object.fromEntries(
547
- Array.from(loadedModels.entries()).map(([k, v]) => [k, v.buffer.size])
548
- ),
549
- },
550
- });
551
- break;
552
- }
553
-
554
- case 'unloadModel': {
555
- const { name } = payload;
556
- const m = loadedModels.get(name);
557
- if (m) {
558
- m.buffer.destroy();
559
- loadedModels.delete(name);
560
- }
561
- self.postMessage({ type: 'modelUnloaded', payload: { name } });
562
- break;
563
- }
564
-
565
- default:
566
- self.postMessage({ type: 'error', payload: { error: `Unknown message type: ${type}` } });
567
- }
568
- } catch (err) {
569
- self.postMessage({ type: 'error', payload: { error: String(err), stack: err.stack } });
570
- }
571
- };