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 {