From 0fad79b8673d5fe00350928fc7931270020440a4 Mon Sep 17 00:00:00 2001 From: Aleksander Grygier Date: Thu, 24 Sep 2026 06:54:14 +0200 Subject: [PATCH] ui : fold the oai providers under one repo Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash --- .../models/ModelsManager/ModelsManager.svelte | 6 ++++- .../ModelsManagerModelsTable.svelte | 17 ++++++++----- .../app/models/ModelsManager/utils.ts | 24 +++++++++++++------ 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/tools/ui/src/lib/components/app/models/ModelsManager/ModelsManager.svelte b/tools/ui/src/lib/components/app/models/ModelsManager/ModelsManager.svelte index 34dd455270..93c5e29097 100644 --- a/tools/ui/src/lib/components/app/models/ModelsManager/ModelsManager.svelte +++ b/tools/ui/src/lib/components/app/models/ModelsManager/ModelsManager.svelte @@ -148,7 +148,11 @@ ordered.push({ backendId: null, isLocal: false, - items: leading(chatOnly), + // one repo, one row per provider that serves it + items: groupModelQuants( + chatOnly.flatMap((entry) => entry.quants), + true + ), key: 'oai-compat', kind: 'compat', label: 'OAI-compat' diff --git a/tools/ui/src/lib/components/app/models/ModelsManager/ModelsManagerModelsTable.svelte b/tools/ui/src/lib/components/app/models/ModelsManager/ModelsManagerModelsTable.svelte index 20c6ea86a6..f0177b52cc 100644 --- a/tools/ui/src/lib/components/app/models/ModelsManager/ModelsManagerModelsTable.svelte +++ b/tools/ui/src/lib/components/app/models/ModelsManager/ModelsManagerModelsTable.svelte @@ -229,10 +229,13 @@ {#snippet repoRow(entry: ModelQuantGroup, indent = 0, compact = false)} {@const isExpanded = !collapsedQuants.has(entry.key)} + {@const providerCount = new Set(entry.quants.map((option) => option.backendId ?? '')).size} {@const groupLabel = - entry.kind === 'variants' - ? `${entry.quants.length} variants` - : `${entry.quants.length} quants available`} + entry.kind === 'providers' + ? `${providerCount} provider${providerCount === 1 ? '' : 's'}` + : entry.kind === 'variants' + ? `${entry.quants.length} variants` + : `${entry.quants.length} quants available`} {@const anyLoaded = entry.quants.some(isLoadedOption)}
@@ -295,7 +298,7 @@
{/snippet} -{#snippet quantRow(option: ModelOption, indent = 0, compact = false)} +{#snippet quantRow(option: ModelOption, indent = 0, compact = false, showProvider = false)} {@const favorite = isFavorite(option)} {@const canLoad = getBackendCapabilities(getBackend(option.backendId)).loadUnload} {@const isHidden = modelsStore.isHidden(option.id)} @@ -316,7 +319,9 @@ tabindex="0" > - {quant} + + {showProvider ? (getBackend(option.backendId)?.name ?? quant) : quant} + {option.model} @@ -347,7 +352,7 @@ {#if !collapsedQuants.has(entry.key)} {#each entry.quants as quant (quant.id)} - {@render quantRow(quant, indent + 24, compact)} + {@render quantRow(quant, indent + 24, compact, entry.kind === 'providers')} {/each} {/if} {:else} diff --git a/tools/ui/src/lib/components/app/models/ModelsManager/utils.ts b/tools/ui/src/lib/components/app/models/ModelsManager/utils.ts index 0ef7ead0cf..84baa09ab3 100644 --- a/tools/ui/src/lib/components/app/models/ModelsManager/utils.ts +++ b/tools/ui/src/lib/components/app/models/ModelsManager/utils.ts @@ -41,7 +41,7 @@ export type ModelOverrideMap = Record; export type { ModelLoadProgress }; /** What a group folds, which decides its label. */ -export type ModelGroupKind = 'quants' | 'variants'; +export type ModelGroupKind = 'providers' | 'quants' | 'variants'; /** One repo of the table, with the rows it ships as. */ export interface ModelQuantGroup { @@ -181,14 +181,15 @@ export function modelRepoKey(model: string): string { } /** Fold the quants of one repo into a single entry, so the table shows one row per model. */ -export function groupModelQuants(models: ModelOption[]): ModelQuantGroup[] { +export function groupModelQuants(models: ModelOption[], mergeProviders = false): ModelQuantGroup[] { const groups = new SvelteMap(); for (const option of models) { const repo = modelRepoKey(option.model); // groups stay within one backend, so the same repo served by two providers - // is not read as two quants of one model - const key = `${option.backendId ?? ''}::${repo}`; + // is not read as two quants of one model. The OAI-compat block asks for the + // opposite: one repo, one row per provider that serves it. + const key = mergeProviders ? repo : `${option.backendId ?? ''}::${repo}`; const group = groups.get(key); if (group) { @@ -204,8 +205,13 @@ export function groupModelQuants(models: ModelOption[]): ModelQuantGroup[] { const kind = groupKind(group.quants); const modelIds = group.quants.map((option) => option.model); - // the very same id twice is not a quant set; keep those rows apart - if (modelIds.length > 1 && modelIds.every((model) => model === modelIds[0])) { + // the very same id twice is not a quant set; keep those rows apart, unless + // the group exists to list the providers that serve it + if ( + kind !== 'providers' && + modelIds.length > 1 && + modelIds.every((model) => model === modelIds[0]) + ) { return group.quants.map((option) => ({ ...group, base: option, @@ -219,8 +225,12 @@ export function groupModelQuants(models: ModelOption[]): ModelQuantGroup[] { }); } -/** Quants share a quantization suffix; anything else is a variant. */ +/** What a group folds: providers, quants, or variants of one repo. */ function groupKind(quants: ModelOption[]): ModelGroupKind { + const backends = new Set(quants.map((option) => option.backendId ?? '')); + + if (backends.size > 1) return 'providers'; + const isQuant = quants.every( (option) => (option.parsedId ?? ModelsService.parseModelId(option.model)).quantization );