mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-10-02 02:47:26 -05:00
ui : share one collapsible section for model lists
Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash
This commit is contained in:
+15
-22
@@ -2,8 +2,8 @@
|
||||
import type { ModelsListFilter, ModelsListGroup } from '../utils';
|
||||
import ModelsManagerModelsListControls from './ModelsManagerModelsListControls.svelte';
|
||||
import ModelsManagerModelsListItem from './ModelsManagerModelsListItem.svelte';
|
||||
import { Cloud, Heart, Server } from '@lucide/svelte';
|
||||
import { CollapsibleSection } from '$lib/components/app';
|
||||
import { Heart } from '@lucide/svelte';
|
||||
import { Logo, ModelsSection } from '$lib/components/app';
|
||||
import type { ModelOption } from '$lib/types/models';
|
||||
|
||||
interface Props {
|
||||
@@ -27,8 +27,6 @@
|
||||
selectedId,
|
||||
view = $bindable<ModelsListFilter>('all')
|
||||
}: Props = $props();
|
||||
|
||||
const headerClass = 'flex w-full cursor-pointer items-center gap-1.5 px-2 py-1.5 text-left';
|
||||
</script>
|
||||
|
||||
<div class="flex h-full min-h-0 flex-col gap-3">
|
||||
@@ -36,13 +34,9 @@
|
||||
|
||||
<div class="min-h-0 flex-1 space-y-1 overflow-y-auto">
|
||||
{#if favorites.length > 0}
|
||||
<CollapsibleSection triggerClass={headerClass}>
|
||||
{#snippet trigger()}
|
||||
<Heart class="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
|
||||
<span class="text-xs font-semibold text-muted-foreground">Favorites</span>
|
||||
|
||||
<span class="text-xs text-muted-foreground/70">{favorites.length}</span>
|
||||
<ModelsSection count={favorites.length} label="Favorites" sticky>
|
||||
{#snippet icon()}
|
||||
<Heart class="h-3.5 w-3.5 shrink-0" />
|
||||
{/snippet}
|
||||
|
||||
{#each favorites as option (option.id)}
|
||||
@@ -54,21 +48,20 @@
|
||||
{option}
|
||||
/>
|
||||
{/each}
|
||||
</CollapsibleSection>
|
||||
</ModelsSection>
|
||||
{/if}
|
||||
|
||||
{#each groups as group (group.backendId)}
|
||||
<CollapsibleSection triggerClass={headerClass}>
|
||||
{#snippet trigger()}
|
||||
<ModelsSection
|
||||
backendId={group.isLocal ? undefined : group.backendId}
|
||||
count={group.items.length}
|
||||
label={group.label}
|
||||
sticky
|
||||
>
|
||||
{#snippet icon()}
|
||||
{#if group.isLocal}
|
||||
<Server class="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
{:else}
|
||||
<Cloud class="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
<Logo class="shrink-0" style="--size: 0.875rem" />
|
||||
{/if}
|
||||
|
||||
<span class="text-xs font-semibold text-muted-foreground">{group.label}</span>
|
||||
|
||||
<span class="text-xs text-muted-foreground/70">{group.items.length}</span>
|
||||
{/snippet}
|
||||
|
||||
{#each group.items as option (option.id)}
|
||||
@@ -80,7 +73,7 @@
|
||||
{option}
|
||||
/>
|
||||
{/each}
|
||||
</CollapsibleSection>
|
||||
</ModelsSection>
|
||||
{/each}
|
||||
|
||||
{#if groups.length === 0 && favorites.length === 0}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
<script lang="ts">
|
||||
import { ChevronLeft, CircleAlert, Loader2 } from '@lucide/svelte';
|
||||
import { CollapsibleSection } from '$lib/components/app';
|
||||
import { BackendIcon } from '$lib/components/app/backends';
|
||||
import { getBackend } from '$lib/utils/api-base';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
/** Section rows. */
|
||||
children: Snippet;
|
||||
/** Renders the backend's logo in the header. */
|
||||
backendId?: string;
|
||||
/** Number shown next to the label, omitted when undefined. */
|
||||
count?: number;
|
||||
error?: boolean;
|
||||
/** Overrides the backend logo, used for sections without a backend. */
|
||||
icon?: Snippet;
|
||||
label: string;
|
||||
loading?: boolean;
|
||||
/** Renders the back control, for a drilled-in provider. */
|
||||
onBack?: () => void;
|
||||
revealChevronOnHover?: boolean;
|
||||
sectionHeaderClass?: string;
|
||||
/** Sticks the header to the top of the scrollport. */
|
||||
sticky?: boolean;
|
||||
}
|
||||
|
||||
let {
|
||||
backendId,
|
||||
children,
|
||||
count,
|
||||
error = false,
|
||||
icon,
|
||||
label,
|
||||
loading = false,
|
||||
onBack,
|
||||
revealChevronOnHover = false,
|
||||
sectionHeaderClass = 'm-0 px-2 py-2 text-[13px] font-semibold text-muted-foreground/70 select-none',
|
||||
sticky = false
|
||||
}: Props = $props();
|
||||
|
||||
let triggerClass = $derived(
|
||||
`${sectionHeaderClass} flex w-full cursor-pointer items-center gap-1.5 text-left${sticky ? ' sticky z-10 bg-popover' : ''}`
|
||||
);
|
||||
// the dropdown publishes its search block height, surfaces without one fall back to 0
|
||||
let triggerStyle = $derived(sticky ? 'top: var(--dropdown-sticky-height, 0px)' : '');
|
||||
</script>
|
||||
|
||||
<CollapsibleSection {revealChevronOnHover} {triggerClass} {triggerStyle}>
|
||||
{#snippet trigger()}
|
||||
{#if onBack}
|
||||
<button
|
||||
aria-label="Back to all providers"
|
||||
class="-ml-1 inline-flex shrink-0 cursor-pointer items-center rounded-sm p-0.5 text-muted-foreground transition hover:bg-muted/60 hover:text-foreground"
|
||||
onclick={(event) => {
|
||||
// the surrounding trigger toggles the section, the back control
|
||||
// must not collapse the list it is leaving
|
||||
event.stopPropagation();
|
||||
onBack?.();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<ChevronLeft class="h-3.5 w-3.5" />
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
{#if icon}
|
||||
{@render icon()}
|
||||
{:else if backendId}
|
||||
<BackendIcon backend={getBackend(backendId)} class="h-3.5 w-3.5" />
|
||||
{/if}
|
||||
|
||||
<span class="truncate">{label}</span>
|
||||
|
||||
{#if loading}
|
||||
<Loader2 class="h-3 w-3 shrink-0 animate-spin" />
|
||||
{:else if error}
|
||||
<CircleAlert class="h-3 w-3 shrink-0 text-destructive" />
|
||||
{/if}
|
||||
|
||||
{#if count !== undefined}
|
||||
<span class="shrink-0 text-xs text-muted-foreground/70">{count}</span>
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
{@render children()}
|
||||
</CollapsibleSection>
|
||||
@@ -1,9 +1,8 @@
|
||||
<script lang="ts">
|
||||
import ModelsSelectorDownloadItem from './ModelsSelectorDownloadItem.svelte';
|
||||
import { ChevronLeft, CircleAlert, Heart, Loader2 } from '@lucide/svelte';
|
||||
import { Heart } from '@lucide/svelte';
|
||||
import { ModelsSelectorOption } from '$lib/components/app';
|
||||
import { CollapsibleSection } from '$lib/components/app';
|
||||
import { BackendIcon } from '$lib/components/app/backends';
|
||||
import { ModelsSection } from '$lib/components/app';
|
||||
import { DialogConfirmDownload } from '$lib/components/app/dialogs';
|
||||
import Logo from '$lib/components/app/misc/Logo.svelte';
|
||||
import {
|
||||
@@ -14,7 +13,6 @@
|
||||
import { MODEL_ROW_WINDOW } from '$lib/constants';
|
||||
import { ModelDownloadConfirmAction } from '$lib/enums';
|
||||
import { modelsStore } from '$lib/stores';
|
||||
import { getBackend } from '$lib/utils/api-base';
|
||||
|
||||
interface Props {
|
||||
groups: GroupedModelOptions;
|
||||
@@ -129,21 +127,15 @@
|
||||
{/each}
|
||||
{:else}
|
||||
<!-- Favorites come first; the sections below skip them -->
|
||||
<CollapsibleSection
|
||||
revealChevronOnHover
|
||||
triggerClass="{headerClass} flex w-full cursor-pointer items-center gap-1.5 text-left"
|
||||
triggerStyle={headerStyle}
|
||||
>
|
||||
{#snippet trigger()}
|
||||
<ModelsSection label="Favorites" revealChevronOnHover sticky>
|
||||
{#snippet icon()}
|
||||
<Heart class="h-3.5 w-3.5 shrink-0" />
|
||||
|
||||
Favorites
|
||||
{/snippet}
|
||||
|
||||
{#each favorites as item (`fav-${item.option.id}`)}
|
||||
{@render render(item, !showOrgName)}
|
||||
{/each}
|
||||
</CollapsibleSection>
|
||||
</ModelsSection>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
@@ -176,57 +168,27 @@
|
||||
{#if localOnly}
|
||||
{@render localRows()}
|
||||
{:else}
|
||||
<CollapsibleSection
|
||||
revealChevronOnHover
|
||||
triggerClass="{headerClass} flex w-full cursor-pointer items-center gap-1.5 text-left"
|
||||
triggerStyle={headerStyle}
|
||||
>
|
||||
{#snippet trigger()}
|
||||
<ModelsSection label="Local models" revealChevronOnHover sticky>
|
||||
{#snippet icon()}
|
||||
<Logo class="shrink-0" style="--size: 0.875rem" />
|
||||
|
||||
Local models
|
||||
{/snippet}
|
||||
|
||||
{@render localRows()}
|
||||
</CollapsibleSection>
|
||||
</ModelsSection>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<!-- One section per remote provider. -->
|
||||
{#each groups.providers as provider (provider.backendId)}
|
||||
<CollapsibleSection
|
||||
<ModelsSection
|
||||
backendId={provider.backendId}
|
||||
error={Boolean(provider.error)}
|
||||
label={provider.name}
|
||||
loading={provider.loading}
|
||||
onBack={onProviderBack}
|
||||
revealChevronOnHover
|
||||
triggerClass="{headerClass} flex w-full cursor-pointer items-center gap-1.5 text-left"
|
||||
triggerStyle={headerStyle}
|
||||
sticky
|
||||
>
|
||||
{#snippet trigger()}
|
||||
{#if onProviderBack}
|
||||
<button
|
||||
aria-label="Back to all providers"
|
||||
class="-ml-1 inline-flex shrink-0 cursor-pointer items-center rounded-sm p-0.5 text-muted-foreground transition hover:bg-muted/60 hover:text-foreground"
|
||||
onclick={(event) => {
|
||||
// the surrounding trigger toggles the section, the back control
|
||||
// must not collapse the list it is leaving
|
||||
event.stopPropagation();
|
||||
onProviderBack?.();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<ChevronLeft class="h-3.5 w-3.5" />
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<BackendIcon backend={getBackend(provider.backendId)} class="h-3.5 w-3.5" />
|
||||
|
||||
{provider.name}
|
||||
|
||||
{#if provider.loading}
|
||||
<Loader2 class="h-3 w-3 animate-spin" />
|
||||
{:else if provider.error}
|
||||
<CircleAlert class="h-3 w-3 text-destructive" />
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
{#if provider.items.length > 0}
|
||||
{#each provider.items as item (`${provider.backendId}-${item.option.id}`)}
|
||||
{@render render(item, !showOrgName)}
|
||||
@@ -247,7 +209,7 @@
|
||||
{provider.error ?? (provider.loading ? 'Loading models...' : 'No models')}
|
||||
</p>
|
||||
{/if}
|
||||
</CollapsibleSection>
|
||||
</ModelsSection>
|
||||
{/each}
|
||||
|
||||
<DialogConfirmDownload
|
||||
|
||||
@@ -48,6 +48,7 @@ export { default as ModelBadge } from './ModelBadge.svelte';
|
||||
* Respects the user's `showRawModelNames` setting.
|
||||
*/
|
||||
export { default as ModelId } from './ModelId.svelte';
|
||||
export { default as ModelsSection } from './ModelsSection.svelte';
|
||||
|
||||
/**
|
||||
* **ModelCapabilityIcons** - Capability and modality icon row
|
||||
|
||||
Reference in New Issue
Block a user