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.
This commit is contained in:
marius-kilocode
2026-07-07 14:41:05 +02:00
parent 08f0bf6457
commit 21848889cd
3 changed files with 59 additions and 0 deletions
+5
View File
@@ -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.
@@ -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;
@@ -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(() => <ImagePreview src={url} alt={alt} />)
return (
<>
<BasicTool
@@ -1783,6 +1791,23 @@ ToolRegistry.register({
/>
)}
</For>
<Show when={images().length > 0}>
<div data-slot="tool-read-images">
<For each={images()}>
{(file) => (
<div data-slot="tool-read-image" onClick={() => preview(file.url, file.filename)}>
<img
data-slot="tool-read-image-img"
src={file.url}
alt={file.filename ?? i18n.t("ui.message.attachment.alt")}
loading="lazy"
decoding="async"
/>
</div>
)}
</For>
</div>
</Show>
</>
)
},