@hanzo/browser-extension 1.9.31 → 1.9.33

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 (62) hide show
  1. package/package.json +2 -1
  2. package/src/answer/AnswerEngine.tsx +553 -0
  3. package/src/manifest-firefox.json +10 -2
  4. package/src/manifest.json +17 -2
  5. package/src/newtab.css +277 -0
  6. package/src/newtab.html +13 -0
  7. package/src/popup.html +28 -0
  8. package/dist/browser-extension/README.md +0 -43
  9. package/dist/browser-extension/background-firefox.js +0 -4556
  10. package/dist/browser-extension/background.js +0 -7245
  11. package/dist/browser-extension/browser-control.js +0 -1317
  12. package/dist/browser-extension/chrome/ai-worker.js +0 -571
  13. package/dist/browser-extension/chrome/background.js +0 -7245
  14. package/dist/browser-extension/chrome/callback.html +0 -17
  15. package/dist/browser-extension/chrome/content-script.js +0 -2789
  16. package/dist/browser-extension/chrome/icon128.png +0 -0
  17. package/dist/browser-extension/chrome/icon16.png +0 -0
  18. package/dist/browser-extension/chrome/icon48.png +0 -0
  19. package/dist/browser-extension/chrome/manifest.json +0 -66
  20. package/dist/browser-extension/chrome/popup.css +0 -761
  21. package/dist/browser-extension/chrome/popup.html +0 -353
  22. package/dist/browser-extension/chrome/popup.js +0 -1650
  23. package/dist/browser-extension/chrome/sidebar.css +0 -1792
  24. package/dist/browser-extension/chrome/sidebar.html +0 -463
  25. package/dist/browser-extension/chrome/sidebar.js +0 -26541
  26. package/dist/browser-extension/cli.js +0 -233
  27. package/dist/browser-extension/firefox/ai-worker.js +0 -571
  28. package/dist/browser-extension/firefox/background.js +0 -4556
  29. package/dist/browser-extension/firefox/callback.html +0 -17
  30. package/dist/browser-extension/firefox/content-script.js +0 -2789
  31. package/dist/browser-extension/firefox/icon128.png +0 -0
  32. package/dist/browser-extension/firefox/icon16.png +0 -0
  33. package/dist/browser-extension/firefox/icon48.png +0 -0
  34. package/dist/browser-extension/firefox/manifest.json +0 -77
  35. package/dist/browser-extension/firefox/popup.css +0 -761
  36. package/dist/browser-extension/firefox/popup.html +0 -353
  37. package/dist/browser-extension/firefox/popup.js +0 -1650
  38. package/dist/browser-extension/firefox/sidebar.css +0 -1792
  39. package/dist/browser-extension/firefox/sidebar.html +0 -463
  40. package/dist/browser-extension/firefox/sidebar.js +0 -26541
  41. package/dist/browser-extension/icon128.png +0 -0
  42. package/dist/browser-extension/icon16.png +0 -0
  43. package/dist/browser-extension/icon48.png +0 -0
  44. package/dist/browser-extension/manifest.json +0 -66
  45. package/dist/browser-extension/package.json +0 -41
  46. package/dist/browser-extension/popup.js +0 -1650
  47. package/dist/browser-extension/safari/Info.plist +0 -21
  48. package/dist/browser-extension/safari/ai-worker.js +0 -571
  49. package/dist/browser-extension/safari/background.js +0 -7245
  50. package/dist/browser-extension/safari/callback.html +0 -17
  51. package/dist/browser-extension/safari/content-script.js +0 -2789
  52. package/dist/browser-extension/safari/icon128.png +0 -0
  53. package/dist/browser-extension/safari/icon16.png +0 -0
  54. package/dist/browser-extension/safari/icon48.png +0 -0
  55. package/dist/browser-extension/safari/popup.css +0 -761
  56. package/dist/browser-extension/safari/popup.html +0 -353
  57. package/dist/browser-extension/safari/popup.js +0 -1650
  58. package/dist/browser-extension/safari/sidebar.css +0 -1792
  59. package/dist/browser-extension/safari/sidebar.html +0 -463
  60. package/dist/browser-extension/safari/sidebar.js +0 -26541
  61. package/dist/browser-extension/sidebar.js +0 -26541
  62. package/dist/browser-extension/webgpu-ai.js +0 -568
@@ -1,21 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
- <plist version="1.0">
4
- <dict>
5
- <key>CFBundleDisplayName</key>
6
- <string>Hanzo AI Dev Assistant</string>
7
- <key>CFBundleIdentifier</key>
8
- <string>ai.hanzo.browser-extension</string>
9
- <key>CFBundleVersion</key>
10
- <string>1.9.31</string>
11
- <key>CFBundleShortVersionString</key>
12
- <string>1.9.31</string>
13
- <key>NSExtension</key>
14
- <dict>
15
- <key>NSExtensionPointIdentifier</key>
16
- <string>com.apple.Safari.web-extension</string>
17
- <key>NSExtensionPrincipalClass</key>
18
- <string>SafariWebExtensionHandler</string>
19
- </dict>
20
- </dict>
21
- </plist>
@@ -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
- };