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 b7f40f8502..e9baa7bd0a 100644
--- a/tools/ui/src/lib/components/app/models/ModelsManager/ModelsManager.svelte
+++ b/tools/ui/src/lib/components/app/models/ModelsManager/ModelsManager.svelte
@@ -50,9 +50,7 @@
// one entry per repo, so a model with several quants is a single table row;
// loaded models lead the table, then favorites, then one block per provider,
// and an entry lands in the first group that claims it
- /** Off keeps the same repo from two providers apart; on merges them. */
- let groupProviders = $state(false);
- let entries = $derived(groupModelQuants(visible, groupProviders));
+ let entries = $derived(groupModelQuants(visible));
let groups = $derived.by(() => {
// only llama-compat servers report a load state
const isLlamaCompat = (entry: ModelQuantGroup) =>
@@ -217,7 +215,6 @@
(selectedId = option.id)}
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 abc41f16b3..168e32c218 100644
--- a/tools/ui/src/lib/components/app/models/ModelsManager/ModelsManagerModelsTable.svelte
+++ b/tools/ui/src/lib/components/app/models/ModelsManager/ModelsManagerModelsTable.svelte
@@ -30,19 +30,17 @@
} from '$lib/components/app';
import { DialogConfirmDownload } from '$lib/components/app/dialogs';
import { Badge } from '$lib/components/ui/badge';
- import { Checkbox } from '$lib/components/ui/checkbox';
import { Input } from '$lib/components/ui/input';
- import { Label } from '$lib/components/ui/label';
+ import { MODEL_ROW_WINDOW } from '$lib/constants';
import { ModelCapability, ModelDownloadConfirmAction, ServerModelStatus } from '$lib/enums';
import { modelsStore } from '$lib/stores';
import type { ModelOption } from '$lib/types/models';
import { getBackend } from '$lib/utils/api-base';
import { getBackendCapabilities } from '$lib/utils/backend';
- import { SvelteSet } from 'svelte/reactivity';
+ import { SvelteMap, SvelteSet } from 'svelte/reactivity';
interface Props {
filter?: string;
- groupProviders?: boolean;
groups: ModelsTableGroup[];
isFavorite: (option: ModelOption) => boolean;
onSelect: (option: ModelOption) => void;
@@ -53,7 +51,6 @@
let {
filter = $bindable(''),
- groupProviders = $bindable(false),
groups,
isFavorite,
onSelect,
@@ -84,6 +81,60 @@
deleteOpen = true;
}
+ /** Rows mounted per section, grown by the show more row. */
+ const sectionLimits = new SvelteMap();
+
+ function sectionLimit(key: string): number {
+ return sectionLimits.get(key) ?? MODEL_ROW_WINDOW;
+ }
+
+ function growSection(key: string): void {
+ sectionLimits.set(key, sectionLimit(key) + MODEL_ROW_WINDOW);
+ }
+
+ /** Cut a section down to the mounted window, counting models rather than groups. */
+ function windowSection(group: (typeof sections)[number]): {
+ families: ModelFamilyGroup[];
+ hidden: number;
+ items: ModelQuantGroup[];
+ unit: string;
+ } {
+ const limit = sectionLimit(group.key);
+
+ if (group.flat) {
+ const items: ModelQuantGroup[] = [];
+
+ let shown = 0;
+
+ for (const entry of group.items) {
+ if (shown >= limit) break;
+
+ items.push(entry);
+ shown += entry.quants.length;
+ }
+
+ return { families: [], hidden: group.items.length - items.length, items, unit: 'models' };
+ }
+
+ const families: ModelFamilyGroup[] = [];
+
+ let shown = 0;
+
+ for (const family of group.families) {
+ if (shown >= limit) break;
+
+ families.push(family);
+ shown += family.entries.reduce((sum, entry) => sum + entry.quants.length, 0);
+ }
+
+ return {
+ families,
+ hidden: group.families.length - families.length,
+ items: [],
+ unit: 'families'
+ };
+ }
+
function toggleFamily(key: string): void {
if (collapsedFamilies.has(key)) {
collapsedFamilies.delete(key);
@@ -234,11 +285,9 @@
{#snippet repoRow(entry: ModelQuantGroup, indent = 0)}
{@const isExpanded = !collapsedQuants.has(entry.key)}
{@const groupLabel =
- entry.kind === 'providers'
- ? `${entry.quants.length} providers`
- : entry.kind === 'variants'
- ? `${entry.quants.length} variants`
- : `${entry.quants.length} quants available`}
+ entry.kind === 'variants'
+ ? `${entry.quants.length} variants`
+ : `${entry.quants.length} quants available`}
{@const anyLoaded = entry.quants.some(isLoadedOption)}
+ {/if}
{/if}
{/each}
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 cf4becf7c3..06568067fb 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 = 'providers' | 'quants' | 'variants';
+export type ModelGroupKind = 'quants' | 'variants';
/** One repo of the table, with the rows it ships as. */
export interface ModelQuantGroup {
@@ -181,14 +181,14 @@ 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[], mergeProviders = false): ModelQuantGroup[] {
+export function groupModelQuants(models: ModelOption[]): 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 = mergeProviders ? repo : `${option.backendId ?? ''}::${repo}`;
+ const key = `${option.backendId ?? ''}::${repo}`;
const group = groups.get(key);
if (group) {
@@ -204,13 +204,8 @@ export function groupModelQuants(models: ModelOption[], mergeProviders = false):
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, unless
- // the group is there to show the providers that serve it
- if (
- kind !== 'providers' &&
- modelIds.length > 1 &&
- modelIds.every((model) => model === modelIds[0])
- ) {
+ // the very same id twice is not a quant set; keep those rows apart
+ if (modelIds.length > 1 && modelIds.every((model) => model === modelIds[0])) {
return group.quants.map((option) => ({
...group,
base: option,
@@ -224,12 +219,8 @@ export function groupModelQuants(models: ModelOption[], mergeProviders = false):
});
}
-/** What a group folds, which decides its label: quants, variants or providers. */
+/** Quants share a quantization suffix; anything else is a variant. */
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
);