Skip to content

Conversation

@cpsievert
Copy link
Contributor

@cpsievert cpsievert commented Nov 24, 2025

This PR introduces a major API redesign for the R package, replacing the functional API with a simpler R6 class-based approach. These changes are in part motivated by the recent changes made to the Python package (#101 and #108), where the class-based approach is much more idiomatic to Python and Shiny Express.

Summary

The previous API required users to juggle multiple function calls (querychat_init(), querychat_sidebar(), querychat_server()), explicitly create data sources with querychat_data_source(), and route output/input values into the proper locations. The new QueryChat R6 class consolidates all this functionality into a single object.

API Comparison

Before:

  # Create data source
  mtcars_source <- querychat_data_source(mtcars)

  # Configure querychat
  config <- querychat_init(mtcars_source, greeting = "Hello!")

  # Build UI
  ui <- page_sidebar(
    sidebar = querychat_sidebar("chat"),
    DT::DTOutput("data")
  )

  # Server logic
  server <- function(input, output, session) {
    chat <- querychat_server("chat", config)
    output$data <- renderDT(chat$df())
  }

After:

  # Create QueryChat instance
  qc <- QueryChat$new(mtcars, "mtcars", greeting = "Hello!")

  # Build UI
  ui <- page_sidebar(
    sidebar = qc$sidebar(),
    DT::DTOutput("data")
  )
  
    # Server logic
  server <- function(input, output, session) {
    qc_valls <- qc$server()
    output$data <- renderDT(qc_vals$df())
  }

Key Changes

New QueryChat R6 Class

  • Constructor: QueryChat$new(data_source, table_name, ...) accepts data frames or database connections directly
  • UI Methods: $sidebar(), $ui() create chat interface components
  • Server Method: $server() initializes server logic (must be called within Shiny server function)
  • Complete App: $app() generates a ready-to-run Shiny app with sensible defaults
  • Greeting Generation: $generate_greeting() creates reusable greeting messages
  • Cleanup: $cleanup() closes data source resources

Hard Deprecations

All previous functional API functions now throw errors with helpful migration messages:

  • querychat_init()QueryChat$new()
  • querychat_app()QueryChat$app()
  • querychat_sidebar()QueryChat$sidebar()
  • querychat_ui()QueryChat$ui()
  • querychat_server()QueryChat$server()
  • querychat_greeting()QueryChat$generate_greeting()
  • querychat_data_source()QueryChat$new() (data sources now created internally)

Internal Refactoring

  • Renamed querychat_data_source() to as_querychat_data_source() - now exported as an internal developer extension point only
  • Removed categorical_threshold from data source creation (moved to QueryChat$new())
  • Updated all examples and documentation
  • Added new example apps: 01-hello-app/ (minimal usage) and 02-sidebar-app/ (custom UI)

Migration Guide

Users upgrading from the old API will receive clear error messages with side-by-side code comparisons showing exactly how to migrate. The new API is more intuitive and requires less code in most cases.

Documentation Updates

  • Complete rewrite of README with new API examples
  • Updated all function documentation with proper cross-references
  • Added comprehensive examples for QueryChat class
  • Updated pkgdown configuration

@cpsievert cpsievert changed the title feat!(pkg-r): new QueryChat() API feat!(pkg-r): new QueryChat API Nov 24, 2025
@cpsievert cpsievert requested a review from Copilot November 24, 2025 22:36

This comment was marked as resolved.

@cpsievert cpsievert marked this pull request as ready for review November 24, 2025 23:22
@cpsievert cpsievert requested a review from gadenbuie November 24, 2025 23:22
@cpsievert
Copy link
Contributor Author

cpsievert commented Nov 24, 2025

@gadenbuie ideally we'd also address the data source plugin API (which is a bit of a mess), but I'm gonna punt on that for now #110

Copy link
Contributor

@gadenbuie gadenbuie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really nice, going to be a great overall improvement to the package!

@cpsievert
Copy link
Contributor Author

Thanks for the detailed feedback @gadenbuie. Just FYI, I'm likely going to merge this by EOD Wed to unblock Veerle.

@cpsievert
Copy link
Contributor Author

cpsievert commented Nov 25, 2025

Also, one thing I realized while addressing feedback is that it's not currently possible to access the app object underlying $app(), which is useful for deployment. 54cc574 adds a $app_obj() method for this.

Comment on lines 249 to 251
app <- self$app_obj(..., bookmark_store = bookmark_store)
tryCatch(shiny::runGadget(app), interrupt = function(cnd) NULL)
invisible(private$server_values$chat)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I missed this in the first review. The advantage of calling runGadget() is so that we can return the chat object. Is there another way to expose the session-specific chat clients?

Thinking about that, will our current approach work in apps with simultaneous users?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm conflicted on this; I personally find it helpful that shinychat::chat_app() (and ellmer::live_browser() by extension) returns the session chat client, but I'm not sure how many other people use this feature and it'd be a lot easier to just return the app object here.

Copy link
Contributor Author

@cpsievert cpsievert Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Man I feel silly for not catching this earlier. I think we're gonna have to go back to $server() returning a list of reactives

Copy link
Contributor Author

@cpsievert cpsievert Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See a5c13ba which does the R equivalent of #116

Copy link
Contributor Author

@cpsievert cpsievert Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, if stopApp() is passed a value, runGadget() will return it. I also think it's pretty neat that you can get at the most-recent query, so now I'm returning all the session-specific values.

This comment was marked as resolved.

@cpsievert cpsievert merged commit 2a521cb into main Nov 26, 2025
16 checks passed
@cpsievert cpsievert deleted the feat/r-new-api branch November 26, 2025 23:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants