|
|
@@ -76,7 +76,7 @@
|
|
|
>
|
|
|
</div>
|
|
|
<div class="ai-dialog-content">
|
|
|
- <div class="ai-message-history">
|
|
|
+ <div class="ai-message-history" ref="messageContainer">
|
|
|
<div
|
|
|
v-for="(message, index) in messageList"
|
|
|
:key="index"
|
|
|
@@ -124,7 +124,7 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { ref, defineProps, defineEmits, onMounted,watch } from 'vue'
|
|
|
+import {ref, defineProps, defineEmits, onMounted, watch, nextTick} from 'vue'
|
|
|
import { ElMessage } from 'element-plus'
|
|
|
import { CreateDialogue, sendChatMessageStream } from '@/api/questions.js'
|
|
|
import { teacherList } from '@/api/teachers.js'
|
|
|
@@ -152,6 +152,7 @@ const showAIDialog = ref(false)
|
|
|
const selectedOption = ref(null)
|
|
|
const messageList = ref([])
|
|
|
const prompt = ref('')
|
|
|
+const messageContainer = ref(null)
|
|
|
const aiQuestionCount = ref(0)
|
|
|
const xZAiData = ref({})
|
|
|
const activeConversationId = ref(null)
|
|
|
@@ -162,6 +163,10 @@ const isComposing = ref(false)
|
|
|
const inputTimeout = ref()
|
|
|
const enableContext = ref(true)
|
|
|
|
|
|
+//tts
|
|
|
+import { useAudioPlayer } from '@/components/TTS/useAudioPlayer';
|
|
|
+const { playAudioChunk } = useAudioPlayer();
|
|
|
+
|
|
|
// 处理选择的默认消息
|
|
|
const handleSelectMessage = message => {
|
|
|
prompt.value = message
|
|
|
@@ -380,25 +385,39 @@ const doSendMessageStream = async userMessage => {
|
|
|
console.log(`对话异常! ${msg}`)
|
|
|
return
|
|
|
}
|
|
|
- receiveMessageFullText.value = receiveMessageFullText.value + data.receive.content
|
|
|
- // 首次返回需要添加一个 message 到页面,后面的都是更新
|
|
|
- if (isFirstChunk) {
|
|
|
- isFirstChunk = false
|
|
|
- // 弹出两个假数据
|
|
|
- messageList.value.pop()
|
|
|
- messageList.value.pop()
|
|
|
- // 更新返回的数据
|
|
|
- messageList.value.push(data.send)
|
|
|
- messageList.value.push(data.receive)
|
|
|
- } else {
|
|
|
- // 更新最后一条消息
|
|
|
- if (messageList.value.length > 0) {
|
|
|
- const lastMessage = messageList.value[messageList.value.length - 1]
|
|
|
- if (lastMessage.id === data.receive.id) {
|
|
|
- lastMessage.content = receiveMessageFullText.value
|
|
|
+
|
|
|
+ if (data.eventType === 'TEXT') {
|
|
|
+
|
|
|
+ // 如果内容为空,就不处理。
|
|
|
+ if (data.receive?.content === '') {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ receiveMessageFullText.value += data.receive.content
|
|
|
+ // 首次返回需要添加一个 message 到页面,后面的都是更新
|
|
|
+ if (isFirstChunk) {
|
|
|
+ isFirstChunk = false
|
|
|
+ // 弹出两个假数据
|
|
|
+ messageList.value.pop()
|
|
|
+ messageList.value.pop()
|
|
|
+ // 更新返回的数据
|
|
|
+ messageList.value.push(data.send)
|
|
|
+ messageList.value.push(data.receive)
|
|
|
+ } else {
|
|
|
+ //更新最后一条消息
|
|
|
+ if (messageList.value.length > 0) {
|
|
|
+ const lastMessage = messageList.value[messageList.value.length - 1]
|
|
|
+ if (lastMessage.id === data.receive.id) {
|
|
|
+ lastMessage.content = receiveMessageFullText.value
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
+ } else if (data.eventType === 'AUDIO') {
|
|
|
+ // 处理音频消息
|
|
|
+ await playAudioChunk(data.audioData);
|
|
|
}
|
|
|
+
|
|
|
+ // 添加此行确保触发滚动
|
|
|
+ scrollToBottom()
|
|
|
},
|
|
|
error => {
|
|
|
console.log(`对话异常! ${error}`)
|
|
|
@@ -463,7 +482,24 @@ watch(() => props.questionDialogVisible, (newVal) => {
|
|
|
selectedOption.value = null
|
|
|
}
|
|
|
})
|
|
|
-
|
|
|
+// 监听消息列表变化,自动滚动到底部
|
|
|
+watch(messageList, () => {
|
|
|
+ scrollToBottom()
|
|
|
+}, { deep: true })
|
|
|
+
|
|
|
+// 单独的滚动到底部函数
|
|
|
+const scrollToBottom = () => {
|
|
|
+ nextTick(() => {
|
|
|
+ if (messageContainer.value) {
|
|
|
+ // 强制重排以确保获取最新高度
|
|
|
+ messageContainer.value.scrollTop = messageContainer.value.scrollHeight
|
|
|
+ // 双重保险:使用requestAnimationFrame确保在浏览器重绘后执行
|
|
|
+ requestAnimationFrame(() => {
|
|
|
+ messageContainer.value.scrollTop = messageContainer.value.scrollHeight
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
onMounted(() => {
|
|
|
// 初始化
|
|
|
})
|