Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pkg-r/NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# querychat (development version)

* 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)

* 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)
* 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)

Expand Down
17 changes: 13 additions & 4 deletions pkg-r/R/QueryChat.R
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,9 @@ QueryChat <- R6::R6Class(
}

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

output$query_title <- shiny::renderText({
if (shiny::isTruthy(qc_vals$title())) {
Expand Down Expand Up @@ -432,6 +434,12 @@ QueryChat <- R6::R6Class(
#' the reactive logic for the chat interface and returns session-specific
#' reactive values.
#'
#' @param enable_bookmarking Whether to enable bookmarking for the chat
#' state. Default is `FALSE`. When enabled, the chat state (including
#' current query, title, and chat history) will be saved and restored
#' with Shiny bookmarks. This requires that the Shiny app has bookmarking
#' enabled via `shiny::enableBookmarking()` or the `enableBookmarking`
#' parameter of `shiny::shinyApp()`.
#' @param session The Shiny session object.
#'
#' @return A list containing session-specific reactive values and the chat
Expand All @@ -446,14 +454,14 @@ QueryChat <- R6::R6Class(
#' qc <- QueryChat$new(mtcars, "mtcars")
#'
#' server <- function(input, output, session) {
#' qc_vals <- qc$server()
#' qc_vals <- qc$server(enable_bookmarking = TRUE)
#'
#' output$data <- renderDataTable(qc_vals$df())
#' output$query <- renderText(qc_vals$sql())
#' output$title <- renderText(qc_vals$title() %||% "No Query")
#' }
#' }
server = function(session = shiny::getDefaultReactiveDomain()) {
server = function(enable_bookmarking = FALSE, session = shiny::getDefaultReactiveDomain()) {
if (is.null(session)) {
rlang::abort(
"$server() must be called within a Shiny server function."
Expand All @@ -464,7 +472,8 @@ QueryChat <- R6::R6Class(
self$id,
data_source = private$.data_source,
greeting = self$greeting,
client = private$.client
client = private$.client,
enable_bookmarking = enable_bookmarking
)
},

Expand Down
57 changes: 43 additions & 14 deletions pkg-r/R/querychat_module.R
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ mod_ui <- function(id, ...) {
}

# Main module server function
mod_server <- function(id, data_source, greeting, client) {
mod_server <- function(id, data_source, greeting, client, enable_bookmarking = FALSE) {
shiny::moduleServer(id, function(input, output, session) {
current_title <- shiny::reactiveVal(NULL, label = "current_title")
current_query <- shiny::reactiveVal("", label = "current_query")
has_greeted <- shiny::reactiveVal(FALSE, label = "has_greeted")
filtered_df <- shiny::reactive(label = "filtered_df", {
execute_query(data_source, query = DBI::SQL(current_query()))
})
Expand Down Expand Up @@ -56,20 +57,26 @@ mod_server <- function(id, data_source, greeting, client) {
# Prepopulate the chat UI with a welcome message that appears to be from the
# chat model (but is actually hard-coded). This is just for the user, not for
# the chat model to see.
greeting_content <- if (!is.null(greeting) && any(nzchar(greeting))) {
greeting
} else {
# Generate greeting on the fly if none provided
rlang::warn(c(
"No greeting provided; generating one now. This adds latency and cost.",
"i" = "Consider using $generate_greeting() to create a reusable greeting."
))
chat_temp <- client$clone()
prompt <- "Please give me a friendly greeting. Include a few sample prompts in a two-level bulleted list."
chat_temp$stream_async(prompt)
}
shiny::observe(label = "greet_on_startup", {
if (has_greeted()) {
return()
}

shinychat::chat_append("chat", greeting_content)
greeting_content <- if (!is.null(greeting) && any(nzchar(greeting))) {
greeting
} else {
# Generate greeting on the fly if none provided
rlang::warn(c(
"No greeting provided; generating one now. This adds latency and cost.",
"i" = "Consider using $generate_greeting() to create a reusable greeting."
))
prompt <- "Please give me a friendly greeting. Include a few sample prompts in a two-level bulleted list."
chat$stream_async(prompt)
}

shinychat::chat_append("chat", greeting_content)
has_greeted(TRUE)
})

append_stream_task <- shiny::ExtendedTask$new(
function(client, user_input) {
Expand All @@ -94,6 +101,28 @@ mod_server <- function(id, data_source, greeting, client) {
current_title(input$chat_update$title)
})

if (enable_bookmarking) {
shinychat::chat_restore("chat", chat, session = session)

shiny::onBookmark(function(state) {
state$values$querychat_sql <- current_query()
state$values$querychat_title <- current_title()
state$values$querychat_has_greeted <- has_greeted()
})

shiny::onRestore(function(state) {
if (!is.null(state$values$querychat_sql)) {
current_query(state$values$querychat_sql)
}
if (!is.null(state$values$querychat_title)) {
current_title(state$values$querychat_title)
}
if (!is.null(state$values$querychat_has_greeted)) {
has_greeted(state$values$querychat_has_greeted)
}
})
}

list(
client = chat,
sql = current_query,
Expand Down