|
@@ -71,7 +71,7 @@ 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])) {
|
|
@@ -143,14 +143,14 @@ const playTTS = async (ttsMessage) => {
|
|
|
`/openapi-prd/ai/ttt/synthesize?text=${ttsMessage}&sessionId=${sessionId.value}`,
|
|
|
{ responseType: "arraybuffer" }
|
|
|
);
|
|
|
-
|
|
|
+
|
|
|
// 即使请求失败也要保持队列连续性(新增部分)
|
|
|
const audioItem = {
|
|
|
orderId: currentOrderId,
|
|
|
audioData: res.data || null,
|
|
|
status: res.data ? 'valid' : 'invalid'
|
|
|
};
|
|
|
-
|
|
|
+
|
|
|
audioQueue.push(audioItem);
|
|
|
audioQueue.sort((a, b) => a.orderId - b.orderId); // 保持队列有序
|
|
|
|
|
@@ -158,7 +158,7 @@ const playTTS = async (ttsMessage) => {
|
|
|
} catch (error) {
|
|
|
console.error("调用 TTS API 时出错:", error);
|
|
|
// 请求失败时插入占位符保持顺序(关键修复)
|
|
|
- audioQueue.push({
|
|
|
+ audioQueue.push({
|
|
|
orderId: currentOrderId,
|
|
|
audioData: null,
|
|
|
status: 'invalid'
|
|
@@ -177,18 +177,18 @@ const playNextAudio = async () => {
|
|
|
// return;
|
|
|
// }
|
|
|
if (audioQueue.length === 0 && playingQueue.length > 0) {
|
|
|
- const maxPlayed = Math.max(...playingQueue);
|
|
|
- if (orderId > maxPlayed + 1) {
|
|
|
- console.log("仍有未完成请求,暂不重置");
|
|
|
- } else {
|
|
|
- isPlaying = false;
|
|
|
- // 新增:当队列完全播放完毕时重置所有状态
|
|
|
- audioQueue = [];
|
|
|
- playingQueue = [];
|
|
|
- orderId = 0; // 可选:如果需要重置顺序号
|
|
|
- console.log("所有音频播放完毕,已重置播放队列.");
|
|
|
- return;
|
|
|
- }
|
|
|
+ const maxPlayed = Math.max(...playingQueue);
|
|
|
+ if (orderId > maxPlayed + 1) {
|
|
|
+ console.log("仍有未完成请求,暂不重置");
|
|
|
+ } else {
|
|
|
+ isPlaying = false;
|
|
|
+ // 新增:当队列完全播放完毕时重置所有状态
|
|
|
+ audioQueue = [];
|
|
|
+ playingQueue = [];
|
|
|
+ orderId = 0; // 可选:如果需要重置顺序号
|
|
|
+ console.log("所有音频播放完毕,已重置播放队列.");
|
|
|
+ return;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
const nextAudio = getNextAudioToPlay();
|
|
@@ -225,22 +225,22 @@ const playNextAudio = async () => {
|
|
|
// 获取下一个应该播放的音频(修改后)
|
|
|
const getNextAudioToPlay = () => {
|
|
|
// 获取当前应该播放的最小序号
|
|
|
- const expectedOrderId = playingQueue.length > 0
|
|
|
- ? Math.max(...playingQueue) + 1
|
|
|
+ const expectedOrderId = playingQueue.length > 0
|
|
|
+ ? Math.max(...playingQueue) + 1
|
|
|
: 0;
|
|
|
|
|
|
// 查找第一个匹配的有效音频(新增有效性检查)
|
|
|
- const nextAudio = audioQueue.find(audio =>
|
|
|
+ const nextAudio = audioQueue.find(audio =>
|
|
|
audio.orderId === expectedOrderId &&
|
|
|
audio.status === 'valid'
|
|
|
);
|
|
|
|
|
|
// 如果找不到有效音频但有序号匹配的无效项,跳过该序号
|
|
|
if (!nextAudio) {
|
|
|
- const invalidAudio = audioQueue.find(audio =>
|
|
|
+ const invalidAudio = audioQueue.find(audio =>
|
|
|
audio.orderId === expectedOrderId
|
|
|
);
|
|
|
-
|
|
|
+
|
|
|
if (invalidAudio) {
|
|
|
console.warn(`检测到无效音频,自动跳过序号:${expectedOrderId}`);
|
|
|
playingQueue.push(expectedOrderId);
|
|
@@ -250,108 +250,7 @@ const getNextAudioToPlay = () => {
|
|
|
|
|
|
return nextAudio || null;
|
|
|
};
|
|
|
-// 1. 发起 TTS 请求
|
|
|
-
|
|
|
-// const playTTS = async (ttsMessage) => {
|
|
|
-// queue.add(async () => {
|
|
|
-// const currentOrderId = orderId++;
|
|
|
-
|
|
|
-// try {
|
|
|
-// const res = await axios.get(
|
|
|
-// `/openapi-prd/ai/ttt/synthesize?text=${ttsMessage}&sessionId=${sessionId.value}`,
|
|
|
-// { responseType: "arraybuffer" }
|
|
|
-// );
|
|
|
-// if (!res.data || res.data.byteLength === 0) {
|
|
|
-// console.error("音频数据无效,无法播放.");
|
|
|
-// return;
|
|
|
-// }
|
|
|
-// // 按顺序存入音频队列
|
|
|
-// audioQueue.push({ orderId: currentOrderId, audioData: res.data });
|
|
|
-
|
|
|
-// // 仅在没有播放时启动播放
|
|
|
-// if (!isPlaying) {
|
|
|
-// await playNextAudio();
|
|
|
-// }
|
|
|
-// } catch (error) {
|
|
|
-// console.error("调用 TTS API 时出错:", error);
|
|
|
-// }
|
|
|
-// });
|
|
|
-// };
|
|
|
-// 2. 播放下一个音频
|
|
|
-// const playNextAudio = async () => {
|
|
|
-// // 如果播放队列为空,停止播放
|
|
|
-// if (audioQueue.length === 0) {
|
|
|
-// isPlaying = false; // 播放完所有音频
|
|
|
-// // audioQueue = []; // 确保列表为空
|
|
|
-// console.log("所有音频播放完毕.");
|
|
|
-// return;
|
|
|
-// }
|
|
|
-
|
|
|
-// // 找到队列中最小序号的音频,确保顺序播放
|
|
|
-// const nextAudio = getNextAudioToPlay();
|
|
|
-// console.log(nextAudio,"没有找到下一个应该播放的音频,队列尚未按顺序准备好.");
|
|
|
-
|
|
|
-// // 如果找不到下一个应该播放的音频,表示队列还没有按顺序准备好
|
|
|
-// if (!nextAudio) {
|
|
|
-// console.log("没有找到下一个应该播放的音频,队列尚未按顺序准备好.");
|
|
|
-// console.log("当前音频队列:", audioQueue); // 打印当前音频队列
|
|
|
-// console.log("当前已播放的序列:", playingQueue); // 打印已经播放的音频序列
|
|
|
-// return;
|
|
|
-// }
|
|
|
-
|
|
|
-// // 打印正在播放的音频序号
|
|
|
-// console.log(`正在播放音频,序号:${nextAudio.orderId}`);
|
|
|
-
|
|
|
-// try {
|
|
|
-// isPlaying = true; // 标记当前正在播放
|
|
|
-// playingLock = true; // 锁定播放,避免新的音频插入
|
|
|
-
|
|
|
-// // 播放当前音频
|
|
|
-// await playAudio(nextAudio.audioData);
|
|
|
-
|
|
|
-// // 播放完成后,移除已播放的音频
|
|
|
-// audioQueue = audioQueue.filter(audio => audio.orderId !== nextAudio.orderId);
|
|
|
-// playingQueue.push(nextAudio.orderId); // 将已播放的序号添加到播放记录
|
|
|
-
|
|
|
-// // 输出音频播放完成的日志
|
|
|
-// console.log(`音频序号 ${nextAudio.orderId} 播放完成,等待播放下一个包...`);
|
|
|
-
|
|
|
-// // 解除锁定,等待下一个音频播放
|
|
|
-// playingLock = false;
|
|
|
-
|
|
|
-// // 使用定时器周期性检查是否可以播放下一个音频
|
|
|
-// 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();
|
|
|
-// }
|
|
|
-// };
|
|
|
|
|
|
-// 获取下一个应该播放的音频(保证顺序)
|
|
|
-// 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) => {
|
|
@@ -362,37 +261,37 @@ const playAudio = (audioData) => {
|
|
|
const audioBufferSourceNode = audioContext.createBufferSource();
|
|
|
|
|
|
fetch(url)
|
|
|
- .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
|
|
|
- });
|
|
|
+ .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) => {
|
|
@@ -480,17 +379,17 @@ const chatGpt = async (userMessage) => {
|
|
|
queue.add(async () => {
|
|
|
await splitMessage(responseText);
|
|
|
});
|
|
|
-
|
|
|
+
|
|
|
queue2.add(async () => await updateChatList(responseText));
|
|
|
-
|
|
|
+
|
|
|
//updateChatList(responseText);
|
|
|
}
|
|
|
},
|
|
|
}
|
|
|
);
|
|
|
setTimeout(() => {
|
|
|
- handleTaskStatus()
|
|
|
- },3000)
|
|
|
+ handleTaskStatus()
|
|
|
+ }, 3000)
|
|
|
//queue2.add(async () => await updateChatList("", false));
|
|
|
} catch (error) {
|
|
|
//updateChatList("", false);
|
|
@@ -574,7 +473,7 @@ const next = () => {
|
|
|
};
|
|
|
|
|
|
const loadChatHistory = () => {
|
|
|
-
|
|
|
+
|
|
|
const chatHistory = localStorage.getItem("chatHistory");
|
|
|
const chatStatus = localStorage.getItem("status");
|
|
|
if (chatHistory) {
|
|
@@ -583,7 +482,7 @@ const loadChatHistory = () => {
|
|
|
chatList.value.push(item);
|
|
|
});
|
|
|
}
|
|
|
- if(chatStatus){
|
|
|
+ if (chatStatus) {
|
|
|
taskStatus.value = JSON.parse(chatStatus)
|
|
|
}
|
|
|
};
|
|
@@ -591,8 +490,8 @@ const loadChatHistory = () => {
|
|
|
// 查询任务结束状态
|
|
|
|
|
|
const handleTaskStatus = async () => {
|
|
|
- const res = await fetchTaskStatus(conversationId.value)
|
|
|
- if(res.code == 200){
|
|
|
+ const res = await fetchTaskStatus(conversationId.value)
|
|
|
+ if (res.code == 200) {
|
|
|
taskStatus.value = res.body
|
|
|
localStorage.setItem("status", res.body);
|
|
|
}
|
|
@@ -612,39 +511,43 @@ onMounted(() => {
|
|
|
<div class="main">
|
|
|
<nav-bar-page title="考试" />
|
|
|
<div class="rate-circle">
|
|
|
- <van-circle
|
|
|
- :stroke-width="80"
|
|
|
- size="70px"
|
|
|
- v-model:current-rate="currentRate"
|
|
|
- :rate="rate"
|
|
|
- :text="text"
|
|
|
- />
|
|
|
+ <van-circle :stroke-width="80" size="70px" v-model:current-rate="currentRate" :rate="rate" :text="text" />
|
|
|
</div>
|
|
|
+ <div class="content">
|
|
|
+ <div class="tooltip">
|
|
|
+ <p>车辆信息:2024年10月起租,起租金额14万元,租期36月</p>
|
|
|
+ <p>还款情况:已还9期(截至2025年7月)</p>
|
|
|
+ <p>提前结清金额:121,021.62元</p>
|
|
|
+ <p>全额打款金额:137,362.73元 </p>
|
|
|
+ <p>预期利益损失:6,535.05元</p>
|
|
|
+ <p>下一期租金:5,068.99元(本金3,421元,利息1,647.99元) </p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
<BG />
|
|
|
<div class="loading" v-show="loading">
|
|
|
<van-loading color="#0094ff" size="26px" vertical>加载中...</van-loading>
|
|
|
</div>
|
|
|
-
|
|
|
+
|
|
|
<ChatList :chatList="chatList" />
|
|
|
- <BottomArea
|
|
|
- @startRecord="handleStartRecord"
|
|
|
- @stopRecord="handleStopRecord"
|
|
|
- v-show="rate < 100 || taskStatus"
|
|
|
- />
|
|
|
+ <BottomArea @startRecord="handleStartRecord" @stopRecord="handleStopRecord" v-show="rate < 100 || taskStatus" />
|
|
|
<div v-show="rate >= 100 || taskStatus" class="next-btn">
|
|
|
- <van-button style="width: 100%" @click="next" type="primary"
|
|
|
- >已完成对话,下一步</van-button
|
|
|
- >
|
|
|
+ <van-button style="width: 100%" @click="next" type="primary">已完成对话,下一步</van-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</template>
|
|
|
<style>
|
|
|
* {
|
|
|
- -webkit-touch-callout: none; /*系统默认菜单被禁用*/
|
|
|
- -webkit-user-select: none; /*webkit浏览器*/
|
|
|
- -khtml-user-select: none; /*早期浏览器*/
|
|
|
- -moz-user-select: none; /*火狐*/
|
|
|
- -ms-user-select: none; /*IE10*/
|
|
|
+ -webkit-touch-callout: none;
|
|
|
+ /*系统默认菜单被禁用*/
|
|
|
+ -webkit-user-select: none;
|
|
|
+ /*webkit浏览器*/
|
|
|
+ -khtml-user-select: none;
|
|
|
+ /*早期浏览器*/
|
|
|
+ -moz-user-select: none;
|
|
|
+ /*火狐*/
|
|
|
+ -ms-user-select: none;
|
|
|
+ /*IE10*/
|
|
|
user-select: none;
|
|
|
}
|
|
|
</style>
|
|
@@ -655,12 +558,28 @@ onMounted(() => {
|
|
|
height: 100vh;
|
|
|
overflow: hidden;
|
|
|
}
|
|
|
+
|
|
|
.rate-circle {
|
|
|
position: fixed;
|
|
|
right: 20px;
|
|
|
top: 70px;
|
|
|
z-index: 9999;
|
|
|
}
|
|
|
+
|
|
|
+/* .content{
|
|
|
+ padding: 0 40px;
|
|
|
+} */
|
|
|
+.tooltip {
|
|
|
+ position: fixed;
|
|
|
+ top: 152px;
|
|
|
+ z-index: 9999;
|
|
|
+ font-size: 12px;
|
|
|
+ padding: 8px;
|
|
|
+ background-color: rgba(0, 0, 0, 0.5);
|
|
|
+ color: #fff;
|
|
|
+ margin: 0 20px;
|
|
|
+}
|
|
|
+
|
|
|
.next-btn {
|
|
|
position: fixed;
|
|
|
bottom: calc(20px + env(safe-area-inset-bottom));
|
|
@@ -668,6 +587,7 @@ onMounted(() => {
|
|
|
box-sizing: border-box;
|
|
|
padding: 0 20px;
|
|
|
}
|
|
|
+
|
|
|
.loading {
|
|
|
position: fixed;
|
|
|
top: 0;
|