From 82f6094aa2a3b58a8d2f66f1ad181a3ae3760cdc Mon Sep 17 00:00:00 2001 From: Pascal Date: Sun, 1 Feb 2026 12:06:25 +0100 Subject: [PATCH] feat: render images inline below attachment markers in tool results Parse tool results line-by-line to display images immediately after their [Attachment saved: xxx.png] markers. Fixes previous commit where all images from all tool calls were shown in every section. Each tool call now displays only its own images. Uses Svelte derived for memoization to avoid re-parsing on every streaming chunk. Parsing only occurs when section.toolResult or message.extra changes --- .../ChatMessageAgenticContent.svelte | 69 +++++++++++++------ 1 file changed, 48 insertions(+), 21 deletions(-) diff --git a/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageAgenticContent.svelte b/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageAgenticContent.svelte index 49d1a62c80..f1810412ff 100644 --- a/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageAgenticContent.svelte +++ b/tools/server/webui/src/lib/components/app/chat/ChatMessages/ChatMessageAgenticContent.svelte @@ -9,7 +9,7 @@ import { AgenticSectionType, AttachmentType } from '$lib/enums'; import { formatJsonPretty } from '$lib/utils'; import { parseAgenticContent, type AgenticSection } from '$lib/utils/agentic'; - import type { DatabaseMessage } from '$lib/types/database'; + import type { DatabaseMessage, DatabaseMessageExtraImageFile } from '$lib/types/database'; interface Props { /** Optional database message for context */ @@ -28,6 +28,16 @@ const showToolCallInProgress = $derived(config().showToolCallInProgress as boolean); const showThoughtInProgress = $derived(config().showThoughtInProgress as boolean); + // Parse toolResults with images only when sections or message.extra change + const sectionsParsed = $derived( + sections.map((section) => ({ + ...section, + parsedLines: section.toolResult + ? parseToolResultWithImages(section.toolResult, message?.extra) + : [] + })) + ); + function getDefaultExpanded(section: AgenticSection): boolean { if ( section.type === AgenticSectionType.TOOL_CALL_PENDING || @@ -56,10 +66,34 @@ expandedStates[index] = !currentState; } + + type ToolResultLine = { + text: string; + image?: DatabaseMessageExtraImageFile; + }; + + function parseToolResultWithImages( + toolResult: string, + extras?: DatabaseMessage['extra'] + ): ToolResultLine[] { + const lines = toolResult.split('\n'); + return lines.map((line) => { + const match = line.match(/\[Attachment saved: ([^\]]+)\]/); + if (!match || !extras) return { text: line }; + + const attachmentName = match[1]; + const image = extras.find( + (e): e is DatabaseMessageExtraImageFile => + e.type === AttachmentType.IMAGE && e.name === attachmentName + ); + + return { text: line, image }; + }); + }
- {#each sections as section, index (index)} + {#each sectionsParsed as section, index (index)} {#if section.type === AgenticSectionType.TEXT}
@@ -144,26 +178,19 @@ {/if}
{#if section.toolResult} -
- -
{section.toolResult}
+
+ {#each section.parsedLines as line, i (i)} +
{line.text}
+ {#if line.image} + {line.image.name} + {/if} + {/each}
- - {#if message?.extra} - {@const images = message.extra.filter((e) => e.type === AttachmentType.IMAGE)} - {#if images.length > 0} -
- {#each images as image (image.name)} - {image.name} - {/each} -
- {/if} - {/if} {:else if isPending}
Waiting for result...