已修改8个文件
202 ■■■■ 文件已修改
package-lock.json 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
scripts/helper.js 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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 | 历史
vite.config.ts 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package-lock.json
@@ -80,6 +80,7 @@
                "@vue/compiler-sfc": "^3.4.15",
                "autoprefixer": "^10.4.19",
                "code-inspector-plugin": "^0.20.3",
                "dotenv": "^16.4.7",
                "eslint": "^8.35.0",
                "eslint-plugin-vue": "^9.9.0",
                "fs-extra": "^11.2.0",
@@ -3971,6 +3972,7 @@
            "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.4.7.tgz",
            "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
            "dev": true,
            "license": "BSD-2-Clause",
            "engines": {
                "node": ">=12"
            },
package.json
@@ -97,6 +97,7 @@
        "@vue/compiler-sfc": "^3.4.15",
        "autoprefixer": "^10.4.19",
        "code-inspector-plugin": "^0.20.3",
        "dotenv": "^16.4.7",
        "eslint": "^8.35.0",
        "eslint-plugin-vue": "^9.9.0",
        "fs-extra": "^11.2.0",
scripts/helper.js
@@ -3,6 +3,7 @@
const os = require('os');
const path = require('path');
const chalk = require('chalk');
require('dotenv').config({ path: '.env.local' });
// 获取当前脚本所在的目录
const scriptDir = __dirname;
@@ -10,18 +11,19 @@
const argv2 = process.argv[2];
const customerList = argv2?.split(' ') ?? '';
const publicDir = path.join(rootDir, 'public');
const distDir = path.join(rootDir, 'dist');
const distDir = process.env.VITE_OUTPUT_DIR || path.join(rootDir, 'dist');
const customerListDir = path.join(rootDir, 'customer_list');
const customerProjectListDir = path.join(rootDir, 'src', 'views', 'project');
const firstCustomerName = customerList[0]?.split(':')[0];
/** 公共文件夹,所有客户文件夹共享文件 */
const commonDir = path.join(customerListDir, 'common');
 const item = customerList[0];
const item = customerList[0];
const customerSplit = item?.split(':');
const deployEnv = customerSplit?.[1];
// 是否为生产环境
const isPro = deployEnv==='pro';
const isPro = deployEnv === 'pro';
// const deployEnv = process.argv[3];
const logColor = (text, color) => {
@@ -72,7 +74,7 @@
 */
const checkCustomer = (command, customer = firstCustomerName) => {
    if (!customer) {
        console.error(chalk.red(`请正确使用命令 “${command} [customer]” `));
        console.error(chalk.red(`请正确使用命令 "${command} [customer]"`));
        exit(); // 退出脚本
    }
@@ -238,7 +240,7 @@
        checkFileExist(deployJSON, '配置文件');
        // 读取 JSON 文件
        const config = await fs.readJson(deployJSON).catch((err) => {
            console.error(`读取配置文件“${deployJSON}”出错:`, err);
            console.error(`读取配置文件"${deployJSON}"出错:`, err);
            exit();
        });
@@ -293,7 +295,7 @@
                    }
                }
            } catch (error) {
                console.error(`读取“${customerFolder}”失败`, error);
                console.error(`读取"${customerFolder}"失败`, error);
            }
            try {
@@ -323,7 +325,7 @@
/**
 * 切换分支
 */
const changeBranch = () =>{
const changeBranch = () => {
    return;
    if (isPro) {
        try {
@@ -334,7 +336,7 @@
            execSync('git checkout test', { stdio: 'inherit' });
        } catch (error) {}
    }
}
};
module.exports = {
    isPro,
@@ -368,5 +370,5 @@
    updateImportGlob,
    restoreImportGlob,
    deployEnv,
    changeBranch
    changeBranch,
};
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);
vite.config.ts
@@ -1,11 +1,10 @@
import vue from '@vitejs/plugin-vue';
import autoprefixer from 'autoprefixer';
import { CodeInspectorPlugin } from 'code-inspector-plugin';
import { resolve } from 'path';
import tailwindcss from 'tailwindcss';
import type { ConfigEnv } from 'vite';
import { defineConfig, loadEnv } from 'vite';
import { CodeInspectorPlugin } from 'code-inspector-plugin';
import { visualizer } from 'rollup-plugin-visualizer';
// import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
// import AutoImport from 'unplugin-auto-import/vite';
// import Components from 'unplugin-vue-components/vite';
@@ -81,8 +80,7 @@
            },
        },
        build: {
            // outDir: 'dist/' + mode.mode,
            outDir: 'dist',
            outDir: env.VITE_OUTPUT_DIR || 'dist',
            chunkSizeWarningLimit: 1500,
            rollupOptions: {