From 12be081410e3da922da3bad10bd0b69925c092aa Mon Sep 17 00:00:00 2001
From: gerson <1405270578@qq.com>
Date: 星期二, 25 三月 2025 00:58:01 +0800
Subject: [PATCH] 语音合成

---
 src/components/chat/hooks/useAssistantContentOpt.ts |   30 +++++++++++++--
 src/components/chat/assistant/index.vue             |   35 +++++++++--------
 2 files changed, 44 insertions(+), 21 deletions(-)

diff --git a/src/components/chat/assistant/index.vue b/src/components/chat/assistant/index.vue
index f4cfd29..f1720ce 100644
--- a/src/components/chat/assistant/index.vue
+++ b/src/components/chat/assistant/index.vue
@@ -159,23 +159,23 @@
 						<div class="flex items-center justify-center size-[15px]" v-if="checkIsText(msg)">
 							<i class="p-2 ywifont ywicon-copy cursor-pointer hover:text-[#0284ff] hover:!text-[18px]" @click="copyClick(msg)" />
 						</div>
-						<template v-if="msg.content.errCode !== ErrorCode.Message">
-							<el-tooltip v-if="checkIsText(msg)" effect="dark" :content="isSpeaking ? '鏆傚仠鏈楄' : '璇煶鏈楄'" placement="top">
-								<div v-if="isSpeaking" class="cursor-pointer flex items-center space-x-[1px]" @click="speechClick(msg)">
-									<div class="w-[2px] h-[6px] bg-[#0284ff] animate-[soundWave_1.5s_ease-in-out_infinite]"></div>
-									<div class="w-[2px] h-[9px] bg-[#0284ff] animate-[soundWave_1.5s_ease-in-out_infinite_0.1s]"></div>
-									<div class="w-[2px] h-[12px] bg-[#0284ff] animate-[soundWave_1.5s_ease-in-out_infinite_0.2s]"></div>
-									<div class="w-[2px] h-[9px] bg-[#0284ff] animate-[soundWave_1.5s_ease-in-out_infinite_0.3s]"></div>
-									<div class="w-[2px] h-[6px] bg-[#0284ff] animate-[soundWave_1.5s_ease-in-out_infinite_0.4s]"></div>
-								</div>
+						<el-tooltip v-if="checkIsText(msg)" effect="dark" :content="isItemSpeaking(msg) ? '鏆傚仠鏈楄' : '璇煶鏈楄'" placement="top">
+							<div v-if="isItemSpeaking(msg)" class="cursor-pointer flex items-center space-x-[1px]" @click="speechClick(msg)">
+								<div class="w-[2px] h-[6px] bg-[#0284ff] animate-[soundWave_1.5s_ease-in-out_infinite]"></div>
+								<div class="w-[2px] h-[9px] bg-[#0284ff] animate-[soundWave_1.5s_ease-in-out_infinite_0.1s]"></div>
+								<div class="w-[2px] h-[12px] bg-[#0284ff] animate-[soundWave_1.5s_ease-in-out_infinite_0.2s]"></div>
+								<div class="w-[2px] h-[9px] bg-[#0284ff] animate-[soundWave_1.5s_ease-in-out_infinite_0.3s]"></div>
+								<div class="w-[2px] h-[6px] bg-[#0284ff] animate-[soundWave_1.5s_ease-in-out_infinite_0.4s]"></div>
+							</div>
 
-								<div v-else class="flex items-center justify-center size-[15px]">
-									<i
-										class="p-2 ywifont ywicon-shengyin cursor-pointer hover:text-[#0284ff] font-medium hover:!text-[18px]"
-										@click="speechClick(msg)"
-									/>
-								</div>
-							</el-tooltip>
+							<div v-else class="flex items-center justify-center size-[15px]">
+								<i
+									class="p-2 ywifont ywicon-shengyin cursor-pointer hover:text-[#0284ff] font-medium hover:!text-[18px]"
+									@click="speechClick(msg)"
+								/>
+							</div>
+						</el-tooltip>
+						<template v-if="msg.content.errCode !== ErrorCode.Message">
 							<el-tooltip effect="dark" content="鐐硅禐" placement="top">
 								<div class="flex items-center justify-center size-[15px]">
 									<i
@@ -325,7 +325,8 @@
 	fixQuestionClick,
 	showFixQuestion,
 	speechClick,
-	isSpeaking,
+
+	isItemSpeaking,
 	checkIsText,
 } = useAssistantContentOpt({
 	sendChatMessage,
diff --git a/src/components/chat/hooks/useAssistantContentOpt.ts b/src/components/chat/hooks/useAssistantContentOpt.ts
index 2cd2ef5..22df671 100644
--- a/src/components/chat/hooks/useAssistantContentOpt.ts
+++ b/src/components/chat/hooks/useAssistantContentOpt.ts
@@ -1,17 +1,16 @@
 import { ElMessage } from 'element-plus';
-import type { ComputedRef } from 'vue';
-import { computed, nextTick, ref } from 'vue';
+import { computed, nextTick, onDeactivated, ref } from 'vue';
 // import useClipboard from 'vue-clipboard3';
 import { onClickOutside, useClipboard } from '@vueuse/core';
-import type { ChatMessage } from '../model/types';
+import markdownToTxt from 'markdown-to-txt';
 import { AnswerState, AnswerType, RoleEnum } from '../model/types';
 import { SetHistoryAnswerState } from '/@/api/ai/chat';
 import { isSharePage } from '/@/stores/chatRoom';
 import BrowserSpeechSynthesis from '/@/utils/speech/synthesis';
-import markdownToTxt from 'markdown-to-txt';
 export type AssistantContentOptOption = {
 	sendChatMessage: any;
 };
+const activeSpeakItem = ref(null);
 
 export const useAssistantContentOpt = (option: AssistantContentOptOption) => {
 	const isSpeaking = ref(false);
@@ -94,6 +93,11 @@
 		});
 	};
 
+	const isItemSpeaking = (item) => {
+		const checkSpeak = activeSpeakItem.value === item && isSpeaking.value;
+		return checkSpeak;
+	};
+
 	onClickOutside(
 		computed(() => feedbackPanelRef.value?.[curFeedbackIndex.value]),
 		(e) => {
@@ -134,8 +138,18 @@
 
 	let isEnterStop = false;
 
+	const resetSpeak = () => {
+		isSpeaking.value = false;
+		isEnterStop = false;
+		const instance = BrowserSpeechSynthesis.getInstance();
+		instance.cancel();
+	};
+
 	const speechClick = (item) => {
 		if (!checkIsText(item)) return;
+		if (activeSpeakItem.value !== item) {
+			resetSpeak();
+		}
 		isSpeaking.value = !isSpeaking.value;
 		if (isSpeaking.value) {
 			startSpeechClick(item);
@@ -145,6 +159,8 @@
 	};
 
 	const startSpeechClick = (item) => {
+		activeSpeakItem.value = item;
+
 		const instance = BrowserSpeechSynthesis.getInstance();
 		if (isEnterStop) {
 			instance.resume();
@@ -163,6 +179,11 @@
 		instance.pause();
 	};
 
+	onDeactivated(() => {
+		const instance = BrowserSpeechSynthesis.getInstance();
+		instance.cancel();
+	});
+
 	return {
 		copyClick,
 		likeClick,
@@ -179,6 +200,7 @@
 		showFixQuestion,
 		speechClick,
 		isSpeaking,
+		isItemSpeaking,
 		checkIsText,
 	};
 };

--
Gitblit v1.9.3