Skip to contents

Create a simple Shiny app for live chatting using an ellmer::Chat object. Note that these functions will mutate the input client object as you chat because your turns will be appended to the history.

The app created by chat_app() is suitable for interactive use by a single user. For multi-user Shiny apps, use chat_ui() and chat_server() and be sure to create a new chat client for each user session.

Usage

chat_app(
  client,
  ...,
  title = NULL,
  icon = NULL,
  window_title = NULL,
  id = "chat",
  greeting = NULL,
  history = TRUE,
  bookmark_store = "url",
  app_options = list()
)

chat_server(
  id,
  client,
  greeting = NULL,
  history = TRUE,
  bookmark_on_input = lifecycle::deprecated(),
  bookmark_on_response = lifecycle::deprecated(),
  session = shiny::getDefaultReactiveDomain()
)

Arguments

client

A chat object created by ellmer, e.g. ellmer::chat_openai() and friends.

...

Named arguments passed to page_chat().

title

The title displayed in the page header. If NULL (the default), a "{model} ({provider})" title is derived from client.

icon

Optional UI displayed before title. See page_chat().

window_title

The browser-window title. If NULL (the default), uses "shinychat | {model} | {date}" derived from client.

id

The ID shared by page_chat() and chat_server().

greeting

Optional greeting to set when the module initializes. Accepts a static value (string, htmltools::HTML(), htmltools::tagList(), or chat_greeting()) or a function that generates the greeting dynamically. See the Greeting section below for details.

history

Conversation history configuration. TRUE (default) enables history with default settings; FALSE disables it; pass a history_options() object to customise storage, identity, titling, or hooks.

bookmark_store

The bookmarking store to use for the app. Passed to enableBookmarking in shiny::shinyApp(). Defaults to "url", which uses the URL to store the chat state. URL-based bookmarking is limited in size; use "server" to store the state on the server side without size limitations; or disable bookmarking by setting this to "disable".

app_options

A list passed to the options argument of shiny::shinyApp().

bookmark_on_input

A logical value determines if the bookmark should be updated when the user submits a message. Default is TRUE.

bookmark_on_response

A logical value determines if the bookmark should be updated when the response stream completes. Default is TRUE.

session

The Shiny session. Defaults to the current reactive domain.

Value

  • chat_app() returns a shiny::shinyApp() object.

  • chat_server() includes the shinychat server logic, and returns an environment containing:

    • last_input: A reactive value containing the last user input (a string when attachments are disabled, a list of ellmer Content objects when enabled).

    • last_turn: A reactive value containing the last assistant turn.

    • update_user_input(): A function to update the chat input or submit a new user input. Takes the same arguments as update_chat_user_input(), except for id and session, which are supplied automatically.

    • append(): A function to append a new message to the chat UI. Takes the same arguments as chat_append(), except for id and session, which are supplied automatically.

    • clear(): A function to clear the chat client turns and the chat UI. It optionally takes a list of messages used to initialize the chat after clearing. messages should be a list of messages, where each message is a list with role and content fields. The client_history argument controls how the chat client's history is updated after clearing. It can be one of: "clear" the chat history; "set" the chat history to messages; "append" messages to the existing chat history; or "keep" the existing chat history. clear() is unavailable when conversation history is enabled; use new_chat() instead.

    • new_chat(): A function to save the current conversation and start a new one by clearing the chat client's turns and chat UI, resetting the active conversation, and updating the history drawer. It is available only when conversation history is enabled. new_chat(greeting = TRUE) also clears the greeting and requests a new one. new_chat() errors while a response is streaming; wait for it to complete or stop it first.

    • set_greeting(): A function to set, stream, or clear the chat greeting. Pass a chat_greeting() object, a plain string, or NULL to clear. Streaming greetings run inside an shiny::ExtendedTask so the session stays responsive; if called while a greeting is already streaming, the new greeting is queued. If the greeting has already been dismissed, calling set_greeting() updates the content but does not make it visible again; call clear(greeting = TRUE) first to show a new greeting after dismissal.

    • status: A reactive value indicating the current chat interaction state. Returns "idle" when no response is in progress, or "streaming" while a response is actively being received.

    • history: A namespace for managing conversation-history callbacks and persistence. saved <- chat_module$history$save() saves only the existing active conversation and returns whether it was saved. Storage and bookmark errors propagate to the caller.

    • last_error: A reactive value holding the condition from the most recent response if it failed, and NULL otherwise. Both a finished and a failed response read as "idle" in status, so this is what tells them apart. Responses only: a greeting streams from its own task, and an error raised by a slash command handler is reported as a notification, so neither appears here.

    • client: The current chat client object (an active binding that always reflects the latest client, even after set_client() is called).

    • history$conversation_id(): A reactive expression returning the active conversation ID: NULL when history is disabled or the chat is still an empty draft, otherwise the ID allocated on the first user submission – before the model call – that the saved conversation record carries. The ID is stable across retries, restores, conversation switches, and set_client() calls. The ID is also handed to the client (via its conversation_id binding, when supported), which records it as the gen_ai.conversation.id attribute on its own OpenTelemetry spans.

    • set_client(new_client, sync = TRUE): Replace the chat client used by the module. When sync is TRUE (the default), the new client inherits conversation turns, system prompt, and tools from the previous client so the conversation continues seamlessly. Set sync = FALSE to use the new client as-is. If a response is currently streaming, the swap is deferred until the stream completes. If called multiple times while streaming, only the most recent new client is used.

    • slash_command(name, description, handler, ..., echo, force): Register a slash command. handler is required: pass a function (taking 0 or 1 argument), or NULL for a client-side command handled in JavaScript via the shiny:chat-slash-command DOM event. A handler that takes one argument receives a ContentSlashCommand object (not a plain string). See ContentSlashCommand for details on how to use this object to preserve the original command text across bookmarks. echo controls whether invoking the command is echoed as a user message and awaits a response; it defaults to TRUE when a handler is given and FALSE otherwise (set echo = FALSE for a handler that only performs side effects). Returns a function that removes the command. Errors if a command with the same name is already registered unless force = TRUE.

