From ae433124b3f88690924d56c29913779ffbdee11c Mon Sep 17 00:00:00 2001 From: Aleksander Grygier Date: Fri, 2 Oct 2026 00:16:33 +0200 Subject: [PATCH] ui : make resuming a paused download work Re-posting the tag of a paused download hit the router's "already exists" check, because a paused download parks its entry as DOWNLOADED until the next reload: a download entry that is not running is now replaced and resumed. The UI keeps the paused row when the request still fails, instead of dropping it and looking finished. Assisted-by: pi --- tools/server/server-models.cpp | 18 ++++++++++++++++-- .../ui/src/lib/stores/models/status.svelte.ts | 11 ++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/tools/server/server-models.cpp b/tools/server/server-models.cpp index a8996b00c9..7f3151b180 100644 --- a/tools/server/server-models.cpp +++ b/tools/server/server-models.cpp @@ -2158,9 +2158,23 @@ void server_models_routes::init_routes() { throw std::invalid_argument("model validation failed, unable to download"); } - // reject if model already exists + // reject if model already exists, but a paused download parks its entry as + // DOWNLOADED until the next reload: re-posting that tag is how a resume is + // asked for, so a download that is not running is replaced instead if (models.has_model(name)) { - throw std::invalid_argument("model '" + name + "' already exists"); + auto existing = models.get_meta(name); + + // the entry can disappear here: get_meta() triggers the reload that + // erases a finished download, which is the resume case as well + const bool resumable = existing.has_value() && !existing->is_running() && + (existing->status == SERVER_MODEL_STATUS_DOWNLOADED || + existing->status == SERVER_MODEL_STATUS_DOWNLOADING); + + if (existing.has_value() && !resumable) { + throw std::invalid_argument("model '" + name + "' already exists"); + } + + SRV_INF("resuming download for model '%s'\n", name.c_str()); } // then, proceed with the actual download diff --git a/tools/ui/src/lib/stores/models/status.svelte.ts b/tools/ui/src/lib/stores/models/status.svelte.ts index 642e8707a3..9790ac35b9 100644 --- a/tools/ui/src/lib/stores/models/status.svelte.ts +++ b/tools/ui/src/lib/stores/models/status.svelte.ts @@ -254,6 +254,7 @@ export class ModelStatusManager { // not fall back to 0%, and let the server discard its stale DOWNLOADED entry // (via the list fetch) before re-posting const snapshot = this.pausedDownloads.get(repoWithTag) ?? null; + const wasPaused = this.pausedDownloads.has(repoWithTag); if (this.deletePausedDownload(repoWithTag) || this.stopRequests.delete(repoWithTag)) { await this.host.fetchRouterModels(); @@ -271,7 +272,15 @@ export class ModelStatusManager { toast.success(`Download started: ${this.host.toDisplayName(repoWithTag)}`); } catch (error) { - toast.error(`Download failed: ${repoWithTag}`); + // the download did not start: a resumed one stays paused rather than + // dropping off the list, which reads as a finished download + if (wasPaused) { + this.setPausedDownload(repoWithTag, snapshot); + } + + const reason = error instanceof Error ? error.message : 'unknown error'; + + toast.error(`Download failed: ${repoWithTag} (${reason})`); throw error; }