mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-09-29 01:17:36 -05:00
ui : put the discover view in the models manager
The manager dialog gains its second view: the Discover Models call to action fades the table out, slides the title with its compass mark, and fades the discover surface in; the arrow button returns to the table. Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash
This commit is contained in:
@@ -1,7 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { ArrowLeft, Compass } from '@lucide/svelte';
|
||||
import ModelsDiscover from '$lib/components/app/models/discover/ModelsDiscover.svelte';
|
||||
import ModelsManager from '$lib/components/app/models/ModelsManager/ModelsManager.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
import { MODEL_ICON } from '$lib/constants';
|
||||
import { uiStore } from '$lib/stores';
|
||||
import { untrack } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
open?: boolean;
|
||||
@@ -10,6 +15,54 @@
|
||||
|
||||
let { onOpenChange, open = $bindable(false) }: Props = $props();
|
||||
|
||||
type View = 'discover' | 'manage';
|
||||
|
||||
/** How long a view takes to fade out before the next one fades in. */
|
||||
const VIEW_FADE_MS = 150;
|
||||
|
||||
// what the user asked for, and what is actually rendered: a view change fades the
|
||||
// current one out, swaps, then fades the next one in
|
||||
let view = $state<View>('manage');
|
||||
let shownView = $state<View>('manage');
|
||||
let isSwapping = $state(false);
|
||||
|
||||
$effect(() => {
|
||||
const next = view;
|
||||
|
||||
if (next === shownView) return;
|
||||
|
||||
// nothing to fade while the dialog is closed, so the view is set before it opens
|
||||
if (!open) {
|
||||
untrack(() => (shownView = next));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
untrack(() => (isSwapping = true));
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
untrack(() => {
|
||||
shownView = next;
|
||||
isSwapping = false;
|
||||
});
|
||||
}, VIEW_FADE_MS);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
});
|
||||
|
||||
let title = $derived(view === 'discover' ? 'Discover' : 'Models');
|
||||
|
||||
// the sidebar's Discover entry opens this dialog on its Discover view
|
||||
$effect(() => {
|
||||
if (!uiStore.discoverModelsOpen) return;
|
||||
|
||||
untrack(() => {
|
||||
uiStore.discoverModelsOpen = false;
|
||||
view = 'discover';
|
||||
handleOpenChange(true);
|
||||
});
|
||||
});
|
||||
|
||||
function handleOpenChange(value: boolean) {
|
||||
open = value;
|
||||
onOpenChange?.(value);
|
||||
@@ -22,16 +75,91 @@
|
||||
onCloseAutoFocus={(event) => event.preventDefault()}
|
||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||
>
|
||||
<Dialog.Header class="flex flex-row items-center p-2 pr-8">
|
||||
<Dialog.Title class="flex min-h-7 items-center gap-2">
|
||||
<MODEL_ICON class="h-5 w-5 shrink-0" />
|
||||
<Dialog.Header class="relative flex flex-row items-center justify-between p-2 pr-8">
|
||||
<!--
|
||||
The arrow is out of the flow and the title slides, so entering a sub-view
|
||||
moves the title with a transform instead of reflowing the header. min-h
|
||||
keeps the header's height fixed either way.
|
||||
-->
|
||||
<Button
|
||||
aria-label="Back to models"
|
||||
class="absolute top-1/2 left-1 h-7 w-7 -translate-y-1/2 transition-[opacity,visibility] duration-200 ease-[cubic-bezier(0.23,1,0.32,1)] {view ===
|
||||
'manage'
|
||||
? 'invisible opacity-0'
|
||||
: 'visible opacity-100'}"
|
||||
onclick={() => (view = 'manage')}
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
>
|
||||
<ArrowLeft class="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
<span>Models</span>
|
||||
<Dialog.Title
|
||||
class="flex min-h-7 items-center gap-2 transition-transform duration-200 ease-[cubic-bezier(0.23,1,0.32,1)] {view ===
|
||||
'manage'
|
||||
? 'translate-x-0'
|
||||
: 'translate-x-8'}"
|
||||
>
|
||||
<!-- the marks cross-fade in one grid cell, so the title never shifts -->
|
||||
<span class="grid h-5 w-5 shrink-0 place-items-center">
|
||||
<MODEL_ICON
|
||||
class="col-start-1 row-start-1 h-5 w-5 transition-opacity duration-150 {view ===
|
||||
'manage'
|
||||
? 'opacity-100'
|
||||
: 'opacity-0'}"
|
||||
/>
|
||||
|
||||
<Compass
|
||||
class="col-start-1 row-start-1 h-5 w-5 transition-opacity duration-150 {view ===
|
||||
'discover'
|
||||
? 'opacity-100'
|
||||
: 'opacity-0'}"
|
||||
/>
|
||||
</span>
|
||||
|
||||
<span>{title}</span>
|
||||
</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
|
||||
<div class="min-h-0 flex-1">
|
||||
<ModelsManager class="h-full" />
|
||||
<div class="dialog-view min-h-0 flex-1" data-visible={!isSwapping}>
|
||||
{#if shownView === 'manage'}
|
||||
<ModelsManager class="h-full">
|
||||
{#snippet toolbarEnd()}
|
||||
<Button
|
||||
class="gap-1.5"
|
||||
onclick={() => (view = 'discover')}
|
||||
size="sm"
|
||||
variant="tertiary"
|
||||
>
|
||||
<Compass class="h-3.5 w-3.5" />
|
||||
|
||||
Discover Models
|
||||
</Button>
|
||||
{/snippet}
|
||||
</ModelsManager>
|
||||
{:else}
|
||||
<div class="grid h-full overflow-hidden" style="grid-template-columns: auto 1fr;">
|
||||
<ModelsDiscover />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
|
||||
<style>
|
||||
/* a view change reads as one surface swapping, not as content teleporting */
|
||||
.dialog-view {
|
||||
opacity: 0;
|
||||
transition: opacity 150ms cubic-bezier(0.23, 1, 0.32, 1);
|
||||
}
|
||||
|
||||
.dialog-view[data-visible='true'] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.dialog-view {
|
||||
transition-duration: 100ms;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -10,6 +10,8 @@ class UiStore {
|
||||
/** Set when a flow outside the chat wants the composer focused once it can take it. */
|
||||
composerFocusRequested = $state(false);
|
||||
|
||||
discoverModelsOpen = $state(false);
|
||||
|
||||
/** Whether the desktop sidebar is expanded (open). */
|
||||
isSidebarExpanded = $state(false);
|
||||
/** Model the manager reveals when it opens, a qualified id or a raw model name. */
|
||||
@@ -25,6 +27,10 @@ class UiStore {
|
||||
return requested;
|
||||
}
|
||||
|
||||
openDiscoverModels(): void {
|
||||
this.discoverModelsOpen = true;
|
||||
}
|
||||
|
||||
/** Open the models manager, optionally focused on one model. */
|
||||
openModelsManager(focus?: string): void {
|
||||
this.manageModelFocus = focus ?? null;
|
||||
|
||||
Reference in New Issue
Block a user