26  Shiny Assistant

Published

2025-03-15

Caution

The contents for this section are being reviewed. Thank you for your patience.

The Shiny Assistant is a ‘knowledgeable colleague who is always ready to help out with your Shiny projects, in both R and in Python.1

  • The user interface includes a chat dialogue box with an IDE (with a Console, Source Files, and Viewer).

  • Shiny Assistant can be set to return Code only, or a Concise or Verbose description of it’s response to prompts.

  • The assitant can be used to build an application, refactor code into modules or functions, and write roxygen2 documentation.

  • Applications can be downloaded from the UI or shared by clicking on the Share icon and then choosing to share the Editor or Application URL

The Shiny Assistant is an AI-powered chat bot anyone can use to help them build a Shiny application.

you can ask it questions about Shiny, or to create a Shiny application from scratch, or ask it to make changes to an existing application.” - Shiny Assistant, Winston Chang

Shiny Assist UI

Shiny Assist UI

26.1 Chat + IDE

Launch app with the shinypak package:

launch('23_llm-shiny-assist')

The UI for Shiny Assistant looks like a standard chat, but if you ask it to “Open the editor”, we see an interface with a script editor, console, and viewer pane:

Shiny Assist Editor

Shiny Assist Editor

I’ve created the shinypak R package In an effort to make each section accessible and easy to follow:

Install shinypak using pak (or remotes):

# install.packages('pak')
pak::pak('mjfrigaard/shinypak')

Review the chapters in each section:

library(shinypak)
list_apps(regex = '23')
## # A tibble: 1 × 2
##   branch   last_updated       
##   <chr>    <dttm>             
## 1 23_golem 2025-03-25 07:41:17

Launch an app:

launch(app = "23_llm-shiny-assist")

Shiny Assistant is great for trying different UI components, layouts, and styling (color palettes, themes, fonts, etc.). We will walk through using the Assistant for building an Shiny app-package, but we’ll limit the number of prompts to keep this chapter brief. If you’d like to read a more comprehensive example of using Shiny Assistant to build an application, I recommend reading this post on Appsilon’s blog.

I think you’ll be convinced that the Shiny Assistant is an impressively helpful tool to have in your Shiny toolbox, even with a limited number of prompts. Let’s get started!

26.1.1 Prompt 1: Build an app

Let’s start with trying to reproduce some of the applications we have in sap. I typically have data before I have an idea for a dashboard or app, so we’ll pass the structure of simplified version our movies data to the Assistant with some instructions on what we’d like to see:2

Prompt: Create a shiny application that visualizes IMDB movie data using a scatter plot. Include inputs for x, y, color, size, and transparency.

The column names are below:

```
Rows: 651
Columns: 18
$ title             …
$ genre             …
$ runtime           …
$ year              …
$ top200_box        …
$ director          …
$ mpaa_rating       …
$ imdb_rating       …
$ critics_rating    …
$ audience_rating   …
$ imdb_num_votes    …
$ critics_score     …
$ audience_score    …
$ best_pic_nom      …
$ best_pic_win      …
$ best_actor_win    …
$ best_actress_win  …
$ best_dir_win      …
```

Use the data stored in this URL:

https://raw.githubusercontent.com/mjfrigaard/sap/refs/heads/23_llm-shiny-assist/data-raw/movies_sample.csv

Tip: Providing data

I’ve found providing a data structure (from str() or dplyr::glimpse()) is incredibly helpful when guiding AI chat bots on building dashboards. Shiny Assistant is great because we can even pass it a URL with a .csv data file.

Below we can see the initial app:

Shiny Assistant: initial app

Shiny Assistant: initial app

The initial application has nearly identical input, outputs, and layout. However, we can see an additional checkbox for Show Movie Titles and the Size by input has been mapped to a variable (not a numerical value).

In our next prompt, we’ll refine the app to more closely match what we have in our sap app-package.

