| | |
| | | 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; |
| | | displayMessageList: ComputedRef<ChatMessage[]>; |
| | | }; |
| | | const activeSpeakItem = ref(null); |
| | | |
| | | export const useAssistantContentOpt = (option: AssistantContentOptOption) => { |
| | | const { sendChatMessage, displayMessageList } = option; |
| | | const isSpeaking = ref(false); |
| | | const { sendChatMessage } = option; |
| | | const { copy } = useClipboard(); |
| | | const preQuestion = ref(null); |
| | | |
| | | 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) => { |
| | |
| | | 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( |
| | |
| | | // feedbackContent.value = ''; |
| | | // } |
| | | // ); |
| | | const showAskMore = computed(() => { |
| | | if (!displayMessageList.value || displayMessageList.value.length === 0) return false; |
| | | const last = displayMessageList.value.at(-1); |
| | | const isShow = last?.role === RoleEnum.assistant && last?.content?.values && last.content?.askMoreList?.length > 0; |
| | | const result = isShow && !isSharePage.value; |
| | | return result; |
| | | }); |
| | | |
| | | const showFixQuestion = (item) => { |
| | | const isShow = item?.role === RoleEnum.assistant && item.content?.origin?.sample_question?.length > 0; |
| | | const isShow = item?.role === RoleEnum.assistant && item.content?.origin?.sample_question?.length > 0 && !isSharePage.value; |
| | | return isShow; |
| | | }; |
| | | const askMoreClick = (item) => { |
| | |
| | | } |
| | | }; |
| | | |
| | | 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, |
| | |
| | | feedbackClick, |
| | | askMoreClick, |
| | | fixQuestionClick, |
| | | preQuestion, |
| | | showAskMore, |
| | | showFixQuestion, |
| | | speechClick, |
| | | isSpeaking, |
| | | isItemSpeaking, |
| | | checkIsText, |
| | | }; |
| | | }; |