mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-09-29 09:27:33 -05:00
ui : pick backends from recommended provider cards
Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
<script lang="ts">
|
||||
import BackendPresetIcon from './BackendPresetIcon.svelte';
|
||||
import { Check } from '@lucide/svelte';
|
||||
import { Badge } from '$lib/components/ui/badge';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import type { BackendPreset } from '$lib/types';
|
||||
|
||||
interface Props {
|
||||
preset: BackendPreset;
|
||||
onClick?: () => void;
|
||||
selected?: boolean;
|
||||
dimmed?: boolean;
|
||||
/** A backend already points at this provider. */
|
||||
added?: boolean;
|
||||
}
|
||||
|
||||
let { added = false, dimmed = false, onClick, preset, selected = false }: Props = $props();
|
||||
|
||||
// an added provider is a label, not an action: no hover, no selection
|
||||
let interactive = $derived(!added && Boolean(onClick));
|
||||
</script>
|
||||
|
||||
<Card.Root
|
||||
class={`relative gap-3! select-none bg-muted/30 p-4 transition-all ${interactive ? 'cursor-pointer hover:bg-muted/50 hover:opacity-100' : ''} ${selected && !added ? 'bg-muted/30 ring-1 ring-primary/40' : ''} ${dimmed || added ? 'opacity-50' : ''}`}
|
||||
onclick={interactive ? onClick : undefined}
|
||||
>
|
||||
{#if added}
|
||||
<Badge class="absolute top-2 right-2 gap-1 px-1.5 text-[10px]" variant="secondary">
|
||||
<Check class="h-3 w-3" />
|
||||
Added
|
||||
</Badge>
|
||||
{/if}
|
||||
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<BackendPresetIcon class="h-5 w-5" {preset} />
|
||||
|
||||
<h4 class="min-w-0 flex-1 truncate font-medium">{preset.name}</h4>
|
||||
</div>
|
||||
|
||||
<p class="text-xs text-muted-foreground">{preset.description}</p>
|
||||
</Card.Root>
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import BackendForm from './BackendForm.svelte';
|
||||
import BackendPresetIcon from './BackendPresetIcon.svelte';
|
||||
import BackendPresetCard from './BackendPresetCard.svelte';
|
||||
import { CheckCircle2, Loader2, XCircle } from '@lucide/svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
@@ -9,7 +9,7 @@
|
||||
import type { BackendTestResult } from '$lib/services/backends.service';
|
||||
import { backendsStore } from '$lib/stores';
|
||||
import type { Backend, BackendPreset } from '$lib/types';
|
||||
import { uuid } from '$lib/utils';
|
||||
import { findBackendPreset, uuid } from '$lib/utils';
|
||||
|
||||
interface Props {
|
||||
backend?: Backend | null;
|
||||
@@ -21,10 +21,18 @@
|
||||
let { backend = null, onOpenChange, onSaved, open = $bindable(false) }: Props = $props();
|
||||
|
||||
let draft = $state<Backend>(createBackend());
|
||||
let selectedPresetId = $state<string | null>(null);
|
||||
let testResult = $state<BackendTestResult | null>(null);
|
||||
let testing = $state(false);
|
||||
|
||||
// the card follows the URL, so editing any field deselects it
|
||||
let selectedPresetId = $derived(findBackendPreset(draft.baseUrl)?.id ?? null);
|
||||
// presets a configured backend already points at
|
||||
let addedPresetIds = $derived(
|
||||
backendsStore.external
|
||||
.map((backend) => findBackendPreset(backend.baseUrl)?.id)
|
||||
.filter((id) => id !== undefined)
|
||||
);
|
||||
|
||||
let isEdit = $derived(backend !== null);
|
||||
let urlError = $derived.by(() => {
|
||||
const url = draft.baseUrl.trim();
|
||||
@@ -46,7 +54,6 @@
|
||||
if (!open) return;
|
||||
|
||||
draft = backend ? { ...backend } : createBackend();
|
||||
selectedPresetId = null;
|
||||
testResult = null;
|
||||
testing = false;
|
||||
});
|
||||
@@ -62,14 +69,13 @@
|
||||
}
|
||||
|
||||
function applyPreset(preset: BackendPreset) {
|
||||
selectedPresetId = preset.id;
|
||||
draft = {
|
||||
...draft,
|
||||
baseUrl: preset.baseUrl,
|
||||
chatPath: preset.chatPath,
|
||||
compat: preset.compat,
|
||||
modelsPath: preset.modelsPath,
|
||||
name: preset.id === 'custom' ? '' : preset.name,
|
||||
name: preset.name,
|
||||
protocol: preset.protocol
|
||||
};
|
||||
testResult = null;
|
||||
@@ -136,19 +142,20 @@
|
||||
</Dialog.Header>
|
||||
|
||||
{#if !isEdit}
|
||||
<div class="grid grid-cols-2 gap-2 pt-2 sm:grid-cols-3">
|
||||
{#each BACKEND_PRESETS as preset (preset.id)}
|
||||
<Button
|
||||
class="justify-start"
|
||||
onclick={() => applyPreset(preset)}
|
||||
size="sm"
|
||||
variant={selectedPresetId === preset.id ? 'secondary' : 'outline'}
|
||||
>
|
||||
<BackendPresetIcon class="h-4 w-4" {preset} />
|
||||
<div class="space-y-3 pt-2">
|
||||
<h3 class="text-sm font-medium">Recommended providers</h3>
|
||||
|
||||
{preset.name}
|
||||
</Button>
|
||||
{/each}
|
||||
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
{#each BACKEND_PRESETS as preset (preset.id)}
|
||||
<BackendPresetCard
|
||||
added={addedPresetIds.includes(preset.id)}
|
||||
dimmed={Boolean(selectedPresetId) && selectedPresetId !== preset.id}
|
||||
onClick={() => applyPreset(preset)}
|
||||
{preset}
|
||||
selected={selectedPresetId === preset.id}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export { default as BackendCard } from './BackendCard.svelte';
|
||||
export { default as BackendForm } from './BackendForm.svelte';
|
||||
export { default as BackendPresetCard } from './BackendPresetCard.svelte';
|
||||
export { default as BackendPresetIcon } from './BackendPresetIcon.svelte';
|
||||
export { default as DialogBackendForm } from './DialogBackendForm.svelte';
|
||||
export { default as SettingsBackends } from './SettingsBackends.svelte';
|
||||
|
||||
@@ -63,6 +63,7 @@ export const BACKEND_COMPAT: Record<BackendProtocol, BackendCompat> = {
|
||||
export const BACKEND_PRESETS: readonly BackendPreset[] = [
|
||||
{
|
||||
baseUrl: 'https://router.huggingface.co',
|
||||
description: 'Open models from the Hub, served by inference providers.',
|
||||
iconUrl: '/backend-presets/huggingface.svg',
|
||||
id: 'huggingface',
|
||||
name: 'Hugging Face',
|
||||
@@ -70,58 +71,12 @@ export const BACKEND_PRESETS: readonly BackendPreset[] = [
|
||||
},
|
||||
{
|
||||
baseUrl: 'https://openrouter.ai/api',
|
||||
description: 'Models from many providers behind a single API.',
|
||||
// v2 brand: purple on light, lime on dark
|
||||
iconUrlDark: '/backend-presets/openrouter-dark.svg',
|
||||
iconUrlLight: '/backend-presets/openrouter-light.svg',
|
||||
id: 'openrouter',
|
||||
name: 'OpenRouter',
|
||||
protocol: 'openai'
|
||||
},
|
||||
{
|
||||
baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai',
|
||||
chatPath: '/chat/completions',
|
||||
iconUrl: '/backend-presets/google.svg',
|
||||
id: 'google',
|
||||
modelsPath: '/models',
|
||||
name: 'Google',
|
||||
protocol: 'openai'
|
||||
},
|
||||
{
|
||||
baseUrl: 'https://api.z.ai/api/paas/v4',
|
||||
chatPath: '/chat/completions',
|
||||
iconUrl: '/backend-presets/zai.png',
|
||||
id: 'zai',
|
||||
modelsPath: '/models',
|
||||
name: 'Z.ai',
|
||||
protocol: 'openai'
|
||||
},
|
||||
{
|
||||
baseUrl: 'https://api.deepseek.com',
|
||||
iconUrl: '/backend-presets/deepseek.svg',
|
||||
id: 'deepseek',
|
||||
name: 'DeepSeek',
|
||||
protocol: 'openai'
|
||||
},
|
||||
{
|
||||
baseUrl: 'https://api.moonshot.ai',
|
||||
iconUrl: '/backend-presets/kimi.png',
|
||||
id: 'kimi',
|
||||
name: 'Kimi',
|
||||
protocol: 'openai'
|
||||
},
|
||||
{
|
||||
baseUrl: 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1',
|
||||
chatPath: '/chat/completions',
|
||||
iconUrl: '/backend-presets/qwen.png',
|
||||
id: 'qwen',
|
||||
modelsPath: '/models',
|
||||
name: 'Qwen',
|
||||
protocol: 'openai'
|
||||
},
|
||||
{
|
||||
baseUrl: '',
|
||||
id: 'custom',
|
||||
name: 'Custom',
|
||||
protocol: 'openai'
|
||||
}
|
||||
];
|
||||
|
||||
Vendored
+2
@@ -75,6 +75,8 @@ export interface BackendPreset {
|
||||
/** Optional help text shown under the API key field. */
|
||||
apiKeyHelp?: string;
|
||||
baseUrl: string;
|
||||
/** One line describing the endpoint, shown on the preset card. */
|
||||
description?: string;
|
||||
chatPath?: string;
|
||||
/** Brand mark used in both themes, for logos that carry their own background. */
|
||||
iconUrl?: string;
|
||||
|
||||
Reference in New Issue
Block a user