Skip to content

feat(settings): ask before downloading a newly selected model - #152

Merged
Valtora merged 3 commits into
mainfrom
feat/model-download-prompt
Jul 28, 2026
Merged

feat(settings): ask before downloading a newly selected model#152
Valtora merged 3 commits into
mainfrom
feat/model-download-prompt

Conversation

@Valtora

@Valtora Valtora commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Pull Request

Description

Selecting a transcription model queued a download as a side effect of saving the setting. That download runs on the GPU lane, the same lane that serves live transcription, so on a single-card host it could sit in front of a meeting. Nothing in the UI said it had started, and the setup wizard's promise to "check progress later in Settings" was never honoured by anything.

Preparation is now explicit. Selecting a model that is not on the server prompts for a choice: fetch it now so it is ready for the next recording, or leave it to the lazy fetch on first use, which delays live transcription and Meeting Edge until the download completes. Selecting a model already on disk saves silently, so the prompt only appears when there is something to download.

A new admin-only POST /system/models/prepare carries the request, with targets for the active selection, the core batch, and each ONNX ASR model, so a single missing row in Model dependencies can be repaired on its own. It refuses a second request with 409 while one is in flight, and resolves the model from the admin's own settings rather than the install config, since the transcription keys are user-scoped and reading config alone would prepare the install default instead. Model dependencies gains a Download action on every missing row plus a live progress strip, so declining the prompt is recoverable and a running preparation is visible however it was started.

Two pre-existing bugs surfaced while validating this against a real install and are fixed here, since the new Download button is unusable without them.

Canary was reported as missing however many times it was downloaded. The status check matched the Nojoin model id against Hugging Face cache directory names, but onnx-asr caches nemo-canary-1b-v2 under the repo istupakov/canary-1b-v2-onnx, so the id never appeared in the directory name. Parakeet escaped the bug only because its id happens to be a substring of its repo name. Matching a fragment of the repo name instead also unblocks deletion and the admin health card, both of which read the same status. Resolving the id through onnx-asr would be exact, but that would pull the ASR stack into the API process, which the torch boundary test exists to prevent.

Deleting a model failed on every Docker install with EROFS. The API mounts the shared model volume read-only, by design, so the delete could never have worked from there. The delete now goes to the io lane, which mounts that volume read-write. The task resolves the path itself rather than accepting one from the API, because the same volume is mounted at a different path in each container, and a delete sink that trusts a caller-supplied path is worth not building. It returns a status dict rather than raising, since the JSON serialiser would not carry the difference between "not found" and "refused" across the boundary. The request still waits for the result so the UI keeps refreshing status the moment it returns, with a 60 second ceiling that fails the request rather than holding a thread when no worker answers.

No new dependencies.

Behaviour changes worth noting

  • Saving settings through the API no longer prepares models for any caller. Lazy fetch on first use still covers that path.
  • Deleting a model now requires a running worker. With no worker the request returns 504 explaining that, rather than the old EROFS error.

Type of change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that changes existing behaviour)
  • Documentation update

Checks run

  • Backend tests: source .venv/bin/activate && pytest
  • Python quality: python scripts/check.py (Ruff lint, format check, mypy, doc and Alembic validators)
  • Frontend lint: cd frontend && npm run lint
  • Frontend unit tests: cd frontend && npm run test
  • Frontend build: cd frontend && npm run build
  • Docs validation: python3 scripts/validate_docs.py
  • Alembic validation: python3 scripts/validate_alembic.py

scripts/check.py reported OK across lint, format, whitespace, filesize, heldpins, typecheck, docs, alembic and tests, with 1122 backend tests passing. Frontend: 296 tests across 52 files, lint clean, build clean.

Migration impact

  • No database migration in this PR.
  • Adds an Alembic migration.

Documentation impact

  • No documentation change required.
  • Updated the relevant guide(s) in the same PR.

docs/DEPLOYMENT.md (worker startup: preparation is opt-in, deletion needs a worker, the read-only mount), docs/ADMIN.md (the new admin actions), docs/USAGE.md (the prompt in Settings).

