From 5835dff4397e44bc6b39b27e3a7c9012bd6cd797 Mon Sep 17 00:00:00 2001 From: Aleksander Grygier Date: Mon, 28 Sep 2026 15:37:21 +0200 Subject: [PATCH] ui : route chat through the active provider Chat goes through protocol adapters, so an OpenAI-compatible endpoint speaks its own wire format: per-backend paths and headers, the model on the request, tools kept on the local server, and token counts synthesized for endpoints that do not stream their own timings. The server store keeps the local server's props while another provider is active, a conversation resolves the provider its model belongs to before sending, and the chat screen never blocks on the local probe when the install has none. Assisted-by: pi:llama.cpp/DeepSeek-V4.1-Flash --- .../ContextGaugePopup.svelte | 2 +- .../ChatMessageAssistant.svelte | 1 - .../ChatMessageAssistantModel.svelte | 22 +- .../app/chat/ChatScreen/ChatScreen.svelte | 7 +- .../chat/ChatScreen/ChatScreenGreeting.svelte | 4 +- .../app/server/ServerErrorSplash.svelte | 4 +- .../components/app/server/ServerStatus.svelte | 2 +- .../hooks/use-backend-availability.svelte.ts | 38 +++ .../src/lib/hooks/use-context-gauge.svelte.ts | 10 +- tools/ui/src/lib/services/chat.service.ts | 287 +++++++++++------- tools/ui/src/lib/services/models.service.ts | 139 +++++++-- tools/ui/src/lib/services/props.service.ts | 8 +- tools/ui/src/lib/services/protocols/index.ts | 24 ++ tools/ui/src/lib/services/protocols/openai.ts | 183 +++++++++++ tools/ui/src/lib/services/protocols/types.ts | 42 +++ tools/ui/src/lib/services/tools.service.ts | 14 +- tools/ui/src/lib/stores/chat/index.svelte.ts | 21 +- tools/ui/src/lib/stores/index.ts | 5 + tools/ui/src/lib/stores/init.ts | 38 ++- .../ui/src/lib/stores/models/index.svelte.ts | 252 +++++++++++++-- .../ui/src/lib/stores/models/props.svelte.ts | 50 ++- .../ui/src/lib/stores/models/status.svelte.ts | 140 ++++++++- tools/ui/src/lib/stores/server.svelte.ts | 153 +++++++++- tools/ui/src/lib/stores/tools.svelte.ts | 11 + tools/ui/src/lib/stores/ui.svelte.ts | 1 - tools/ui/src/routes/(chat)/+page.ts | 7 +- tools/ui/src/routes/(chat)/chat/[id]/+page.ts | 7 +- 27 files changed, 1247 insertions(+), 225 deletions(-) create mode 100644 tools/ui/src/lib/hooks/use-backend-availability.svelte.ts create mode 100644 tools/ui/src/lib/services/protocols/index.ts create mode 100644 tools/ui/src/lib/services/protocols/openai.ts create mode 100644 tools/ui/src/lib/services/protocols/types.ts diff --git a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormContextGauge/ContextGaugePopup.svelte b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormContextGauge/ContextGaugePopup.svelte index 8fa09cf704..cdd6821e38 100644 --- a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormContextGauge/ContextGaugePopup.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormContextGauge/ContextGaugePopup.svelte @@ -73,7 +73,7 @@ - {#if gauge.activeModelId !== null && !gauge.isActiveModelLoaded} + {#if gauge.canLoadActiveModel && gauge.activeModelId !== null && !gauge.isActiveModelLoaded} diff --git a/tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageAssistant/ChatMessageAssistantModel.svelte b/tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageAssistant/ChatMessageAssistantModel.svelte index c5b80f1569..e0a2f6299d 100644 --- a/tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageAssistant/ChatMessageAssistantModel.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessage/ChatMessageAssistant/ChatMessageAssistantModel.svelte @@ -1,17 +1,21 @@ -{#if isRouter} +{#if isSelectable} { + onModelChange={async (modelId: string, modelName: string, backendId?: string) => { + // capability of the picked model's own backend, not the active one + const loadsOnRequest = getBackendCapabilities(getBackend(backendId)).loadUnload; const status = modelsStore.getModelStatus(modelId); - if (status !== ServerModelStatus.LOADED) { + // only a llama.cpp server loads up front; remote backends load the + // model with the request itself + if (loadsOnRequest && status !== ServerModelStatus.LOADED) { pendingModel = modelId; try { diff --git a/tools/ui/src/lib/components/app/chat/ChatScreen/ChatScreen.svelte b/tools/ui/src/lib/components/app/chat/ChatScreen/ChatScreen.svelte index 6cea95d0d0..95780596ee 100644 --- a/tools/ui/src/lib/components/app/chat/ChatScreen/ChatScreen.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatScreen/ChatScreen.svelte @@ -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'; @@ -44,8 +45,10 @@ showCenteredEmpty && conversationsStore.activeMessages.length === 0 && !chatStore.isLoading ); let activeErrorDialog = $derived(chatStore.errorDialogState); - let isServerLoading = $derived(serverStore.loading); - let hasPropsError = $derived(!!serverStore.error); + // no local server in this deployment: never block on its probe + let isServerLoading = $derived(serverStore.loading && !serverStore.localServerMissing); + const availability = useBackendAvailability(); + let hasPropsError = $derived(availability.isOffline); let isCurrentConversationLoading = $derived(chatStore.isLoading || chatStore.isStreaming()); let chatFormBottomPosition = $derived.by(() => { if (!deviceStore.isMobile) return '1rem'; diff --git a/tools/ui/src/lib/components/app/chat/ChatScreen/ChatScreenGreeting.svelte b/tools/ui/src/lib/components/app/chat/ChatScreen/ChatScreenGreeting.svelte index 5af00ebb47..b4d951392b 100644 --- a/tools/ui/src/lib/components/app/chat/ChatScreen/ChatScreenGreeting.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatScreen/ChatScreenGreeting.svelte @@ -17,7 +17,7 @@

Hello there

- {serverStore.props?.modalities?.audio ? 'Record audio, type a message ' : 'Type a message'} or upload - files to get started + {serverStore.localProps?.modalities?.audio ? 'Record audio, type a message ' : 'Type a message'} or + upload files to get started

diff --git a/tools/ui/src/lib/components/app/server/ServerErrorSplash.svelte b/tools/ui/src/lib/components/app/server/ServerErrorSplash.svelte index e8154e5ce2..41af511298 100644 --- a/tools/ui/src/lib/components/app/server/ServerErrorSplash.svelte +++ b/tools/ui/src/lib/components/app/server/ServerErrorSplash.svelte @@ -1,13 +1,13 @@