add 'typing' indicator to opey messages

This commit is contained in:
Nemo Godebski-Pedersen 2025-03-26 18:09:21 +00:00
parent 0504ff206e
commit b192085aa3
4 changed files with 68 additions and 6 deletions

View File

@ -28,7 +28,11 @@ export default {
message: {
type: Object,
required: true
}
},
loading: {
type: Boolean,
default: false
},
},
data() {
return {
@ -93,7 +97,16 @@ export default {
</div>
<div class="message-container">
<div class="content" v-html="renderMarkdown(message.content)"></div>
<div v-if="!loading" class="content" v-html="renderMarkdown(message.content)"></div>
<div v-else class="content">
<div class="ticontainer">
<div class="tiblock">
<div class="tidot"></div>
<div class="tidot"></div>
<div class="tidot"></div>
</div>
</div>
</div>
</div>
<div v-if="message.error" class="error"><el-icon><Warning /></el-icon> {{ message.error }}</div>
</div>
@ -167,4 +180,46 @@ export default {
margin-top: -10px;
margin-bottom: -10px;
}
/* for the loading indicator */
.tiblock {
align-items: center;
display: flex;
height: 17px;
}
.ticontainer .tidot {
background-color: #90949c;
}
.tidot {
animation: mercuryTypingAnimation 1.5s infinite ease-in-out;
border-radius: 2px;
display: inline-block;
height: 4px;
margin-right: 2px;
width: 4px;
}
@keyframes mercuryTypingAnimation{
0%{
-webkit-transform:translateY(0px)
}
28%{
-webkit-transform:translateY(-5px)
}
44%{
-webkit-transform:translateY(0px)
}
}
.tidot:nth-child(1) {
animation-delay:200ms;
}
.tidot:nth-child(2){
animation-delay:300ms;
}
.tidot:nth-child(3){
animation-delay:400ms;
}
</style>

View File

@ -120,7 +120,7 @@ export default {
</div>
<div v-else class="messages-container" v-bind:class="{ disabled: !chat.userIsAuthenticated }">
<el-scrollbar>
<ChatMessage v-for="message in chat.messages" :key="message.id" :message="message" />
<ChatMessage v-for="message in chat.messages" :key="message.id" :message="message" :loading="message.loading" />
</el-scrollbar>
</div>
</el-main>

View File

@ -50,6 +50,7 @@ export interface OpeyMessage {
role: "assistant" | "user" | "tool";
content: string;
error?: string;
loading?: boolean;
}
export interface UserMessage extends OpeyMessage {

View File

@ -44,7 +44,7 @@ export const useChat = defineStore('chat', {
role: 'assistant',
id: '',
} as AssistantMessage,
status: 'ready' as 'ready' | 'streaming' | 'error' | 'loading',
status: 'ready' as 'ready' | 'streaming' | 'error',
userIsAuthenticated: false,
threadId: '',
}
@ -172,10 +172,13 @@ export const useChat = defineStore('chat', {
content: '',
role: 'assistant',
id: uuidv4(),
toolCalls: []
toolCalls: [],
loading: true,
}
this.addMessage(this.currentAssistantMessage)
// Set the status to 'loading' before we fetch the stream
// Handle stream
try {
const response = await fetch('/api/opey/stream', {
@ -213,6 +216,7 @@ export const useChat = defineStore('chat', {
},
async _processOpeyStream(stream: ReadableStream<Uint8Array>): Promise<void> {
this.status = 'streaming'
const reader = stream.getReader();
let decoder = new TextDecoder();
@ -234,7 +238,7 @@ export const useChat = defineStore('chat', {
for (const line of lines) {
if (line.startsWith('data: ') && line !== 'data: [DONE]') {
try {
let data;
const jsonStr = line.substring(6); // Remove 'data: '
try {
@ -292,6 +296,7 @@ export const useChat = defineStore('chat', {
}
if (data.type === 'token' && data.content) {
this.currentAssistantMessage.loading = false;
// Append content to the current assistant message
this.currentAssistantMessage.content += data.content;
// Force Vue to detect the change
@ -310,6 +315,7 @@ export const useChat = defineStore('chat', {
role: 'assistant',
content: '',
toolCalls: [],
loading: true,
};
}
}