@degreesign/chat 1.2.0 → 1.3.1

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.
package/README.md CHANGED
@@ -1,26 +1,27 @@
1
1
  # @degreesign/chat
2
2
 
3
- One-shot on-device AI chat for browsers, workers and Node, using wllama CPU
4
- engine and LiteRT-LM WebGPU engine.
5
- The package is an A-Z solution for local AI chat and more.
6
- Includes voice transcription to many languages
7
- and a super accurate image vision function.
8
- The package manages download of AI models automatically,
9
- with auto resume for low network connections.
3
+ One-shot on-device AI chat for browsers, workers and Node, using a wllama CPU
4
+ engine and a LiteRT-LM WebGPU engine. It streams text, describes images,
5
+ manages model downloads with automatic resume, and renders no UI.
10
6
 
11
- `@degreesign/chat` is plug and play solution for any UI.
7
+ Voice notes are transcribed on device by `@degreesign/voice`, a companion
8
+ package, then passed to `@degreesign/chat` as text.
9
+
10
+ `@degreesign/chat` is a plug and play engine for any UI.
12
11
 
13
12
  [![npm version](https://img.shields.io/npm/v/@degreesign/chat.svg)](https://www.npmjs.com/package/@degreesign/chat)
14
13
  [![license](https://img.shields.io/badge/license-MIT-blue.svg)](#licensing)
15
14
  [![node](https://img.shields.io/badge/node-%3E%3D24-brightgreen.svg)](#faq)
16
15
  [![types](https://img.shields.io/badge/types-included-brightgreen.svg)](#faq)
17
16
  [![architecture](https://img.shields.io/badge/architecture-view-blue.svg)](https://degreesign.github.io/ds_chat/architecture.html)
17
+ [![comparison](https://img.shields.io/badge/comparison-web--llm-blue.svg)](https://degreesign.github.io/ds_chat/vs_web_llm.html)
18
18
 
19
19
  ## Table of contents
20
20
 
21
21
  - [Why](#why)
22
22
  - [Install](#install)
23
23
  - [Quick start](#quick-start)
24
+ - [Case study: DoListy](#case-study-dolisty)
24
25
  - [Enable WebGPU](#enable-webgpu)
25
26
  - [Public API](#public-api)
26
27
  - [Tools](#tools)
@@ -38,6 +39,8 @@ with auto resume for low network connections.
38
39
 
39
40
  ## Why
40
41
 
42
+ - Proven: [DoListy](https://dolisty.com/#chat), a no-signup PWA, answers
43
+ with the network off.
41
44
  - On device: model weights download once, then every reply is generated locally.
42
45
  - Headless: the package returns text through callbacks and renders no UI.
43
46
  - Stateless: the host passes the whole message list and keeps its own history.
@@ -46,7 +49,7 @@ with auto resume for low network connections.
46
49
  - Images: a user turn can carry image URLs, sent only to a vision model and
47
50
  capped by the prompt budgets.
48
51
  - Audio: a user turn can carry an audio URL, sent only to a model with audio
49
- support and capped by the prompt budgets.
52
+ support. No model in the catalog has an audio encoder.
50
53
  - Reusable: the loaded model stays in memory for the next call.
51
54
  - Typed: written in TypeScript with declarations for every export.
52
55
  - Portable: importing and calling works in browsers, workers, React and Node.
@@ -133,38 +136,84 @@ second, measured from the first token so prompt processing is excluded.
133
136
  Attach images to a user turn with `images: [dataUrl]`. The package sends them
134
137
  only to a model with vision support, fits each image to 128 to 136 vision
135
138
  tokens, snaps both sides to multiples of 32 px, crops the remainder, and caps
136
- the number with `limits.maxImages`. The WebGPU engine asks LiteRT for the
137
- smallest Gemma 4 vision budget (70 tokens, 630 patches), so it center-crops to a
138
- 384 x 384 square instead. On a phone or tablet a Gemma request resolves to
139
- `gemma4_e2b_limited`, the text-only browser build, so images there go through
140
- `qwen3_vl_2b` instead.
141
-
142
- Attach a recording with `audio: [dataUrl]`. Gemma 4 E2B accepts WAV, MP3 and
143
- FLAC on the desktop WebGPU engine and loads its audio encoder on demand. A short
144
- 16 kHz mono WAV is the best input. `limits.maxAudio` caps the clips (default 1).
145
- A model without audio drops the clip when the turn also has text; an audio-only
146
- turn reports `Model does not accept audio`. On a phone or tablet
147
- `gemma4_e2b_limited` is text only, so audio follows the same rule as images
148
- there. The package does not transcode, so the host supplies one of those
149
- formats.
139
+ the number with `limits.maxImages`. `qwen3_vl_2b` is the vision model in the
140
+ catalog, and it runs on the CPU engine. A turn sent to a model without vision
141
+ drops its images when the turn also has text, and an image-only turn reports
142
+ `Model does not accept images`.
143
+
144
+ Attach a recording with `audio: [dataUrl]`. No model in the catalog has an
145
+ audio encoder, so a text turn drops the clip and an audio-only turn reports
146
+ `Model does not accept audio`. `limits.maxAudio` still caps the clips
147
+ (default 1).
150
148
 
151
149
  The package never writes storage, so the host appends the user message before
152
150
  the call and appends the assistant message after the last `onResponse`.
153
151
 
152
+ ## Case study: DoListy
153
+
154
+ [DoListy](https://dolisty.com/#chat) is a DegreeSign project: a private
155
+ to-do list PWA with no sign-up. It ships `@degreesign/chat` for replies
156
+ and image descriptions, and `@degreesign/voice` for voice notes.
157
+
158
+ I wanted an assistant that could read my daily tasks, but I did not want
159
+ to give 100% of my data to big tech. I was also working on a new project
160
+ that had to stay under wraps until its soft launch, so I could not send
161
+ that work to a hosted model. The assistant had to run on the device, and
162
+ it had to be easy for any developer to drop into an app. Most days I have
163
+ that conversation on my phone: a voice note, a transcript, a few tasks,
164
+ all local. DoListy is where that engine got its first real use.
165
+
166
+ ![DoListy assistant answering a question on a phone][dolisty-shot]
167
+
168
+ [dolisty-shot]: https://degreesign.github.io/ds_chat/case-study-dolisty.png
169
+
170
+ *A real session on dolisty.com. The green dot is the local model, not a
171
+ server.*
172
+
173
+ ### What it does
174
+
175
+ - Describes a photo and tags it without uploading it.
176
+ - Turns a voice note into a task through `@degreesign/voice`.
177
+ - Writes titles, tags and a running summary in 19 languages. The persona
178
+ is the app's; the package adds no identity text.
179
+ - Answers with the network off, from the model already on disk.
180
+
181
+ ### What it costs
182
+
183
+ - No per-reply cost, no API key, no rate limit. The only bill is the
184
+ one-time download: about 1.5 GB for Qwen3-VL, 2.0 GB for Gemma 4.
185
+ - An image waits about 70 seconds for its first token on an 8-thread
186
+ desktop CPU. That is the cost of the CPU vision path, chosen so images
187
+ work on phones without WebGPU; text takes the GPU path when the browser
188
+ has one.
189
+ - A 2 GB download resumes from the last committed part. A reload costs at
190
+ most one part, never the whole file.
191
+ - The gallery describes images in the background while you work.
192
+
193
+ ### How it is wired
194
+
195
+ - One `createChat` per send, over a prompt assembled from the app's
196
+ encrypted stores.
197
+ - The reply is committed when `onStatus` reports the end; a newer send
198
+ drops the stale one.
199
+ - `gpuAdapter()` picks Gemma on WebGPU and Qwen everywhere else.
200
+ - One model stays resident, so a switch reloads the engine but never the
201
+ file.
202
+
203
+ **See it work:** open [dolisty.com](https://dolisty.com/#chat) on a phone,
204
+ let the model download once, then turn off the network and ask for a
205
+ summary. Speed numbers are in the
206
+ [web-llm comparison](https://degreesign.github.io/ds_chat/vs_web_llm.html).
207
+
154
208
  ## Enable WebGPU
155
209
 
156
- A request without `model` picks Gemma when the browser exposes a WebGPU adapter
157
- and `qwen3_vl_2b` when it does not. A phone or tablet asking for Gemma gets
158
- `gemma4_e2b_limited`, the 2.0 GB text-only browser build
159
- (`gemma-4-E2B-it-gpu.litertlm`); a desktop gets `gemma4_e2b`, the 2.6 GB
160
- multimodal build with vision and audio. The downgrade applies to `gemma4_e2b`
161
- alone: an explicit `gemma4_e2b_limited` loads the smaller build on every
162
- device, so a desktop can keep the text-only file too. Once a model is requested
163
- or resolved, every status snapshot reports the build that runs in `model`, so a
164
- host that asks for `gemma4_e2b` on a phone reads `gemma4-e2b-limited` back.
165
- Hosts that need images on mobile pass `qwen3_vl_2b`; audio is desktop only. The
166
- GPU engine is far faster on a desktop, so check the browser before blaming the
167
- package:
210
+ A request without `model` picks `gemma4_e2b_limited` when the browser exposes a
211
+ WebGPU adapter and `qwen3_vl_2b` when it does not. The Gemma build is the 2.0 GB
212
+ text-only `gemma-4-E2B-it-gpu.litertlm`, and it is the only WebGPU model in the
213
+ catalog, so it loads on a desktop and on a phone alike. Every status snapshot
214
+ reports the build that runs in `model`. Hosts that need images pass
215
+ `qwen3_vl_2b`. The GPU engine is far faster on a desktop, so check the browser
216
+ before blaming the package:
168
217
 
169
218
  ```js
170
219
  // Run in the console of the page that loads the package
@@ -217,19 +266,14 @@ CPU, not the GPU. Remove it when measuring speed. Headless Chromium hides
217
266
  `--enable-features=Vulkan,WebGPU` and `--use-angle=vulkan`; check the adapter
218
267
  there too, because a missing adapter silently selects the CPU model.
219
268
 
220
- ### GPU memory, images and audio
269
+ ### GPU memory
221
270
 
222
- The WebGPU engine keeps the model, its KV cache and the vision encoder in GPU
223
- memory. The vision encoder loads on the first image turn. The audio encoder
224
- runs on the CPU backend the Gemma 4 build requires and loads on the first audio
225
- turn. A card that is already busy can lose the device (`VK_ERROR_DEVICE_LOST`)
226
- on the first image turn. The package requests the smallest Gemma 4 vision
227
- budget, 70 tokens and 630 patches, so the encoder runs one small signature and
228
- the crop is resized to 384 x 384 rather than the full 768 x 768. If the error
229
- still appears, close other GPU tabs, unload the model, or switch to the CPU
230
- model. A lost device needs a page or model reload; later calls cannot reuse it.
231
- A phone or tablet resolves Gemma to the text-only browser build, so it never
232
- starts the vision or audio encoder.
271
+ The WebGPU engine keeps the model and its KV cache in GPU memory. The Gemma
272
+ build carries no vision or audio encoder, so it never starts one and never
273
+ resizes an image. A card that is already busy can lose the device
274
+ (`VK_ERROR_DEVICE_LOST`) during a long generation. If the error appears, close
275
+ other GPU tabs, unload the model, or switch to the CPU model. A lost device
276
+ needs a page or model reload; later calls cannot reuse it.
233
277
 
234
278
  While a reply streams, the GPU engine rests after every token readback, so the
235
279
  card stays near 70% of wall time instead of pegging at 100% and starving the
@@ -247,16 +291,16 @@ describeImages(input: DescribeImagesInput): Promise<ImageDescription[]>
247
291
  describeTranslated(input: DescribeTranslatedInput): Promise<string[]>
248
292
  describeModelId: ChatModelId
249
293
  imageReplyTokens: number
294
+ gpuAdapter(): Promise<boolean>
250
295
  clearModelCache(): Promise<void>
251
296
  ```
252
297
 
253
298
  Call it once per response. A new call aborts the previous stream. When the
254
- requested model is already loaded, no download or load happens. On a desktop a
255
- loaded model stays resident, one per backend, so the CPU describer and the GPU
256
- translator never reload each other; a new model of the same backend replaces the
257
- old one. A phone keeps one model in total. Files stay on disk, and
258
- `clearModelCache` releases every loaded model and deletes every cached model
259
- file from this device.
299
+ requested model is already loaded, no download or load happens. One model stays
300
+ resident on every device, so a new model replaces the old one, including a move
301
+ between the CPU describer and the GPU translator. Files stay on disk, and
302
+ `clearModelCache` releases the loaded model and deletes every cached model file
303
+ from this device.
260
304
 
261
305
  `ChatInput`:
262
306
 
@@ -273,10 +317,11 @@ file from this device.
273
317
  | `onStatus?` | Receives a `ChatStatusSnapshot` on every state change. |
274
318
 
275
319
  Without `model` the package picks the GPU build when the browser provides a
276
- WebGPU adapter and the CPU build when it does not. The budget defaults are 250
277
- reply tokens, 128 CPU thinking tokens, 1200 GPU reasoning tokens (320 on the
278
- CPU), 2 images, 1 audio clip, 1000 characters per message, 10000 prompt
279
- characters and 32 CPU threads.
320
+ WebGPU adapter and the CPU build when it does not. `gpuAdapter` runs the same
321
+ probe, so a host can check the device before it names a model. The budget
322
+ defaults are 250 reply tokens, 128 CPU thinking tokens, 1200 GPU reasoning
323
+ tokens (320 on the CPU), 2 images, 1 audio clip, 1000 characters per message,
324
+ 10000 prompt characters and 32 CPU threads.
280
325
 
281
326
  `CancelChat` is `() => void`. Its meaning depends on the current state:
282
327
 
@@ -286,10 +331,9 @@ characters and 32 CPU threads.
286
331
  | Ready | Aborts the reply and unloads the model that answered. |
287
332
  | An older call | Nothing, because a newer call owns the runtime. |
288
333
 
289
- The model stays loaded between calls until a cancel frees it, and a newer
290
- model of the same backend replaces it. One model download serves every caller:
291
- a chat that joins a preload shares its progress and never starts a second
292
- download.
334
+ The model stays loaded between calls until a cancel frees it, and a newer model
335
+ replaces it. One model download serves every caller: a chat that joins a preload
336
+ shares its progress and never starts a second download.
293
337
 
294
338
  ### Preload
295
339
 
@@ -304,9 +348,8 @@ runtime, and either call joins a load already in flight. A preload owns no cance
304
348
  function, so a chat that joins its download and is cancelled leaves the download
305
349
  running.
306
350
 
307
- Preloads on different backends run together, so a host may warm the GPU text
308
- model and the CPU media model inside one `Promise.all`. Each call reports only
309
- its own model, and a load on the same backend still replaces the one in flight.
351
+ A preload of another model replaces the resident one, because one model stays
352
+ in memory at a time. A load of the same model still joins the one in flight.
310
353
 
311
354
  `ChatPreloadInput` is the subset of `ChatInput` that a preload needs:
312
355
 
@@ -327,7 +370,7 @@ on failure. A preload owns no cancel function; a new load replaces it.
327
370
  describeImages(input: DescribeImagesInput): Promise<ImageDescription[]>
328
371
  ```
329
372
 
330
- Describe every image in English with `qwen3_vl_2b_instruct_q3`, the Q3_K_M
373
+ Describe every image in English with `qwen3_vl_2b_instruct`, the Q4_K_M
331
374
  build of Unsloth Qwen3-VL 2B Instruct, one image at a time. When a WebGPU
332
375
  adapter exists and `targetLanguage` is not English, the English descriptions
333
376
  are then translated one by one with `gemma4_e2b_limited`. Without a GPU, when
@@ -438,7 +481,7 @@ search and currency tools fetch over the network; `calculate` does not.
438
481
 
439
482
  | Field | Description |
440
483
  | ----- | ----------- |
441
- | `model?` | The build that runs after the mobile swap. |
484
+ | `model?` | The model that runs, or the requested build before it resolves. |
442
485
  | `availability` | `ChatStatus.online`, `.away` or `.offline`, derived from `status`. |
443
486
  | `status` | `LlmStatus`: `idle`, `downloading`, `ready`, `error` or `unsupported`. |
444
487
  | `backend` | `LlmBackend.cpu`, `.gpu` or `.none`. |
@@ -453,7 +496,7 @@ search and currency tools fetch over the network; `calculate` does not.
453
496
 
454
497
  The first snapshot fires synchronously inside `createChat`. A final snapshot
455
498
  with `requesting: false` marks the end of the call. `describeImages` reports the
456
- same snapshot for each model it loads: `qwen3_vl_2b_instruct_q3` while it
499
+ same snapshot for each model it loads: `qwen3_vl_2b_instruct` while it
457
500
  describes, then `gemma4_e2b_limited` while it translates.
458
501
 
459
502
  A stream that never produces its first chunk ends with an error after 5
@@ -478,43 +521,34 @@ without it goes quiet again.
478
521
  | `qwen3_vl_2b` | Qwen3-VL 2B | Yes | Yes | No | Yes | No |
479
522
  | `qwen3_vl_2b_instruct` | Qwen3-VL 2B Instruct | No | Yes | No | Yes | No |
480
523
  | `qwen3_vl_2b_instruct_q3` | Qwen3-VL 2B Instruct Q3 | No | Yes | No | Yes | No |
481
- | `gemma4_e2b` | Gemma 4 E2B | Yes | Yes | Yes | No | Yes |
482
524
  | `gemma4_e2b_limited` | Gemma 4 E2B limited | Yes | No | No | No | Yes |
483
525
 
484
- A phone or tablet asking for `gemma4_e2b` gets `gemma4_e2b_limited`,
485
- `gemma-4-E2B-it-gpu.litertlm`, which is text only. An explicit
486
- `gemma4_e2b_limited` loads that same file on a desktop. The build that actually
487
- runs is always the one in `ChatStatusSnapshot.model`. The limited build has no
526
+ `gemma4_e2b_limited` loads `gemma-4-E2B-it-gpu.litertlm` on every device. The
527
+ build that runs is always the one in `ChatStatusSnapshot.model`. It has no
488
528
  vision or audio encoder, so `supportsImages` and `supportsAudio` report `false`
489
- there. Use `qwen3_vl_2b` for images on mobile.
529
+ there. Use `qwen3_vl_2b` for images.
490
530
 
491
531
  The first call downloads the model before its engine loads it. Later calls
492
- reuse the loaded runtime. A desktop keeps one model per backend in memory, so
493
- the CPU describer and the GPU text model stay resident together. An interrupted
494
- download keeps every part it committed and resumes from the last one on the
495
- next attempt, including after a reload.
532
+ reuse the loaded runtime. One model stays resident, and a new model replaces it.
533
+ An interrupted download keeps every part it committed and resumes from the last
534
+ one on the next attempt, including after a reload.
496
535
 
497
536
  Qwen3-VL 2B downloads a vision projector of about 445 MB with the model, so its
498
537
  total download is about 1.5 GB. It runs on the CPU engine, with a 4096 token
499
538
  context on a phone and 8192 on a desktop. Its projector uses the same CPU thread
500
- budget as the text pass, so image turns are slower than the Gemma 4 WebGPU path.
501
- Use the GPU model when images need to be fast.
539
+ budget as the text pass, so image turns wait behind the CPU queue.
502
540
 
503
- `qwen3_vl_2b_instruct_q3` is the Unsloth Qwen3-VL 2B Instruct build that
504
- `describeImages` loads for the English pass, its Q3_K_M file. Both instruct
541
+ `qwen3_vl_2b_instruct` is the Unsloth Qwen3-VL 2B Instruct build that
542
+ `describeImages` loads for the English pass, its Q4_K_M file. Both instruct
505
543
  builds use Qwen's Q8_0 projector, about 445 MB, so the describer totals about
506
- 1.4 GB on the CPU engine. It has no reasoning pass, so it answers an image
507
- prompt directly. `qwen3_vl_2b_instruct` is the same model in Q4_K_M, about
508
- 200 MB larger, for a host that prefers the higher precision describer and
509
- names it explicitly.
510
-
511
- Gemma 4 E2B downloads `gemma-4-E2B-it.litertlm`, about 2.6 GB, on a desktop. It
512
- runs on the WebGPU engine with its vision and audio encoders in the same file,
513
- so an image or audio prompt does not download a projector. `gemma4_e2b_limited`
514
- instead downloads the 2.0 GB `gemma-4-E2B-it-gpu.litertlm`, the precompiled
515
- GPU_ARTISAN build Google ships for browsers. The smaller file is what keeps a
516
- mobile tab alive at the end of the model load, and a desktop can ask for it
517
- too, trading vision and audio for 600 MB less download.
544
+ 1.5 GB on the CPU engine. It has no reasoning pass, so it answers an image
545
+ prompt directly. `qwen3_vl_2b_instruct_q3` is the same model in Q3_K_M, about
546
+ 160 MB smaller, for a host that prefers the smaller file and names it
547
+ explicitly.
548
+
549
+ Gemma 4 E2B downloads the 2.0 GB `gemma-4-E2B-it-gpu.litertlm`, the
550
+ precompiled GPU_ARTISAN build Google ships for browsers. It runs on the WebGPU
551
+ engine, carries no vision or audio encoder, and needs no projector download.
518
552
 
519
553
  The WebGPU engine asks for a 32768 token context on a desktop and 16384 on a
520
554
  phone, both multiples of 1024. Its int8 KV cache holds the full desktop window
@@ -551,11 +585,9 @@ user: {next turn}
551
585
  (default 2). Older image parts are dropped and their text stays.
552
586
  - Every image part keeps its full frame and targets 128 vision tokens, with at
553
587
  most 136. Both sides are snapped to multiples of 32 px and the remainder is
554
- cropped. The WebGPU engine instead center-crops to a 384 x 384 square, the
555
- smallest Gemma 4 encoder signature.
588
+ cropped.
556
589
  - Audio is collected from the newest turns first and capped by `maxAudio`
557
- (default 1). Older audio parts are dropped and their text stays. WAV, MP3 and
558
- FLAC are accepted, and a short 16 kHz mono WAV is the best input.
590
+ (default 1). Older audio parts are dropped and their text stays.
559
591
  - Image and audio payloads do not count against `promptCharBudget`.
560
592
  - An image-only turn with a text model reports `Model does not accept images`,
561
593
  and an audio-only turn reports `Model does not accept audio`.
@@ -618,11 +650,9 @@ self hosted. Chromium also accepts
618
650
  changed. A page inside an iframe is isolated only when every ancestor sets the
619
651
  same headers.
620
652
 
621
- Each engine keeps its own cache. A desktop keeps one loaded model per backend,
622
- so a switch between the CPU and GPU models skips both the download and the
623
- load; a model of the same backend replaces the previous one. A phone keeps one
624
- model in total. The previous model's files always stay, so a switch back skips
625
- the download.
653
+ Each engine keeps its own cache. One model stays loaded, so a switch between
654
+ the CPU and GPU models skips the download but still loads the new engine. The
655
+ previous model's files always stay, so a switch back skips the download.
626
656
  `clearModelCache()` deletes them all and frees the storage. Both engines store
627
657
  model files in the origin private file system as numbered parts, under
628
658
  `wllama-store` for the CPU engine and `litert-store` for the GPU engine. The GPU
@@ -650,7 +680,7 @@ wasm under `dist/assets/litert`; model weights remain external.
650
680
 
651
681
  LiteRT-LM needs WebGPU and the JSPI and relaxed SIMD wasm features, currently
652
682
  available in Chromium. It loads a 22.7 MB wasm binary from `dist/assets/litert`
653
- and the 2.6 GB model file. The GPU engine reports progress while the file
683
+ and the 2.0 GB model file. The GPU engine reports progress while the file
654
684
  downloads and 100% of the load phase while the model compiles.
655
685
 
656
686
  wllama needs the compat worker on the same origin. When the app serves the
@@ -739,7 +769,7 @@ The public enums are `ChatModelId`, `LlmRole`, `ChatStatus`, `LlmStatus`,
739
769
  The suites live in `test/` and run against the local checkout.
740
770
 
741
771
  `yarn test:instruct` starts a local llama.cpp server with the Qwen3-VL 2B
742
- Instruct Q3_K_M build:
772
+ Instruct Q4_K_M build:
743
773
 
744
774
  ```bash
745
775
  yarn test:instruct
@@ -769,6 +799,9 @@ An interactive map of the package lives at
769
799
  the two entries, every source module with line numbers, the helper edges and the
770
800
  send flow.
771
801
 
802
+ A side-by-side comparison with `@mlc-ai/web-llm` for Gemma 4 E2B on WebGPU lives
803
+ at [degreesign.github.io/ds_chat/vs_web_llm.html](https://degreesign.github.io/ds_chat/vs_web_llm.html).
804
+
772
805
  ## FAQ
773
806
 
774
807
  ### What is @degreesign/chat?
@@ -783,25 +816,19 @@ No. The host passes the full prompt on every call and keeps the reply.
783
816
  ### Does it support images?
784
817
 
785
818
  Yes, on models with vision support. Put image URLs in a user turn's `images`
786
- array. Qwen3-VL 2B accepts images on the CPU engine and Gemma 4 E2B accepts
787
- images on the desktop WebGPU engine. On a phone or tablet Gemma resolves to
788
- `gemma4_e2b_limited`, the text-only browser build, so use `qwen3_vl_2b` for
789
- mobile images. Each image keeps its full frame and targets 128 vision tokens,
790
- at most 136, with both sides snapped to multiples of 32 px and the remainder
791
- cropped. The WebGPU engine instead center-crops a 384 x 384 square with the
792
- smallest Gemma 4 vision budget. A model without vision drops images
819
+ array. Qwen3-VL 2B accepts images on the CPU engine and is the vision model in
820
+ the catalog. Each image keeps its full frame and targets 128 vision tokens, at
821
+ most 136, with both sides snapped to multiples of 32 px and the remainder
822
+ cropped. The WebGPU build is text only, so a model without vision drops images
793
823
  when the turn also has text; an image-only turn reports
794
824
  `Model does not accept images`.
795
825
 
796
826
  ### Does it support audio?
797
827
 
798
- Yes, on models with audio support. Put an audio URL in a user turn's `audio`
799
- array. Gemma 4 E2B accepts WAV, MP3 and FLAC on the desktop WebGPU engine, and a
800
- short 16 kHz mono WAV is the best input. `limits.maxAudio` caps the clips
801
- (default 1). Audio is desktop only; `gemma4_e2b_limited` has no audio encoder.
802
- The package does not transcode, so the host supplies one of those formats. A
803
- model without audio drops the clip when the turn also has text; an audio-only
804
- turn reports `Model does not accept audio`.
828
+ No model in the catalog has an audio encoder. A user turn can still carry an
829
+ audio URL in `audio`, and `limits.maxAudio` caps the clips (default 1), but a
830
+ model without audio support drops the clip when the turn also has text, and an
831
+ audio-only turn reports `Model does not accept audio`.
805
832
 
806
833
  ### Does it need a server?
807
834
 
@@ -824,11 +851,11 @@ when an older version stored the file. The package stores no model data itself.
824
851
 
825
852
  ### Which model should I pick?
826
853
 
827
- Qwen3-VL 2B supports reasoning and images on the CPU. Gemma 4 E2B supports
828
- reasoning, images and audio on desktop WebGPU, with much faster media prompts
829
- but a larger download and a slower first compile. Use Qwen for broad browser
830
- support and for mobile images. Use Gemma on a desktop with Chromium WebGPU, or
831
- on a phone for text, where it resolves to `gemma4_e2b_limited`.
854
+ Qwen3-VL 2B supports reasoning and images on the CPU, and it runs on any browser
855
+ with WebAssembly. Gemma 4 E2B limited supports reasoning on WebGPU, with faster
856
+ text generation but a larger download and a slower first compile. Use Qwen for
857
+ broad browser support and for images. Use Gemma on a device with a WebGPU
858
+ adapter when text speed matters.
832
859
 
833
860
  ## Licensing
834
861