Skip to content

Commit 10ce8e6

Browse files
authored
v0.2.0 (#44)
* Increment version number to 0.2.0 * chore: Add update js assets to release bullets * chore: polish news * chore: revdepcheck::cloud_check() * chore: update cran comments * docs: update pkgdown site * chore: update shinychat.js * feat: Add shinychat hex logo * chore: update logo * feat: Use silent error in `chat_append()` to avoid crashing app (#46) * feat: Don't crash app with an error in `chat_append_stream()` * feat: Improve formatting of error message in chat * chore: Update news item * chore: remove unused code * fix: Avoid unhandled promise error, but still throw silent error * chore: update comment * tests(chat_append_stream): Handles errors in the stream * chore: Suggest {later} * feat(chat_append): Return stream results as promise value (#45) * feat(chat_append): Return stream results as value of promise * chore: more robust to future ellmer changes * docs: Add news item * chore: Use fastmap * tests(chat_append_stream): Promise resolves to stream contents * docs(chat_append): Document resolved promise value * chore: restore code changes * avoid warning about fastmap * rename: test-markdown-stream.R * chore: organize reference index * chore: Add Posit ROR id * ci: Run r-cmd-check on all pull requests * chore: Update shinychat.js assets * chore: Use branch with fix * tests: update snaps * chore: Garrick as maintainer
1 parent 13296ac commit 10ce8e6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+544
-164
lines changed

.Rbuildignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@
1010
^_dev$
1111
^cran-comments\.md$
1212
^CRAN-SUBMISSION$
13+
^revdep$

.github/workflows/R-CMD-check.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ on:
44
push:
55
branches: [main, master]
66
pull_request:
7-
branches: [main, master]
87

98
name: R-CMD-check.yaml
109

DESCRIPTION

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
Package: shinychat
22
Title: Chat UI Component for 'shiny'
3-
Version: 0.1.1.9001
3+
Version: 0.2.0
44
Authors@R: c(
5-
person("Joe", "Cheng", , "[email protected]", role = c("aut", "cre")),
5+
person("Joe", "Cheng", , "[email protected]", role = "aut"),
66
person("Carson", "Sievert", , "[email protected]", role = "aut"),
7-
person("Garrick", "Aden-Buie", , "[email protected]", role = "aut",
7+
person("Garrick", "Aden-Buie", , "[email protected]", role = c("aut", "cre"),
88
comment = c(ORCID = "0000-0002-7111-0077")),
9-
person("Posit Software, PBC", role = c("cph", "fnd"))
9+
person("Posit Software, PBC", role = c("cph", "fnd"),
10+
comment = c(ROR = "03wc8by49"))
1011
)
1112
Description: Provides a scrolling chat interface with multiline input,
1213
suitable for creating chatbot apps based on Large Language Models
@@ -20,12 +21,14 @@ Imports:
2021
bslib,
2122
coro,
2223
ellmer,
24+
fastmap,
2325
htmltools,
2426
jsonlite,
2527
promises (>= 1.3.2),
2628
rlang,
2729
shiny (>= 1.10.0)
2830
Suggests:
31+
later,
2932
testthat (>= 3.0.0)
3033
Config/Needs/website: tidyverse/tidytemplate
3134
Config/testthat/edition: 3

NEWS.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
1-
# shinychat (development version)
1+
# shinychat 0.2.0
2+
3+
## New features and improvements
24

35
* Added new `output_markdown_stream()` and `markdown_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)
46

5-
* Both `chat_ui()` and `output_markdown_stream()` now support arbirary 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. (#1868)
7+
* Both `chat_ui()` and `output_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)
68

79
* Added a new `chat_clear()` function to clear the chat of all messages. (#25)
810

11+
* Added `chat_app()`, `chat_mod_ui()` and `chat_mod_server()`. `chat_app()` takes an `ellmer::Chat` client and launches a simple Shiny app interface with the chat. `chat_mod_ui()` and `chat_mod_server()` replicate the interface as a Shiny module, for easily adding a simple chat interface connected to a specific `ellmer::Chat` client. (#36)
12+
13+
* The promise returned by `chat_append()` now resolves to the content streamed into the chat. (#49)
14+
15+
## Bug fixes
16+
917
* `chat_append()`, `chat_append_message()` and `chat_clear()` now all work in Shiny modules without needing to namespace the `id` of the Chat component. (#37)
1018

11-
* Added `chat_app()`, `chat_mod_ui()` and `chat_mod_server()`. `chat_app()` takes an `ellmer::Chat` client and launches a simple Shiny app interface with the chat. `chat_mod_ui()` and `chat_mod_server()` replicate the interface as a Shiny module, for easily adding a simple chat interface connected to a specific `ellmer::Chat` client. (#36)
19+
* `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 use `promises::catch()` to handle the error in your app code if desired. (#46)
1220

1321
# shinychat 0.1.1
1422

R/chat.R

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,11 @@ chat_deps <- function() {
1212
src = "lib/shiny",
1313
script = list(
1414
list(src = "chat/chat.js", type = "module"),
15-
list(src = "markdown-stream/markdown-stream.js", type = "module"),
16-
list(src = "text-area/textarea-autoresize.js", type = "module")
15+
list(src = "markdown-stream/markdown-stream.js", type = "module")
1716
),
1817
stylesheet = c(
1918
"chat/chat.css",
20-
"markdown-stream/markdown-stream.css",
21-
"text-area/textarea-autoresize.css"
19+
"markdown-stream/markdown-stream.css"
2220
)
2321
)
2422
}
@@ -197,11 +195,12 @@ chat_ui <- function(
197195
#' @param role The role of the message (either "assistant" or "user"). Defaults
198196
#' to "assistant".
199197
#' @param session The Shiny session object
200-
#' @returns Returns a promise. This promise resolves when the message has been
201-
#' successfully sent to the client; note that it does not guarantee that the
202-
#' message was actually received or rendered by the client. The promise
203-
#' rejects if an error occurs while processing the response (see the "Error
204-
#' handling" section).
198+
#'
199+
#' @returns Returns a promise that resolves to the contents of the stream, or an
200+
#' error. This promise resolves when the message has been successfully sent to
201+
#' the client; note that it does not guarantee that the message was actually
202+
#' received or rendered by the client. The promise rejects if an error occurs
203+
#' while processing the response (see the "Error handling" section).
205204
#'
206205
#' @examplesIf interactive()
207206
#' library(shiny)
@@ -409,26 +408,38 @@ chat_append_stream <- function(
409408
) {
410409
result <- chat_append_stream_impl(id, stream, role, session)
411410
# Handle erroneous result...
411+
result <- promises::catch(result, function(reason) {
412+
# ...but rethrow the error as a silent error, so the caller can also handle
413+
# it if they want, but it won't bring down the app.
414+
class(reason) <- c("shiny.silent.error", class(reason))
415+
cnd_signal(reason)
416+
})
417+
412418
promises::catch(result, function(reason) {
413419
chat_append_message(
414420
id,
415421
list(
416422
role = role,
417-
content = paste0(
418-
"\n\n**An error occurred:** ",
419-
conditionMessage(reason)
420-
)
423+
content = sanitized_chat_error(reason)
421424
),
422425
chunk = "end",
423426
operation = "append",
424427
session = session
425428
)
429+
rlang::warn(
430+
sprintf(
431+
"ERROR: An error occurred in `chat_append_stream(id=\"%s\")`",
432+
session$ns(id)
433+
),
434+
parent = reason
435+
)
426436
})
427-
# ...but also return it, so the caller can also handle it if they want. Note
428-
# that we're not returning the result of `promises::catch`; we want to return
429-
# a rejected promise (so the caller can see the error) that was already
430-
# handled (so there's no "unhandled promise error" warning if the caller
431-
# chooses not to do anything with it).
437+
438+
# Note that we're not returning the result of `promises::catch()`, because we
439+
# want to return a rejected promise so the caller can see the error. But we
440+
# use the `catch()` both to make the error visible to the user *and* to ensure
441+
# there's no "unhandled promise error" warning if the caller chooses not to do
442+
# anything with it.
432443
result
433444
}
434445

@@ -448,13 +459,19 @@ rlang::on_load(
448459
chunk = "start",
449460
session = session
450461
)
462+
463+
res <- fastmap::fastqueue(200)
464+
451465
for (msg in stream) {
452466
if (promises::is.promising(msg)) {
453467
msg <- await(msg)
454468
}
455469
if (coro::is_exhausted(msg)) {
456470
break
457471
}
472+
473+
res$add(msg)
474+
458475
chat_append_message(
459476
id,
460477
list(role = role, content = msg),
@@ -463,13 +480,21 @@ rlang::on_load(
463480
session = session
464481
)
465482
}
483+
466484
chat_append_message(
467485
id,
468486
list(role = role, content = ""),
469487
chunk = "end",
470488
operation = "append",
471489
session = session
472490
)
491+
492+
res <- res$as_list()
493+
if (every(res, is.character)) {
494+
paste(unlist(res), collapse = "")
495+
} else {
496+
res
497+
}
473498
})
474499
)
475500

R/chat_app.R

Lines changed: 0 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -157,68 +157,10 @@ chat_mod_server <- function(id, client) {
157157
)
158158
})
159159

