shinychat 0.5.0
API additions
Added
chat_server()as the new primary way to wire up server-side chat logic. Pair it withchat_ui()by matchingid, e.g.chat_ui("chat")withchat_server("chat", client). It does the same job aschat_mod_server()but runs directly in the caller’s session scope rather than creating its own module scope, and it enables many of the features under New features and improvements below automatically.chat_mod_server()andchat_mod_ui()are now soft-deprecated in favor ofchat_server()andchat_ui(). (#264)-
Added
page_chat()for full-window chat pages with persistent chat navigation, responsive sidebars, and drawers. Register secondary pages withchat_nav_panel(), configure sidebars withchat_sidebar(), and add a drawer withchat_drawer(), controllable from the server viachat_drawer_show(),chat_drawer_update(),chat_drawer_hide(), andchat_drawer_toggle(). Related additions:chat_ui_history()for mounting conversation history outside the chat, apage_chat_theme()baseline theme, anddrawer/show_historyoptions onchat_ui(). (#329)- The page shell also supports standard bslib programmatic navigation (e.g.
bslib::nav_select()), with the active page readable server-side asinput$<id>_page.
- The page shell also supports standard bslib programmatic navigation (e.g.
Added slash commands: a typeahead command palette that lets users trigger named shortcuts directly from the chat input (type
/to open it). Commands can expand into LLM prompts, trigger server-side side effects (clear chat, open a modal, export transcript), or be handled entirely client-side via the cancelableshiny:chat-slash-commandDOM event. Register commands with theslash_command()method of the object returned bychat_server(); itsechoparameter controls whether an invocation is recorded as a user message and triggers a loading state. (#239)Added
chat_get_greeting()for reading the current greeting (and whether the user dismissed it) from the server, along with a newinput$<id>_greeting_dismissedevent. Server-set greetings now also survive Shiny bookmarking round-trips. (#254)
New features and improvements
chat_server()gets multi-conversation history automatically: a drawer for starting new chats and returning to previous ones, with LLM-generated titles, search, rename, and delete. Conversations are persisted per-user (or a custom scope) via a pluggable store (the defaultFileConversationStoreworks out of the box, including on Posit Connect). Customize withhistory = history_options(...)— e.g. how the active conversation is restored across reloads (browser storage, URL, or Shiny bookmarking) and callbacks to keep app state synced to it — or opt out withhistory = FALSE. The history object also offers programmatic control, including a reactive conversation ID and an explicitsave()method. For apps that can’t usechat_server(), wire it up manually withchat_enable_history(). (#266, #307, #328)You can now edit and resend a message after sending it. Editing forks the conversation from that point — the original branch is kept as a sibling, and
‹ 1 / 2 ›controls let you switch between versions at any time, including after reloading the page or returning from the history drawer. Requires history to be enabled (the default withchat_server()). (#269)Cancelling an in-progress response is now automatic with
chat_server(): the stop button appears by default and cancellation is handled for you. (#264)-
Added file attachment support: users can upload images, PDFs, and text files alongside their messages.
chat_server()enables attachments by default and automatically converts uploads into ellmerContentobjects for the model. For non-chat_server()usage, enable withallow_attachments = TRUE(or a MIME allow-list). The maximum combined attachment size defaults to approximately 30 MB and can be configured via theSHINYCHAT_MAX_ATTACHMENT_SIZEenvironment variable. (#250)- When attachments are enabled,
input$<id>_user_inputis a list of ellmerContentobjects (typed text, if present, followed by one object per attachment) rather than a plain string — forward it to a chat method by splicing with!!!, e.g.chat$stream_async(!!!input$<id>_user_input). Thelast_inputreactive returned bychat_server()mirrors this shape.
- When attachments are enabled,
-
Web search and web fetch responses from ellmer now show their activity and citations directly in the chat. Readers can open a citation beside its claim or use the message-wide Sources pill.
ContentCitation@grounded_spanlinks each citation to the answer text it supports. (#280)- Citations are powered by a new
<shiny-aside>markup convention that any assistant message can use to attach source details to specific claims — useful for custom RAG workflows. See theAsidessection in?chat_append. (#278)
- Citations are powered by a new
-
Tool call displays have been reworked to be more concise and to intelligently group multiple calls together. By default, calls render as a condensed activity row; expand a group row to see each call, then drill into a call for its full request/result card. (#283)
- Added
tool_result_display(), a constructor for thedisplayobject passed asextra = list(display = ...)on anellmer::ContentToolResult(a bare named list with the same fields still works). Newlabelandvalue_previewfields (e.g. a filename, and “1,204 rows”) are shown in the activity row. - A tool’s definition
title(from its annotations) and its resulttitle(fromtool_result_display()) are now shown as-is, without client-side tense conjugation — the old"Running {title}"/"{title} failed"templates are gone. The definition title shows while the call is running; for a single-call row, the result title (if provided) replaces it when the result arrives, and failures are shown via a separate status cue. If a title now reads oddly while running, write it in the present tense (e.g. “Running R code”). - Control grouping with the
tool_groupingparameter ofchat_ui():"tool"(default) groups calls to the same tool within a tool-calling loop,"all"groups every call in the loop together, and"none"shows one activity row per call (thinking or prose starts a new loop). Individual tools can override the chat-level setting with agroupingtool annotation, e.g.tool(..., annotations = tool_annotations(grouping = "all")). - Set
open_style = "framed"intool_result_display()to draw a border around an open tool result’s header and contents — a better fit for results with a footer or fullscreen toggle. (#331) - Fully custom tool-result UI returned from a
contents_shinychat()method is now paired with its tool request: while the tool runs it appears in the activity row, and once the result arrives the custom UI renders as standalone output. Custom results are also preserved when preloading or restoring conversations.
- Added
Added
submit_keyparameter tochat_ui():"enter"(default, Enter submits) or"enter+modifier"(Ctrl/Cmd+Enter submits, plain Enter inserts a line break). The input remains editable while a response is streaming — only submission is blocked, not typing. (#251)The send button is easier to customize: a new
icon_sendparameter onchat_ui()swaps the submit icon, and adata-stateattribute exposes the button’s current state for styling. (#350)Styling is more customizable via CSS custom properties: the set of public
--shiny-chat-*variables has grown to cover the send button, history drawer, page layout, thinking display, and more, and all of them can now be overridden from:root(previously, element-level defaults always won). (#350, #355)Single tildes no longer trigger strikethrough in markdown. Text like
(~$1.50)and~/Documentsnow renders as literal text; only~~text~~produces strikethrough. (#349, #353)
Breaking changes
chat_app()now configures its full-window page throughpage_chat(). Arguments in...are passed topage_chat()instead ofshiny::shinyApp(): useapp_optionsinstead ofoptions,bookmark_storeinstead ofenableBookmarking, and composepage_chat()withchat_server()when you needonStartoruiPattern.chat_app()now owns the page layout, so itstitle,icon, andidconfigure the page-chat shell. Existing layouts that embed chat should usechat_ui()andchat_server()directly. (#329)The
messagesparameter is deprecated in favor of the conversation-history feature:chat_ui(messages = ...)now warns, andchat_app(messages = ...)errors when history is enabled (the default). Usegreetingfor a startup message,chat_append()to replay messages, orhistory = FALSEif you manage state yourself. (#381)CSS classes and custom properties used by the external-link dialog, thinking display, and tool-result images/PDFs now use the
.shiny-chat-*prefix instead of.shinychat-*. Update any custom CSS that targets these identifiers. (#285, #286)
Changes
chat_ui()andpage_chat()no longer show an assistant icon by default. Passicon_assistant = TRUEto restore the built-in robot icon, or supply your own icon as before. (#345)chat_ui()now uses a wider default content width on large displays while preserving its existing width on smaller windows. (#364)The
dismissibleparameter ofchat_greeting()has been renamed topersistentwith an inverted value.dismissible = FALSE(greeting stays visible) is nowpersistent = TRUE. The olddismissibleargument still works but warns. (#260)
Bug fixes
Fixed a security issue where model-authored Markdown could create or escape into shinychat’s raw-HTML islands (
<shiny-chat-raw-html>), potentially injecting arbitrary HTML into the page. As part of this,output_markdown_stream()/markdown_stream()now track which parts of a mixed value are trusted server-rendered HTML versus untrusted text. (#287, #360)A response that fails before it streams anything (e.g. an exhausted quota, an over-long context, or a dropped connection) is now reported in the chat instead of leaving a loading indicator that never resolves and a locked composer. The
chat_server()return also gainedlast_error, a reactive holding the condition from the most recent failed response, since both a finished and a failed response report"idle"instatus. (#304, #314)Fixed
output_markdown_stream()permanently stopping following new content after the user scrolled back to the bottom. (#282)Fixed expanding or collapsing a tool result yanking the chat’s scroll position away from what you were reading. (#348)
chat_app()no longer renders a close button or registers astopApp()observer when deployed to a server. Both are now gated onrlang::is_interactive(), preventing session crashes in multi-user deployments. (#265)Fixed control-only inputs (the close button, cancel button, and greeting-requested signal) leaking into bookmarked state. (#258, #259)
Fixed
set_client()failing when the conversation contains an htmlwidget ortagList(). (#362, #369)Fixed suggestion cards and the greeting overflowing the chat container in narrow spaces such as sidebars. (#255)
Fixed the copy button on code blocks not working in some embedded contexts. (@thisisnic, #247)
shinychat 0.4.0
CRAN release: 2026-06-01
Experimental internal changes
- The chat UI’s rendering layer has been migrated from Lit to React. This significantly improves streaming performance — incoming chunks no longer clear previous DOM state — and makes the codebase more maintainable. One trade-off is that certain Shiny UI elements embedded in chat messages may not work as well as before (e.g., inline
<script>tags are generally not supported inside a React runtime). If you encounter issues, please let us know.
New features and improvements
The chat UI now displays model reasoning/thinking content as collapsible panels above assistant responses. Thinking content streams in real-time with animated topic labels. This works with providers that support structured thinking (e.g., Claude’s extended thinking via
ellmer) and with local models that wrap reasoning in<thinking>tags. (#208)Added
enable_cancelparameter tochat_ui()to show a stop button that lets users cancel an in-progress AI response. Press the stop button or hit Escape to cancel.chat_mod_ui()enables cancellation by default, andchat_mod_server()handles the cancellation wiring automatically, using the stream cancellation features introduced in ellmer v0.4.1. (#221)Markdown lists where every item is a
<span class="suggestion">are now rendered as a grid of clickable suggestion cards. Each suggestion’s text content becomes both the card label and the value sent on click. To add a short heading above the body text, set thetitleattribute on the span — e.g.<span class="suggestion" title="Heading">Body text shown on the card.</span>. Only the body text (not the title) is submitted when the card is clicked. Cards stream in with staggered animations and support keyboard navigation (arrow keys, Home/End) with roving tabindex. (#219)Added
chat_greeting()for creating welcome messages that appear when the chat is empty. Greetings can be set statically viachat_ui(greeting=)or dynamically from the server withchat_set_greeting(). They are automatically dismissed when the user sends their first message. A newgreeting_requestedinput fires when the chat is visible, empty, and has no greeting, enabling LLM-generated welcome messages.chat_mod_server(greeting=)accepts a function for auto-generated greetings. (#217)Tool result cards now render images and PDFs returned by ellmer tools. When a tool returns
content_image_file(),content_image_url(), orcontent_pdf_file(), the result is displayed as an inline image or a PDF filename badge. Mixed content lists (e.g.,list(ContentText("summary"), content_image_file("plot.png"))) are rendered with items interleaved in order. (#225)Added
footerparameter tochat_ui()for displaying arbitrary HTML content below the chat input. Useful for disclaimers, attribution, or interactive toolbars. Styled with sensible defaults and customizable via--shiny-chat-footer-font-sizeand--shiny-chat-footer-colorCSS custom properties. (#224)Tool result cards now support a fullscreen toggle. Set
full_screen = TRUEin thedisplaylist (or setres$full_screen <- NAin a customcontents_shinychat()method) to add a button that expands the card to fill the viewport. PressEscape, click the backdrop, or use the close button to exit fullscreen.Added
footerfield toToolResultDisplayfor displaying custom HTML content below the tool result card body. (#178)chat_mod_server()now returns aset_client(new_client, sync = TRUE)function for swapping the chat client used by the module at runtime. Whensync = TRUE(the default), the new client inherits the current conversation’s turns, system prompt, and tools so the conversation continues seamlessly. If a response is currently streaming, the swap is deferred until the stream completes. (#227)chat_mod_server()now returns astatusreactive that reports the current interaction state:"idle"when no response is in progress, or"streaming"while a response is actively being received. (#227)chat_restore()now invisibly returns a cancel function that tears down all bookmark registrations made by that call. This is useful when swapping the chat client viaset_client(), which handles the re-registration automatically. (#227)
Improvements
- All navigating links in assistant messages now open in a new tab to preserve the app’s session state. Cross-origin links still show the confirmation dialog; same-origin links open directly. (#238)
Bug fixes
Fixed the external link confirmation dialog not rendering in Safari. The backdrop overlay appeared but the dialog content was invisible due to a Bootstrap/
<dialog>CSS interaction. (#201, #238)Fixed pressing Escape to dismiss the external link dialog leaving it in a broken state where subsequent link clicks no longer worked. (#238)
Fixed an issue where user chat messages would display the default assistant icon. (#162)
shinychat 0.3.0
CRAN release: 2025-11-20
Breaking changes
-
chat_mod_server()now returns a list of reactives forlast_inputandlast_turn, as well functions toupdate_user_input(),append()andclear()the chat. (#130, #143, #145)
New features
Added
chat_restore()which adds Shiny bookmarking hooks to save and restore the ellmer chat client. (#28, #82)Added
update_chat_user_input()for programmatically updating the user input of a chat UI element. (#78)shinychat now shows tool call request and results in the UI, and the feature is enabled by default in
chat_app()and the chat module (chat_mod_server()). When usingchat_append()withchat_ui(), setstream = "content"when you call the$stream_async()method on theellmer::Chatclient to ensure tool calls are included in the chat stream output. Learn more in the tool calling UI article. (#52)Added
chat_append(icon=...)andchat_ui(icon_assistant=...)for customizing the icon that appears next to assistant responses. (#88)
Improvements
chat_app()now correctly restores the chat client state when refreshing the app, e.g. by reloading the page. (#71)External links in chat messages in
chat_ui()now open in a new tab by default, with a confirmation dialog. (#120)
Bug fixes
- The chat input no longer submits incomplete text when the user has activated IME completions (e.g. while typing in Japanese or Chinese). (#85)
Internal changes
We consolidated the
<shiny-chat-message>and<shiny-user-message>components into a single<shiny-chat-message>component with adata-roleattribute to indicate whether it’s an “assistant” or “user” message. This likely has minimal impact on your apps, other than custom styles. You should update anyshiny-user-messagerules to useshiny-chat-message[data-role="user"]. (#101)The chat UI’s send input button is now identified by the class
.shiny-chat-btn-send. (@DeepanshKhurana, #138)
shinychat 0.2.0
CRAN release: 2025-05-16
New features and improvements
Added new
output_markdown_stream()andmarkdown_stream()functions to allow for streaming markdown content to the client. This is useful for showing Generative AI responses in real-time in a Shiny app, outside of a chat interface. (#23)Both
chat_ui()andoutput_markdown_stream()now support arbitrary Shiny UI elements inside of messages. This allows for gathering input from the user (e.g.,selectInput()), displaying of rich output (e.g., htmlwidgets like plotly), and more. (#29)Added a new
chat_clear()function to clear the chat of all messages. (#25)Added
chat_app(),chat_mod_ui()andchat_mod_server().chat_app()takes an ellmer chat client and launches a simple Shiny app interface with the chat.chat_mod_ui()andchat_mod_server()replicate the interface as a Shiny module, for easily adding a simple chat interface connected to a specific ellmer chat client. (#36)The promise returned by
chat_append()now resolves to the content streamed into the chat. (#49)
Bug fixes
chat_append(),chat_append_message()andchat_clear()now all work in Shiny modules without needing to namespace theidof the Chat component. (#37)chat_append()now logs and throws a silent error if the stream errors for any reason. This prevents the app from crashing if the stream is interrupted. You can still usepromises::catch()to handle the error in your app code if desired. (#46)