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$server()
    output$data <- renderDT(qc$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)
  • Reactive Getters: $df(), $sql(), $title() provide reactive access to current state
  • 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 create_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!

#' @export
querychat_data_source <- function(x, ...) {
UseMethod("querychat_data_source")
create_data_source <- function(x, table_name = NULL, ...) {
Copy link
Contributor

Choose a reason for hiding this comment

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

If we're renaming this function, I'd rather as_querychat_data_source(). It's verbose, but developer facing, and reflects that it converts from one data source type to another.

If we're going to revamp this flow when we re-evaluate data sources, I'd prefer that we don't rename this function for now

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea, I like that, and I also think we should revamp the plugin interface. Since I'm not ready to sign up for that right now, I'd rather we make a small improvement here than nothing at all.

a13172d

Comment on lines -18 to -19
# The connection will automatically be closed when the app stops, thanks to
# querychat_init
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you comment on the behavior changes you made in this area?

Especially with the new R6 class, I don't think we should be automatically closing this connection -- or at least we definitely shouldn't be doing that when you call the $app() method.

We should probably create an example where the querychat object lives entirely within the session; i.e. we connect to the database in each session and use renderUI() so that it's entirely session-based. We can also show, in that example, how to clean up the connection.

Otherwise, the more common pattern will be to create the querychat instance in the global environment and use it in both the UI and server. In that use case, it doesn't make sense to clean up the data source except at the session end; it could make sense at app end, but that's something you'd probably want to opt into.

Relatedly, it might be good to have an example that uses pool, or at least to verify that pool would work with querychat.

Copy link
Contributor

Choose a reason for hiding this comment

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

Note: I eventually hit the auto_close code in QueryChat$new() and left a comment there.

Copy link
Contributor Author

@cpsievert cpsievert Nov 25, 2025

Choose a reason for hiding this comment

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

Oh, this is a great point, thanks. I hadn't thought through this carefully -- I was just porting what was already going on in querychat_init(). See #109 (comment) for some thoughts on this.

@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.

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