@hanzo/browser-extension 1.7.6 → 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 (144) hide show
  1. package/LICENSE +9 -18
  2. package/dist/browser-extension/content-script.js +1642 -123
  3. package/images/icon128.png +0 -0
  4. package/images/icon16.png +0 -0
  5. package/images/icon48.png +0 -0
  6. package/manifest.json +7 -2
  7. package/package.json +28 -10
  8. package/src/answer/AnswerEngine.tsx +553 -0
  9. package/src/chat-widget.tsx +96 -0
  10. package/src/gui-primitives.tsx +116 -0
  11. package/src/icon128.png +0 -0
  12. package/src/icon16.png +0 -0
  13. package/src/icon32.png +0 -0
  14. package/src/icon48.png +0 -0
  15. package/src/manifest-firefox.json +32 -13
  16. package/src/manifest.json +35 -11
  17. package/src/newtab.css +277 -0
  18. package/src/newtab.html +13 -0
  19. package/src/package.json +1 -1
  20. package/src/popup.css +352 -110
  21. package/src/popup.html +186 -47
  22. package/src/popup.js +196 -0
  23. package/src/sidebar.css +772 -156
  24. package/src/sidebar.html +268 -85
  25. package/src/sidebar.js +885 -0
  26. package/.github/workflows/docs.yml +0 -44
  27. package/.github/workflows/release.yml +0 -55
  28. package/README.md +0 -126
  29. package/dist/browser-extension/README.md +0 -43
  30. package/dist/browser-extension/ai-worker.js +0 -30
  31. package/dist/browser-extension/background-firefox.js +0 -875
  32. package/dist/browser-extension/background.js +0 -2732
  33. package/dist/browser-extension/browser-control.js +0 -683
  34. package/dist/browser-extension/cdp-bridge-server.js +0 -444
  35. package/dist/browser-extension/chrome/ai-worker.js +0 -571
  36. package/dist/browser-extension/chrome/background.js +0 -2732
  37. package/dist/browser-extension/chrome/callback.html +0 -17
  38. package/dist/browser-extension/chrome/content-script.js +0 -1270
  39. package/dist/browser-extension/chrome/icon128.png +0 -0
  40. package/dist/browser-extension/chrome/icon16.png +0 -0
  41. package/dist/browser-extension/chrome/icon32.png +0 -0
  42. package/dist/browser-extension/chrome/icon48.png +0 -0
  43. package/dist/browser-extension/chrome/manifest.json +0 -57
  44. package/dist/browser-extension/chrome/popup.css +0 -519
  45. package/dist/browser-extension/chrome/popup.html +0 -242
  46. package/dist/browser-extension/chrome/popup.js +0 -278
  47. package/dist/browser-extension/chrome/sidebar.css +0 -1176
  48. package/dist/browser-extension/chrome/sidebar.html +0 -280
  49. package/dist/browser-extension/chrome/sidebar.js +0 -779
  50. package/dist/browser-extension/cli.js +0 -233
  51. package/dist/browser-extension/firefox/ai-worker.js +0 -571
  52. package/dist/browser-extension/firefox/background.js +0 -875
  53. package/dist/browser-extension/firefox/callback.html +0 -17
  54. package/dist/browser-extension/firefox/content-script.js +0 -1270
  55. package/dist/browser-extension/firefox/icon128.png +0 -0
  56. package/dist/browser-extension/firefox/icon16.png +0 -0
  57. package/dist/browser-extension/firefox/icon32.png +0 -0
  58. package/dist/browser-extension/firefox/icon48.png +0 -0
  59. package/dist/browser-extension/firefox/manifest.json +0 -66
  60. package/dist/browser-extension/firefox/popup.css +0 -519
  61. package/dist/browser-extension/firefox/popup.html +0 -242
  62. package/dist/browser-extension/firefox/popup.js +0 -278
  63. package/dist/browser-extension/firefox/sidebar.css +0 -1176
  64. package/dist/browser-extension/firefox/sidebar.html +0 -280
  65. package/dist/browser-extension/firefox/sidebar.js +0 -779
  66. package/dist/browser-extension/hanzo-browser-extension-firefox-1.1.0.zip +0 -0
  67. package/dist/browser-extension/icon128.png +0 -0
  68. package/dist/browser-extension/icon16.png +0 -0
  69. package/dist/browser-extension/icon32.png +0 -0
  70. package/dist/browser-extension/icon48.png +0 -0
  71. package/dist/browser-extension/manifest.json +0 -57
  72. package/dist/browser-extension/package.json +0 -41
  73. package/dist/browser-extension/popup.css +0 -468
  74. package/dist/browser-extension/popup.html +0 -191
  75. package/dist/browser-extension/popup.js +0 -278
  76. package/dist/browser-extension/safari/Info.plist +0 -21
  77. package/dist/browser-extension/safari/ai-worker.js +0 -571
  78. package/dist/browser-extension/safari/background.js +0 -2732
  79. package/dist/browser-extension/safari/callback.html +0 -17
  80. package/dist/browser-extension/safari/content-script.js +0 -1270
  81. package/dist/browser-extension/safari/icon128.png +0 -0
  82. package/dist/browser-extension/safari/icon16.png +0 -0
  83. package/dist/browser-extension/safari/icon32.png +0 -0
  84. package/dist/browser-extension/safari/icon48.png +0 -0
  85. package/dist/browser-extension/safari/popup.css +0 -519
  86. package/dist/browser-extension/safari/popup.html +0 -242
  87. package/dist/browser-extension/safari/popup.js +0 -278
  88. package/dist/browser-extension/safari/sidebar.css +0 -1176
  89. package/dist/browser-extension/safari/sidebar.html +0 -280
  90. package/dist/browser-extension/safari/sidebar.js +0 -779
  91. package/dist/browser-extension/sidebar.css +0 -718
  92. package/dist/browser-extension/sidebar.html +0 -184
  93. package/dist/browser-extension/sidebar.js +0 -779
  94. package/dist/browser-extension/webgpu-ai.js +0 -495
  95. package/dist/cdp-bridge-server.js +0 -411
  96. package/dist/hanzo-ai-chrome-1.7.2.zip +0 -0
  97. package/dist/hanzo-ai-chrome-1.7.3.zip +0 -0
  98. package/dist/hanzo-ai-firefox-1.7.2.zip +0 -0
  99. package/dist/hanzo-ai-firefox-1.7.3.zip +0 -0
  100. package/dist/safari/Hanzo AI/Hanzo AI.xcodeproj/project.pbxproj +0 -1047
  101. package/dist/safari/Hanzo AI/Hanzo AI.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
  102. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AccentColor.colorset/Contents.json +0 -11
  103. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -98
  104. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@1x.png +0 -0
  105. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@2x.png +0 -0
  106. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@1x.png +0 -0
  107. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@2x.png +0 -0
  108. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@1x.png +0 -0
  109. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@2x.png +0 -0
  110. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@1x.png +0 -0
  111. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@2x.png +0 -0
  112. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@1x.png +0 -0
  113. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@2x.png +0 -0
  114. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/universal-icon-1024@1x.png +0 -0
  115. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/Contents.json +0 -6
  116. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/LargeIcon.imageset/Contents.json +0 -21
  117. package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/LargeIcon.imageset/icon128.png +0 -0
  118. package/dist/safari/Hanzo AI/Shared (App)/Resources/Base.lproj/Main.html +0 -20
  119. package/dist/safari/Hanzo AI/Shared (App)/Resources/Icon.png +0 -0
  120. package/dist/safari/Hanzo AI/Shared (App)/Resources/Script.js +0 -24
  121. package/dist/safari/Hanzo AI/Shared (App)/Resources/Style.css +0 -61
  122. package/dist/safari/Hanzo AI/Shared (App)/ViewController.swift +0 -81
  123. package/dist/safari/Hanzo AI/Shared (Extension)/SafariWebExtensionHandler.swift +0 -42
  124. package/dist/safari/Hanzo AI/iOS (App)/AppDelegate.swift +0 -24
  125. package/dist/safari/Hanzo AI/iOS (App)/Base.lproj/LaunchScreen.storyboard +0 -36
  126. package/dist/safari/Hanzo AI/iOS (App)/Base.lproj/Main.storyboard +0 -38
  127. package/dist/safari/Hanzo AI/iOS (App)/Info.plist +0 -27
  128. package/dist/safari/Hanzo AI/iOS (App)/SceneDelegate.swift +0 -18
  129. package/dist/safari/Hanzo AI/iOS (Extension)/Info.plist +0 -13
  130. package/dist/safari/Hanzo AI/macOS (App)/AppDelegate.swift +0 -21
  131. package/dist/safari/Hanzo AI/macOS (App)/Base.lproj/Main.storyboard +0 -124
  132. package/dist/safari/Hanzo AI/macOS (App)/Info.plist +0 -8
  133. package/dist/safari/Hanzo AI/macOS (Extension)/Info.plist +0 -13
  134. package/docs/_config.yml +0 -13
  135. package/docs/_layouts/default.html +0 -435
  136. package/docs/index.md +0 -54
  137. package/hanzo-ai-chrome-v1.7.6.zip +0 -0
  138. package/hanzo-ai-firefox-v1.7.6.zip +0 -0
  139. package/install-extension.sh +0 -75
  140. package/test/claude-integration.test.ts +0 -251
  141. package/test/server-frameworks-edge-cases.test.ts +0 -145
  142. package/test/server-frameworks.test.ts +0 -395
  143. package/test-cdp-connection.js +0 -140
  144. package/tsconfig.json +0 -19
@@ -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
- };