rshinyscrollable

Automatic scrolling in TextOutput


I am building a chatbot in Shiny. And as a chat, all the new messages should appear at the bottom of a textOutput. However when a message is received, the content is generated again, and the textoutput shows the top. I'm not able to make the textOutput to be automatically scrolled to the bottom. This is my code:

UI:

library(shiny)
library(shinyjs)

shinyUI(fluidPage(
  #includeScript("enter.js"),
  tags$script(
    "$( document ).ready(function() {
               $(\".tab-content [type='textOutput']\").on('click', function(){
                  setTimeout(function() {
                    window.scrollTo(0,document.body.scrollHeight);
                }, 200)
               })
             })"
  ),  
  titlePanel("Shiny Bot"),
  sidebarPanel(
    wellPanel(
      fluidRow(
        textInput("texto","Escribe"),
        actionButton("enviar", label = "Enviar")
      )
    ),
    fluidRow(
    wellPanel(
      textOutput("respuesta"),
      tags$head(
        tags$style("#respuesta{color:red;
                              font-size:12px;
                              font-style:italic; 
                              overflow-y:scroll;
                              max-height: 200px;
                              max-width: 300px;
                              white-space: pre-wrap;
                              background: ghostwhite;
                              position:relative}"))
    )
    )
  )
))

Server

library(shiny)
library(openai)

skey='xxxxxxxxxxxxxxxxxxxxxxxx'
initPrompt="Marv is a chatbot that reluctantly answers questions with sarcastic responses:

You: How many pounds are in a kilogram?
Marv: This again? There are 2.2 pounds in a kilogram. Please make a note of this.
You: What does HTML stand for?
Marv: Was Google too busy? Hypertext Markup Language. The T is for try to ask better questions in the future.
You: When did the first airplane fly?
Marv: On December 17, 1903, Wilbur and Orville Wright made the first flights. I wish they’d come and take me away.
You: What is the meaning of life?
Marv: I’m not sure. I’ll ask my friend Google."

chainreq<<-initPrompt

shinyServer(function(input, output, session) {
  
  #textLog <- reactiveVal("")
  
  observeEvent(input$enviar,{
    updateTextInput(session,inputId = "texto", value = "")

    chainreq<<-paste0(chainreq,"\nYou: ",input$texto)
    
    response<-create_completion(
     engine_id = "text-davinci-002",
     prompt = chainreq,
     openai_api_key=skey,
     temperature = 0.5,
     max_tokens = 60,
     top_p = 0.3,
     frequency_penalty = 0.5,
     presence_penalty = 0
    )    
  

    chainreq<<-paste0(chainreq,'\n',gsub("[\r\n]", "", response$choices$text))
    
    output$respuesta=renderText(chainreq)

  })
})

This is how it appears, and in blue what I want.

enter image description here

Can you help me?


Solution

  • You need to watch for the output recalculated event instead. This event is fired when new messages are sent from R to JS, read more events here. However, it takes JS some time to render into HTML. So we still need to wait a small amount of time. Unfortunately, Shiny does not give us an event when the new thing is finally on HTML, we can use a setTimeout to guess.

            $(document).ready(function() {
                var el = $('#respuesta');
                el.on('shiny:recalculated', function(e){
                    setTimeout(function() {
                         el.scrollTop(el.prop('scrollHeight'));
                    }, 200)
                });
            });
    

    I don't have an access key, faked your API call below.

    library(openai)
    library(shiny)
    
    skey='xxxxxxxxxxxxxxxxxxxxxxxx'
    initPrompt="Marv is a chatbot that reluctantly answers questions with sarcastic responses:
    
    You: How many pounds are in a kilogram?
    Marv: This again? There are 2.2 pounds in a kilogram. Please make a note of this.
    You: What does HTML stand for?
    Marv: Was Google too busy? Hypertext Markup Language. The T is for try to ask better questions in the future.
    You: When did the first airplane fly?
    Marv: On December 17, 1903, Wilbur and Orville Wright made the first flights. I wish they’d come and take me away.
    You: What is the meaning of life?
    Marv: I’m not sure. I’ll ask my friend Google."
    
    chainreq<<-initPrompt
    
    ui <- fluidPage(
        #includeScript("enter.js"),
        tags$script(
            "
            $(document).ready(function() {
                var el = $('#respuesta');
                el.on('shiny:recalculated', function(e){
                    setTimeout(function() {
                         el.scrollTop(el.prop('scrollHeight'));
                    }, 200)
                });
            });
            "
        ),  
        titlePanel("Shiny Bot"),
        sidebarPanel(
            wellPanel(
                fluidRow(
                    textInput("texto","Escribe"),
                    actionButton("enviar", label = "Enviar")
                )
            ),
            fluidRow(
                wellPanel(
                    textOutput("respuesta"),
                    tags$head(
                        tags$style("#respuesta{color:red;
                                  font-size:12px;
                                  font-style:italic; 
                                  overflow-y:scroll;
                                  max-height: 200px;
                                  max-width: 300px;
                                  white-space: pre-wrap;
                                  background: ghostwhite;
                                  position:relative}"))
                )
            )
        )
    )
    
    server <- function(input, output, session) {
        
        #textLog <- reactiveVal("")
        
        observeEvent(input$enviar,{
            updateTextInput(session,inputId = "texto", value = "")
            
            chainreq<<-paste0(chainreq,"\nYou: ",input$texto)
            
            # response<-create_completion(
            #     engine_id = "text-davinci-002",
            #     prompt = chainreq,
            #     openai_api_key=skey,
            #     temperature = 0.5,
            #     max_tokens = 60,
            #     top_p = 0.3,
            #     frequency_penalty = 0.5,
            #     presence_penalty = 0
            # )    
            Sys.sleep(sample(1:9, 1)*0.2) # randomly sleep 0-2s 
            response <- list(choices = list(text = initPrompt)) # fake responds
            
            chainreq<<-paste0(chainreq,'\n',gsub("[\r\n]", "", response$choices$text))
            
            output$respuesta=renderText(chainreq)
            
        })
    }
    
    shinyApp(ui, server)
    

    enter image description here