| | |
| | | import { nextTick, onDeactivated, onMounted, ref } from 'vue'; |
| | | import { SignJWT } from 'jose'; |
| | | import { nextTick, onDeactivated, onMounted, ref } from 'vue'; |
| | | |
| | | import axios from 'axios'; |
| | | import { ElMessage } from 'element-plus'; |
| | | import { markdownToTxt } from 'markdown-to-txt'; |
| | | import './libs/duix.js'; |
| | | import { questionStreamByPost } from '/@/api/ai/chat'; |
| | | import { activeGroupType, activeRoomId } from '/@/stores/chatRoom'; |
| | | import { markdownToTxt } from 'markdown-to-txt'; |
| | | |
| | | import type { QuestionLifecycle } from '../../../types'; |
| | | export type UseDigitalHumanProps = { |
| | | container: string; |
| | | autoSendMessage: (question: string, lifecycleCall?: QuestionLifecycle) => void; |
| | | }; |
| | | |
| | | export const getKnowledgePlainText = (item) => { |
| | | let result = ''; |
| | | const knowledgeText = item.knowledge.reduce((acc, cur) => { |
| | | const mdText = cur.answer; |
| | | const linkText = cur.metadata?.Title; |
| | | if (linkText) { |
| | | return `${mdText}\n\n${linkText}`; |
| | | } |
| | | return acc + mdText; |
| | | }, ''); |
| | | // const conclusionText = |
| | | // item.conclusion |
| | | // ?.filter((item) => !!item.report) |
| | | // .map((item) => item.report) |
| | | // .join('\n\n') ?? ''; |
| | | // result += knowledgeText + conclusionText; |
| | | result = knowledgeText; |
| | | return markdownToTxt(result); |
| | | }; |
| | | |
| | | export const useDigitalHuman = (props: UseDigitalHumanProps) => { |
| | | const { container } = props; |
| | | const { container, autoSendMessage } = props; |
| | | const digitalHumanWidth = '240px'; |
| | | const duixConfig = { |
| | | appId: '1356792813207031808', |
| | | appKey: '659b068e-900c-4fe5-bb96-3ca70fe0aae4', |
| | | sign: '', |
| | | conversationId: '1909088110274277378', |
| | | conversationId: '1911336603251347458', |
| | | /** @description 过期时间(小时) */ |
| | | expired: 12, |
| | | }; |
| | |
| | | digitalHumanIsShow.value = false; |
| | | resetDuixStatus(); |
| | | }; |
| | | /** |
| | | * 检查数字人是否可用 |
| | | */ |
| | | const checkIsUseable = async () => { |
| | | const config = { |
| | | method: 'get', |
| | | url: `https://duix.guiji.ai/duix-openapi-v2/v1/getconcurrentNumber?appId=${duixConfig.appId}`, |
| | | headers: { |
| | | priority: 'u=1, i', |
| | | sig: duixConfig.sign, |
| | | }, |
| | | }; |
| | | |
| | | const response = await axios(config); |
| | | const data = response.data.data; |
| | | const total = data.totalConcurrentNumber; |
| | | const user = data.userConcurrentNumber; |
| | | if (total === null || total === 0) { |
| | | return false; |
| | | } |
| | | if (total !== null && total === user) { |
| | | return false; |
| | | } |
| | | |
| | | return true; |
| | | }; |
| | | |
| | | const resetDuixStatus = () => { |
| | | humanIsLoading.value = true; |
| | |
| | | // isSpeaking.value = false; |
| | | digitalHumanIsShow.value = false; |
| | | duix?.stop(); |
| | | }; |
| | | const getPlainText = (item) => { |
| | | let result = ''; |
| | | const knowledgeText = item.knowledge.reduce((acc, cur) => { |
| | | const mdText = cur.answer; |
| | | const linkText = cur.metadata?.Title; |
| | | if (linkText) { |
| | | return `${mdText}\n\n${linkText}`; |
| | | } |
| | | return acc + mdText; |
| | | }, ''); |
| | | // const conclusionText = |
| | | // item.conclusion |
| | | // ?.filter((item) => !!item.report) |
| | | // .map((item) => item.report) |
| | | // .join('\n\n') ?? ''; |
| | | // result += knowledgeText + conclusionText; |
| | | result = knowledgeText; |
| | | return markdownToTxt(result); |
| | | }; |
| | | |
| | | let isWaitingSpeak = false; |
| | |
| | | duix.speak({ |
| | | content, |
| | | }); |
| | | }; |
| | | |
| | | const startDuix = () => { |
| | | const conversationId = duixConfig.conversationId; // duix平台会话id |
| | | |
| | | duix |
| | | .start({ conversationId, openAsr: true, wipeGreen: true }) |
| | | .then((res) => { |
| | | console.info('start', res); |
| | | }) |
| | | .catch((err) => { |
| | | console.error('start error', err); |
| | | }); |
| | | }; |
| | | |
| | | const initDuix = () => { |
| | |
| | | duix.on('intialSucccess', () => { |
| | | console.info('intialSucccess'); |
| | | // 此时初始化成功,可调用start |
| | | duix.start({ conversationId, openAsr: true,wipeGreen:true }).then((res) => { |
| | | console.info('start', res); |
| | | }); |
| | | startDuix(); |
| | | }); |
| | | duix.on('bye', (data) => { |
| | | console.info('bye', data); |
| | |
| | | duix.on('speakEnd', (data) => { |
| | | if (!isWaitingSpeak) { |
| | | isReceiveRes.value = false; |
| | | duix.openAsr().then((...a) => {}); |
| | | } |
| | | }); |
| | | duix.on('speakSection', (data) => { |
| | |
| | | if (isReceiveRes.value) { |
| | | return; |
| | | } |
| | | duix.closeAsr().then((...a) => {}); |
| | | |
| | | let hasResult = false; |
| | | isReceiveRes.value = true; |
| | | try { |
| | | isWaitingSpeak = true; |
| | | duix.speak({ |
| | | content: '已收到您的问题,正在思考中...请稍等', |
| | | }); |
| | | questionStreamByPost( |
| | | { |
| | | question: data, |
| | | history_group_id: activeRoomId.value, |
| | | raw_mode: false, |
| | | group_type: activeGroupType.value, |
| | | is_digital_human: true, |
| | | }, |
| | | (chunkRes) => { |
| | | if (chunkRes.mode === 'result' && chunkRes.value?.answer_type === 'knowledge') { |
| | | const plainText = getPlainText(chunkRes.value); |
| | | hasResult = true; |
| | | speakContent(plainText); |
| | | } else if (!chunkRes.value?.json_ok && chunkRes.value?.err_code === 'MESSAGE') { |
| | | if (hasResult) return; |
| | | hasResult = true; |
| | | speakContent(chunkRes.value.json_msg); |
| | | } |
| | | isWaitingSpeak = true; |
| | | duix.speak({ |
| | | content: '已收到您的问题,正在思考中...请稍等', |
| | | }); |
| | | |
| | | if (chunkRes.mode === 'finish') { |
| | | if (!hasResult) { |
| | | speakContent('暂时无法口头描述你所说的问题'); |
| | | } else { |
| | | hasResult = false; |
| | | } |
| | | // isReceiveRes.value = false; |
| | | let content = ''; |
| | | |
| | | try { |
| | | autoSendMessage(data, { |
| | | receiveText: (text: string) => { |
| | | content += text; |
| | | }, |
| | | finish: () => { |
| | | if (!content) { |
| | | speakContent('暂时无法口头描述你所说的问题'); |
| | | } else { |
| | | speakContent(content); |
| | | } |
| | | } |
| | | ); |
| | | }, |
| | | }); |
| | | } catch (error) { |
| | | console.error(error); |
| | | isReceiveRes.value = false; |
| | | } |
| | | }); |
| | |
| | | |
| | | let hasInitDuix = false; |
| | | let duix: any; |
| | | const openDigitalHuman = () => { |
| | | const openDigitalHuman = async () => { |
| | | duixConfig.sign = await createSig(duixConfig.appId, duixConfig.appKey, 60 * 60 * duixConfig.expired); |
| | | const isUsable = await checkIsUseable(); |
| | | if (!isUsable) { |
| | | ElMessage.warning('"资源占用中,请检查后再试~"'); |
| | | return; |
| | | } |
| | | digitalHumanIsShow.value = true; |
| | | |
| | | nextTick(async () => { |
| | | duixConfig.sign = await createSig(duixConfig.appId, duixConfig.appKey, 60 * 60 * duixConfig.expired); |
| | | nextTick(() => { |
| | | if (!hasInitDuix) { |
| | | hasInitDuix = true; |
| | | |
| | | duix = new DUIX(); |
| | | initDuix(); |
| | | } else { |
| | | duix.start({ conversationId: duixConfig.conversationId, openAsr: true }).then((res) => { |
| | | console.info('start', res); |
| | | }); |
| | | startDuix(); |
| | | } |
| | | }); |
| | | }; |
| | |
| | | isHumanTalking: isReceiveRes, |
| | | closeDigitalHuman, |
| | | humanIsLoading, |
| | | digitalHumanWidth, |
| | | }; |
| | | }; |