mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-09-29 01:17:36 -05:00
ui : drop the provider merge checkbox and window the manager rows
Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash
This commit is contained in:
@@ -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 @@
|
||||
<div class="min-h-0">
|
||||
<ModelsManagerModelsTable
|
||||
bind:filter
|
||||
bind:groupProviders
|
||||
{groups}
|
||||
{isFavorite}
|
||||
onSelect={(option) => (selectedId = option.id)}
|
||||
|
||||
+80
-29
@@ -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<string, number>();
|
||||
|
||||
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)}
|
||||
|
||||
<div class="px-2">
|
||||
@@ -292,7 +341,7 @@
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
{#snippet quantRow(option: ModelOption, indent = 0, showProvider = false)}
|
||||
{#snippet quantRow(option: ModelOption, indent = 0)}
|
||||
{@const favorite = isFavorite(option)}
|
||||
{@const canLoad = getBackendCapabilities(getBackend(option.backendId)).loadUnload}
|
||||
{@const isLoaded = isLoadedOption(option)}
|
||||
@@ -313,9 +362,7 @@
|
||||
tabindex="0"
|
||||
>
|
||||
<span class="flex min-w-0 items-center gap-3" style="padding-left: {indent}px">
|
||||
<Badge class="h-5 shrink-0 px-1.5 text-[10px]" variant="secondary">
|
||||
{showProvider ? (getBackend(option.backendId)?.name ?? quant) : quant}
|
||||
</Badge>
|
||||
<Badge class="h-5 shrink-0 px-1.5 text-[10px]" variant="secondary">{quant}</Badge>
|
||||
|
||||
<span class="truncate text-sm text-muted-foreground">{option.model}</span>
|
||||
</span>
|
||||
@@ -346,7 +393,7 @@
|
||||
|
||||
{#if !collapsedQuants.has(entry.key)}
|
||||
{#each entry.quants as quant (quant.id)}
|
||||
{@render quantRow(quant, indent + 24, entry.kind === 'providers')}
|
||||
{@render quantRow(quant, indent + 24)}
|
||||
{/each}
|
||||
{/if}
|
||||
{:else}
|
||||
@@ -444,14 +491,6 @@
|
||||
<div class="flex shrink-0 items-center gap-2 py-4">
|
||||
<Input bind:value={filter} class="h-8 max-w-64 text-sm" placeholder="Filter models..." />
|
||||
|
||||
<span class="flex items-center gap-2">
|
||||
<Checkbox bind:checked={groupProviders} id="group-providers" />
|
||||
|
||||
<Label class="cursor-pointer text-xs text-muted-foreground" for="group-providers">
|
||||
Group models from different providers
|
||||
</Label>
|
||||
</span>
|
||||
|
||||
<span class="ml-auto text-xs text-muted-foreground">{summary}</span>
|
||||
</div>
|
||||
|
||||
@@ -484,6 +523,8 @@
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
{@const windowed = windowSection(group)}
|
||||
|
||||
<ModelsSection
|
||||
backendId={group.kind === 'provider' ? (group.backendId ?? undefined) : undefined}
|
||||
count={group.items.length}
|
||||
@@ -492,13 +533,11 @@
|
||||
open={group.kind !== 'hidden'}
|
||||
sticky
|
||||
>
|
||||
{#if group.flat}
|
||||
{#each group.items as entry (entry.key)}
|
||||
{@render entryTree(entry, 16)}
|
||||
{/each}
|
||||
{/if}
|
||||
{#each windowed.items as entry (entry.key)}
|
||||
{@render entryTree(entry, 16)}
|
||||
{/each}
|
||||
|
||||
{#each group.families as family (family.key)}
|
||||
{#each windowed.families as family (family.key)}
|
||||
{@render familyRow(family)}
|
||||
|
||||
{#if !collapsedFamilies.has(family.key)}
|
||||
@@ -507,6 +546,18 @@
|
||||
{/each}
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
{#if windowed.hidden > 0}
|
||||
<div class="px-2">
|
||||
<button
|
||||
class="w-full cursor-pointer rounded-md px-2 py-2 text-left text-xs text-muted-foreground transition hover:bg-muted/40"
|
||||
onclick={() => growSection(group.key)}
|
||||
type="button"
|
||||
>
|
||||
Show {Math.min(MODEL_ROW_WINDOW, windowed.hidden)} more {windowed.unit}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</ModelsSection>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
@@ -41,7 +41,7 @@ export type ModelOverrideMap = Record<string, ModelOverride>;
|
||||
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<string, ModelQuantGroup>();
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user