---
title: "Get started with shinygenui"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Get started with shinygenui}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include=FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  eval = FALSE
)
```

shinygenui adds two things to a Shiny app: a chat panel where people can ask
questions and a canvas where a large language model can add UI components.
The app developer decides which UI components the model may use and which
arguments each one accepts. Together, these components form a catalog.

Each component in the catalog becomes an [ellmer](https://ellmer.tidyverse.org)
tool. The model builds the interface by calling these tools with data.
It never writes R code, and shinygenui never evaluates model output.
If the model supplies an invalid argument, such as a column that does not exist,
validation rejects the call. The model receives a useful error and can try again
while the Shiny session keeps running.

## A complete app

```{r}
library(shiny)
library(bslib)
library(shinygenui)

ui <- page_sidebar(
  title = "mtcars explorer",
  sidebar = sidebar(width = 380, shinychat::chat_ui("chat", height = "100%")),
  genui_canvas("canvas")
)

server <- function(input, output, session) {
  catalog <- genui_catalog(genui_components_bslib(data = mtcars))

  genui_server(
    "canvas",
    catalog = catalog,
    chat = ellmer::chat_anthropic(),
    data = reactive(mtcars),
    chat_id = "chat",
    greeting = "Ask me about the mtcars data.",
    system_prompt = genui_prompt(
      catalog,
      context = "The data is the mtcars dataset included with R."
    )
  )
}

shinyApp(ui, server)
```

The app has three main pieces:

- `genui_canvas()` creates the space where components appear.
- `genui_catalog()` collects the components the model may use. This example
  uses the components supplied by shinygenui: a value box, a Markdown card,
  a data table, a scatter plot, and a histogram.
- `genui_server()` turns the catalog into tools and adds them to the ellmer
  `Chat`. It also adds tools for changing the canvas, installs the system
  prompt, and connects the model to the `shinychat::chat_ui()` in the sidebar.

Any ellmer provider works here: you can swap `ellmer::chat_anthropic()` for
`ellmer::chat_openai()`, `ellmer::chat_ollama()`, and so on. Create the
`Chat` inside the server function so that each session gets its own object.
This keeps the conversation and the functions used by its tools separate for
each user.

## Defining a component

A component describes what the model can ask for and how Shiny should display
the result:

```{r}
histogram <- genui_component(
  name = "histogram",
  description = "A numeric histogram with a slider for the number of bins.",
  args = list(
    column = ellmer::type_enum(names(mtcars), "Column to plot."),
    bins = ellmer::type_integer("Initial number of bins.", required = FALSE)
  ),
  ui = function(id, args) {
    ns <- shiny::NS(id)
    bslib::card(
      shiny::plotOutput(ns("plot")),
      shiny::sliderInput(ns("bins"), "Bins", 5, 60, args$bins %||% 30)
    )
  },
  server = function(id, args, data) {
    shiny::moduleServer(id, function(input, output, session) {
      output$plot <- shiny::renderPlot({
        hist(data()[[args$column]], breaks = input$bins)
      })
    })
  },
  check = function(args, data) {
    if (!is.numeric(data[[args$column]])) {
      paste0("Column \"", args$column, "\" is not numeric.")
    }
  }
)
```

- `name` and `description` tell the model when to use the component.
  Write descriptions that are clear and specific.
- `args` is a named list of ellmer types. A plain string is shorthand for
  `ellmer::type_string()`. You can build the catalog inside the server
  function, which means its choices can depend on values available in the
  current session. In this example, `column` only accepts names that occur in
  `mtcars`. Validation rejects any other name before your code runs.
- `ui(id, args)` returns htmltools tags. `id` is the module id for this
  instance. Use `shiny::NS(id)` for inputs and outputs inside the component.
- `server(id, args, data)` is optional. It usually calls
  `shiny::moduleServer()`, as in the example. `data` is the reactive passed to
  `genui_server()`. The slider is a regular Shiny input, so moving it redraws
  the plot without contacting the model. If the module creates observers,
  return them on their own or in a list. shinygenui will destroy them when it
  updates or removes the component.
- `check(args, data)` runs after argument validation and receives the current
  value of `data`. Return `NULL` to accept the arguments or a string to reject
  them. The model receives this string as an error.

## The component lifecycle

Each component created by the model gets an id such as `c1` or `c2`.
The model receives this id, which lets it refer to the same component later.
shinygenui also gives the model four tools for working with the canvas:

- `update_component(id, args)` changes only the arguments supplied by the
  model, validates the result, and starts the module again in the same place.
  Because the module restarts, its inputs return to their default values.
- `remove_component(id)` removes the component. If it is a container, this
  also removes its children.
- `clear_canvas()` empties the canvas.
- `get_canvas_state()` reports each component, its arguments, and the current
  values of its Shiny inputs. It does not change the canvas. The model can use
  this information to see what a user has adjusted.

The system prompt created by `genui_prompt()` asks the model to keep its chat
response brief while it adds components. It also asks the model to update an
existing component when the user refines a request and to avoid filling the
canvas with unnecessary components. Use the `context` argument to explain
your app and its data. Useful context might include a description of the
columns, a few sample rows, or terms that are specific to your organization.
shinygenui only puts raw data in the prompt if you include it yourself.

## Containers

A component declared with `container = TRUE` can hold other components.
The model creates the container first, then passes its id as `parent_id` when
it creates each child. `genui_card_row()` is an example supplied by the package.
It creates a titled row that can group value boxes or plots.
Removing a container also removes its children.
See `inst/examples/02-layout/app.R` for a full app.

## Trace and replay

shinygenui records each successful call as a plain list. Together, these lists
describe the current canvas in the order it was built. `genui_trace()` returns
this record as a reactive, and `genui_replay()` passes a saved record through
the same validation and rendering code to rebuild the canvas. You can replay
it in a new session without an LLM:

```{r}
# In the session that built the canvas
observe({
  saveRDS(genui_trace(session)(), "canvas-trace.rds")
})

# In a later session, no chat anywhere
server <- function(input, output, session) {
  genui_replay(
    readRDS("canvas-trace.rds"),
    catalog = catalog,
    target = "canvas",
    data = reactive(mtcars)
  )
}
```

The same calls produce the same instance ids, and ids are never reused.
The record does not include values from Shiny inputs, so replay restores those
inputs to their defaults.

## When things go wrong

If argument validation, `check()`, or rendering fails, the model receives an
error and can try again. A failed create leaves nothing behind, and a failed
update leaves the existing component unchanged. The error does not crash the
Shiny session. Because the model can only call components in your catalog, a
prompt injection cannot make it run arbitrary code. Failures are always
written to the server log. Set `options(shinygenui.verbose = TRUE)` to also
log successful changes to the canvas.