26.1.2 Prompt 2: Refine

Prompt: Change the size to a numeric slider and include a text input for a plot title.

The application is updated with the changes and we can view the changes to app.R in the Shiny Assistant UI:

Shiny Assistant: initial app

Shiny Assistant: initial app
Warning: aes_string()

We can see the following warning regarding the ggplot2 graph:

Shiny Assistant: ggplot2 warning

Shiny Assistant: ggplot2 warning

We avoided this warning with the scatter_plot() utility function in previous branches, but for now we will just note this is something we should return to and refactor after our application in a more complete state.

26.1.3 Prompt 3: Functionalize

Now that we have a UI with the correct inputs and outputs, we’ll convert these into modules and separate the application into it’s respective components.

Prompt: Convert the inputs and outputs into modules and create a standalone app function that calls separate UI and server functions.

The response to this prompt won’t change the app, but we’re given a summary of the changes in the chat:

Shiny Assistant: app functions and files

Shiny Assistant: app functions and files

Response:

I’ve modified the code to use a modular structure following these changes:

  1. Created separate modules for:

    • Controls (inputs) - controlsUI and controlsServer

    • Plot (output) - plotOutputUI and plotOutputServer

    • About data section - aboutDataUI (UI only as it’s static)

  2. Created standalone UI and server functions:

    • ui_function() - assembles all UI components

    • server_function() - coordinates between modules

  3. Created an application function:

    • movie_visualization_app() - bundles everything together
  4. The communication between modules:

    • The controls module returns all inputs as a reactive list

    • The plot module takes two parameters: the data and controls reactives

This modular approach makes the code more maintainable and easier to extend. Each component has a well-defined responsibility, and they communicate through clear interfaces.

I’ve left the Shiny Assistant chat setting to Verbose, but this can be changed to return less descriptive details on the code being produced:

Shiny Assistant Chat setting

Shiny Assistant Chat setting

26.1.4 Prompt 4: Structure

Now we’re reading to start structure the application as an R package. We’ll attempt to create the files and documentation with a single prompt.

Prompt: Store the modules, UI, server, and standalone app functions in separate files and include roxygen2 documentation.

This results in an error when the Shiny Assistant tries to run our updates:

Shiny Assistant: error

Shiny Assistant: error

The summary tells us what changed, but doesn’t give us advice on (or even acknowledge) the error.

Response:

I’ve reorganized the code into separate files with roxygen2 documentation:

  1. controls_module.R: Contains the UI and server functions for the controls module with full roxygen2 documentation including parameters, return values, and imported functions.

  2. plot_module.R: Contains the UI and server functions for the plot output module with documentation for both components.

  3. about_module.R: Contains the UI function for the about data section.

  4. **ui.R*: Contains the main UI function that assembles all UI components.

  5. server.R: Contains the main server function that coordinates between modules.

  6. app.R: Contains the standalone app function that sources all other files and runs the application.

Each function includes proper roxygen2 documentation with:

  • Function descriptions

  • Parameter descriptions

  • Return value descriptions

  • @importFrom tags for explicit dependencies

  • @export tags for functions that should be exported

The app.R file also includes logic to run the app only if the file is being executed directly in an interactive session.

26.1.5 Prompt 5: Error

The error above tells us the bslib::page_sidebar() function isn’t being loaded before runApp() is being called, but the call to library(bslib) is in the app.R file. We’ll see if providing some direction on function/file names and where to call shinyApp() will resolve this error:

Prompt: An error occurred because the page_sidebar function could not be found. The app.R file needs to contain the library() and source() calls before the call to shinyApp(). Also, rename the ui.R and server.R to app_ui.R and app_server.R.

This results in the following changes to the app code structure:

Response:

