From ca5c3be7f6989b700073997414ed08cb14da7436 Mon Sep 17 00:00:00 2001 From: Aleksander Grygier Date: Fri, 4 Sep 2026 21:37:38 +0200 Subject: [PATCH] ui : shared model display primitives Extract ModelCapabilityIcons (canonical Tools/Reasoning/Vision/Video/Audio order) out of ModelId and reuse it there, add the shared DialogConfirmDownload for destructive download actions, the discover org avatar with dark-mode inversion and the thin download progress bar, and rework ModelId badges to take thinking/tool-use support directly. Assisted-by: pi:GLM-5.3-Flash --- .../app/dialogs/DialogConfirmDownload.svelte | 60 +++++ .../src/lib/components/app/dialogs/index.ts | 11 + .../app/models/ModelCapabilityIcons.svelte | 93 +++++++ .../lib/components/app/models/ModelId.svelte | 255 +++++++++++------- .../app/models/ModelsSelectorOption.svelte | 3 +- .../discover/DownloadProgressBar.svelte | 32 +++ .../discover/ModelsDiscoverAvatar.svelte | 105 ++++++++ .../ui/src/lib/components/app/models/index.ts | 9 + tools/ui/src/lib/constants/ui.constants.ts | 3 + tools/ui/src/lib/enums/index.ts | 2 +- tools/ui/src/lib/enums/model.enums.ts | 10 + tools/ui/src/lib/utils/formatters.ts | 5 +- 12 files changed, 492 insertions(+), 96 deletions(-) create mode 100644 tools/ui/src/lib/components/app/dialogs/DialogConfirmDownload.svelte create mode 100644 tools/ui/src/lib/components/app/models/ModelCapabilityIcons.svelte create mode 100644 tools/ui/src/lib/components/app/models/discover/DownloadProgressBar.svelte create mode 100644 tools/ui/src/lib/components/app/models/discover/ModelsDiscoverAvatar.svelte diff --git a/tools/ui/src/lib/components/app/dialogs/DialogConfirmDownload.svelte b/tools/ui/src/lib/components/app/dialogs/DialogConfirmDownload.svelte new file mode 100644 index 0000000000..e772810e9c --- /dev/null +++ b/tools/ui/src/lib/components/app/dialogs/DialogConfirmDownload.svelte @@ -0,0 +1,60 @@ + + + diff --git a/tools/ui/src/lib/components/app/dialogs/index.ts b/tools/ui/src/lib/components/app/dialogs/index.ts index 1ffa7a2b9e..9b0b62bd08 100644 --- a/tools/ui/src/lib/components/app/dialogs/index.ts +++ b/tools/ui/src/lib/components/app/dialogs/index.ts @@ -108,6 +108,17 @@ export { default as DialogExportSettings } from './DialogExportSettings.svelte'; */ export { default as DialogConfirmation } from './DialogConfirmation.svelte'; +/** + * **DialogConfirmDownload** - Confirm a destructive download action + * + * Shared confirmation for stopping/cancelling an in-flight download or deleting + * a downloaded model, used by the discover quant chips and the model selector's + * download rows so both word the action identically. Owns the copy and the + * default store removal; render one instance per surface keyed by the acted-on + * repo:tag. + */ +export { default as DialogConfirmDownload } from './DialogConfirmDownload.svelte'; + /** * **DialogConversationRename** - Rename a conversation * diff --git a/tools/ui/src/lib/components/app/models/ModelCapabilityIcons.svelte b/tools/ui/src/lib/components/app/models/ModelCapabilityIcons.svelte new file mode 100644 index 0000000000..c9505a95eb --- /dev/null +++ b/tools/ui/src/lib/components/app/models/ModelCapabilityIcons.svelte @@ -0,0 +1,93 @@ + + + + {#if supportsToolUse && !hideCapabilities} + + + + + + +

Tool use

+
+
+ {/if} + + {#if supportsThinking && !hideCapabilities} + + + + + + +

Reasoning

+
+
+ {/if} + + {#if hasModalityIcons && !hideModalities} + + {#if modalities?.vision} + + + + + + +

Vision

+
+
+ {/if} + + {#if modalities?.video} + + + + + +

Video

+
+
+ {/if} + + {#if modalities?.audio} + + + + + + +

Audio

+
+
+ {/if} +
+ {/if} +
diff --git a/tools/ui/src/lib/components/app/models/ModelId.svelte b/tools/ui/src/lib/components/app/models/ModelId.svelte index 0b9722a73c..b75d7a7fd1 100644 --- a/tools/ui/src/lib/components/app/models/ModelId.svelte +++ b/tools/ui/src/lib/components/app/models/ModelId.svelte @@ -1,45 +1,66 @@ {#if resolvedShowRaw} {:else} {#snippet nameAndBadges()} - - {#if !hideOrgName && parsed.orgName}{parsed.orgName}/{/if}{displayName} - - - - {#if parsed.params} - - {parsed.params}{parsed.activatedParams ? `-${parsed.activatedParams}` : ''} - - {/if} - - {#if parsed.quantization && !resolvedHideQuantization} - - {parsed.quantization} - - {/if} - - {#if primaryAlias} - {#if primaryAlias !== parsed.modelName} - {parsed.modelName ?? modelId} - {/if} - {:else if uniqueAliases.length > 1} - {#each uniqueAliases as alias (alias)} - {alias} - {/each} - {/if} - - {#if uniqueTags.length > 0 && !resolvedHideTags} - {#each uniqueTags as tag (tag)} - {tag} - {/each} - {/if} - - {/snippet} - - - {#if showRawTooltip} - - - {@render nameAndBadges()} - - - -

{modelId}

-
-
- {:else} - {@render nameAndBadges()} + {#if !hideName} + + {#if !hideOrgName && parsed.orgName}{parsed.orgName}/{/if}{displayName} + {/if} - {#if activeCapabilities.length > 0 || activeModalities.length > 0} - - {#each activeCapabilities as capability (capability)} - {@const CapabilityIcon = CAPABILITY_ICONS[capability]} + {#if hasBadges} + + {#if parsed.sidecar} + + {parsed.sidecar} + + {/if} - - - - + {#if parsed.params && !hideParameters} + + {parsed.params}{parsed.activatedParams ? `-${parsed.activatedParams}` : ''} + + {/if} - -

{CAPABILITY_LABELS[capability]}

-
-
+ {#each uniqueDraftSidecars as sidecar (sidecar)} + + {sidecar} + {/each} - {#each activeModalities as modality (modality)} - {@const ModalityIcon = MODALITY_ICONS[modality]} + {#if parsed.quantization && !resolvedHideQuantization} + + {parsed.quantization} + + {/if} - - - - + {#if primaryAlias} + {#if primaryAlias !== parsed.modelName} + + {parsed.modelName ?? modelId} + + {/if} + {:else if uniqueAliases.length > 1} + {#each uniqueAliases.slice(0, MAX_ALIAS_BADGES) as alias (alias)} + + {alias} + + {/each} - -

{MODALITY_LABELS[modality]}

-
-
- {/each} + {#if uniqueAliases.length > MAX_ALIAS_BADGES} + + +{uniqueAliases.length - MAX_ALIAS_BADGES} more + + {/if} + {/if} + + {#if uniqueTags.length > 0 && !resolvedHideTags} + {#each uniqueTags as tag (tag)} + {tag} + {/each} + {/if} +
+ {/if} + {/snippet} + + + + {#if showRawTooltip} + + + {@render nameAndBadges()} + + + +

{modelId}

+
+
+ {:else} + {@render nameAndBadges()} + {/if} + + {#if !iconsOnNewLine} + + {/if} +
+ + {#if iconsOnNewLine || contextLength || sizeRange} + + {#if iconsOnNewLine} + + {/if} + + {#if contextLength} + + + + {formatParameters(contextLength)} + + {/if} + + {#if sizeRange} + + + + {HuggingFaceService.formatSizeRange(sizeRange.min, sizeRange.max)} + + {/if} {/if}
diff --git a/tools/ui/src/lib/components/app/models/ModelsSelectorOption.svelte b/tools/ui/src/lib/components/app/models/ModelsSelectorOption.svelte index 63b8ac6647..cee7d0c3b5 100644 --- a/tools/ui/src/lib/components/app/models/ModelsSelectorOption.svelte +++ b/tools/ui/src/lib/components/app/models/ModelsSelectorOption.svelte @@ -86,12 +86,13 @@ > diff --git a/tools/ui/src/lib/components/app/models/discover/DownloadProgressBar.svelte b/tools/ui/src/lib/components/app/models/discover/DownloadProgressBar.svelte new file mode 100644 index 0000000000..c3a89c0ba4 --- /dev/null +++ b/tools/ui/src/lib/components/app/models/discover/DownloadProgressBar.svelte @@ -0,0 +1,32 @@ + + +{#if overlay} +
+
+
+{:else} +
+
+
+{/if} diff --git a/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverAvatar.svelte b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverAvatar.svelte new file mode 100644 index 0000000000..394229a37a --- /dev/null +++ b/tools/ui/src/lib/components/app/models/discover/ModelsDiscoverAvatar.svelte @@ -0,0 +1,105 @@ + + + + {#if avatarError} + + {:else} +
+ (avatarError = true)} + src={HuggingFaceService.getAvatarUrl(org)} + /> +
+ {/if} + + {#if quantOrg && quantOrg !== org} + + + {#if quantError} + + {:else} + (quantError = true)} + src={HuggingFaceService.getAvatarUrl(quantOrg)} + /> + {/if} + + + +

{quantOrg}

+
+
+ {/if} +
diff --git a/tools/ui/src/lib/components/app/models/index.ts b/tools/ui/src/lib/components/app/models/index.ts index 3ac6ecb678..f6a9f1f9ba 100644 --- a/tools/ui/src/lib/components/app/models/index.ts +++ b/tools/ui/src/lib/components/app/models/index.ts @@ -109,3 +109,12 @@ export { default as ModelBadge } from './ModelBadge.svelte'; * Respects the user's `showRawModelNames` setting. */ export { default as ModelId } from './ModelId.svelte'; + +/** + * **ModelCapabilityIcons** - Capability and modality icon row + * + * The shared tool-use / reasoning / vision / video / audio icon cluster with + * tooltips, used by ModelId and the discover details header so the order and + * styling stay consistent across every model-id surface. + */ +export { default as ModelCapabilityIcons } from './ModelCapabilityIcons.svelte'; diff --git a/tools/ui/src/lib/constants/ui.constants.ts b/tools/ui/src/lib/constants/ui.constants.ts index f40cecfb61..20696f987d 100644 --- a/tools/ui/src/lib/constants/ui.constants.ts +++ b/tools/ui/src/lib/constants/ui.constants.ts @@ -44,6 +44,9 @@ export const STATS_UNITS = { export const DEFAULT_MOBILE_BREAKPOINT = 768; +/** Orgs whose avatar is dark and needs inverting in dark mode. */ +export const DARK_INVERT_AVATAR_ORGS = ['openai']; + /** Icon used for the model selector and the `/model` slash command. */ export const MODEL_SELECTOR_ICON = Package; diff --git a/tools/ui/src/lib/enums/index.ts b/tools/ui/src/lib/enums/index.ts index efc78e9d38..6b529b66e4 100644 --- a/tools/ui/src/lib/enums/index.ts +++ b/tools/ui/src/lib/enums/index.ts @@ -77,7 +77,7 @@ export { ModelSelectableFileKind } from './model.enums'; -export { ModelDownloadStopRequest } from './model.enums'; +export { ModelDownloadConfirmAction, ModelDownloadStopRequest } from './model.enums'; export { ServerRole, ServerModelStatus, ServerModelsSseEventType } from './server.enums'; diff --git a/tools/ui/src/lib/enums/model.enums.ts b/tools/ui/src/lib/enums/model.enums.ts index 54913c3d48..0b54ec1631 100644 --- a/tools/ui/src/lib/enums/model.enums.ts +++ b/tools/ui/src/lib/enums/model.enums.ts @@ -55,3 +55,13 @@ export enum ModelDownloadStopRequest { CANCEL = 'cancel', PAUSE = 'pause' } + +/** + * Destructive download action the user is asked to confirm: stop and discard an + * in-flight download, or delete an already-downloaded model from disk. Both + * resolve through the same store removal call, differing only in the copy. + */ +export enum ModelDownloadConfirmAction { + CANCEL = 'cancel', + DELETE = 'delete' +} diff --git a/tools/ui/src/lib/utils/formatters.ts b/tools/ui/src/lib/utils/formatters.ts index 27555a47be..f9f6477dcb 100644 --- a/tools/ui/src/lib/utils/formatters.ts +++ b/tools/ui/src/lib/utils/formatters.ts @@ -28,6 +28,9 @@ export function formatFileSize(bytes: number | unknown): string { /** * Format parameter count to human-readable format (B, M, K) * + * Billions render as whole numbers (`176.94e9` -> `177B`): the decimals are + * noise at badge sizes and id-parsed counts are whole anyway (`Qwen3-8B`). + * * @param params - Parameter count * @returns Human-readable parameter count */ @@ -35,7 +38,7 @@ export function formatParameters(params: number | unknown): string { if (typeof params !== 'number') return 'Unknown'; if (params >= 1e9) { - return `${(params / 1e9).toFixed(2)}B`; + return `${Math.round(params / 1e9)}B`; } if (params >= 1e6) {