mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-09-29 09:27:33 -05:00
ui : put manage, discover and providers in one dialog
Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash
This commit is contained in:
@@ -4,7 +4,8 @@
|
||||
import { CheckCircle2, Loader2, XCircle } from '@lucide/svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
import { BACKEND_ID_PREFIX, BACKEND_PRESETS } from '$lib/constants';
|
||||
import * as ToggleGroup from '$lib/components/ui/toggle-group';
|
||||
import { BACKEND_ID_PREFIX, BACKEND_PRESETS, BACKEND_PROTOCOLS } from '$lib/constants';
|
||||
import { BackendsService } from '$lib/services';
|
||||
import type { BackendTestResult } from '$lib/services/backends.service';
|
||||
import { backendsStore } from '$lib/stores';
|
||||
@@ -29,8 +30,15 @@
|
||||
|
||||
let draft = $state<Backend>(createBackend());
|
||||
|
||||
// presets offered for the protocol the caller is adding
|
||||
let presets = $derived(BACKEND_PRESETS.filter((preset) => preset.protocol === defaultProtocol));
|
||||
// the caller seeds the protocol, the picker overrides it while adding
|
||||
let protocolPick = $state<string | undefined>(undefined);
|
||||
let protocol = $derived(
|
||||
BACKEND_PROTOCOLS.includes(protocolPick as BackendProtocol)
|
||||
? (protocolPick as BackendProtocol)
|
||||
: (backend?.protocol ?? defaultProtocol)
|
||||
);
|
||||
// presets offered for the protocol being added
|
||||
let presets = $derived(BACKEND_PRESETS.filter((preset) => preset.protocol === protocol));
|
||||
let testResult = $state<BackendTestResult | null>(null);
|
||||
let testing = $state(false);
|
||||
|
||||
@@ -149,12 +157,26 @@
|
||||
<Dialog.Title>{isEdit ? 'Edit backend' : 'Add backend'}</Dialog.Title>
|
||||
|
||||
<Dialog.Description>
|
||||
{defaultProtocol === 'llama.cpp'
|
||||
{protocol === 'llama.cpp'
|
||||
? 'Point at another llama-server.'
|
||||
: 'Connect an OpenAI-compatible endpoint.'}
|
||||
</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
|
||||
{#if !isEdit}
|
||||
<div class="space-y-3 pt-2">
|
||||
<h3 class="text-sm font-medium">Protocol</h3>
|
||||
|
||||
<ToggleGroup.Root bind:value={protocolPick} type="single" variant="outline">
|
||||
{#each BACKEND_PROTOCOLS as option (option)}
|
||||
<ToggleGroup.Item value={option}>
|
||||
{option === 'llama.cpp' ? 'llama.cpp' : 'OpenAI-compatible'}
|
||||
</ToggleGroup.Item>
|
||||
{/each}
|
||||
</ToggleGroup.Root>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if !isEdit && presets.length > 0}
|
||||
<div class="space-y-3 pt-2">
|
||||
<h3 class="text-sm font-medium">Recommended providers</h3>
|
||||
|
||||
@@ -9,14 +9,33 @@
|
||||
|
||||
interface Props {
|
||||
class?: string;
|
||||
protocol: BackendProtocol;
|
||||
/** Narrows the list to one protocol; omit it to list every provider at once. */
|
||||
protocol?: BackendProtocol;
|
||||
}
|
||||
|
||||
let { class: className, protocol }: Props = $props();
|
||||
|
||||
// the switcher above the list narrows it to one protocol
|
||||
let backends = $derived(backendsStore.external.filter((b) => b.protocol === protocol));
|
||||
let isLlamaCpp = $derived(protocol === 'llama.cpp');
|
||||
let backends = $derived(
|
||||
protocol
|
||||
? backendsStore.external.filter((b) => b.protocol === protocol)
|
||||
: backendsStore.external
|
||||
);
|
||||
// the bundled server belongs to the list whenever llama.cpp providers are in it
|
||||
let showsLocal = $derived(protocol === undefined || protocol === 'llama.cpp');
|
||||
let addTitle = $derived(
|
||||
protocol === 'openai'
|
||||
? 'Add a backend'
|
||||
: protocol === 'llama.cpp'
|
||||
? 'Add a llama.cpp backend'
|
||||
: 'Add a provider'
|
||||
);
|
||||
let addDescription = $derived(
|
||||
protocol === 'openai'
|
||||
? 'Connect an OpenAI-compatible endpoint.'
|
||||
: protocol === 'llama.cpp'
|
||||
? 'Point at another llama-server.'
|
||||
: 'Point at a llama-server or connect an OpenAI-compatible endpoint.'
|
||||
);
|
||||
|
||||
let isAdding = $state(false);
|
||||
let editing = $state<Backend | null>(null);
|
||||
@@ -48,12 +67,12 @@
|
||||
<DialogBackendForm
|
||||
bind:open={isAdding}
|
||||
backend={editing}
|
||||
defaultProtocol={protocol}
|
||||
defaultProtocol={protocol ?? 'llama.cpp'}
|
||||
onOpenChange={handleOpenChange}
|
||||
onSaved={() => void backendsModelsStore.loadAll()}
|
||||
/>
|
||||
|
||||
{#if isLlamaCpp && !serverStore.localServerMissing}
|
||||
{#if showsLocal && !serverStore.localServerMissing}
|
||||
<BackendCard
|
||||
backend={backendsStore.local}
|
||||
isLocal
|
||||
@@ -82,11 +101,9 @@
|
||||
<Plus />
|
||||
</Empty.Media>
|
||||
|
||||
<Empty.Title>{isLlamaCpp ? 'Add a llama.cpp backend' : 'Add a backend'}</Empty.Title>
|
||||
<Empty.Title>{addTitle}</Empty.Title>
|
||||
|
||||
<Empty.Description>
|
||||
{isLlamaCpp ? 'Point at another llama-server.' : 'Connect an OpenAI-compatible endpoint.'}
|
||||
</Empty.Description>
|
||||
<Empty.Description>{addDescription}</Empty.Description>
|
||||
</Empty.Header>
|
||||
|
||||
<Empty.Content>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { Box, PackageSearch } from '@lucide/svelte';
|
||||
import { Box, Compass, Server } from '@lucide/svelte';
|
||||
import SettingsBackends from '$lib/components/app/backends/SettingsBackends.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 * as Tabs from '$lib/components/ui/tabs';
|
||||
import { uiStore } from '$lib/stores';
|
||||
|
||||
interface Props {
|
||||
@@ -12,6 +14,17 @@
|
||||
|
||||
let { onOpenChange, open = $bindable(false) }: Props = $props();
|
||||
|
||||
let tab = $state('manage');
|
||||
|
||||
// the sidebar's Discover entry opens this dialog on its Discover tab
|
||||
$effect(() => {
|
||||
if (!uiStore.discoverModelsOpen) return;
|
||||
|
||||
uiStore.discoverModelsOpen = false;
|
||||
tab = 'discover';
|
||||
handleOpenChange(true);
|
||||
});
|
||||
|
||||
function handleOpenChange(value: boolean) {
|
||||
open = value;
|
||||
onOpenChange?.(value);
|
||||
@@ -20,7 +33,7 @@
|
||||
|
||||
<Dialog.Root onOpenChange={handleOpenChange} {open}>
|
||||
<Dialog.Content
|
||||
class="md:h-[calc(100vh-4rem)]! md:max-h-240! md:w-[calc(100vw-4rem)]! md:max-w-360! flex flex-col p-4"
|
||||
class="md:h-[calc(100vh-4rem)]! md:max-h-240! md:w-[calc(100vw-4rem)]! md:max-w-380! flex flex-col p-4"
|
||||
onCloseAutoFocus={(event) => event.preventDefault()}
|
||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||
>
|
||||
@@ -28,16 +41,46 @@
|
||||
<Dialog.Title class="flex items-center gap-2">
|
||||
<Box class="h-5 w-5" />
|
||||
|
||||
<span>Manage models</span>
|
||||
<span>Models</span>
|
||||
</Dialog.Title>
|
||||
|
||||
<Button onclick={() => uiStore.openDiscoverModels()} size="sm" variant="outline">
|
||||
<PackageSearch class="h-3.5 w-3.5" />
|
||||
|
||||
Discover models
|
||||
</Button>
|
||||
</Dialog.Header>
|
||||
|
||||
<ModelsManager class="mt-4" onClose={() => handleOpenChange(false)} />
|
||||
<Tabs.Root bind:value={tab} class="mt-2 min-h-0 flex-1 gap-0">
|
||||
<div class="px-2">
|
||||
<Tabs.List>
|
||||
<Tabs.Trigger value="manage">
|
||||
<Box class="h-3.5 w-3.5" />
|
||||
|
||||
Manage
|
||||
</Tabs.Trigger>
|
||||
|
||||
<Tabs.Trigger value="discover">
|
||||
<Compass class="h-3.5 w-3.5" />
|
||||
|
||||
Discover
|
||||
</Tabs.Trigger>
|
||||
|
||||
<Tabs.Trigger value="providers">
|
||||
<Server class="h-3.5 w-3.5" />
|
||||
|
||||
Providers
|
||||
</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
</div>
|
||||
|
||||
<Tabs.Content class="flex min-h-0 flex-1 flex-col pt-4" value="manage">
|
||||
<ModelsManager />
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content class="min-h-0 flex-1 overflow-hidden pt-4" value="discover">
|
||||
<div class="grid h-full min-h-0 gap-0" style="grid-template-columns: auto 1fr;">
|
||||
<ModelsDiscover />
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content class="min-h-0 flex-1 overflow-y-auto px-2 pt-4" value="providers">
|
||||
<SettingsBackends />
|
||||
</Tabs.Content>
|
||||
</Tabs.Root>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { ModelsDiscover } from '$lib/components/app/models/discover';
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
|
||||
interface Props {
|
||||
open?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
}
|
||||
|
||||
let { onOpenChange, open = $bindable(false) }: Props = $props();
|
||||
|
||||
function handleOpenChange(value: boolean) {
|
||||
open = value;
|
||||
onOpenChange?.(value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root onOpenChange={handleOpenChange} {open}>
|
||||
<Dialog.Content
|
||||
class="grid gap-0 p-0 md:h-[calc(100vh-4rem)]! md:max-h-240! md:w-[calc(100vw-4rem)]! md:max-w-380!"
|
||||
style="grid-template-columns: auto 1fr;"
|
||||
>
|
||||
<ModelsDiscover />
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -549,4 +549,3 @@ export { default as DialogMermaidPreview } from './DialogMermaidPreview.svelte';
|
||||
*
|
||||
* @see ModelsDiscover in $lib/components/app/models/discover
|
||||
*/
|
||||
export { default as DialogModelsDiscover } from './DialogModelsDiscover.svelte';
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
ActionIcon,
|
||||
DialogConversationRename,
|
||||
DialogManageModels,
|
||||
DialogModelsDiscover,
|
||||
DialogSettingsChat,
|
||||
Logo,
|
||||
SidebarNavigationActions,
|
||||
@@ -457,8 +456,6 @@
|
||||
|
||||
<DialogManageModels bind:open={uiStore.manageModelsOpen} />
|
||||
|
||||
<DialogModelsDiscover bind:open={uiStore.discoverModelsOpen} />
|
||||
|
||||
<style>
|
||||
aside {
|
||||
@media (max-width: 768px) {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
SettingsChatFields,
|
||||
SettingsChatImportExportTab,
|
||||
SettingsChatMobileHeader,
|
||||
SettingsChatProvidersTab,
|
||||
SettingsChatToolsTab,
|
||||
SettingsFooter
|
||||
} from '$lib/components/app/settings';
|
||||
@@ -161,9 +160,7 @@
|
||||
<div class="mx-auto max-w-2xl px-4 flex-1 md:mt-4">
|
||||
<div class="space-y-6 pt-3">
|
||||
<div class="grid">
|
||||
{#if currentSection.slug === SETTINGS_SECTION_SLUGS.BACKENDS}
|
||||
<SettingsChatProvidersTab />
|
||||
{:else if currentSection.slug === SETTINGS_SECTION_SLUGS.TOOLS}
|
||||
{#if currentSection.slug === SETTINGS_SECTION_SLUGS.TOOLS}
|
||||
<SettingsChatToolsTab />
|
||||
{:else if currentSection.slug === SETTINGS_SECTION_SLUGS.IMPORT_EXPORT}
|
||||
<SettingsChatImportExportTab />
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { SettingsBackends } from '$lib/components/app/backends';
|
||||
import * as Tabs from '$lib/components/ui/tabs';
|
||||
import type { BackendProtocol } from '$lib/types';
|
||||
|
||||
const PROTOCOL_TABS: Array<{ label: string; value: BackendProtocol }> = [
|
||||
{ label: 'Llama-compat', value: 'llama.cpp' },
|
||||
{ label: 'OpenAI-compat', value: 'openai' }
|
||||
];
|
||||
|
||||
let protocol = $state<BackendProtocol>('llama.cpp');
|
||||
</script>
|
||||
|
||||
<div class="space-y-3">
|
||||
<p class="text-sm text-muted-foreground">Endpoints this UI can talk to.</p>
|
||||
|
||||
<Tabs.Root
|
||||
class="gap-3"
|
||||
onValueChange={(value) => (protocol = value as BackendProtocol)}
|
||||
value={protocol}
|
||||
>
|
||||
<Tabs.List>
|
||||
{#each PROTOCOL_TABS as protocolTab (protocolTab.value)}
|
||||
<Tabs.Trigger value={protocolTab.value}>{protocolTab.label}</Tabs.Trigger>
|
||||
{/each}
|
||||
</Tabs.List>
|
||||
|
||||
{#each PROTOCOL_TABS as protocolTab (protocolTab.value)}
|
||||
<Tabs.Content value={protocolTab.value}>
|
||||
<SettingsBackends protocol={protocolTab.value} />
|
||||
</Tabs.Content>
|
||||
{/each}
|
||||
</Tabs.Root>
|
||||
</div>
|
||||
@@ -79,4 +79,3 @@ export { default as SettingsChatToolsTab } from './SettingsChat/SettingsChatTool
|
||||
* Providers panel for chat settings. Switches between the llama.cpp and
|
||||
* OpenAI-compatible protocols and lists the endpoints configured for each.
|
||||
*/
|
||||
export { default as SettingsChatProvidersTab } from './SettingsChat/SettingsChatProvidersTab.svelte';
|
||||
|
||||
Reference in New Issue
Block a user