From 1d15e29185c6a8419a1c768529922743455d317e Mon Sep 17 00:00:00 2001 From: Aleksander Grygier Date: Tue, 27 Jan 2026 12:57:42 +0100 Subject: [PATCH] refactor: Adapt message child components to MessageEditContext --- .../ChatMessages/ChatMessageAssistant.svelte | 58 +++++++------ .../ChatMessages/ChatMessageEditForm.svelte | 84 ++++++------------- .../ChatMessages/ChatMessageMcpPrompt.svelte | 41 ++------- .../ChatMessages/ChatMessageSystem.svelte | 46 ++++++---- .../chat/ChatMessages/ChatMessageUser.svelte | 53 +++--------- 5 files changed, 103 insertions(+), 179 deletions(-) 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 eb713e0913..5360d7eb81 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 @@ -7,10 +7,11 @@ ModelBadge, ModelsSelector } from '$lib/components/app'; + import { getMessageEditContext } from '$lib/contexts'; import { useProcessingState } from '$lib/hooks/use-processing-state.svelte'; import { isLoading, isChatStreaming } from '$lib/stores/chat.svelte'; import { agenticStreamingToolCall } from '$lib/stores/agentic.svelte'; - import { autoResizeTextarea, copyToClipboard } from '$lib/utils'; + import { autoResizeTextarea, copyToClipboard, isIMEComposing } from '$lib/utils'; import { fade } from 'svelte/transition'; import { Check, X } from '@lucide/svelte'; import { Button } from '$lib/components/ui/button'; @@ -32,25 +33,17 @@ assistantMessages: number; messageTypes: string[]; } | null; - editedContent?: string; - isEditing?: boolean; message: DatabaseMessage; messageContent: string | undefined; - onCancelEdit?: () => void; onCopy: () => void; onConfirmDelete: () => void; onContinue?: () => void; onDelete: () => void; onEdit?: () => void; - onEditKeydown?: (event: KeyboardEvent) => void; - onEditedContentChange?: (content: string) => void; onNavigateToSibling?: (siblingId: string) => void; onRegenerate: (modelOverride?: string) => void; - onSaveEdit?: () => void; onShowDeleteDialogChange: (show: boolean) => void; - onShouldBranchAfterEditChange?: (value: boolean) => void; showDeleteDialog: boolean; - shouldBranchAfterEdit?: boolean; siblingInfo?: ChatMessageSiblingInfo | null; textareaElement?: HTMLTextAreaElement; } @@ -58,29 +51,37 @@ let { class: className = '', deletionInfo, - editedContent = '', - isEditing = false, message, messageContent, - onCancelEdit, onConfirmDelete, onContinue, onCopy, onDelete, onEdit, - onEditKeydown, - onEditedContentChange, onNavigateToSibling, onRegenerate, - onSaveEdit, onShowDeleteDialogChange, - onShouldBranchAfterEditChange, showDeleteDialog, - shouldBranchAfterEdit = false, siblingInfo = null, textareaElement = $bindable() }: Props = $props(); + // Get edit context + const editCtx = getMessageEditContext(); + + // Local state for assistant-specific editing + let shouldBranchAfterEdit = $state(false); + + function handleEditKeydown(event: KeyboardEvent) { + if (event.key === 'Enter' && !event.shiftKey && !isIMEComposing(event)) { + event.preventDefault(); + editCtx.save(); + } else if (event.key === 'Escape') { + event.preventDefault(); + editCtx.cancel(); + } + } + const hasAgenticMarkers = $derived( messageContent?.includes(AGENTIC_TAGS.TOOL_CALL_START) ?? false ); @@ -104,7 +105,7 @@ } $effect(() => { - if (isEditing && textareaElement) { + if (editCtx.isEditing && textareaElement) { autoResizeTextarea(textareaElement); } }); @@ -131,16 +132,16 @@ {/if} - {#if isEditing} + {#if editCtx.isEditing}
@@ -150,19 +151,24 @@ onShouldBranchAfterEditChange?.(checked === true)} + onCheckedChange={(checked) => (shouldBranchAfterEdit = checked === true)} />
- - @@ -239,7 +245,7 @@ {/if}
- {#if message.timestamp && !isEditing} + {#if message.timestamp && !editCtx.isEditing} void; - onSaveEdit: () => void; - onSaveEditOnly?: () => void; - onEditedContentChange: (content: string) => void; - onEditedExtrasChange?: (extras: DatabaseMessageExtra[]) => void; - onEditedUploadedFilesChange?: (files: ChatUploadedFile[]) => void; - } - - let { - editedContent, - editedExtras = [], - editedUploadedFiles = [], - originalContent, - originalExtras = [], - showSaveOnlyOption = false, - onCancelEdit, - onSaveEdit, - onSaveEditOnly, - onEditedContentChange, - onEditedExtrasChange, - onEditedUploadedFilesChange - }: Props = $props(); + const editCtx = getMessageEditContext(); let inputAreaRef: ChatForm | undefined = $state(undefined); let saveWithoutRegenerate = $state(false); let showDiscardDialog = $state(false); let hasUnsavedChanges = $derived.by(() => { - if (editedContent !== originalContent) return true; - if (editedUploadedFiles.length > 0) return true; + if (editCtx.editedContent !== editCtx.originalContent) return true; + if (editCtx.editedUploadedFiles.length > 0) return true; const extrasChanged = - editedExtras.length !== originalExtras.length || - editedExtras.some((extra, i) => extra !== originalExtras[i]); + editCtx.editedExtras.length !== editCtx.originalExtras.length || + editCtx.editedExtras.some((extra, i) => extra !== editCtx.originalExtras[i]); if (extrasChanged) return true; @@ -54,11 +27,11 @@ }); let hasAttachments = $derived( - (editedExtras && editedExtras.length > 0) || - (editedUploadedFiles && editedUploadedFiles.length > 0) + (editCtx.editedExtras && editCtx.editedExtras.length > 0) || + (editCtx.editedUploadedFiles && editCtx.editedUploadedFiles.length > 0) ); - let canSubmit = $derived(editedContent.trim().length > 0 || hasAttachments); + let canSubmit = $derived(editCtx.editedContent.trim().length > 0 || hasAttachments); function handleGlobalKeydown(event: KeyboardEvent) { if (event.key === 'Escape') { @@ -71,47 +44,40 @@ if (hasUnsavedChanges) { showDiscardDialog = true; } else { - onCancelEdit(); + editCtx.cancel(); } } function handleSubmit() { if (!canSubmit) return; - if (saveWithoutRegenerate && onSaveEditOnly) { - onSaveEditOnly(); + if (saveWithoutRegenerate && editCtx.showSaveOnlyOption) { + editCtx.saveOnly(); } else { - onSaveEdit(); + editCtx.save(); } saveWithoutRegenerate = false; } function handleAttachmentRemove(index: number) { - if (!onEditedExtrasChange) return; - - const newExtras = [...editedExtras]; + const newExtras = [...editCtx.editedExtras]; newExtras.splice(index, 1); - onEditedExtrasChange(newExtras); + editCtx.setExtras(newExtras); } function handleUploadedFileRemove(fileId: string) { - if (!onEditedUploadedFilesChange) return; - - const newFiles = editedUploadedFiles.filter((f) => f.id !== fileId); - onEditedUploadedFilesChange(newFiles); + const newFiles = editCtx.editedUploadedFiles.filter((f) => f.id !== fileId); + editCtx.setUploadedFiles(newFiles); } async function handleFilesAdd(files: File[]) { - if (!onEditedUploadedFilesChange) return; - const processed = await processFilesToChatUploaded(files); - - onEditedUploadedFilesChange([...editedUploadedFiles, processed].flat()); + editCtx.setUploadedFiles([...editCtx.editedUploadedFiles, ...processed]); } function handleUploadedFilesChange(files: ChatUploadedFile[]) { - onEditedUploadedFilesChange?.(files); + editCtx.setUploadedFiles(files); } $effect(() => { @@ -128,11 +94,11 @@
- {#if showSaveOnlyOption && onSaveEditOnly} + {#if editCtx.showSaveOnlyOption}
@@ -169,6 +135,6 @@ cancelText="Keep editing" variant="destructive" icon={AlertTriangle} - onConfirm={onCancelEdit} + onConfirm={editCtx.cancel} onCancel={() => (showDiscardDialog = false)} /> diff --git a/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageMcpPrompt.svelte b/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageMcpPrompt.svelte index b29f706339..4fbaf02f96 100644 --- a/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageMcpPrompt.svelte +++ b/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageMcpPrompt.svelte @@ -1,5 +1,6 @@
- {#if isEditing} - + {#if editCtx.isEditing} + {:else} void; - onSaveEdit: () => void; - onEditKeydown: (event: KeyboardEvent) => void; - onEditedContentChange: (content: string) => void; onCopy: () => void; onEdit: () => void; onDelete: () => void; @@ -37,15 +33,9 @@ let { class: className = '', message, - isEditing, - editedContent, siblingInfo = null, showDeleteDialog, deletionInfo, - onCancelEdit, - onSaveEdit, - onEditKeydown, - onEditedContentChange, onCopy, onEdit, onDelete, @@ -55,10 +45,25 @@ textareaElement = $bindable() }: Props = $props(); + const editCtx = getMessageEditContext(); + + function handleEditKeydown(event: KeyboardEvent) { + if (event.key === 'Enter' && !event.shiftKey && !isIMEComposing(event)) { + event.preventDefault(); + + editCtx.save(); + } else if (event.key === 'Escape') { + event.preventDefault(); + + editCtx.cancel(); + } + } + let isMultiline = $state(false); let messageElement: HTMLElement | undefined = $state(); let isExpanded = $state(false); let contentHeight = $state(0); + const MAX_HEIGHT = 200; // pixels const currentConfig = config(); @@ -98,24 +103,29 @@ class="group flex flex-col items-end gap-3 md:gap-2 {className}" role="group" > - {#if isEditing} + {#if editCtx.isEditing}
- - diff --git a/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageUser.svelte b/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageUser.svelte index 565fcef8fa..afc79dd2ef 100644 --- a/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageUser.svelte +++ b/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageUser.svelte @@ -1,6 +1,7 @@