|
@@ -0,0 +1,683 @@
|
|
|
+<script setup>
|
|
|
+import { ref, computed, onMounted, onBeforeUnmount } from "vue";
|
|
|
+import BG from "./components/BG.vue";
|
|
|
+import BottomArea from "./components/BottomArea.vue";
|
|
|
+import ChatList from "./components/ChatList.vue";
|
|
|
+import NavBarPage from "../components/NavBarPage.vue";
|
|
|
+import PQueue from "p-queue";
|
|
|
+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 loading = ref(true); //初始化进入的加载状态
|
|
|
+
|
|
|
+const currentRate = ref(0);
|
|
|
+const currentRound = computed(() => {
|
|
|
+ const filteredMessages = chatList.value.filter(
|
|
|
+ (message) =>
|
|
|
+ message.text !== "我没有听清,麻烦在重复一下。" && message.text !== ""
|
|
|
+ );
|
|
|
+ return Math.floor(filteredMessages.length / 2);
|
|
|
+});
|
|
|
+// 当前进度百分比整数部分
|
|
|
+const rate = computed(() => {
|
|
|
+ return Math.floor((currentRound.value / round.value) * 100);
|
|
|
+});
|
|
|
+
|
|
|
+const text = computed(() => `${currentRound.value} / ${round.value}`);
|
|
|
+
|
|
|
+// 对话记录
|
|
|
+const chatList = ref([]);
|
|
|
+let i = 0;
|
|
|
+// 我 我是 我是中国人 我是中国人的英雄 我是中国人的英雄。[
|
|
|
+const updateChatList = async (message = "", loading = true) => {
|
|
|
+ const timer = setInterval(() => {
|
|
|
+ i = i + 1;
|
|
|
+ chatList.value = chatList.value.map((item, index) => {
|
|
|
+ if (index == chatList.value.length - 1) {
|
|
|
+ return {
|
|
|
+ ...item,
|
|
|
+ text: message.substring(0, i),
|
|
|
+ };
|
|
|
+ } else {
|
|
|
+ return item;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ localStorage.setItem("chatHistory", JSON.stringify(chatList.value));
|
|
|
+ if (i >= message.length) {
|
|
|
+ clearInterval(timer);
|
|
|
+ }
|
|
|
+ }, 50);
|
|
|
+};
|
|
|
+
|
|
|
+// 创建一个队列实例,设置并发数为 1
|
|
|
+const queue = new PQueue({ concurrency: 3 });
|
|
|
+const queue1 = new PQueue({ concurrency: 1 });
|
|
|
+const queue2 = new PQueue({ concurrency: 1 });
|
|
|
+
|
|
|
+// 需要tts的文本队列
|
|
|
+const messageQueue = [];
|
|
|
+
|
|
|
+// lastStrIndex 用于记录上一个字符串的结束位置
|
|
|
+let lastStrIndex = 0;
|
|
|
+
|
|
|
+// 将字符串根据标点符号断句分割,并添加到messageQueue中
|
|
|
+const splitMessage = async (str) => {
|
|
|
+ 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, "==========");
|
|
|
+ playTTS(message);
|
|
|
+ lastStrIndex = i + 1; // 更新上一个字符串的结束位置
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const handleStartRecord = async () => {
|
|
|
+ queue.clear();
|
|
|
+ queue1.clear();
|
|
|
+};
|
|
|
+
|
|
|
+const generateSessionId = () => {
|
|
|
+ // 获取当前时间戳
|
|
|
+ const timestamp = Date.now().toString();
|
|
|
+ // 生成一个随机数
|
|
|
+ const randomNum = Math.floor(Math.random() * 10000);
|
|
|
+ // 将时间戳和随机数拼接成会话ID
|
|
|
+ const sessionId = `${timestamp}${randomNum}`;
|
|
|
+ return sessionId;
|
|
|
+};
|
|
|
+
|
|
|
+// 发送消息
|
|
|
+const handleStopRecord = (message) => {
|
|
|
+ lastStrIndex = 0; // 重置
|
|
|
+ chatList.value = [
|
|
|
+ ...chatList.value,
|
|
|
+ {
|
|
|
+ id: Date.now(),
|
|
|
+ type: "user",
|
|
|
+ text: message,
|
|
|
+ loading: false,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: Date.now(),
|
|
|
+ type: "gpt",
|
|
|
+ text: "",
|
|
|
+ loading: true,
|
|
|
+ },
|
|
|
+ ];
|
|
|
+ sessionId.value = generateSessionId();
|
|
|
+ chatGpt(message);
|
|
|
+};
|
|
|
+// let audioQueue = []; // 音频队列,保存按请求顺序的音频数据
|
|
|
+// let playingQueue = []; // 用来记录当前正在播放的音频序号
|
|
|
+// let isPlaying = false; // 当前是否正在播放
|
|
|
+// let orderId = 0; // 请求顺序号
|
|
|
+
|
|
|
+// 用来控制正在播放音频的锁定机制
|
|
|
+let playingLock = false; // 标识是否正在播放
|
|
|
+
|
|
|
+
|
|
|
+let audioQueue = []; // 音频队列,保存按请求顺序的音频数据
|
|
|
+let playingQueue = []; // 记录已播放的音频序号
|
|
|
+let isPlaying = false; // 当前是否正在播放
|
|
|
+let orderId = 0; // 请求顺序号
|
|
|
+
|
|
|
+// 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" }
|
|
|
+ );
|
|
|
+
|
|
|
+ // 即使请求失败也要保持队列连续性(新增部分)
|
|
|
+ const audioItem = {
|
|
|
+ orderId: currentOrderId,
|
|
|
+ audioData: res.data || null,
|
|
|
+ status: res.data ? 'valid' : 'invalid'
|
|
|
+ };
|
|
|
+
|
|
|
+ audioQueue.push(audioItem);
|
|
|
+ audioQueue.sort((a, b) => a.orderId - b.orderId); // 保持队列有序
|
|
|
+
|
|
|
+ if (!isPlaying) await playNextAudio();
|
|
|
+ } catch (error) {
|
|
|
+ console.error("调用 TTS API 时出错:", error);
|
|
|
+ // 请求失败时插入占位符保持顺序(关键修复)
|
|
|
+ audioQueue.push({
|
|
|
+ orderId: currentOrderId,
|
|
|
+ audioData: null,
|
|
|
+ status: 'invalid'
|
|
|
+ });
|
|
|
+ audioQueue.sort((a, b) => a.orderId - b.orderId);
|
|
|
+ if (!isPlaying) await playNextAudio();
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+// 2. 播放下一个音频(修改后)
|
|
|
+const playNextAudio = async () => {
|
|
|
+ // if (audioQueue.length === 0) {
|
|
|
+ // isPlaying = false;
|
|
|
+ // console.log("所有音频播放完毕.");
|
|
|
+ // 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 nextAudio = getNextAudioToPlay();
|
|
|
+ if (!nextAudio) {
|
|
|
+ console.log("等待后续音频数据...");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ isPlaying = true;
|
|
|
+ console.log(`正在播放音频,序号:${nextAudio.orderId}`);
|
|
|
+
|
|
|
+ if (nextAudio.status === 'valid') {
|
|
|
+ await playAudio(nextAudio.audioData);
|
|
|
+ } else {
|
|
|
+ console.warn(`跳过无效音频,序号:${nextAudio.orderId}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新播放状态(优化处理)
|
|
|
+ playingQueue = [...new Set([...playingQueue, nextAudio.orderId])].sort((a, b) => a - b);
|
|
|
+ audioQueue = audioQueue.filter(audio => audio.orderId !== nextAudio.orderId);
|
|
|
+
|
|
|
+ // 立即尝试播放下一个(优化播放流程)
|
|
|
+ isPlaying = false;
|
|
|
+ await playNextAudio();
|
|
|
+ } catch (error) {
|
|
|
+ console.error(`播放失败,序号:${nextAudio.orderId}`, error);
|
|
|
+ audioQueue = audioQueue.filter(audio => audio.orderId !== nextAudio.orderId);
|
|
|
+ isPlaying = false;
|
|
|
+ await playNextAudio();
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 获取下一个应该播放的音频(修改后)
|
|
|
+const getNextAudioToPlay = () => {
|
|
|
+ // 获取当前应该播放的最小序号
|
|
|
+ const expectedOrderId = playingQueue.length > 0
|
|
|
+ ? Math.max(...playingQueue) + 1
|
|
|
+ : 0;
|
|
|
+
|
|
|
+ // 查找第一个匹配的有效音频(新增有效性检查)
|
|
|
+ const nextAudio = audioQueue.find(audio =>
|
|
|
+ audio.orderId === expectedOrderId &&
|
|
|
+ audio.status === 'valid'
|
|
|
+ );
|
|
|
+
|
|
|
+ // 如果找不到有效音频但有序号匹配的无效项,跳过该序号
|
|
|
+ if (!nextAudio) {
|
|
|
+ const invalidAudio = audioQueue.find(audio =>
|
|
|
+ audio.orderId === expectedOrderId
|
|
|
+ );
|
|
|
+
|
|
|
+ if (invalidAudio) {
|
|
|
+ console.warn(`检测到无效音频,自动跳过序号:${expectedOrderId}`);
|
|
|
+ playingQueue.push(expectedOrderId);
|
|
|
+ return 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) => {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ 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) => {
|
|
|
+ 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;
|
|
|
+ queue2.add(() => delay(3000));
|
|
|
+ try {
|
|
|
+ const response = await axios.post(
|
|
|
+ downloadUrl,
|
|
|
+ {
|
|
|
+ conversationId: conversationId.value,
|
|
|
+ content: userMessage,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ headers: {
|
|
|
+ "Content-Type": "application/json",
|
|
|
+ },
|
|
|
+ onDownloadProgress: ({ event }) => {
|
|
|
+ const xhr = event.target;
|
|
|
+ let { responseText } = xhr;
|
|
|
+ console.log("responseText", responseText);
|
|
|
+ if (responseText.includes("code") && responseText.includes("400")) {
|
|
|
+ // console.log("responseTextcode", responseText);
|
|
|
+ // 更新聊天列表
|
|
|
+ const text = "我没有听清,麻烦在重复一下。";
|
|
|
+ updateChatList(text, false);
|
|
|
+ queue.add(async () => {
|
|
|
+ await splitMessage(text);
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ // 用于语音播报
|
|
|
+ queue.add(async () => {
|
|
|
+ await splitMessage(responseText);
|
|
|
+ });
|
|
|
+
|
|
|
+ queue2.add(async () => await updateChatList(responseText));
|
|
|
+
|
|
|
+ //updateChatList(responseText);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ }
|
|
|
+ );
|
|
|
+ setTimeout(() => {
|
|
|
+ handleTaskStatus()
|
|
|
+ },3000)
|
|
|
+ //queue2.add(async () => await updateChatList("", false));
|
|
|
+ } catch (error) {
|
|
|
+ //updateChatList("", false);
|
|
|
+ console.error("出错:", error);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const chatGpt2 = (userMessage) => {
|
|
|
+ const params = {
|
|
|
+ conversationId: "2c64eb8b69be432ca0bb9ae55bc78def",
|
|
|
+ content: userMessage,
|
|
|
+ };
|
|
|
+ const ctrlAbout = new AbortController();
|
|
|
+ fetchEventSource(
|
|
|
+ "https://fls-ai.pingan.com.cn/openapi/ai/intelligent-tutoring/task/dialogue",
|
|
|
+ {
|
|
|
+ method: "POST",
|
|
|
+ headers: {
|
|
|
+ "Content-Type": "application/json", // 文本返回格式
|
|
|
+ },
|
|
|
+ body: JSON.stringify(params),
|
|
|
+ signal: ctrlAbout.signal,
|
|
|
+ openWhenHidden: true, // 在浏览器标签页隐藏时保持与服务器的EventSource连接
|
|
|
+ onmessage(res) {
|
|
|
+ // 操作流式数据
|
|
|
+ console.log(JSON.parse(res.data), "==========");
|
|
|
+ },
|
|
|
+ onclose(res) {
|
|
|
+ // 关闭流
|
|
|
+ },
|
|
|
+ onerror(error) {
|
|
|
+ // 返回流报错
|
|
|
+ },
|
|
|
+ }
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+const chatGpt1 = async (userMessage) => {
|
|
|
+ const downloadUrl =
|
|
|
+ "https://fls-ai.pingan.com.cn/openapi/ai/llm/forward/api/ai/nlp/dialogue";
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await axios.post(
|
|
|
+ downloadUrl,
|
|
|
+ {
|
|
|
+ conversationId: "1976cfe3a5174f9ba768677f789cad7e",
|
|
|
+ content: `${userMessage},请输出纯文本的回答,不要使用markdown输出`,
|
|
|
+ messageId: Date.now(),
|
|
|
+ applicationId: "",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ headers: {
|
|
|
+ "Team-Id": "123456",
|
|
|
+ Authorization: "9b2f86c99b5847739045e6b85f355301",
|
|
|
+ "Content-Type": "application/json",
|
|
|
+ },
|
|
|
+ onDownloadProgress: ({ event }) => {
|
|
|
+ const xhr = event.target;
|
|
|
+ const { responseText } = xhr;
|
|
|
+ console.log(responseText);
|
|
|
+ // 更新聊天列表
|
|
|
+ updateChatList(responseText);
|
|
|
+ // 用于语音播报
|
|
|
+ queue.add(async () => {
|
|
|
+ await splitMessage(responseText);
|
|
|
+ });
|
|
|
+ },
|
|
|
+ }
|
|
|
+ );
|
|
|
+ updateChatList("", false);
|
|
|
+ } catch (error) {
|
|
|
+ updateChatList("", false);
|
|
|
+ console.error("下载文件时出错:", error);
|
|
|
+ }
|
|
|
+};
|
|
|
+const next = () => {
|
|
|
+ router.push({
|
|
|
+ name: "result",
|
|
|
+ query: { conversationId: conversationId.value },
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+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();
|
|
|
+ setTimeout(() => {
|
|
|
+ loading.value = false;
|
|
|
+ }, 3000);
|
|
|
+});
|
|
|
+
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <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"
|
|
|
+ />
|
|
|
+ </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"
|
|
|
+ />
|
|
|
+ <div v-show="rate >= 100 || taskStatus" class="next-btn">
|
|
|
+ <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*/
|
|
|
+ user-select: none;
|
|
|
+}
|
|
|
+</style>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.main {
|
|
|
+ width: 100vw;
|
|
|
+ height: 100vh;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+.rate-circle {
|
|
|
+ position: fixed;
|
|
|
+ right: 20px;
|
|
|
+ top: 70px;
|
|
|
+ z-index: 9999;
|
|
|
+}
|
|
|
+.next-btn {
|
|
|
+ position: fixed;
|
|
|
+ bottom: calc(20px + env(safe-area-inset-bottom));
|
|
|
+ width: 100%;
|
|
|
+ box-sizing: border-box;
|
|
|
+ padding: 0 20px;
|
|
|
+}
|
|
|
+.loading {
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ background-color: rgba(0, 0, 0, 0.5);
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ z-index: 9999;
|
|
|
+}
|
|
|
+</style>
|