Skip to content

fix(tui): make model download progress feel alive and stop offering a running download - #137

Merged
sosidudku1 merged 3 commits into
mainfrom
valeryb/responsive-download-progress
Aug 18, 2026
Merged

fix(tui): make model download progress feel alive and stop offering a running download#137
sosidudku1 merged 3 commits into
mainfrom
valeryb/responsive-download-progress

Conversation

@plombeer31

Copy link
Copy Markdown
Collaborator

Two problems while a local model is downloading: the numbers barely move, and the list keeps
offering a download that is already running.

1. The byte counter freezes

downloadFile emitted progress only when the whole-number percentage changed
(src/local-llm/download-file.ts, if (percent !== lastReportedPercent)). Since
transferred was sampled only at those moments, the KB/MB figure sat still in between: one
percent of a 4 GB GGUF is ~41 MB, so at 5–20 MB/s the number moved once every 2–8 seconds and
the download looked stalled.

Worse, with no content-length the total is 0, which pins percent at 0 — after the first
chunk the guard never opened again and the counter was frozen for the entire download.

Progress is now emitted on a 200 ms time base, with a forced final emit at the end so the
terminal 100% is never lost. Nothing downstream throttles (the orchestrator forwards each
callback, and Ink's default maxFps: 30 is far faster than this), so the UI now updates
about five times a second.

2. The list still says Enter: download

The row builders only looked at model.downloaded; they never consulted
state.localModelsPanel.pull / .embeddingPull, which is the live per-model download state
the Models tab already renders. A model being pulled right now therefore advertised
Enter: download, which reads as if the keypress did nothing and invites a second press —
and pressing Enter really did re-fire the pull.

Row state Before After
Downloading Enter: download Downloading… 42%
Downloaded, not selected Enter: use local-llama/<id> Enter: select model
Pull failed Enter: download Enter: download (unchanged)
Different model downloading Enter: download Enter: download (unchanged)

primaryAction gains a "downloading" variant on both the chat and embedding row unions, and
triggerLocalChatModel / triggerLocalEmbeddingModel now no-op while that model's pull is in
flight instead of starting it again. The Models-tab detail pane got the same treatment — it
was showing Enter — download mid-pull too.

Testing

  • npm run lint (tsc --noEmit) clean
  • 2 new tests in download-file.test.ts, both verified to fail against the old
    implementation
    (1 progress callback where the test requires 5+) and pass against the new
    one: chunks smaller than one percent, and a response with no content-length
  • 4 new selector tests: downloading, downloaded, failed pull, and a pull for a different
    model (which must not mark this row as downloading)
  • One existing assertion updated: llm-panel-selectors.test.ts pinned
    "Enter: use local-llama/qwen-3.5-4b", now "Enter: select model"
  • Full src/tui + src/local-llm suites, serial: 111 files passed, 4 failed — the same
    four that fail on unmodified main (tui-app ×2 stale snapshots, splash-banner,
    chat-log, persist-embedding-hybrid-recall)

The 200 ms interval is a deliberate choice over exactly 1 s: it is frequent enough that the
counter reads as live, and still ~6× cheaper than the render throttle above it.

@sosidudku1
sosidudku1 merged commit 74cd1d7 into main Aug 18, 2026
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.

2 participants