|
@@ -1,5 +1,5 @@
|
|
<script setup>
|
|
<script setup>
|
|
-import { ref, computed, onMounted, onBeforeUnmount } from "vue";
|
|
|
|
|
|
+import { ref, computed, onMounted, onBeforeUnmount, onUnmounted } from "vue";
|
|
import BG from "./components/BG.vue";
|
|
import BG from "./components/BG.vue";
|
|
import BottomArea from "./components/BottomArea.vue";
|
|
import BottomArea from "./components/BottomArea.vue";
|
|
import ChatList from "./components/ChatList.vue";
|
|
import ChatList from "./components/ChatList.vue";
|
|
@@ -9,7 +9,7 @@ import axios from "axios";
|
|
import { fetchEventSource } from "@microsoft/fetch-event-source";
|
|
import { fetchEventSource } from "@microsoft/fetch-event-source";
|
|
import { useRoute, useRouter } from "vue-router";
|
|
import { useRoute, useRouter } from "vue-router";
|
|
import delay from "delay";
|
|
import delay from "delay";
|
|
-import { fetchTaskStatus } from "./utils/api";
|
|
|
|
|
|
+import { fetchTaskEnd, fetchTaskStatus } from "./utils/api";
|
|
|
|
|
|
const route = useRoute();
|
|
const route = useRoute();
|
|
const router = useRouter();
|
|
const router = useRouter();
|
|
@@ -19,6 +19,11 @@ const sessionId = ref("");
|
|
const taskStatus = ref(false)
|
|
const taskStatus = ref(false)
|
|
const loading = ref(true); //初始化进入的加载状态
|
|
const loading = ref(true); //初始化进入的加载状态
|
|
|
|
|
|
|
|
+const lastMessageTime = ref(Date.now()); // 上次发送消息的时间
|
|
|
|
+let inactivityTimer = null; // 定时器
|
|
|
|
+let warned = false; // 3 分钟提醒是否已触发
|
|
|
|
+let ended = false; // 5 分钟结束是否已触发
|
|
|
|
+
|
|
const currentRate = ref(0);
|
|
const currentRate = ref(0);
|
|
const currentRound = computed(() => {
|
|
const currentRound = computed(() => {
|
|
const filteredMessages = chatList.value.filter(
|
|
const filteredMessages = chatList.value.filter(
|
|
@@ -27,6 +32,63 @@ const currentRound = computed(() => {
|
|
);
|
|
);
|
|
return Math.floor(filteredMessages.length / 2);
|
|
return Math.floor(filteredMessages.length / 2);
|
|
});
|
|
});
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+function startInactivityTimer() {
|
|
|
|
+ console.log("开始计时");
|
|
|
|
+ // clearInterval(inactivityTimer);
|
|
|
|
+ inactivityTimer = setInterval(async () => {
|
|
|
|
+ const diff = (Date.now() - lastMessageTime.value) / 1000; // 单位: 秒
|
|
|
|
+ console.log(diff,"开始计时1");
|
|
|
|
+ //diff >= 180 && diff < 300
|
|
|
|
+ if (diff >= 30 && diff < 60 && !warned) {
|
|
|
|
+ console.log("开始计时3");
|
|
|
|
+ // 超过 3 分钟没消息 -> 提示
|
|
|
|
+ console.log("超过3分钟但小于5分钟,发送提示消息");
|
|
|
|
+ const text = "还有事儿吗?没事儿就挂了~";
|
|
|
|
+ pushTimeoutMessage(text);
|
|
|
|
+ // const text = "还有事儿吗?没事儿就挂了~";
|
|
|
|
+ // updateChatList(text, false);
|
|
|
|
+ // queue.add(async () => await splitMessage(text));
|
|
|
|
+ warned = true;
|
|
|
|
+ }
|
|
|
|
+ // console.log("开始计时2");
|
|
|
|
+ if (diff >= 60 && !ended) {
|
|
|
|
+ ended = true; // 🔑 立即标记已结束
|
|
|
|
+ clearInterval(inactivityTimer); // 🔑 停止定时器
|
|
|
|
+ inactivityTimer = null;
|
|
|
|
+ try {
|
|
|
|
+ const res = await fetchTaskEnd(conversationId.value); // 🔑 只调用一次
|
|
|
|
+ if (res.code === 200) {
|
|
|
|
+ taskStatus.value = true;
|
|
|
|
+ console.log("[Timer] 会话结束接口调用成功");
|
|
|
|
+ }
|
|
|
|
+ } catch (err) {
|
|
|
|
+ console.error("[Timer] 会话结束接口出错:", err);
|
|
|
|
+ }
|
|
|
|
+ // const res = await fetchTaskEnd(conversationId.value);
|
|
|
|
+ // if (res.code === 200) {
|
|
|
|
+ // taskStatus.value = true
|
|
|
|
+ // }
|
|
|
|
+ // ended = true;
|
|
|
|
+ // clearInterval(inactivityTimer);
|
|
|
|
+ }
|
|
|
|
+ }, 1000);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+function resetInactivityTimer() {
|
|
|
|
+ // 停掉旧的
|
|
|
|
+ if (inactivityTimer) {
|
|
|
|
+ clearInterval(inactivityTimer);
|
|
|
|
+ inactivityTimer = null;
|
|
|
|
+ }
|
|
|
|
+ // 重新开始
|
|
|
|
+ warned = false;
|
|
|
|
+ ended = false;
|
|
|
|
+ lastMessageTime.value = Date.now();
|
|
|
|
+ startInactivityTimer();
|
|
|
|
+}
|
|
|
|
+
|
|
// 当前进度百分比整数部分
|
|
// 当前进度百分比整数部分
|
|
const rate = computed(() => {
|
|
const rate = computed(() => {
|
|
return Math.floor((currentRound.value / round.value) * 100);
|
|
return Math.floor((currentRound.value / round.value) * 100);
|
|
@@ -57,7 +119,19 @@ const updateChatList = async (message = "", loading = true) => {
|
|
}
|
|
}
|
|
}, 50);
|
|
}, 50);
|
|
};
|
|
};
|
|
|
|
+// 追加超时提示消息,不覆盖最后一条AI消息
|
|
|
|
+const pushTimeoutMessage = (text) => {
|
|
|
|
+ chatList.value.push({
|
|
|
|
+ id: Date.now(),
|
|
|
|
+ type: "gpt", // 标识AI消息
|
|
|
|
+ text: text,
|
|
|
|
+ loading: false, // 超时提示不需要流式显示
|
|
|
|
+ timestamp: new Date(),
|
|
|
|
+ });
|
|
|
|
|
|
|
|
+ localStorage.setItem("chatHistory", JSON.stringify(chatList.value));
|
|
|
|
+ queue.add(async () => await splitMessage(text)); // 可继续TTS播报
|
|
|
|
+};
|
|
// 创建一个队列实例,设置并发数为 1
|
|
// 创建一个队列实例,设置并发数为 1
|
|
const queue = new PQueue({ concurrency: 3 });
|
|
const queue = new PQueue({ concurrency: 3 });
|
|
const queue1 = new PQueue({ concurrency: 1 });
|
|
const queue1 = new PQueue({ concurrency: 1 });
|
|
@@ -86,6 +160,7 @@ const splitMessage = async (str) => {
|
|
const handleStartRecord = async () => {
|
|
const handleStartRecord = async () => {
|
|
queue.clear();
|
|
queue.clear();
|
|
queue1.clear();
|
|
queue1.clear();
|
|
|
|
+ resetInactivityTimer();
|
|
};
|
|
};
|
|
|
|
|
|
const generateSessionId = () => {
|
|
const generateSessionId = () => {
|
|
@@ -118,6 +193,7 @@ const handleStopRecord = (message) => {
|
|
];
|
|
];
|
|
sessionId.value = generateSessionId();
|
|
sessionId.value = generateSessionId();
|
|
chatGpt(message);
|
|
chatGpt(message);
|
|
|
|
+ // lastMessageTime.value = Date.now(); // 用户发送消息,计时器重置
|
|
};
|
|
};
|
|
// let audioQueue = []; // 音频队列,保存按请求顺序的音频数据
|
|
// let audioQueue = []; // 音频队列,保存按请求顺序的音频数据
|
|
// let playingQueue = []; // 用来记录当前正在播放的音频序号
|
|
// let playingQueue = []; // 用来记录当前正在播放的音频序号
|
|
@@ -348,6 +424,11 @@ const playAudio = (audioData) => {
|
|
// };
|
|
// };
|
|
|
|
|
|
const chatGpt = async (userMessage) => {
|
|
const chatGpt = async (userMessage) => {
|
|
|
|
+ // 重置状态
|
|
|
|
+ lastMessageTime.value = Date.now();
|
|
|
|
+ warned = false;
|
|
|
|
+ ended = false;
|
|
|
|
+
|
|
const downloadUrl = "/openapi-prd/ai/intelligent-tutoring/task/dialogue";
|
|
const downloadUrl = "/openapi-prd/ai/intelligent-tutoring/task/dialogue";
|
|
i = 0;
|
|
i = 0;
|
|
queue2.add(() => delay(3000));
|
|
queue2.add(() => delay(3000));
|
|
@@ -499,12 +580,17 @@ const handleTaskStatus = async () => {
|
|
|
|
|
|
// 在组件挂载时调用
|
|
// 在组件挂载时调用
|
|
onMounted(() => {
|
|
onMounted(() => {
|
|
|
|
+ startInactivityTimer();
|
|
|
|
+ // timer = setInterval(checkTimeout, 1000); // 每秒检查一次
|
|
loadChatHistory();
|
|
loadChatHistory();
|
|
setTimeout(() => {
|
|
setTimeout(() => {
|
|
loading.value = false;
|
|
loading.value = false;
|
|
}, 3000);
|
|
}, 3000);
|
|
});
|
|
});
|
|
|
|
|
|
|
|
+onUnmounted(() => {
|
|
|
|
+ clearInterval(inactivityTimer); // 组件卸载时清除定时器
|
|
|
|
+})
|
|
</script>
|
|
</script>
|
|
|
|
|
|
<template>
|
|
<template>
|
|
@@ -514,12 +600,12 @@ onMounted(() => {
|
|
<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>
|
|
<div class="tooltip">
|
|
<div class="tooltip">
|
|
- <p>车辆信息:24年10月起租金额14万,租期36月,已还9期</p>
|
|
|
|
- <p>提前结清:121,021.62元,预期利益损失:6,535.05元</p>
|
|
|
|
- <p>全额打款:137,362.73元,两者差额:16,341.11元 </p>
|
|
|
|
- <p>下一期租金:5,068.99元(本金3,421元,利息1,647.99元) </p>
|
|
|
|
- <p>违约金规则:1-12期6%,13-36期5%(两段式)</p>
|
|
|
|
- <p>租金减免测算:最高8,500元 </p>
|
|
|
|
|
|
+ <p>车辆信息:24年10月起租金额14万,租期36月,已还9期</p>
|
|
|
|
+ <p>提前结清:121,021.62元,预期利益损失:6,535.05元</p>
|
|
|
|
+ <p>全额打款:137,362.73元,两者差额:16,341.11元 </p>
|
|
|
|
+ <p>下一期租金:5,068.99元(本金3,421元,利息1,647.99元) </p>
|
|
|
|
+ <p>违约金规则:1-12期6%,13-36期5%(两段式)</p>
|
|
|
|
+ <p>租金减免测算:最高8,500元 </p>
|
|
</div>
|
|
</div>
|
|
<BG />
|
|
<BG />
|
|
<div class="loading" v-show="loading">
|
|
<div class="loading" v-show="loading">
|
|
@@ -567,8 +653,10 @@ onMounted(() => {
|
|
width: 85%;
|
|
width: 85%;
|
|
position: fixed;
|
|
position: fixed;
|
|
top: 150px;
|
|
top: 150px;
|
|
- left: 50%; /* 左边到视口宽度的一半 */
|
|
|
|
- transform: translateX(-50%); /* 再左移自身宽度的一半,实现居中 */
|
|
|
|
|
|
+ left: 50%;
|
|
|
|
+ /* 左边到视口宽度的一半 */
|
|
|
|
+ transform: translateX(-50%);
|
|
|
|
+ /* 再左移自身宽度的一半,实现居中 */
|
|
z-index: 9999;
|
|
z-index: 9999;
|
|
font-size: 12px;
|
|
font-size: 12px;
|
|
padding: 8px 12px;
|
|
padding: 8px 12px;
|