123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458 |
- <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";
- const route = useRoute();
- const router = useRouter();
- const conversationId = computed(() => route.query.taskId); // 会话ID
- const round = computed(() => route.query.round); // 轮次
- const sessionId = ref("");
- 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);
- // 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 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, "==========");
- await 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);
- };
- 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 playTTS = async (ttsMessage) => {
- try {
- const res = await axios.get(
- `/openapi-prd/ai/ttt/synthesize?text=${ttsMessage}&sessionId=${sessionId.value}`,
- {
- responseType: "arraybuffer",
- }
- );
- 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 playAudio = (audioData, options = {}) => {
- 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) => 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);
- });
- };
- 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);
- });
- // 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);
- }
- },
- }
- );
- //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");
- if (chatHistory) {
- const history = JSON.parse(chatHistory);
- history.forEach((item) => {
- chatList.value.push(item);
- });
- }
- };
- // 在组件挂载时调用
- onMounted(() => {
- loadChatHistory();
- });
- // onBeforeUnmount(() => {
- // localStorage.removeItem("chatHistory");
- // });
- </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 />
- <ChatList :chatList="chatList" />
- <BottomArea
- @startRecord="handleStartRecord"
- @stopRecord="handleStopRecord"
- v-show="rate < 100"
- />
- <div v-show="rate >= 100" 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;
- }
- </style>
|