From 2b8b2cac4fe3f05474459a034bc4034f2d7aa0cb Mon Sep 17 00:00:00 2001
From: wujingjing <gersonwu@qq.com>
Date: 星期三, 09 四月 2025 10:14:06 +0800
Subject: [PATCH] Merge branch 'test' into huishui_iframe

---
 src/components/chat/hooks/useAssistantContentOpt.ts |  122 +++++++++++++++++++++++++++++++++++-----
 1 files changed, 107 insertions(+), 15 deletions(-)

diff --git a/src/components/chat/hooks/useAssistantContentOpt.ts b/src/components/chat/hooks/useAssistantContentOpt.ts
index ff2ec9f..5678b93 100644
--- a/src/components/chat/hooks/useAssistantContentOpt.ts
+++ b/src/components/chat/hooks/useAssistantContentOpt.ts
@@ -1,31 +1,63 @@
 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';
 export type AssistantContentOptOption = {
 	sendChatMessage: any;
 };
+const activeSpeakItem = ref(null);
 
 export const useAssistantContentOpt = (option: AssistantContentOptOption) => {
+	const isSpeaking = ref(false);
 	const { sendChatMessage } = option;
 	const { copy } = useClipboard();
 
 	const copyClick = (item) => {
-		const type = item.content.type;
-		let text = '';
-		if (type === AnswerType.Knowledge) {
-			text = item.content.values?.map((item) => item.answer).join('\n\n') ?? '';
-		} else {
-			text = item.content.values;
-		}
+		const isText = checkIsText(item);
+		if (!isText) return;
+		const text = getPlainText(item);
 		ElMessage.success('澶嶅埗鎴愬姛');
 		copy(text);
+	};
+
+	const checkIsText = (item) => {
+		const isText = item?.content?.values?.some((item) => item?.content?.type === AnswerType.Knowledge)  || item?.conclusion?.length > 0;
+		return isText;
+	};
+
+	const getPlainText = (item) => {
+		let result = '';
+		const knowledgeText = item.content.values
+			.filter((item) => {
+				const type = item?.content?.type;
+				return type === AnswerType.Knowledge;
+			})
+			.reduce((acc, cur) => {
+				const answer = cur?.content?.values
+					?.map((item) => {
+						const mdText = item.answer;
+						const linkText = item.metadata?.Title;
+						if (linkText) {
+							return `${mdText}\n\n${linkText}`;
+						}
+						return mdText;
+					})
+					.join('\n\n');
+				return acc + answer;
+			}, '');
+
+		const conclusionText =
+			item.conclusion
+				?.filter((item) => !!item.report)
+				.map((item) => item.report)
+				.join('\n\n') ?? '';
+		result += knowledgeText + conclusionText;
+		return markdownToTxt(result);
 	};
 
 	const likeClick = async (item) => {
@@ -63,10 +95,15 @@
 		feedbackIsShow.value = true;
 		nextTick(() => {
 			feedbackPosition.value = {
-				x: -feedbackPanelRef.value[index].$el.clientWidth + offsetX,
-				y: -feedbackPanelRef.value[index].$el.clientHeight + offsetY,
+				x: -feedbackPanelRef.value[index]?.$el.clientWidth + offsetX,
+				y: -feedbackPanelRef.value[index]?.$el.clientHeight + offsetY,
 			};
 		});
+	};
+
+	const isItemSpeaking = (item) => {
+		const checkSpeak = activeSpeakItem.value === item && isSpeaking.value;
+		return checkSpeak;
 	};
 
 	onClickOutside(
@@ -85,9 +122,8 @@
 	// 	}
 	// );
 
-
 	const showFixQuestion = (item) => {
-		const isShow = item?.role === RoleEnum.assistant  && item.content?.origin?.sample_question?.length > 0 && !isSharePage.value;
+		const isShow = item?.role === RoleEnum.assistant && item.content?.origin?.sample_question?.length > 0 && !isSharePage.value;
 		return isShow;
 	};
 	const askMoreClick = (item) => {
@@ -108,6 +144,58 @@
 		}
 	};
 
+	let isEnterStop = false;
+
+	const resetSpeak = () => {
+		isSpeaking.value = false;
+		isEnterStop = false;
+		const instance = BrowserSpeechSynthesis.getInstance();
+		instance.cancel();
+		activeSpeakItem.value = null;
+	};
+
+	const speechClick = (item) => {
+		if (!checkIsText(item)) return;
+		if (activeSpeakItem.value !== item) {
+			resetSpeak();
+		}
+		isSpeaking.value = !isSpeaking.value;
+		if (isSpeaking.value) {
+			startSpeechClick(item);
+		} else {
+			stopSpeechClick();
+		}
+	};
+
+	const startSpeechClick = (item) => {
+		activeSpeakItem.value = item;
+
+		const instance = BrowserSpeechSynthesis.getInstance();
+		instance.onEnd(() => {
+			resetSpeak();
+		});
+		if (isEnterStop) {
+			instance.resume();
+		} else {
+			const text = getPlainText(item);
+			if (text) {
+				instance.speak(text);
+			}
+		}
+		isEnterStop = false;
+	};
+
+	const stopSpeechClick = () => {
+		isEnterStop = true;
+		const instance = BrowserSpeechSynthesis.getInstance();
+		instance.pause();
+	};
+
+	onDeactivated(() => {
+		const instance = BrowserSpeechSynthesis.getInstance();
+		instance.cancel();
+	});
+
 	return {
 		copyClick,
 		likeClick,
@@ -122,5 +210,9 @@
 		askMoreClick,
 		fixQuestionClick,
 		showFixQuestion,
+		speechClick,
+		isSpeaking,
+		isItemSpeaking,
+		checkIsText,
 	};
 };

--
Gitblit v1.9.3