dangdang 1 месяц назад
Родитель
Сommit
6acdd3cd58

BIN
public/image0.jpg


BIN
public/image3.png


BIN
src/assets/WechatIMG221.jpg


BIN
src/assets/WechatIMG222.jpg


BIN
src/assets/role.png


BIN
src/assets/role1.png


BIN
src/assets/role2.png


BIN
src/assets/role3.png


+ 226 - 127
src/views/ChatTts.vue

@@ -9,12 +9,14 @@ import axios from "axios";
 import { fetchEventSource } from "@microsoft/fetch-event-source";
 import { useRoute, useRouter } from "vue-router";
 import delay from "delay";
+import { fetchTaskStatus } from "./utils/api";
 
 const route = useRoute();
 const router = useRouter();
 const conversationId = computed(() => route.query.taskId); // 会话ID
 const round = computed(() => route.query.round); // 轮次
 const sessionId = ref("");
+const taskStatus = ref(false)
 
 const currentRate = ref(0);
 const currentRound = computed(() => {
@@ -53,19 +55,10 @@ const updateChatList = async (message = "", loading = true) => {
       clearInterval(timer);
     }
   }, 50);
-
-  // chatList.value = chatList.value.map((item) =>
-  //   item.loading
-  //     ? message
-  //       ? { ...item, text: message, loading }
-  //       : { ...item, loading }
-  //     : item
-  // );
-  // localStorage.setItem("chatHistory", JSON.stringify(chatList.value));
 };
 
 // 创建一个队列实例,设置并发数为 1
-const queue = new PQueue({ concurrency: 1 });
+const queue = new PQueue({ concurrency: 4 });
 const queue1 = new PQueue({ concurrency: 1 });
 const queue2 = new PQueue({ concurrency: 1 });
 
@@ -77,13 +70,13 @@ let lastStrIndex = 0;
 
 // 将字符串根据标点符号断句分割,并添加到messageQueue中
 const splitMessage = async (str) => {
-  const punctuation = ["。", "!", "?", ";", ":"];
+  const punctuation = ["。","," ,"!", "?", ";", ":"];
 
   for (let i = lastStrIndex; i < str.length; i++) {
     if (punctuation.includes(str[i])) {
       const message = str.slice(lastStrIndex, i + 1);
       console.log(message, "==========");
-      await playTTS(message);
+      playTTS(message);
       lastStrIndex = i + 1; // 更新上一个字符串的结束位置
     }
   }
@@ -125,114 +118,229 @@ const handleStopRecord = (message) => {
   sessionId.value = generateSessionId();
   chatGpt(message);
 };
+let audioQueue = [];  // 音频队列,保存按请求顺序的音频数据
+let playingQueue = []; // 用来记录当前正在播放的音频序号
+let isPlaying = false;  // 当前是否正在播放
+let orderId = 0;  // 请求顺序号
+
+// 用来控制正在播放音频的锁定机制
+let playingLock = false;  // 标识是否正在播放
+
+// 1. 发起 TTS 请求
+// const playTTS = async (ttsMessage) => {
+//   const currentOrderId = orderId++;  // 获取当前请求的顺序号
+
+//   try {
+//     // 发起请求
+//     const res = await axios.get(
+//         `/openapi-prd/ai/ttt/synthesize?text=${ttsMessage}&sessionId=${sessionId.value}`,
+//         { responseType: "arraybuffer" }
+//     );
+
+//     // 将音频数据和顺序号一起存入 audioQueue
+//     audioQueue.push({ orderId: currentOrderId, audioData: res.data });
+
+//     // 如果当前没有音频在播放,开始播放
+//     if (!isPlaying && !playingLock) {
+//       await playNextAudio();  // 开始播放
+//     }
+//   } catch (error) {
+//     console.error("调用 TTS API 时出错:", error);
+//   }
+// };
+const playTTS = async (ttsMessage) => {
+  queue.add(async () => {
+    const currentOrderId = orderId++;
 
-const playTTS1 = async (ttsMessage) => {
-  try {
-    const res = await axios.post(
-      `/openapi-stg/ai/voice/tts/v2`,
-      { sessionId: "N7FB_G0WlrOLjc", text: ttsMessage },
-      {
-        responseType: "arraybuffer",
-        headers: {
-          "X-Ai-TTS-Appid": "2b1317fb5b284b308dc90a6fdeae6c4e",
-        },
+    try {
+      const res = await axios.get(
+        `/openapi-prd/ai/ttt/synthesize?text=${ttsMessage}&sessionId=${sessionId.value}`,
+        { responseType: "arraybuffer" }
+      );
+
+      // 按顺序存入音频队列
+      audioQueue.push({ orderId: currentOrderId, audioData: res.data });
+
+      // 仅在没有播放时启动播放
+      if (!isPlaying) {
+        await playNextAudio();
       }
-    );
-    console.log(res.data);
+    } catch (error) {
+      console.error("调用 TTS API 时出错:", error);
+    }
+  });
+};
+// 2. 播放下一个音频
+const playNextAudio = async () => {
+  // 如果播放队列为空,停止播放
+  if (audioQueue.length === 0) {
+    isPlaying = false;  // 播放完所有音频
+    console.log("所有音频播放完毕.");
+    return;
+  }
 
-    queue1.add(async () => {
-      // 播放获取到的音频
-      await playAudio(res.data);
-    });
-  } catch (error) {
-    console.error("Error calling TTS API:", error);
+  // 找到队列中最小序号的音频,确保顺序播放
+  const nextAudio = getNextAudioToPlay();
+
+  // 如果找不到下一个应该播放的音频,表示队列还没有按顺序准备好
+  if (!nextAudio) {
+    console.log("没有找到下一个应该播放的音频,队列尚未按顺序准备好.");
+    console.log("当前音频队列:", audioQueue);  // 打印当前音频队列
+    console.log("当前已播放的序列:", playingQueue);  // 打印已经播放的音频序列
+    return;
   }
-};
 
-const playTTS = async (ttsMessage) => {
+  // 打印正在播放的音频序号
+  console.log(`正在播放音频,序号:${nextAudio.orderId}`);
+
   try {
-    const res = await axios.get(
-      `/openapi-prd/ai/ttt/synthesize?text=${ttsMessage}&sessionId=${sessionId.value}`,
-      {
-        responseType: "arraybuffer",
-      }
-    );
-    console.log(res.data);
+    isPlaying = true;  // 标记当前正在播放
+    playingLock = true;  // 锁定播放,避免新的音频插入
 
-    queue1.add(async () => {
-      // 播放获取到的音频
-      await playAudio(res.data);
-    });
-  } catch (error) {
-    console.error("Error calling TTS API:", error);
-  }
-};
+    // 播放当前音频
+    await playAudio(nextAudio.audioData);
 
-const playAudio1 = (audioData, options = {}) => {
-  return new Promise((resolve, reject) => {
-    const blob = new Blob([audioData], { type: "audio/wav" });
-    const url = URL.createObjectURL(blob);
-    const audio = new Audio(url);
+    // 播放完成后,移除已播放的音频
+    audioQueue = audioQueue.filter(audio => audio.orderId !== nextAudio.orderId);
+    playingQueue.push(nextAudio.orderId);  // 将已播放的序号添加到播放记录
 
-    audio.setAttribute("id", "audio");
-    audio.setAttribute("autoplay", "autoplay");
+    // 输出音频播放完成的日志
+    console.log(`音频序号 ${nextAudio.orderId} 播放完成,等待播放下一个包...`);
 
-    if (options.volume) {
-      audio.volume = options.volume; // 设置音量
-    }
+    // 解除锁定,等待下一个音频播放
+    playingLock = false;
 
-    audio.onended = () => {
-      URL.revokeObjectURL(url);
-      resolve();
-    };
+    // 使用定时器周期性检查是否可以播放下一个音频
+    const checkNextAudioTimer = setInterval(async () => {
+      if (!playingLock) {
+        await playNextAudio();
+        clearInterval(checkNextAudioTimer);  // 清除定时器
+      }
+    }, 300); // 每秒检查一次
+  } catch (error) {
+    console.error(`播放音频序号 ${nextAudio.orderId} 时出错:`, error);
+    // 如果某个音频播放失败,跳过这个音频,继续播放下一个
+    audioQueue = audioQueue.filter(audio => audio.orderId !== nextAudio.orderId);  // 移除报错的音频
+    playingQueue.push(nextAudio.orderId);  // 将已播放的序号添加到播放记录
+    playingLock = false;  // 解除锁定
+    // 继续播放下一个音频
+    await playNextAudio();
+  }
+};
 
-    audio.onerror = (error) => {
-      URL.revokeObjectURL(url);
-      reject(error);
-    };
 
-    audio
-      .play()
-      .then(() => console.log("Audio playing"))
-      .catch(reject);
-  });
+// 获取下一个应该播放的音频(保证顺序)
+const getNextAudioToPlay = () => {
+  // 检查音频队列中是否有下一个应该播放的音频
+  console.log("检查下一个播放的音频...");
+  for (let i = 0; i < audioQueue.length; i++) {
+    const audio = audioQueue[i];
+    // 找到最小的序号,即请求的顺序号
+    if (audio.orderId === playingQueue.length) {
+      console.log(`找到了下一个应该播放的音频,序号:${audio.orderId}`);
+      return audio;
+    }
+  }
+  console.log("没有找到下一个应该播放的音频,返回 null");
+  return null;  // 如果没有找到匹配的音频,返回 null
 };
 
-const playAudio = (audioData, options = {}) => {
+// 播放音频的方法
+const playAudio = (audioData) => {
   return new Promise((resolve, reject) => {
-    const audioContext = new (window.AudioContext ||
-      window.webkitAudioContext)();
+    const audioContext = new (window.AudioContext || window.webkitAudioContext)();
     const blob = new Blob([audioData], { type: "audio/wav" });
     const url = URL.createObjectURL(blob);
     const audioBufferSourceNode = audioContext.createBufferSource();
 
     fetch(url)
-      .then((response) => response.arrayBuffer())
-      .then((arrayBuffer) => audioContext.decodeAudioData(arrayBuffer))
-      .then((audioBuffer) => {
-        audioBufferSourceNode.buffer = audioBuffer;
-        audioBufferSourceNode.connect(audioContext.destination);
-
-        if (options.volume) {
-          audioBufferSourceNode.gain.value = options.volume; // 设置音量
-        }
-
-        audioBufferSourceNode.start(0);
-
-        audioBufferSourceNode.onended = () => {
-          URL.revokeObjectURL(url);
-          resolve();
-        };
-
-        audioBufferSourceNode.onerror = (error) => {
-          URL.revokeObjectURL(url);
-          reject(error);
-        };
-      })
-      .catch(reject);
+        .then((response) => {
+          if (!response.ok) {
+            console.error("音频文件请求失败: ", response.statusText);
+            reject(new Error("音频文件请求失败"));
+            return;
+          }
+          return response.arrayBuffer();
+        })
+        .then((arrayBuffer) => audioContext.decodeAudioData(arrayBuffer))
+        .then((audioBuffer) => {
+          audioBufferSourceNode.buffer = audioBuffer;
+          audioBufferSourceNode.connect(audioContext.destination);
+
+          audioBufferSourceNode.start(0);
+
+          // 当音频播放完毕时,调用 resolve
+          audioBufferSourceNode.onended = () => {
+            URL.revokeObjectURL(url);
+            resolve();  // 播放完成后,继续播放下一个
+          };
+
+          audioBufferSourceNode.onerror = (error) => {
+            console.error("音频播放错误:", error);
+            URL.revokeObjectURL(url);
+            reject(error);  // 播放失败时,返回错误
+          };
+        })
+        .catch((error) => {
+          console.error("音频加载或解码时出错:", error);
+          reject(error);  // 如果解码或播放出错,reject
+        });
   });
 };
 
+// const playTTS1 = async (ttsMessage) => {
+//   try {
+//     const res = await axios.post(
+//       `/openapi-stg/ai/voice/tts/v2`,
+//       { sessionId: "N7FB_G0WlrOLjc", text: ttsMessage },
+//       {
+//         responseType: "arraybuffer",
+//         headers: {
+//           "X-Ai-TTS-Appid": "2b1317fb5b284b308dc90a6fdeae6c4e",
+//         },
+//       }
+//     );
+//     console.log(res.data);
+
+//     queue1.add(async () => {
+//       // 播放获取到的音频
+//       await playAudio(res.data);
+//     });
+//   } catch (error) {
+//     console.error("Error calling TTS API:", error);
+//   }
+// };
+
+// const playAudio1 = (audioData, options = {}) => {
+//   return new Promise((resolve, reject) => {
+//     const blob = new Blob([audioData], { type: "audio/wav" });
+//     const url = URL.createObjectURL(blob);
+//     const audio = new Audio(url);
+
+//     audio.setAttribute("id", "audio");
+//     audio.setAttribute("autoplay", "autoplay");
+
+//     if (options.volume) {
+//       audio.volume = options.volume; // 设置音量
+//     }
+
+//     audio.onended = () => {
+//       URL.revokeObjectURL(url);
+//       resolve();
+//     };
+
+//     audio.onerror = (error) => {
+//       URL.revokeObjectURL(url);
+//       reject(error);
+//     };
+
+//     audio
+//       .play()
+//       .then(() => console.log("Audio playing"))
+//       .catch(reject);
+//   });
+// };
+
 const chatGpt = async (userMessage) => {
   const downloadUrl = "/openapi-prd/ai/intelligent-tutoring/task/dialogue";
   i = 0;
@@ -265,38 +373,17 @@ const chatGpt = async (userMessage) => {
             queue.add(async () => {
               await splitMessage(responseText);
             });
-            // const timer = setInterval(() => {
-            //   i = i + 1;
-            //   //updateChatList(responseText.substring(0, i));
-            //   queue2.add(
-            //     async () => await updateChatList(responseText.substring(0, i))
-            //   );
-            //   if (i >= responseText.length) {
-            //     clearInterval(timer);
-            //   }
-            //   console.log(
-            //     i,
-            //     responseText.substring(i),
-            //     "=======responseText.substring(i)"
-            //   );
-            //   if (
-            //     responseText.substring(i).indexOf("[DONE]") === 0 ||
-            //     responseText.substring(i).indexOf("[DONE]") === 1
-            //   ) {
-            //     console.log("========done===========");
-            //     updateChatList("", false);
-            //     // 保存聊天记录到本地
-            //     const chatHistory = JSON.stringify(chatList.value);
-            //     localStorage.setItem("chatHistory", chatHistory);
-            //   }
-            //}, 200);
-
+            
             queue2.add(async () => await updateChatList(responseText));
+            
             //updateChatList(responseText);
           }
         },
       }
     );
+    setTimeout(() => {
+         handleTaskStatus()
+     },3000)
     //queue2.add(async () => await updateChatList("", false));
   } catch (error) {
     //updateChatList("", false);
@@ -380,23 +467,35 @@ const next = () => {
 };
 
 const loadChatHistory = () => {
+  
   const chatHistory = localStorage.getItem("chatHistory");
+  const chatStatus = localStorage.getItem("status");
   if (chatHistory) {
     const history = JSON.parse(chatHistory);
     history.forEach((item) => {
       chatList.value.push(item);
     });
   }
+  if(chatStatus){
+    taskStatus.value = JSON.parse(chatStatus)
+  }
 };
 
+// 查询任务结束状态
+
+const handleTaskStatus = async () => {
+  const res =  await fetchTaskStatus(conversationId.value)
+  if(res.code == 200){
+    taskStatus.value = res.body
+    localStorage.setItem("status", res.body);
+  }
+}
+
 // 在组件挂载时调用
 onMounted(() => {
   loadChatHistory();
 });
 
-// onBeforeUnmount(() => {
-//   localStorage.removeItem("chatHistory");
-// });
 </script>
 
 <template>
@@ -416,9 +515,9 @@ onMounted(() => {
     <BottomArea
       @startRecord="handleStartRecord"
       @stopRecord="handleStopRecord"
-      v-show="rate < 100"
+      v-show="rate < 100 || taskStatus"
     />
-    <div v-show="rate >= 100" class="next-btn">
+    <div v-show="rate >= 100 || taskStatus" class="next-btn">
       <van-button style="width: 100%" @click="next" type="primary"
         >已完成对话,下一步</van-button
       >

+ 2 - 7
src/views/DetailView.vue

@@ -14,13 +14,6 @@ const selectedNature = ref(null);
 const selectedRounds = ref(0);
 
 const rolelist = ref([]);
-// const roleOptions = [
-//   { text: "投资人", value: "投资人" },
-//   { text: "店总", value: "店总" },
-//   { text: "销售经理", value: "销售经理" },
-//   { text: "金融经理", value: "金融经理" },
-//   { text: "销售顾问", value: "销售顾问" },
-// ];
 const roleOptions = JSON.parse(localStorage.getItem('role')).roleOption
 
 const roundsOptions = [
@@ -58,6 +51,7 @@ const handleExam = async () => {
     tutoringRole: selectedRole.value,
     personality: selectedNature.value,
     round: selectedRounds.value,
+    sceneType:"XIAOWEI",
   };
   try {
     const { body } = await fetchTaskCreate(data);
@@ -73,6 +67,7 @@ onMounted(() => {
   const role = JSON.parse(localStorage.getItem('role'))
   rolelist.value = role
   localStorage.removeItem("chatHistory");
+  localStorage.removeItem("status");
 });
 </script>
 <template>

+ 20 - 33
src/views/HomeView.vue

@@ -5,10 +5,10 @@ import banner1 from "../assets/banner1.jpg";
 import banner2 from "../assets/banner2.jpg";
 import banner3 from "../assets/banner3.jpg";
 import banner4 from "../assets/banner4.jpg";
-import role1 from "../assets/role1.jpg";
-import role2 from "../assets/role2.jpg";
-import role3 from "../assets/role3.jpg";
-import role from "../assets/role.jpg";
+import role1 from "../assets/role1.png";
+import role2 from "../assets/role2.png";
+import role3 from "../assets/role3.png";
+import role from "../assets/role.png";
 
 const images = ref([
   { url: banner1 },
@@ -34,13 +34,13 @@ const cardlist = ref([
     ]
   },
   {
-    title: "转介营销",
+    title: "转介客户",
     image: role2,
     subTitle: "转介名单拓展营销",
     description:'通过电话联系渠道(或存量客户)转介名单,开展营销,寻找商机',
     roleOption:[
       {text: '渠道转介', value: '渠道转介'},
-      {text: '存量客户', value: '存量客户'},
+      {text: '存量转介', value: '存量转介'},
     ]
   },
   {
@@ -63,12 +63,15 @@ const cardlist = ref([
   }
 ])
 
-const handleDetail = (item) => {
-  localStorage.setItem("role", JSON.stringify(item));
-  router.push("/detail");
+const handleDetail = (item,index) => { 
+  if(index === 0){
+    localStorage.setItem("role", JSON.stringify(item));
+    router.push("/detail");
+  }
 };
 onMounted(() => {
   localStorage.removeItem("chatHistory");
+  localStorage.removeItem("status");
 });
 </script>
 <template>
@@ -100,30 +103,8 @@ onMounted(() => {
       </div>
       <div class="card_list">
         <p>陪练课程</p>
-        <!-- <div class="card_content">
-          <div class="card_item" @click="handleDetail()">
-            <img src="../assets/role3.jpg" alt="" />
-            <p>新客触达</p>
-            <span>白名单新客触达</span>
-          </div>
-          <div class="card_item">
-            <img src="../assets/role2.jpg" alt="" />
-            <p>转介营销</p>
-            <span>转介名单拓展营销</span>
-          </div>
-          <div class="card_item">
-            <img src="../assets/role1.jpg" alt="" />
-            <p>存量客户</p>
-            <span>存量客户名单</span>
-          </div>
-          <div class="card_item">
-            <img src="../assets/role.jpg" alt="" />
-            <p>区域陌拜</p>
-            <span>地推周边企业陌拜</span>
-          </div>
-        </div> -->
         <div class="card_content">
-          <div v-for="(item, index) in cardlist" :key="index" class="card_item" @click="handleDetail(item)">
+          <div v-for="(item, index) in cardlist" :key="index" class="card_item" @click="handleDetail(item,index)">
             <img :src="item.image" alt="" />
             <p>{{ item.title }}</p>
             <span>{{ item.subTitle }}</span>
@@ -268,7 +249,7 @@ html {
     }
 
     .card_item {
-      width: 9.88rem;
+      width: 47%;
       height: 13.75rem;
       background-color: rgba(255, 255, 255, 1);
       box-shadow: 0rem 0.06rem 0.88rem 0rem rgba(0, 1, 130, 0.08);
@@ -300,6 +281,12 @@ html {
         margin-left: 0.63rem;
       }
     }
+    // @media (min-width: 600px) {
+    //   .card {
+    //     width: 50%;
+    //     height: 400px;
+    //   }
+    // }
   }
 }
 </style>

+ 1 - 1
src/views/ResultView.vue

@@ -96,7 +96,7 @@ onBeforeUnmount(() => {
     <div class="score">
       <span>最终得分</span>
       <p class="score-num">
-        <span>{{ score }}</span
+        <span style="margin-right: 0.2rem;">{{ score }}</span
         >分
       </p>
     </div>

+ 7 - 0
src/views/utils/api.js

@@ -16,3 +16,10 @@ export async function fetchTaskScore(conversationId) {
   });
 }
 
+//查询任务结束状态
+
+export async function fetchTaskStatus(conversationId) {
+  return request(`/openapi-prd/ai/intelligent-tutoring/task/endFlag?conversationId=${conversationId}`,{
+    method:"get",
+  });
+}