@tryhamster/gerbil 1.1.2 → 1.1.3

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/README.md +18 -16
  2. package/dist/cli.mjs +1 -1
  3. package/dist/cli.mjs.map +1 -1
  4. package/dist/gpu/hooks.d.mts +3 -3
  5. package/dist/gpu/hooks.mjs +5 -5
  6. package/dist/gpu/hooks.mjs.map +1 -1
  7. package/dist/index-Dgmb2kE3.d.mts.map +1 -1
  8. package/package.json +1 -2
  9. package/docs/PROJECT-STATE.md +0 -321
  10. package/docs/adding-a-model-family.md +0 -280
  11. package/docs/ai-sdk.md +0 -205
  12. package/docs/architecture/README.md +0 -84
  13. package/docs/architecture/caching.md +0 -227
  14. package/docs/architecture/inference.md +0 -176
  15. package/docs/architecture/overview.md +0 -189
  16. package/docs/architecture/streaming.md +0 -261
  17. package/docs/architecture/webgpu.md +0 -213
  18. package/docs/browser.md +0 -762
  19. package/docs/cli.md +0 -155
  20. package/docs/embeddings.md +0 -156
  21. package/docs/frameworks.md +0 -90
  22. package/docs/gerbil-site-native-migration.md +0 -217
  23. package/docs/gpu-engine/architectures.md +0 -398
  24. package/docs/gpu-engine/ir.md +0 -372
  25. package/docs/gpu-engine/kernels.md +0 -718
  26. package/docs/gpu-engine/paper.html +0 -1759
  27. package/docs/gpu-engine/paper.md +0 -2109
  28. package/docs/gpu-engine/safetensors.md +0 -312
  29. package/docs/gpu-engine/tokenizer.md +0 -302
  30. package/docs/kernel-research-queue.md +0 -85
  31. package/docs/mcp-client.md +0 -224
  32. package/docs/mcp.md +0 -109
  33. package/docs/memory-rag.md +0 -91
  34. package/docs/memory.md +0 -301
  35. package/docs/metal-safari-intel.md +0 -190
  36. package/docs/mobile-failure-diagnosis.md +0 -124
  37. package/docs/mobile.md +0 -99
  38. package/docs/observability.md +0 -230
  39. package/docs/onnx-removal-plan.md +0 -339
  40. package/docs/repl.md +0 -473
  41. package/docs/research/autoresearch-portable.md +0 -933
  42. package/docs/research/dispatch-reduction-hivemind.md +0 -84
  43. package/docs/research/ios-safari-model-caching.md +0 -117
  44. package/docs/research/mobile-webgpu-speed-fusion.md +0 -135
  45. package/docs/research/native-stt-model-selection.md +0 -49
  46. package/docs/research/native-tts-model-selection.md +0 -90
  47. package/docs/research/native-vs-chromium-decision.md +0 -152
  48. package/docs/research/nemotron-mamba2-inference.md +0 -910
  49. package/docs/research/qwen35-multimodal.md +0 -293
  50. package/docs/research/qwen36-gemma4-targets.md +0 -337
  51. package/docs/research/sota-embedding-models.md +0 -179
  52. package/docs/research/sota-mobile-models-2026.md +0 -263
  53. package/docs/research/sota-modality-models.md +0 -202
  54. package/docs/research/tps-baselines.md +0 -71
  55. package/docs/research/webgpu-m4-reference.md +0 -104
  56. package/docs/site-update-plan.md +0 -155
  57. package/docs/skills.md +0 -261
  58. package/docs/structured-output.md +0 -123
  59. package/docs/stt.md +0 -111
  60. package/docs/tools.md +0 -304
  61. package/docs/tts.md +0 -147
  62. package/docs/vision.md +0 -158