Functions

  • chat_app(): A simple Shiny app for live chatting. Note that this app is suitable for interactive use by a single user; do not use chat_app() in a multi-user Shiny app context.

  • chat_server(): Wire up batteries-included chat server logic in a Shiny session. Pair with chat_ui() by passing it the same id; see Pairing with chat_server() in chat_ui() for the top-level and module-based patterns.

Migration

... now configures page_chat() instead of shiny::shinyApp(). Pass Shiny app options through app_options, and use bookmark_store instead of enableBookmarking. To customize onStart or uiPattern, compose page_chat() and chat_server() manually.

This is a breaking change: ... no longer accepts arguments for shiny::shinyApp(), including options, enableBookmarking, onStart, and uiPattern.

Greeting

When greeting is a function, it is called each time the greeting_requested event fires — on first view when the chat is empty, and again after clear(greeting = TRUE). The function should return a chat_greeting() (typically wrapping a stream). Static values (strings, chat_greeting() objects) are set once at init and do not regenerate.

The function signature determines what is passed. Currently the only recognized argument is client.

function(client) (recommended). A clone of the client with its turn history wiped is passed as client. This avoids manually creating and configuring a separate client:

chat_server("chat", client, greeting = function(client) {
  stream <- client$stream_async("Generate a short welcome message.")
  chat_greeting(stream)
})

function() (zero arguments). You create and manage your own client:

chat_server("chat", client, greeting = function() {
  greeter <- ellmer::chat_openai(model = "gpt-4o")
  stream <- greeter$stream_async("Generate a short welcome message.")
  chat_greeting(stream)
})

Static value. Set once; does not regenerate after clear():

chat_server("chat", client, greeting = "## Welcome!\n\nHow can I help?")

The returned set_greeting() helper is available for cases where you need to set a greeting outside the greeting lifecycle.

Examples

if (FALSE) { # \dontrun{
# Interactive in the console ----
client <- ellmer::chat_anthropic()
chat_app(client)

# Inside a Shiny app ----
library(shiny)
library(bslib)
library(shinychat)

ui <- page_fillable(
  titlePanel("shinychat example"),

  layout_columns(
    card(
      card_header("Chat with Claude"),
      chat_ui(
        "claude",
        greeting = "Hi! Use this chat interface to chat with Anthropic's `claude-3-5-sonnet`."
      )
    ),
    card(
      card_header("Chat with ChatGPT"),
      chat_ui(
        "openai",
        greeting = "Hi! Use this chat interface to chat with OpenAI's `gpt-4o`."
      )
    )
  )
)

server <- function(input, output, session) {
  claude <- ellmer::chat_anthropic(model = "claude-3-5-sonnet-latest") # Requires ANTHROPIC_API_KEY
  openai <- ellmer::chat_openai(model = "gpt-4o") # Requires OPENAI_API_KEY

  chat_server("claude", claude)
  chat_server("openai", openai)
}

shinyApp(ui, server)
} # }