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