wujingjing
2025-04-10 b9d28bd6af15026741099c25a6e72fe947ad3772
修改数字人对话
已修改6个文件
已添加1个文件
199 ■■■■■ 文件已修改
src/components/chat/Chat.vue 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/chat/components/ChatContainer.vue 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/chat/components/playBar/hook/useDigitalHuman.ts 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/chat/hooks/useAssistantContentOpt.ts 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/chat/messageList/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/chat/types.ts 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/chat/Chat.vue
@@ -1,5 +1,11 @@
<template>
    <ChatContainer :loading="chatListLoading" :more-is-loading="moreIsLoading" :is-share-page="isSharePage" ref="containerRef">
    <ChatContainer
        :loading="chatListLoading"
        :more-is-loading="moreIsLoading"
        :is-share-page="isSharePage"
        ref="containerRef"
        @autoSendMessage="autoSendMessage"
    >
        <!-- æ¶ˆæ¯åˆ—表 -->
        <template #message-list>
            <MessageList
@@ -54,6 +60,7 @@
import { computed, nextTick, onActivated, onMounted, ref } from 'vue';
import { loadAmisSource } from '../amis/load';
import ChatContainer from './components/ChatContainer.vue';
import { getKnowledgePlainText } from './components/playBar/hook/useDigitalHuman';
import ShareLinkDlg from './components/shareLink/index.vue';
import type { SendMsg } from './hooks/types';
import { useLoadData } from './hooks/useLoadData';
@@ -62,6 +69,7 @@
import MessageList from './messageList/index.vue';
import type { ChatContent } from './model/types';
import { AnswerState, AnswerType, RoleEnum, type ChatMessage } from './model/types';
import type { QuestionLifecycle } from './types';
import { getShareChatJsonByPost, questionStreamByPost } from '/@/api/ai/chat';
import PlayBar from '/@/components/chat/components/playBar/PlayBar.vue';
import CustomDrawer from '/@/components/drawer/CustomDrawer.vue';
@@ -131,7 +139,8 @@
let streamOutputIsStart = false;
let position: Position = null;
const questionAi = async (text) => {
const questionAi = async (text: string, lifecycleCall?: QuestionLifecycle) => {
    let judgeParams = null;
    if (!preQuestion.value) {
        judgeParams = {};
@@ -211,6 +220,9 @@
                if (chunkRes.mode === 'result') {
                    lastIsResult = true;
                    const res = chunkRes.value;
                    if (chunkRes.value?.answer_type === 'knowledge') {
                        lifecycleCall?.receiveText?.(getKnowledgePlainText(chunkRes.value));
                    }
                    if (checkReportEmpty()) {
                        const resReport = getResReport();
                        resReport.reports.push(res);
@@ -364,6 +376,7 @@
                    stepList.at(-1).ms = ms;
                    isTalking.value = false;
                    streamOutputIsStart = false;
                    lifecycleCall?.finish?.();
                    return;
                }
@@ -546,7 +559,7 @@
        scrollToBottom();
    }, 300);
};
const sendChatMessage = async (content: ChatContent = messageContent.value) => {
const sendChatMessage = async (content: ChatContent = messageContent.value, lifecycleCall?: QuestionLifecycle) => {
    if (!checkCanSend(content)) {
        return;
    }
@@ -565,7 +578,7 @@
    try {
        const [userItem, assistantItem] = addChatItem(content);
        resMsgContent = await questionAi(content.values);
        resMsgContent = await questionAi(content.values, lifecycleCall);
        handleAfterQuestion(userItem, assistantItem, resMsgContent, {
            historyId: questionRes?.history_id,
            question: questionRes?.question,
@@ -661,6 +674,11 @@
    sendChatMessage(messageContent.value);
};
const autoSendMessage = (question: string, lifecycleCall?: QuestionLifecycle) => {
    messageContent.value.values = question;
    sendChatMessage(messageContent.value, lifecycleCall);
};
const { loadRangeData, onChatListScroll, moreIsLoading, updateLoadIndex } = useScrollLoad({
    container: chatListDom,
    historyGroupId: currentRouteId,
src/components/chat/components/ChatContainer.vue
@@ -69,23 +69,31 @@
import { onActivated, onDeactivated, ref } from 'vue';
import { useChatWidth } from '../hooks/useChatWidth';
import { useScroll } from '../hooks/useScroll';
import emitter from '/@/utils/mitt';
import type { QuestionLifecycle } from '../types';
import { useDigitalHuman } from './playBar/hook/useDigitalHuman';
import emitter from '/@/utils/mitt';
const props = defineProps<{
    loading?: boolean;
    moreIsLoading?: boolean;
    isSharePage?: boolean;
}>();
const emit = defineEmits<{
    autoSendMessage: [string, QuestionLifecycle];
}>();
const chatListDom = ref<HTMLDivElement>();
const { openDigitalHuman, isHumanTalking, humanIsLoading, digitalHumanIsShow, closeDigitalHuman } = useDigitalHuman({
    container: '.duix-container',
});
const { openDigitalHuman, isHumanTalking, humanIsLoading, digitalHumanIsShow, closeDigitalHuman, digitalHumanWidth } = useDigitalHuman(
    {
        container: '.duix-container',
        autoSendMessage: (question: string, lifecycleCall?: QuestionLifecycle) => {
            emit('autoSendMessage', question, lifecycleCall);
        },
    }
);
const { scrollToBottom, isBottom } = useScroll({
    chatListDom,
});
const digitalHumanWidth = '240px';
const fileContentIsShow = ref(false);
src/components/chat/components/playBar/hook/useDigitalHuman.ts
@@ -1,19 +1,41 @@
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 axios from 'axios';
import { ElMessage } from 'element-plus';
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',
@@ -65,25 +87,6 @@
        // 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;
@@ -165,44 +168,28 @@
            }
            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;
            }
        });
@@ -270,5 +257,6 @@
        isHumanTalking: isReceiveRes,
        closeDigitalHuman,
        humanIsLoading,
        digitalHumanWidth,
    };
};
src/components/chat/hooks/useAssistantContentOpt.ts
@@ -26,29 +26,39 @@
    };
    const checkIsText = (item) => {
        const isText = item?.content?.values?.some((item) => item?.content?.type === AnswerType.Knowledge)  || item?.conclusion?.length > 0;
        const isText =
            item?.content?.values?.some((item) => item?.content?.type === AnswerType.Knowledge || item?.conclusion?.length > 0) ||
            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 =
                    cur.conclusion
                        ?.filter((cur) => !!cur.report)
                        .map((cur) => cur.report)
                        .join('\n\n') ?? '';
                acc += conclusionText;
                if (cur?.content?.type === AnswerType.Knowledge) {
                    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');
                    acc += answer;
                }
                return acc;
            }, '');
        const conclusionText =
src/components/chat/messageList/index.vue
@@ -19,7 +19,7 @@
                :isTalking="isTalking"
            />
        </div>
        <div v-if="showAskMore" class="ml-4 mt-5 ">
        <div v-if="showAskMore && msgList.at(-1)?.content?.askMoreList?.length > 0" class="ml-4 mt-5 ">
            <div class="text-gray-600 mb-5">你可以继续问我:</div>
            <div class="space-y-2 inline-flex flex-col">
                <div
src/components/chat/types.ts
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,5 @@
export type QuestionLifecycle = {
    finish?: () => void;
    receiveText?: (text: string) => void;
};
vite.config.ts
@@ -57,7 +57,7 @@
            host: '0.0.0.0',
            port: env.VITE_PORT as unknown as number,
            open: JSON.parse(env.VITE_OPEN),
            hmr: true,
            hmr: false,
            proxy: {
                '/events': {
                    target: 'http://localhost:3000',