This commit is contained in:
Your Name
2026-07-14 10:00:34 +08:00
parent 01152efce6
commit 99a6ca0a77
59 changed files with 10014 additions and 0 deletions
+120
View File
@@ -0,0 +1,120 @@
<template>
<div class="message-list" ref="listRef">
<div v-if="!visibleMessages.length && !loading && !sending && !streaming.trim()" class="welcome">
<div class="welcome-icon">💬</div>
<h2>有什么可以帮您的</h2>
<p>输入消息开始对话支持 Markdown图片文件等</p>
</div>
<div v-if="loading" class="loading-state">加载中...</div>
<MessageItem
v-for="msg in visibleMessages"
:key="msg.id"
:message="msg"
/>
<MessageItem
v-if="streaming.trim()"
:message="{ role: 'assistant', content: streaming, content_type: 'markdown' }"
:is-streaming="true"
/>
<div v-if="sending && !streaming" class="typing-indicator">
<span></span><span></span><span></span>
</div>
</div>
</template>
<script setup>
import { ref, watch, nextTick, computed } from 'vue'
import MessageItem from './MessageItem.vue'
const props = defineProps({
messages: { type: Array, default: () => [] },
loading: Boolean,
streaming: { type: String, default: '' },
sending: Boolean
})
const listRef = ref(null)
const visibleMessages = computed(() => {
return props.messages.filter(msg => {
const hasContent = !!(msg.content && String(msg.content).trim())
const attachments = msg.attachments || []
const hasAttachments = Array.isArray(attachments) && attachments.length > 0
return hasContent || hasAttachments
})
})
watch(
() => [props.messages.length, props.streaming],
async () => {
await nextTick()
if (listRef.value) {
listRef.value.scrollTop = listRef.value.scrollHeight
}
}
)
</script>
<style scoped>
.message-list {
flex: 1;
overflow-y: auto;
padding: 24px 16px;
}
.welcome {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
text-align: center;
color: var(--text-secondary);
}
.welcome-icon {
font-size: 64px;
margin-bottom: 16px;
}
.welcome h2 {
font-size: 24px;
color: var(--text-primary);
margin-bottom: 8px;
}
.welcome p {
font-size: 14px;
}
.loading-state {
text-align: center;
padding: 24px;
color: var(--text-muted);
}
.typing-indicator {
display: flex;
gap: 4px;
padding: 16px 24px;
}
.typing-indicator span {
width: 8px;
height: 8px;
background: var(--text-muted);
border-radius: 50%;
animation: bounce 1.4s infinite ease-in-out both;
}
.typing-indicator span:nth-child(1) { animation-delay: -0.32s; }
.typing-indicator span:nth-child(2) { animation-delay: -0.16s; }
@keyframes bounce {
0%, 80%, 100% { transform: scale(0); }
40% { transform: scale(1); }
}
</style>