Skip to content

Commit 1cec7a6

Browse files
committed
feat: Support image/pdf content in tool results
1 parent fa7dfa2 commit 1cec7a6

File tree

1 file changed

+44
-12
lines changed

1 file changed

+44
-12
lines changed

pkg-r/R/contents_shinychat.R

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,20 @@ S7::method(contents_shinychat, ellmer::ContentText) <- function(content) {
136136
content@text
137137
}
138138

139+
S7::method(contents_shinychat, ellmer::ContentPDF) <- function(content) {
140+
htmltools::div(
141+
class = "shinychat-pdf badge fs-6 text-bg-secondary",
142+
htmltools::span(
143+
class = "shinychat-pdf__icon me-1",
144+
htmltools::HTML(ICON_FILE_PDF_FILL)
145+
),
146+
htmltools::span(
147+
class = "shinychat-pdf__filename font-monospace"
148+
content@filename
149+
)
150+
)
151+
}
152+
139153
new_tool_card <- function(type, request_id, tool_name, ...) {
140154
type <- arg_match(type, c("request", "result"))
141155

@@ -316,18 +330,22 @@ tool_result_display <- function(content, display = NULL) {
316330
use_basic_display <- opt_shinychat_tool_display() == "basic"
317331

318332
if (tool_errored(content) || use_basic_display || !has_display) {
319-
return(list(value = tool_string(content), value_type = "code"))
333+
return(tool_default_display(content))
320334
}
321335

322336
if (is.list(display)) {
323337
has_type <- intersect(c("html", "markdown", "text"), names(display))
324338
if (length(has_type) > 0) {
325339
value_type <- has_type[1]
326-
return(list(value = display[[value_type]], value_type = value_type))
340+
return(as_tool_display(display[[value_type]], value_type))
327341
}
328342
}
329343

330-
list(value = tool_string(content), value_type = "code")
344+
tool_default_display(content)
345+
}
346+
347+
as_tool_display <- function(value, type = "code") {
348+
list(value = value, value_type = type)
331349
}
332350

333351
# Copied from
@@ -336,21 +354,33 @@ tool_errored <- function(x) !is.null(x@error)
336354
tool_error_string <- function(x) {
337355
if (inherits(x@error, "condition")) conditionMessage(x@error) else x@error
338356
}
339-
tool_string <- function(x) {
357+
tool_default_display <- function(x) {
340358
if (tool_errored(x)) {
341359
# Changed from original: if tool errored, just return the error message
342-
strip_ansi(tool_error_string(x))
343-
} else if (inherits(x@value, "AsIs")) {
344-
x@value
345-
} else if (inherits(x@value, "json")) {
346-
x@value
347-
} else if (is.character(x@value)) {
348-
paste(x@value, collapse = "\n")
360+
as_tool_display(strip_ansi(tool_error_string(x)))
361+
}
362+
value <- x@value
363+
if (inherits(value, "AsIs")) {
364+
as_tool_display(value)
365+
} else if (inherits(value, "json")) {
366+
as_tool_display(value)
367+
} else if (is.character(value)) {
368+
as_tool_display(paste(value, collapse = "\n"))
369+
} else if (is_content_extra(value)) {
370+
as_tool_display(htmltools::div(contents_shinychat(value)), "html")
371+
} else if (is.list(value) && every(value, is_content_extra)) {
372+
as_tool_display(htmltools::div(map(value, contents_shinychat)), "html")
349373
} else {
350-
jsonlite::toJSON(x@value, auto_unbox = TRUE, pretty = 2, force = TRUE)
374+
as_tool_display(
375+
jsonlite::toJSON(value, auto_unbox = TRUE, pretty = 2, force = TRUE)
376+
)
351377
}
352378
}
353379

380+
is_content_extra <- function(x) {
381+
S7::S7_inherits(x, ellmer::ContentImage) ||
382+
S7::S7_inherits(x, ellmer::ContentPDF)
383+
}
354384

355385
S7::method(contents_shinychat, ellmer::Turn) <- function(content) {
356386
# Process all contents in the turn, filtering out empty results
@@ -427,3 +457,5 @@ S7::method(contents_shinychat, S7::new_S3_class(c("Chat", "R6"))) <- function(
427457

428458
compact(messages)
429459
}
460+
461+
ICON_FILE_PDF_FILL <- "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\" class=\"bi bi-file-pdf-fill \" style=\"height:1em;width:1em;fill:currentColor;vertical-align:-0.125em;\" aria-hidden=\"true\" role=\"img\" ><path d=\"M5.523 10.424c.14-.082.293-.162.459-.238a7.878 7.878 0 0 1-.45.606c-.28.337-.498.516-.635.572a.266.266 0 0 1-.035.012.282.282 0 0 1-.026-.044c-.056-.11-.054-.216.04-.36.106-.165.319-.354.647-.548zm2.455-1.647c-.119.025-.237.05-.356.078a21.035 21.035 0 0 0 .5-1.05 11.96 11.96 0 0 0 .51.858c-.217.032-.436.07-.654.114zm2.525.939a3.888 3.888 0 0 1-.435-.41c.228.005.434.022.612.054.317.057.466.147.518.209a.095.095 0 0 1 .026.064.436.436 0 0 1-.06.2.307.307 0 0 1-.094.124.107.107 0 0 1-.069.015c-.09-.003-.258-.066-.498-.256zM8.278 4.97c-.04.244-.108.524-.2.829a4.86 4.86 0 0 1-.089-.346c-.076-.353-.087-.63-.046-.822.038-.177.11-.248.196-.283a.517.517 0 0 1 .145-.04c.013.03.028.092.032.198.005.122-.007.277-.038.465z\"></path>\n<path fill-rule=\"evenodd\" d=\"M4 0h8a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2zm.165 11.668c.09.18.23.343.438.419.207.075.412.04.58-.03.318-.13.635-.436.926-.786.333-.401.683-.927 1.021-1.51a11.64 11.64 0 0 1 1.997-.406c.3.383.61.713.91.95.28.22.603.403.934.417a.856.856 0 0 0 .51-.138c.155-.101.27-.247.354-.416.09-.181.145-.37.138-.563a.844.844 0 0 0-.2-.518c-.226-.27-.596-.4-.96-.465a5.76 5.76 0 0 0-1.335-.05 10.954 10.954 0 0 1-.98-1.686c.25-.66.437-1.284.52-1.794.036-.218.055-.426.048-.614a1.238 1.238 0 0 0-.127-.538.7.7 0 0 0-.477-.365c-.202-.043-.41 0-.601.077-.377.15-.576.47-.651.823-.073.34-.04.736.046 1.136.088.406.238.848.43 1.295a19.707 19.707 0 0 1-1.062 2.227 7.662 7.662 0 0 0-1.482.645c-.37.22-.699.48-.897.787-.21.326-.275.714-.08 1.103z\"></path></svg>"

0 commit comments

Comments
 (0)