Security impact

  • No security-sensitive change.
  • Touches auth, tokens, encryption, capture ownership, or exposure.

Both new endpoints are admin-only. The model deletion task takes a model identity from a fixed allowlist rather than a path, so nothing caller-supplied reaches the filesystem sink, and the API keeps its read-only mount on the model volume.

Manual verification

Verified against a live install on the deployment host:

  • Confirmed the Canary asset was present on disk as models--istupakov--canary-1b-v2-onnx while the UI reported it Missing, which is what led to the detection fix. Confirmed the corrected matcher reports both ONNX models present using those exact directory names.
  • Confirmed the API container mounts the model volume read-only and the worker lanes mount it read-write, which is the cause of the delete failure.
  • Observed the preparation flow end to end: queueing, the progress strip, and the completion toast.

Still pending, because the running containers predate these commits and both fixes need the api and worker images rebuilt:

  • The Canary row flipping to Ready without a download, once the api image ships the corrected detection.
  • A successful delete through the worker lane.

No capture, recording context-menu, or migration changes in this PR.

Valtora added 3 commits July 27, 2026 23:39
Saving a transcription model change queued a download as a side effect.
That download runs on the GPU lane, the same lane that serves live
transcription, so on a single-card host it could sit in front of a
meeting; nothing in the UI said it had started, and the setup wizard's
promise to "check progress later in Settings" was never honoured.

Preparation is now explicit. Selecting a model that is not on the server
prompts for a choice: fetch it now so it is ready for the next
recording, or leave it to the lazy fetch on first use, which delays live
transcription and Meeting Edge until the download completes. Selecting a
model already on disk saves silently, so the prompt only appears when
there is something to download.

A new admin-only POST /system/models/prepare carries the request, with
targets for the active selection, the core batch, and each ONNX ASR
model, so a single missing row in Model dependencies can be repaired on
its own. It refuses a second request with 409 while one is in flight,
and resolves the model from the admin's own settings rather than the
install config, since the transcription keys are user-scoped and reading
config alone would prepare the install default instead.

Model dependencies gains a Download action on every missing row plus a
live progress strip, so declining the prompt is recoverable and a
running preparation is visible however it was started.

Saving settings through the API no longer prepares models for any
caller. Lazy fetch on first use still covers that path.

Refs: docs/DEPLOYMENT.md, docs/ADMIN.md, docs/USAGE.md
Canary was reported as missing however many times it was downloaded.
The status check matched the Nojoin model id against Hugging Face cache
directory names, but onnx-asr caches nemo-canary-1b-v2 under the repo
istupakov/canary-1b-v2-onnx, so the id never appeared in the directory
name. Parakeet escaped the bug only because its id happens to be a
substring of its repo name.

Match a fragment of the repo name instead, held in one named map with
the divergence written down so it is not "corrected" back to the model
id later. Resolving the id through onnx-asr would be exact, but that
would pull the ASR stack into the API process, which the torch boundary
test exists to prevent.

This also unblocks deletion and the admin health card, both of which
read the same status: a model reported as missing has no resolvable path
to delete, and the transcription component showed Canary as uncached.

Refs: backend/preload_models.py
Deleting a model failed on every Docker install with EROFS. The API
mounts the shared model volume read-only, by design, so the delete could
never have worked from there: the failure was structural rather than a
bad path.

Dispatch the delete to the io lane, which mounts that volume read-write
along with the other worker lanes. The task resolves the path itself
rather than accepting one from the API, because the same volume is
mounted at a different path in each container, and a delete sink that
trusts a caller-supplied path is worth not building. It returns a status
dict instead of raising, since the JSON serialiser would not carry the
difference between "not found" and "refused" across the boundary.

The request still waits for the result, so the UI can keep refreshing
model status the moment it returns, with a 60 second ceiling that fails
the request rather than holding a thread when no worker answers.

Deleting a model now needs a running worker; that is documented.

Refs: docs/DEPLOYMENT.md, docs/ADMIN.md
@Valtora
Valtora merged commit 44689e5 into main Jul 28, 2026
19 of 20 checks passed
@Valtora
Valtora deleted the feat/model-download-prompt branch July 28, 2026 00:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant