Эх сурвалжийг харах

Merge branch 'wanzi' of http://59.110.91.129:3000/zhangmengying/AIClass into wanzi

丸子 10 сар өмнө
parent
commit
d6dd2fb4dd

+ 2 - 2
.env

@@ -2,8 +2,8 @@
 VITE_APP_TITLE=AI课程网
 
 # 请求路径
-VITE_BASE_URL='http://59.110.91.129/admin-api'
-# VITE_BASE_URL='http://192.168.110.8:8080/admin-api'
+# VITE_BASE_URL='http://59.110.91.129/admin-api'
+VITE_BASE_URL='http://192.168.110.8:8080/admin-api'
 
 # 默认账户密码
 VITE_APP_DEFAULT_LOGIN_TENANT = 博雅智算

+ 72 - 0
src/components/TTS/useAudioPlayer.js

@@ -0,0 +1,72 @@
+import { ref } from 'vue';
+
+export function useAudioPlayer() {
+    let audioContext = null;
+    let audioQueue = [];
+    let isPlaying = false;
+
+    // 初始化AudioContext
+    const initAudioContext = () => {
+        if (!audioContext) {
+            audioContext = new (window.AudioContext || window.webkitAudioContext)({
+                sampleRate: 24000 // 匹配TTS采样率
+            });
+        }
+    };
+
+    // 播放音频块
+    const playAudioChunk = async (base64Audio) => {
+
+        // console.log('playAudioChunk=========', base64Audio);
+        initAudioContext();
+
+        // 解码Base64音频数据
+        const audioBytes = Uint8Array.from(atob(base64Audio), c => c.charCodeAt(0));
+        audioQueue.push(audioBytes);
+
+        if (!isPlaying) {
+            processAudioQueue();
+        }
+    };
+
+    // 处理音频队列
+    const processAudioQueue = async () => {
+        if (audioQueue.length === 0) {
+            isPlaying = false;
+            return;
+        }
+
+        isPlaying = true;
+        const audioData = audioQueue.shift();
+
+        try {
+            const audioBuffer = await audioContext.decodeAudioData(audioData.buffer);
+            const source = audioContext.createBufferSource();
+            source.buffer = audioBuffer;
+            source.connect(audioContext.destination);
+            source.start(0);
+
+            // 播放完成后继续处理队列
+            source.onended = processAudioQueue;
+        } catch (error) {
+            console.error('音频解码失败:', error);
+            isPlaying = false;
+        }
+    };
+
+    // 停止播放并清理
+    const stopPlayback = () => {
+        if (audioContext) {
+            audioContext.close().then(() => {
+                audioContext = null;
+            });
+        }
+        audioQueue = [];
+        isPlaying = false;
+    };
+
+    return {
+        playAudioChunk,
+        stopPlayback
+    };
+}

+ 1 - 1
src/views/AIPainting.vue

@@ -302,7 +302,7 @@ const sendMessage = async() => {
     }
 
     CreatePainting({
-      "modelId": 56,
+      "modelId": 57,
       "prompt":content,
       "width":1024,
       "height":1024

+ 36 - 20
src/views/AIQuestions.vue

@@ -41,7 +41,7 @@
           <!-- AI对话框 -->
           <div class="chat-dialog">
             <!-- 对话消息列表 -->
-            <div class="message-list">
+            <div class="message-list" ref="messageListRef">
               <div v-for="(item, index) in messageList" :key="index">
                 <!-- AI消息 -->
                 <div class="ai-message" v-if="item.type !== 'user'">
@@ -80,7 +80,7 @@
 </template>
 
 <script setup>
-import { ref, onMounted, computed,watch } from "vue";
+import {ref, onMounted, computed, watch, nextTick} from "vue";
 import { CreateDialogue, sendChatMessageStream } from "@/api/questions.js";
 import { useRouter, useRoute } from "vue-router";
 import { saveRecord } from '@/api/personalized/index.js'
@@ -179,6 +179,8 @@ const enableContext = ref(true); // 是否开启上下文
 // 接收 Stream 消息
 const receiveMessageFullText = ref("");
 const receiveMessageDisplayedText = ref("");
+const messageListRef = ref(null);
+
 
 // =========== 【聊天对话】相关 ===========
 
@@ -299,6 +301,11 @@ const doSendMessage = async (content) => {
   });
 };
 
+
+import { useAudioPlayer } from '@/components/TTS/useAudioPlayer';
+
+const { playAudioChunk } = useAudioPlayer();
+
 /** 真正执行【发送】消息操作 */
 const doSendMessageStream = async (userMessage) => {
   // 创建 AbortController 实例,以便中止请求
@@ -343,20 +350,29 @@ const doSendMessageStream = async (userMessage) => {
           return;
         }
         // 如果内容为空,就不处理。
-        // if (data.receive.content === '') {
+        // if (data.receive?.content === '') {
         //   return
         // }
-        receiveMessageFullText.value =
-          receiveMessageFullText.value + data.receive.content;
-        // 首次返回需要添加一个 message 到页面,后面的都是更新
-        if (isFirstChunk) {
-          isFirstChunk = false;
-          // 弹出两个假数据
-          activeMessageList.value.pop();
-          activeMessageList.value.pop();
-          // 更新返回的数据
-          activeMessageList.value.push(data.send);
-          activeMessageList.value.push(data.receive);
+
+        // 根据事件类型处理
+        if (data.eventType === 'TEXT') {
+
+          // 处理文本消息
+          receiveMessageFullText.value += data.receive.content;
+
+          // 首次返回需要添加一个 message 到页面,后面的都是更新
+          if (isFirstChunk) {
+            isFirstChunk = false;
+            // 弹出两个假数据
+            activeMessageList.value.pop();
+            activeMessageList.value.pop();
+            // 更新返回的数据
+            activeMessageList.value.push(data.send);
+            activeMessageList.value.push(data.receive);
+          }
+        } else if (data.eventType === 'AUDIO') {
+          // 处理音频消息
+          await playAudioChunk(data.audioData);
         }
       },
       (error) => {
@@ -408,10 +424,11 @@ const messageList = computed(() => {
 // ============== 【消息滚动】相关 =============
 
 /** 滚动到 message 底部 */
-const scrollToBottom = async (isIgnore) => {
-  // if (messageRef.value) {
-  // messageRef.value.scrollToBottom(isIgnore)
-  // }
+const scrollToBottom = async (isIgnore = false) => {
+  await nextTick();
+  if (messageListRef.value) {
+    messageListRef.value.scrollTop = messageListRef.value.scrollHeight;
+  }
 };
 
 /** 自提滚动效果 */
@@ -446,8 +463,7 @@ const textRoll = async () => {
       }
 
       if (index < receiveMessageFullText.value.length) {
-        receiveMessageDisplayedText.value +=
-          receiveMessageFullText.value[index];
+        receiveMessageDisplayedText.value += receiveMessageFullText.value[index];
         index++;
 
         // 更新 message