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
I sadly dont have enough reputation to post a comment.
I think the most important part is this:
which you covered I think.
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/#watchYou can try my way:
I use it in my project to scroll screen when i want.