library(shiny)
library(bslib)
library(shinychat)
library(coro)
# Simulate a long streaming response with "tool call" outputs
fake_stream <- async_generator(function(input) {
yield("I'll run several analyses for you.\n\n")
await(async_sleep(0.2))
for (i in 1:8) {
yield(sprintf("**Running tool %d: analyze_data**\n\n", i))
await(async_sleep(0.05))
# Large code block (simulates tool output)
code <- paste0("```r\n# Results for dataset ", i, "\n",
paste(sprintf("row_%02d <- c(%s)\n", 1:40,
sapply(1:40, function(x) paste(sample(1:99, 8), collapse = ", "))),
collapse = ""), "```\n\n")
for (chunk in strsplit(code, "(?<=.{50})", perl = TRUE)[[1]]) {
yield(chunk)
await(async_sleep(0.008))
}
# Markdown table
table <- paste0(
"| Metric | Value | SE | CI Low | CI High | p |\n",
"|--------|-------|-----|--------|---------|------|\n",
paste(sprintf("| %s | %.2f | %.2f | %.2f | %.2f | %.4f |\n",
c("Mean","Median","SD","Var","Skew","Kurt","Min","Max","Range","IQR"),
runif(10,0,100), runif(10,0,5), runif(10,-5,0),
runif(10,0,5), runif(10,0,0.1)), collapse = ""), "\n")
for (char in strsplit(table, "")[[1]]) {
yield(char)
await(async_sleep(0.003))
}
}
yield("All analyses complete!")
})
ui <- page_fillable(
p("Send any message to trigger a long streaming response."),
chat_ui("chat", fill = TRUE)
)
server <- function(input, output, session) {
observeEvent(input$chat_user_input, {
chat_append("chat", fake_stream(input$chat_user_input))
})
}
shinyApp(ui, server)
When streaming long markdown content, the browser can freeze as markdown is re-parsed on every incoming chunk. This is especially noticeable when using LLMs with tool calls, where each tool result can add substantial content to the stream.
Reprex
The browser becomes increasingly unresponsive as the response grows, eventually freezing for several seconds. In extreme cases, it completely freezes that app.
Proposed solution
I have a local fix that implements incremental markdown rendering. It uses block-based rendering and throttling based on content size.
Would a PR be welcome? If so, should I only modify the
.tsor should I also build and copy to both packages?Abbreviated Session info