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 fromclient.- icon
Optional UI displayed before
title. Seepage_chat().- window_title
The browser-window title. If
NULL(the default), uses"shinychat | {model} | {date}"derived fromclient.- id
The ID shared by
page_chat()andchat_server().- greeting
Optional greeting to set when the module initializes. Accepts a static value (string,
htmltools::HTML(),htmltools::tagList(), orchat_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;FALSEdisables it; pass ahistory_options()object to customise storage, identity, titling, or hooks.- bookmark_store
The bookmarking store to use for the app. Passed to
enableBookmarkinginshiny::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
optionsargument ofshiny::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 ashiny::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 ellmerContentobjects 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 asupdate_chat_user_input(), except foridandsession, which are supplied automatically.append(): A function to append a new message to the chat UI. Takes the same arguments aschat_append(), except foridandsession, which are supplied automatically.clear(): A function to clear the chat client turns and the chat UI. It optionally takes a list ofmessagesused to initialize the chat after clearing.messagesshould be a list of messages, where each message is a list withroleandcontentfields. Theclient_historyargument controls how the chat client's history is updated after clearing. It can be one of:"clear"the chat history;"set"the chat history tomessages;"append"messagesto the existing chat history; or"keep"the existing chat history.clear()is unavailable when conversation history is enabled; usenew_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 achat_greeting()object, a plain string, orNULLto 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, callingset_greeting()updates the content but does not make it visible again; callclear(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, andNULLotherwise. Both a finished and a failed response read as"idle"instatus, 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 afterset_client()is called).history$conversation_id(): A reactive expression returning the active conversation ID:NULLwhen 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, andset_client()calls. The ID is also handed to the client (via itsconversation_idbinding, when supported), which records it as thegen_ai.conversation.idattribute on its own OpenTelemetry spans.set_client(new_client, sync = TRUE): Replace the chat client used by the module. WhensyncisTRUE(the default), the new client inherits conversation turns, system prompt, and tools from the previous client so the conversation continues seamlessly. Setsync = FALSEto 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.handleris required: pass a function (taking 0 or 1 argument), orNULLfor a client-side command handled in JavaScript via theshiny:chat-slash-commandDOM 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.echocontrols whether invoking the command is echoed as a user message and awaits a response; it defaults toTRUEwhen a handler is given andFALSEotherwise (setecho = FALSEfor 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 unlessforce = 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 usechat_app()in a multi-user Shiny app context.chat_server(): Wire up batteries-included chat server logic in a Shiny session. Pair withchat_ui()by passing it the sameid; see Pairing withchat_server()inchat_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)
} # }