160-
shiny::observe({
161-
if (append_stream_task$status() == "error") {
162-
tryCatch(
163-
append_stream_task$result(),
164-
error = notify_error("chat", session = session, module_id = id)
165-
)
166-
}
167-
})
168-
169160
shiny::reactive({
170161
if (append_stream_task$status() == "success") {
171162
client$last_turn()
172163
}
173164
})
174165
})
175166
}
176-
177-
notify_error <- function(
178-
id,
179-
module_id,
180-
session = shiny::getDefaultReactiveDomain()
181-
) {
182-
function(err) {
183-
rlang::warn(
184-
sprintf(
185-
"ERROR: An error occurred in `chat_mod_server(id=\"%s\")`",
186-
module_id
187-
),
188-
parent = err
189-
)
190-
191-
needs_sanitized <-
192-
isTRUE(getOption("shiny.sanitize.errors")) &&
193-
!inherits(err, "shiny.custom.error")
194-
if (needs_sanitized) {
195-
msg <- "**An error occurred.** Please try again or contact the app author."
196-
} else {
197-
msg <- sprintf(
198-
"**An error occurred:**\n\n```\n%s\n```",
199-
strip_ansi(conditionMessage(err))
200-
)
201-
}
202-
203-
chat_append_message(
204-
id,
205-
msg = list(role = "assistant", content = msg),
206-
chunk = TRUE,
207-
operation = "append",
208-
session = session
209-
)
210-
chat_append_message(
211-
id,
212-
list(role = "assistant", content = ""),
213-
chunk = "end",
214-
operation = "append",
215-
session = session
216-
)
217-
}
218-
}
219-
220-
strip_ansi <- function(text) {
221-
# Matches codes like "\x1B[31;43m", "\x1B[1;3;4m"
222-
ansi_pattern <- "(\x1B|\x033)\\[[0-9;?=<>]*[@-~]"
223-
gsub(ansi_pattern, "", text)
224-
}

