Step-by-step guide to integrating the Shiny AI Assistant into your dashboard.
- R 4.0+ with Shiny
- Node.js 18+ (for running the API server)
- Anthropic API key
cd packages/server
cp .env.example .env
# Edit .env and add: ANTHROPIC_API_KEY=your-key
pnpm install
pnpm dev
# Server runs at http://localhost:3000# From the packages/r-shiny directory
devtools::install_local("packages/r-shiny")library(shiny)
library(shinyAIAssistant)
ui <- fluidPage(
titlePanel("My Dashboard"),
# Your existing UI...
# Add AI Assistant widget
aiAssistantWidget(
apiUrl = "http://localhost:3000/api/chat"
)
)
server <- function(input, output, session) {
# Your existing server logic...
}
shinyApp(ui, server)That's it! You now have a floating chat button in your dashboard.
The assistant supports 4 capability levels, each adding more features:
| Level | Features | Requirements |
|---|---|---|
| 0 | Navigation, highlighting, basic Q&A | Widget only |
| 1 | Enhanced context, glossary, component metadata | + Manifest YAML |
| 2 | Action execution (filters, exports) | + Action handlers in R |
| 3 | Data queries, analytics | + Data source config |
With just the widget, the AI can:
- Answer questions about what it sees on screen
- Navigate between tabs/pages
- Highlight and locate UI elements
- Provide general assistance
aiAssistantWidget(
apiUrl = "http://localhost:3000/api/chat",
position = "bottom-right", # or "bottom-left"
theme = "auto" # "light", "dark", or "auto"
)A manifest file provides rich metadata about your dashboard.
name: "Sales Analytics Dashboard"
version: "1.0.0"
description: "Revenue analysis for the executive team"
capabilityLevel: "level-1"
# Custom instructions for the AI
aiInstructions: |
This dashboard shows sales performance metrics.
Always mention specific numbers when answering questions.
# Business term definitions
glossary:
- term: "ARR"
definition: "Annual Recurring Revenue"
synonyms: ["annual revenue"]
- term: "MRR"
definition: "Monthly Recurring Revenue"
# Navigation structure
navigation:
- id: "overview"
label: "Overview"
path: "/overview"
description: "Executive summary with KPIs"
- id: "regional"
label: "Regional"
path: "/regional"
description: "Performance by region"
# Page and component documentation
pages:
- id: "overview"
title: "Overview"
components:
- id: "revenue_chart"
label: "Revenue Trend"
type: "chart"
description: "Monthly ARR over time"aiAssistantWidget(
apiUrl = "http://localhost:3000/api/chat",
manifest = "dashboard-manifest.yaml"
)Allow the AI to execute actions like setting filters or exporting data.
capabilityLevel: "level-2"
actions:
- id: "set_filter"
name: "Set Filter"
description: "Apply a filter value"
category: "update"
parameters:
- name: "filterId"
type: "string"
required: true
- name: "value"
type: "string"
required: true
- id: "reset_filters"
name: "Reset Filters"
description: "Clear all filters"
category: "update"server <- function(input, output, session) {
# Your existing logic...
# Register AI action handlers
aiAssistantHandler(session, handlers = list(
# Navigate to a tab
navigate_to = function(params) {
nav_select("main_nav", params$target)
list(success = TRUE, message = paste("Navigated to", params$target))
},
# Set a filter
set_filter = function(params) {
updateSelectInput(session, params$filterId, selected = params$value)
list(success = TRUE, message = paste("Set", params$filterId, "to", params$value))
},
# Set date range
set_date_range = function(params) {
updateDateRangeInput(session, "date_filter",
start = as.Date(params$start),
end = as.Date(params$end)
)
list(success = TRUE, message = "Date range updated")
},
# Reset all filters
reset_filters = function(params) {
updateSelectInput(session, "region_filter", selected = "All")
updateSelectInput(session, "product_filter", selected = "All")
updateDateRangeInput(session, "date_filter", start = DATE_MIN, end = DATE_MAX)
list(success = TRUE, message = "All filters reset")
}
))
}Enable the AI to query your data and provide analytical insights.
# Define data sources
data_sources <- list(
list(
id = "sales",
name = "Sales Transactions",
description = "Transaction data with revenue, region, and product info",
fields = list(
list(name = "date", type = "date", filterable = TRUE),
list(name = "region", type = "string", filterable = TRUE,
enumValues = c("North America", "EMEA", "APAC", "LATAM")),
list(name = "arr", type = "number", aggregatable = TRUE,
description = "Annual Recurring Revenue"),
list(name = "product", type = "string", filterable = TRUE)
)
)
)
# Enable Level 3
aiAssistantWidget(
apiUrl = "http://localhost:3000/api/chat",
manifest = "dashboard-manifest.yaml",
dataSources = data_sources,
enableDataQueries = TRUE
)aiAssistantHandler(session, handlers = list(
# ... other handlers ...
query_data = function(params) {
# params contains: source, select, filters, groupBy, aggregations, limit
data <- sales_data # Your data source
# Apply filters
if (!is.null(params$filters)) {
for (f in params$filters) {
data <- data %>% filter(.data[[f$field]] == f$value)
}
}
# Apply grouping and aggregation
if (!is.null(params$groupBy)) {
data <- data %>%
group_by(across(all_of(params$groupBy))) %>%
summarise(across(where(is.numeric), sum))
}
# Limit results
if (!is.null(params$limit)) {
data <- head(data, params$limit)
}
list(
success = TRUE,
data = as.list(data),
rowCount = nrow(data)
)
}
))aiAssistantWidget(
# Required
apiUrl = "http://localhost:3000/api/chat",
# Optional - Enhanced context
manifest = "dashboard-manifest.yaml",
# Optional - Level 3 data queries
dataSources = list(...),
enableDataQueries = FALSE,
# Optional - Appearance
position = "bottom-right", # "bottom-left" | "bottom-right"
theme = "auto", # "light" | "dark" | "auto"
# Optional - Initial state
defaultOpen = FALSE,
welcomeMessage = "How can I help you today?"
)All handlers should return a list with:
# Success
list(success = TRUE, message = "Action completed")
# Success with data
list(success = TRUE, data = list(field = "value"), message = "Query complete")
# Failure
list(success = FALSE, error = "Description of what went wrong")set_filter = function(params) {
valid_filters <- c("region_filter", "product_filter", "segment_filter")
if (!params$filterId %in% valid_filters) {
return(list(success = FALSE, error = paste("Unknown filter:", params$filterId)))
}
updateSelectInput(session, params$filterId, selected = params$value)
list(success = TRUE, message = paste("Set", params$filterId, "to", params$value))
}set_date_range = function(params) {
start <- as.Date(params$start)
end <- as.Date(params$end)
if (is.na(start) || is.na(end)) {
return(list(success = FALSE, error = "Invalid date format. Use YYYY-MM-DD."))
}
if (start > end) {
return(list(success = FALSE, error = "Start date must be before end date."))
}
updateDateRangeInput(session, "date_filter", start = start, end = end)
list(success = TRUE, message = paste("Date range set to", start, "-", end))
}navigate_to = function(params) {
valid_tabs <- c("overview", "regional", "details", "explorer")
if (!params$target %in% valid_tabs) {
return(list(success = FALSE, error = paste("Unknown tab:", params$target)))
}
nav_select("main_nav", params$target)
list(success = TRUE, message = paste("Navigated to", params$target))
}- Check browser console for JavaScript errors
- Verify API URL is accessible (try opening it directly)
- Ensure
shinyAIAssistantpackage is loaded
- Verify
aiAssistantHandler()is called in server function - Check R console for error messages
- Ensure handler names match action types (e.g.,
set_filternotsetFilter)
- Check network latency to API server
- Verify Anthropic API key is valid
- Consider using streaming (enabled by default)
- Ensure elements have stable, unique IDs
- Check for CSS z-index conflicts
- Verify manifest component IDs match actual element IDs
- See
examples/demo-dashboard/for a complete working example - Review ARCHITECTURE.md for system design details
- Check MANIFEST_SPEC.md for full manifest documentation