@hanzo/browser-extension 1.9.34 → 1.9.36

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 (71) hide show
  1. package/package.json +1 -1
  2. package/src/answer/AnswerEngine.tsx +8 -0
  3. package/src/onboarding.css +78 -0
  4. package/src/onboarding.html +45 -0
  5. package/src/popup.html +28 -0
  6. package/dist/browser-extension/README.md +0 -43
  7. package/dist/browser-extension/background-firefox.js +0 -5983
  8. package/dist/browser-extension/background.js +0 -8670
  9. package/dist/browser-extension/browser-control.js +0 -1317
  10. package/dist/browser-extension/chrome/ai-worker.js +0 -571
  11. package/dist/browser-extension/chrome/background.js +0 -8670
  12. package/dist/browser-extension/chrome/callback.html +0 -17
  13. package/dist/browser-extension/chrome/content-script.js +0 -2789
  14. package/dist/browser-extension/chrome/icon128.png +0 -0
  15. package/dist/browser-extension/chrome/icon16.png +0 -0
  16. package/dist/browser-extension/chrome/icon48.png +0 -0
  17. package/dist/browser-extension/chrome/manifest.json +0 -81
  18. package/dist/browser-extension/chrome/newtab.css +0 -277
  19. package/dist/browser-extension/chrome/newtab.html +0 -13
  20. package/dist/browser-extension/chrome/newtab.js +0 -64
  21. package/dist/browser-extension/chrome/popup.css +0 -761
  22. package/dist/browser-extension/chrome/popup.html +0 -381
  23. package/dist/browser-extension/chrome/popup.js +0 -1716
  24. package/dist/browser-extension/chrome/sidebar.css +0 -1792
  25. package/dist/browser-extension/chrome/sidebar.html +0 -463
  26. package/dist/browser-extension/chrome/sidebar.js +0 -26541
  27. package/dist/browser-extension/cli.js +0 -233
  28. package/dist/browser-extension/content-script.js +0 -2789
  29. package/dist/browser-extension/firefox/ai-worker.js +0 -571
  30. package/dist/browser-extension/firefox/background.js +0 -5983
  31. package/dist/browser-extension/firefox/callback.html +0 -17
  32. package/dist/browser-extension/firefox/content-script.js +0 -2789
  33. package/dist/browser-extension/firefox/icon128.png +0 -0
  34. package/dist/browser-extension/firefox/icon16.png +0 -0
  35. package/dist/browser-extension/firefox/icon48.png +0 -0
  36. package/dist/browser-extension/firefox/manifest.json +0 -85
  37. package/dist/browser-extension/firefox/newtab.css +0 -277
  38. package/dist/browser-extension/firefox/newtab.html +0 -13
  39. package/dist/browser-extension/firefox/newtab.js +0 -64
  40. package/dist/browser-extension/firefox/popup.css +0 -761
  41. package/dist/browser-extension/firefox/popup.html +0 -381
  42. package/dist/browser-extension/firefox/popup.js +0 -1716
  43. package/dist/browser-extension/firefox/sidebar.css +0 -1792
  44. package/dist/browser-extension/firefox/sidebar.html +0 -463
  45. package/dist/browser-extension/firefox/sidebar.js +0 -26541
  46. package/dist/browser-extension/icon128.png +0 -0
  47. package/dist/browser-extension/icon16.png +0 -0
  48. package/dist/browser-extension/icon48.png +0 -0
  49. package/dist/browser-extension/manifest.json +0 -81
  50. package/dist/browser-extension/newtab.js +0 -64
  51. package/dist/browser-extension/package.json +0 -41
  52. package/dist/browser-extension/popup.js +0 -1716
  53. package/dist/browser-extension/safari/Info.plist +0 -21
  54. package/dist/browser-extension/safari/ai-worker.js +0 -571
  55. package/dist/browser-extension/safari/background.js +0 -8670
  56. package/dist/browser-extension/safari/callback.html +0 -17
  57. package/dist/browser-extension/safari/content-script.js +0 -2789
  58. package/dist/browser-extension/safari/icon128.png +0 -0
  59. package/dist/browser-extension/safari/icon16.png +0 -0
  60. package/dist/browser-extension/safari/icon48.png +0 -0
  61. package/dist/browser-extension/safari/newtab.css +0 -277
  62. package/dist/browser-extension/safari/newtab.html +0 -13
  63. package/dist/browser-extension/safari/newtab.js +0 -64
  64. package/dist/browser-extension/safari/popup.css +0 -761
  65. package/dist/browser-extension/safari/popup.html +0 -381
  66. package/dist/browser-extension/safari/popup.js +0 -1716
  67. package/dist/browser-extension/safari/sidebar.css +0 -1792
  68. package/dist/browser-extension/safari/sidebar.html +0 -463
  69. package/dist/browser-extension/safari/sidebar.js +0 -26541
  70. package/dist/browser-extension/sidebar.js +0 -26541
  71. 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
- };