mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-10-03 03:17:32 -05:00
ui : keep the model selector usable when a provider is down
The selector was disabled whenever no backend was reachable, which locked the one control that lets you leave the broken provider. It now stays enabled and wears the error colours, the manager marks the unreachable provider in its section header, and the banner only speaks when there is a single provider and nowhere to switch to. Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash
This commit is contained in:
+14
-5
@@ -1,7 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { ModelsSelectorDropdown, ModelsSelectorSheet } from '$lib/components/app';
|
||||
import { useBackendAvailability } from '$lib/hooks/use-backend-availability.svelte';
|
||||
import { LOCAL_BACKEND_ID } from '$lib/constants';
|
||||
import {
|
||||
backendsModelsStore,
|
||||
backendsStore,
|
||||
conversationsStore,
|
||||
deviceStore,
|
||||
@@ -35,8 +36,14 @@
|
||||
}: Props = $props();
|
||||
|
||||
let isRouter = $derived(serverStore.isRouterMode);
|
||||
const availability = useBackendAvailability();
|
||||
let isOffline = $derived(availability.isOffline);
|
||||
// the provider this selector is pointed at, which is the one its colours report
|
||||
let selectorError = $derived.by(() => {
|
||||
const backendId = backendsStore.active.id;
|
||||
|
||||
return backendId === LOCAL_BACKEND_ID
|
||||
? Boolean(serverStore.error)
|
||||
: backendsModelsStore.get(backendId).error !== null;
|
||||
});
|
||||
|
||||
let conversationModel = $derived(
|
||||
getConversationModel(conversationsStore.activeMessages as DatabaseMessage[])
|
||||
@@ -169,7 +176,8 @@
|
||||
<ModelsSelectorSheet
|
||||
bind:this={selectorModelRef}
|
||||
currentModel={selectorModel}
|
||||
disabled={disabled || isOffline}
|
||||
{disabled}
|
||||
error={selectorError}
|
||||
{forceForegroundText}
|
||||
{useGlobalSelection}
|
||||
/>
|
||||
@@ -177,7 +185,8 @@
|
||||
<ModelsSelectorDropdown
|
||||
bind:this={selectorModelRef}
|
||||
currentModel={selectorModel}
|
||||
disabled={disabled || isOffline}
|
||||
{disabled}
|
||||
error={selectorError}
|
||||
{forceForegroundText}
|
||||
{useGlobalSelection}
|
||||
/>
|
||||
|
||||
@@ -3,12 +3,16 @@
|
||||
import * as Alert from '$lib/components/ui/alert';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
import { useBackendAvailability } from '$lib/hooks/use-backend-availability.svelte';
|
||||
import { serverStore } from '$lib/stores';
|
||||
import { backendsStore, serverStore } from '$lib/stores';
|
||||
|
||||
const availability = useBackendAvailability();
|
||||
|
||||
let isLoadingModel = $derived(serverStore.status === 503);
|
||||
let hasError = $derived(availability.isOffline);
|
||||
// A single provider leaves nowhere to switch to, so the banner is the only
|
||||
// place the failure can be reported. With another provider available the
|
||||
// selector carries it instead, and stays usable.
|
||||
let isOnlyProvider = $derived(backendsStore.enabled.length <= 1);
|
||||
let hasError = $derived(availability.isOffline && isOnlyProvider);
|
||||
</script>
|
||||
|
||||
{#if hasError || isLoadingModel}
|
||||
|
||||
+11
-1
@@ -49,7 +49,13 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { FAMILY_ROW_WINDOW, MODEL_ROW_WINDOW } from '$lib/constants';
|
||||
import { ModelDownloadConfirmAction, ServerModelStatus } from '$lib/enums';
|
||||
import { backendsStore, modelsStore, settingsStore, uiStore } from '$lib/stores';
|
||||
import {
|
||||
backendsModelsStore,
|
||||
backendsStore,
|
||||
modelsStore,
|
||||
settingsStore,
|
||||
uiStore
|
||||
} from '$lib/stores';
|
||||
import type { ModelOption } from '$lib/types/models';
|
||||
import { getBackend } from '$lib/utils/api-base';
|
||||
import { getBackendCapabilities } from '$lib/utils/backend';
|
||||
@@ -709,13 +715,17 @@
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
{@const backendState = group.backendId ? backendsModelsStore.get(group.backendId) : null}
|
||||
|
||||
<ModelsSection
|
||||
backendId={group.kind === 'provider' ? (group.backendId ?? undefined) : undefined}
|
||||
chevronClass="mr-7"
|
||||
count={group.items.length}
|
||||
defaultOpen={group.kind !== 'hidden'}
|
||||
error={Boolean(backendState?.error)}
|
||||
icon={group.kind === 'provider' ? undefined : groupIcon}
|
||||
label={group.label}
|
||||
loading={Boolean(backendState?.loading)}
|
||||
persistKey={group.key}
|
||||
revealChevronOnHover
|
||||
sticky
|
||||
|
||||
+11
-6
@@ -29,6 +29,8 @@
|
||||
class?: string;
|
||||
currentModel?: string | null;
|
||||
disabled?: boolean;
|
||||
/** The provider behind this selector is unreachable. */
|
||||
error?: boolean;
|
||||
forceForegroundText?: boolean;
|
||||
onModelChange?: (
|
||||
modelId: string,
|
||||
@@ -42,6 +44,7 @@
|
||||
class: className = '',
|
||||
currentModel = null,
|
||||
disabled = false,
|
||||
error = false,
|
||||
forceForegroundText = false,
|
||||
onModelChange,
|
||||
useGlobalSelection = false
|
||||
@@ -239,13 +242,15 @@
|
||||
{...props}
|
||||
class={[
|
||||
`relative inline-grid cursor-pointer grid-cols-[1fr_auto_1fr] items-center gap-1 rounded-sm bg-background px-1.5 py-1 text-xs shadow-sm transition hover:bg-muted-foreground/20 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-60 dark:bg-muted-foreground/15 dark:text-secondary-foreground`,
|
||||
!ms.isCurrentModelInCache
|
||||
? 'bg-red-400/10 !text-red-400 hover:bg-red-400/20 hover:text-red-400'
|
||||
: forceForegroundText
|
||||
? 'text-foreground'
|
||||
: ms.isHighlightedCurrentModelActive
|
||||
error
|
||||
? 'border-destructive/40 bg-destructive/10 !text-destructive hover:bg-destructive/20'
|
||||
: !ms.isCurrentModelInCache
|
||||
? 'bg-red-400/10 !text-red-400 hover:bg-red-400/20 hover:text-red-400'
|
||||
: forceForegroundText
|
||||
? 'text-foreground'
|
||||
: 'text-foreground',
|
||||
: ms.isHighlightedCurrentModelActive
|
||||
? 'text-foreground'
|
||||
: 'text-foreground',
|
||||
isOpen && 'text-foreground',
|
||||
'max-w-[min(calc(100vw-4rem) md:max-w-[min(calc(100cqw-9rem),25rem)]'
|
||||
]}
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
backendId?: string
|
||||
) => Promise<boolean> | boolean | void;
|
||||
disabled?: boolean;
|
||||
/** The provider behind this selector is unreachable. */
|
||||
error?: boolean;
|
||||
forceForegroundText?: boolean;
|
||||
/** When true, user's global selection takes priority over currentModel (for form selector) */
|
||||
useGlobalSelection?: boolean;
|
||||
@@ -36,6 +38,7 @@
|
||||
class: className = '',
|
||||
currentModel = null,
|
||||
disabled = false,
|
||||
error = false,
|
||||
forceForegroundText = false,
|
||||
onModelChange,
|
||||
useGlobalSelection = false
|
||||
@@ -107,13 +110,15 @@
|
||||
<button
|
||||
class={[
|
||||
`relative inline-flex cursor-pointer items-center gap-1.5 rounded-sm bg-background px-1.5 py-1 text-xs shadow-sm transition hover:bg-muted-foreground/20 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-60 max-sm:px-3 max-sm:py-2 max-sm:text-sm dark:bg-muted-foreground/15 dark:text-secondary-foreground`,
|
||||
!ms.isCurrentModelInCache
|
||||
? 'bg-red-400/10 !text-red-400 hover:bg-red-400/20 hover:text-red-400'
|
||||
: forceForegroundText
|
||||
? 'text-foreground'
|
||||
: ms.isHighlightedCurrentModelActive
|
||||
error
|
||||
? 'border-destructive/40 bg-destructive/10 !text-destructive hover:bg-destructive/20'
|
||||
: !ms.isCurrentModelInCache
|
||||
? 'bg-red-400/10 !text-red-400 hover:bg-red-400/20 hover:text-red-400'
|
||||
: forceForegroundText
|
||||
? 'text-foreground'
|
||||
: 'text-foreground',
|
||||
: ms.isHighlightedCurrentModelActive
|
||||
? 'text-foreground'
|
||||
: 'text-foreground',
|
||||
sheetOpen && 'text-foreground'
|
||||
]}
|
||||
disabled={disabled || ms.updating}
|
||||
@@ -214,13 +219,15 @@
|
||||
<button
|
||||
class={[
|
||||
`inline-flex cursor-pointer items-center gap-1.5 rounded-sm bg-background px-1.5 py-1 text-xs shadow-sm transition hover:bg-muted-foreground/20 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-60 dark:bg-muted-foreground/15 dark:text-secondary-foreground`,
|
||||
!ms.isCurrentModelInCache
|
||||
? 'bg-red-400/10 !text-red-400 hover:bg-red-400/20 hover:text-red-400'
|
||||
: forceForegroundText
|
||||
? 'text-foreground'
|
||||
: ms.isHighlightedCurrentModelActive
|
||||
error
|
||||
? 'border-destructive/40 bg-destructive/10 !text-destructive hover:bg-destructive/20'
|
||||
: !ms.isCurrentModelInCache
|
||||
? 'bg-red-400/10 !text-red-400 hover:bg-red-400/20 hover:text-red-400'
|
||||
: forceForegroundText
|
||||
? 'text-foreground'
|
||||
: 'text-foreground'
|
||||
: ms.isHighlightedCurrentModelActive
|
||||
? 'text-foreground'
|
||||
: 'text-foreground'
|
||||
]}
|
||||
disabled={disabled || ms.updating}
|
||||
onclick={() => ms.handleOpenChange(true)}
|
||||
|
||||
Reference in New Issue
Block a user