Skip to content

Commit c398ac0

Browse files
committed
Render inline image previews
1 parent a23785b commit c398ac0

2 files changed

Lines changed: 86 additions & 9 deletions

File tree

pi-coding-agent-render.el

Lines changed: 81 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
(require 'pi-coding-agent-table)
4545
(require 'cl-lib)
4646
(require 'ansi-color)
47+
(require 'image)
4748

4849
;; Forward references for functions in other modules
4950
(declare-function pi-coding-agent-compact "pi-coding-agent-menu" (&optional custom-instructions))
@@ -1597,21 +1598,85 @@ streaming state changed."
15971598
event-type
15981599
event-content-index))))
15991600

1601+
(defun pi-coding-agent--image-type-from-mime (mime-type)
1602+
"Return an Emacs image type symbol for MIME-TYPE."
1603+
(pcase mime-type
1604+
("image/png" 'png)
1605+
((or "image/jpeg" "image/jpg") 'jpeg)
1606+
("image/gif" 'gif)
1607+
("image/webp" 'webp)
1608+
("image/svg+xml" 'svg)))
1609+
1610+
(defun pi-coding-agent--image-block-display (block)
1611+
"Return an image display object for image content BLOCK, or nil."
1612+
(when (display-images-p)
1613+
(let* ((mime-type (plist-get block :mimeType))
1614+
(image-type (pi-coding-agent--image-type-from-mime mime-type))
1615+
(data (plist-get block :data)))
1616+
(when (and image-type
1617+
(image-type-available-p image-type)
1618+
data)
1619+
(condition-case nil
1620+
(create-image (base64-decode-string data) image-type t
1621+
:max-width pi-coding-agent-image-preview-max-width)
1622+
(error nil))))))
1623+
1624+
(defun pi-coding-agent--render-image-content-block (block)
1625+
"Return a display string for image content BLOCK."
1626+
(let ((fallback (format "[image%s]"
1627+
(if-let* ((mime-type (or (plist-get block :mimeType)
1628+
(plist-get block :mime-type))))
1629+
(format ": %s" mime-type)
1630+
""))))
1631+
(if-let* ((image (pi-coding-agent--image-block-display block)))
1632+
(propertize fallback
1633+
'display image
1634+
'rear-nonsticky t
1635+
'help-echo fallback)
1636+
fallback)))
1637+
1638+
(defun pi-coding-agent--file-image-display (path)
1639+
"Return an image display object for local image PATH, or nil."
1640+
(when (and (stringp path)
1641+
(file-readable-p path)
1642+
(display-images-p)
1643+
(image-supported-file-p path))
1644+
(condition-case nil
1645+
(create-image path nil nil
1646+
:max-width pi-coding-agent-image-preview-max-width)
1647+
(error nil))))
1648+
1649+
(defun pi-coding-agent--render-file-image (path)
1650+
"Return a display string for local image PATH."
1651+
(let* ((mime-type (when-let* ((image-type (image-supported-file-p path)))
1652+
(pcase image-type
1653+
('svg "image/svg+xml")
1654+
(_ (format "image/%s" image-type)))))
1655+
(fallback (format "[image%s]"
1656+
(if mime-type (format ": %s" mime-type) ""))))
1657+
(if-let* ((image (pi-coding-agent--file-image-display path)))
1658+
(propertize fallback
1659+
'display image
1660+
'rear-nonsticky t
1661+
'help-echo fallback)
1662+
fallback)))
1663+
16001664
(defun pi-coding-agent--extract-text-from-content (content-blocks)
1601-
"Extract text from CONTENT-BLOCKS vector efficiently.
1602-
Returns the concatenated text from all text blocks.
1603-
Optimized for the common case of a single text block."
1665+
"Extract displayable content from CONTENT-BLOCKS vector.
1666+
Text blocks are concatenated as text; image blocks are rendered inline when
1667+
Emacs can display them, otherwise a textual placeholder is used."
16041668
(if (and (vectorp content-blocks) (> (length content-blocks) 0))
16051669
(let ((first-block (aref content-blocks 0)))
16061670
(if (and (= (length content-blocks) 1)
16071671
(equal (plist-get first-block :type) "text"))
16081672
;; Fast path: single text block (common case)
16091673
(or (plist-get first-block :text) "")
1610-
;; Slow path: multiple blocks, need to filter and concat
1674+
;; Slow path: multiple blocks, render supported block types.
16111675
(mapconcat (lambda (c)
1612-
(if (equal (plist-get c :type) "text")
1613-
(or (plist-get c :text) "")
1614-
""))
1676+
(pcase (plist-get c :type)
1677+
("text" (or (plist-get c :text) ""))
1678+
("image" (concat "\n" (pi-coding-agent--render-image-content-block c) "\n"))
1679+
(_ "")))
16151680
content-blocks "")))
16161681
""))
16171682

@@ -1779,12 +1844,18 @@ if none exists, render the result at point without a live overlay."
17791844
(is-edit-diff (and (equal tool-name "edit")
17801845
(not is-error)
17811846
(plist-get details :diff)))
1847+
(image-read-content
1848+
(when (equal tool-name "read")
1849+
(when-let* ((path (pi-coding-agent--tool-path args))
1850+
((pi-coding-agent--file-image-display path)))
1851+
(concat raw-output "\n" (pi-coding-agent--render-file-image path)))))
17821852
(display-content
17831853
(ansi-color-filter-apply
17841854
(pcase tool-name
17851855
("edit" (or (plist-get details :diff) raw-output))
17861856
("write" (or (plist-get args :content) raw-output))
1787-
((or "bash" "read") raw-output)
1857+
("bash" raw-output)
1858+
("read" (or image-read-content raw-output))
17881859
(_ (if-let* ((details-json
17891860
(pi-coding-agent--pretty-print-json details)))
17901861
(concat raw-output "\n\n"
@@ -1799,7 +1870,8 @@ if none exists, render the result at point without a live overlay."
17991870
(truncation (pi-coding-agent--truncate-to-visual-lines
18001871
display-content preview-limit width))
18011872
(hidden-count (plist-get truncation :hidden-lines))
1802-
(needs-collapse (> hidden-count 0))
1873+
(needs-collapse (and (not image-read-content)
1874+
(> hidden-count 0)))
18031875
(inhibit-read-only t))
18041876
(pi-coding-agent--with-scroll-preservation
18051877
(save-excursion

pi-coding-agent-ui.el

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,11 @@ When nil (the default), only the visible text is copied."
171171
:type 'boolean
172172
:group 'pi-coding-agent)
173173

174+
(defcustom pi-coding-agent-image-preview-max-width 900
175+
"Maximum width in pixels for inline image previews."
176+
:type 'integer
177+
:group 'pi-coding-agent)
178+
174179
(defcustom pi-coding-agent-quit-without-confirmation nil
175180
"Whether `pi-coding-agent-quit' skips confirmation for a live process.
176181
When non-nil, quitting a session never asks whether a running pi process

0 commit comments

Comments
 (0)