import { SignJWT } from 'jose';
|
import { nextTick, onDeactivated, onMounted, ref } from 'vue';
|
|
import { markdownToTxt } from 'markdown-to-txt';
|
import './libs/duix.js';
|
import { questionStreamByPost } from '/@/api/ai/chat';
|
import { activeGroupType, activeRoomId } from '/@/stores/chatRoom';
|
import axios from 'axios';
|
import { ElMessage } from 'element-plus';
|
|
export type UseDigitalHumanProps = {
|
container: string;
|
};
|
|
export const useDigitalHuman = (props: UseDigitalHumanProps) => {
|
const { container } = props;
|
const duixConfig = {
|
appId: '1356792813207031808',
|
appKey: '659b068e-900c-4fe5-bb96-3ca70fe0aae4',
|
sign: '',
|
conversationId: '1909088110274277378',
|
/** @description 过期时间(小时) */
|
expired: 12,
|
};
|
|
// 是否已接口相应
|
const isReceiveRes = ref(false);
|
const humanIsLoading = ref(true);
|
|
const digitalHumanIsShow = ref(false);
|
const closeDigitalHuman = () => {
|
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;
|
isReceiveRes.value = false;
|
// 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;
|
|
const speakContent = (content: string) => {
|
// 打断之前的已收到xxx
|
duix.break();
|
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 = () => {
|
const sign = duixConfig.sign; // sign由服务端生成
|
const conversationId = duixConfig.conversationId; // duix平台会话id
|
if (!sign || !conversationId) {
|
return alert('参数不能为空');
|
}
|
duix.on('error', (data) => {
|
console.error(data);
|
});
|
duix.on('intialSucccess', () => {
|
console.info('intialSucccess');
|
// 此时初始化成功,可调用start
|
startDuix();
|
});
|
duix.on('bye', (data) => {
|
console.info('bye', data);
|
});
|
duix.on('progress', (progress) => {
|
console.info('progress', progress);
|
});
|
duix.on('show', () => {
|
console.info('show');
|
humanIsLoading.value = false;
|
// 此时可确认视频已
|
// (document.querySelector('#modal') as HTMLElement).style.display = 'none';
|
});
|
duix.on('openAsrSuccess', () => {
|
console.info('openAsrSuccess');
|
});
|
duix.on('asrClose', () => {
|
console.info('asrClose');
|
});
|
duix.on('speakStart', (data) => {
|
// isSpeaking.value = true;
|
console.info('speakStart', data);
|
});
|
duix.on('speakEnd', (data) => {
|
if (!isWaitingSpeak) {
|
isReceiveRes.value = false;
|
duix.openAsr().then((...a) => {});
|
}
|
});
|
duix.on('speakSection', (data) => {
|
console.info('speakSection', data);
|
});
|
duix.on('speakError', (data) => {
|
console.info('speakError', data);
|
});
|
duix.on('asrResult', (data) => {
|
console.info('asrResult', 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);
|
}
|
|
if (chunkRes.mode === 'finish') {
|
if (!hasResult) {
|
speakContent('暂时无法口头描述你所说的问题');
|
} else {
|
hasResult = false;
|
}
|
// isReceiveRes.value = false;
|
}
|
}
|
);
|
} catch (error) {
|
console.error(error);
|
isReceiveRes.value = false;
|
}
|
});
|
duix.on('report', (data) => {
|
// console.info('report', data)
|
});
|
duix
|
.init({
|
sign,
|
containerLable: container,
|
})
|
.then((data) => {
|
console.info('init', data);
|
});
|
};
|
|
let hasInitDuix = false;
|
let duix: any;
|
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(() => {
|
if (!hasInitDuix) {
|
hasInitDuix = true;
|
|
duix = new DUIX();
|
initDuix();
|
} else {
|
startDuix();
|
}
|
});
|
};
|
|
function createSig(appId, appKey, sigExp) {
|
const now = Math.floor(Date.now() / 1000);
|
const expiresAt = now + sigExp;
|
|
const sign = new SignJWT({ appId })
|
.setProtectedHeader({ alg: 'HS256' })
|
.setIssuedAt(now)
|
.setExpirationTime(expiresAt)
|
.sign(new TextEncoder().encode(appKey));
|
return sign;
|
}
|
|
onMounted(() => {
|
window.addEventListener('beforeunload', () => {
|
closeDigitalHuman();
|
});
|
});
|
|
onDeactivated(() => {
|
closeDigitalHuman();
|
});
|
|
return {
|
digitalHumanIsShow,
|
openDigitalHuman,
|
isHumanTalking: isReceiveRes,
|
closeDigitalHuman,
|
humanIsLoading,
|
};
|
};
|