skip to Main Content

I am creating a chatbot app which streams content from a REST API and it works perfectly fine, but the scroll to bottom is not doing what is expected, a scrollbar is generated and I have to manually scroll to see the answer streaming.

 async sendMsg() {
      const messageToSend = this.newMessage.trim();
      this.newMessage = '';
  
      const botName = "Prometheus";
      const usermessagePayload = {
          content: messageToSend,
          sender: 'user',
          receiver: botName,
          time: util.formatDate.format(new Date(), 'hh:mm:ss'),
          sources: []
      };
  
      this.$store.commit('SEND_MESSAGE', usermessagePayload);
  
      try {
          const path = `https://<url>/privategptstream?prompt=${messageToSend}`;
          console.log("Sending message:", messageToSend);

          const response = await fetch(path);
          const reader = response.body.getReader();

          const initialanswerPayload = {
            content: '',
            sender: botName,
            receiver: 'user',
            time: util.formatDate.format(new Date(), 'hh:mm:ss'),
            isNewMessage: true
          }
          
          this.$store.commit('APPEND_MESSAGE_CHUNK', initialanswerPayload);
          
          while (true) {
            const { done, value } = await reader.read();
            if (done) break;
      
            const chunk = new TextDecoder("utf-8").decode(value);
            const answerPayload = {
              content: chunk,
              sender: botName,
              receiver: 'user',
              time: util.formatDate.format(new Date(), 'hh:mm:ss'),
              isNewMessage: false
            }
    
            // Append the chunk to the last message in the conversation
            this.$store.commit('APPEND_MESSAGE_CHUNK', answerPayload);
            this.$nextTick(() => {
              setTimeout(() => {
                this.scrollToBottom();
              }, 100); // Adjust the delay as needed
            });
          }
  
          console.log("Streaming complete");
      } catch (error) {
          console.error("Error streaming the answer:", error);
          this.showSnackbar('Failed to get an answer from the server.');
      }
    },

    scrollToBottom() {
      const chatContainer = this.$refs.chatContainer;
      chatContainer.scrollTop = chatContainer.scrollHeight;
      console.log("chatcontainer scrollTop:" + chatContainer.scrollTop );
      console.log("chatcontainer scrollHeight:" + chatContainer.scrollHeight );

    },


