From c1b716b5a53f3710a97fbb2350dd4c8f4af1cfc5 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 2 Oct 2025 16:07:52 -0400 Subject: [PATCH 1/2] feat(chat_mod_ui): Improve `$clear()` to also set initial messages --- pkg-r/R/chat_app.R | 98 +++++++++++++++++++++++++++++------------- pkg-r/R/utils-ellmer.R | 12 ++++++ 2 files changed, 80 insertions(+), 30 deletions(-) create mode 100644 pkg-r/R/utils-ellmer.R diff --git a/pkg-r/R/chat_app.R b/pkg-r/R/chat_app.R index 14222572..552df109 100644 --- a/pkg-r/R/chat_app.R +++ b/pkg-r/R/chat_app.R @@ -82,8 +82,13 @@ #' new user input. Takes the same arguments as [update_chat_user_input()], #' except for `id` and `session`, which are supplied automatically. #' * `clear()`: A function to clear the chat history and the chat UI. -#' Takes a single argument, `clear_history`, which indicates whether to -#' also clear the chat history in the `client` object. Defaults to `TRUE`. +#' `clear()` takes an optional list of `messages` used to initialize the +#' chat after clearing. `messages` should be a list of messages, where +#' each message is a list with `role` and `content` fields. The +#' `client_history` argument controls how the chat client's history is +#' updated after clearing. It can be one of: `"clear"` the chat history; +#' `"set"` the chat history to `messages`; `"append"` `messages` to the +#' existing chat history; or `"keep"` the existing chat history. #' * `client`: The chat client object, which is mutated as you chat. #' #' @describeIn chat_app A simple Shiny app for live chatting. Note that this @@ -201,40 +206,73 @@ chat_mod_server <- function( } }) - list( - last_turn = shiny::reactive(last_turn()), - last_input = shiny::reactive(last_input()), - client = client, - update_user_input = function( - value = NULL, + chat_update_user_input <- function( + value = NULL, + ..., + placeholder = NULL, + submit = FALSE, + focus = FALSE + ) { + update_chat_user_input( + "chat", + value = value, + placeholder = placeholder, + submit = submit, + focus = focus, ..., - placeholder = NULL, - submit = FALSE, - focus = FALSE - ) { - update_chat_user_input( - "chat", - value = value, - placeholder = placeholder, - submit = submit, - focus = focus, - ..., - session = session - ) - }, - clear = function(clear_history = TRUE) { - if (!rlang::is_bool(clear_history)) { + session = session + ) + } + + client_clear <- function( + messages = NULL, + client_history = c("clear", "set", "append", "keep") + ) { + client_history <- arg_match(client_history) + + if (!is.null(messages)) { + if (rlang::is_string(messages)) { + # Promote strings to single assistant message + messages <- list(list(role = "assistant", content = messages)) + } + if (!rlang::is_list(messages)) { cli::cli_abort( - "{.var clear_history} must be {.or {.val {c(TRUE, FALSE)}}}." + "{.var messages} must be a list of messages, and each message must be a list with {.field role} and {.field content}." ) } - chat_clear("chat", session = session) - if (isTRUE(clear_history)) { - client$set_turns(list()) + if (length(intersect(c("role", "content"), names(messages))) == 2) { + # Catch the single-message case and promote it to a list of messages + messages <- list(messages) + } + } + + chat_clear("chat", session = session) + if (!is.null(messages)) { + for (msg in messages) { + chat_append("chat", msg$content, role = msg$role, session = session) } - last_turn(NULL) - last_input(NULL) } + + if (client_history == "clear") { + client$set_turns(list()) + } else if (client_history == "set") { + client$set_turns(as_ellmer_turns(messages)) + } else if (client_history == "append") { + turns <- client$get_turns() + turns <- c(turns, as_ellmer_turns(messages)) + client$set_turns(turns) + } + + last_turn(NULL) + last_input(NULL) + } + + list( + last_turn = shiny::reactive(last_turn()), + last_input = shiny::reactive(last_input()), + client = client, + update_user_input = chat_update_user_input, + clear = client_clear ) }) } diff --git a/pkg-r/R/utils-ellmer.R b/pkg-r/R/utils-ellmer.R new file mode 100644 index 00000000..9d9f798e --- /dev/null +++ b/pkg-r/R/utils-ellmer.R @@ -0,0 +1,12 @@ +as_ellmer_turns <- function(messages) { + if (is.null(messages) || length(messages) == 0) { + return(list()) + } + + map(messages, function(msg) { + ellmer::Turn( + role = msg$role, + contents = list(ellmer::ContentText(msg$content)) + ) + }) +} From c7c1e280ec575c16162df9813ced7b66f5ef7c4c Mon Sep 17 00:00:00 2001 From: gadenbuie Date: Thu, 2 Oct 2025 20:19:14 +0000 Subject: [PATCH 2/2] `devtools::document()` (GitHub Actions) --- pkg-r/man/chat_app.Rd | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pkg-r/man/chat_app.Rd b/pkg-r/man/chat_app.Rd index b61066da..923eb8a6 100644 --- a/pkg-r/man/chat_app.Rd +++ b/pkg-r/man/chat_app.Rd @@ -56,8 +56,13 @@ and a list with: new user input. Takes the same arguments as \code{\link[=update_chat_user_input]{update_chat_user_input()}}, except for \code{id} and \code{session}, which are supplied automatically. \item \code{clear()}: A function to clear the chat history and the chat UI. -Takes a single argument, \code{clear_history}, which indicates whether to -also clear the chat history in the \code{client} object. Defaults to \code{TRUE}. +\code{clear()} takes an optional list of \code{messages} used to initialize the +chat after clearing. \code{messages} should be a list of messages, where +each message is a list with \code{role} and \code{content} fields. The +\code{client_history} argument controls how the chat client's history is +updated after clearing. It can be one of: \code{"clear"} the chat history; +\code{"set"} the chat history to \code{messages}; \code{"append"} \code{messages} to the +existing chat history; or \code{"keep"} the existing chat history. \item \code{client}: The chat client object, which is mutated as you chat. } }