ui : only show the server error when no backend is available

Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash
This commit is contained in:
Aleksander Grygier
2026-09-16 19:22:47 +02:00
parent 088030d384
commit 23a19a7952
4 changed files with 49 additions and 4 deletions
@@ -1,5 +1,6 @@
<script lang="ts">
import { ModelsSelectorDropdown, ModelsSelectorSheet } from '$lib/components/app';
import { useBackendAvailability } from '$lib/hooks/use-backend-availability.svelte';
import { conversationsStore, deviceStore, modelsStore, serverStore } from '$lib/stores';
import { getConversationModel } from '$lib/utils';
@@ -28,7 +29,8 @@
}: Props = $props();
let isRouter = $derived(serverStore.isRouterMode);
let isOffline = $derived(!!serverStore.error);
const availability = useBackendAvailability();
let isOffline = $derived(availability.isOffline);
let conversationModel = $derived(
getConversationModel(conversationsStore.activeMessages as DatabaseMessage[])
@@ -13,6 +13,7 @@
} from '$lib/components/app';
import { LANDING_SETTLE_MAX_MS, LANDING_STABLE_FRAMES, ROUTES } from '$lib/constants';
import { createAutoScrollController } from '$lib/hooks/use-auto-scroll.svelte';
import { useBackendAvailability } from '$lib/hooks/use-backend-availability.svelte';
import { useChatScreenActiveModel } from '$lib/hooks/use-chat-screen-active-model.svelte';
import { useChatScreenDragAndDrop } from '$lib/hooks/use-chat-screen-drag-and-drop.svelte';
import { useChatScreenFileUpload } from '$lib/hooks/use-chat-screen-file-upload.svelte';
@@ -45,7 +46,8 @@
);
let activeErrorDialog = $derived(chatStore.errorDialogState);
let isServerLoading = $derived(serverStore.loading);
let hasPropsError = $derived(!!serverStore.error);
const availability = useBackendAvailability();
let hasPropsError = $derived(availability.isOffline);
let isCurrentConversationLoading = $derived(chatStore.isLoading || chatStore.isStreaming());
let chatFormBottomPosition = $derived.by(() => {
if (!deviceStore.isMobile) return '1rem';
@@ -2,13 +2,16 @@
import { AlertTriangle, Loader2, RefreshCw } from '@lucide/svelte';
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';
let hasError = $derived(!!serverStore.error);
const availability = useBackendAvailability();
let isLoadingModel = $derived(serverStore.status === 503);
let hasError = $derived(availability.isOffline);
</script>
{#if hasError}
{#if hasError || isLoadingModel}
<div class="pointer-events-auto mx-auto mb-4 max-w-[48rem] px-1">
<Alert.Root variant={isLoadingModel ? 'default' : 'destructive'}>
{#if isLoadingModel}
@@ -0,0 +1,38 @@
/**
* Backend availability for connection error states.
*
* The server error banner and the offline affordances should only appear when
* no backend can serve requests. The active backend failing is not enough:
* with an external backend configured, the UI stays usable.
*/
import { LOCAL_BACKEND_ID } from '$lib/constants';
import { backendsModelsStore, backendsStore, serverStore } from '$lib/stores';
export interface BackendAvailability {
readonly hasAvailableBackend: boolean;
readonly isOffline: boolean;
}
export function useBackendAvailability(): BackendAvailability {
const hasAvailableBackend = $derived.by(() => {
// no connection error at all: nothing to report
if (!serverStore.error) return true;
// the local server failed, but an external backend may still be usable
return backendsStore.enabled.some(
(backend) =>
backend.id !== LOCAL_BACKEND_ID && backendsModelsStore.get(backend.id).error === null
);
});
return {
get hasAvailableBackend() {
return hasAvailableBackend;
},
get isOffline() {
return !hasAvailableBackend;
}
};
}