@degreesign/chat 1.2.0 → 1.3.0

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
@@ -15,6 +15,7 @@ with auto resume for low network connections.
15
15
  [![node](https://img.shields.io/badge/node-%3E%3D24-brightgreen.svg)](#faq)
16
16
  [![types](https://img.shields.io/badge/types-included-brightgreen.svg)](#faq)
17
17
  [![architecture](https://img.shields.io/badge/architecture-view-blue.svg)](https://degreesign.github.io/ds_chat/architecture.html)
18
+ [![comparison](https://img.shields.io/badge/comparison-web--llm-blue.svg)](https://degreesign.github.io/ds_chat/vs_web_llm.html)
18
19
 
19
20
  ## Table of contents
20
21
 
@@ -46,7 +47,7 @@ with auto resume for low network connections.
46
47
  - Images: a user turn can carry image URLs, sent only to a vision model and
47
48
  capped by the prompt budgets.
48
49
  - Audio: a user turn can carry an audio URL, sent only to a model with audio
49
- support and capped by the prompt budgets.
50
+ support. No model in the catalog has an audio encoder.
50
51
  - Reusable: the loaded model stays in memory for the next call.
51
52
  - Typed: written in TypeScript with declarations for every export.
52
53
  - Portable: importing and calling works in browsers, workers, React and Node.
@@ -133,38 +134,28 @@ second, measured from the first token so prompt processing is excluded.
133
134
  Attach images to a user turn with `images: [dataUrl]`. The package sends them
134
135
  only to a model with vision support, fits each image to 128 to 136 vision
135
136
  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.
137
+ the number with `limits.maxImages`. `qwen3_vl_2b` is the vision model in the
138
+ catalog, and it runs on the CPU engine. A turn sent to a model without vision
139
+ drops its images when the turn also has text, and an image-only turn reports
140
+ `Model does not accept images`.
141
+
142
+ Attach a recording with `audio: [dataUrl]`. No model in the catalog has an
143
+ audio encoder, so a text turn drops the clip and an audio-only turn reports
144
+ `Model does not accept audio`. `limits.maxAudio` still caps the clips
145
+ (default 1).
150
146
 
151
147
  The package never writes storage, so the host appends the user message before
152
148
  the call and appends the assistant message after the last `onResponse`.
153
149
 
154
150
  ## Enable WebGPU
155
151
 
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:
152
+ A request without `model` picks `gemma4_e2b_limited` when the browser exposes a
153
+ WebGPU adapter and `qwen3_vl_2b` when it does not. The Gemma build is the 2.0 GB
154
+ text-only `gemma-4-E2B-it-gpu.litertlm`, and it is the only WebGPU model in the
155
+ catalog, so it loads on a desktop and on a phone alike. Every status snapshot
156
+ reports the build that runs in `model`. Hosts that need images pass
157
+ `qwen3_vl_2b`. The GPU engine is far faster on a desktop, so check the browser
158
+ before blaming the package:
168
159
 
169
160
  ```js
170
161
  // Run in the console of the page that loads the package
@@ -217,19 +208,14 @@ CPU, not the GPU. Remove it when measuring speed. Headless Chromium hides
217
208
  `--enable-features=Vulkan,WebGPU` and `--use-angle=vulkan`; check the adapter
218
209
  there too, because a missing adapter silently selects the CPU model.
219
210
 
220
- ### GPU memory, images and audio
211
+ ### GPU memory
221
212
 
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.
213
+ The WebGPU engine keeps the model and its KV cache in GPU memory. The Gemma
214
+ build carries no vision or audio encoder, so it never starts one and never
215
+ resizes an image. A card that is already busy can lose the device
216
+ (`VK_ERROR_DEVICE_LOST`) during a long generation. If the error appears, close
217
+ other GPU tabs, unload the model, or switch to the CPU model. A lost device
218
+ needs a page or model reload; later calls cannot reuse it.
233
219
 
234
220
  While a reply streams, the GPU engine rests after every token readback, so the
235
221
  card stays near 70% of wall time instead of pegging at 100% and starving the
@@ -247,16 +233,16 @@ describeImages(input: DescribeImagesInput): Promise<ImageDescription[]>
247
233
  describeTranslated(input: DescribeTranslatedInput): Promise<string[]>
248
234
  describeModelId: ChatModelId
249
235
  imageReplyTokens: number
236
+ gpuAdapter(): Promise<boolean>
250
237
  clearModelCache(): Promise<void>
251
238
  ```
252
239
 
253
240
  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.
241
+ requested model is already loaded, no download or load happens. One model stays
242
+ resident on every device, so a new model replaces the old one, including a move
243
+ between the CPU describer and the GPU translator. Files stay on disk, and
244
+ `clearModelCache` releases the loaded model and deletes every cached model file
245
+ from this device.
260
246
 
261
247
  `ChatInput`:
262
248
 
@@ -273,10 +259,11 @@ file from this device.
273
259
  | `onStatus?` | Receives a `ChatStatusSnapshot` on every state change. |
274
260
 
275
261
  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.
262
+ WebGPU adapter and the CPU build when it does not. `gpuAdapter` runs the same
263
+ probe, so a host can check the device before it names a model. The budget
264
+ defaults are 250 reply tokens, 128 CPU thinking tokens, 1200 GPU reasoning
265
+ tokens (320 on the CPU), 2 images, 1 audio clip, 1000 characters per message,
266
+ 10000 prompt characters and 32 CPU threads.
280
267
 
281
268
  `CancelChat` is `() => void`. Its meaning depends on the current state:
282
269
 
@@ -286,10 +273,9 @@ characters and 32 CPU threads.
286
273
  | Ready | Aborts the reply and unloads the model that answered. |
287
274
  | An older call | Nothing, because a newer call owns the runtime. |
288
275
 
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.
276
+ The model stays loaded between calls until a cancel frees it, and a newer model
277
+ replaces it. One model download serves every caller: a chat that joins a preload
278
+ shares its progress and never starts a second download.
293
279
 
294
280
  ### Preload
295
281
 
@@ -304,9 +290,8 @@ runtime, and either call joins a load already in flight. A preload owns no cance
304
290
  function, so a chat that joins its download and is cancelled leaves the download
305
291
  running.
306
292
 
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.
293
+ A preload of another model replaces the resident one, because one model stays
294
+ in memory at a time. A load of the same model still joins the one in flight.
310
295
 
311
296
  `ChatPreloadInput` is the subset of `ChatInput` that a preload needs:
312
297
 
@@ -327,7 +312,7 @@ on failure. A preload owns no cancel function; a new load replaces it.
327
312
  describeImages(input: DescribeImagesInput): Promise<ImageDescription[]>
328
313
  ```
329
314
 
330
- Describe every image in English with `qwen3_vl_2b_instruct_q3`, the Q3_K_M
315
+ Describe every image in English with `qwen3_vl_2b_instruct`, the Q4_K_M
331
316
  build of Unsloth Qwen3-VL 2B Instruct, one image at a time. When a WebGPU
332
317
  adapter exists and `targetLanguage` is not English, the English descriptions
333
318
  are then translated one by one with `gemma4_e2b_limited`. Without a GPU, when
@@ -438,7 +423,7 @@ search and currency tools fetch over the network; `calculate` does not.
438
423
 
439
424
  | Field | Description |
440
425
  | ----- | ----------- |
441
- | `model?` | The build that runs after the mobile swap. |
426
+ | `model?` | The model that runs, or the requested build before it resolves. |
442
427
  | `availability` | `ChatStatus.online`, `.away` or `.offline`, derived from `status`. |
443
428
  | `status` | `LlmStatus`: `idle`, `downloading`, `ready`, `error` or `unsupported`. |
444
429
  | `backend` | `LlmBackend.cpu`, `.gpu` or `.none`. |
@@ -453,7 +438,7 @@ search and currency tools fetch over the network; `calculate` does not.
453
438
 
454
439
  The first snapshot fires synchronously inside `createChat`. A final snapshot
455
440
  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
441
+ same snapshot for each model it loads: `qwen3_vl_2b_instruct` while it
457
442
  describes, then `gemma4_e2b_limited` while it translates.
458
443
 
459
444
  A stream that never produces its first chunk ends with an error after 5
@@ -478,43 +463,34 @@ without it goes quiet again.
478
463
  | `qwen3_vl_2b` | Qwen3-VL 2B | Yes | Yes | No | Yes | No |
479
464
  | `qwen3_vl_2b_instruct` | Qwen3-VL 2B Instruct | No | Yes | No | Yes | No |
480
465
  | `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
466
  | `gemma4_e2b_limited` | Gemma 4 E2B limited | Yes | No | No | No | Yes |
483
467
 
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
468
+ `gemma4_e2b_limited` loads `gemma-4-E2B-it-gpu.litertlm` on every device. The
469
+ build that runs is always the one in `ChatStatusSnapshot.model`. It has no
488
470
  vision or audio encoder, so `supportsImages` and `supportsAudio` report `false`
489
- there. Use `qwen3_vl_2b` for images on mobile.
471
+ there. Use `qwen3_vl_2b` for images.
490
472
 
491
473
  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.
474
+ reuse the loaded runtime. One model stays resident, and a new model replaces it.
475
+ An interrupted download keeps every part it committed and resumes from the last
476
+ one on the next attempt, including after a reload.
496
477
 
497
478
  Qwen3-VL 2B downloads a vision projector of about 445 MB with the model, so its
498
479
  total download is about 1.5 GB. It runs on the CPU engine, with a 4096 token
499
480
  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.
481
+ budget as the text pass, so image turns wait behind the CPU queue.
502
482
 
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
483
+ `qwen3_vl_2b_instruct` is the Unsloth Qwen3-VL 2B Instruct build that
484
+ `describeImages` loads for the English pass, its Q4_K_M file. Both instruct
505
485
  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.
486
+ 1.5 GB on the CPU engine. It has no reasoning pass, so it answers an image
487
+ prompt directly. `qwen3_vl_2b_instruct_q3` is the same model in Q3_K_M, about
488
+ 160 MB smaller, for a host that prefers the smaller file and names it
489
+ explicitly.
490
+
491
+ Gemma 4 E2B downloads the 2.0 GB `gemma-4-E2B-it-gpu.litertlm`, the
492
+ precompiled GPU_ARTISAN build Google ships for browsers. It runs on the WebGPU
493
+ engine, carries no vision or audio encoder, and needs no projector download.
518
494
 
519
495
  The WebGPU engine asks for a 32768 token context on a desktop and 16384 on a
520
496
  phone, both multiples of 1024. Its int8 KV cache holds the full desktop window
@@ -551,11 +527,9 @@ user: {next turn}
551
527
  (default 2). Older image parts are dropped and their text stays.
552
528
  - Every image part keeps its full frame and targets 128 vision tokens, with at
553
529
  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.
530
+ cropped.
556
531
  - 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.
532
+ (default 1). Older audio parts are dropped and their text stays.
559
533
  - Image and audio payloads do not count against `promptCharBudget`.
560
534
  - An image-only turn with a text model reports `Model does not accept images`,
561
535
  and an audio-only turn reports `Model does not accept audio`.
@@ -618,11 +592,9 @@ self hosted. Chromium also accepts
618
592
  changed. A page inside an iframe is isolated only when every ancestor sets the
619
593
  same headers.
620
594
 
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.
595
+ Each engine keeps its own cache. One model stays loaded, so a switch between
596
+ the CPU and GPU models skips the download but still loads the new engine. The
597
+ previous model's files always stay, so a switch back skips the download.
626
598
  `clearModelCache()` deletes them all and frees the storage. Both engines store
627
599
  model files in the origin private file system as numbered parts, under
628
600
  `wllama-store` for the CPU engine and `litert-store` for the GPU engine. The GPU
@@ -650,7 +622,7 @@ wasm under `dist/assets/litert`; model weights remain external.
650
622
 
651
623
  LiteRT-LM needs WebGPU and the JSPI and relaxed SIMD wasm features, currently
652
624
  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
625
+ and the 2.0 GB model file. The GPU engine reports progress while the file
654
626
  downloads and 100% of the load phase while the model compiles.
655
627
 
656
628
  wllama needs the compat worker on the same origin. When the app serves the
@@ -739,7 +711,7 @@ The public enums are `ChatModelId`, `LlmRole`, `ChatStatus`, `LlmStatus`,
739
711
  The suites live in `test/` and run against the local checkout.
740
712
 
741
713
  `yarn test:instruct` starts a local llama.cpp server with the Qwen3-VL 2B
742
- Instruct Q3_K_M build:
714
+ Instruct Q4_K_M build:
743
715
 
744
716
  ```bash
745
717
  yarn test:instruct
@@ -769,6 +741,9 @@ An interactive map of the package lives at
769
741
  the two entries, every source module with line numbers, the helper edges and the
770
742
  send flow.
771
743
 
744
+ A side-by-side comparison with `@mlc-ai/web-llm` for Gemma 4 E2B on WebGPU lives
745
+ at [degreesign.github.io/ds_chat/vs_web_llm.html](https://degreesign.github.io/ds_chat/vs_web_llm.html).
746
+
772
747
  ## FAQ
773
748
 
774
749
  ### What is @degreesign/chat?
@@ -783,25 +758,19 @@ No. The host passes the full prompt on every call and keeps the reply.
783
758
  ### Does it support images?
784
759
 
785
760
  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
761
+ array. Qwen3-VL 2B accepts images on the CPU engine and is the vision model in
762
+ the catalog. Each image keeps its full frame and targets 128 vision tokens, at
763
+ most 136, with both sides snapped to multiples of 32 px and the remainder
764
+ cropped. The WebGPU build is text only, so a model without vision drops images
793
765
  when the turn also has text; an image-only turn reports
794
766
  `Model does not accept images`.
795
767
 
796
768
  ### Does it support audio?
797
769
 
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`.
770
+ No model in the catalog has an audio encoder. A user turn can still carry an
771
+ audio URL in `audio`, and `limits.maxAudio` caps the clips (default 1), but a
772
+ model without audio support drops the clip when the turn also has text, and an
773
+ audio-only turn reports `Model does not accept audio`.
805
774
 
806
775
  ### Does it need a server?
807
776
 
@@ -824,11 +793,11 @@ when an older version stored the file. The package stores no model data itself.
824
793
 
825
794
  ### Which model should I pick?
826
795
 
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`.
796
+ Qwen3-VL 2B supports reasoning and images on the CPU, and it runs on any browser
797
+ with WebAssembly. Gemma 4 E2B limited supports reasoning on WebGPU, with faster
798
+ text generation but a larger download and a slower first compile. Use Qwen for
799
+ broad browser support and for images. Use Gemma on a device with a WebGPU
800
+ adapter when text speed matters.
832
801
 
833
802
  ## Licensing
834
803