From 21848889cdee0f0e485780bd2fd97be1cc68ef61 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Tue, 7 Jul 2026 14:41:05 +0200 Subject: [PATCH] feat(vscode): render read-tool images inline in chat view The read tool already produces base64 data URL attachments for image files and threads them through SSE into the webview part store, but the tool renderer never received them. Thread part.state.attachments into ToolProps and render image attachments inline below the read tool card, reusing the existing user-attachment image pattern with click-to-open preview. No backend, schema, or SDK changes needed. --- .changeset/inline-read-images.md | 5 ++++ .../kilo-ui/src/components/message-part.css | 29 +++++++++++++++++++ .../kilo-ui/src/components/message-part.tsx | 25 ++++++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 .changeset/inline-read-images.md diff --git a/.changeset/inline-read-images.md b/.changeset/inline-read-images.md new file mode 100644 index 00000000000..7d4a511f0d0 --- /dev/null +++ b/.changeset/inline-read-images.md @@ -0,0 +1,5 @@ +--- +"kilo-code": minor +--- + +Render images inline in the chat view when the agent reads an image file. Images appear below the read tool card and can be clicked to open a full-size preview. diff --git a/packages/kilo-ui/src/components/message-part.css b/packages/kilo-ui/src/components/message-part.css index 5a3fd732da0..d55bc2c4ce9 100644 --- a/packages/kilo-ui/src/components/message-part.css +++ b/packages/kilo-ui/src/components/message-part.css @@ -412,6 +412,35 @@ html[data-theme="kilo-vscode"] [data-component="bash-output"] { margin-bottom: 0px; } +[data-slot="tool-read-images"] { + display: flex; + flex-wrap: wrap; + gap: 8px; + padding-top: 8px; + max-width: min(100%, 64ch); +} + +[data-slot="tool-read-image"] { + max-width: 200px; + max-height: 200px; + border-radius: var(--radius-sm, 6px); + overflow: hidden; + border: 1px solid var(--border-weak-base); + cursor: pointer; + transition: border-color 0.15s ease; + + &:hover { + border-color: var(--border-strong-base); + } +} + +[data-slot="tool-read-image-img"] { + display: block; + max-width: 100%; + max-height: 200px; + object-fit: contain; +} + [data-component="todos"] { [data-slot="message-part-todo-hidden"] { padding: 2px 0 2px 28px; diff --git a/packages/kilo-ui/src/components/message-part.tsx b/packages/kilo-ui/src/components/message-part.tsx index 7fca6106088..a5ab17ed51d 100644 --- a/packages/kilo-ui/src/components/message-part.tsx +++ b/packages/kilo-ui/src/components/message-part.tsx @@ -995,6 +995,7 @@ export interface ToolProps { callID?: string output?: string status?: string + attachments?: FilePart[] hideDetails?: boolean defaultOpen?: boolean forceOpen?: boolean @@ -1243,6 +1244,8 @@ PART_MAPPING["tool"] = function ToolPartDisplay(props) { // @ts-expect-error output={part.state.output} status={part.state.status} + // @ts-expect-error + attachments={part.state.attachments} hideDetails={props.hideDetails} defaultOpen={props.defaultOpen} animate @@ -1746,6 +1749,7 @@ ToolRegistry.register({ render(props) { const data = useData() const i18n = useI18n() + const dialog = useDialog() const args: string[] = [] if (props.input.offset) args.push("offset=" + props.input.offset) if (props.input.limit) args.push("limit=" + props.input.limit) @@ -1755,6 +1759,10 @@ ToolRegistry.register({ return value.filter((p): p is string => typeof p === "string") }) const pending = createMemo(() => busy(props.status)) + const images = createMemo(() => + (props.attachments ?? []).filter((f) => f.mime.startsWith("image/") && f.url), + ) + const preview = (url: string, alt?: string) => dialog.show(() => ) return ( <> )} + 0}> +
+ + {(file) => ( +
preview(file.url, file.filename)}> + {file.filename +
+ )} +
+
+
) },