From 571aeba3962dba4e851fba903a3ef6d7e9e8fb08 Mon Sep 17 00:00:00 2001 From: Carson Date: Mon, 21 Jul 2025 10:58:50 -0500 Subject: [PATCH 01/14] Get bookmarking working --- pkg-r/R/chat_app.R | 33 +++++++++------------------------ pkg-r/R/client_state.R | 16 ++++++---------- 2 files changed, 15 insertions(+), 34 deletions(-) diff --git a/pkg-r/R/chat_app.R b/pkg-r/R/chat_app.R index 08cc9957..4f8601a9 100644 --- a/pkg-r/R/chat_app.R +++ b/pkg-r/R/chat_app.R @@ -111,30 +111,13 @@ check_ellmer_chat <- function(client) { #' provided or when the chat `client` doesn't already contain turns. Passed to #' `messages` in [chat_ui()]. #' @export -chat_mod_ui <- function(id, ..., client = NULL, messages = NULL) { - if (!is.null(client)) { - check_ellmer_chat(client) - - client_msgs <- map(client$get_turns(), function(turn) { - content <- ellmer::contents_markdown(turn) - if (is.null(content) || identical(content, "")) { - return(NULL) - } - list(role = turn@role, content = content) - }) - client_msgs <- compact(client_msgs) - - if (length(client_msgs)) { - if (!is.null(messages)) { - warn( - "`client` was provided and has initial messages, `messages` will be ignored." - ) - } - messages <- client_msgs - } - } - - shinychat::chat_ui( +chat_mod_ui <- function( + id, + ..., + client = lifecycle::deprecate_soft(), + messages = NULL +) { + chat_ui( shiny::NS(id, "chat"), messages = messages, ... @@ -158,6 +141,8 @@ chat_mod_server <- function(id, client) { ) shiny::moduleServer(id, function(input, output, session) { + chat_enable_bookmarking("chat", client, session = session) + shiny::observeEvent(input$chat_user_input, { append_stream_task$invoke( client, diff --git a/pkg-r/R/client_state.R b/pkg-r/R/client_state.R index a146e0bd..ba89f97d 100644 --- a/pkg-r/R/client_state.R +++ b/pkg-r/R/client_state.R @@ -73,7 +73,8 @@ method(client_set_state, S7::new_S3_class(c("Chat", "R6"))) <- replayed_turns <- lapply( recorded_turns, - ellmer::contents_replay + ellmer::contents_replay, + tools = client$get_tools() ) client$set_turns(replayed_turns) @@ -84,15 +85,10 @@ method(client_set_ui, S7::new_S3_class(c("Chat", "R6"))) <- function(client, ..., id) { # TODO-future: Disable bookmarking when restoring. Leverage `tryCatch(finally={})` # TODO-barret-future; In shinychat, make this a single/internal custom message call to send all the messages at once (and then scroll) - lapply(client$get_turns(), function(turn) { - chat_append( - id, - # Use `contents_markdown()` as it handles image serialization - # TODO: Use `contents_shinychat()` from posit-dev/shinychat#52 - ellmer::contents_markdown(turn), - # turn_info$contents, - role = turn@role - ) + + msgs <- contents_shinychat(client) + lapply(msgs, function(x) { + chat_append(id, x$content, role = x$role) }) } From 4c8e676529ec394099b2ba5fe66ffc1ab127a85e Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 30 Jul 2025 14:18:47 -0400 Subject: [PATCH 02/14] chore(chat_mod_ui): Restore `client` argument --- pkg-r/R/chat_app.R | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/pkg-r/R/chat_app.R b/pkg-r/R/chat_app.R index 4f8601a9..ac79480e 100644 --- a/pkg-r/R/chat_app.R +++ b/pkg-r/R/chat_app.R @@ -114,9 +114,31 @@ check_ellmer_chat <- function(client) { chat_mod_ui <- function( id, ..., - client = lifecycle::deprecate_soft(), + client = NULL, messages = NULL ) { + if (!is.null(client)) { + check_ellmer_chat(client) + + client_msgs <- map(client$get_turns(), function(turn) { + content <- ellmer::contents_markdown(turn) + if (is.null(content) || identical(content, "")) { + return(NULL) + } + list(role = turn@role, content = content) + }) + client_msgs <- compact(client_msgs) + + if (length(client_msgs)) { + if (!is.null(messages)) { + warn( + "`client` was provided and has initial messages, `messages` will be ignored." + ) + } + messages <- client_msgs + } + } + chat_ui( shiny::NS(id, "chat"), messages = messages, From 36cf5bce86468f635bddcbf3f3f3ddd743a7ee90 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 30 Jul 2025 15:26:58 -0400 Subject: [PATCH 03/14] fix: Enable bookmarking in `chat_app()` and set it up in `chat_mod_server()` --- pkg-r/R/chat_app.R | 13 +++---------- pkg-r/R/chat_enable_bookmarking.R | 27 ++++++++++----------------- pkg-r/R/contents_shinychat.R | 14 ++++++++++++++ 3 files changed, 27 insertions(+), 27 deletions(-) create mode 100644 pkg-r/R/contents_shinychat.R diff --git a/pkg-r/R/chat_app.R b/pkg-r/R/chat_app.R index ac79480e..d02f618d 100644 --- a/pkg-r/R/chat_app.R +++ b/pkg-r/R/chat_app.R @@ -88,6 +88,8 @@ chat_app <- function(client, ...) { ) } + shiny::enableBookmarking("url") + server <- function(input, output, session) { chat_mod_server("chat", client) @@ -118,16 +120,7 @@ chat_mod_ui <- function( messages = NULL ) { if (!is.null(client)) { - check_ellmer_chat(client) - - client_msgs <- map(client$get_turns(), function(turn) { - content <- ellmer::contents_markdown(turn) - if (is.null(content) || identical(content, "")) { - return(NULL) - } - list(role = turn@role, content = content) - }) - client_msgs <- compact(client_msgs) + client_msgs <- contents_shinychat(client) if (length(client_msgs)) { if (!is.null(messages)) { diff --git a/pkg-r/R/chat_enable_bookmarking.R b/pkg-r/R/chat_enable_bookmarking.R index 81163182..8e5ba397 100644 --- a/pkg-r/R/chat_enable_bookmarking.R +++ b/pkg-r/R/chat_enable_bookmarking.R @@ -78,17 +78,6 @@ chat_enable_bookmarking <- function( # Verify bookmark store is not disabled. Bookmark options: "disable", "url", "server" bookmark_store <- shiny::getShinyOption("bookmarkStore", "disable") - # TODO: Q - I feel this should be removed. Since we are only adding hooks, it doesn't matter if it's enabled or not. If the user diables chat, it would be very annoying to receive error messages for code they may not own. - if (bookmark_store == "disable") { - rlang::abort( - paste0( - "Error: Shiny bookmarking is not enabled. ", - "Please enable bookmarking in your Shiny app either by calling ", - "`shiny::enableBookmarking(\"server\")` or by setting the parameter in ", - "`shiny::shinyApp(enableBookmarking = \"server\")`" - ) - ) - } # Exclude works with bookmark names excluded_names <- session$getBookmarkExclude() @@ -126,8 +115,10 @@ chat_enable_bookmarking <- function( client_set_state(client, client_state) # Set the UI - chat_clear(id) - client_set_ui(client, id = id) + shiny::withReactiveDomain(session, { + chat_clear(id) + client_set_ui(client, id = id) + }) }) # Update URL @@ -151,9 +142,11 @@ chat_enable_bookmarking <- function( cancel_update_bookmark <- NULL if (bookmark_on_input || bookmark_on_response) { cancel_update_bookmark <- - # Update the query string when bookmarked - shiny::onBookmarked(function(url) { - shiny::updateQueryString(url) + shiny::withReactiveDomain(session$rootScope(), { + # Update the query string when bookmarked + shiny::onBookmarked(function(url) { + shiny::updateQueryString(url) + }) }) } @@ -205,7 +198,7 @@ chat_update_bookmark <- function( prom <- promises::then(stream_promise, function(stream) { # Force a bookmark update when the stream ends! - session$doBookmark() + shiny::isolate(session$doBookmark()) }) return(prom) diff --git a/pkg-r/R/contents_shinychat.R b/pkg-r/R/contents_shinychat.R new file mode 100644 index 00000000..16fa7758 --- /dev/null +++ b/pkg-r/R/contents_shinychat.R @@ -0,0 +1,14 @@ +contents_shinychat <- function(client) { + # TODO(garrick): placeholder, will be replaces with S7 generic + check_ellmer_chat(client) + + client_msgs <- map(client$get_turns(), function(turn) { + content <- ellmer::contents_markdown(turn) + if (is.null(content) || identical(content, "")) { + return(NULL) + } + list(role = turn@role, content = content) + }) + + compact(client_msgs) +} From 99850c7d5da11d66ff3162883f27d867f8124abb Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 30 Jul 2025 15:59:32 -0400 Subject: [PATCH 04/14] rename: `chat_restore()` replaces `chat_enable_bookmarking()` --- pkg-r/NAMESPACE | 2 +- pkg-r/NEWS.md | 1 + pkg-r/R/chat_app.R | 32 +++++++++---------- ...at_enable_bookmarking.R => chat_restore.R} | 16 +++++++--- pkg-r/man/chat_app.Rd | 9 ++++-- ..._enable_bookmarking.Rd => chat_restore.Rd} | 10 +++--- pkg-r/pkgdown/_pkgdown.yml | 2 +- 7 files changed, 41 insertions(+), 31 deletions(-) rename pkg-r/R/{chat_enable_bookmarking.R => chat_restore.R} (93%) rename pkg-r/man/{chat_enable_bookmarking.Rd => chat_restore.Rd} (91%) diff --git a/pkg-r/NAMESPACE b/pkg-r/NAMESPACE index 5bb459a5..60e95dfc 100644 --- a/pkg-r/NAMESPACE +++ b/pkg-r/NAMESPACE @@ -4,9 +4,9 @@ export(chat_app) export(chat_append) export(chat_append_message) export(chat_clear) -export(chat_enable_bookmarking) export(chat_mod_server) export(chat_mod_ui) +export(chat_restore) export(chat_ui) export(markdown_stream) export(output_markdown_stream) diff --git a/pkg-r/NEWS.md b/pkg-r/NEWS.md index 7b9d9d60..23cdbf02 100644 --- a/pkg-r/NEWS.md +++ b/pkg-r/NEWS.md @@ -3,6 +3,7 @@ ## 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 diff --git a/pkg-r/R/chat_app.R b/pkg-r/R/chat_app.R index d02f618d..a8a8e905 100644 --- a/pkg-r/R/chat_app.R +++ b/pkg-r/R/chat_app.R @@ -88,7 +88,7 @@ chat_app <- function(client, ...) { ) } - shiny::enableBookmarking("url") + # shiny::enableBookmarking("url") server <- function(input, output, session) { chat_mod_server("chat", client) @@ -116,22 +116,9 @@ check_ellmer_chat <- function(client) { chat_mod_ui <- function( id, ..., - client = NULL, + client = lifecycle::deprecated(), messages = NULL ) { - if (!is.null(client)) { - client_msgs <- contents_shinychat(client) - - if (length(client_msgs)) { - if (!is.null(messages)) { - warn( - "`client` was provided and has initial messages, `messages` will be ignored." - ) - } - messages <- client_msgs - } - } - chat_ui( shiny::NS(id, "chat"), messages = messages, @@ -141,7 +128,12 @@ chat_mod_ui <- function( #' @describeIn chat_app A simple chat app module server. #' @export -chat_mod_server <- function(id, client) { +chat_mod_server <- function( + id, + client, + bookmark_on_input = TRUE, + bookmark_on_response = TRUE +) { check_ellmer_chat(client) append_stream_task <- shiny::ExtendedTask$new( @@ -156,7 +148,13 @@ chat_mod_server <- function(id, client) { ) shiny::moduleServer(id, function(input, output, session) { - chat_enable_bookmarking("chat", client, session = session) + chat_restore( + "chat", + client, + session = session, + bookmark_on_input = bookmark_on_input, + bookmark_on_response = bookmark_on_response + ) shiny::observeEvent(input$chat_user_input, { append_stream_task$invoke( diff --git a/pkg-r/R/chat_enable_bookmarking.R b/pkg-r/R/chat_restore.R similarity index 93% rename from pkg-r/R/chat_enable_bookmarking.R rename to pkg-r/R/chat_restore.R index 8e5ba397..245429c7 100644 --- a/pkg-r/R/chat_enable_bookmarking.R +++ b/pkg-r/R/chat_restore.R @@ -39,7 +39,7 @@ #' echo = TRUE #' ) #' # Update bookmark to chat on user submission and completed response -#' chat_enable_bookmarking("chat", chat_client) +#' chat_restore("chat", chat_client) #' #' observeEvent(input$chat_user_input, { #' stream <- chat_client$stream_async(input$chat_user_input) @@ -50,7 +50,7 @@ #' # Enable bookmarking! #' shinyApp(ui, server, enableBookmarking = "server") #' @export -chat_enable_bookmarking <- function( +chat_restore <- function( id, client, ..., @@ -72,7 +72,7 @@ chat_enable_bookmarking <- function( if (is.null(session)) { rlang::abort( - "A `session` must be provided. Be sure to call `chat_enable_bookmarking()` where a session context is available." + "A `session` must be provided. Be sure to call `chat_restore()` where a session context is available." ) } @@ -104,6 +104,11 @@ chat_enable_bookmarking <- function( state$values[[id]] <- client_state }) + cancel_set_ui <- observe({ + client_set_ui(client, id = id) + cancel_set_ui$destroy() + }) + # Restore cancel_on_restore_client <- session$onRestore(function(state) { @@ -112,6 +117,7 @@ chat_enable_bookmarking <- function( return() } + cancel_set_ui$destroy() client_set_state(client, client_state) # Set the UI @@ -150,7 +156,7 @@ chat_enable_bookmarking <- function( }) } - # Set callbacks to cancel if `chat_enable_bookmarking(id, client)` is called again with the same id + # Set callbacks to cancel if `chat_restore(id, client)` is called again with the same id # Only allow for bookmarks for each chat once. Last bookmark method would win if all values were to be computed. # Remove previous `on*()` methods under same hash (.. odd author behavior) previous_info <- get_session_chat_bookmark_info(session, id) @@ -162,7 +168,7 @@ chat_enable_bookmarking <- function( } } - # Store callbacks to cancel in case a new call to `chat_enable_bookmarking(id, client)` is called with the same id + # Store callbacks to cancel in case a new call to `chat_restore(id, client)` is called with the same id set_session_chat_bookmark_info( session, id, diff --git a/pkg-r/man/chat_app.Rd b/pkg-r/man/chat_app.Rd index 844e77eb..532fee9e 100644 --- a/pkg-r/man/chat_app.Rd +++ b/pkg-r/man/chat_app.Rd @@ -8,9 +8,14 @@ \usage{ chat_app(client, ...) -chat_mod_ui(id, ..., client = NULL, messages = NULL) +chat_mod_ui(id, ..., client = lifecycle::deprecated(), messages = NULL) -chat_mod_server(id, client) +chat_mod_server( + id, + client, + bookmark_on_input = TRUE, + bookmark_on_response = TRUE +) } \arguments{ \item{client}{A chat object created by \pkg{ellmer}, e.g. diff --git a/pkg-r/man/chat_enable_bookmarking.Rd b/pkg-r/man/chat_restore.Rd similarity index 91% rename from pkg-r/man/chat_enable_bookmarking.Rd rename to pkg-r/man/chat_restore.Rd index af25d651..556d7c89 100644 --- a/pkg-r/man/chat_enable_bookmarking.Rd +++ b/pkg-r/man/chat_restore.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/chat_enable_bookmarking.R -\name{chat_enable_bookmarking} -\alias{chat_enable_bookmarking} +% Please edit documentation in R/chat_restore.R +\name{chat_restore} +\alias{chat_restore} \title{Add Shiny bookmarking for shinychat} \usage{ -chat_enable_bookmarking( +chat_restore( id, client, ..., @@ -61,7 +61,7 @@ server <- function(input, output, session) { echo = TRUE ) # Update bookmark to chat on user submission and completed response - chat_enable_bookmarking("chat", chat_client) + chat_restore("chat", chat_client) observeEvent(input$chat_user_input, { stream <- chat_client$stream_async(input$chat_user_input) diff --git a/pkg-r/pkgdown/_pkgdown.yml b/pkg-r/pkgdown/_pkgdown.yml index a1bef232..ca6b3487 100644 --- a/pkg-r/pkgdown/_pkgdown.yml +++ b/pkg-r/pkgdown/_pkgdown.yml @@ -50,4 +50,4 @@ reference: - title: Bookmark chat history contents: - - chat_enable_bookmarking + - chat_restore From c354a3fcb609023804ae100335c3d3fbde60cc99 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 30 Jul 2025 16:00:17 -0400 Subject: [PATCH 05/14] chore: update news --- pkg-r/NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg-r/NEWS.md b/pkg-r/NEWS.md index 23cdbf02..5a12d946 100644 --- a/pkg-r/NEWS.md +++ b/pkg-r/NEWS.md @@ -2,7 +2,7 @@ ## New features -* Added `chat_enable_bookmarking()` which adds Shiny bookmarking hooks to save and restore the `{ellmer}` chat client. (#28) +* Added `chat_restore()` which adds Shiny bookmarking hooks to save and restore the `{ellmer}` chat client. (#28, #82) * Added `update_chat_user_input()` for programmatically updating the user input of a chat UI element. (#78) From 2ec5def1d10696a59ef4baf4338c83b20af00d15 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 30 Jul 2025 16:03:07 -0400 Subject: [PATCH 06/14] =?UTF-8?q?=F0=9F=A4=A6=E2=80=8D=E2=99=82=EF=B8=8F?= =?UTF-8?q?=20restore=20bookmarking=20in=20chat=5Fapp()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg-r/R/chat_app.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg-r/R/chat_app.R b/pkg-r/R/chat_app.R index a8a8e905..ff4604a1 100644 --- a/pkg-r/R/chat_app.R +++ b/pkg-r/R/chat_app.R @@ -88,7 +88,7 @@ chat_app <- function(client, ...) { ) } - # shiny::enableBookmarking("url") + shiny::enableBookmarking("url") server <- function(input, output, session) { chat_mod_server("chat", client) From f4dc4abac1396c15b8d508d78803e87299f0988d Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 30 Jul 2025 16:05:38 -0400 Subject: [PATCH 07/14] actually, enable bookmarking via shinyApp option --- pkg-r/R/chat_app.R | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg-r/R/chat_app.R b/pkg-r/R/chat_app.R index ff4604a1..bc6dfbc6 100644 --- a/pkg-r/R/chat_app.R +++ b/pkg-r/R/chat_app.R @@ -88,8 +88,6 @@ chat_app <- function(client, ...) { ) } - shiny::enableBookmarking("url") - server <- function(input, output, session) { chat_mod_server("chat", client) @@ -98,7 +96,7 @@ chat_app <- function(client, ...) { }) } - shiny::shinyApp(ui, server, ...) + shiny::shinyApp(ui, server, ..., enableBookmarking = "url") } check_ellmer_chat <- function(client) { From 5a4066e98be45d7141889bd2a1fdebaa0dcde028 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 30 Jul 2025 16:08:31 -0400 Subject: [PATCH 08/14] docs: Add `bookmark_store` param --- pkg-r/R/chat_app.R | 25 ++++++++++++++----------- pkg-r/man/chat_app.Rd | 8 +++++++- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/pkg-r/R/chat_app.R b/pkg-r/R/chat_app.R index bc6dfbc6..79a6c15d 100644 --- a/pkg-r/R/chat_app.R +++ b/pkg-r/R/chat_app.R @@ -62,6 +62,11 @@ #' @param ... In `chat_app()`, additional arguments are passed to #' [shiny::shinyApp()]. In `chat_mod_ui()`, additional arguments are passed to #' [chat_ui()]. +#' @param bookmark_store The bookmarking store to use for the app. Passed to +#' `enable_bookmarking` in [shiny::shinyApp()]. Defaults to `"url"`, which +#' uses the URL to store the chat state. URL-based bookmarking is limited in +#' size; use `"server"` to store the state on the server side without size +#' limitations; or disable bookmarking by setting this to `"disable"`. #' #' @returns #' * `chat_app()` returns a [shiny::shinyApp()] object. @@ -73,20 +78,18 @@ #' app is suitable for interactive use by a single user; do not use #' `chat_app()` in a multi-user Shiny app context. #' @export -chat_app <- function(client, ...) { +chat_app <- function(client, ..., bookmark_store = "url") { check_ellmer_chat(client) - ui <- function(req) { - bslib::page_fillable( - chat_mod_ui("chat", client = client, height = "100%"), - shiny::actionButton( - "close_btn", - label = "", - class = "btn-close", - style = "position: fixed; top: 6px; right: 6px;" - ) + ui <- bslib::page_fillable( + chat_mod_ui("chat", height = "100%"), + shiny::actionButton( + "close_btn", + label = "", + class = "btn-close", + style = "position: fixed; top: 6px; right: 6px;" ) - } + ) server <- function(input, output, session) { chat_mod_server("chat", client) diff --git a/pkg-r/man/chat_app.Rd b/pkg-r/man/chat_app.Rd index 532fee9e..1be63ad0 100644 --- a/pkg-r/man/chat_app.Rd +++ b/pkg-r/man/chat_app.Rd @@ -6,7 +6,7 @@ \alias{chat_mod_server} \title{Open a live chat application in the browser} \usage{ -chat_app(client, ...) +chat_app(client, ..., bookmark_store = "url") chat_mod_ui(id, ..., client = lifecycle::deprecated(), messages = NULL) @@ -25,6 +25,12 @@ chat_mod_server( \code{\link[shiny:shinyApp]{shiny::shinyApp()}}. In \code{chat_mod_ui()}, additional arguments are passed to \code{\link[=chat_ui]{chat_ui()}}.} +\item{bookmark_store}{The bookmarking store to use for the app. Passed to +\code{enable_bookmarking} in \code{\link[shiny:shinyApp]{shiny::shinyApp()}}. Defaults to \code{"url"}, which +uses the URL to store the chat state. URL-based bookmarking is limited in +size; use \code{"server"} to store the state on the server side without size +limitations; or disable bookmarking by setting this to \code{"disable"}.} + \item{id}{The chat module ID.} \item{messages}{Initial messages shown in the chat, used when \code{client} is not From 3bf225e46f53049db981c16f6f50e53b9fef7413 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 30 Jul 2025 16:35:10 -0400 Subject: [PATCH 09/14] fix: namespace observe() --- pkg-r/R/chat_restore.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg-r/R/chat_restore.R b/pkg-r/R/chat_restore.R index 245429c7..de3b5ca7 100644 --- a/pkg-r/R/chat_restore.R +++ b/pkg-r/R/chat_restore.R @@ -104,7 +104,7 @@ chat_restore <- function( state$values[[id]] <- client_state }) - cancel_set_ui <- observe({ + cancel_set_ui <- shiny::observe({ client_set_ui(client, id = id) cancel_set_ui$destroy() }) From 470ab52fc94ed80bdd182c4678ff34ec8c2d3ce3 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 30 Jul 2025 16:35:59 -0400 Subject: [PATCH 10/14] docs(chat_mod_server): Document bookmark_on_* params --- pkg-r/R/chat_app.R | 1 + pkg-r/man/chat_app.Rd | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/pkg-r/R/chat_app.R b/pkg-r/R/chat_app.R index 79a6c15d..e40f6ea5 100644 --- a/pkg-r/R/chat_app.R +++ b/pkg-r/R/chat_app.R @@ -128,6 +128,7 @@ chat_mod_ui <- function( } #' @describeIn chat_app A simple chat app module server. +#' @inheritParams chat_restore #' @export chat_mod_server <- function( id, diff --git a/pkg-r/man/chat_app.Rd b/pkg-r/man/chat_app.Rd index 1be63ad0..e5c93d4c 100644 --- a/pkg-r/man/chat_app.Rd +++ b/pkg-r/man/chat_app.Rd @@ -36,6 +36,10 @@ limitations; or disable bookmarking by setting this to \code{"disable"}.} \item{messages}{Initial messages shown in the chat, used when \code{client} is not provided or when the chat \code{client} doesn't already contain turns. Passed to \code{messages} in \code{\link[=chat_ui]{chat_ui()}}.} + +\item{bookmark_on_input}{A logical value determines if the bookmark should be updated when the user submits a message. Default is \code{TRUE}.} + +\item{bookmark_on_response}{A logical value determines if the bookmark should be updated when the response stream completes. Default is \code{TRUE}.} } \value{ \itemize{ From 25cb52b3f2a86efb8a2489d14a5c5cc47e416990 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 30 Jul 2025 16:46:26 -0400 Subject: [PATCH 11/14] chore: fully commit to deprecation --- pkg-r/DESCRIPTION | 1 + pkg-r/NAMESPACE | 1 + pkg-r/R/chat_app.R | 14 ++++++++-- pkg-r/R/shinychat-package.R | 5 ++-- pkg-r/man/chat_app.Rd | 6 ++-- pkg-r/man/figures/lifecycle-deprecated.svg | 21 ++++++++++++++ pkg-r/man/figures/lifecycle-experimental.svg | 21 ++++++++++++++ pkg-r/man/figures/lifecycle-stable.svg | 29 ++++++++++++++++++++ pkg-r/man/figures/lifecycle-superseded.svg | 21 ++++++++++++++ 9 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 pkg-r/man/figures/lifecycle-deprecated.svg create mode 100644 pkg-r/man/figures/lifecycle-experimental.svg create mode 100644 pkg-r/man/figures/lifecycle-stable.svg create mode 100644 pkg-r/man/figures/lifecycle-superseded.svg diff --git a/pkg-r/DESCRIPTION b/pkg-r/DESCRIPTION index 85ae1c7e..0cc0e5a3 100644 --- a/pkg-r/DESCRIPTION +++ b/pkg-r/DESCRIPTION @@ -27,6 +27,7 @@ Imports: fastmap, htmltools, jsonlite, + lifecycle, promises (>= 1.3.2), rlang, S7, diff --git a/pkg-r/NAMESPACE b/pkg-r/NAMESPACE index 60e95dfc..b707be29 100644 --- a/pkg-r/NAMESPACE +++ b/pkg-r/NAMESPACE @@ -18,4 +18,5 @@ importFrom(coro,async) importFrom(htmltools,HTML) importFrom(htmltools,css) importFrom(htmltools,tag) +importFrom(lifecycle,deprecated) importFrom(shiny,getDefaultReactiveDomain) diff --git a/pkg-r/R/chat_app.R b/pkg-r/R/chat_app.R index e40f6ea5..971cec70 100644 --- a/pkg-r/R/chat_app.R +++ b/pkg-r/R/chat_app.R @@ -58,7 +58,9 @@ #' } #' #' @param client A chat object created by \pkg{ellmer}, e.g. -#' [ellmer::chat_openai()] and friends. +#' [ellmer::chat_openai()] and friends. This argument is deprecated in +#' `chat_mod_ui()` because the client state is now managed by +#' `chat_mod_server()`. #' @param ... In `chat_app()`, additional arguments are passed to #' [shiny::shinyApp()]. In `chat_mod_ui()`, additional arguments are passed to #' [chat_ui()]. @@ -117,9 +119,17 @@ check_ellmer_chat <- function(client) { chat_mod_ui <- function( id, ..., - client = lifecycle::deprecated(), + client = deprecated(), messages = NULL ) { + if (lifecycle::is_present(client)) { + lifecycle::deprecate_warn( + "0.3.0", + "chat_mod_ui(client = )", + "chat_mod_server(client = )" + ) + } + chat_ui( shiny::NS(id, "chat"), messages = messages, diff --git a/pkg-r/R/shinychat-package.R b/pkg-r/R/shinychat-package.R index 554033cf..617fd5dd 100644 --- a/pkg-r/R/shinychat-package.R +++ b/pkg-r/R/shinychat-package.R @@ -2,10 +2,11 @@ "_PACKAGE" ## usethis namespace: start -#' @importFrom coro async -#' @importFrom htmltools tag css HTML #' @import rlang #' @import S7 +#' @importFrom coro async +#' @importFrom htmltools tag css HTML +#' @importFrom lifecycle deprecated ## usethis namespace: end NULL diff --git a/pkg-r/man/chat_app.Rd b/pkg-r/man/chat_app.Rd index e5c93d4c..37f9652a 100644 --- a/pkg-r/man/chat_app.Rd +++ b/pkg-r/man/chat_app.Rd @@ -8,7 +8,7 @@ \usage{ chat_app(client, ..., bookmark_store = "url") -chat_mod_ui(id, ..., client = lifecycle::deprecated(), messages = NULL) +chat_mod_ui(id, ..., client = deprecated(), messages = NULL) chat_mod_server( id, @@ -19,7 +19,9 @@ chat_mod_server( } \arguments{ \item{client}{A chat object created by \pkg{ellmer}, e.g. -\code{\link[ellmer:chat_openai]{ellmer::chat_openai()}} and friends.} +\code{\link[ellmer:chat_openai]{ellmer::chat_openai()}} and friends. This argument is deprecated in +\code{chat_mod_ui()} because the client state is now managed by +\code{chat_mod_server()}.} \item{...}{In \code{chat_app()}, additional arguments are passed to \code{\link[shiny:shinyApp]{shiny::shinyApp()}}. In \code{chat_mod_ui()}, additional arguments are passed to diff --git a/pkg-r/man/figures/lifecycle-deprecated.svg b/pkg-r/man/figures/lifecycle-deprecated.svg new file mode 100644 index 00000000..b61c57c3 --- /dev/null +++ b/pkg-r/man/figures/lifecycle-deprecated.svg @@ -0,0 +1,21 @@ + + lifecycle: deprecated + + + + + + + + + + + + + + + lifecycle + + deprecated + + diff --git a/pkg-r/man/figures/lifecycle-experimental.svg b/pkg-r/man/figures/lifecycle-experimental.svg new file mode 100644 index 00000000..5d88fc2c --- /dev/null +++ b/pkg-r/man/figures/lifecycle-experimental.svg @@ -0,0 +1,21 @@ + + lifecycle: experimental + + + + + + + + + + + + + + + lifecycle + + experimental + + diff --git a/pkg-r/man/figures/lifecycle-stable.svg b/pkg-r/man/figures/lifecycle-stable.svg new file mode 100644 index 00000000..9bf21e76 --- /dev/null +++ b/pkg-r/man/figures/lifecycle-stable.svg @@ -0,0 +1,29 @@ + + lifecycle: stable + + + + + + + + + + + + + + + + lifecycle + + + + stable + + + diff --git a/pkg-r/man/figures/lifecycle-superseded.svg b/pkg-r/man/figures/lifecycle-superseded.svg new file mode 100644 index 00000000..db8d757f --- /dev/null +++ b/pkg-r/man/figures/lifecycle-superseded.svg @@ -0,0 +1,21 @@ + + lifecycle: superseded + + + + + + + + + + + + + + + lifecycle + + superseded + + From 05507bd4bf8da8f18f2da87c703d4611bf2c4177 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 30 Jul 2025 19:35:11 -0400 Subject: [PATCH 12/14] fix(chat_app): UI must be a function of req --- pkg-r/R/chat_app.R | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/pkg-r/R/chat_app.R b/pkg-r/R/chat_app.R index 971cec70..dba8216f 100644 --- a/pkg-r/R/chat_app.R +++ b/pkg-r/R/chat_app.R @@ -83,15 +83,17 @@ chat_app <- function(client, ..., bookmark_store = "url") { check_ellmer_chat(client) - ui <- bslib::page_fillable( - chat_mod_ui("chat", height = "100%"), - shiny::actionButton( - "close_btn", - label = "", - class = "btn-close", - style = "position: fixed; top: 6px; right: 6px;" + ui <- function(req) { + bslib::page_fillable( + chat_mod_ui("chat", height = "100%"), + shiny::actionButton( + "close_btn", + label = "", + class = "btn-close", + style = "position: fixed; top: 6px; right: 6px;" + ) ) - ) + } server <- function(input, output, session) { chat_mod_server("chat", client) From 19b9ea0ad80a1145adb11124e81b6a943a96c3aa Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 30 Jul 2025 19:35:40 -0400 Subject: [PATCH 13/14] fix(chat_app): Actually pass through `bookmark_store` --- pkg-r/R/chat_app.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg-r/R/chat_app.R b/pkg-r/R/chat_app.R index dba8216f..3e134b7a 100644 --- a/pkg-r/R/chat_app.R +++ b/pkg-r/R/chat_app.R @@ -103,7 +103,7 @@ chat_app <- function(client, ..., bookmark_store = "url") { }) } - shiny::shinyApp(ui, server, ..., enableBookmarking = "url") + shiny::shinyApp(ui, server, ..., enableBookmarking = bookmark_store) } check_ellmer_chat <- function(client) { From c713029f14a0d7686982cbda9f523efb236a00ab Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 30 Jul 2025 19:38:30 -0400 Subject: [PATCH 14/14] docs(chat_mod_ui): Clarify that `messages` only applies when there are no turns in the client --- pkg-r/R/chat_app.R | 6 +++--- pkg-r/R/chat_restore.R | 7 ++++++- pkg-r/man/chat_app.Rd | 6 +++--- pkg-r/man/chat_restore.Rd | 7 ++++++- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/pkg-r/R/chat_app.R b/pkg-r/R/chat_app.R index 3e134b7a..4d03f796 100644 --- a/pkg-r/R/chat_app.R +++ b/pkg-r/R/chat_app.R @@ -114,9 +114,9 @@ check_ellmer_chat <- function(client) { #' @describeIn chat_app A simple chat app module UI. #' @param id The chat module ID. -#' @param messages Initial messages shown in the chat, used when `client` is not -#' provided or when the chat `client` doesn't already contain turns. Passed to -#' `messages` in [chat_ui()]. +#' @param messages Initial messages shown in the chat, used only when `client` +#' (in `chat_mod_ui()`) doesn't already contain turns. Passed to `messages` +#' in [chat_ui()]. #' @export chat_mod_ui <- function( id, diff --git a/pkg-r/R/chat_restore.R b/pkg-r/R/chat_restore.R index de3b5ca7..8f9b77fb 100644 --- a/pkg-r/R/chat_restore.R +++ b/pkg-r/R/chat_restore.R @@ -1,7 +1,8 @@ #' Add Shiny bookmarking for shinychat #' #' @description -#' Adds Shiny bookmarking hooks to save and restore the \pkg{ellmer} chat `client`. +#' Adds Shiny bookmarking hooks to save and restore the \pkg{ellmer} chat +#' `client`. Also restores chat messages from the history in the `client`. #' #' If either `bookmark_on_input` or `bookmark_on_response` is `TRUE`, the Shiny #' App's bookmark will be automatically updated without showing a modal to the @@ -13,6 +14,10 @@ #' then you may need to implement your own `session$onRestore()` (and possibly #' `session$onBookmark`) handler to restore any additional state. #' +#' To avoid restoring chat history from the `client`, you can ensure that the +#' history is empty by calling `client$set_turns(list())` before passing the +#' client to `chat_restore()`. +#' #' @param id The ID of the chat element #' @param client The \pkg{ellmer} LLM chat client. #' @param ... Used for future parameter expansion. diff --git a/pkg-r/man/chat_app.Rd b/pkg-r/man/chat_app.Rd index 37f9652a..f1646cc5 100644 --- a/pkg-r/man/chat_app.Rd +++ b/pkg-r/man/chat_app.Rd @@ -35,9 +35,9 @@ limitations; or disable bookmarking by setting this to \code{"disable"}.} \item{id}{The chat module ID.} -\item{messages}{Initial messages shown in the chat, used when \code{client} is not -provided or when the chat \code{client} doesn't already contain turns. Passed to -\code{messages} in \code{\link[=chat_ui]{chat_ui()}}.} +\item{messages}{Initial messages shown in the chat, used only when \code{client} +(in \code{chat_mod_ui()}) doesn't already contain turns. Passed to \code{messages} +in \code{\link[=chat_ui]{chat_ui()}}.} \item{bookmark_on_input}{A logical value determines if the bookmark should be updated when the user submits a message. Default is \code{TRUE}.} diff --git a/pkg-r/man/chat_restore.Rd b/pkg-r/man/chat_restore.Rd index 556d7c89..cde25716 100644 --- a/pkg-r/man/chat_restore.Rd +++ b/pkg-r/man/chat_restore.Rd @@ -30,7 +30,8 @@ chat_restore( Returns nothing (\code{invisible(NULL)}). } \description{ -Adds Shiny bookmarking hooks to save and restore the \pkg{ellmer} chat \code{client}. +Adds Shiny bookmarking hooks to save and restore the \pkg{ellmer} chat +\code{client}. Also restores chat messages from the history in the \code{client}. If either \code{bookmark_on_input} or \code{bookmark_on_response} is \code{TRUE}, the Shiny App's bookmark will be automatically updated without showing a modal to the @@ -41,6 +42,10 @@ the \code{client}'s state doesn't properly capture the chat's UI (i.e., a transformation is applied in-between receiving and displaying the message), then you may need to implement your own \code{session$onRestore()} (and possibly \code{session$onBookmark}) handler to restore any additional state. + +To avoid restoring chat history from the \code{client}, you can ensure that the +history is empty by calling \code{client$set_turns(list())} before passing the +client to \code{chat_restore()}. } \examples{ \dontshow{if (interactive()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf}