ui : group discover and selector components into folders

Move the ModelsDiscover components into ModelsDiscoverList/ and
ModelsDiscoverDetails/ subfolders (with ModelsDiscoverDetailsDownloadOptions/
nested inside), and the model selector components into a ModelsSelector/
subfolder. Rename ModelsDiscoverModelDetails* to ModelsDiscoverDetails* for
consistency, add per-folder index.ts barrels, and keep shared leaves
(ModelId, ModelBadge, ModelLoadHighlight, ModelsDiscoverAvatar,
DownloadProgressBar) at their folder roots.
This commit is contained in:
Aleksander Grygier
2026-09-04 11:12:34 +02:00
parent 6d2574ecff
commit 587c43ef8f
33 changed files with 310 additions and 253 deletions
@@ -1,6 +1,6 @@
<script lang="ts">
import DownloadProgressBar from './discover/DownloadProgressBar.svelte';
import ModelsDiscoverAvatar from './discover/ModelsDiscoverAvatar.svelte';
import DownloadProgressBar from '../discover/DownloadProgressBar.svelte';
import ModelsDiscoverAvatar from '../discover/ModelsDiscoverAvatar.svelte';
import { Loader2, Pause, Play, X } from '@lucide/svelte';
import { ModelId } from '$lib/components/app';
import DialogConfirmation from '$lib/components/app/dialogs/DialogConfirmation.svelte';
@@ -1,5 +1,5 @@
<script lang="ts">
import ModelLoadHighlight from './ModelLoadHighlight.svelte';
import ModelLoadHighlight from '../ModelLoadHighlight.svelte';
import { ChevronDown, Lightbulb, Loader2 } from '@lucide/svelte';
import {
DialogModelInformation,
@@ -1,6 +1,6 @@
<script lang="ts">
import ModelsDiscoverAvatar from './discover/ModelsDiscoverAvatar.svelte';
import ModelLoadHighlight from './ModelLoadHighlight.svelte';
import ModelsDiscoverAvatar from '../discover/ModelsDiscoverAvatar.svelte';
import ModelLoadHighlight from '../ModelLoadHighlight.svelte';
import {
CircleAlert,
Heart,
@@ -1,5 +1,5 @@
<script lang="ts">
import ModelLoadHighlight from './ModelLoadHighlight.svelte';
import ModelLoadHighlight from '../ModelLoadHighlight.svelte';
import { ChevronDown, Loader2, Package } from '@lucide/svelte';
import {
DialogModelInformation,
@@ -0,0 +1,95 @@
/**
*
* MODELS SELECTOR
*
* Model selection UI for the chat form: a desktop dropdown and a mobile sheet,
* each backed by a shared grouped options list, option rows, in-flight download
* rows and an inline reasoning-effort picker. Supports two server modes:
* - **Single model mode**: Server runs with one model, selector shows model info
* - **Router mode**: Server runs with multiple models, selector enables switching
*
* Integrates with modelsStore for model data and serverStore for mode detection.
*
*/
/**
* **ModelsSelectorDropdown** - Model selection dropdown (desktop)
*
* Dropdown for selecting AI models with status indicators,
* search, and model information display. Adapts UI based on server mode.
*
* **Architecture:**
* - Uses DropdownMenuSearchable for model list
* - Integrates with modelsStore for model options and selection
* - Detects router vs single mode from serverStore
* - Opens DialogModelInformation for model details
*
* **Features:**
* - Searchable model list with keyboard navigation
* - Model status indicators (loading/ready/error/updating)
* - Model capabilities badges (vision, tools, etc.)
* - Current/active model highlighting
* - Model information dialog on info button click
* - Router mode: shows all available models with status
* - Single mode: shows current model name only
* - Loading/updating skeleton states
* - Global selection support for form integration
*
* @example
* ```svelte
* <ModelsSelectorDropdown
* currentModel={conversation.modelId}
* onModelChange={(id, name) => updateModel(id)}
* useGlobalSelection
* />
* ```
*/
export { default as ModelsSelectorDropdown } from './ModelsSelectorDropdown.svelte';
/**
* **ModelsSelectorList** - Grouped model options list
*
* Renders grouped model options (loaded, favorites, available) with section
* headers and org subgroups. Shared between ModelsSelectorDropdown and ModelsSelectorSheet
* to avoid template duplication.
*
* Accepts an optional `renderOption` snippet to customize how each option is
* rendered (e.g., to add keyboard navigation or highlighting).
*/
export { default as ModelsSelectorList } from './ModelsSelectorList.svelte';
/**
* **ModelsSelectorDownloadItem** - Single in-flight download row
*
* One "Download in progress" row for the models selector: live progress bar,
* pause / resume on click and a hover-revealed cancel, mirroring the discover
* quant chips.
*/
export { default as ModelsSelectorDownloadItem } from './ModelsSelectorDownloadItem.svelte';
/**
* **ModelsSelectorOption** - Single model option row
*
* Renders a single model option with selection state, favorite toggle,
* load/unload actions, status indicators, and an info button.
* Used inside ModelsSelectorList or directly in custom render snippets.
*/
export { default as ModelsSelectorOption } from './ModelsSelectorOption.svelte';
/**
* **ModelsSelectorReasoningPanel** - Inline reasoning effort picker
*
* Collapsible row that expands in place to reveal the reasoning effort levels.
* Used in the sticky footer of ModelsSelectorDropdown, where a flyout submenu
* would float over the model list it belongs to.
*/
export { default as ModelsSelectorReasoningPanel } from './ModelsSelectorReasoningPanel.svelte';
/**
* **ModelsSelectorSheet** - Mobile model selection sheet
*
* Bottom sheet variant of ModelsSelectorDropdown optimized for touch interaction
* on mobile devices. Same functionality as ModelsSelectorDropdown but uses Sheet UI
* instead of DropdownMenu.
*/
export { default as ModelsSelectorSheet } from './ModelsSelectorSheet.svelte';
@@ -1,8 +1,8 @@
<script lang="ts">
import {
ModelsDiscoverDetails,
ModelsDiscoverList,
ModelsDiscoverListSearch,
ModelsDiscoverModelDetails
ModelsDiscoverListSearch
} from '$lib/components/app/models/discover';
import { HuggingFaceService } from '$lib/services';
import { modelsHubStore } from '$lib/stores';
@@ -119,7 +119,7 @@
<main class="overflow-y-auto">
{#if selectedId}
<ModelsDiscoverModelDetails
<ModelsDiscoverDetails
{details}
error={detailError}
{files}
@@ -1,8 +1,8 @@
<script lang="ts">
import ModelsDiscoverModelDetailsDownloadOptions from './ModelsDiscoverModelDetailsDownloadOptions.svelte';
import ModelsDiscoverModelDetailsHeader from './ModelsDiscoverModelDetailsHeader.svelte';
import ModelsDiscoverModelDetailsReadme from './ModelsDiscoverModelDetailsReadme.svelte';
import ModelsDiscoverModelDetailsSkeleton from './ModelsDiscoverModelDetailsSkeleton.svelte';
import { ModelsDiscoverDetailsDownloadOptions } from './ModelsDiscoverDetailsDownloadOptions';
import ModelsDiscoverDetailsHeader from './ModelsDiscoverDetailsHeader.svelte';
import ModelsDiscoverDetailsReadme from './ModelsDiscoverDetailsReadme.svelte';
import ModelsDiscoverDetailsSkeleton from './ModelsDiscoverDetailsSkeleton.svelte';
import { ModelAuxSidecar } from '$lib/enums';
import { HuggingFaceService } from '$lib/services';
import type { HfModelDetailInfo, HfModelSibling } from '$lib/types/huggingface';
@@ -37,7 +37,9 @@
// Capabilities derived from HF metadata. Vision comes from an mmproj sidecar
// or a multimodal pipeline tag; tool use / reasoning from the chat template.
let hasMmproj = $derived(
files.some((f) => HuggingFaceService.extractQuantMeta(f.path)?.sidecar === ModelAuxSidecar.MMPROJ)
files.some(
(f) => HuggingFaceService.extractQuantMeta(f.path)?.sidecar === ModelAuxSidecar.MMPROJ
)
);
let hasVision = $derived(hasMmproj || details?.pipeline_tag === 'image-text-to-text');
let hasTools = $derived(detectToolUseSupport(gguf?.chat_template ?? ''));
@@ -72,14 +74,14 @@
</script>
{#if loading}
<ModelsDiscoverModelDetailsSkeleton />
<ModelsDiscoverDetailsSkeleton />
{:else if error}
<div class="flex h-full items-center justify-center py-20">
<p class="text-sm text-destructive">{error}</p>
</div>
{:else if details}
<div class="space-y-6 p-6">
<ModelsDiscoverModelDetailsHeader
<ModelsDiscoverDetailsHeader
{baseModels}
{details}
{gguf}
@@ -90,8 +92,8 @@
{modelId}
/>
<ModelsDiscoverModelDetailsDownloadOptions {bitDepthRows} {modelId} />
<ModelsDiscoverDetailsDownloadOptions {bitDepthRows} {modelId} />
<ModelsDiscoverModelDetailsReadme {readme} />
<ModelsDiscoverDetailsReadme {readme} />
</div>
{/if}
@@ -7,8 +7,8 @@
type QuantOption,
type SelectableFile
} from './download-options.utils';
import ModelsDiscoverModelDetailsDownloadOptionsDownloadCommand from './ModelsDiscoverModelDetailsDownloadOptionsDownloadCommand.svelte';
import ModelsDiscoverModelDetailsDownloadOptionsRow from './ModelsDiscoverModelDetailsDownloadOptionsRow.svelte';
import ModelsDiscoverDetailsDownloadOptionsDownloadCommand from './ModelsDiscoverDetailsDownloadOptionsDownloadCommand.svelte';
import ModelsDiscoverDetailsDownloadOptionsRow from './ModelsDiscoverDetailsDownloadOptionsRow.svelte';
import { HuggingFaceService, ModelsService } from '$lib/services';
import { modelsStore } from '$lib/stores';
@@ -120,17 +120,13 @@
lifecycle state; nothing here selects anything. -->
<div class="flex w-full flex-col divide-y divide-border/50 px-4 pb-1 dark:divide-border/35">
{#each rows as row (row.bitDepth)}
<ModelsDiscoverModelDetailsDownloadOptionsRow bitDepth={row.bitDepth} files={row.files} />
<ModelsDiscoverDetailsDownloadOptionsRow bitDepth={row.bitDepth} files={row.files} />
{/each}
</div>
<!-- Terminal command preview, standalone: its picks are not bound to the chips. -->
<div class="border-t border-border/50 px-4 pt-3.5 pb-4 dark:border-border/35">
<ModelsDiscoverModelDetailsDownloadOptionsDownloadCommand
{draftOptions}
{mainOptions}
{modelId}
/>
<ModelsDiscoverDetailsDownloadOptionsDownloadCommand {draftOptions} {mainOptions} {modelId} />
</div>
</section>
{/if}
@@ -1,6 +1,6 @@
<script lang="ts">
import DownloadProgressBar from '../../DownloadProgressBar.svelte';
import { type DownloadEntryState, labelFor } from './download-options.utils';
import DownloadProgressBar from './DownloadProgressBar.svelte';
import { Check, Download, Loader2, Pause, Play, RotateCw, X } from '@lucide/svelte';
import DialogConfirmation from '$lib/components/app/dialogs/DialogConfirmation.svelte';
import * as Tooltip from '$lib/components/ui/tooltip';
@@ -1,6 +1,6 @@
<script lang="ts">
import type { DownloadEntryState, SelectableFile } from './download-options.utils';
import ModelsDiscoverModelDetailsDownloadOptionsQuantDownloadButton from './ModelsDiscoverModelDetailsDownloadOptionsQuantDownloadButton.svelte';
import ModelsDiscoverDetailsDownloadOptionsQuantDownloadButton from './ModelsDiscoverDetailsDownloadOptionsQuantDownloadButton.svelte';
import { minMemoryTierGb } from '$lib/utils';
interface Props {
@@ -36,7 +36,7 @@
<div class="flex flex-wrap justify-end gap-1.5">
{#each files as file (file.path)}
<ModelsDiscoverModelDetailsDownloadOptionsQuantDownloadButton entry={file.state} {file} />
<ModelsDiscoverDetailsDownloadOptionsQuantDownloadButton entry={file.state} {file} />
{/each}
</div>
</div>
@@ -71,8 +71,8 @@ export function quantBitDepth(quant: string | null): number {
// LLAMA-APP-REUSE: --spec-type value for each draft sidecar; aux sidecars stay empty
export const SPEC_TYPE: Record<ModelSidecar, string> = {
[ModelAuxSidecar.MMPROJ]: '',
[ModelAuxSidecar.IMATRIX]: '',
[ModelAuxSidecar.MMPROJ]: '',
[ModelDraftSidecar.DFLASH]: 'draft-dflash',
[ModelDraftSidecar.DSPARK]: 'draft-dspark',
[ModelDraftSidecar.EAGLE3]: 'eagle3',
@@ -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';
@@ -1,6 +1,6 @@
<script lang="ts">
import ModelsDiscoverAvatar from './ModelsDiscoverAvatar.svelte';
import ModelsDiscoverModelDetailsMetadata from './ModelsDiscoverModelDetailsMetadata.svelte';
import ModelsDiscoverAvatar from '../ModelsDiscoverAvatar.svelte';
import ModelsDiscoverDetailsMetadata from './ModelsDiscoverDetailsMetadata.svelte';
import { Download, ExternalLink, Heart, Image, Lightbulb, Wrench } from '@lucide/svelte';
import * as Tooltip from '$lib/components/ui/tooltip';
import { ICON_CLASS_SM } from '$lib/constants';
@@ -137,5 +137,5 @@
{/if}
</div>
<ModelsDiscoverModelDetailsMetadata {details} {gguf} {licenseTag} {modelId} />
<ModelsDiscoverDetailsMetadata {details} {gguf} {licenseTag} {modelId} />
</header>
@@ -1,6 +1,6 @@
<script lang="ts">
import ModelsDiscoverChatTemplateDialog from './ModelsDiscoverChatTemplateDialog.svelte';
import ModelsDiscoverModelDetailsMetadataItem from './ModelsDiscoverModelDetailsMetadataItem.svelte';
import ModelsDiscoverDetailsMetadataItem from './ModelsDiscoverDetailsMetadataItem.svelte';
import { MessageSquareCode } from '@lucide/svelte';
import { modelsHubStore } from '$lib/stores';
import type { HfModelDetailInfo, HfModelGguf } from '$lib/types/huggingface';
@@ -31,21 +31,21 @@
<!-- Metadata chips: label | value pairs, matching the HF model page style -->
<div class="flex flex-wrap items-center gap-1.5">
{#if gguf?.total}
<ModelsDiscoverModelDetailsMetadataItem
<ModelsDiscoverDetailsMetadataItem
label="Model size"
value="{formatParameters(gguf.total)} params"
/>
{/if}
{#if gguf?.context_length}
<ModelsDiscoverModelDetailsMetadataItem
<ModelsDiscoverDetailsMetadataItem
label="Context"
value={gguf.context_length.toLocaleString()}
/>
{/if}
{#if gguf?.architecture}
<ModelsDiscoverModelDetailsMetadataItem label="Architecture" value={gguf.architecture} />
<ModelsDiscoverDetailsMetadataItem label="Architecture" value={gguf.architecture} />
{/if}
{#if gguf?.chat_template}
@@ -60,7 +60,7 @@
{/if}
{#if licenseTag}
<ModelsDiscoverModelDetailsMetadataItem label="License" value={licenseTag} />
<ModelsDiscoverDetailsMetadataItem label="License" value={licenseTag} />
{/if}
{#if details.gated === true}
@@ -2,7 +2,7 @@
import { Skeleton } from '$lib/components/ui/skeleton';
</script>
<!-- Static skeleton of ModelsDiscoverModelDetails: header, metadata chips, download options and readme lines. -->
<!-- Static skeleton of ModelsDiscoverDetails: header, metadata chips, download options and readme lines. -->
<div class="space-y-6 p-6" data-slot="model-details-skeleton">
<!-- Header: avatar, name with capability icons, base model line, HF link -->
<div class="space-y-3">
@@ -0,0 +1,64 @@
/**
*
* MODELS DISCOVER — DETAILS
*
* The right-hand detail pane of the hub: 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';
/**
* **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';
@@ -1,6 +1,6 @@
<script lang="ts">
import ModelId from '../ModelId.svelte';
import ModelsDiscoverAvatar from './ModelsDiscoverAvatar.svelte';
import ModelId from '../../ModelId.svelte';
import ModelsDiscoverAvatar from '../ModelsDiscoverAvatar.svelte';
import { isAuxSidecar, type ModelSidecar } from '$lib/constants';
import { HuggingFaceService, ModelsService } from '$lib/services';
import { modelsHubStore } from '$lib/stores';
@@ -0,0 +1,38 @@
/**
*
* MODELS DISCOVER — LIST
*
* The hub's 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 hub's model list as a navigable column. Each row links to the
* model's detail route 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 sidebar list. Links to `/models-hub/[org]/[model]`.
*/
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';
@@ -1,9 +1,11 @@
/**
*
* MODELS HUB
* MODELS DISCOVER
*
* Components for the Models Hub route (`/models-hub`): a sidebar list of
* HuggingFace GGUF models and a detail view for the selected model.
* Components for the Models Discover hub: 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.
*
*/
@@ -16,125 +18,22 @@
*/
export { default as ModelsDiscover } from './ModelsDiscover.svelte';
/**
* **ModelsDiscoverList** - Sidebar model list
*
* Renders the hub's model list as a navigable column. Each row links to the
* model's detail route 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';
/**
* **ModelsDiscoverItem** - Single sidebar row
*
* One model entry in the sidebar list. Links to `/models-hub/[org]/[model]`.
*/
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';
/**
* **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.
* 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';
/**
* **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 ModelsDiscoverModelDetails } from './ModelsDiscoverModelDetails.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 ModelsDiscoverModelDetailsHeader } from './ModelsDiscoverModelDetailsHeader.svelte';
/**
* **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 ModelsDiscoverModelDetailsDownloadOptions } from './ModelsDiscoverModelDetailsDownloadOptions.svelte';
/**
* **ModelsDiscoverDetailsDownloadOptionsRow** - One bit-depth group of quants
*
* A single bit-depth row inside ModelsDiscoverModelDetailsDownloadOptions: the
* depth label with its memory hint and the quant chips of that depth.
*/
export { default as ModelsDiscoverModelDetailsDownloadOptionsRow } from './ModelsDiscoverModelDetailsDownloadOptionsRow.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 ModelsDiscoverModelDetailsDownloadOptionsQuantDownloadButton } from './ModelsDiscoverModelDetailsDownloadOptionsQuantDownloadButton.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 ModelsDiscoverModelDetailsDownloadOptionsDownloadCommand } from './ModelsDiscoverModelDetailsDownloadOptionsDownloadCommand.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 ModelsDiscoverModelDetailsSkeleton } from './ModelsDiscoverModelDetailsSkeleton.svelte';
/**
* **ModelsDiscoverDetailsMetadataItem** - Single metadata chip
*
* One label | value chip of the detail view's metadata row (model size,
* context, architecture, license).
*/
export { default as ModelsDiscoverModelDetailsMetadataItem } from './ModelsDiscoverModelDetailsMetadataItem.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';
/**
* **ModelsDiscoverDetailsReadme** - Detail view README
*
* Renders the model card README as markdown.
*/
export { default as ModelsDiscoverModelDetailsReadme } from './ModelsDiscoverModelDetailsReadme.svelte';
/**
* **DownloadProgressBar** - 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 DownloadProgressBar } from './DownloadProgressBar.svelte';
export * from './ModelsDiscoverList';
export * from './ModelsDiscoverDetails';
@@ -8,90 +8,11 @@
*
* Integrates with modelsStore for model data and serverStore for mode detection.
*
* The selection UI lives in the ModelsSelector subfolder; the shared model
* display primitives (id / badge rendering) stay here.
*
*/
/**
* **ModelsSelectorDropdown** - Model selection dropdown (desktop)
*
* Dropdown for selecting AI models with status indicators,
* search, and model information display. Adapts UI based on server mode.
*
* **Architecture:**
* - Uses DropdownMenuSearchable for model list
* - Integrates with modelsStore for model options and selection
* - Detects router vs single mode from serverStore
* - Opens DialogModelInformation for model details
*
* **Features:**
* - Searchable model list with keyboard navigation
* - Model status indicators (loading/ready/error/updating)
* - Model capabilities badges (vision, tools, etc.)
* - Current/active model highlighting
* - Model information dialog on info button click
* - Router mode: shows all available models with status
* - Single mode: shows current model name only
* - Loading/updating skeleton states
* - Global selection support for form integration
*
* @example
* ```svelte
* <ModelsSelectorDropdown
* currentModel={conversation.modelId}
* onModelChange={(id, name) => updateModel(id)}
* useGlobalSelection
* />
* ```
*/
export { default as ModelsSelectorDropdown } from './ModelsSelectorDropdown.svelte';
/**
* **ModelsSelectorList** - Grouped model options list
*
* Renders grouped model options (loaded, favorites, available) with section
* headers and org subgroups. Shared between ModelsSelectorDropdown and ModelsSelectorSheet
* to avoid template duplication.
*
* Accepts an optional `renderOption` snippet to customize how each option is
* rendered (e.g., to add keyboard navigation or highlighting).
*/
export { default as ModelsSelectorList } from './ModelsSelectorList.svelte';
/**
* **ModelsSelectorDownloadItem** - Single in-flight download row
*
* One "Download in progress" row for the models selector: live progress bar,
* pause / resume on click and a hover-revealed cancel, mirroring the discover
* quant chips.
*/
export { default as ModelsSelectorDownloadItem } from './ModelsSelectorDownloadItem.svelte';
/**
* **ModelsSelectorOption** - Single model option row
*
* Renders a single model option with selection state, favorite toggle,
* load/unload actions, status indicators, and an info button.
* Used inside ModelsSelectorList or directly in custom render snippets.
*/
export { default as ModelsSelectorOption } from './ModelsSelectorOption.svelte';
/**
* **ModelsSelectorReasoningPanel** - Inline reasoning effort picker
*
* Collapsible row that expands in place to reveal the reasoning effort levels.
* Used in the sticky footer of ModelsSelectorDropdown, where a flyout submenu
* would float over the model list it belongs to.
*/
export { default as ModelsSelectorReasoningPanel } from './ModelsSelectorReasoningPanel.svelte';
/**
* **ModelsSelectorSheet** - Mobile model selection sheet
*
* Bottom sheet variant of ModelsSelectorDropdown optimized for touch interaction
* on mobile devices. Same functionality as ModelsSelectorDropdown but uses Sheet UI
* instead of DropdownMenu.
*/
export { default as ModelsSelectorSheet } from './ModelsSelectorSheet.svelte';
/** * **ModelBadge** - Model name display badge
*
* Compact badge showing current model name with package icon.
@@ -127,3 +48,5 @@ export { default as ModelBadge } from './ModelBadge.svelte';
* Respects the user's `showRawModelNames` setting.
*/
export { default as ModelId } from './ModelId.svelte';
export * from './ModelsSelector';
@@ -8,7 +8,7 @@
* with matching models across all of HuggingFace. Both paths fetch the same
* fields (chat template, context length, repo files), so a row renders the same
* badges and sizes whether it came from the catalog or from a query.
* Detail data is loaded by ModelsDiscoverModelDetails, not here.
* Detail data is loaded by ModelsDiscoverDetails, not here.
*/
import { isAuxSidecar } from '$lib/constants';
@@ -1,11 +1,11 @@
<script lang="ts" module>
import { mockDetails, mockSiblings } from './fixtures/models-discover';
import { defineMeta } from '@storybook/addon-svelte-csf';
import ModelsDiscoverChatTemplateDialog from '$lib/components/app/models/discover/ModelsDiscoverChatTemplateDialog.svelte';
import ModelsDiscoverModelDetails from '$lib/components/app/models/discover/ModelsDiscoverModelDetails.svelte';
import ModelsDiscoverModelDetailsDownloadOptions from '$lib/components/app/models/discover/ModelsDiscoverModelDetailsDownloadOptions.svelte';
import ModelsDiscoverModelDetailsHeader from '$lib/components/app/models/discover/ModelsDiscoverModelDetailsHeader.svelte';
import ModelsDiscoverModelDetailsReadme from '$lib/components/app/models/discover/ModelsDiscoverModelDetailsReadme.svelte';
import ModelsDiscoverChatTemplateDialog from '$lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverChatTemplateDialog.svelte';
import ModelsDiscoverDetails from '$lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetails.svelte';
import ModelsDiscoverDetailsDownloadOptions from '$lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsDownloadOptions/ModelsDiscoverDetailsDownloadOptions.svelte';
import ModelsDiscoverDetailsHeader from '$lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsHeader.svelte';
import ModelsDiscoverDetailsReadme from '$lib/components/app/models/discover/ModelsDiscoverDetails/ModelsDiscoverDetailsReadme.svelte';
import type { HfModelSibling } from '$lib/types';
const { Story } = defineMeta({
@@ -31,7 +31,7 @@
<Story name="Header">
<div class="w-160 p-4">
<ModelsDiscoverModelDetailsHeader
<ModelsDiscoverDetailsHeader
baseModels={mockDetails.cardData?.base_model ? [String(mockDetails.cardData.base_model)] : []}
details={mockDetails}
hasReasoning
@@ -47,7 +47,7 @@
<Story name="Download options">
<div class="w-200 p-4">
<ModelsDiscoverModelDetailsDownloadOptions
<ModelsDiscoverDetailsDownloadOptions
bitDepthRows={[
{ bitDepth: 4, files: files.filter((f) => f.path.includes('Q4_K_M')) },
{ bitDepth: 8, files: files.filter((f) => f.path.includes('Q8_0')) },
@@ -60,7 +60,7 @@
<Story name="Download options (unknown device)">
<div class="w-200 p-4">
<ModelsDiscoverModelDetailsDownloadOptions
<ModelsDiscoverDetailsDownloadOptions
bitDepthRows={[
{ bitDepth: 4, files: files.filter((f) => f.path.includes('Q4_K_M')) },
{ bitDepth: 8, files: files.filter((f) => f.path.includes('Q8_0')) }
@@ -74,7 +74,7 @@
paused, downloaded and the failed retry badge. -->
<Story name="Download options (download states)">
<div class="w-200 p-4">
<ModelsDiscoverModelDetailsDownloadOptions
<ModelsDiscoverDetailsDownloadOptions
bitDepthRows={[
{ bitDepth: 4, files: files.filter((f) => f.path.includes('Q4_K_M')) },
{ bitDepth: 8, files: files.filter((f) => f.path.includes('Q8_0')) },
@@ -99,7 +99,7 @@
<Story name="Readme">
<div class="w-160 p-4">
<ModelsDiscoverModelDetailsReadme {readme} />
<ModelsDiscoverDetailsReadme {readme} />
</div>
</Story>
@@ -111,7 +111,7 @@
<Story name="Details (loading)">
<div class="h-96 w-200 border">
<ModelsDiscoverModelDetails
<ModelsDiscoverDetails
details={null}
files={[]}
loading
@@ -123,7 +123,7 @@
<Story name="Details (error)">
<div class="h-96 w-200 border">
<ModelsDiscoverModelDetails
<ModelsDiscoverDetails
details={null}
error="Model not found"
files={[]}
@@ -135,7 +135,7 @@
<Story name="Details (loaded)">
<div class="h-96 w-200 overflow-y-auto border">
<ModelsDiscoverModelDetails
<ModelsDiscoverDetails
details={mockDetails}
{files}
modelId={mockDetails.id ?? 'ggml-org/gemma-4-12b-it-GGUF'}
@@ -2,8 +2,8 @@
import { mockGemma, mockListModels, mockQwen } from './fixtures/models-discover';
import { defineMeta } from '@storybook/addon-svelte-csf';
import ModelsDiscoverAvatar from '$lib/components/app/models/discover/ModelsDiscoverAvatar.svelte';
import ModelsDiscoverList from '$lib/components/app/models/discover/ModelsDiscoverList.svelte';
import ModelsDiscoverListItem from '$lib/components/app/models/discover/ModelsDiscoverListItem.svelte';
import ModelsDiscoverList from '$lib/components/app/models/discover/ModelsDiscoverList/ModelsDiscoverList.svelte';
import ModelsDiscoverListItem from '$lib/components/app/models/discover/ModelsDiscoverList/ModelsDiscoverListItem.svelte';
const { Story } = defineMeta({
tags: ['autodocs'],
@@ -1,7 +1,7 @@
<script lang="ts" module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import ModelsSelectorList from '$lib/components/app/models/ModelsSelectorList.svelte';
import ModelsSelectorOption from '$lib/components/app/models/ModelsSelectorOption.svelte';
import ModelsSelectorList from '$lib/components/app/models/ModelsSelector/ModelsSelectorList.svelte';
import ModelsSelectorOption from '$lib/components/app/models/ModelsSelector/ModelsSelectorOption.svelte';
import type { GroupedModelOptions, ModelItem } from '$lib/components/app/navigation/utils';
import { ServerModelStatus } from '$lib/enums';
import { modelsStore } from '$lib/stores/models/index.svelte';