From 348fe85bc1b96ac8c980128b1fccd9d1dea7a83f Mon Sep 17 00:00:00 2001 From: Carson Date: Wed, 16 Jul 2025 12:40:19 -0500 Subject: [PATCH 1/2] Close #59: Add update_chat_user_input() to mirror chat.update_user_input() --- pkg-r/NAMESPACE | 1 + pkg-r/R/chat.R | 80 +++++++++++++++++++++++++++++ pkg-r/R/utils.R | 5 ++ pkg-r/man/update_chat_user_input.Rd | 68 ++++++++++++++++++++++++ 4 files changed, 154 insertions(+) create mode 100644 pkg-r/man/update_chat_user_input.Rd diff --git a/pkg-r/NAMESPACE b/pkg-r/NAMESPACE index 56116f09..5bb459a5 100644 --- a/pkg-r/NAMESPACE +++ b/pkg-r/NAMESPACE @@ -10,6 +10,7 @@ export(chat_mod_ui) export(chat_ui) export(markdown_stream) export(output_markdown_stream) +export(update_chat_user_input) if (getRversion() < "4.3.0") importFrom("S7", "@") import(S7) import(rlang) diff --git a/pkg-r/R/chat.R b/pkg-r/R/chat.R index 8683a701..ff89a821 100644 --- a/pkg-r/R/chat.R +++ b/pkg-r/R/chat.R @@ -541,3 +541,83 @@ chat_clear <- function(id, session = getDefaultReactiveDomain()) { ) ) } + + +#' Update the user input of a chat control +#' +#' @param id The ID of the chat element +#' @param value The value to set the user input to. If `NULL`, the input will not be updated. +#' @param placeholder The placeholder text for the user input +#' @param submit Whether to automatically submit the text for the user. Requires `value`. +#' @param focus Whether to move focus to the input element. Requires `value`. +#' @param session The Shiny session object +#' +#' @returns Returns nothing (\code{invisible(NULL)}). +#' +#' @export +#' @examplesIf interactive() +#' library(shiny) +#' library(bslib) +#' library(shinychat) +#' +#' ui <- page_fillable( +#' chat_ui("chat"), +#' layout_columns( +#' fill = FALSE, +#' actionButton("update_placeholder", "Update placeholder"), +#' actionButton("update_value", "Update user input") +#' ) +#' ) +#' +#' server <- function(input, output, session) { +#' observeEvent(input$update_placeholder, { +#' update_chat_user_input("chat", placeholder = "New placeholder text") +#' }) +#' +#' observeEvent(input$update_value, { +#' update_chat_user_input("chat", value = "New user input", focus = TRUE) +#' }) +#' +#' observeEvent(input$chat_user_input, { +#' response <- paste0("You said: ", input$chat_user_input) +#' chat_append("chat", response) +#' }) +#' } +#' +#' shinyApp(ui, server) + +update_chat_user_input <- function( + id, + ..., + value = NULL, + placeholder = NULL, + submit = FALSE, + focus = FALSE, + session = getDefaultReactiveDomain() +) { + check_active_session(session) + + if (is.null(value) && (submit || focus)) { + rlang::abort( + "An input `value` must be provided when `submit` or `focus` are `TRUE`." + ) + } + + vals <- drop_nulls( + list( + value = value, + placeholder = placeholder, + submit = submit, + focus = focus + ) + ) + + session$sendCustomMessage( + "shinyChatMessage", + list( + id = resolve_id(id, session), + handler = "shiny-chat-update-user-input", + obj = vals + ) + ) +} diff --git a/pkg-r/R/utils.R b/pkg-r/R/utils.R index e1373871..ca4a09d9 100644 --- a/pkg-r/R/utils.R +++ b/pkg-r/R/utils.R @@ -18,3 +18,8 @@ strip_ansi <- function(text) { ansi_pattern <- "(\x1B|\x033)\\[[0-9;?=<>]*[@-~]" gsub(ansi_pattern, "", text) } + + +drop_nulls <- function(x) { + x[!vapply(x, is.null, FUN.VALUE = logical(1))] +} diff --git a/pkg-r/man/update_chat_user_input.Rd b/pkg-r/man/update_chat_user_input.Rd new file mode 100644 index 00000000..624d1d81 --- /dev/null +++ b/pkg-r/man/update_chat_user_input.Rd @@ -0,0 +1,68 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/chat.R +\name{update_chat_user_input} +\alias{update_chat_user_input} +\title{Update the user input of a chat control} +\usage{ +update_chat_user_input( + id, + ..., + value = NULL, + placeholder = NULL, + submit = FALSE, + focus = FALSE, + session = getDefaultReactiveDomain() +) +} +\arguments{ +\item{id}{The ID of the chat element} + +\item{value}{The value to set the user input to. If \code{NULL}, the input will not be updated.} + +\item{placeholder}{The placeholder text for the user input} + +\item{submit}{Whether to automatically submit the text for the user. Requires \code{value}.} + +\item{focus}{Whether to move focus to the input element. Requires \code{value}.} + +\item{session}{The Shiny session object} +} +\value{ +Returns nothing (\code{invisible(NULL)}). +} +\description{ +Update the user input of a chat control +} +\examples{ +\dontshow{if (interactive()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +library(shiny) +library(bslib) +library(shinychat) + +ui <- page_fillable( + chat_ui("chat"), + layout_columns( + fill = FALSE, + actionButton("update_placeholder", "Update placeholder"), + actionButton("update_value", "Update user input") + ) +) + +server <- function(input, output, session) { + observeEvent(input$update_placeholder, { + update_chat_user_input("chat", placeholder = "New placeholder text") + }) + + observeEvent(input$update_value, { + update_chat_user_input("chat", value = "New user input", focus = TRUE) + }) + + observeEvent(input$chat_user_input, { + response <- paste0("You said: ", input$chat_user_input) + chat_append("chat", response) + }) +} + +shinyApp(ui, server) +\dontshow{\}) # examplesIf} +} From 17b642a322c981a2cea829daabe4123dde86c03f Mon Sep 17 00:00:00 2001 From: Carson Date: Wed, 16 Jul 2025 12:43:39 -0500 Subject: [PATCH 2/2] Check dots are empty; update news --- pkg-r/NEWS.md | 5 ++++- pkg-r/R/chat.R | 4 ++-- pkg-r/man/update_chat_user_input.Rd | 5 ++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/pkg-r/NEWS.md b/pkg-r/NEWS.md index 941e15b1..7b9d9d60 100644 --- a/pkg-r/NEWS.md +++ b/pkg-r/NEWS.md @@ -1,8 +1,11 @@ # shinychat (development version) -## New features and improvements +## New features * Added `chat_enable_bookmarking()` which adds Shiny bookmarking hooks to save and restore the `{ellmer}` chat client. (#28) +* Added `update_chat_user_input()` for programmatically updating the user input of a chat UI element. (#78) + +## Improvements * `chat_app()` now correctly restores the chat client state when refreshing the app, e.g. by reloading the page. (#71) diff --git a/pkg-r/R/chat.R b/pkg-r/R/chat.R index ff89a821..8237e8a9 100644 --- a/pkg-r/R/chat.R +++ b/pkg-r/R/chat.R @@ -546,14 +546,13 @@ chat_clear <- function(id, session = getDefaultReactiveDomain()) { #' Update the user input of a chat control #' #' @param id The ID of the chat element +#' @param ... Currently unused, but reserved for future use. #' @param value The value to set the user input to. If `NULL`, the input will not be updated. #' @param placeholder The placeholder text for the user input #' @param submit Whether to automatically submit the text for the user. Requires `value`. #' @param focus Whether to move focus to the input element. Requires `value`. #' @param session The Shiny session object #' -#' @returns Returns nothing (\code{invisible(NULL)}). -#' #' @export #' @examplesIf interactive() #' library(shiny) @@ -595,6 +594,7 @@ update_chat_user_input <- function( focus = FALSE, session = getDefaultReactiveDomain() ) { + rlang::check_dots_empty() check_active_session(session) if (is.null(value) && (submit || focus)) { diff --git a/pkg-r/man/update_chat_user_input.Rd b/pkg-r/man/update_chat_user_input.Rd index 624d1d81..828a546f 100644 --- a/pkg-r/man/update_chat_user_input.Rd +++ b/pkg-r/man/update_chat_user_input.Rd @@ -17,6 +17,8 @@ update_chat_user_input( \arguments{ \item{id}{The ID of the chat element} +\item{...}{Currently unused, but reserved for future use.} + \item{value}{The value to set the user input to. If \code{NULL}, the input will not be updated.} \item{placeholder}{The placeholder text for the user input} @@ -27,9 +29,6 @@ update_chat_user_input( \item{session}{The Shiny session object} } -\value{ -Returns nothing (\code{invisible(NULL)}). -} \description{ Update the user input of a chat control }