diff --git a/pkg-r/DESCRIPTION b/pkg-r/DESCRIPTION index e71eb5f2..9d87b99e 100644 --- a/pkg-r/DESCRIPTION +++ b/pkg-r/DESCRIPTION @@ -30,6 +30,7 @@ Imports: whisker, xtable Suggests: + bsicons, DT, R6, RSQLite, diff --git a/pkg-r/NAMESPACE b/pkg-r/NAMESPACE index 8fce3055..cbc584f3 100644 --- a/pkg-r/NAMESPACE +++ b/pkg-r/NAMESPACE @@ -14,6 +14,7 @@ export(create_system_prompt) export(execute_query) export(get_db_type) export(get_schema) +export(querychat_app) export(querychat_data_source) export(querychat_init) export(querychat_server) diff --git a/pkg-r/NEWS.md b/pkg-r/NEWS.md index ee4670d6..df0d6fa7 100644 --- a/pkg-r/NEWS.md +++ b/pkg-r/NEWS.md @@ -21,3 +21,5 @@ * `querychat_server()` now uses a `shiny::ExtendedTask` for streaming the chat response, which allows the dashboard to update and remain responsive while the chat response is streaming in. (#63) * querychat now requires `ellmer` version 0.3.0 or later and uses rich tool cards for dashboard updates and database queries. (#65) + +* New `querychat_app()` function lets you quickly launch a Shiny app with a querychat chat interface. (#66) diff --git a/pkg-r/R/querychat.R b/pkg-r/R/querychat.R index 2f6c54bd..0bad8683 100644 --- a/pkg-r/R/querychat.R +++ b/pkg-r/R/querychat.R @@ -257,7 +257,11 @@ querychat_server <- function(id, querychat_config) { chat = chat, sql = shiny::reactive(current_query()), title = shiny::reactive(current_title()), - df = filtered_df + df = filtered_df, + update_query = function(query, title = NULL) { + current_query(query) + current_title(title) + } ) }) } diff --git a/pkg-r/R/querychat_app.R b/pkg-r/R/querychat_app.R new file mode 100644 index 00000000..92201fd4 --- /dev/null +++ b/pkg-r/R/querychat_app.R @@ -0,0 +1,136 @@ +#' A Simple App for Chatting with Data +#' +#' Creates a Shiny app that allows users to interact with a data source using +#' natural language queries. The app uses a pre-configured Shiny app built on +#' [querychat_sidebar()] and [querychat_server()] to provide a quick-and-easy +#' way to chat with your data. +#' +#' @examplesIf rlang::is_interactive() +#' # Pass in a data frame to start querychatting +#' querychat_app(mtcars) +#' +#' # Or choose your LLM client using ellmer::chat_*() functions +#' querychat_app(mtcars, client = ellmer::chat_anthropic()) +#' +#' @param config A `querychat_config` object or a data source that can be used +#' to create one. +#' @param ... Additional arguments passed to [querychat_init()] if `config` is +#' not already a `querychat_config` object. +#' @inheritParams shinychat::chat_app +#' +#' @return Invisibly returns the `chat` object containing the chat history. +#' +#' @export +querychat_app <- function(config, ..., bookmark_store = "url") { + rlang::check_installed("DT") + rlang::check_installed("bsicons") + + if (!inherits(config, "querychat_config")) { + if (inherits(config, "querychat_data_source")) { + data_source <- config + } else { + data_source <- querychat_data_source( + config, + table_name = deparse(substitute(config)) + ) + } + config <- querychat_init(data_source, ...) + } + + ui <- function(req) { + bslib::page_sidebar( + title = shiny::HTML(paste0( + "querychat with ", + config$data_source$table_name, + "" + )), + sidebar = querychat_sidebar("chat"), + bslib::card( + fill = FALSE, + style = bslib::css(max_height = "33%"), + bslib::card_header( + shiny::div( + class = "hstack", + shiny::div( + bsicons::bs_icon("terminal-fill"), + shiny::textOutput("query_title", inline = TRUE) + ), + shiny::div( + class = "ms-auto", + shiny::uiOutput("ui_reset", inline = TRUE) + ) + ) + ), + shiny::uiOutput("sql_output") + ), + bslib::card( + bslib::card_header(bsicons::bs_icon("table"), "Data"), + DT::DTOutput("dt") + ), + shiny::actionButton( + "close_btn", + label = "", + class = "btn-close", + style = "position: fixed; top: 6px; right: 6px;" + ) + ) + } + + chat <- NULL + + server <- function(input, output, session) { + qc <- querychat_server("chat", config) + chat <<- qc$chat + + output$query_title <- shiny::renderText({ + if (shiny::isTruthy(qc$title())) { + qc$title() + } else { + "SQL Query" + } + }) + + output$ui_reset <- shiny::renderUI({ + shiny::req(qc$sql()) + + shiny::actionButton( + "reset_query", + label = "Reset Query", + class = "btn btn-outline-danger btn-sm lh-1" + ) + }) + + shiny::observeEvent(input$reset_query, { + qc$update_query("", NULL) + }) + + output$dt <- DT::renderDT({ + DT::datatable(qc$df()) + }) + + output$sql_output <- shiny::renderUI({ + sql <- if (shiny::isTruthy(qc$sql())) { + qc$sql() + } else { + paste("SELECT * FROM", config$data_source$table_name) + } + + sql_code <- paste(c("```sql", sql, "```"), collapse = "\n") + + shinychat::output_markdown_stream( + "sql_code", + content = sql_code, + auto_scroll = FALSE, + width = "100%" + ) + }) + + shiny::observeEvent(input$close_btn, { + shiny::stopApp() + }) + } + + app <- shiny::shinyApp(ui, server, ..., enableBookmarking = bookmark_store) + tryCatch(shiny::runGadget(app), interrupt = function(cnd) NULL) + invisible(chat) +} diff --git a/pkg-r/man/querychat_app.Rd b/pkg-r/man/querychat_app.Rd new file mode 100644 index 00000000..9ed93b87 --- /dev/null +++ b/pkg-r/man/querychat_app.Rd @@ -0,0 +1,39 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/querychat_app.R +\name{querychat_app} +\alias{querychat_app} +\title{A Simple App for Chatting with Data} +\usage{ +querychat_app(config, ..., bookmark_store = "url") +} +\arguments{ +\item{config}{A \code{querychat_config} object or a data source that can be used +to create one.} + +\item{...}{Additional arguments passed to \code{\link[=querychat_init]{querychat_init()}} if \code{config} is +not already a \code{querychat_config} object.} + +\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"}.} +} +\value{ +Invisibly returns the \code{chat} object containing the chat history. +} +\description{ +Creates a Shiny app that allows users to interact with a data source using +natural language queries. The app uses a pre-configured Shiny app built on +\code{\link[=querychat_sidebar]{querychat_sidebar()}} and \code{\link[=querychat_server]{querychat_server()}} to provide a quick-and-easy +way to chat with your data. +} +\examples{ +\dontshow{if (rlang::is_interactive()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +# Pass in a data frame to start querychatting +querychat_app(mtcars) + +# Or choose your LLM client using ellmer::chat_*() functions +querychat_app(mtcars, client = ellmer::chat_anthropic()) +\dontshow{\}) # examplesIf} +} diff --git a/pkg-r/pkgdown/_pkgdown.yml b/pkg-r/pkgdown/_pkgdown.yml index 5cef05a7..f94529c9 100644 --- a/pkg-r/pkgdown/_pkgdown.yml +++ b/pkg-r/pkgdown/_pkgdown.yml @@ -37,3 +37,4 @@ reference: - querychat_sidebar - querychat_system_prompt - querychat_ui + - querychat_app