ui : extract the model params badge fallback into a hook

Move the option's fallback logic into useModelParamsFallback and reuse it for the dropdown and sheet trigger badges.

Assisted-by: pi:zai-org/GLM-5.3
This commit is contained in:
Aleksander Grygier
2026-09-02 12:40:18 +02:00
parent 55fe4b2aa4
commit d20d1fa36f
4 changed files with 101 additions and 56 deletions
@@ -14,6 +14,7 @@
import * as Tooltip from '$lib/components/ui/tooltip';
import { MODEL_SELECTOR_ICON, SETTINGS_KEYS } from '$lib/constants';
import { KeyboardKey, ServerModelStatus } from '$lib/enums';
import { useModelParamsFallback } from '$lib/hooks/use-model-params-fallback.svelte';
import { useModelsSelector } from '$lib/hooks/use-models-selector.svelte';
import { useReasoningMenu } from '$lib/hooks/use-reasoning-menu.svelte';
import { modelsStore, settingsStore } from '$lib/stores';
@@ -65,6 +66,15 @@
const reasoning = useReasoningMenu();
const selectedOption = $derived(ms.getDisplayOption());
const triggerModel = $derived(selectedOption?.model ?? null);
// Params badge fallback for trigger ids that carry no params token.
const { paramsFallback } = useModelParamsFallback({
modelId: () => triggerModel,
metaParams: () => selectedOption?.meta?.n_params
});
const showOrgNameInTrigger = $derived(
settingsStore.config[SETTINGS_KEYS.SHOW_MODEL_ORG_NAME_IN_TRIGGER] ?? false
);
@@ -179,8 +189,6 @@
<p class="text-xs text-muted-foreground">No models available.</p>
{/if}
{:else}
{@const selectedOption = ms.getDisplayOption()}
{@const triggerModel = selectedOption?.model}
{@const triggerStatus = triggerModel
? modelsStore.routerModels.find((m) => m.id === triggerModel)?.status?.value
: undefined}
@@ -223,6 +231,7 @@
hideOrgName={!showOrgNameInTrigger}
hideQuantization
modelId={selectedOption.model}
params={paramsFallback}
/>
{:else}
<span class="min-w-0 font-medium">Select model</span>
@@ -331,7 +340,6 @@
Sticks to the bottom of the content scrollport. -->
<div onfocusin={clearHighlight} onmouseenter={clearHighlight} role="none">
<ModelsSelectorReasoningPanel />
</div>
{/snippet}
</DropdownMenuSearchable>
@@ -367,6 +375,7 @@
hideOrgName={!showOrgNameInTrigger}
hideQuantization
modelId={selectedOption.model}
params={paramsFallback}
/>
{/if}
@@ -12,22 +12,13 @@
RotateCw
} from '@lucide/svelte';
import { ActionIcon, ModelId } from '$lib/components/app';
import {
HF_BASE_MODEL_TAG_REGEX,
ICON_CLASS_DEFAULT,
MODEL_ID,
PATH_SEPARATOR
} from '$lib/constants';
import { HF_BASE_MODEL_TAG_REGEX, ICON_CLASS_DEFAULT, PATH_SEPARATOR } from '$lib/constants';
import { ModelCapability, ServerModelStatus } from '$lib/enums';
import { HuggingFaceService, ModelsService } from '$lib/services';
import { useModelParamsFallback } from '$lib/hooks/use-model-params-fallback.svelte';
import { modelsStore } from '$lib/stores';
import type { ModelOption } from '$lib/types/models';
import {
formatParameters,
modelLoadFraction,
modelLoadProgressText,
normalizeModelName
} from '$lib/utils';
import { modelLoadFraction, modelLoadProgressText } from '$lib/utils';
interface Props {
option: ModelOption;
@@ -109,47 +100,10 @@
tools: option.capabilities.includes(ModelCapability.TOOL_USE)
}));
// Params badge fallback: the id usually carries the count (`Qwen3-8B`), but
// ids like `Kimi-K3` do not. Prefer the loaded model's meta `n_params`, then
// the HF GGUF total (`gguf.total`), fetched lazily like the models hub list
// does, only when neither the id nor the meta has it.
let parsedParams = $derived(parsedId.params);
let metaParams = $derived.by(() => {
const value = option.meta?.n_params;
return typeof value === 'number' && value > 0 ? value : null;
const { paramsFallback } = useModelParamsFallback({
modelId: () => option.model,
metaParams: () => option.meta?.n_params
});
// HF repo id: the `org/model` part without the `:revision/quant` suffix;
// local paths and bare names are not HF repos.
let hfRepoId = $derived.by(() => {
const [repo] = option.model.split(MODEL_ID.QUANTIZATION_SEPARATOR);
const normalized = normalizeModelName(repo ?? '');
return normalized.includes(MODEL_ID.ORG_SEPARATOR) ? normalized : null;
});
let fetchedParams = $state<number | null>(null);
$effect(() => {
fetchedParams = null;
if (parsedParams || metaParams || !hfRepoId) return;
let cancelled = false;
void HuggingFaceService.getDetails(hfRepoId).then((info) => {
if (!cancelled && info?.gguf?.total) fetchedParams = info.gguf.total;
});
return () => {
cancelled = true;
};
});
let paramsFallback = $derived(
!parsedParams && (metaParams ?? fetchedParams)
? formatParameters(metaParams ?? fetchedParams)
: undefined
);
</script>
<div
@@ -9,6 +9,7 @@
} from '$lib/components/app';
import * as Sheet from '$lib/components/ui/sheet';
import { ServerModelStatus } from '$lib/enums';
import { useModelParamsFallback } from '$lib/hooks/use-model-params-fallback.svelte';
import { useModelsSelector } from '$lib/hooks/use-models-selector.svelte';
import { modelsStore } from '$lib/stores';
import { modelLoadFraction } from '$lib/utils';
@@ -44,6 +45,15 @@
useGlobalSelection: () => useGlobalSelection
});
const selectedOption = $derived(ms.getDisplayOption());
const triggerModel = $derived(selectedOption?.model ?? null);
// Params badge fallback for trigger ids that carry no params token.
const { paramsFallback } = useModelParamsFallback({
metaParams: () => selectedOption?.meta?.n_params,
modelId: () => triggerModel
});
export function open() {
ms.handleOpenChange(true);
}
@@ -106,6 +116,7 @@
hideQuantization
hideTags
modelId={selectedOption?.model || ''}
params={paramsFallback}
/>
{/if}
@@ -190,7 +201,12 @@
>
<Package class="h-3.5 w-3.5 shrink-0" />
<ModelId class="font-medium" hideQuantization modelId={selectedOption?.model || ''} />
<ModelId
class="font-medium"
hideQuantization
modelId={selectedOption?.model || ''}
params={paramsFallback}
/>
{#if ms.updating}
<Loader2 class="h-3 w-3.5 shrink-0 animate-spin" />
@@ -0,0 +1,66 @@
import { MODEL_ID } from '$lib/constants';
import { HuggingFaceService, ModelsService } from '$lib/services';
import { formatParameters, normalizeModelName } from '$lib/utils';
export interface UseModelParamsFallbackOptions {
/** Model id to parse; null disables the fallback. */
modelId: () => string | null | undefined;
/** Server-reported parameter count from the model meta, when available. */
metaParams?: () => unknown;
}
/**
* Params badge fallback for model ids that carry no params token (`Kimi-K3`).
* Prefers the meta `n_params`, then the HF GGUF total (`gguf.total`) fetched
* lazily like the models hub list does, only when neither the id nor the meta
* has it. Returns the formatted badge text, or undefined when unavailable.
*/
export function useModelParamsFallback(opts: UseModelParamsFallbackOptions) {
const parsedParams = $derived.by(() => {
const id = opts.modelId();
return id ? ModelsService.parseModelId(id).params : null;
});
const metaParams = $derived.by(() => {
const value = opts.metaParams?.();
return typeof value === 'number' && value > 0 ? value : null;
});
// HF repo id: the `org/model` part without the `:revision/quant` suffix;
// local paths and bare names are not HF repos.
const hfRepoId = $derived.by(() => {
const id = opts.modelId();
if (!id) return null;
const [repo] = id.split(MODEL_ID.QUANTIZATION_SEPARATOR);
const normalized = normalizeModelName(repo ?? '');
return normalized.includes(MODEL_ID.ORG_SEPARATOR) ? normalized : null;
});
let fetchedParams = $state<number | null>(null);
$effect(() => {
fetchedParams = null;
if (parsedParams || metaParams || !hfRepoId) return;
let cancelled = false;
void HuggingFaceService.getDetails(hfRepoId).then((info) => {
if (!cancelled && info?.gguf?.total) fetchedParams = info.gguf.total;
});
return () => {
cancelled = true;
};
});
const paramsFallback = $derived(
!parsedParams && (metaParams ?? fetchedParams)
? formatParameters(metaParams ?? fetchedParams)
: undefined
);
return { paramsFallback };
}