feat: 增加加载动画, 修复已知问题
This commit is contained in:
parent
3fd308d6b6
commit
e9ed4a8b39
|
|
@ -119,7 +119,7 @@ async function createConversation() {
|
|||
|
||||
// -- Select conversation --
|
||||
async function selectConversation(id) {
|
||||
// 保存当前对话的流式状态(如果有)
|
||||
// 保存当前对话的流式状态和消息列表(如果有)
|
||||
if (currentConvId.value && streaming.value) {
|
||||
streamStates.set(currentConvId.value, {
|
||||
streaming: true,
|
||||
|
|
@ -127,11 +127,11 @@ async function selectConversation(id) {
|
|||
streamThinking: streamThinking.value,
|
||||
streamToolCalls: [...streamToolCalls.value],
|
||||
streamProcessSteps: [...streamProcessSteps.value],
|
||||
messages: [...messages.value], // 保存消息列表(包括临时用户消息)
|
||||
})
|
||||
}
|
||||
|
||||
currentConvId.value = id
|
||||
messages.value = []
|
||||
nextMsgCursor.value = null
|
||||
hasMoreMessages.value = false
|
||||
|
||||
|
|
@ -143,15 +143,20 @@ async function selectConversation(id) {
|
|||
streamThinking.value = savedState.streamThinking
|
||||
streamToolCalls.value = savedState.streamToolCalls
|
||||
streamProcessSteps.value = savedState.streamProcessSteps
|
||||
messages.value = savedState.messages || [] // 恢复消息列表
|
||||
} else {
|
||||
streaming.value = false
|
||||
streamContent.value = ''
|
||||
streamThinking.value = ''
|
||||
streamToolCalls.value = []
|
||||
streamProcessSteps.value = []
|
||||
messages.value = []
|
||||
}
|
||||
|
||||
// 如果不是正在流式传输,从服务器加载消息
|
||||
if (!streaming.value) {
|
||||
await loadMessages(true)
|
||||
}
|
||||
}
|
||||
|
||||
// -- Load messages --
|
||||
|
|
@ -277,8 +282,7 @@ async function sendMessage(content) {
|
|||
if (currentConvId.value === convId) {
|
||||
streaming.value = false
|
||||
currentStreamPromise = null
|
||||
// Replace temp message and add assistant message from server
|
||||
messages.value = messages.value.filter(m => m.id !== userMsg.id)
|
||||
// 添加助手消息(保留临时用户消息)
|
||||
messages.value.push({
|
||||
id: data.message_id,
|
||||
conversation_id: convId,
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@
|
|||
@delete="$emit('deleteMessage', msg.id)"
|
||||
/>
|
||||
|
||||
<div v-if="streaming" class="message-bubble assistant">
|
||||
<div v-if="streaming" class="message-bubble assistant streaming">
|
||||
<div class="avatar">claw</div>
|
||||
<div class="message-body">
|
||||
<ProcessBlock
|
||||
|
|
@ -56,6 +56,12 @@
|
|||
:streaming="streaming"
|
||||
/>
|
||||
<div class="message-content streaming-content" v-html="renderedStreamContent || '<span class=\'placeholder\'>...</span>'"></div>
|
||||
<div class="streaming-indicator">
|
||||
<svg class="spinner" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 12a9 9 0 1 1-6.219-8.56"/>
|
||||
</svg>
|
||||
<span>正在生成...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -330,6 +336,26 @@ defineExpose({ scrollToBottom })
|
|||
word-break: break-word;
|
||||
}
|
||||
|
||||
.streaming-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid var(--border-light);
|
||||
font-size: 12px;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.spinner {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.streaming-content :deep(p) {
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,17 @@
|
|||
<template>
|
||||
<div class="process-block">
|
||||
<div class="process-block" :class="{ 'is-streaming': streaming }">
|
||||
<!-- 流式加载状态 -->
|
||||
<div v-if="streaming && processItems.length === 0" class="streaming-placeholder">
|
||||
<div class="streaming-icon">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<span class="streaming-text">正在思考中<span class="dots">...</span></span>
|
||||
</div>
|
||||
|
||||
<!-- 正常内容 -->
|
||||
<template v-else>
|
||||
<button class="process-toggle" @click="toggleAll">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/>
|
||||
|
|
@ -56,6 +68,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -267,6 +280,37 @@ watch(() => props.streaming, (streaming) => {
|
|||
max-width: 100%;
|
||||
}
|
||||
|
||||
.streaming-placeholder {
|
||||
padding: 16px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.streaming-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 6px;
|
||||
background: #fef3c7;
|
||||
color: #f59e0b;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
|
||||
.streaming-text {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.streaming-text .dots {
|
||||
display: inline-block;
|
||||
animation: pulse 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.process-toggle {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
|
|
@ -406,7 +450,7 @@ watch(() => props.streaming, (streaming) => {
|
|||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.process-item.loading .process-icon {
|
||||
.process-item.loading.tool_call .process-icon {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue