Skip to content

Commit

Permalink
render streaming message. close MichelNivard#85
Browse files Browse the repository at this point in the history
  • Loading branch information
calderonsamuel committed May 15, 2023
1 parent c518980 commit 3311a93
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 18 deletions.
48 changes: 34 additions & 14 deletions R/mod_chat.R
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ mod_chat_ui <- function(id) {
div(
class = "p-2 mh-100 overflow-auto",
welcomeMessageOutput(ns("welcome")),
shiny::uiOutput(ns("history"))
shiny::uiOutput(ns("history")),
streamingMessageOutput(ns("streaming"))
),
div(
class = "mt-auto",
Expand All @@ -34,30 +35,44 @@ mod_chat_ui <- function(id) {
mod_chat_server <- function(id, ide_colors = get_ide_theme_info()) {
moduleServer(id, function(input, output, session) {

rv <- reactiveValues()
rv$stream_ended <- 0L

waiter_color <-
if (ide_colors$is_dark) "rgba(255,255,255,0.5)" else "rgba(0,0,0,0.5)"

prompt <- mod_prompt_server("prompt", ide_colors)
prompt <- mod_prompt_server("prompt")

output$welcome <- renderWelcomeMessage({
welcomeMessage()
}) |>
welcomeMessage(ide_colors)
}) %>%
bindEvent(prompt$clear_history)


output$streaming <- renderStreamingMessage({
# This has display: none by default. It is inly shown when receiving an stream
# After the stream is completed it will reset.
streamingMessage(ide_colors)
}) %>%
bindEvent(rv$stream_ended)


output$history <- shiny::renderUI({
prompt$chat_history %>%
style_chat_history(ide_colors = ide_colors)
})
}) |>
bindEvent(prompt$chat_history, prompt$clear_history)


shiny::observe({

waiter::waiter_show(
html = shiny::tagList(waiter::spin_flower(),
shiny::h3("Asking ChatGPT...")),
color = waiter_color
)
# waiter::waiter_show(
# html = shiny::tagList(waiter::spin_flower(),
# shiny::h3("Asking ChatGPT...")),
# color = waiter_color
# )

stream_handler <- StreamHandler$new()
stream_handler <- StreamHandler$new(session = session)

stream_chat_completion(
prompt = prompt$input_prompt,
Expand All @@ -73,7 +88,9 @@ mod_chat_server <- function(id, ide_colors = get_ide_theme_info()) {
content = stream_handler$current_value
)

waiter::waiter_hide()
rv$stream_ended <- rv$stream_ended + 1L

# waiter::waiter_hide()
}) %>%
shiny::bindEvent(prompt$start_stream, ignoreInit = TRUE)

Expand Down Expand Up @@ -145,8 +162,11 @@ style_chat_message <- function(message, ide_colors = get_ide_theme_info()) {
`background-color` = colors$bg_color
),
fontawesome::fa(icon_name),
htmltools::tagList(
shiny::markdown(message$content)
htmltools::tags$div(
class = "message-wrapper",
htmltools::tagList(
shiny::markdown(message$content)
)
)
)
)
Expand Down
9 changes: 7 additions & 2 deletions R/mod_prompt.R
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ mod_prompt_ui <- function(id) {
#' @inheritParams run_chatgpt_app
#'
#' @return A shiny server
mod_prompt_server <- function(id, ide_colors = get_ide_theme_info()) {
mod_prompt_server <- function(id) {
moduleServer(id, function(input, output, session) {

rv <- reactiveValues()
Expand All @@ -88,8 +88,13 @@ mod_prompt_server <- function(id, ide_colors = get_ide_theme_info()) {
rv$input_skill <- input$skill

shiny::updateTextAreaInput(session, "chat_input", value = "")
}, priority = 1000) %>%
shiny::bindEvent(input$chat)


shiny::observe({
rv$start_stream <- rv$start_stream + 1L
}) %>%
}, priority = -10) %>%
shiny::bindEvent(input$chat)


Expand Down
55 changes: 55 additions & 0 deletions R/streamingMessage.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#' Streaming message
#'
#' Places an invisible empty chat message that will hold a streaming message.
#' It can be resetted dynamically inside a shiny app
#'
#' @import htmlwidgets
#'
#' @export
streamingMessage <- function(ide_colors = get_ide_theme_info(), width = NULL, height = NULL, elementId = NULL) {

message <- list(role = "assistant", content = "")

# forward options using x
x = list(
message = style_chat_message(message, ide_colors = ide_colors) %>% as.character()
)

# create widget
htmlwidgets::createWidget(
name = 'streamingMessage',
x,
width = width,
height = height,
package = 'gptstudio',
elementId = elementId
)
}

#' Shiny bindings for streamingMessage
#'
#' Output and render functions for using streamingMessage within Shiny
#' applications and interactive Rmd documents.
#'
#' @param outputId output variable to read from
#' @param width,height Must be a valid CSS unit (like \code{'100\%'},
#' \code{'400px'}, \code{'auto'}) or a number, which will be coerced to a
#' string and have \code{'px'} appended.
#' @param expr An expression that generates a streamingMessage
#' @param env The environment in which to evaluate \code{expr}.
#' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This
#' is useful if you want to save an expression in a variable.
#'
#' @name streamingMessage-shiny
#'
#' @export
streamingMessageOutput <- function(outputId, width = '100%', height = NULL){
htmlwidgets::shinyWidgetOutput(outputId, 'streamingMessage', width, height, package = 'gptstudio')
}

#' @rdname streamingMessage-shiny
#' @export
renderStreamingMessage <- function(expr, env = parent.frame(), quoted = FALSE) {
if (!quoted) { expr <- substitute(expr) } # force quoted
htmlwidgets::shinyRenderWidget(expr, streamingMessageOutput, env, quoted = TRUE)
}
4 changes: 2 additions & 2 deletions R/welcomeMessage.R
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
#' This has been created to be able to bind the message to a shiny event to trigger a new render.
#'
#' @import htmlwidgets
welcomeMessage <- function(width = NULL, height = NULL, elementId = NULL) {
welcomeMessage <- function(ide_colors = get_ide_theme_info(), width = NULL, height = NULL, elementId = NULL) {

default_message <- chat_message_default()

# forward options using x
x = list(
message = style_chat_message(default_message) |> as.character()
message = style_chat_message(default_message, ide_colors = ide_colors) %>% as.character()
)

# create widget
Expand Down
43 changes: 43 additions & 0 deletions inst/htmlwidgets/streamingMessage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
HTMLWidgets.widget({

name: 'streamingMessage',

type: 'output',

factory: function(el, width, height) {

// TODO: define shared variables for this instance

return {

renderValue: function(x) {

// TODO: code to render the widget, e.g.
el.innerHTML = x.message;
el.classList.add("d-none"); // to start hidden
el.classList.add("streaming-message"); // to be captured in a message handler

},

resize: function(width, height) {

// TODO: code to re-render the widget with a new size

}

};
}
});

// This is independent from the HTMLwidget code.
// It will only run inside projects with the shiny JS bindings (aka shiny apps).
Shiny.addCustomMessageHandler(
type = 'render-stream', function(message) {
const $el = $('.streaming-message')
$el.removeClass('d-none')

const $messageWrapper = $el.find('.message-wrapper')
$messageWrapper.html($.parseHTML(message))

});

7 changes: 7 additions & 0 deletions inst/htmlwidgets/streamingMessage.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# (uncomment to add a dependency)
# dependencies:
# - name:
# version:
# src:
# script:
# stylesheet:

0 comments on commit 3311a93

Please sign in to comment.