From db479523ec9da26f8ad165d6968e3f2f0a0e38fb Mon Sep 17 00:00:00 2001 From: Aleksander Grygier Date: Thu, 27 Nov 2025 19:13:05 +0100 Subject: [PATCH] feat: Condition available models based on modality + better model loading strategy & UX --- .../ChatFormActions/ChatFormActions.svelte | 13 +- .../ChatMessages/ChatMessageAssistant.svelte | 19 +- .../components/app/misc/SelectorModel.svelte | 178 ++++++++++++++---- .../use-model-change-validation.svelte.ts | 119 ++++++++++++ .../src/lib/stores/conversations.svelte.ts | 52 +++++ .../webui/src/lib/stores/models.svelte.ts | 6 +- tools/server/webui/src/lib/types/api.d.ts | 4 +- 7 files changed, 333 insertions(+), 58 deletions(-) create mode 100644 tools/server/webui/src/lib/hooks/use-model-change-validation.svelte.ts diff --git a/tools/server/webui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActions.svelte b/tools/server/webui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActions.svelte index 679d1eef39..a59ef89636 100644 --- a/tools/server/webui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActions.svelte +++ b/tools/server/webui/src/lib/components/app/chat/ChatForm/ChatFormActions/ChatFormActions.svelte @@ -13,7 +13,8 @@ import { modelsStore, modelOptions, selectedModelId } from '$lib/stores/models.svelte'; import { isRouterMode } from '$lib/stores/server.svelte'; import { chatStore } from '$lib/stores/chat.svelte'; - import { activeMessages } from '$lib/stores/conversations.svelte'; + import { activeMessages, usedModalities } from '$lib/stores/conversations.svelte'; + import { useModelChangeValidation } from '$lib/hooks/use-model-change-validation.svelte'; import type { ChatUploadedFile } from '$lib/types/chat'; interface Props { @@ -157,6 +158,15 @@ export function openModelSelector() { selectorModelRef?.open(); } + + const { handleModelChange } = useModelChangeValidation({ + getRequiredModalities: () => usedModalities(), + onValidationFailure: async (previousModelId) => { + if (previousModelId) { + await modelsStore.selectModelById(previousModelId); + } + } + });
@@ -173,6 +183,7 @@ currentModel={conversationModel} forceForegroundText={true} useGlobalSelection={true} + onModelChange={handleModelChange} /> {#if isLoading} diff --git a/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageAssistant.svelte b/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageAssistant.svelte index fbcb024c17..71b0f79813 100644 --- a/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageAssistant.svelte +++ b/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageAssistant.svelte @@ -9,6 +9,7 @@ SelectorModel } from '$lib/components/app'; import { useProcessingState } from '$lib/hooks/use-processing-state.svelte'; + import { useModelChangeValidation } from '$lib/hooks/use-model-change-validation.svelte'; import { isLoading } from '$lib/stores/chat.svelte'; import autoResizeTextarea from '$lib/utils/autoresize-textarea'; import { fade } from 'svelte/transition'; @@ -18,8 +19,8 @@ import { INPUT_CLASSES } from '$lib/constants/input-classes'; import Label from '$lib/components/ui/label/label.svelte'; import { config } from '$lib/stores/settings.svelte'; + import { conversationsStore } from '$lib/stores/conversations.svelte'; import { isRouterMode } from '$lib/stores/server.svelte'; - import { modelsStore } from '$lib/stores/models.svelte'; import { copyToClipboard } from '$lib/utils/copy'; import type { ApiChatCompletionToolCall } from '$lib/types/api'; @@ -93,7 +94,6 @@ let currentConfig = $derived(config()); let isRouter = $derived(isRouterMode()); let displayedModel = $derived((): string | null => { - // Only show model from streaming data, no fallbacks to server props if (message.model) { return message.model; } @@ -101,16 +101,10 @@ return null; }); - async function handleModelChange(modelId: string, modelName: string) { - try { - await modelsStore.selectModelById(modelId); - - // Pass the selected model name for regeneration - onRegenerate(modelName); - } catch (error) { - console.error('Failed to change model:', error); - } - } + const { handleModelChange } = useModelChangeValidation({ + getRequiredModalities: () => conversationsStore.getModalitiesUpToMessage(message.id), + onSuccess: (modelName) => onRegenerate(modelName) + }); function handleCopyModel() { const model = displayedModel(); @@ -258,6 +252,7 @@ currentModel={displayedModel()} onModelChange={handleModelChange} disabled={isLoading()} + upToMessageId={message.id} /> {:else} diff --git a/tools/server/webui/src/lib/components/app/misc/SelectorModel.svelte b/tools/server/webui/src/lib/components/app/misc/SelectorModel.svelte index e1a850ba71..1b1326fefb 100644 --- a/tools/server/webui/src/lib/components/app/misc/SelectorModel.svelte +++ b/tools/server/webui/src/lib/components/app/misc/SelectorModel.svelte @@ -1,6 +1,7 @@