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 +
+ )} +
+
+
) },