Skip to content

Commit 7e47ebf

Browse files
authored
feat(pkg-r): bookmark support (#131)
* feat(pkg-r): add bookmarking support * `air format` (GitHub Actions) * `devtools::document()` (GitHub Actions) --------- Co-authored-by: cpsievert <[email protected]>
1 parent fea52e4 commit 7e47ebf

File tree

4 files changed

+80
-21
lines changed

4 files changed

+80
-21
lines changed

pkg-r/NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# querychat (development version)
22

3+
* Added bookmarking support to `QueryChat$server()` and `querychat_app()`. When bookmarking is enabled (via `bookmark_store = "url"` or `"server"` in `querychat_app()` or `$app_obj()`, or via `enable_bookmarking = TRUE` in `$server()`), the chat state (including current query, title, and chat history) will be saved and restored with Shiny bookmarks. (#107)
4+
35
* Nearly the entire functional API (i.e., `querychat_init()`, `querychat_sidebar()`, `querychat_server()`, etc) has been hard deprecated in favor of a simpler OOP-based API. Namely, the new `QueryChat$new()` class is now the main entry point (instead of `querychat_init()`) and has methods to replace old functions (e.g., `$sidebar()`, `$server()`, etc). (#109)
46
* In addition, `querychat_data_source()` was renamed to `as_querychat_data_source()`, and remains exported for a developer extension point, but users no longer have to explicitly create a data source. (#109)
57

pkg-r/R/QueryChat.R

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,9 @@ QueryChat <- R6::R6Class(
309309
}
310310

311311
server <- function(input, output, session) {
312-
qc_vals <- self$server()
312+
# Enable bookmarking if bookmark_store is enabled
313+
enable_bookmarking <- bookmark_store %in% c("url", "server")
314+
qc_vals <- self$server(enable_bookmarking = enable_bookmarking)
313315

314316
output$query_title <- shiny::renderText({
315317
if (shiny::isTruthy(qc_vals$title())) {
@@ -432,6 +434,12 @@ QueryChat <- R6::R6Class(
432434
#' the reactive logic for the chat interface and returns session-specific
433435
#' reactive values.
434436
#'
437+
#' @param enable_bookmarking Whether to enable bookmarking for the chat
438+
#' state. Default is `FALSE`. When enabled, the chat state (including
439+
#' current query, title, and chat history) will be saved and restored
440+
#' with Shiny bookmarks. This requires that the Shiny app has bookmarking
441+
#' enabled via `shiny::enableBookmarking()` or the `enableBookmarking`
442+
#' parameter of `shiny::shinyApp()`.
435443
#' @param session The Shiny session object.
436444
#'
437445
#' @return A list containing session-specific reactive values and the chat
@@ -446,14 +454,17 @@ QueryChat <- R6::R6Class(
446454
#' qc <- QueryChat$new(mtcars, "mtcars")
447455
#'
448456
#' server <- function(input, output, session) {
449-
#' qc_vals <- qc$server()
457+
#' qc_vals <- qc$server(enable_bookmarking = TRUE)
450458
#'
451459
#' output$data <- renderDataTable(qc_vals$df())
452460
#' output$query <- renderText(qc_vals$sql())
453461
#' output$title <- renderText(qc_vals$title() %||% "No Query")
454462
#' }
455463
#' }
456-
server = function(session = shiny::getDefaultReactiveDomain()) {
464+
server = function(
465+
enable_bookmarking = FALSE,
466+
session = shiny::getDefaultReactiveDomain()
467+
) {
457468
if (is.null(session)) {
458469
rlang::abort(
459470
"$server() must be called within a Shiny server function."
@@ -464,7 +475,8 @@ QueryChat <- R6::R6Class(
464475
self$id,
465476
data_source = private$.data_source,
466477
greeting = self$greeting,
467-
client = private$.client
478+
client = private$.client,
479+
enable_bookmarking = enable_bookmarking
468480
)
469481
},
470482

pkg-r/R/querychat_module.R

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,17 @@ mod_ui <- function(id, ...) {
2020
}
2121

2222
# Main module server function
23-
mod_server <- function(id, data_source, greeting, client) {
23+
mod_server <- function(
24+
id,
25+
data_source,
26+
greeting,
27+
client,
28+
enable_bookmarking = FALSE
29+
) {
2430
shiny::moduleServer(id, function(input, output, session) {
2531
current_title <- shiny::reactiveVal(NULL, label = "current_title")
2632
current_query <- shiny::reactiveVal("", label = "current_query")
33+
has_greeted <- shiny::reactiveVal(FALSE, label = "has_greeted")
2734
filtered_df <- shiny::reactive(label = "filtered_df", {
2835
execute_query(data_source, query = DBI::SQL(current_query()))
2936
})
@@ -56,20 +63,26 @@ mod_server <- function(id, data_source, greeting, client) {
5663
# Prepopulate the chat UI with a welcome message that appears to be from the
5764
# chat model (but is actually hard-coded). This is just for the user, not for
5865
# the chat model to see.
59-
greeting_content <- if (!is.null(greeting) && any(nzchar(greeting))) {
60-
greeting
61-
} else {
62-
# Generate greeting on the fly if none provided
63-
rlang::warn(c(
64-
"No greeting provided; generating one now. This adds latency and cost.",
65-
"i" = "Consider using $generate_greeting() to create a reusable greeting."
66-
))
67-
chat_temp <- client$clone()
68-
prompt <- "Please give me a friendly greeting. Include a few sample prompts in a two-level bulleted list."
69-
chat_temp$stream_async(prompt)
70-
}
66+
shiny::observe(label = "greet_on_startup", {
67+
if (has_greeted()) {
68+
return()
69+
}
7170

72-
shinychat::chat_append("chat", greeting_content)
71+
greeting_content <- if (!is.null(greeting) && any(nzchar(greeting))) {
72+
greeting
73+
} else {
74+
# Generate greeting on the fly if none provided
75+
rlang::warn(c(
76+
"No greeting provided; generating one now. This adds latency and cost.",
77+
"i" = "Consider using $generate_greeting() to create a reusable greeting."
78+
))
79+
prompt <- "Please give me a friendly greeting. Include a few sample prompts in a two-level bulleted list."
80+
chat$stream_async(prompt)
81+
}
82+
83+
shinychat::chat_append("chat", greeting_content)
84+
has_greeted(TRUE)
85+
})
7386

7487
append_stream_task <- shiny::ExtendedTask$new(
7588
function(client, user_input) {
@@ -94,6 +107,28 @@ mod_server <- function(id, data_source, greeting, client) {
94107
current_title(input$chat_update$title)
95108
})
96109

110+
if (enable_bookmarking) {
111+
shinychat::chat_restore("chat", chat, session = session)
112+
113+
shiny::onBookmark(function(state) {
114+
state$values$querychat_sql <- current_query()
115+
state$values$querychat_title <- current_title()
116+
state$values$querychat_has_greeted <- has_greeted()
117+
})
118+
119+
shiny::onRestore(function(state) {
120+
if (!is.null(state$values$querychat_sql)) {
121+
current_query(state$values$querychat_sql)
122+
}
123+
if (!is.null(state$values$querychat_title)) {
124+
current_title(state$values$querychat_title)
125+
}
126+
if (!is.null(state$values$querychat_has_greeted)) {
127+
has_greeted(state$values$querychat_has_greeted)
128+
}
129+
})
130+
}
131+
97132
list(
98133
client = chat,
99134
sql = current_query,

pkg-r/man/QueryChat.Rd

Lines changed: 13 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)