From d8af98f1ed38c84988d3dcf4353f60652bf05bab Mon Sep 17 00:00:00 2001 From: Pascal Date: Sun, 18 Jan 2026 10:40:30 +0100 Subject: [PATCH] refactor: remove multimodal validation from model selector Remove all frontend validation logic that prevented users from selecting models based on multimodal capabilities. This refactoring removes restrictive UI code while maintaining full functionality - Vision models can describe images as text - That text remains useful for non-vision models - Chaining vision -> non-vision is a valid workflow - Users know their use case better than the UI - Users can return to vision models when needed --- .../ChatFormActions/ChatFormActions.svelte | 13 +- .../ChatMessages/ChatMessageAssistant.svelte | 14 +- .../ChatMessages/ChatMessageEditForm.svelte | 73 +-------- .../chat/ChatMessages/ChatMessageUser.svelte | 1 - .../app/models/ModelsSelector.svelte | 153 +++--------------- .../use-model-change-validation.svelte.ts | 118 -------------- .../src/lib/stores/conversations.svelte.ts | 61 ------- 7 files changed, 24 insertions(+), 409 deletions(-) delete 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 2dcea21652..d3011912ab 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 @@ -15,8 +15,7 @@ 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, usedModalities } from '$lib/stores/conversations.svelte'; - import { useModelChangeValidation } from '$lib/hooks/use-model-change-validation.svelte'; + import { activeMessages } from '$lib/stores/conversations.svelte'; interface Props { canSend?: boolean; @@ -157,15 +156,6 @@ selectorModelRef?.open(); } - const { handleModelChange } = useModelChangeValidation({ - getRequiredModalities: () => usedModalities(), - onValidationFailure: async (previousModelId) => { - if (previousModelId) { - await modelsStore.selectModelById(previousModelId); - } - } - }); - let showMcpDialog = $state(false); @@ -189,7 +179,6 @@ currentModel={conversationModel} forceForegroundText={true} useGlobalSelection={true} - onModelChange={handleModelChange} /> 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 6695b99333..79c43e5cbc 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 @@ -8,7 +8,6 @@ ModelsSelector } 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, isChatStreaming } from '$lib/stores/chat.svelte'; import { agenticStreamingToolCall } from '$lib/stores/agentic.svelte'; import { autoResizeTextarea, copyToClipboard } from '$lib/utils'; @@ -20,7 +19,6 @@ import { MessageRole } from '$lib/enums'; 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 { AGENTIC_TAGS, REASONING_TAGS } from '$lib/constants/agentic'; @@ -97,11 +95,6 @@ let displayedModel = $derived(message.model ?? null); - const { handleModelChange } = useModelChangeValidation({ - getRequiredModalities: () => conversationsStore.getModalitiesUpToMessage(message.id), - onSuccess: (modelName) => onRegenerate(modelName) - }); - function handleCopyModel() { void copyToClipboard(displayedModel ?? ''); } @@ -190,12 +183,7 @@ {#if displayedModel}
{#if isRouter} - + {:else} {/if} diff --git a/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageEditForm.svelte b/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageEditForm.svelte index 9ab81307e1..4c228058db 100644 --- a/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageEditForm.svelte +++ b/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageEditForm.svelte @@ -5,22 +5,13 @@ import { ChatAttachmentsList, DialogConfirmation, ModelsSelector } from '$lib/components/app'; import { INPUT_CLASSES } from '$lib/constants/css-classes'; import { SETTING_CONFIG_DEFAULT } from '$lib/constants/settings-config'; - import { AttachmentType, FileTypeCategory, MimeTypeText } from '$lib/enums'; + import { MimeTypeText } from '$lib/enums'; import { config } from '$lib/stores/settings.svelte'; - import { useModelChangeValidation } from '$lib/hooks/use-model-change-validation.svelte'; import { chatStore } from '$lib/stores/chat.svelte'; - import { conversationsStore } from '$lib/stores/conversations.svelte'; - import { modelsStore } from '$lib/stores/models.svelte'; import { isRouterMode } from '$lib/stores/server.svelte'; - import { - autoResizeTextarea, - getFileTypeCategory, - getFileTypeCategoryByExtension, - parseClipboardContent - } from '$lib/utils'; + import { autoResizeTextarea, parseClipboardContent } from '$lib/utils'; interface Props { - messageId: string; editedContent: string; editedExtras?: DatabaseMessageExtra[]; editedUploadedFiles?: ChatUploadedFile[]; @@ -38,7 +29,6 @@ } let { - messageId, editedContent, editedExtras = [], editedUploadedFiles = [], @@ -87,59 +77,6 @@ let canSubmit = $derived(editedContent.trim().length > 0 || hasAttachments); - function getEditedAttachmentsModalities(): ModelModalities { - const modalities: ModelModalities = { vision: false, audio: false }; - - for (const extra of editedExtras) { - if (extra.type === AttachmentType.IMAGE) { - modalities.vision = true; - } - - if ( - extra.type === AttachmentType.PDF && - 'processedAsImages' in extra && - extra.processedAsImages - ) { - modalities.vision = true; - } - - if (extra.type === AttachmentType.AUDIO) { - modalities.audio = true; - } - } - - for (const file of editedUploadedFiles) { - const category = getFileTypeCategory(file.type) || getFileTypeCategoryByExtension(file.name); - if (category === FileTypeCategory.IMAGE) { - modalities.vision = true; - } - if (category === FileTypeCategory.AUDIO) { - modalities.audio = true; - } - } - - return modalities; - } - - function getRequiredModalities(): ModelModalities { - const beforeModalities = conversationsStore.getModalitiesUpToMessage(messageId); - const editedModalities = getEditedAttachmentsModalities(); - - return { - vision: beforeModalities.vision || editedModalities.vision, - audio: beforeModalities.audio || editedModalities.audio - }; - } - - const { handleModelChange } = useModelChangeValidation({ - getRequiredModalities, - onValidationFailure: async (previousModelId) => { - if (previousModelId) { - await modelsStore.selectModelById(previousModelId); - } - } - }); - function handleFileInputChange(event: Event) { const input = event.target as HTMLInputElement; if (!input.files || input.files.length === 0) return; @@ -336,11 +273,7 @@
{#if isRouter} - + {/if}