From 416330ee2deb4ebf9c6d230a69683c7c276e6851 Mon Sep 17 00:00:00 2001 From: Aleksander Grygier Date: Wed, 2 Sep 2026 12:38:57 +0200 Subject: [PATCH] ui : read sidecar download state from the models list Solo sidecar downloads register in /v1/models under the entry tag, so the state checks that first (normalized against the UD- quant prefix) and keeps the --model-draft / --mmproj args of registered models as a fallback. The chips no longer flip to downloaded while a download is in progress. Assisted-by: pi:zai-org/GLM-5.3 --- ...DiscoverModelDetailsDownloadOptions.svelte | 13 ++++-- .../ui/src/lib/stores/models/status.svelte.ts | 41 ++++++++++++++----- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverModelDetailsDownloadOptions.svelte b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverModelDetailsDownloadOptions.svelte index f698c65597..88dfe91481 100644 --- a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverModelDetailsDownloadOptions.svelte +++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverModelDetailsDownloadOptions.svelte @@ -41,11 +41,16 @@ function stateFor(repoWithTag: string, filePath: string, isSidecar: boolean): DownloadEntryState { if (getDownloadState) return getDownloadState(repoWithTag, filePath, isSidecar); + const isDownloading = modelsStore.status.isDownloadInProgress(repoWithTag); + return { - isDownloaded: isSidecar - ? modelsStore.status.isDraftDownloaded(modelId, filePath) - : modelsStore.status.isModelDownloaded(repoWithTag), - isDownloading: modelsStore.status.isDownloadInProgress(repoWithTag), + // solo downloads register in /v1/models under the tag (args stay empty), + // while drafts pulled by a loaded model only show up as its --model-draft + isDownloaded: + !isDownloading && + (modelsStore.status.isModelDownloaded(repoWithTag) || + (isSidecar && modelsStore.status.isSidecarDownloaded(modelId, filePath))), + isDownloading, isFailed: modelsStore.status.hasFailedDownload(repoWithTag), progress: modelsStore.status.getDownloadProgress(repoWithTag) }; diff --git a/tools/ui/src/lib/stores/models/status.svelte.ts b/tools/ui/src/lib/stores/models/status.svelte.ts index b5e58321bc..a20293ad52 100644 --- a/tools/ui/src/lib/stores/models/status.svelte.ts +++ b/tools/ui/src/lib/stores/models/status.svelte.ts @@ -7,6 +7,7 @@ * modelsStore; the host owns the router model rows the feed updates. */ +import { HF_UD_QUANT_PREFIX_REGEX } from '$lib/constants'; import { ServerModelsSseEventType, ServerModelStatus } from '$lib/enums'; import { HuggingFaceService } from '$lib/services/huggingface.service'; import { ModelsService } from '$lib/services/models.service'; @@ -33,13 +34,28 @@ export interface ModelStatusHost { toDisplayName(id: string): string; } +/** + * Comparison key of a `:` download identifier: uppercased, with the + * `UD-` quant prefix stripped. The router derives cached model names from the + * actual file, which drops the prefix, so `repo:UD-Q4_K_XL` and `repo:Q4_K_XL` + * must compare equal. + */ +function downloadIdKey(repoWithTag: string): string { + const idx = repoWithTag.indexOf(':'); + const repo = idx === -1 ? repoWithTag : repoWithTag.slice(0, idx); + const tag = idx === -1 ? '' : repoWithTag.slice(idx + 1); + + return `${repo.toUpperCase()}:${tag.toUpperCase().replace(HF_UD_QUANT_PREFIX_REGEX, '')}`; +} + export class ModelStatusManager { /** - * Draft sidecar files pulled by registered models, as `/` keys. - * Drafts are not separate /v1/models entries - the router pulls them as - * sidecars of a main model and records them in its `--model-draft` arg. + * Sidecar files pulled by registered models, as `/` keys. + * Sidecars are not separate /v1/models entries - the router pulls them as + * sidecars of a main model and records them in its `--model-draft` / + * `--mmproj` args. */ - private downloadedDrafts = $derived.by(() => { + private downloadedSidecars = $derived.by(() => { const result = new SvelteSet(); for (const m of this.host.routerModels) { @@ -48,7 +64,9 @@ export class ModelStatusManager { if (!args) continue; for (let i = 0; i < args.length - 1; i++) { - if (args[i] !== '--model-draft' && args[i] !== '-md') continue; + if (args[i] !== '--model-draft' && args[i] !== '-md' && args[i] !== '--mmproj') { + continue; + } const parsed = HuggingFaceService.parseCachePath(args[i + 1]); @@ -208,19 +226,22 @@ export class ModelStatusManager { } /** - * True when the given draft sidecar file (repo-relative path) has been pulled - * as the `--model-draft` of some registered model. + * True when the given sidecar file (repo-relative path) has been pulled as + * the `--model-draft` or `--mmproj` of some registered model. */ - isDraftDownloaded(repoId: string, filePath: string): boolean { - return this.downloadedDrafts.has(`${repoId}/${filePath}`); + isSidecarDownloaded(repoId: string, filePath: string): boolean { + return this.downloadedSidecars.has(`${repoId}/${filePath}`); } /** * True when the given `:` is already a fully downloaded model * registered with the server (i.e. it shows up in the /v1/models list). + * Both ids are normalized, see downloadIdKey(). */ isModelDownloaded(repoWithTag: string): boolean { - return this.host.routerModels.some((m) => m.id === repoWithTag); + const key = downloadIdKey(repoWithTag); + + return this.host.routerModels.some((m) => downloadIdKey(m.id) === key); } isOperationInProgress(modelId: string): boolean {