<template>
  <!-- Main container for the entire chat interface -->
  <v-container>
    <!-- Container for displaying chat messages -->
    <v-row class="messages-container" >
      <v-col cols="14">
        <!-- Styling for the messages -->
        <div class="pa-4 messages" ref="chatContainer" >
          <!-- Loop through messages in reverse order to display newest at the bottom -->
          <div v-for="(message, index) in reversedChats" :key="index" class="my-2 d-flex flex-column">
            <!-- Conditional styling for user and bot messages -->
            <div :class="['message-chip', message.sender === 'user' ? 'question' : 'response']">
              <!-- Display avatar for user message -->
              <img v-if="message.sender === 'user'" src="../../assets/user.png" class="avatar user-avatar">
              <!-- Display avatar for bot response -->
              <img v-if="message.sender !== 'user'" src="../../assets/bot.png" class="avatar bot-avatar">
              <!-- Display message content -->
              <pre v-if="message.sender === 'user'">{{ message.content }}</pre>
              <div v-else>
                <!-- Display image if message is an image -->
                <pre v-if="!message.isImage">{{ message.content }}</pre>
                <img v-else :src="message.content" alt="Generated Image" class="generated-image">
                <!-- Display source buttons if sources are available -->
                <div v-if="message.sources && message.sources.length" class="source-buttons">
                  <!-- Loop through sources and display buttons for each -->
                  <v-btn
                    v-for="(source, sIndex) in message.sources"
                    :key="`source-${sIndex}`"
                    small
                    class="source-button"
                    @click="sourceAction(source.url)"
                  >
                    {{ source.title }}
                  </v-btn>
                </div>
              </div>
            </div>
          </div>
        </div>
      </v-col>
    </v-row>

    <!-- Container for user input and predefined options -->
    <v-row class="input-row" no-gutters>
      <v-col cols="12">
        <!-- Display bot description before user input -->
        <div class="bot-description"  v-if="showPredefinedCards">
          <img src="../../assets/logo.png" alt="Logo" class="e61-logo">
          <!-- Bot introduction message -->
          <p>This bot can help you with a variety of tasks, such as answering questions, providing information, and more.</p>
          <p>Try one of the options below and type in your question or make use of the predefined prompts.</p>
        </div>
        <!-- Display predefined text options if available -->
        <div class="predefined-text-cards" v-if="showPredefinedCards">
          <v-row>
            <!-- Loop through predefined text options and display as cards -->
            <v-col cols="12" sm="3" v-for="(item, index) in actionButtons" :key="index">
              <v-card outlined @click="selectCard(index)" :class="{ 'selected': selectedCard === index }" class="pa-2">
                <v-card-title class="justify-center">
                  <!-- Display icon and title for each predefined option -->
                  <v-icon left>{{ item.icon }}</v-icon>
                  {{ item.title }}
                </v-card-title>
                <!-- Display summary text for each predefined option -->
                <v-card-text>{{ item.summary }}</v-card-text>
              </v-card>
            </v-col>
          </v-row>
        </div>
        <!-- Form and other elements remain unchanged -->
      </v-col>
      <v-col cols="12">
        <!-- Display predefined prompts based on selected card -->
        <v-row class="predefined-prompts-container" justify="center" v-if="selectedCard !==null">
          <!-- Loop through predefined prompts and display as buttons -->
          <v-btn small outlined v-for="(prompt, index) in predefinedPrompts" :key="index" @click="setPredefinedText(prompt.prompt)" class="ma-1">
            {{ prompt.text }}
          </v-btn>
        </v-row>
        <!-- Form for user input -->
        <v-form @submit.prevent="sendMsg" class="d-flex align-center" v-if="selectedCard !==null">
          <!-- Button to clear conversation -->
          <v-btn
              :class="{'expand-btn': hoverClearButton}" 
              icon 
              @click="clearMessages"
              @mouseover="hoverClearButton = true"
              @mouseleave="hoverClearButton = false" 
              class="my-auto">
              <v-icon v-if="!hoverClearButton">mdi-delete</v-icon>
              <span v-if="hoverClearButton">Clear Conversation</span>
          </v-btn>
          
          <!-- Textarea for user input -->
          <v-textarea
            outlined
            counter
            label="Ask me anything..."
            auto-grow
            :rows="1"
            :rules="rules"
            v-model="newMessage"
            class="mr-2 flex-grow-1"
            no-resize
            @keyup.enter.prevent="sendMsg"
          ></v-textarea>
          <!-- Button to send user message -->
          <v-btn type="submit" color="primary" class="my-auto" icon>
            <v-icon>mdi-send</v-icon>
          </v-btn>
        </v-form>
      </v-col>
    </v-row>
    <!-- Snackbar for displaying messages -->
    <v-snackbar v-model="snackbar.show">
      {{ snackbar.message }}
      <!-- Button to close snackbar -->
      <v-btn color="red" text @click="snackbar.show = false">Close</v-btn>
    </v-snackbar>
  </v-container>
</template>

<script src="./ChatList.js"></script>
<style src="./ChatList.css" scoped></style>

2

Answers


  1. I sadly dont have enough reputation to post a comment.

    I think the most important part is this:

      var elem = document.getElementById('data');
      elem.scrollTop = elem.scrollHeight;
    

    which you covered I think.

        scrollToBottom() {
          const chatContainer = this.$refs.chatContainer;
          chatContainer.scrollTop = chatContainer.scrollHeight;
          console.log("chatcontainer scrollTop:" + chatContainer.scrollTop );
          console.log("chatcontainer scrollHeight:" + chatContainer.scrollHeight );
    
        }
    

    is this function triggered? What does it say?

    Normally I’d think you need to use a watch function and then fire scrollToBottom. You seem to be using vuex. There is documentation for watch: https://vuex.vuejs.org/api/#watch

    Login or Signup to reply.
  2. You can try my way:

    setTimeout(() => {
       window.scrollTo({ left: 0, top: document.body.scrollHeight, behavior: "smooth" });
    }, 100);
    

    I use it in my project to scroll screen when i want.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search