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 0a87622461..edb5d83720 100644 --- a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverModelDetailsDownloadOptions.svelte +++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverModelDetailsDownloadOptions.svelte @@ -43,6 +43,19 @@ let selectedPaths = $state([]); + function stateFor(repoWithTag: string, filePath: string, isSidecar: boolean): DownloadEntryState { + if (getDownloadState) return getDownloadState(repoWithTag, filePath, isSidecar); + + return { + isDownloaded: isSidecar + ? modelsStore.status.isDraftDownloaded(modelId, filePath) + : modelsStore.status.isModelDownloaded(repoWithTag), + isDownloading: modelsStore.status.isDownloadInProgress(repoWithTag), + isFailed: modelsStore.status.hasFailedDownload(repoWithTag), + progress: modelsStore.status.getDownloadProgress(repoWithTag) + }; + } + /** Kind of a file path: the main weights, a draft sidecar, or an aux sidecar (mmproj). */ function classify(path: string): 'main' | 'draft' | 'aux' { const sidecar = HuggingFaceService.extractQuantMeta(path)?.sidecar; @@ -59,6 +72,8 @@ function handleSelection(next: string[]) { const added = next.find((p) => !selectedPaths.includes(p)); + if (added && downloadedPaths.has(added)) return; + if (!added) { selectedPaths = next; @@ -70,24 +85,29 @@ selectedPaths = [...base, added]; } + /** Paths already downloaded on the server; those render as static chips. */ + let downloadedPaths = $derived( + new Set( + bitDepthRows + .flatMap((r) => r.files) + .filter((f) => { + const meta = HuggingFaceService.extractQuantMeta(f.path); + const tag = ModelsService.buildDownloadTag( + modelId, + meta?.quant ?? null, + meta?.sidecar ?? null + ); + + return stateFor(tag, f.path, Boolean(meta?.sidecar)).isDownloaded; + }) + .map((f) => f.path) + ) + ); let copied = $state(false); /** All repo files in one lookup by path. */ let fileByPath = $derived(new Map(bitDepthRows.flatMap((r) => r.files).map((f) => [f.path, f]))); - function stateFor(repoWithTag: string, filePath: string, isSidecar: boolean): DownloadEntryState { - if (getDownloadState) return getDownloadState(repoWithTag, filePath, isSidecar); - - return { - isDownloaded: isSidecar - ? modelsStore.status.isDraftDownloaded(modelId, filePath) - : modelsStore.status.isModelDownloaded(repoWithTag), - isDownloading: modelsStore.status.isDownloadInProgress(repoWithTag), - isFailed: modelsStore.status.hasFailedDownload(repoWithTag), - progress: modelsStore.status.getDownloadProgress(repoWithTag) - }; - } - /** Selection ordered main-quant-first, so the command reads naturally. */ let selected: SelectedDownload[] = $derived.by(() => { const mains: SelectedDownload[] = []; @@ -129,20 +149,6 @@ [ModelDraftSidecar.MTP]: 'draft-mtp' }; - function buttonClass(parts: { isDownloaded: boolean; isFailed: boolean }): string { - const classes = [ - 'relative inline-flex items-center gap-1 overflow-hidden rounded-md border bg-muted px-2 py-1 text-left font-mono text-xs transition-colors' - ]; - - if (parts.isDownloaded && !parts.isFailed) { - classes.push('border-foreground bg-muted'); - } else if (parts.isFailed) { - classes.push('border-destructive'); - } - - return classes.join(' '); - } - async function copyCommand() { await copyToClipboard(serveCommand); copied = true; @@ -231,51 +237,73 @@ : `Download ${file.path} (requires ~${memoryGb} GB of memory)`} - - {#if isFailed && !isDownloading && !isDownloaded} - - Failed - - {/if} - - {#if meta?.sidecar && !isAuxSidecar(meta.sidecar)} - - {meta.sidecar} - - {/if} - - {label} +
- - - - - {#if isDownloading && progress && progress.totalBytes > 0} - {Math.round((progress.downloadedBytes / progress.totalBytes) * 100)}% - {:else} - {HuggingFaceService.formatFileSize(file.size ?? 0)} + {#if meta?.sidecar && !isAuxSidecar(meta.sidecar)} + + {meta.sidecar} + {/if} - - {#if isDownloading && progress} - - {/if} - + {label} + + + + {HuggingFaceService.formatFileSize(file.size ?? 0)} + + +
+ {:else} + + {#if isFailed && !isDownloading} + + Failed + + {/if} + + {#if meta?.sidecar && !isAuxSidecar(meta.sidecar)} + + {meta.sidecar} + + {/if} + + {label} + + + + + {#if isDownloading && progress && progress.totalBytes > 0} + {Math.round((progress.downloadedBytes / progress.totalBytes) * 100)}% + {:else} + {HuggingFaceService.formatFileSize(file.size ?? 0)} + {/if} + + + {#if isDownloading && progress} + + {/if} + + {/if}