feat: AI对话

This commit is contained in:
zhangyh
2025-10-28 11:33:20 +08:00
parent 2a1023aea0
commit 0ce0c41dac
12 changed files with 358 additions and 111 deletions

View File

@@ -1,17 +1,15 @@
<template>
<div
class="chat-message"
:class="{ 'user-message': isMyself, 'ai-message': !isMyself }"
>
<div class="chat-message" :class="{ 'user-message': isMyself, 'ai-message': !isMyself }">
<!-- AI消息显示头像 -->
<div v-if="!isMyself" class="chat-avatar">
<img :src="value.thumb" alt="AI Avatar" />
<img src="@/assets/images/asistant.png" alt="AI Avatar" />
</div>
<!-- 消息内容 -->
<div class="message-content">
<div class="message-text">
{{ value.content }}
<div class="message-text" :class="{ streaming: isStreaming }">
<div v-html="content"></div>
<span v-if="isStreaming" class="streaming-cursor">|</span>
</div>
<!-- <div v-if="!isMyself" class="message-actions flex">
<SvgIcon
@@ -28,13 +26,18 @@
<script setup lang="ts">
import { computed } from 'vue'
import MarkdownIt from 'markdown-it'
const md = new MarkdownIt()
// 定义消息类型
interface ChatMessage {
id: string
sessionId: string | number
type: string
content: string
userId: string
time: string
thumb: string
timestamp: string
id?: string
self?: boolean
}
// 定义操作项类型
@@ -46,12 +49,17 @@ interface ActionItem {
// 定义组件props类型
interface NoticeItemProps {
value: ChatMessage
isStreaming?: boolean
}
const props = defineProps<NoticeItemProps>()
const isMyself = computed(()=>{
return props.value.userId === '1'
const isMyself = computed(() => {
return props.value.self || false
})
const content = computed(() => {
return md.render(props.value.content)
})
const handleClickAction = (value: string): void => {
@@ -117,6 +125,7 @@ const actionList: ActionItem[] = [
.message-text {
color: #000;
border-radius: 0 2rem 2rem 2rem;
word-break: break-word;
}
}
}
@@ -127,12 +136,14 @@ const actionList: ActionItem[] = [
height: 7.4rem;
border-radius: 50%;
margin-right: 1.88rem;
border: 2px solid #000;
padding: 1.4rem;
img {
width: 100%;
height: 100%;
width: 4.9rem;
height: 4.9rem;
border-radius: 50%;
object-fit: cover;
// object-fit: cover;
}
}
@@ -150,4 +161,21 @@ const actionList: ActionItem[] = [
cursor: pointer;
}
}
.streaming-cursor {
animation: blink 1s infinite;
font-weight: bold;
color: #000;
}
@keyframes blink {
0%,
50% {
opacity: 1;
}
51%,
100% {
opacity: 0;
}
}
</style>

View File

@@ -1,7 +1,10 @@
<template>
<div class="chat-list" ref="chatListRef">
<div class="chat-message-item" v-for="message in list" :key="message.id">
<NoticeItem :value="message" />
<NoticeItem
:value="message"
:is-streaming="isStreaming && streamingMessage?.id === message.id"
/>
</div>
</div>
</template>
@@ -21,6 +24,8 @@ interface ChatMessage {
}
const props = defineProps<{
list: ChatMessage[]
isStreaming?: boolean
streamingMessage?: ChatMessage | null
}>()
const emits = defineEmits(['send-message'])
@@ -47,6 +52,17 @@ watch(
{ deep: true }
)
// 监听流式消息内容变化,实时滚动
watch(
() => props.streamingMessage?.content,
async () => {
if (props.isStreaming) {
await nextTick()
scrollToBottom()
}
}
)
const handleSendMessage = (message: string): void => {
console.log('list发送消息:', message)
emits('send-message', message)