I’ve made the following changes to fix the issues:

  1. Renamed the files:

    • Changed ui.R to app_ui.R

    • Changed server.R to app_server.R

  2. Modified the app.R file to:

    • Load all required libraries at the beginning of the file

    • Source all module and function files

    • Define the movie_visualization_app() function

    • Call the function directly (removed the if (interactive()) check to ensure it runs)

  3. Maintained the same structure for all modules and functions

These changes ensure that:

  1. All necessary files are sourced before using their functions

  2. The application will properly find all functions when running

  3. The organization follows a more standard Shiny module structure

The application should now run without the “page_sidebar function could not be found” error since we’re properly sourcing all the module files before they’re used in the UI and server functions.

In the app.R file, if we explicitly run the call to library(bslib) and then source("app_ui.R") (which contains the call to page_sidebar()), we see these are executed without errors in the Console:

Shiny Assistant: library() and source() calls

Shiny Assistant: library() and source() calls

However, when we try to source and call the standalone app function, we see the following error:

Shiny Assistant: runApp() error

Shiny Assistant: runApp() error
Error in runApp(x) :
  Can't call `runApp()` from within `runApp()`. If your application code contains
  `runApp()`, please remove it.

This error is a little tricky, because we’re calling shinyApp(), but this is happening because we’re trying to explicitly call shinyApp() (and the UI works by call runApp()).

We can fix this by clicking on the icon in the UI to run the app:

Shiny Assistant: run the app using the ‘Run App’ icon

Shiny Assistant: run the app using the ‘Run App’ icon

26.1.6 Bonus: Theme

Now that we have our modules, app functions and roxygen2 documentation, we’ll finish by adding a dark thematic theme:

Prompt: Add a dark theme using the thematic package: https://rstudio.github.io/thematic/

The updated app includes a dark theme:

Shiny Assistant: thematic theme

Shiny Assistant: thematic theme

And the summary of changes is below:

Response:

I’ve integrated the thematic package to add a dark theme to the application. Here are the changes I made:

  1. Added library(thematic) to the app.R file to load the thematic package.

  2. Added thematic_shiny(font = "auto") to automatically adjust plot themes to match the app theme. This automatically styles the ggplot2 visualizations to match the Shiny app’s theme.

  3. Modified the UI function in app_ui.R to use a dark theme:

    • Created a dark theme using bs_theme() from bslib with dark background colors, light text, and appropriate accent colors

    • Applied the theme to the page_sidebar() using the theme parameter

  4. Updated the plot_module.R to work with thematic:

    • Removed the explicit theme_minimal() call since thematic will handle the base theming

    • Kept specific theme adjustments for title styling and legend positioning

The thematic package now automatically coordinates the ggplot2 theme with the application’s dark theme, creating a cohesive dark mode experience throughout the entire application. The plots will automatically inherit the dark background and appropriate colors for text and elements.

After confirming the new theme works, we’ll download the app files by clicking on the cloud icon in the upper right corner:

Download files

Download files

26.2 Finishing touches

We’ll place the downloaded files in the inst/shiny-assist/movies folder in sap, and after some minor changes to the files/folder organization (we’ll move the standalone app function into it’s own file in the R/ folder):

inst/shiny-assist/
└── movies
    ├── R
    │   ├── about_module.R
    │   ├── app_server.R
    │   ├── app_ui.R
    │   ├── controls_module.R
    │   ├── movie_visualization_app.R
    │   └── plot_module.R
    └── app.R

3 directories, 7 files
1
R/ folder for modules and functions
2
Standalone app function

The app-package structure starting to take shape!

26.2.1 The app.R file

The app.R file no longer needs to source() the modules and app functions if they are placed in an R/ folder.3

# Load required libraries
library(shiny)
library(ggplot2)
library(dplyr)
library(readr)
library(bslib)
library(thematic)

# Setup thematic to automatically adjust plot theme to match app theme
thematic_shiny(font = "auto")

# Run the application 
movie_visualization_app()

Now app.R only loads the packages, theme, and launches the app.

In Positron, we can see the Run App icon is present at the top of app.R, and it’s fully functional:

