From 3f56d5b38d29f533f217fb924036dbdb62fb9582 Mon Sep 17 00:00:00 2001 From: Aleksander Grygier Date: Mon, 28 Sep 2026 15:15:25 +0200 Subject: [PATCH] ui : add the discover models components The Discover surface: the curated-catalog list with its search and skeletons, the details pane with its readme, metadata, Hub stats and download options, and the standalone download progress bar that replaces the plain one. The quant download button sizes its select with a new xs trigger, so the select gains that size and the muted box look. The store stays incomplete when a repo fails, so the next mount retries it. Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash --- .../discover/DownloadProgressBar.svelte | 32 -- .../app/models/discover/ModelsDiscover.svelte | 138 +++++++++ .../ModelsDiscoverChatTemplateDialog.svelte | 67 +++++ .../ModelsDiscoverDetails.svelte | 96 ++++++ ...odelsDiscoverDetailsDownloadOptions.svelte | 173 +++++++++++ ...tailsDownloadOptionsDownloadCommand.svelte | 278 ++++++++++++++++++ ...sDownloadOptionsQuantDownloadButton.svelte | 244 +++++++++++++++ ...lsDiscoverDetailsDownloadOptionsRow.svelte | 60 ++++ .../download-options.utils.ts | 28 ++ .../index.ts | 40 +++ .../ModelsDiscoverDetailsHeader.svelte | 94 ++++++ .../ModelsDiscoverDetailsHfHubStats.svelte | 31 ++ .../ModelsDiscoverDetailsMetadata.svelte | 80 +++++ .../ModelsDiscoverDetailsMetadataItem.svelte | 17 ++ .../ModelsDiscoverDetailsReadme.svelte | 17 ++ .../ModelsDiscoverDetailsSkeleton.svelte | 93 ++++++ .../discover/ModelsDiscoverDetails/index.ts | 72 +++++ .../ModelsDiscoverList.svelte | 41 +++ .../ModelsDiscoverListItem.svelte | 129 ++++++++ .../ModelsDiscoverListItemSkeleton.svelte | 39 +++ .../ModelsDiscoverListSearch.svelte | 26 ++ .../discover/ModelsDiscoverList/index.ts | 38 +++ .../components/app/models/discover/index.ts | 39 +++ .../ui/select/select-content.svelte | 4 +- .../ui/select/select-trigger.svelte | 14 +- .../stores/models-discover/index.svelte.ts | 13 +- 26 files changed, 1862 insertions(+), 41 deletions(-) delete mode 100644 tools/ui/src/lib/components/app/models/discover/DownloadProgressBar.svelte create mode 100644 tools/ui/src/lib/components/app/models/discover/ModelsDiscover.svelte create mode 100644 tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverChatTemplateDialog.svelte create mode 100644 tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetails.svelte create mode 100644 tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsDownloadOptions/ModelsDiscoverDetailsDownloadOptions.svelte create mode 100644 tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsDownloadOptions/ModelsDiscoverDetailsDownloadOptionsDownloadCommand.svelte create mode 100644 tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsDownloadOptions/ModelsDiscoverDetailsDownloadOptionsQuantDownloadButton.svelte create mode 100644 tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsDownloadOptions/ModelsDiscoverDetailsDownloadOptionsRow.svelte create mode 100644 tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsDownloadOptions/download-options.utils.ts create mode 100644 tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsDownloadOptions/index.ts create mode 100644 tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsHeader.svelte create mode 100644 tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsHfHubStats.svelte create mode 100644 tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsMetadata.svelte create mode 100644 tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsMetadataItem.svelte create mode 100644 tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsReadme.svelte create mode 100644 tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsSkeleton.svelte create mode 100644 tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/index.ts create mode 100644 tools/ui/src/lib/components/app/models/discover/ModelsDiscoverList/ModelsDiscoverList.svelte create mode 100644 tools/ui/src/lib/components/app/models/discover/ModelsDiscoverList/ModelsDiscoverListItem.svelte create mode 100644 tools/ui/src/lib/components/app/models/discover/ModelsDiscoverList/ModelsDiscoverListItemSkeleton.svelte create mode 100644 tools/ui/src/lib/components/app/models/discover/ModelsDiscoverList/ModelsDiscoverListSearch.svelte create mode 100644 tools/ui/src/lib/components/app/models/discover/ModelsDiscoverList/index.ts create mode 100644 tools/ui/src/lib/components/app/models/discover/index.ts diff --git a/tools/ui/src/lib/components/app/models/discover/DownloadProgressBar.svelte b/tools/ui/src/lib/components/app/models/discover/DownloadProgressBar.svelte deleted file mode 100644 index c3a89c0ba4..0000000000 --- a/tools/ui/src/lib/components/app/models/discover/DownloadProgressBar.svelte +++ /dev/null @@ -1,32 +0,0 @@ - - -{#if overlay} -
-
-
-{:else} -
-
-
-{/if} diff --git a/tools/ui/src/lib/components/app/models/discover/ModelsDiscover.svelte b/tools/ui/src/lib/components/app/models/discover/ModelsDiscover.svelte new file mode 100644 index 0000000000..01b4e90e0a --- /dev/null +++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscover.svelte @@ -0,0 +1,138 @@ + + + + +
+ {#if selectedId} + + {/if} +
diff --git a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverChatTemplateDialog.svelte b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverChatTemplateDialog.svelte new file mode 100644 index 0000000000..45eb209a2a --- /dev/null +++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverChatTemplateDialog.svelte @@ -0,0 +1,67 @@ + + + + + + + Chat template + + + + + + + + +
{chatTemplate}
+
+
diff --git a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetails.svelte b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetails.svelte new file mode 100644 index 0000000000..cecdda738c --- /dev/null +++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetails.svelte @@ -0,0 +1,96 @@ + + +{#if loading} + +{:else if error} +
+

{error}

+
+{:else if details} +
+ + + + + +
+{/if} diff --git a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsDownloadOptions/ModelsDiscoverDetailsDownloadOptions.svelte b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsDownloadOptions/ModelsDiscoverDetailsDownloadOptions.svelte new file mode 100644 index 0000000000..75ecef320e --- /dev/null +++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsDownloadOptions/ModelsDiscoverDetailsDownloadOptions.svelte @@ -0,0 +1,173 @@ + + +{#if bitDepthRows.length} +
+ +
+ {#each rows as row (row.bitDepth)} + + {/each} +
+ + +
+ +
+
+{/if} + + (confirmOpen = false)} + open={confirmOpen} + repoWithTag={pending.repoWithTag} +/> diff --git a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsDownloadOptions/ModelsDiscoverDetailsDownloadOptionsDownloadCommand.svelte b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsDownloadOptions/ModelsDiscoverDetailsDownloadOptionsDownloadCommand.svelte new file mode 100644 index 0000000000..e380505f38 --- /dev/null +++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsDownloadOptions/ModelsDiscoverDetailsDownloadOptionsDownloadCommand.svelte @@ -0,0 +1,278 @@ + + + + +
+ +
+ {SERVE_COMMAND.BIN} + + {SERVE_COMMAND.SUBCOMMAND} + + {SERVE_COMMAND.MODEL_FLAG} + + + {modelId}{mainQuant ? MODEL_ID.QUANTIZATION_SEPARATOR : ''} + + + + {#if baseOption} + v && (basePick = v)} type="single" value={baseOption.path}> + + {baseOption.label} + + + + {#each mainOptions as option (option.path)} + + {option.label} + + {/each} + + + {/if} + + + {#if draftOptions.length && !withDraft} + + {/if} + + + {#if draftOption} + + {SERVE_COMMAND.DRAFT_FLAG} + + + {modelId}{draftQuant ? MODEL_ID.QUANTIZATION_SEPARATOR : ''} + + + v && (draftPick = v)} + type="single" + value={draftOption.path} + > + + {draftOption.label} + + + + {#each typeDraftOptions as option (option.path)} + + {option.label} + + {/each} + + + + {#if draftType} + {SERVE_COMMAND.SPEC_TYPE_FLAG} + + + {#if specTypes.length > 1} + v && (draftTypePick = v as ModelSidecar)} + type="single" + value={draftType} + > + + {SPEC_TYPE[draftType]} + + + + {#each specTypes as type (type)} + + {SPEC_TYPE[type]} + + {/each} + + + {:else} + {SPEC_TYPE[draftType]} + {/if} + {/if} + + + + {/if} +
+ + +
diff --git a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsDownloadOptions/ModelsDiscoverDetailsDownloadOptionsQuantDownloadButton.svelte b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsDownloadOptions/ModelsDiscoverDetailsDownloadOptionsQuantDownloadButton.svelte new file mode 100644 index 0000000000..2908ea97a3 --- /dev/null +++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsDownloadOptions/ModelsDiscoverDetailsDownloadOptionsQuantDownloadButton.svelte @@ -0,0 +1,244 @@ + + +{#snippet chipBody(dividerClass: string)} + + {#if meta?.sidecar} + + {meta.sidecar} + + {/if} + + {label} + + +{/snippet} + + +{#snippet tooltipTrigger(tip: string, button: Snippet<[Record]>)} + + + {#snippet child({ props })} + {@render button(props)} + {/snippet} + + + +

{tip}

+
+
+{/snippet} + +{#snippet deleteChip(props: Record)} + + +{/snippet} + +{#snippet pauseResumeChip(props: Record)} + +{/snippet} + +{#snippet cancelChip(props: Record)} + + +{/snippet} + +{#snippet downloadChip(props: Record)} + + +{/snippet} + +{#if entry.isDownloaded} + {@render tooltipTrigger(tooltipText, deleteChip)} +{:else if entry.isDownloading || entry.isPaused} + +
+ {@render tooltipTrigger(tooltipText, pauseResumeChip)} + + {@render tooltipTrigger('Cancel downloading', cancelChip)} + + {#if percent !== null} + + {/if} +
+{:else} + {@render tooltipTrigger(tooltipText, downloadChip)} +{/if} diff --git a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsDownloadOptions/ModelsDiscoverDetailsDownloadOptionsRow.svelte b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsDownloadOptions/ModelsDiscoverDetailsDownloadOptionsRow.svelte new file mode 100644 index 0000000000..11bfe45d42 --- /dev/null +++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsDownloadOptions/ModelsDiscoverDetailsDownloadOptionsRow.svelte @@ -0,0 +1,60 @@ + + +
+
+ {#if bitDepth === OTHER_BIT_DEPTH} + {OTHER_BIT_DEPTH_LABEL} + {:else} + {bitDepth}{BIT_DEPTH_LABEL_SUFFIX} + {/if} + + {#if mainMemGb} + + needs at least {mainMemGb}{GIGABYTE_LABEL}{draftMemGb + ? ` + ${draftMemGb}${GIGABYTE_LABEL}` + : ''} memory + + {/if} +
+ +
+ {#each files as file (file.path)} + + {/each} +
+
diff --git a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsDownloadOptions/download-options.utils.ts b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsDownloadOptions/download-options.utils.ts new file mode 100644 index 0000000000..4dd8accb42 --- /dev/null +++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsDownloadOptions/download-options.utils.ts @@ -0,0 +1,28 @@ +import { DRAFT_FILE_LABEL, MODEL_ID, PATH_SEPARATOR } from '$lib/constants'; +import { ModelSelectableFileKind } from '$lib/enums'; +import { HuggingFaceService } from '$lib/services'; +import { isAuxSidecar, isDraftSidecar } from '$lib/utils'; + +/** Kind of a file path: the main weights, a draft sidecar, or an aux sidecar (mmproj). */ +export function classify(path: string): ModelSelectableFileKind { + const sidecar = HuggingFaceService.extractQuantMeta(path)?.sidecar; + + if (!sidecar) return ModelSelectableFileKind.MAIN; + + return isAuxSidecar(sidecar) ? ModelSelectableFileKind.AUX : ModelSelectableFileKind.DRAFT; +} + +/** Display label of a file: its quant, else the file name without the extension. */ +export function labelFor(path: string): string { + const meta = HuggingFaceService.extractQuantMeta(path); + + if (meta?.quant) return meta.quant; + + // Quantless sidecar files: the chip badge already carries the sidecar type, + // so the label only marks draft files; aux sidecars badge alone. + if (meta?.sidecar) return isDraftSidecar(meta.sidecar) ? DRAFT_FILE_LABEL : ''; + + const basename = path.split(PATH_SEPARATOR).pop() ?? path; + + return basename.replace(MODEL_ID.WEIGHT_EXTENSION_REGEX, ''); +} diff --git a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsDownloadOptions/index.ts b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsDownloadOptions/index.ts new file mode 100644 index 0000000000..b5c0ebef67 --- /dev/null +++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsDownloadOptions/index.ts @@ -0,0 +1,40 @@ +/** + * + * MODELS DISCOVER - DETAILS - DOWNLOAD OPTIONS + * + * The download area of the detail pane: GGUF files grouped by bit depth, each an + * independent download action chip, plus the standalone terminal command preview. + * + */ + +/** + * **ModelsDiscoverDetailsDownloadOptions** - GGUF download options + * + * Groups GGUF files by bit depth and renders one independent download + * action chip per file, plus the standalone terminal command preview. + */ +export { default as ModelsDiscoverDetailsDownloadOptions } from './ModelsDiscoverDetailsDownloadOptions.svelte'; + +/** + * **ModelsDiscoverDetailsDownloadOptionsRow** - One bit-depth group of quants + * + * A single bit-depth row inside ModelsDiscoverDetailsDownloadOptions: the + * depth label with its memory hint and the quant chips of that depth. + */ +export { default as ModelsDiscoverDetailsDownloadOptionsRow } from './ModelsDiscoverDetailsDownloadOptionsRow.svelte'; + +/** + * **ModelsDiscoverDetailsDownloadOptionsQuantDownloadButton** - One quant chip + * + * A single GGUF file as an independent action chip: download / retry when + * idle, pause / resume / cancel while in flight, delete when downloaded. + */ +export { default as ModelsDiscoverDetailsDownloadOptionsQuantDownloadButton } from './ModelsDiscoverDetailsDownloadOptionsQuantDownloadButton.svelte'; + +/** + * **ModelsDiscoverDetailsDownloadOptionsDownloadCommand** - Terminal command + * + * The `llama serve -hf ...` command box with inline quant selects and a copy + * button; owns its picks, nothing two-way binds them to the quant chips. + */ +export { default as ModelsDiscoverDetailsDownloadOptionsDownloadCommand } from './ModelsDiscoverDetailsDownloadOptionsDownloadCommand.svelte'; diff --git a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsHeader.svelte b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsHeader.svelte new file mode 100644 index 0000000000..a216283436 --- /dev/null +++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsHeader.svelte @@ -0,0 +1,94 @@ + + +
+
+
+ + +
+
+

{details.id ?? modelId}

+ + +
+ + {#if baseModels.length} +
+ {baseModels.join(', ')} + + + + +
+ {/if} +
+
+ + + + + View on Hugging Face + + + +
+ + + + +
diff --git a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsHfHubStats.svelte b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsHfHubStats.svelte new file mode 100644 index 0000000000..b848a9bfeb --- /dev/null +++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsHfHubStats.svelte @@ -0,0 +1,31 @@ + + +
+ {#if typeof details.downloads === 'number'} + + + {HuggingFaceService.formatDownloads(details.downloads)} + + {/if} + + {#if typeof details.likes === 'number'} + + + {HuggingFaceService.formatLikes(details.likes)} + + {/if} + + {#if details.lastModified} + Updated {HuggingFaceService.formatRelativeTime(details.lastModified)} + {/if} +
diff --git a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsMetadata.svelte b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsMetadata.svelte new file mode 100644 index 0000000000..9f0184ac9f --- /dev/null +++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsMetadata.svelte @@ -0,0 +1,80 @@ + + +{#if description} +

{description}

+{/if} + + +
+ {#if gguf?.total} + + {/if} + + {#if gguf?.context_length} + + {/if} + + {#if gguf?.architecture} + + {/if} + + {#if gguf?.chat_template} + + {/if} + + {#if licenseTag} + + {/if} + + {#if details.gated === true} + + gated + + {/if} +
+ +{#if gguf?.chat_template} + +{/if} diff --git a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsMetadataItem.svelte b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsMetadataItem.svelte new file mode 100644 index 0000000000..90c3c4c74f --- /dev/null +++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsMetadataItem.svelte @@ -0,0 +1,17 @@ + + + + + {label} + + {value} + diff --git a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsReadme.svelte b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsReadme.svelte new file mode 100644 index 0000000000..fbf043b797 --- /dev/null +++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsReadme.svelte @@ -0,0 +1,17 @@ + + +{#if readme} +
+ +
+{/if} diff --git a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsSkeleton.svelte b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsSkeleton.svelte new file mode 100644 index 0000000000..030d90fa58 --- /dev/null +++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsSkeleton.svelte @@ -0,0 +1,93 @@ + + + +
+ +
+
+
+ + +
+ + + +
+
+ + +
+ + +
+ + + + + +
+ + +
+
+ + + + + + + +
+
+
+ + +
+
+ {#each [0, 1, 2] as _, index (index)} +
+
+ + + +
+ +
+ + + +
+
+ {/each} +
+ +
+ + +
+ + + + + +
+ + +
+
+ + +
+ + + + + + + +
+
diff --git a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/index.ts b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/index.ts new file mode 100644 index 0000000000..2420bd5ef2 --- /dev/null +++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverDetails/index.ts @@ -0,0 +1,72 @@ +/** + * + * MODELS DISCOVER - DETAILS + * + * The right-hand detail pane of the discover view: header (avatar, name, stats, metadata + * chips, capability badges), the download options area and the model-card README. + * + */ + +/** + * **ModelsDiscoverDetails** - Model detail view + * + * Detail pane for the selected model. Loads its own data (details + GGUF file + * list) from HuggingFaceService based on the `modelId` route param. + */ +export { default as ModelsDiscoverDetails } from './ModelsDiscoverDetails.svelte'; + +/** + * **ModelsDiscoverDetailsHeader** - Detail view header + * + * Shows the model avatar (base org + quant org corner badge), name, base model + * info, stats, metadata chips and capability badges. + */ +export { default as ModelsDiscoverDetailsHeader } from './ModelsDiscoverDetailsHeader.svelte'; + +/** + * **ModelsDiscoverDetailsHfHubStats** - HuggingFace Hub stats row + * + * Downloads, likes and last-updated for the viewed model, formatted the way + * the Hub shows them. + */ +export { default as ModelsDiscoverDetailsHfHubStats } from './ModelsDiscoverDetailsHfHubStats.svelte'; + +/** + * **ModelsDiscoverDetailsSkeleton** - Detail view loading skeleton + * + * Static placeholder matching the detail layout: header with avatar and name, + * metadata chips, the download options box and readme text lines. + */ +export { default as ModelsDiscoverDetailsSkeleton } from './ModelsDiscoverDetailsSkeleton.svelte'; + +/** + * **ModelsDiscoverDetailsReadme** - Detail view README + * + * Renders the model card README as markdown. + */ +export { default as ModelsDiscoverDetailsReadme } from './ModelsDiscoverDetailsReadme.svelte'; + +/** + * **ModelsDiscoverDetailsMetadata** - Detail view metadata row + * + * The detail view's metadata chips (model size, context, architecture, license, + * chat template). + */ +export { default as ModelsDiscoverDetailsMetadata } from './ModelsDiscoverDetailsMetadata.svelte'; + +/** + * **ModelsDiscoverDetailsMetadataItem** - Single metadata chip + * + * One label | value chip of the detail view's metadata row (model size, + * context, architecture, license). + */ +export { default as ModelsDiscoverDetailsMetadataItem } from './ModelsDiscoverDetailsMetadataItem.svelte'; + +/** + * **ModelsDiscoverChatTemplateDialog** - Chat template viewer + * + * Shows the model's chat template in a scrollable dialog with a copy button. + */ +export { default as ModelsDiscoverChatTemplateDialog } from './ModelsDiscoverChatTemplateDialog.svelte'; + +export * from './ModelsDiscoverDetailsDownloadOptions'; diff --git a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverList/ModelsDiscoverList.svelte b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverList/ModelsDiscoverList.svelte new file mode 100644 index 0000000000..5ebff7230d --- /dev/null +++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverList/ModelsDiscoverList.svelte @@ -0,0 +1,41 @@ + + +
    + {#if loading} + {#each Array(loadingSkeletonRowsCount) as _, index (index)} + + {/each} + {:else} + {#each models as model (model.id)} + + {/each} + {/if} +
diff --git a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverList/ModelsDiscoverListItem.svelte b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverList/ModelsDiscoverListItem.svelte new file mode 100644 index 0000000000..b1b1cdb85b --- /dev/null +++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverList/ModelsDiscoverListItem.svelte @@ -0,0 +1,129 @@ + + +
  • + +
  • diff --git a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverList/ModelsDiscoverListItemSkeleton.svelte b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverList/ModelsDiscoverListItemSkeleton.svelte new file mode 100644 index 0000000000..851abc5238 --- /dev/null +++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverList/ModelsDiscoverListItemSkeleton.svelte @@ -0,0 +1,39 @@ + + + +
  • +
    + + +
    + + +
    + {#each badgeWidths as width, i (i)} + + {/each} +
    +
    +
    +
  • diff --git a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverList/ModelsDiscoverListSearch.svelte b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverList/ModelsDiscoverListSearch.svelte new file mode 100644 index 0000000000..dce949a0f6 --- /dev/null +++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverList/ModelsDiscoverListSearch.svelte @@ -0,0 +1,26 @@ + + +
    + handleInput(v)} {placeholder} size="sm" /> +
    diff --git a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverList/index.ts b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverList/index.ts new file mode 100644 index 0000000000..d888343a15 --- /dev/null +++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverList/index.ts @@ -0,0 +1,38 @@ +/** + * + * MODELS DISCOVER - LIST + * + * The discover sidebar column: a debounced search field above a navigable list of + * HuggingFace GGUF models, with skeleton rows while loading. + * + */ + +/** + * **ModelsDiscoverList** - Sidebar model list + * + * Renders the discover model list as a navigable column. Each row links to the + * model's detail and highlights the active one. + */ +export { default as ModelsDiscoverList } from './ModelsDiscoverList.svelte'; + +/** + * **ModelsDiscoverListSearch** - Sidebar search input + * + * Debounced search field for the model list. + */ +export { default as ModelsDiscoverListSearch } from './ModelsDiscoverListSearch.svelte'; + +/** + * **ModelsDiscoverListItem** - Single sidebar row + * + * One model entry in the discover sidebar list, selectable via `onSelect`. + */ +export { default as ModelsDiscoverListItem } from './ModelsDiscoverListItem.svelte'; + +/** + * **ModelsDiscoverListItemSkeleton** - Skeleton sidebar row + * + * Pulsing placeholder matching a ModelsDiscoverListItem row, shown while the + * list is loading. + */ +export { default as ModelsDiscoverListItemSkeleton } from './ModelsDiscoverListItemSkeleton.svelte'; diff --git a/tools/ui/src/lib/components/app/models/discover/index.ts b/tools/ui/src/lib/components/app/models/discover/index.ts new file mode 100644 index 0000000000..5926b75153 --- /dev/null +++ b/tools/ui/src/lib/components/app/models/discover/index.ts @@ -0,0 +1,39 @@ +/** + * + * MODELS DISCOVER + * + * Components for the Models Discover view: a sidebar search + list of + * HuggingFace GGUF models and a detail view for the selected model, used as the + * body of the discovery dialog. The list and detail trees live in their own + * subfolders; this barrel re-exports them alongside the shared leaves. + * + */ + +/** + * **ModelsDiscover** - Models discover explorer + * + * The complete discovery layout: a sidebar search + model list on the left and a + * detail view for the selected model on the right. Used as the body of the + * discovery dialog. + */ +export { default as ModelsDiscover } from './ModelsDiscover.svelte'; + +/** + * **ModelsDiscoverAvatar** - Org avatar for a model row + * + * Shows the org's avatar image, falling back to a monogram on a stable hue + * derived from the org name when the image fails to load. Shared by the list, + * the detail header and the model selector rows. + */ +export { default as ModelsDiscoverAvatar } from './ModelsDiscoverAvatar.svelte'; + +/** + * **ModelsDiscoverDownloadProgressBar** - Thin download progress bar + * + * Normalizes bytes to a 0..100% bar; can pin to the bottom edge as an overlay. + * Shared by the quant chips and the model selector's download rows. + */ +export { default as ModelsDiscoverDownloadProgressBar } from './ModelsDiscoverDownloadProgressBar.svelte'; + +export * from './ModelsDiscoverList'; +export * from './ModelsDiscoverDetails'; diff --git a/tools/ui/src/lib/components/ui/select/select-content.svelte b/tools/ui/src/lib/components/ui/select/select-content.svelte index f8792f8a9d..a8263819a1 100644 --- a/tools/ui/src/lib/components/ui/select/select-content.svelte +++ b/tools/ui/src/lib/components/ui/select/select-content.svelte @@ -88,7 +88,7 @@ {@render children?.()} diff --git a/tools/ui/src/lib/components/ui/select/select-trigger.svelte b/tools/ui/src/lib/components/ui/select/select-trigger.svelte index 9f642819ec..de3abcdf21 100644 --- a/tools/ui/src/lib/components/ui/select/select-trigger.svelte +++ b/tools/ui/src/lib/components/ui/select/select-trigger.svelte @@ -11,20 +11,26 @@ variant = 'default', ...restProps }: WithoutChild & { - size?: 'sm' | 'default'; + size?: 'xs' | 'sm' | 'default'; variant?: 'default' | 'plain'; } = $props(); + // Super small trigger: fits its selected value, for dense inline use. + const xsClasses = + "group flex h-6 w-fit items-center justify-between gap-1 rounded-md border border-border/30 bg-muted/60 px-2 py-0 text-xs whitespace-nowrap outline-none select-none transition-colors hover:bg-muted/80 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 data-[placeholder]:text-muted-foreground dark:border-border/20 dark:bg-muted/75 dark:hover:bg-muted [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='text-'])]:text-muted-foreground"; + const baseClasses = $derived( variant === 'plain' ? "group inline-flex w-full items-center justify-end gap-2 whitespace-nowrap px-0 py-0 text-sm font-medium text-muted-foreground transition-colors focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50 data-[placeholder]:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3 [&_svg:not([class*='text-'])]:text-muted-foreground" - : "flex w-fit items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none select-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground" + : size === 'xs' + ? xsClasses + : "flex w-fit items-center justify-between gap-2 rounded-md border border-border/30 bg-muted/60 px-3 py-2 text-sm whitespace-nowrap shadow-sm transition-colors outline-none select-none hover:bg-muted/80 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 dark:border-border/20 dark:bg-muted/75 dark:hover:bg-muted dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5 [&_svg:not([class*='text-'])]:text-muted-foreground" ); const chevronClasses = $derived( - variant === 'plain' + variant === 'plain' || size === 'xs' ? 'size-3 opacity-60 transition-transform group-data-[state=open]:-rotate-180' - : 'size-4 opacity-50' + : 'size-3.5 opacity-60 transition-transform group-data-[state=open]:-rotate-180' ); diff --git a/tools/ui/src/lib/stores/models-discover/index.svelte.ts b/tools/ui/src/lib/stores/models-discover/index.svelte.ts index 91a5cb6783..28cc66f00d 100644 --- a/tools/ui/src/lib/stores/models-discover/index.svelte.ts +++ b/tools/ui/src/lib/stores/models-discover/index.svelte.ts @@ -2,7 +2,7 @@ * modelsDiscoverStore - Models Discover browse state * * Owns the HuggingFace GGUF model list shown in the discover sidebar - * (DialogModelsDiscover). By default the list is the curated catalog set; + * (the Discover tab of DialogManageModels). By default the list is the curated catalog set; * search replaces it with matches across all of HuggingFace. Both paths fetch * the same fields, so a row renders the same badges and sizes either way. */ @@ -80,6 +80,7 @@ class ModelsDiscoverStore { this.catalog = catalog; const builds = this.catalogBuilds(); + const failedBuilds: HfCatalogBuild[] = []; this.defaultModels = []; @@ -98,7 +99,11 @@ class ModelsDiscoverStore { ); for (const { build, info, tree } of batch) { - if (!info) continue; + if (!info) { + failedBuilds.push(build); + + continue; + } this.catalogSizeRanges.set(build.repo, this.sizeRangeFor(build, tree)); @@ -112,7 +117,9 @@ class ModelsDiscoverStore { } } - this.fetched = true; + // stay incomplete when some repos failed, so the next mount retries + // them; succeeded repos are served from the service cache for free + this.fetched = failedBuilds.length === 0; } catch (err) { this.error = err instanceof Error ? err.message : 'Failed to fetch models'; } finally {