The OpenAI-compatible server ships as a standalone bundle for macOS (Apple silicon), Windows
diff --git a/site-dasllama/test_metadata.py b/site-dasllama/test_metadata.py
index 8602c1cdbd..00d7a40e96 100644
--- a/site-dasllama/test_metadata.py
+++ b/site-dasllama/test_metadata.py
@@ -110,6 +110,7 @@ def test_caddy_redirects_explicit_index(self):
EXAMPLE_SHELLS = {
"storyteller": (REPO_ROOT / "examples" / "dasLLAMA" / "storyteller" / "web_shell.html", "runStoryteller"),
"storywish": (REPO_ROOT / "examples" / "dasLLAMA" / "storywish" / "web_shell.html", "runStorywish"),
+ "parrot": (REPO_ROOT / "examples" / "dasLLAMA" / "parrot" / "web_shell.html", "runParrot"),
}
diff --git a/utils/dasllama-server/README.md b/utils/dasllama-server/README.md
index 8af526e6ad..3913449d04 100644
--- a/utils/dasllama-server/README.md
+++ b/utils/dasllama-server/README.md
@@ -184,10 +184,11 @@ Catalog entries carry their **towers**: a vision-capable row offers its pinned m
under the table offers the ASR tower (parakeet v3; wires the `asr` key the same way) -
`POST /catalog/download` takes `{"name", "tower": "vision"}` or `{"tower": "asr"}` on the
same one-at-a-time rail. A **speech** strip sits beside it for the text-to-speech set the
-`/catalog` document's `tts` list carries: the two front-end packs first (every speech model
-loads them), then one model, then **enable speech** wires the `tts` key -
-`{"tower": "tts", "file": }` pulls one file of that set on the same rail; a Pocket file is
-its whole set. Setup-mode
+`/catalog` document's `tts` list carries: a model on disk that can speak goes straight to
+**enable speech**, which wires the `tts` key (a Pocket file stands alone; a phoneme family
+needs the two front-end packs beside it - `needs_packs` on the row); otherwise the packs first,
+then one model - `{"tower": "tts", "file": }` pulls one file of that set on the same rail.
+Setup-mode
**serve this model** wires any tower already on disk automatically. Each row also wears a
**fit badge** (fits gpu / fits / tight / too big) from
the box facts the `/catalog` document carries (`box.ram_gb`, and the armed tier's weight
@@ -309,7 +310,7 @@ server first; Windows locks the DLLs.
| `POST` | `/v1/audio/speech` | Text->speech (needs `--tts`): `{"input", "voice"?, "speed"?, "response_format"?: "wav" \| "pcm"}` - the OpenAI shape; `wav` (default) is 16-bit PCM at the model's rate, `pcm` the raw samples; the compressed formats answer `400` (no encoder here). One synthesis at a time on the TTS worker (its kernels run inline under `hybrid`, like the ASR workers'), 16 queued |
| `POST` | `/v1/audio/phonemes` | The front end alone (needs `--tts`): `{"model"?, "input", "voice"?}` -> `{"normalized", "lang", "chunks": [{"text", "phonemes"}]}` - the normalizer's spoken form of the text, the dialect the voice speaks (`lang`), then one row per chunk a synthesis of it would take, each carrying that chunk beside its phoneme string in that dialect. A model whose front end phonemizes ONE language reads every voice name in it - an alias, or a name it does not carry, since there is no other answer to give; a model that phonemizes several requires a voice from its `caps` and refuses an unservable one with the speech route's own 400. `model` is read the way the speech route reads it (`404` on an id that is not the served one). Answered by the TTS worker on the same queue as a synthesis (the same 4096-CHARACTER cap - codepoints, not bytes - and the same 503 when no speech model is served), so the speech studio can show what the model will actually say |
| `POST` | `/vad` | Silero speech spans over an uploaded clip (the control page's waveform overlay; in-handler, <=120 s, needs the in-repo `silero_vad.bin`) |
-| `GET` | `/catalog` | The curated model list with local presence, the `asr` tower row, the `tts` list (the three speech GGUFs and the two front-end packs, each `file`/`bytes`/`pack`/`present`/`path`), the `box` memory facts + the download state machine (`idle | downloading | verifying | done | failed`, byte progress) |
+| `GET` | `/catalog` | The curated model list with local presence, the `asr` tower row, the `tts` list (the two front-end packs the speech route loads, then every served speech GGUF, each `file`/`bytes`/`pack`/`present`/`path`/`needs_packs` - on a model, whether the file on disk reads the packs, true until it is here; false on a pack), the `box` memory facts + the download state machine (`idle | downloading | verifying | done | failed`, byte progress) |
| `POST` | `/catalog/download` | `{"name": }` - start one catalog download; `{"name", "tower": "vision"}` / `{"tower": "asr"}` pull a tower, `{"tower": "tts", "file": }` one file of the speech set (409 while one runs or the file exists; sha-verified, never waived) |
| `POST` | `/bench` | Loopback-only: start the benchmark, quiesced. In process by default: pp512 and tg128 on the served model, an untimed warmup then three timed reps each, one pp prefill or one tg token per tick, the text, audio and speech routes (503) and the model-switching, bake and catalog-download routes (409) holding until it finishes - in either mode; with `lcpp_bin` in the config on a source-tree daslang, the A/B child instead - our lcpp_bench then llama-bench on the same GGUF. 400 in setup mode or when the served context is shorter than pp512, 409 while a bench, a bake or a catalog download runs or streams are active, 503 while draining |
| `GET` | `/bench` | Bench state (`idle | running | done | failed`), `mode` (`inprocess` | `ab`: what a `POST` runs), live log lines, the result JSON - `ours_pp`, `ours_tg`, `threads`, `elapsed_s`, `ts`; in process also `mode`, `pp_sd`, `tg_sd`, `reps`, `model`, `gguf`, `backend` (`metal`, `gpu:resident`, or the slot's word), `kv` (the codec the rows ran), `exec` (`exe-native` | `jit` | `interpreted`), `tune` (`fat` | `tuned` | `untuned (N of M on fallback)` | `none`) and `ref_cmd` (the llama-bench line for the comparison); the A/B also `theirs_pp`, `theirs_tg`, `pp_ratio`, `tg_ratio` and `record` - and the hardware line |
diff --git a/utils/dasllama-server/control.html b/utils/dasllama-server/control.html
index aeb6da1c3a..6abd3059af 100644
--- a/utils/dasllama-server/control.html
+++ b/utils/dasllama-server/control.html
@@ -3238,10 +3238,12 @@
strip.append(b, label);
}
-// the download ladder over /catalog's `tts` list: the two front-end packs first (every speech
-// model loads them), then the models, then the enable. The rail runs one download at a time,
-// so an absent pack pair is one button and the card re-offers what is still missing.
-// False back = the catalog has no tts list to offer from, and the caller says so instead.
+// the download ladder over /catalog's `tts` list: a model on disk that can speak - a Pocket file
+// stands alone, a phoneme family reads the two front-end packs beside it - goes straight to the
+// enable; otherwise the packs first (every phoneme family loads them), then the models. The rail
+// runs one download at a time, so an absent pack pair is one button and the card re-offers what
+// is still missing. False back = the catalog has no tts list to offer from, and the caller says
+// so instead.
function ttsOfferArm(strip, dl, kept) {
const items = (catState && catState.tts) || [];
if (!items.length) return false;
@@ -3253,30 +3255,31 @@
return true;
}
const missingPacks = items.filter(i => i.pack && !i.present);
+ const models = items.filter(i => !i.pack);
+ const present = models.filter(m => m.present && (!m.needs_packs || !missingPacks.length))
+ .sort((a, b) => a.bytes - b.bytes);
+ if (present.length) {
+ ttsEnableArm(strip, label, present, kept);
+ return true;
+ }
if (missingPacks.length) {
const bytes = missingPacks.reduce((a, p) => a + p.bytes, 0);
const b = miniButton("download the front-end packs (" + fmtSize(bytes) + ")",
() => startTtsDownload(missingPacks[0].file));
- b.title = "the text normalizer and grapheme-to-phoneme tables every speech model loads";
+ b.title = "the text normalizer and grapheme-to-phoneme tables every phoneme family loads";
b.disabled = dlBusy(dl);
label.textContent = "add speech synthesis — /v1/audio/speech and the speech studio above";
strip.append(b, label);
return true;
}
- const models = items.filter(i => !i.pack);
- const present = models.filter(m => m.present).sort((a, b) => a.bytes - b.bytes);
- if (!present.length) {
- label.textContent = "the front-end packs are here — pick a speech model";
- for (const m of models) {
- const b = miniButton("download " + ttsName(m.file) + " (" + fmtSize(m.bytes) + ")",
- () => startTtsDownload(m.file));
- b.disabled = dlBusy(dl);
- strip.append(b);
- }
- strip.append(label);
- return true;
+ label.textContent = "the front-end packs are here — pick a speech model";
+ for (const m of models) {
+ const b = miniButton("download " + ttsName(m.file) + " (" + fmtSize(m.bytes) + ")",
+ () => startTtsDownload(m.file));
+ b.disabled = dlBusy(dl);
+ strip.append(b);
}
- ttsEnableArm(strip, label, present, kept);
+ strip.append(label);
return true;
}
diff --git a/utils/dasllama-server/model_catalog.das b/utils/dasllama-server/model_catalog.das
index d4a5fcbebf..70164d94f3 100644
--- a/utils/dasllama-server/model_catalog.das
+++ b/utils/dasllama-server/model_catalog.das
@@ -16,6 +16,12 @@ require daslib/jobque_boost
require daslib/strings_boost
require strings
+//! The front-end packs the speech route loads beside a phoneme family's GGUF - the pack rows the
+//! /catalog document's `tts` list carries. A companion of the served set outside this list (the
+//! browser's American-only phoneme pack) is fetchable through `catalog_download_start_tts` and
+//! rides no ladder.
+let public TTS_FRONT_END_PACKS : array <- ["tts_g2p.bin", "tts_postag.bin"]
+
//! One curated model: a commit-pinned, ungated HF file this engine serves today.
//! `vram_hint_gb` is the advertised working-set hint, not a fit check. The `vision_*`
//! fields carry the row's pinned vision tower (mmproj) when it has one — "" = text only.
@@ -341,9 +347,11 @@ def public catalog_download_tick() {
}
//! The GET /catalog document: entries with local presence (model AND vision tower), the ASR
-//! tower, the box's memory facts (`ram_gb` in GiB, `vram_mb` in MiB, 0 = unknown — the page's fit badges),
-//! plus the download state machine.
-def public catalog_state_json(dir : string; ram_gb : int = 0; vram_mb : int64 = 0l) : string {
+//! tower, the `tts` list (the two front-end packs, then every served speech model; a model row's
+//! `needs_packs` is `needs_packs(path)` on the file once it is here - the server passes the
+//! engine's family test - and true until then), the box's memory facts (`ram_gb` in GiB,
+//! `vram_mb` in MiB, 0 = unknown — the page's fit badges), plus the download state machine.
+def public catalog_state_json(dir : string; ram_gb : int; vram_mb : int64; needs_packs : function<(path : string) : bool>) : string {
var cat <- model_catalog()
var entries : array
entries |> reserve(length(cat))
@@ -367,10 +375,12 @@ def public catalog_state_json(dir : string; ram_gb : int = 0; vram_mb : int64 =
var tts_items : array
tts_items |> reserve(length(tts_set))
for (item in tts_set) {
+ continue if (item.pack && !has_value(TTS_FRONT_END_PACKS, item.entry.name))
let p = path_join(dir, item.entry.name)
let present = stat(p).is_valid
+ let packs = !item.pack && (!present || invoke(needs_packs, p))
tts_items |> push(JV((file = item.entry.name, bytes = item.entry.bytes, pack = item.pack,
- present = present, path = present ? p : "")))
+ present = present, path = present ? p : "", needs_packs = packs)))
}
var got = 0l
if (g_dl_state == "downloading") {
diff --git a/utils/dasllama-server/openai_server.das b/utils/dasllama-server/openai_server.das
index e36d99725e..413dacad7e 100644
--- a/utils/dasllama-server/openai_server.das
+++ b/utils/dasllama-server/openai_server.das
@@ -179,7 +179,6 @@ let TTS_LANE_DEFAULT = "q8"
var g_tts_lane = TTS_LANE_DEFAULT // the weight lane the worker is ASKED for; its ready event echoes the pin it took
var g_tts_voices_dir = "" // clips a cloning model adds to its voices at boot, each under its file's stem
let TTS_VOICE_CLIP_EXTENSIONS : array <- [".wav", ".flac", ".mp3", ".ogg"] // what the decode rail reads
-let TTS_FRONT_END_PACKS : array <- ["tts_g2p.bin", "tts_postag.bin"] // load_tts_model reads both from the GGUF's directory
var g_shutdown_requested = false
var g_gc_requested = false
var g_req_counter = 0l
@@ -5285,7 +5284,7 @@ class OpenAiServer : HvWebServer {
return handle_bake_start(req, resp)
}
GET("/catalog") <| @(var req : HttpRequest?; var resp : HttpResponse?) : http_status {
- return resp |> JSON(catalog_state_json(g_catalog_dir, g_box_ram_gb, gpu_weight_budget_bytes() / (1024l * 1024l)))
+ return resp |> JSON(catalog_state_json(g_catalog_dir, g_box_ram_gb, gpu_weight_budget_bytes() / (1024l * 1024l), @@tts_needs_packs))
}
POST("/catalog/download") <| @(var req : HttpRequest?; var resp : HttpResponse?) : http_status {
if (!is_loopback_req(req)) return deny_operator(resp)
diff --git a/utils/dasllama-server/test_model_catalog.das b/utils/dasllama-server/test_model_catalog.das
index 604a34dca1..ffdbb25012 100644
--- a/utils/dasllama-server/test_model_catalog.das
+++ b/utils/dasllama-server/test_model_catalog.das
@@ -92,10 +92,10 @@ def test_catalog_table(t : T?) {
packs++
}
}
- t |> equal(length(set), 12, "the set is nine models (three phoneme families, six Pocket languages) and three packs")
+ t |> equal(length(set), 14, "the set is eleven models (three phoneme families, six Pocket languages, the two small English Pocket forms) and three packs")
t |> equal(packs, 3, "three front-end packs: the full phoneme pack, its American-only twin, the tagger")
for (want in ["kitten-nano.gguf", "kitten-mini.gguf", "kokoro-82m.gguf", "tts_g2p.bin", "tts_g2p_en_us.bin", "tts_postag.bin",
- "pocket-tts-en-q8.gguf", "pocket-tts-de-q8.gguf", "pocket-tts-es-q8.gguf", "pocket-tts-it-q8.gguf", "pocket-tts-pt-q8.gguf", "pocket-tts-fr-q8.gguf"]) {
+ "pocket-tts-en-q8.gguf", "pocket-tts-de-q8.gguf", "pocket-tts-es-q8.gguf", "pocket-tts-it-q8.gguf", "pocket-tts-pt-q8.gguf", "pocket-tts-fr-q8.gguf", "pocket-tts-en-kq.gguf", "pocket-tts-en-stuart-kq.gguf"]) {
t |> success(key_exists(names, want), "the set carries {want}")
}
}
@@ -183,12 +183,18 @@ def test_download_refusals(t : T?) {
}
}
+// the family test the document is handed in place of the engine's: a file named as a Pocket
+// form reads text and needs no pack, everything else is a phoneme family
+def private needs_packs_by_name(path : string) : bool {
+ return find(base_name(path), "pocket-tts-") < 0
+}
+
[test]
def test_state_json(t : T?) {
t |> run("the /catalog document carries every entry with presence and an idle machine") @(t : T?) {
let dir = tmp_models_dir("doc")
var jerr = ""
- var doc = read_json(catalog_state_json(dir, 64, 8192l), jerr)
+ var doc = read_json(catalog_state_json(dir, 64, 8192l, @@needs_packs_by_name), jerr)
t |> success(doc != null, "state json parses: {jerr}")
if (doc != null) {
t |> equal(doc?["models_dir"] ?? "", dir)
@@ -228,7 +234,7 @@ def test_state_json(t : T?) {
}
}
var jerr = ""
- var doc = read_json(catalog_state_json(dir), jerr)
+ var doc = read_json(catalog_state_json(dir, 0, 0l, @@needs_packs_by_name), jerr)
t |> success(doc != null, "state json parses: {jerr}")
let entries = doc?["entries"]
if (entries != null && (entries.value is _array)) {
@@ -250,6 +256,51 @@ def test_state_json(t : T?) {
}
}
+[test]
+def test_state_json_tts(t : T?) {
+ t |> run("the tts list: the front-end packs the route loads, then the models, each saying whether it reads them") @(t : T?) {
+ let dir = tmp_models_dir("ttsdoc")
+ let planted = path_join(dir, "pocket-tts-en-stuart-kq.gguf")
+ fopen(planted, "wb") $(f) {
+ if (f != null) {
+ f |> fwrite("GGUF")
+ }
+ }
+ var jerr = ""
+ var doc = read_json(catalog_state_json(dir, 0, 0l, @@needs_packs_by_name), jerr)
+ t |> success(doc != null, "state json parses: {jerr}")
+ let items = doc?["tts"]
+ t |> success(items != null && (items.value is _array), "tts is an array")
+ if (items != null && (items.value is _array)) {
+ var packs : array
+ var models = 0
+ for (item in items.value as _array) {
+ let file = "{item?["file"] ?? ""}"
+ if (item?["pack"] ?? false) {
+ packs |> push(file)
+ t |> equal(item?["needs_packs"] ?? true, false, "{file}: a pack reads no pack")
+ } else {
+ models++
+ let present = item?["present"] ?? false
+ t |> equal(present, file == "pocket-tts-en-stuart-kq.gguf", "{file}: presence follows the dir")
+ t |> equal(item?["needs_packs"] ?? false, !present, "{file}: an absent model reads as needing the packs, the planted Pocket file as not")
+ }
+ }
+ t |> equal(packs, TTS_FRONT_END_PACKS, "the pack rows are the route's own list, in its order - the browser's American twin rides no ladder")
+ var served = 0
+ for (item in serve_tts_set()) {
+ served += item.pack ? 0 : 1
+ }
+ t |> equal(models, served, "every served model is a row")
+ }
+ unsafe {
+ delete doc
+ }
+ remove(planted)
+ rmdir(dir)
+ }
+}
+
[test]
def test_fixture_path_normalization(t : T?) {
t |> run("the captured catalog fixtures carry only normalized paths") @(t : T?) {
diff --git a/utils/dasllama-server/tests/fixtures/catalog_done.json b/utils/dasllama-server/tests/fixtures/catalog_done.json
index 5378d8b2e6..ac9fc3317e 100644
--- a/utils/dasllama-server/tests/fixtures/catalog_done.json
+++ b/utils/dasllama-server/tests/fixtures/catalog_done.json
@@ -1 +1 @@
-{"models_dir": "C:\\Users\\user\\.dasllama\\models", "entries": [{"is_default": true, "path": "C:\\Users\\user\\.dasllama\\models\\gemma-4-E2B-it-Q4_K_M.gguf", "sha256": "740185b21d22ceb83a11c3aa62ad5842ef32c70f6096d756bbee85a1e4ec34b8", "vision_bytes": 986833664, "display": "Gemma 4 E2B", "file": "gemma-4-E2B-it-Q4_K_M.gguf", "bytes": 3106738272, "present": true, "name": "gemma-4-e2b", "vram_hint_gb": 4, "vision_file": "mmproj-gemma-4-E2B-it-bf16.gguf", "vision_present": false, "vision_path": "", "ctx": 32768, "note": "the recommended default - fast, capable, runs everywhere"}, {"is_default": false, "path": "", "sha256": "85a896a047553e842f25297ee5b031d64ff30147d9c4af17b1e4b394cd1fab87", "vision_bytes": 991552256, "display": "Gemma 4 E4B", "file": "gemma-4-E4B-it-Q4_K_M.gguf", "bytes": 4977171584, "present": false, "name": "gemma-4-e4b", "vram_hint_gb": 6.5, "vision_file": "mmproj-gemma-4-E4B-it-BF16.gguf", "vision_present": false, "vision_path": "", "ctx": 32768, "note": "the bigger E-series - better answers, still laptop-class"}, {"is_default": false, "path": "", "sha256": "f2c28b3dc4776931ac6f879e11f203dec637ea0f14267a86ec8f6165f63f293f", "vision_bytes": 0, "display": "Gemma 4 26B-A4B", "file": "gemma-4-26B-A4B-it-UD-Q4_K_M.gguf", "bytes": 16947541728, "present": false, "name": "gemma-4-26b-a4b", "vram_hint_gb": 19, "vision_file": "", "vision_present": false, "vision_path": "", "ctx": 131072, "note": "the MoE - 26B quality at 4B active weights per token"}, {"is_default": false, "path": "", "sha256": "322e194ff79741c7baa497c240f677f54b201b0efab44ca8e50f122b39123482", "vision_bytes": 0, "display": "Qwen 3.8 27B", "file": "Qwen3.8-27B-UD-Q4_K_M.gguf", "bytes": 16464440224, "present": false, "name": "qwen3.8-27b", "vram_hint_gb": 18.5, "vision_file": "", "vision_present": false, "vision_path": "", "ctx": 262144, "note": "the newest dense Qwen - thinking model, strong at code"}, {"is_default": false, "path": "", "sha256": "054721f478bc5fa6beffb7f38eae575d45298f88cbb8d2f83ef675a727863eb1", "vision_bytes": 836180256, "display": "Qwen3 VL 4B", "file": "Qwen3VL-4B-Instruct-Q8_0.gguf", "bytes": 4280406144, "present": false, "name": "qwen3-vl-4b", "vram_hint_gb": 6, "vision_file": "mmproj-Qwen3VL-4B-Instruct-F16.gguf", "vision_present": false, "vision_path": "", "ctx": 262144, "note": "the deepstack vision Qwen - reads images through wide multi-tap rows"}, {"is_default": false, "path": "", "sha256": "c7d8b07c8d8d7a9ed1de1b8df7ac821eb4d259a224bd44310baacfaa5a473d4c", "vision_bytes": 2623983328, "display": "Qwen2.5 Omni 3B", "file": "Qwen2.5-Omni-3B-Q8_0.gguf", "bytes": 3616087360, "present": false, "name": "qwen2.5-omni-3b", "vram_hint_gb": 5.5, "vision_file": "mmproj-Qwen2.5-Omni-3B-f16.gguf", "vision_present": false, "vision_path": "", "ctx": 32768, "note": "the window-ViT omni - compact vision chat on the Metal tower"}, {"is_default": false, "path": "", "sha256": "0b21525e972670ed59e1812e170b27c26355381f0656ecc4e25617ece7dac58b", "vision_bytes": 0, "display": "Qwen 3.6 35B-A3B", "file": "Qwen3.6-35B-A3B-MTP-UD-Q4_K_M.gguf", "bytes": 22663387424, "present": false, "name": "qwen3.6-35b-a3b", "vram_hint_gb": 24, "vision_file": "", "vision_present": false, "vision_path": "", "ctx": 262144, "note": "the Qwen MoE - 3B active weights, MTP-ready"}], "asr": {"file": "ggml-parakeet-tdt-0.6b-v3-f32.bin", "bytes": 2508463079, "present": false, "path": ""}, "tts": [{"file": "kitten-nano.gguf", "bytes": 59331456, "pack": false, "present": false, "path": ""}, {"file": "tts_g2p.bin", "bytes": 14011554, "pack": true, "present": false, "path": ""}, {"file": "tts_postag.bin", "bytes": 12566510, "pack": true, "present": false, "path": ""}, {"file": "kitten-mini.gguf", "bytes": 295975008, "pack": false, "present": false, "path": ""}, {"file": "kokoro-82m.gguf", "bytes": 352965024, "pack": false, "present": false, "path": ""}], "box": {"ram_gb": 64, "vram_mb": 0}, "download": {"state": "done", "name": "gemma-4-e2b", "got": 0, "total": 3106738272, "error": ""}}
\ No newline at end of file
+{"models_dir": "C:\\Users\\user\\.dasllama\\models", "entries": [{"is_default": true, "path": "C:\\Users\\user\\.dasllama\\models\\gemma-4-E2B-it-Q4_K_M.gguf", "sha256": "740185b21d22ceb83a11c3aa62ad5842ef32c70f6096d756bbee85a1e4ec34b8", "vision_bytes": 986833664, "display": "Gemma 4 E2B", "file": "gemma-4-E2B-it-Q4_K_M.gguf", "bytes": 3106738272, "present": true, "name": "gemma-4-e2b", "vram_hint_gb": 4, "vision_file": "mmproj-gemma-4-E2B-it-bf16.gguf", "vision_present": false, "vision_path": "", "ctx": 32768, "note": "the recommended default - fast, capable, runs everywhere"}, {"is_default": false, "path": "", "sha256": "85a896a047553e842f25297ee5b031d64ff30147d9c4af17b1e4b394cd1fab87", "vision_bytes": 991552256, "display": "Gemma 4 E4B", "file": "gemma-4-E4B-it-Q4_K_M.gguf", "bytes": 4977171584, "present": false, "name": "gemma-4-e4b", "vram_hint_gb": 6.5, "vision_file": "mmproj-gemma-4-E4B-it-BF16.gguf", "vision_present": false, "vision_path": "", "ctx": 32768, "note": "the bigger E-series - better answers, still laptop-class"}, {"is_default": false, "path": "", "sha256": "f2c28b3dc4776931ac6f879e11f203dec637ea0f14267a86ec8f6165f63f293f", "vision_bytes": 0, "display": "Gemma 4 26B-A4B", "file": "gemma-4-26B-A4B-it-UD-Q4_K_M.gguf", "bytes": 16947541728, "present": false, "name": "gemma-4-26b-a4b", "vram_hint_gb": 19, "vision_file": "", "vision_present": false, "vision_path": "", "ctx": 131072, "note": "the MoE - 26B quality at 4B active weights per token"}, {"is_default": false, "path": "", "sha256": "322e194ff79741c7baa497c240f677f54b201b0efab44ca8e50f122b39123482", "vision_bytes": 0, "display": "Qwen 3.8 27B", "file": "Qwen3.8-27B-UD-Q4_K_M.gguf", "bytes": 16464440224, "present": false, "name": "qwen3.8-27b", "vram_hint_gb": 18.5, "vision_file": "", "vision_present": false, "vision_path": "", "ctx": 262144, "note": "the newest dense Qwen - thinking model, strong at code"}, {"is_default": false, "path": "", "sha256": "054721f478bc5fa6beffb7f38eae575d45298f88cbb8d2f83ef675a727863eb1", "vision_bytes": 836180256, "display": "Qwen3 VL 4B", "file": "Qwen3VL-4B-Instruct-Q8_0.gguf", "bytes": 4280406144, "present": false, "name": "qwen3-vl-4b", "vram_hint_gb": 6, "vision_file": "mmproj-Qwen3VL-4B-Instruct-F16.gguf", "vision_present": false, "vision_path": "", "ctx": 262144, "note": "the deepstack vision Qwen - reads images through wide multi-tap rows"}, {"is_default": false, "path": "", "sha256": "c7d8b07c8d8d7a9ed1de1b8df7ac821eb4d259a224bd44310baacfaa5a473d4c", "vision_bytes": 2623983328, "display": "Qwen2.5 Omni 3B", "file": "Qwen2.5-Omni-3B-Q8_0.gguf", "bytes": 3616087360, "present": false, "name": "qwen2.5-omni-3b", "vram_hint_gb": 5.5, "vision_file": "mmproj-Qwen2.5-Omni-3B-f16.gguf", "vision_present": false, "vision_path": "", "ctx": 32768, "note": "the window-ViT omni - compact vision chat on the Metal tower"}, {"is_default": false, "path": "", "sha256": "0b21525e972670ed59e1812e170b27c26355381f0656ecc4e25617ece7dac58b", "vision_bytes": 0, "display": "Qwen 3.6 35B-A3B", "file": "Qwen3.6-35B-A3B-MTP-UD-Q4_K_M.gguf", "bytes": 22663387424, "present": false, "name": "qwen3.6-35b-a3b", "vram_hint_gb": 24, "vision_file": "", "vision_present": false, "vision_path": "", "ctx": 262144, "note": "the Qwen MoE - 3B active weights, MTP-ready"}], "asr": {"file": "ggml-parakeet-tdt-0.6b-v3-f32.bin", "bytes": 2508463079, "present": false, "path": ""}, "tts": [{"file": "kitten-nano.gguf", "bytes": 59331456, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "tts_g2p.bin", "bytes": 14011554, "pack": true, "present": false, "path": "", "needs_packs": false}, {"file": "tts_postag.bin", "bytes": 12566510, "pack": true, "present": false, "path": "", "needs_packs": false}, {"file": "kitten-mini.gguf", "bytes": 295975008, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "kokoro-82m.gguf", "bytes": 352965024, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "pocket-tts-en-q8.gguf", "bytes": 152613664, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "pocket-tts-de-q8.gguf", "bytes": 134667200, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "pocket-tts-es-q8.gguf", "bytes": 134624480, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "pocket-tts-it-q8.gguf", "bytes": 134415072, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "pocket-tts-pt-q8.gguf", "bytes": 134667488, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "pocket-tts-fr-q8.gguf", "bytes": 375793696, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "pocket-tts-en-kq.gguf", "bytes": 74970016, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "pocket-tts-en-stuart-kq.gguf", "bytes": 65107520, "pack": false, "present": false, "path": "", "needs_packs": true}], "box": {"ram_gb": 64, "vram_mb": 0}, "download": {"state": "done", "name": "gemma-4-e2b", "got": 0, "total": 3106738272, "error": ""}}
\ No newline at end of file
diff --git a/utils/dasllama-server/tests/fixtures/catalog_downloading.json b/utils/dasllama-server/tests/fixtures/catalog_downloading.json
index e34da76038..e820e54050 100644
--- a/utils/dasllama-server/tests/fixtures/catalog_downloading.json
+++ b/utils/dasllama-server/tests/fixtures/catalog_downloading.json
@@ -1 +1 @@
-{"models_dir": "C:\\Users\\user\\.dasllama\\models", "entries": [{"is_default": true, "path": "", "sha256": "740185b21d22ceb83a11c3aa62ad5842ef32c70f6096d756bbee85a1e4ec34b8", "vision_bytes": 986833664, "display": "Gemma 4 E2B", "file": "gemma-4-E2B-it-Q4_K_M.gguf", "bytes": 3106738272, "present": false, "name": "gemma-4-e2b", "vram_hint_gb": 4, "vision_file": "mmproj-gemma-4-E2B-it-bf16.gguf", "vision_present": false, "vision_path": "", "ctx": 32768, "note": "the recommended default - fast, capable, runs everywhere"}, {"is_default": false, "path": "", "sha256": "85a896a047553e842f25297ee5b031d64ff30147d9c4af17b1e4b394cd1fab87", "vision_bytes": 991552256, "display": "Gemma 4 E4B", "file": "gemma-4-E4B-it-Q4_K_M.gguf", "bytes": 4977171584, "present": false, "name": "gemma-4-e4b", "vram_hint_gb": 6.5, "vision_file": "mmproj-gemma-4-E4B-it-BF16.gguf", "vision_present": false, "vision_path": "", "ctx": 32768, "note": "the bigger E-series - better answers, still laptop-class"}, {"is_default": false, "path": "", "sha256": "f2c28b3dc4776931ac6f879e11f203dec637ea0f14267a86ec8f6165f63f293f", "vision_bytes": 0, "display": "Gemma 4 26B-A4B", "file": "gemma-4-26B-A4B-it-UD-Q4_K_M.gguf", "bytes": 16947541728, "present": false, "name": "gemma-4-26b-a4b", "vram_hint_gb": 19, "vision_file": "", "vision_present": false, "vision_path": "", "ctx": 131072, "note": "the MoE - 26B quality at 4B active weights per token"}, {"is_default": false, "path": "", "sha256": "322e194ff79741c7baa497c240f677f54b201b0efab44ca8e50f122b39123482", "vision_bytes": 0, "display": "Qwen 3.8 27B", "file": "Qwen3.8-27B-UD-Q4_K_M.gguf", "bytes": 16464440224, "present": false, "name": "qwen3.8-27b", "vram_hint_gb": 18.5, "vision_file": "", "vision_present": false, "vision_path": "", "ctx": 262144, "note": "the newest dense Qwen - thinking model, strong at code"}, {"is_default": false, "path": "", "sha256": "054721f478bc5fa6beffb7f38eae575d45298f88cbb8d2f83ef675a727863eb1", "vision_bytes": 836180256, "display": "Qwen3 VL 4B", "file": "Qwen3VL-4B-Instruct-Q8_0.gguf", "bytes": 4280406144, "present": false, "name": "qwen3-vl-4b", "vram_hint_gb": 6, "vision_file": "mmproj-Qwen3VL-4B-Instruct-F16.gguf", "vision_present": false, "vision_path": "", "ctx": 262144, "note": "the deepstack vision Qwen - reads images through wide multi-tap rows"}, {"is_default": false, "path": "", "sha256": "c7d8b07c8d8d7a9ed1de1b8df7ac821eb4d259a224bd44310baacfaa5a473d4c", "vision_bytes": 2623983328, "display": "Qwen2.5 Omni 3B", "file": "Qwen2.5-Omni-3B-Q8_0.gguf", "bytes": 3616087360, "present": false, "name": "qwen2.5-omni-3b", "vram_hint_gb": 5.5, "vision_file": "mmproj-Qwen2.5-Omni-3B-f16.gguf", "vision_present": false, "vision_path": "", "ctx": 32768, "note": "the window-ViT omni - compact vision chat on the Metal tower"}, {"is_default": false, "path": "", "sha256": "0b21525e972670ed59e1812e170b27c26355381f0656ecc4e25617ece7dac58b", "vision_bytes": 0, "display": "Qwen 3.6 35B-A3B", "file": "Qwen3.6-35B-A3B-MTP-UD-Q4_K_M.gguf", "bytes": 22663387424, "present": false, "name": "qwen3.6-35b-a3b", "vram_hint_gb": 24, "vision_file": "", "vision_present": false, "vision_path": "", "ctx": 262144, "note": "the Qwen MoE - 3B active weights, MTP-ready"}], "asr": {"file": "ggml-parakeet-tdt-0.6b-v3-f32.bin", "bytes": 2508463079, "present": false, "path": ""}, "tts": [{"file": "kitten-nano.gguf", "bytes": 59331456, "pack": false, "present": false, "path": ""}, {"file": "tts_g2p.bin", "bytes": 14011554, "pack": true, "present": false, "path": ""}, {"file": "tts_postag.bin", "bytes": 12566510, "pack": true, "present": false, "path": ""}, {"file": "kitten-mini.gguf", "bytes": 295975008, "pack": false, "present": false, "path": ""}, {"file": "kokoro-82m.gguf", "bytes": 352965024, "pack": false, "present": false, "path": ""}], "box": {"ram_gb": 64, "vram_mb": 0}, "download": {"state": "downloading", "name": "gemma-4-e2b", "got": 157736960, "total": 3106738272, "error": ""}}
\ No newline at end of file
+{"models_dir": "C:\\Users\\user\\.dasllama\\models", "entries": [{"is_default": true, "path": "", "sha256": "740185b21d22ceb83a11c3aa62ad5842ef32c70f6096d756bbee85a1e4ec34b8", "vision_bytes": 986833664, "display": "Gemma 4 E2B", "file": "gemma-4-E2B-it-Q4_K_M.gguf", "bytes": 3106738272, "present": false, "name": "gemma-4-e2b", "vram_hint_gb": 4, "vision_file": "mmproj-gemma-4-E2B-it-bf16.gguf", "vision_present": false, "vision_path": "", "ctx": 32768, "note": "the recommended default - fast, capable, runs everywhere"}, {"is_default": false, "path": "", "sha256": "85a896a047553e842f25297ee5b031d64ff30147d9c4af17b1e4b394cd1fab87", "vision_bytes": 991552256, "display": "Gemma 4 E4B", "file": "gemma-4-E4B-it-Q4_K_M.gguf", "bytes": 4977171584, "present": false, "name": "gemma-4-e4b", "vram_hint_gb": 6.5, "vision_file": "mmproj-gemma-4-E4B-it-BF16.gguf", "vision_present": false, "vision_path": "", "ctx": 32768, "note": "the bigger E-series - better answers, still laptop-class"}, {"is_default": false, "path": "", "sha256": "f2c28b3dc4776931ac6f879e11f203dec637ea0f14267a86ec8f6165f63f293f", "vision_bytes": 0, "display": "Gemma 4 26B-A4B", "file": "gemma-4-26B-A4B-it-UD-Q4_K_M.gguf", "bytes": 16947541728, "present": false, "name": "gemma-4-26b-a4b", "vram_hint_gb": 19, "vision_file": "", "vision_present": false, "vision_path": "", "ctx": 131072, "note": "the MoE - 26B quality at 4B active weights per token"}, {"is_default": false, "path": "", "sha256": "322e194ff79741c7baa497c240f677f54b201b0efab44ca8e50f122b39123482", "vision_bytes": 0, "display": "Qwen 3.8 27B", "file": "Qwen3.8-27B-UD-Q4_K_M.gguf", "bytes": 16464440224, "present": false, "name": "qwen3.8-27b", "vram_hint_gb": 18.5, "vision_file": "", "vision_present": false, "vision_path": "", "ctx": 262144, "note": "the newest dense Qwen - thinking model, strong at code"}, {"is_default": false, "path": "", "sha256": "054721f478bc5fa6beffb7f38eae575d45298f88cbb8d2f83ef675a727863eb1", "vision_bytes": 836180256, "display": "Qwen3 VL 4B", "file": "Qwen3VL-4B-Instruct-Q8_0.gguf", "bytes": 4280406144, "present": false, "name": "qwen3-vl-4b", "vram_hint_gb": 6, "vision_file": "mmproj-Qwen3VL-4B-Instruct-F16.gguf", "vision_present": false, "vision_path": "", "ctx": 262144, "note": "the deepstack vision Qwen - reads images through wide multi-tap rows"}, {"is_default": false, "path": "", "sha256": "c7d8b07c8d8d7a9ed1de1b8df7ac821eb4d259a224bd44310baacfaa5a473d4c", "vision_bytes": 2623983328, "display": "Qwen2.5 Omni 3B", "file": "Qwen2.5-Omni-3B-Q8_0.gguf", "bytes": 3616087360, "present": false, "name": "qwen2.5-omni-3b", "vram_hint_gb": 5.5, "vision_file": "mmproj-Qwen2.5-Omni-3B-f16.gguf", "vision_present": false, "vision_path": "", "ctx": 32768, "note": "the window-ViT omni - compact vision chat on the Metal tower"}, {"is_default": false, "path": "", "sha256": "0b21525e972670ed59e1812e170b27c26355381f0656ecc4e25617ece7dac58b", "vision_bytes": 0, "display": "Qwen 3.6 35B-A3B", "file": "Qwen3.6-35B-A3B-MTP-UD-Q4_K_M.gguf", "bytes": 22663387424, "present": false, "name": "qwen3.6-35b-a3b", "vram_hint_gb": 24, "vision_file": "", "vision_present": false, "vision_path": "", "ctx": 262144, "note": "the Qwen MoE - 3B active weights, MTP-ready"}], "asr": {"file": "ggml-parakeet-tdt-0.6b-v3-f32.bin", "bytes": 2508463079, "present": false, "path": ""}, "tts": [{"file": "kitten-nano.gguf", "bytes": 59331456, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "tts_g2p.bin", "bytes": 14011554, "pack": true, "present": false, "path": "", "needs_packs": false}, {"file": "tts_postag.bin", "bytes": 12566510, "pack": true, "present": false, "path": "", "needs_packs": false}, {"file": "kitten-mini.gguf", "bytes": 295975008, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "kokoro-82m.gguf", "bytes": 352965024, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "pocket-tts-en-q8.gguf", "bytes": 152613664, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "pocket-tts-de-q8.gguf", "bytes": 134667200, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "pocket-tts-es-q8.gguf", "bytes": 134624480, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "pocket-tts-it-q8.gguf", "bytes": 134415072, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "pocket-tts-pt-q8.gguf", "bytes": 134667488, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "pocket-tts-fr-q8.gguf", "bytes": 375793696, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "pocket-tts-en-kq.gguf", "bytes": 74970016, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "pocket-tts-en-stuart-kq.gguf", "bytes": 65107520, "pack": false, "present": false, "path": "", "needs_packs": true}], "box": {"ram_gb": 64, "vram_mb": 0}, "download": {"state": "downloading", "name": "gemma-4-e2b", "got": 160342016, "total": 3106738272, "error": ""}}
\ No newline at end of file
diff --git a/utils/dasllama-server/tests/fixtures/catalog_empty.json b/utils/dasllama-server/tests/fixtures/catalog_empty.json
index 10f183648c..4b06409c8e 100644
--- a/utils/dasllama-server/tests/fixtures/catalog_empty.json
+++ b/utils/dasllama-server/tests/fixtures/catalog_empty.json
@@ -1 +1 @@
-{"models_dir": "C:\\Users\\user\\.dasllama\\models", "entries": [{"is_default": true, "path": "", "sha256": "740185b21d22ceb83a11c3aa62ad5842ef32c70f6096d756bbee85a1e4ec34b8", "vision_bytes": 986833664, "display": "Gemma 4 E2B", "file": "gemma-4-E2B-it-Q4_K_M.gguf", "bytes": 3106738272, "present": false, "name": "gemma-4-e2b", "vram_hint_gb": 4, "vision_file": "mmproj-gemma-4-E2B-it-bf16.gguf", "vision_present": false, "vision_path": "", "ctx": 32768, "note": "the recommended default - fast, capable, runs everywhere"}, {"is_default": false, "path": "", "sha256": "85a896a047553e842f25297ee5b031d64ff30147d9c4af17b1e4b394cd1fab87", "vision_bytes": 991552256, "display": "Gemma 4 E4B", "file": "gemma-4-E4B-it-Q4_K_M.gguf", "bytes": 4977171584, "present": false, "name": "gemma-4-e4b", "vram_hint_gb": 6.5, "vision_file": "mmproj-gemma-4-E4B-it-BF16.gguf", "vision_present": false, "vision_path": "", "ctx": 32768, "note": "the bigger E-series - better answers, still laptop-class"}, {"is_default": false, "path": "", "sha256": "f2c28b3dc4776931ac6f879e11f203dec637ea0f14267a86ec8f6165f63f293f", "vision_bytes": 0, "display": "Gemma 4 26B-A4B", "file": "gemma-4-26B-A4B-it-UD-Q4_K_M.gguf", "bytes": 16947541728, "present": false, "name": "gemma-4-26b-a4b", "vram_hint_gb": 19, "vision_file": "", "vision_present": false, "vision_path": "", "ctx": 131072, "note": "the MoE - 26B quality at 4B active weights per token"}, {"is_default": false, "path": "", "sha256": "322e194ff79741c7baa497c240f677f54b201b0efab44ca8e50f122b39123482", "vision_bytes": 0, "display": "Qwen 3.8 27B", "file": "Qwen3.8-27B-UD-Q4_K_M.gguf", "bytes": 16464440224, "present": false, "name": "qwen3.8-27b", "vram_hint_gb": 18.5, "vision_file": "", "vision_present": false, "vision_path": "", "ctx": 262144, "note": "the newest dense Qwen - thinking model, strong at code"}, {"is_default": false, "path": "", "sha256": "054721f478bc5fa6beffb7f38eae575d45298f88cbb8d2f83ef675a727863eb1", "vision_bytes": 836180256, "display": "Qwen3 VL 4B", "file": "Qwen3VL-4B-Instruct-Q8_0.gguf", "bytes": 4280406144, "present": false, "name": "qwen3-vl-4b", "vram_hint_gb": 6, "vision_file": "mmproj-Qwen3VL-4B-Instruct-F16.gguf", "vision_present": false, "vision_path": "", "ctx": 262144, "note": "the deepstack vision Qwen - reads images through wide multi-tap rows"}, {"is_default": false, "path": "", "sha256": "c7d8b07c8d8d7a9ed1de1b8df7ac821eb4d259a224bd44310baacfaa5a473d4c", "vision_bytes": 2623983328, "display": "Qwen2.5 Omni 3B", "file": "Qwen2.5-Omni-3B-Q8_0.gguf", "bytes": 3616087360, "present": false, "name": "qwen2.5-omni-3b", "vram_hint_gb": 5.5, "vision_file": "mmproj-Qwen2.5-Omni-3B-f16.gguf", "vision_present": false, "vision_path": "", "ctx": 32768, "note": "the window-ViT omni - compact vision chat on the Metal tower"}, {"is_default": false, "path": "", "sha256": "0b21525e972670ed59e1812e170b27c26355381f0656ecc4e25617ece7dac58b", "vision_bytes": 0, "display": "Qwen 3.6 35B-A3B", "file": "Qwen3.6-35B-A3B-MTP-UD-Q4_K_M.gguf", "bytes": 22663387424, "present": false, "name": "qwen3.6-35b-a3b", "vram_hint_gb": 24, "vision_file": "", "vision_present": false, "vision_path": "", "ctx": 262144, "note": "the Qwen MoE - 3B active weights, MTP-ready"}], "asr": {"file": "ggml-parakeet-tdt-0.6b-v3-f32.bin", "bytes": 2508463079, "present": false, "path": ""}, "tts": [{"file": "kitten-nano.gguf", "bytes": 59331456, "pack": false, "present": false, "path": ""}, {"file": "tts_g2p.bin", "bytes": 14011554, "pack": true, "present": false, "path": ""}, {"file": "tts_postag.bin", "bytes": 12566510, "pack": true, "present": false, "path": ""}, {"file": "kitten-mini.gguf", "bytes": 295975008, "pack": false, "present": false, "path": ""}, {"file": "kokoro-82m.gguf", "bytes": 352965024, "pack": false, "present": false, "path": ""}], "box": {"ram_gb": 64, "vram_mb": 0}, "download": {"state": "idle", "name": "", "got": 0, "total": 0, "error": ""}}
\ No newline at end of file
+{"models_dir": "C:\\Users\\user\\.dasllama\\models", "entries": [{"is_default": true, "path": "", "sha256": "740185b21d22ceb83a11c3aa62ad5842ef32c70f6096d756bbee85a1e4ec34b8", "vision_bytes": 986833664, "display": "Gemma 4 E2B", "file": "gemma-4-E2B-it-Q4_K_M.gguf", "bytes": 3106738272, "present": false, "name": "gemma-4-e2b", "vram_hint_gb": 4, "vision_file": "mmproj-gemma-4-E2B-it-bf16.gguf", "vision_present": false, "vision_path": "", "ctx": 32768, "note": "the recommended default - fast, capable, runs everywhere"}, {"is_default": false, "path": "", "sha256": "85a896a047553e842f25297ee5b031d64ff30147d9c4af17b1e4b394cd1fab87", "vision_bytes": 991552256, "display": "Gemma 4 E4B", "file": "gemma-4-E4B-it-Q4_K_M.gguf", "bytes": 4977171584, "present": false, "name": "gemma-4-e4b", "vram_hint_gb": 6.5, "vision_file": "mmproj-gemma-4-E4B-it-BF16.gguf", "vision_present": false, "vision_path": "", "ctx": 32768, "note": "the bigger E-series - better answers, still laptop-class"}, {"is_default": false, "path": "", "sha256": "f2c28b3dc4776931ac6f879e11f203dec637ea0f14267a86ec8f6165f63f293f", "vision_bytes": 0, "display": "Gemma 4 26B-A4B", "file": "gemma-4-26B-A4B-it-UD-Q4_K_M.gguf", "bytes": 16947541728, "present": false, "name": "gemma-4-26b-a4b", "vram_hint_gb": 19, "vision_file": "", "vision_present": false, "vision_path": "", "ctx": 131072, "note": "the MoE - 26B quality at 4B active weights per token"}, {"is_default": false, "path": "", "sha256": "322e194ff79741c7baa497c240f677f54b201b0efab44ca8e50f122b39123482", "vision_bytes": 0, "display": "Qwen 3.8 27B", "file": "Qwen3.8-27B-UD-Q4_K_M.gguf", "bytes": 16464440224, "present": false, "name": "qwen3.8-27b", "vram_hint_gb": 18.5, "vision_file": "", "vision_present": false, "vision_path": "", "ctx": 262144, "note": "the newest dense Qwen - thinking model, strong at code"}, {"is_default": false, "path": "", "sha256": "054721f478bc5fa6beffb7f38eae575d45298f88cbb8d2f83ef675a727863eb1", "vision_bytes": 836180256, "display": "Qwen3 VL 4B", "file": "Qwen3VL-4B-Instruct-Q8_0.gguf", "bytes": 4280406144, "present": false, "name": "qwen3-vl-4b", "vram_hint_gb": 6, "vision_file": "mmproj-Qwen3VL-4B-Instruct-F16.gguf", "vision_present": false, "vision_path": "", "ctx": 262144, "note": "the deepstack vision Qwen - reads images through wide multi-tap rows"}, {"is_default": false, "path": "", "sha256": "c7d8b07c8d8d7a9ed1de1b8df7ac821eb4d259a224bd44310baacfaa5a473d4c", "vision_bytes": 2623983328, "display": "Qwen2.5 Omni 3B", "file": "Qwen2.5-Omni-3B-Q8_0.gguf", "bytes": 3616087360, "present": false, "name": "qwen2.5-omni-3b", "vram_hint_gb": 5.5, "vision_file": "mmproj-Qwen2.5-Omni-3B-f16.gguf", "vision_present": false, "vision_path": "", "ctx": 32768, "note": "the window-ViT omni - compact vision chat on the Metal tower"}, {"is_default": false, "path": "", "sha256": "0b21525e972670ed59e1812e170b27c26355381f0656ecc4e25617ece7dac58b", "vision_bytes": 0, "display": "Qwen 3.6 35B-A3B", "file": "Qwen3.6-35B-A3B-MTP-UD-Q4_K_M.gguf", "bytes": 22663387424, "present": false, "name": "qwen3.6-35b-a3b", "vram_hint_gb": 24, "vision_file": "", "vision_present": false, "vision_path": "", "ctx": 262144, "note": "the Qwen MoE - 3B active weights, MTP-ready"}], "asr": {"file": "ggml-parakeet-tdt-0.6b-v3-f32.bin", "bytes": 2508463079, "present": false, "path": ""}, "tts": [{"file": "kitten-nano.gguf", "bytes": 59331456, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "tts_g2p.bin", "bytes": 14011554, "pack": true, "present": false, "path": "", "needs_packs": false}, {"file": "tts_postag.bin", "bytes": 12566510, "pack": true, "present": false, "path": "", "needs_packs": false}, {"file": "kitten-mini.gguf", "bytes": 295975008, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "kokoro-82m.gguf", "bytes": 352965024, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "pocket-tts-en-q8.gguf", "bytes": 152613664, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "pocket-tts-de-q8.gguf", "bytes": 134667200, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "pocket-tts-es-q8.gguf", "bytes": 134624480, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "pocket-tts-it-q8.gguf", "bytes": 134415072, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "pocket-tts-pt-q8.gguf", "bytes": 134667488, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "pocket-tts-fr-q8.gguf", "bytes": 375793696, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "pocket-tts-en-kq.gguf", "bytes": 74970016, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "pocket-tts-en-stuart-kq.gguf", "bytes": 65107520, "pack": false, "present": false, "path": "", "needs_packs": true}], "box": {"ram_gb": 64, "vram_mb": 0}, "download": {"state": "idle", "name": "", "got": 0, "total": 0, "error": ""}}
\ No newline at end of file
diff --git a/utils/dasllama-server/tests/fixtures/catalog_idle.json b/utils/dasllama-server/tests/fixtures/catalog_idle.json
index 7af982ab03..d0e11c62a6 100644
--- a/utils/dasllama-server/tests/fixtures/catalog_idle.json
+++ b/utils/dasllama-server/tests/fixtures/catalog_idle.json
@@ -1 +1 @@
-{"models_dir": "C:\\Users\\user\\.dasllama\\models", "entries": [{"is_default": true, "path": "C:\\Users\\user\\.dasllama\\models\\gemma-4-E2B-it-Q4_K_M.gguf", "sha256": "740185b21d22ceb83a11c3aa62ad5842ef32c70f6096d756bbee85a1e4ec34b8", "vision_bytes": 986833664, "display": "Gemma 4 E2B", "file": "gemma-4-E2B-it-Q4_K_M.gguf", "bytes": 3106738272, "present": true, "name": "gemma-4-e2b", "vram_hint_gb": 4, "vision_file": "mmproj-gemma-4-E2B-it-bf16.gguf", "vision_present": true, "vision_path": "C:\\Users\\user\\.dasllama\\models\\mmproj-gemma-4-E2B-it-bf16.gguf", "ctx": 32768, "note": "the recommended default - fast, capable, runs everywhere"}, {"is_default": false, "path": "", "sha256": "85a896a047553e842f25297ee5b031d64ff30147d9c4af17b1e4b394cd1fab87", "vision_bytes": 991552256, "display": "Gemma 4 E4B", "file": "gemma-4-E4B-it-Q4_K_M.gguf", "bytes": 4977171584, "present": false, "name": "gemma-4-e4b", "vram_hint_gb": 6.5, "vision_file": "mmproj-gemma-4-E4B-it-BF16.gguf", "vision_present": true, "vision_path": "C:\\Users\\user\\.dasllama\\models\\mmproj-gemma-4-E4B-it-BF16.gguf", "ctx": 32768, "note": "the bigger E-series - better answers, still laptop-class"}, {"is_default": false, "path": "", "sha256": "f2c28b3dc4776931ac6f879e11f203dec637ea0f14267a86ec8f6165f63f293f", "vision_bytes": 0, "display": "Gemma 4 26B-A4B", "file": "gemma-4-26B-A4B-it-UD-Q4_K_M.gguf", "bytes": 16947541728, "present": false, "name": "gemma-4-26b-a4b", "vram_hint_gb": 19, "vision_file": "", "vision_present": false, "vision_path": "", "ctx": 131072, "note": "the MoE - 26B quality at 4B active weights per token"}, {"is_default": false, "path": "", "sha256": "322e194ff79741c7baa497c240f677f54b201b0efab44ca8e50f122b39123482", "vision_bytes": 0, "display": "Qwen 3.8 27B", "file": "Qwen3.8-27B-UD-Q4_K_M.gguf", "bytes": 16464440224, "present": false, "name": "qwen3.8-27b", "vram_hint_gb": 18.5, "vision_file": "", "vision_present": false, "vision_path": "", "ctx": 262144, "note": "the newest dense Qwen - thinking model, strong at code"}, {"is_default": false, "path": "C:\\Users\\user\\.dasllama\\models\\Qwen3VL-4B-Instruct-Q8_0.gguf", "sha256": "054721f478bc5fa6beffb7f38eae575d45298f88cbb8d2f83ef675a727863eb1", "vision_bytes": 836180256, "display": "Qwen3 VL 4B", "file": "Qwen3VL-4B-Instruct-Q8_0.gguf", "bytes": 4280406144, "present": true, "name": "qwen3-vl-4b", "vram_hint_gb": 6, "vision_file": "mmproj-Qwen3VL-4B-Instruct-F16.gguf", "vision_present": true, "vision_path": "C:\\Users\\user\\.dasllama\\models\\mmproj-Qwen3VL-4B-Instruct-F16.gguf", "ctx": 262144, "note": "the deepstack vision Qwen - reads images through wide multi-tap rows"}, {"is_default": false, "path": "C:\\Users\\user\\.dasllama\\models\\Qwen2.5-Omni-3B-Q8_0.gguf", "sha256": "c7d8b07c8d8d7a9ed1de1b8df7ac821eb4d259a224bd44310baacfaa5a473d4c", "vision_bytes": 2623983328, "display": "Qwen2.5 Omni 3B", "file": "Qwen2.5-Omni-3B-Q8_0.gguf", "bytes": 3616087360, "present": true, "name": "qwen2.5-omni-3b", "vram_hint_gb": 5.5, "vision_file": "mmproj-Qwen2.5-Omni-3B-f16.gguf", "vision_present": true, "vision_path": "C:\\Users\\user\\.dasllama\\models\\mmproj-Qwen2.5-Omni-3B-f16.gguf", "ctx": 32768, "note": "the window-ViT omni - compact vision chat on the Metal tower"}, {"is_default": false, "path": "C:\\Users\\user\\.dasllama\\models\\Qwen3.6-35B-A3B-MTP-UD-Q4_K_M.gguf", "sha256": "0b21525e972670ed59e1812e170b27c26355381f0656ecc4e25617ece7dac58b", "vision_bytes": 0, "display": "Qwen 3.6 35B-A3B", "file": "Qwen3.6-35B-A3B-MTP-UD-Q4_K_M.gguf", "bytes": 22663387424, "present": true, "name": "qwen3.6-35b-a3b", "vram_hint_gb": 24, "vision_file": "", "vision_present": false, "vision_path": "", "ctx": 262144, "note": "the Qwen MoE - 3B active weights, MTP-ready"}], "asr": {"file": "ggml-parakeet-tdt-0.6b-v3-f32.bin", "bytes": 2508463079, "present": false, "path": ""}, "tts": [{"file": "kitten-nano.gguf", "bytes": 59331456, "pack": false, "present": true, "path": "C:\\Users\\user\\.dasllama\\models\\kitten-nano.gguf"}, {"file": "tts_g2p.bin", "bytes": 14011554, "pack": true, "present": true, "path": "C:\\Users\\user\\.dasllama\\models\\tts_g2p.bin"}, {"file": "tts_postag.bin", "bytes": 12566510, "pack": true, "present": true, "path": "C:\\Users\\user\\.dasllama\\models\\tts_postag.bin"}, {"file": "kitten-mini.gguf", "bytes": 295975008, "pack": false, "present": false, "path": ""}, {"file": "kokoro-82m.gguf", "bytes": 352965024, "pack": false, "present": false, "path": ""}], "box": {"ram_gb": 64, "vram_mb": 0}, "download": {"state": "idle", "name": "", "got": 0, "total": 0, "error": ""}}
\ No newline at end of file
+{"models_dir": "C:\\Users\\user\\.dasllama\\models", "entries": [{"is_default": true, "path": "C:\\Users\\user\\.dasllama\\models\\gemma-4-E2B-it-Q4_K_M.gguf", "sha256": "740185b21d22ceb83a11c3aa62ad5842ef32c70f6096d756bbee85a1e4ec34b8", "vision_bytes": 986833664, "display": "Gemma 4 E2B", "file": "gemma-4-E2B-it-Q4_K_M.gguf", "bytes": 3106738272, "present": true, "name": "gemma-4-e2b", "vram_hint_gb": 4, "vision_file": "mmproj-gemma-4-E2B-it-bf16.gguf", "vision_present": true, "vision_path": "C:\\Users\\user\\.dasllama\\models\\mmproj-gemma-4-E2B-it-bf16.gguf", "ctx": 32768, "note": "the recommended default - fast, capable, runs everywhere"}, {"is_default": false, "path": "", "sha256": "85a896a047553e842f25297ee5b031d64ff30147d9c4af17b1e4b394cd1fab87", "vision_bytes": 991552256, "display": "Gemma 4 E4B", "file": "gemma-4-E4B-it-Q4_K_M.gguf", "bytes": 4977171584, "present": false, "name": "gemma-4-e4b", "vram_hint_gb": 6.5, "vision_file": "mmproj-gemma-4-E4B-it-BF16.gguf", "vision_present": true, "vision_path": "C:\\Users\\user\\.dasllama\\models\\mmproj-gemma-4-E4B-it-BF16.gguf", "ctx": 32768, "note": "the bigger E-series - better answers, still laptop-class"}, {"is_default": false, "path": "", "sha256": "f2c28b3dc4776931ac6f879e11f203dec637ea0f14267a86ec8f6165f63f293f", "vision_bytes": 0, "display": "Gemma 4 26B-A4B", "file": "gemma-4-26B-A4B-it-UD-Q4_K_M.gguf", "bytes": 16947541728, "present": false, "name": "gemma-4-26b-a4b", "vram_hint_gb": 19, "vision_file": "", "vision_present": false, "vision_path": "", "ctx": 131072, "note": "the MoE - 26B quality at 4B active weights per token"}, {"is_default": false, "path": "", "sha256": "322e194ff79741c7baa497c240f677f54b201b0efab44ca8e50f122b39123482", "vision_bytes": 0, "display": "Qwen 3.8 27B", "file": "Qwen3.8-27B-UD-Q4_K_M.gguf", "bytes": 16464440224, "present": false, "name": "qwen3.8-27b", "vram_hint_gb": 18.5, "vision_file": "", "vision_present": false, "vision_path": "", "ctx": 262144, "note": "the newest dense Qwen - thinking model, strong at code"}, {"is_default": false, "path": "C:\\Users\\user\\.dasllama\\models\\Qwen3VL-4B-Instruct-Q8_0.gguf", "sha256": "054721f478bc5fa6beffb7f38eae575d45298f88cbb8d2f83ef675a727863eb1", "vision_bytes": 836180256, "display": "Qwen3 VL 4B", "file": "Qwen3VL-4B-Instruct-Q8_0.gguf", "bytes": 4280406144, "present": true, "name": "qwen3-vl-4b", "vram_hint_gb": 6, "vision_file": "mmproj-Qwen3VL-4B-Instruct-F16.gguf", "vision_present": true, "vision_path": "C:\\Users\\user\\.dasllama\\models\\mmproj-Qwen3VL-4B-Instruct-F16.gguf", "ctx": 262144, "note": "the deepstack vision Qwen - reads images through wide multi-tap rows"}, {"is_default": false, "path": "C:\\Users\\user\\.dasllama\\models\\Qwen2.5-Omni-3B-Q8_0.gguf", "sha256": "c7d8b07c8d8d7a9ed1de1b8df7ac821eb4d259a224bd44310baacfaa5a473d4c", "vision_bytes": 2623983328, "display": "Qwen2.5 Omni 3B", "file": "Qwen2.5-Omni-3B-Q8_0.gguf", "bytes": 3616087360, "present": true, "name": "qwen2.5-omni-3b", "vram_hint_gb": 5.5, "vision_file": "mmproj-Qwen2.5-Omni-3B-f16.gguf", "vision_present": true, "vision_path": "C:\\Users\\user\\.dasllama\\models\\mmproj-Qwen2.5-Omni-3B-f16.gguf", "ctx": 32768, "note": "the window-ViT omni - compact vision chat on the Metal tower"}, {"is_default": false, "path": "C:\\Users\\user\\.dasllama\\models\\Qwen3.6-35B-A3B-MTP-UD-Q4_K_M.gguf", "sha256": "0b21525e972670ed59e1812e170b27c26355381f0656ecc4e25617ece7dac58b", "vision_bytes": 0, "display": "Qwen 3.6 35B-A3B", "file": "Qwen3.6-35B-A3B-MTP-UD-Q4_K_M.gguf", "bytes": 22663387424, "present": true, "name": "qwen3.6-35b-a3b", "vram_hint_gb": 24, "vision_file": "", "vision_present": false, "vision_path": "", "ctx": 262144, "note": "the Qwen MoE - 3B active weights, MTP-ready"}], "asr": {"file": "ggml-parakeet-tdt-0.6b-v3-f32.bin", "bytes": 2508463079, "present": false, "path": ""}, "tts": [{"file": "kitten-nano.gguf", "bytes": 59331456, "pack": false, "present": true, "path": "C:\\Users\\user\\.dasllama\\models\\kitten-nano.gguf", "needs_packs": true}, {"file": "tts_g2p.bin", "bytes": 14011554, "pack": true, "present": true, "path": "C:\\Users\\user\\.dasllama\\models\\tts_g2p.bin", "needs_packs": false}, {"file": "tts_postag.bin", "bytes": 12566510, "pack": true, "present": true, "path": "C:\\Users\\user\\.dasllama\\models\\tts_postag.bin", "needs_packs": false}, {"file": "kitten-mini.gguf", "bytes": 295975008, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "kokoro-82m.gguf", "bytes": 352965024, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "pocket-tts-en-q8.gguf", "bytes": 152613664, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "pocket-tts-de-q8.gguf", "bytes": 134667200, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "pocket-tts-es-q8.gguf", "bytes": 134624480, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "pocket-tts-it-q8.gguf", "bytes": 134415072, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "pocket-tts-pt-q8.gguf", "bytes": 134667488, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "pocket-tts-fr-q8.gguf", "bytes": 375793696, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "pocket-tts-en-kq.gguf", "bytes": 74970016, "pack": false, "present": false, "path": "", "needs_packs": true}, {"file": "pocket-tts-en-stuart-kq.gguf", "bytes": 65107520, "pack": false, "present": false, "path": "", "needs_packs": true}], "box": {"ram_gb": 64, "vram_mb": 0}, "download": {"state": "idle", "name": "", "got": 0, "total": 0, "error": ""}}
\ No newline at end of file
diff --git a/utils/dasllama-server/tests/tts.spec.js b/utils/dasllama-server/tests/tts.spec.js
index 01f2b33233..314033d124 100644
--- a/utils/dasllama-server/tests/tts.spec.js
+++ b/utils/dasllama-server/tests/tts.spec.js
@@ -323,6 +323,37 @@ test('a downloaded model beside the packs offers enable-speech, which wires and
expect(posts.some(p => p.path === '/restart')).toBe(true);
});
+// the packs taken away again - the state a box is in with one file on disk
+function withPacksAbsent(doc) {
+ const d = JSON.parse(JSON.stringify(doc));
+ for (const i of d.tts) {
+ if (i.pack) { i.present = false; i.path = ''; }
+ }
+ return d;
+}
+
+test('a model that reads no packs enables speech with the packs absent; one that reads them waits for them', async ({ page }) => {
+ const idle = fx('catalog_idle');
+ const readsPacks = speechModels(idle).find(m => m.present);
+ expect(readsPacks.needs_packs).toBe(true); // the capture stocks one phoneme family
+ // the phoneme family alone, packs gone: the ladder re-offers the packs, no enable
+ const waiting = withPacksAbsent(idle);
+ await openControl(page, { catalog: waiting });
+ await expect(page.locator('#tts-offer button', { hasText: 'enable speech' })).toHaveCount(0);
+ await expect(page.locator('#tts-offer button', { hasText: 'download the front-end packs' })).toHaveCount(1);
+ // a second model on disk whose row says it reads no packs - the page reads only that key,
+ // so any absent row of the capture serves, its needs_packs overridden: it is the one enable
+ // wires, the packs still absent
+ const alone = speechModels(idle).find(m => !m.present);
+ const doc = withModelPresent(waiting, alone.file);
+ doc.tts.find(i => i.file === alone.file).needs_packs = false;
+ const { posts } = await openControl(page, { catalog: doc });
+ await expect(page.locator('#tts-pick')).toHaveCount(0); // the phoneme family is not a choice without its packs
+ await page.locator('#tts-offer button', { hasText: 'enable speech' }).click();
+ await expect(page.locator('#cat-note')).toContainText('speech wired (' + modelName(alone.file) + ')');
+ expect(lastJson(posts.filter(p => p.path === '/config')).tts).toBe(doc.models_dir + '\\' + alone.file);
+});
+
test('several downloaded models become a picker that defaults to the smallest', async ({ page }) => {
const idle = fx('catalog_idle');
const absent = bySize(speechModels(idle).filter(m => !m.present))[0];