diff --git a/tools/ui/src/app.css b/tools/ui/src/app.css index f9b544bebc..e79e577bc4 100644 --- a/tools/ui/src/app.css +++ b/tools/ui/src/app.css @@ -26,6 +26,7 @@ --border: oklch(0.875 0 0); --input: oklch(0.92 0 0); --ring: oklch(0.708 0 0); + --brand: #f65e00; --chart-1: oklch(0.646 0.222 41.116); --chart-2: oklch(0.6 0.118 184.704); --chart-3: oklch(0.398 0.07 227.392); @@ -115,6 +116,7 @@ --color-muted-foreground: var(--muted-foreground); --color-accent: var(--accent); --color-accent-foreground: var(--accent-foreground); + --color-brand: var(--brand); --color-destructive: var(--destructive); --color-border: var(--border); --color-input: var(--input); diff --git a/tools/ui/src/lib/components/app/models/ModelId.svelte b/tools/ui/src/lib/components/app/models/ModelId.svelte index 24559114ca..49c6f5051e 100644 --- a/tools/ui/src/lib/components/app/models/ModelId.svelte +++ b/tools/ui/src/lib/components/app/models/ModelId.svelte @@ -31,6 +31,8 @@ contextLength?: number; /** Min/max GGUF file size (main + draft) across quants; renders a range when set. */ sizeRange?: { min: number; max: number } | null; + /** Params badge fallback (formatted) when the model id carries no params token. */ + params?: string; draftVariants?: DraftVariant[]; /** Allow badges to wrap onto new lines instead of truncating. */ wrap?: boolean; @@ -52,6 +54,7 @@ iconsOnNewLine = false, modalities, modelId, + params, showRaw = undefined, showRawTooltip = false, sizeRange, @@ -104,9 +107,9 @@ {/if} - {#if parsed.params && !hideParameters} + {#if (parsed.params || params) && !hideParameters} - {parsed.params}{parsed.activatedParams ? `-${parsed.activatedParams}` : ''} + {parsed.params ?? params}{parsed.activatedParams ? `-${parsed.activatedParams}` : ''} {/if} diff --git a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverChatTemplateDialog.svelte b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverChatTemplateDialog.svelte new file mode 100644 index 0000000000..f4d9deafb1 --- /dev/null +++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverChatTemplateDialog.svelte @@ -0,0 +1,39 @@ + + + + + + Chat template + copyToClipboard(chatTemplate)} + class="inline-flex items-center gap-1.5 rounded-md border px-2 py-1 text-xs font-medium transition-colors hover:bg-muted" + > + + Copy + + + + {chatTemplate} + + diff --git a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails.svelte b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails.svelte index 4d57b2c526..e5f50aa595 100644 --- a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails.svelte +++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails.svelte @@ -95,7 +95,10 @@ } details = info; - files = HuggingFaceService.filterByExtension(tree, '.gguf'); + files = HuggingFaceService.filterByExtension( + HuggingFaceService.collapseGgufShards(tree), + '.gguf' + ); readme = readmeText; } catch (err) { error = err instanceof Error ? err.message : 'Failed to load model'; @@ -131,7 +134,12 @@ {hasReasoning} /> - + diff --git a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetailsDownloadOptions.svelte b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetailsDownloadOptions.svelte index 31f6e36b78..160e66372c 100644 --- a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetailsDownloadOptions.svelte +++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetailsDownloadOptions.svelte @@ -1,15 +1,20 @@ {#if bitDepthRows.length} - - - Download options - + + + + Download options + + + + + {osLabel} + {#if deviceMemoryGb > 0} + ({deviceMemoryGb} GB) + {/if} + + {#each bitDepthRows as row (row.bitDepth)} @@ -59,60 +109,97 @@ ? modelsStore.status.isDraftDownloaded(modelId, file.path) : modelsStore.status.isModelDownloaded(hfRepoWithTag)} {@const isFailed = modelsStore.status.hasFailedDownload(hfRepoWithTag)} - - (pendingDownload = { - filePath: file.path, - quant: meta?.quant ?? null, - sizeBytes: file.size ?? null, - variant: meta?.variant ?? null - })} - title={isDownloading - ? `Downloading ${file.path}` - : isDownloaded - ? `Already downloaded: ${file.path}` - : isFailed - ? `Last attempt failed: ${file.path}. Click to retry.` + {@const tier = tiers.get(file.path)} + {@const isUnavailable = + tier === 'none' && !isDownloaded && !isDownloading && !isFailed} + {@const isAvailable = tier === 'full' && !isDownloaded && !isDownloading && !isFailed} + {@const isLimited = + tier === 'limited' && !isDownloaded && !isDownloading && !isFailed} + {@const tooltipText = isDownloading + ? `Downloading ${file.path}` + : isDownloaded + ? `Already downloaded: ${file.path}` + : isFailed + ? `Last attempt failed: ${file.path}. Click to retry.` + : isUnavailable + ? `Does not fit this device: ${file.path}` : `Download ${file.path}`} - class="relative inline-flex cursor-pointer items-center gap-1 overflow-hidden rounded-md border bg-background px-2 py-1 text-left font-mono text-xs transition-colors hover:border-primary/60 hover:bg-primary/5" - class:border-foreground={isDownloaded && !isDownloading && !isFailed} - class:bg-muted={isDownloaded && !isDownloading && !isFailed} - class:border-destructive={isFailed && !isDownloading} - > - {#if isDownloaded && !isDownloading} - - {/if} - {#if isFailed && !isDownloading && !isDownloaded} - - Failed - - {/if} - {#if meta?.variant} - - {meta.variant} - - {/if} - {label} - - {#if isDownloading && progress && progress.totalBytes > 0} - {Math.round((progress.downloadedBytes / progress.totalBytes) * 100)}% - {:else} - {HuggingFaceService.formatFileSize(file.size ?? 0)} + + { + if (isUnavailable) return; + + pendingDownload = { + filePath: file.path, + quant: meta?.quant ?? null, + sizeBytes: file.size ?? null, + variant: meta?.variant ?? null + }; + }} + aria-disabled={isUnavailable} + class={buttonClass({ + isDownloaded, + isFailed, + isUnavailable + })} + > + {#if isAvailable} + + + + {:else if isLimited} + + + + {:else if isUnavailable} + + + {/if} - - {#if isDownloading && progress} - - {/if} - + {#if isFailed && !isDownloading && !isDownloaded} + + Failed + + {/if} + {#if meta?.variant} + + {meta.variant} + + {/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} + + + + {tooltipText} + + {/each} diff --git a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetailsHeader.svelte b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetailsHeader.svelte index ac1546dd0b..d62ef4e77b 100644 --- a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetailsHeader.svelte +++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetailsHeader.svelte @@ -1,10 +1,21 @@ @@ -76,7 +89,7 @@ {#if gguf?.total} - {HuggingFaceService.formatFileSize(gguf.total).replace(' B', '')}B params + {formatParameters(gguf.total)} params {/if} {#if gguf?.architecture} @@ -91,6 +104,16 @@ {gguf.context_length.toLocaleString()} ctx {/if} + {#if gguf?.chat_template} + (chatTemplateOpen = true)} + class="inline-flex items-center gap-1 rounded bg-secondary px-2 py-0.5 text-xs font-medium text-secondary-foreground transition-colors hover:bg-secondary/70" + > + + Chat template + + {/if} {#if licenseTag} {licenseTag} @@ -105,33 +128,46 @@ {/if} - + {#if hasVision || hasTools || hasReasoning} - + {#if hasVision} - - - Vision - + + + + + + Vision + + {/if} {#if hasTools} - - - Tool use - + + + + + + Tool use + + {/if} {#if hasReasoning} - - - Reasoning - + + + + + + Reasoning + + {/if} {/if} + +{#if gguf?.chat_template} + +{/if} diff --git a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverInfo.svelte b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverInfo.svelte index afe21aec74..9d4e24df03 100644 --- a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverInfo.svelte +++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverInfo.svelte @@ -1,11 +1,11 @@
{chatTemplate}
{tooltipText}
Vision
Tool use
Reasoning