R/shinychat-package.R

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,11 @@ NULL
1010

1111
ignore_unused_imports <- function() {
1212
jsonlite::fromJSON
13+
fastmap::fastqueue
14+
}
15+
16+
release_bullets <- function() {
17+
c(
18+
"Check that shinychat js assets are up-to-date (`scripts/update-chat.sh`)"
19+
)
1320
}

R/utils.R

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
sanitized_chat_error <- function(err) {
2+
needs_sanitized <-
3+
isTRUE(getOption("shiny.sanitize.errors")) &&
4+
!inherits(err, "shiny.custom.error")
5+
6+
if (needs_sanitized) {
7+
"\n\n**An error occurred.** Please try again or contact the app author."
8+
} else {
9+
sprintf(
10+
"\n\n**An error occurred:**\n\n```\n%s\n```",
11+
strip_ansi(conditionMessage(err))
12+
)
13+
}
14+
}
15+
16+
strip_ansi <- function(text) {
17+
# Matches codes like "\x1B[31;43m", "\x1B[1;3;4m"
18+
ansi_pattern <- "(\x1B|\x033)\\[[0-9;?=<>]*[@-~]"
19+
gsub(ansi_pattern, "", text)
20+
}

_pkgdown.yml

Lines changed: 0 additions & 4 deletions
This file was deleted.

cran-comments.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
## revdepcheck results
2+
3+
We checked 2 reverse dependencies, comparing R CMD check results across CRAN and dev versions of this package.
4+
5+
* We saw 0 new problems
6+
* We failed to check 0 packages

0 commit comments

Comments
 (0)