package/docs/browser.md DELETED
@@ -1,762 +0,0 @@
1
- # Browser Usage
2
-
3
- Run models directly in the browser with WebGPU. No server required.
4
-
5
- Browser inference runs on the **native engine** — the React hooks at
6
- `@tryhamster/gerbil/gpu/hooks` (`useEngine` / `useChat` / `useText` / `useVision` /
7
- `useEmbedding` / `useTTS` / `useSTT` / `useVoiceChat` / `useMemory`),
8
- backed by the from-scratch WGSL `WebGPUEngine`. Pure compute shaders, no ONNX, no
9
- transformers.js. This is the supported path for text, vision, embeddings, and speech, and the
10
- lane the Gerbil site itself runs on.
11
-
12
- > The old inline transformers.js/ONNX worker hooks (`useChat`, `useSpeech`, `useVoiceInput`,
13
- > `useEmbedding`, `createGerbilWorker`, `preload*`) have been **removed** from
14
- > `@tryhamster/gerbil/browser`. `@tryhamster/gerbil/browser` now exports only device/WebGPU
15
- > utilities (`isModelSafeForDevice`, `detectMemoryCrash`, `downloadModelChunked`,
16
- > `checkWebGPUCapabilities`, `getBrowserDiagnostics`, …). The "Legacy Worker Lane" sections
17
- > below are retained for historical reference and no longer reflect the shipped API.
18
-
19
- > **Pre-1.0.** APIs may still shift before 1.0.
20
-
21
- ## Native Engine (recommended)
22
-
23
- ```tsx
24
- import { useEngine } from "@tryhamster/gerbil/gpu/hooks";
25
-
26
- function Chat() {
27
- const { complete, completion, isLoading, isGenerating, tps } = useEngine({
28
- model: "mlx-community/Qwen3.5-0.8B-4bit",
29
- autoLoad: true, // dtype defaults to "auto": int4 on mobile, native on desktop
30
- });
31
-
32
- if (isLoading) return <div>Loading model…</div>;
33
-
34
- return (
35
- <div>
36
- <button onClick={() => complete("Write a haiku about coding")} disabled={isGenerating}>
37
- Generate
38
- </button>
39
- <p>{completion}</p>
40
- {isGenerating && <span>{tps?.toFixed(1)} tok/s</span>}
41
- </div>
42
- );
43
- }
44
- ```
45
-
46
- `useEngine` owns the engine lifecycle — load, unload, hot-swap on config change, and
47
- **reference-counted sharing** so multiple components requesting the same
48
- `model|dtype|vision|embedding|maxSeqLen` share ONE engine (weights uploaded to the GPU once).
49
-
50
- ```typescript
51
- const {
52
- complete, // (prompt, opts?) => Promise<string> — streams into `completion`
53
- describeImage, // (image, prompt?, opts?) => Promise<string> — needs enableVision
54
- embed, // (text, { taskType }?) => Promise<Float32Array> — needs embedding
55
- similarity, // (a, b) => Promise<number> — needs embedding
56
- completion, // string — current text, streams token by token
57
- isLoading, loadingProgress, isGenerating, isReady, tps, error, errorKind,
58
- load, stop, dispose,
59
- } = useEngine({
60
- model: "mlx-community/Qwen3.5-0.8B-4bit", // HF repo id
61
- dtype: "auto", // "auto" (default) | "f32" | "q4"
62
- maxSeqLen, // default: 2048 mobile / 4096 desktop
63
- autoLoad: false, // load on mount
64
- enableVision: false, // build the ViT so describeImage() works
65
- embedding: false, // load as an embedding model
66
- onReady, onError, // onError(err, kind) — kind: "no-webgpu" | "oom" | …
67
- });
68
- ```
69
-
70
- Use `enableVision: true` for image→text (see [Vision](./vision.md)) and `embedding: true`
71
- for embeddings (see [Embeddings](./embeddings.md)).
72
-
73
- ### Browser support (native engine)
74
-
75
- - **Chrome / Edge 113+**
76
- - **Safari 26+ (iOS / iPadOS 26+)**
77
- - **Firefox 141+**
78
-
79
- On devices without WebGPU the hook reports an `errorKind` of `"no-webgpu"` rather than
80
- silently degrading.
81
-
82
- ---
83
-
84
- ## Legacy Worker Lane (removed — historical reference only)
85
-
86
- > **Removed.** Everything below this point documents the old inline transformers.js/ONNX
87
- > worker hooks, which have been **deleted** from the package (no `useChat`/`useSpeech`/
88
- > `useVoiceInput`/`useEmbedding` worker exports, no `createGerbilWorker`, no `preload*`
89
- > functions, and no ONNX/transformers.js dependency). It is kept here only for historical
90
- > reference. Use the native hooks from `@tryhamster/gerbil/gpu/hooks` instead.
91
-
92
- ## Quick Start (React)
93
-
94
- ```tsx
95
- import { useChat } from "@tryhamster/gerbil/browser";
96
-
97
- function Chat() {
98
- const { messages, input, setInput, handleSubmit, isLoading, isGenerating } = useChat();
99
-
100
- if (isLoading) return <div>Loading model...</div>;
101
-
102
- return (
103
- <div>
104
- {messages.map(m => (
105
- <div key={m.id}>
106
- <strong>{m.role}:</strong> {m.content}
107
- </div>
108
- ))}
109
- <form onSubmit={handleSubmit}>
110
- <input
111
- value={input}
112
- onChange={e => setInput(e.target.value)}
113
- placeholder="Say something..."
114
- />
115
- <button disabled={isGenerating}>Send</button>
116
- </form>
117
- </div>
118
- );
119
- }
120
- ```
121
-
122
- That's it! The hook handles model loading, streaming, and state management.
123
-
124
- ## Model Preloading
125
-
126
- Download models during app initialization so they're ready when users need them:
127
-
128
- ```typescript
129
- import {
130
- preloadChatModel,
131
- preloadEmbeddingModel,
132
- preloadTTSModel,
133
- preloadSTTModel
134
- } from "@tryhamster/gerbil/browser";
135
-
136
- // During app initialization
137
- async function initApp() {
138
- // Preload LLM
139
- await preloadChatModel("qwen3-0.6b", {
140
- onProgress: (p) => {
141
- if (p.status === "downloading") {
142
- console.log(`Downloading ${p.file}: ${p.progress}%`);
143
- }
144
- },
145
- });
146
-
147
- // Preload other models as needed
148
- await preloadEmbeddingModel("Xenova/all-MiniLM-L6-v2");
149
- await preloadTTSModel("kokoro-82m");
150
- await preloadSTTModel("whisper-tiny.en");
151
- }
152
-
153
- initApp();
154
- ```
155
-
156
- After preloading, hooks like `useChat` will load instantly from IndexedDB cache.
157
-
158
- ## React Hooks
159
-
160
- ### `useChat`
161
-
162
- Full-featured chat hook with message history, streaming, and thinking mode.
163
-
164
- ```tsx
165
- import { useChat } from "@tryhamster/gerbil/browser";
166
-
167
- function Chat() {
168
- const {
169
- messages, // Message[] - chat history
170
- input, // string - current input
171
- setInput, // (value: string) => void
172
- handleSubmit, // (e?) => void - submit handler
173
- isLoading, // boolean - model loading
174
- loadingProgress, // { status, file?, progress?, downloadCount? }
175
- isGenerating, // boolean - generating response
176
- thinking, // string - current thinking (streaming)
177
- stop, // () => void - stop generation
178
- clear, // () => void - clear messages
179
- tps, // number - tokens per second
180
- isReady, // boolean - model ready
181
- error, // string | null
182
- load, // () => void - manually load model
183
- } = useChat({
184
- model: "qwen3-0.6b", // Model ID
185
- system: "You are helpful.", // System prompt
186
- thinking: true, // Enable thinking mode
187
- maxTokens: 512, // Max tokens per response
188
- temperature: 0.7, // Sampling temperature
189
- autoLoad: false, // Auto-load on mount (default: false)
190
- });
191
- }
192
- ```
193
-
194
- #### Message Type
195
-
196
- ```typescript
197
- interface Message {
198
- id: string;
199
- role: "user" | "assistant";
200
- content: string;
201
- thinking?: string; // Thinking content (if enabled)
202
- }
203
- ```
204
-
205
- #### Auto-Loading Behavior
206
-
207
- By default, hooks **don't** auto-load on mount (to avoid unexpected downloads). The model loads when:
208
- 1. You call `handleSubmit()` / `complete()` - triggers load automatically
209
- 2. You call `load()` manually
210
- 3. You set `autoLoad: true` - loads on mount
211
-
212
- ```tsx
213
- // Default: loads when user submits first message
214
- function Chat() {
215
- const { handleSubmit, isLoading } = useChat();
216
- // Model loads on first handleSubmit()
217
- }
218
-
219
- // Explicit preload on mount
220
- function ChatPreloaded() {
221
- const { handleSubmit, isLoading } = useChat({ autoLoad: true });
222
- // Model starts loading immediately
223
- }
224
-
225
- // Manual load control
226
- function ChatManual() {
227
- const { load, isLoading, isReady } = useChat();
228
- return <button onClick={load} disabled={isLoading || isReady}>Load Model</button>;
229
- }
230
- ```
231
-
232
- #### With Thinking Mode
233
-
234
- ```tsx
235
- function ChatWithThinking() {
236
- const { messages, input, setInput, handleSubmit, isLoading } = useChat({
237
- thinking: true,
238
- });
239
-
240
- if (isLoading) return <div>Loading...</div>;
241
-
242
- return (
243
- <div>
244
- {messages.map(m => (
245
- <div key={m.id}>
246
- {m.thinking && (
247
- <details>
248
- <summary>Thinking...</summary>
249
- <pre>{m.thinking}</pre>
250
- </details>
251
- )}
252
- <p>{m.content}</p>
253
- </div>
254
- ))}
255
- <form onSubmit={handleSubmit}>
256
- <input value={input} onChange={e => setInput(e.target.value)} />
257
- </form>
258
- </div>
259
- );
260
- }
261
- ```
262
-
263
- #### With Loading Progress
264
-
265
- The `loadingProgress` object tells you what's happening:
266
-
267
- - `status: "loading"` - Initial load / compiling shaders
268
- - `status: "downloading"` - Downloading model files from network
269
- - `status: "ready"` - Model ready
270
-
271
- When loading from **cache**, you'll only see `"loading"` → `"ready"` (no `"downloading"`).
272
- When **downloading**, you'll see `"downloading"` with progress info.
273
-
274
- ```tsx
275
- function ChatWithProgress() {
276
- const { messages, handleSubmit, isLoading, loadingProgress } = useChat();
277
-
278
- if (isLoading) {
279
- const p = loadingProgress;
280
-
281
- if (p?.status === "downloading") {
282
- // Downloading from network - show progress
283
- return <div>Downloading {p.file}: {p.progress}%</div>;
284
- }
285
-
286
- // Loading from cache or compiling shaders
287
- return <div>{p?.message || "Loading model..."}</div>;
288
- }
289
-
290
- // ... rest of chat UI
291
- }
292
- ```
293
-
294
- ### `useCompletion`
295
-
296
- Simple completion hook for one-off generations.
297
-
298
- ```tsx
299
- import { useCompletion } from "@tryhamster/gerbil/browser";
300
-
301
- function App() {
302
- const { complete, completion, isLoading, isGenerating, tps } = useCompletion();
303
-
304
- if (isLoading) return <div>Loading model...</div>;
305
-
306
- return (
307
- <div>
308
- <button
309
- onClick={() => complete("Write a haiku about coding")}
310
- disabled={isGenerating}
311
- >
312
- Generate
313
- </button>
314
- <p>{completion}</p>
315
- {isGenerating && <span>{tps.toFixed(0)} tok/s</span>}
316
- </div>
317
- );
318
- }
319
- ```
320
-
321
- #### API
322
-
323
- ```typescript
324
- const {
325
- completion, // string - generated text
326
- thinking, // string - thinking content
327
- complete, // (prompt: string, options?) => Promise<string>
328
- isLoading, // boolean - model loading
329
- loadingProgress, // { status, file?, progress? }
330
- isGenerating, // boolean - generating
331
- stop, // () => void - stop generation
332
- tps, // number - tokens per second
333
- isReady, // boolean - model ready
334
- error, // string | null
335
- } = useCompletion({
336
- model: "qwen3-0.6b",
337
- thinking: false,
338
- maxTokens: 512,
339
- });
340
- ```
341
-
342
- #### Vision (Image Analysis)
343
-
344
- > For native image→text, use `useEngine({ enableVision: true }).describeImage(...)` —
345
- > see [Vision docs](./vision.md). The legacy-lane example below uses the retired ONNX worker.
346
-
347
- Use `useCompletion` with a vision model to analyze images:
348
-
349
- ```tsx
350
- import { useCompletion } from "@tryhamster/gerbil/browser";
351
-
352
- function ImageAnalyzer() {
353
- const { complete, completion, isLoading, isGenerating } = useCompletion({
354
- model: "ministral-3b", // Vision model (legacy lane)
355
- maxTokens: 2048,
356
- });
357
- const [imageUrl, setImageUrl] = useState<string | null>(null);
358
-
359
- const handleFile = (e: React.ChangeEvent<HTMLInputElement>) => {
360
- const file = e.target.files?.[0];
361
- if (file) {
362
- const reader = new FileReader();
363
- reader.onload = () => setImageUrl(reader.result as string);
364
- reader.readAsDataURL(file);
365
- }
366
- };
367
-
368
- const analyze = () => {
369
- if (imageUrl) {
370
- // Pass images array in the second argument
371
- complete("Describe this image in detail", { images: [imageUrl] });
372
- }
373
- };
374
-
375
- if (isLoading) return <div>Loading vision model...</div>;
376
-
377
- return (
378
- <div>
379
- <input type="file" accept="image/*" onChange={handleFile} />
380
- {imageUrl && <img src={imageUrl} style={{ maxWidth: 300 }} />}
381
- <button onClick={analyze} disabled={!imageUrl || isGenerating}>
382
- Analyze Image
383
- </button>
384
- <p>{completion}</p>
385
- </div>
386
- );
387
- }
388
- ```
389
-
390
- Images can be:
391
- - **Data URIs** (`data:image/png;base64,...`) — from FileReader or canvas
392
- - **HTTP URLs** — external image links (must be CORS-accessible)
393
-
394
- Both formats work:
395
- ```tsx
396
- // Plain strings
397
- complete("Describe", { images: ["https://example.com/photo.jpg"] });
398
-
399
- // ImageInput objects (same as core Gerbil API)
400
- complete("Describe", { images: [{ source: "https://example.com/photo.jpg" }] });
401
- ```
402
-
403
- ## Voice Hooks
404
-
405
- ### `useSpeech` (TTS)
406
-
407
- Generate speech from text in the browser:
408
-
409
- ```tsx
410
- import { useSpeech } from "@tryhamster/gerbil/browser";
411
-
412
- function SpeechDemo() {
413
- const { speak, stop, isSpeaking, isLoading, listVoices } = useSpeech();
414
-
415
- if (isLoading) return <div>Loading TTS model...</div>;
416
-
417
- return (
418
- <div>
419
- <button onClick={() => speak("Hello from the browser!")}>
420
- {isSpeaking ? "Speaking..." : "Speak"}
421
- </button>
422
- {isSpeaking && <button onClick={stop}>Stop</button>}
423
- </div>
424
- );
425
- }
426
- ```
427
-
428
- #### API
429
-
430
- ```typescript
431
- const {
432
- speak, // (text: string, opts?) => Promise<void>
433
- stop, // () => void
434
- isSpeaking, // boolean
435
- isLoading, // boolean
436
- isReady, // boolean
437
- listVoices, // () => VoiceInfo[]
438
- currentVoice, // string
439
- setVoice, // (id: string) => void
440
- currentSpeed, // number
441
- setSpeed, // (speed: number) => void
442
- loadingProgress, // { status, file?, progress? }
443
- error, // string | null
444
- } = useSpeech({
445
- voice: "af_heart", // Default voice
446
- speed: 1.0, // Speed multiplier
447
- autoLoad: false, // Loads on first speak()
448
- });
449
- ```
450
-
451
- 📖 See [TTS docs](./tts.md) for voice list and options.
452
-
453
- ### `useVoiceInput` (STT)
454
-
455
- Record and transcribe audio:
456
-
457
- ```tsx
458
- import { useVoiceInput } from "@tryhamster/gerbil/browser";
459
-
460
- function VoiceInput() {
461
- const { startRecording, stopRecording, isRecording, transcript } = useVoiceInput({
462
- onTranscript: (text) => console.log("User said:", text),
463
- });
464
-
465
- return (
466
- <button onClick={isRecording ? stopRecording : startRecording}>
467
- {isRecording ? "🔴 Stop" : "🎤 Record"}
468
- </button>
469
- );
470
- }
471
- ```
472
-
473
- #### Streaming Transcription (Real-time)
474
-
475
- Transcribe audio in chunks as the user speaks - perfect for live captioning or call transcription:
476
-
477
- ```tsx
478
- function LiveTranscription() {
479
- const {
480
- startRecording,
481
- stopRecording,
482
- isRecording,
483
- transcript, // Full accumulated transcript
484
- streamingChunk, // Current chunk being transcribed
485
- chunkCount, // Number of chunks processed
486
- } = useVoiceInput({
487
- streaming: true, // Enable streaming mode
488
- chunkDuration: 3000, // Transcribe every 3 seconds
489
- onChunk: (text, idx) => console.log(`Chunk ${idx}: ${text}`),
490
- });
491
-
492
- return (
493
- <div>
494
- <button onClick={isRecording ? stopRecording : startRecording}>
495
- {isRecording ? "Stop" : "Start Live Transcription"}
496
- </button>
497
- {streamingChunk && <p style={{ color: 'gray' }}>Current: {streamingChunk}</p>}
498
- <p>Transcript: {transcript}</p>
499
- </div>
500
- );
501
- }
502
- ```
503
-
504
- #### API
505
-
506
- ```typescript
507
- const {
508
- startRecording, // () => Promise<void>
509
- stopRecording, // () => Promise<string>
510
- cancelRecording, // () => void
511
- transcribe, // (audio: Float32Array) => Promise<string>
512
- isRecording, // boolean
513
- isTranscribing, // boolean
514
- isLoading, // boolean
515
- isReady, // boolean
516
- transcript, // string - full transcript
517
- streamingChunk, // string - current chunk (streaming mode)
518
- chunkCount, // number - chunks processed (streaming mode)
519
- loadingProgress, // { status, file?, progress? }
520
- error, // string | null
521
- } = useVoiceInput({
522
- model: "whisper-tiny.en",
523
- autoLoad: false,
524
- onTranscript: (text) => {},
525
- // Streaming options:
526
- streaming: false, // Enable streaming mode
527
- chunkDuration: 1500, // ms between transcriptions (default)
528
- onChunk: (text, idx) => {}, // Called for each chunk
529
- });
530
- ```
531
-
532
- 📖 See [STT docs](./stt.md) for model options.
533
-
534
- ### `useVoiceChat` (Full Voice Conversation)
535
-
536
- Complete voice-to-voice: record → transcribe → LLM → speak:
537
-
538
- ```tsx
539
- import { useVoiceChat } from "@tryhamster/gerbil/browser";
540
-
541
- function VoiceAssistant() {
542
- const {
543
- messages,
544
- startListening,
545
- stopListening,
546
- isListening,
547
- isSpeaking,
548
- stage, // "idle" | "listening" | "transcribing" | "thinking" | "speaking"
549
- } = useVoiceChat({
550
- llmModel: "qwen3-0.6b",
551
- sttModel: "whisper-tiny.en",
552
- voice: "af_bella",
553
- system: "You are a helpful assistant.",
554
- });
555
-
556
- return (
557
- <button
558
- onMouseDown={startListening}
559
- onMouseUp={stopListening}
560
- >
561
- {stage === "idle" ? "🎤 Hold to Speak" : stage}
562
- </button>
563
- );
564
- }
565
- ```
566
-
567
- ### Audio Playback Utilities
568
-
569
- ```typescript
570
- import { playAudio, createAudioPlayer } from "@tryhamster/gerbil/browser";
571
-
572
- // One-shot playback
573
- const controller = await playAudio(audioFloat32Array, 24000);
574
- await controller.onEnded;
575
-
576
- // Streaming playback
577
- const player = createAudioPlayer(24000);
578
- for await (const chunk of gerbil.speakStream("Long text...")) {
579
- player.queue(chunk.samples);
580
- }
581
- ```
582
-
583
- ## Embeddings Hook
584
-
585
- ### `useEmbedding`
586
-
587
- Generate embeddings for semantic search and similarity:
588
-
589
- ```tsx
590
- import { useEmbedding } from "@tryhamster/gerbil/browser";
591
-
592
- function SemanticSearch() {
593
- const { embed, similarity, search, isLoading, isReady, load } = useEmbedding({
594
- model: "Xenova/all-MiniLM-L6-v2", // Default
595
- autoLoad: false,
596
- });
597
-
598
- if (isLoading) return <div>Loading embedding model...</div>;
599
-
600
- const handleSearch = async () => {
601
- const results = await search("capital of France", [
602
- "Paris is beautiful",
603
- "London is in England",
604
- "Dogs are pets",
605
- ], 2); // topK = 2
606
-
607
- console.log(results);
608
- // [{ text: "Paris is beautiful", score: 0.89, index: 0 }, ...]
609
- };
610
-
611
- const handleSimilarity = async () => {
612
- const score = await similarity("Hello world", "Hi there");
613
- console.log(score); // 0.85
614
- };
615
-
616
- return (
617
- <div>
618
- <button onClick={handleSearch}>Search</button>
619
- <button onClick={handleSimilarity}>Compare</button>
620
- </div>
621
- );
622
- }
623
- ```
624
-
625
- ### Options
626
-
627
- ```typescript
628
- const {
629
- // Actions
630
- embed, // (text: string) => Promise<number[]>
631
- embedBatch, // (texts: string[]) => Promise<{ vector, text }[]>
632
- similarity, // (a: string, b: string) => Promise<number>
633
- search, // (query: string, corpus: string[], topK?) => Promise<SearchResult[]>
634
- findNearest, // (embedding: number[], candidates: string[], topK?) => Promise<SearchResult[]>
635
- cosineSimilarity, // (a: number[], b: number[]) => number (sync)
636
- load, // () => void - manually load model
637
-
638
- // State
639
- isLoading, // boolean - model loading
640
- isReady, // boolean - model ready
641
- loadingProgress, // { status, message?, progress? }
642
- error, // string | null
643
- } = useEmbedding({
644
- model: "Xenova/all-MiniLM-L6-v2", // Embedding model
645
- normalize: true, // Normalize vectors (default: true)
646
- autoLoad: false, // Load on mount (default: false)
647
- onReady: () => {},
648
- onError: (err) => {},
649
- });
650
- ```
651
-
652
- ## Low-Level API
653
-
654
- For full control, use `createGerbilWorker` directly:
655
-
656
- ```typescript
657
- import { createGerbilWorker, isWebGPUSupported } from "@tryhamster/gerbil/browser";
658
-
659
- if (!isWebGPUSupported()) {
660
- console.log("WebGPU not supported");
661
- return;
662
- }
663
-
664
- const gerbil = await createGerbilWorker({
665
- modelId: "qwen3-0.6b",
666
- onProgress: (p) => console.log(p.status),
667
- onToken: (token) => console.log(token.text),
668
- onComplete: (result) => console.log(`${result.tps} tok/s`),
669
- });
670
-
671
- await gerbil.generate("Hello!", { thinking: true });
672
- gerbil.terminate();
673
- ```
674
-
675
- ### `createGerbilWorker(options)`
676
-
677
- ```typescript
678
- interface GerbilWorkerOptions {
679
- modelId?: string; // Default: "qwen3-0.6b"
680
- onProgress?: (progress: WorkerProgress) => void;
681
- onToken?: (token: WorkerToken) => void;
682
- onComplete?: (result: WorkerComplete) => void;
683
- onError?: (error: string) => void;
684
- }
685
-
686
- interface GerbilWorker {
687
- generate: (prompt: string, options?: GenerateOptions) => Promise<string>;
688
- interrupt: () => void;
689
- reset: () => void;
690
- terminate: () => void;
691
- isReady: () => boolean;
692
- }
693
- ```
694
-
695
- ### Generation Options
696
-
697
- ```typescript
698
- interface GenerateOptions {
699
- maxTokens?: number; // Default: 256
700
- temperature?: number; // Default: 0.7
701
- topP?: number; // Default: 0.9
702
- topK?: number; // Default: 20
703
- thinking?: boolean; // Enable thinking mode
704
- system?: string; // System prompt
705
- }
706
- ```
707
-
708
- ## Utilities
709
-
710
- ### `isWebGPUSupported()`
711
-
712
- ```typescript
713
- import { isWebGPUSupported } from "@tryhamster/gerbil/browser";
714
-
715
- if (!isWebGPUSupported()) {
716
- // Show fallback or error
717
- }
718
- ```
719
-
720
- ### `getWebGPUInfo()`
721
-
722
- ```typescript
723
- import { getWebGPUInfo } from "@tryhamster/gerbil/browser";
724
-
725
- const info = await getWebGPUInfo();
726
- // { supported: true, adapter: "Apple", device: "Apple M4 Max" }
727
- ```
728
-
729
- ## Models (legacy worker lane)
730
-
731
- | Model | Size | Best For |
732
- |-------|------|----------|
733
- | `qwen3-0.6b` | ~400MB | General use, thinking mode |
734
- | `smollm2-360m` | ~250MB | Faster, smaller |
735
- | `smollm2-135m` | ~100MB | Fastest, basic tasks |
736
-
737
- > For vision, embeddings, and speech use the native engine (`useEngine`) — see the
738
- > [Native Engine](#native-engine-recommended) section above.
739
-
740
- Legacy-lane models are cached in IndexedDB after first download.
741
-
742
- ## Browser Support (legacy worker lane)
743
-
744
- - **Chrome/Edge 113+** — Full WebGPU support
745
- - **Safari 26+ (iOS/iPadOS 26+)** — WebGPU support
746
- - **Firefox 141+** — WebGPU support
747
-
748
- ## Troubleshooting
749
-
750
- ### "WebGPU not supported"
751
-
752
- - Update to Chrome/Edge 113+
753
- - Check `chrome://gpu` for WebGPU status
754
- - Try `chrome://flags/#enable-unsafe-webgpu`
755
-
756
- ### Slow first load
757
-
758
- First load downloads the model (~400MB for qwen3-0.6b) and compiles WebGPU shaders. Subsequent loads use IndexedDB cache (~2-5s).
759
-
760
- ### Out of memory
761
-
762
- Use smaller models like `smollm2-135m`. Close other GPU-intensive tabs.