Shiny Assistant app launced in Positron

Shiny Assistant app launced in Positron

26.2.2 ggplot2 warnings

We have a persistent warning printed to the console about the use of aes_string() in our plot_module.R.

ggplot2::aes_string() warning

ggplot2::aes_string() warning

The solution is to convert the aes_string() to use rlang::sym() and !!.

aes_string()
# Create base plot
p <- ggplot2::ggplot(data(), 
        ggplot2::aes_string(
        x = ctrl$x_var, 
        y = ctrl$y_var)
      )
rlang::sym() & !!
# convert to symbols 
x_var <- rlang::sym(ctrl$x_var)
y_var <- rlang::sym(ctrl$y_var)

# Create base plot
p <- ggplot2::ggplot(
          data(), 
        ggplot2::aes(
          # use double bang!!
            x = !!x_var, 
            y = !!y_var
            )
          )
aes_string()
# Add color if selected
if (ctrl$color_var != "none") {

  p <- p + aes_string(
              color = ctrl$color_var
              )
  
}
rlang::sym() & !!
# Add color if selected
if (ctrl$color_var != "none") {

  # convert to symbols 
  color_var <- rlang::sym(ctrl$color_var)

  p <- p + ggplot2::aes(
              # use double bang!!
              color = !!color_var
              )
}

Finally, we’ll add this app to our launch_app() function:

show/hide updated launch_app() function
launch_app <- function(app = NULL, options = list(), run = "p", ...) {
  if (interactive()) {
    display_type(run = run)
  }

  if (is.null(app)) {
    app <- "movies"
  }

  logr_msg(glue::glue("Launching app: {app}"),
    level = "INFO"
  )

  tryCatch({
      if (app == "bslib") {
        shinyApp(
          ui = movies_ui(bslib = TRUE),
          server = movies_server,
          options = options
        )
      } else if (app == "ggp2") {
        shinyAppDir(
          appDir = system.file("tidy-movies", package = "sap"),
          options = options
        )
      } else if (app == "quarto") {
        quarto::quarto_preview(
          system.file("quarto", "index.qmd", package = "sap"),
          render = "all"
        )
      } else if (app == "assist") {
        shinyAppDir(
          appDir = system.file("shiny-assist", package = "sap"),
          options = options
        )
      } else {
        shinyApp(
          ui = movies_ui(...),
          server = movies_server,
          options = options
        )
      }
    }, error = function(e) {
      logr_msg(glue::glue("FATAL: Application failed to launch. Reason: {e$message}"),
        level = "FATAL"
      )

      stop("Application launch failed. Check logs for details.")
    }
  )
}
1
New Shiny Assistant application

Recap

This chapter covered building a Shiny app ‘fit for a package’ using the Shiny Assistant. As you can see, in just a few prompts, we can have a Shiny application that ~80% ready to be loaded, installed, and launched. The callout box below includes a few items to watch out for when using Shiny Assistant to build apps:

Recap: Shiny Assistant

Shiny Assistant is a powerful tool for building Shiny apps. With the right prompting, the application code can be easily structured into an R package. The only caveats I’ve found are:

  • Deprecated functions: the ggplot2 (aes_string()) warning is an example of somthing that might require more specific prompting (or knowledge of rlang/ggplot2).

  • Documentation/Dependencies: the roxygen2 documentation generated by Shiny Assistant used copius @importFrom and @import tags, which is something we want to avoid.

  • Module structure: Shiny Assistant came up with a different module structure than we had in our previous branches, but that’s not to say it’s incorrect. However, it’s important to check the reactivity and understand how the namespaces and reactive inputs are managed in the application before putting it into production.


  1. Read more in the Shiny Assistant post on the Shiny blog.↩︎

  2. I’ve included a .csv version of these data in the data-raw/ folder of this branch.↩︎

  3. loadSupport() was covered back in Section 2.3.↩︎