已修改4个文件
173 ■■■■ 文件已修改
src/components/chat/components/ChatContainer.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/chat/components/playBar/hook/useDigitalHuman.ts 109 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layout/component/header/Header.vue 54 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/stores/chatRoom.ts 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/chat/components/ChatContainer.vue
@@ -22,7 +22,7 @@
                    <i class="ywifont ywicon-xiangxiajiantou !text-[20px]" />
                </div>
            </div>
            <div v-show="digitalHumanIsShow" v-loading="humanIsLoading" class="absolute right-28 bottom-[250px] w-[400px] h-[540px]">
            <div v-show="digitalHumanIsShow" v-loading="humanIsLoading" class="absolute right-28 bottom-[250px] w-[303.75px] h-[540px]">
                <span class="ywifont ywicon-guanbi text-[20px] cursor-pointer absolute top-2 right-2 z-[1]" @click="closeDigitalHuman"></span>
                <div class="duix-container h-full w-full"></div>
            </div>
src/components/chat/components/playBar/hook/useDigitalHuman.ts
@@ -1,10 +1,12 @@
import { nextTick, onDeactivated, onMounted, ref } from 'vue';
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 { markdownToTxt } from 'markdown-to-txt';
import axios from 'axios';
import { ElMessage } from 'element-plus';
export type UseDigitalHumanProps = {
    container: string;
@@ -29,6 +31,32 @@
    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 = () => {
@@ -59,6 +87,29 @@
    };
    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
@@ -71,9 +122,7 @@
        duix.on('intialSucccess', () => {
            console.info('intialSucccess');
            // 此时初始化成功,可调用start
            duix.start({ conversationId, openAsr: true }).then((res) => {
                console.info('start', res);
            });
            startDuix();
        });
        duix.on('bye', (data) => {
            console.info('bye', data);
@@ -98,7 +147,10 @@
            console.info('speakStart', data);
        });
        duix.on('speakEnd', (data) => {
            isReceiveRes.value = false;
            if (!isWaitingSpeak) {
                isReceiveRes.value = false;
                duix.openAsr().then((...a) => {});
            }
        });
        duix.on('speakSection', (data) => {
            console.info('speakSection', data);
@@ -108,18 +160,18 @@
        });
        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: '已收到您的问题,正在思考中...请稍等',
                // });
                isWaitingSpeak = true;
                duix.speak({
                    content: '已收到您的问题,正在思考中...请稍等',
                });
                questionStreamByPost(
                    {
                        question: data,
@@ -132,26 +184,16 @@
                        if (chunkRes.mode === 'result' && chunkRes.value?.answer_type === 'knowledge') {
                            const plainText = getPlainText(chunkRes.value);
                            hasResult = true;
                            duix.speak({
                                content: plainText,
                            });
                        }
                        if (!chunkRes.value?.json_ok && chunkRes.value?.err_code === 'MESSAGE') {
                            speakContent(plainText);
                        } else if (!chunkRes.value?.json_ok && chunkRes.value?.err_code === 'MESSAGE') {
                            if (hasResult) return;
                            hasResult = true;
                            isWaitingSpeak = false;
                            duix.speak({
                                content: chunkRes.value.json_msg,
                            });
                            speakContent(chunkRes.value.json_msg);
                        }
                        if (chunkRes.mode === 'finish') {
                            if (!hasResult) {
                                isWaitingSpeak = false;
                                duix.speak({
                                    content: '暂时无法口头描述你所说的问题',
                                });
                                speakContent('暂时无法口头描述你所说的问题');
                            } else {
                                hasResult = false;
                            }
@@ -179,20 +221,23 @@
    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();
            }
        });
    };
src/layout/component/header/Header.vue
@@ -2,19 +2,10 @@
    <div class="top_text flex justify-between px-6 items-center pl-[unset] pr-6">
        <div class="flex-items-center h-full">
            <div class="nav-menu">
                <router-link :to="firstToPath" class="nav-item" active-class="active">
                <router-link v-for="item in menuList" :to="item.path" :key="item.label" class="nav-item" active-class="active">
                    <i class="icon-park-outline-robot"></i>
                    智能助手
                    {{ item.label }}
                </router-link>
                <!-- <router-link to="/workspace/situation" class="nav-item" active-class="active">
                    <i class="icon-park-outline-workbench"></i>
                    个人工作台
                </router-link>
                <!-- <router-link to="/gis/situation" class="nav-item" active-class="active">
                    <i class="icon-park-outline-system"></i>
                    GIS系统
                </router-link> -->
            </div>
        </div>
        <el-dialog
@@ -48,9 +39,18 @@
import { systemNotifyList } from '/@/api/ai/chat';
import router from '/@/router';
import pinia from '/@/stores';
import { activeChatRoom, newChatRoomClick, sidebarIsShow } from '/@/stores/chatRoom';
import { ParentRegister } from '/@/stores/global';
import {
    activeChatRoom,
    activeRoomId,
    activeTopMenuStyle,
    isSharePage,
    newChatRoomClick,
    TopMenuStyle,
    sidebarIsShow,
} from '/@/stores/chatRoom';
import { useThemeConfig } from '/@/stores/themeConfig';
import { ParentRegister } from '/@/stores/global';
import emitter from '/@/utils/mitt';
import { userInfoKey } from '/@/utils/request';
import { Local } from '/@/utils/storage';
@@ -64,6 +64,34 @@
    announcementTime: '',
});
const menuList = computed(() => {
    const smartAssistant = {
        path: firstToPath.value,
        label: '智能助手',
    };
    const personalWorkbench = {
        path: '/workspace/situation',
        label: '个人工作台',
    };
    const gis = {
        path: '/gis/situation',
        label: 'GIS系统',
    };
    return [smartAssistant];
    switch (activeTopMenuStyle.value) {
        case TopMenuStyle.Normal:
            return [smartAssistant, personalWorkbench];
        case TopMenuStyle.Gis:
            return [smartAssistant, gis];
        default:
            return [smartAssistant];
    }
});
const smallScreenClick = () => {
    const pathname = window.location.pathname;
    const basePath = pathname.replace(/\/web\/index\.html$/, '');
src/stores/chatRoom.ts
@@ -304,3 +304,11 @@
    isLoginStatus.value = false;
    LoginInfo.remove();
};
/** @description 顶部菜单布局方式 */
export const enum TopMenuStyle {
    Normal = 'normal',
    Gis = 'gis',
}
export const activeTopMenuStyle = ref<TopMenuStyle>(TopMenuStyle.Normal);