wujingjing
2025-01-16 f1360cc184810c1458af6577b9e43f32aca7b24d
消息同步
已修改7个文件
123 ■■■■■ 文件已修改
customer_list/ch/static/config/globalConfig.chengtou.js 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
customer_list/ch/static/config/globalConfig.test.js 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/chat/Chat.vue 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/chat/hooks/useSyncMsg.ts 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/constants/index.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/stores/global.ts 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/types/global.d.ts 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
customer_list/ch/static/config/globalConfig.chengtou.js
@@ -13,6 +13,8 @@
        MobileProdShareRealUrl: 'https://wi.beng35.com/mobile/chengtou',
        // 真实访问的测试环境链接地址
        MobileTestShareRealUrl: 'https://wi.beng35.com/mobile/chengtou',
        SSEUrl: 'https://widev.cpolar.top/sse/chat/connect_broadcast_chat',
        // 分享链接
        ShareUrl: 'share/index.html',
    },
customer_list/ch/static/config/globalConfig.test.js
@@ -9,6 +9,8 @@
    WebApiUrl: {
        MainUrl: 'https://widev.cpolar.top/ai_dev/',
        AuthUrl: 'http://47.100.245.85:8190/',
        SSEUrl: 'https://widev.cpolar.top/sse/chat/connect_broadcast_chat',
        // 分享链接
        ShareUrl: 'share/index.html',
        // 手机端下载地址
src/components/chat/Chat.vue
@@ -78,7 +78,9 @@
import { useSyncMsg } from './hooks/useSyncMsg';
const containerRef = useCompRef(ChatContainer);
const chatListDom = computed(() => containerRef.value?.chatListDom);
const scrollToBottom = () => {
    containerRef.value?.scrollToBottom();
};
const { loadReplyData, parseContent, parseExtraContent, convertProcessItem, convertProcessToStep, formatShowTimeYear } = useLoadData();
const voicePageIsShow = ref(false);
let isTalking = ref(false);
@@ -427,9 +429,14 @@
    loadReplyData,
});
useSyncMsg({
    msgList: computedMessageList,
    msgList: messageList,
    updateLoadIndex,
    historyGroupId: currentRouteId,
    checkCanSync: (data) => {
        return !isTalking.value && !moreIsLoading.value;
    },
    loadReplyData,
    scrollToBottom,
});
const chatListLoading = ref(true);
@@ -444,9 +451,7 @@
    };
    sendChatMessage();
};
const scrollToBottom = () => {
    containerRef.value?.scrollToBottom();
};
const initHistoryChat = () => {
    // 初始状态滚一下
src/components/chat/hooks/useSyncMsg.ts
@@ -1,65 +1,87 @@
import { differenceBy } from 'lodash-es';
import moment from 'moment';
import { onActivated, onDeactivated, unref, type Ref } from 'vue';
import { RoleEnum, type ChatMessage } from '../model/types';
import { QueryHistoryDetail } from '/@/api/ai/chat';
import { sseClient } from '/@/stores/global';
import { LOAD_CHAT_LIMIT } from '../constants';
import { differenceBy } from 'lodash-es';
type UseSyncMsgOptions = {
    updateLoadIndex: (addCount: number) => void;
    msgList: Ref<ChatMessage[]>;
    historyGroupId: string | Ref<string>;
    checkCanSync: (data: any) => boolean;
    loadReplyData: (data: any) => Promise<ChatMessage[]>;
    scrollToBottom: () => void;
};
export const useSyncMsg = (options: UseSyncMsgOptions) => {
    const { updateLoadIndex, msgList, historyGroupId } = options;
    const { updateLoadIndex, msgList, historyGroupId, checkCanSync, loadReplyData, scrollToBottom } = options;
    // const loadRangeData = async (lastEnd = nextUserMsgEndIndex) => {
    //     const res = await QueryHistoryDetail({
    //         history_group_id: unref(historyGroupId),
    //         last_end: lastEnd,
    //         last_count: LOAD_CHAT_LIMIT,
    //     });
    //     const result: ChatMessage[] = res.details ?? [];
    //     if (result.length) {
    //         nextUserMsgEndIndex += result.length;
    //         const rangeMsgList = await loadReplyData(res.details);
    //         messageList.value.unshift(...rangeMsgList);
    //     } else {
    //         isNoMore = true;
    //     }
    //     return result;
    // };
    const insertSyncMsg = (replayData: any[]) => {
        const insertResult: { index: number; item: any }[] = [];
        for (let i = replayData.length - 1; i >= 0; i--) {
            const insertItem = replayData[i];
            if (insertItem.role === RoleEnum.assistant) continue;
            for (let j = msgList.value.length - 1; j >= 0; j--) {
                const currentItem = msgList.value[j];
                if (currentItem.role === RoleEnum.assistant) continue;
                if (moment(insertItem.createTime).isAfter(currentItem.createTime)) {
                    const insertAssistantItem = replayData[i + 1];
                    insertResult.push({
                        index: j + 2,
                        item: [insertItem, insertAssistantItem],
                    });
                    break;
                }
            }
        }
        insertResult.forEach((resultItem) => {
            msgList.value.splice(resultItem.index, 0, ...resultItem.item);
        });
    };
    const historyUpdate = async (data: any) => {
        if (!checkCanSync(data)) return;
        if (!data) return;
        const groupId = unref(historyGroupId);
        if (!groupId) return;
        if (data?.type === 'chat_history_id') {
            const groupId = unref(historyGroupId);
            if (!groupId) return;
            const recentIds = data.id_list ?? [];
            const recentGroupHistoryIds = recentIds.filter((item) => item.group_id === groupId);
            if (recentGroupHistoryIds.length === 0) return;
            const userHistoryIds = msgList.value
                .filter((item) => item.role === RoleEnum.user)
                .map((item) => ({ id: item.historyId, time: item.createTime }));
            const unSyncedHistoryIds = differenceBy(recentIds, userHistoryIds, 'id');
            console.log('unSyncedHistoryIds', unSyncedHistoryIds);
        }
                .map((item) => ({ history_id: item.historyId, time: item.createTime }));
            const tmpUnSyncedHistoryIds = differenceBy(recentGroupHistoryIds, userHistoryIds, 'history_id') as any[];
            const latestUserHistory = userHistoryIds[userHistoryIds.length - 1];
            let unSyncedHistoryIds = tmpUnSyncedHistoryIds;
            // 太晚的不需要更新
            if (latestUserHistory) {
                unSyncedHistoryIds = tmpUnSyncedHistoryIds.filter((item) => moment(item.time).isAfter(latestUserHistory.time));
            }
            if (!unSyncedHistoryIds || unSyncedHistoryIds.length === 0) return;
            const res = await QueryHistoryDetail({
                history_group_id: groupId,
                id_list: unSyncedHistoryIds.map((item) => item.history_id).join(','),
            });
            if (!checkCanSync(data)) return;
        const res = await QueryHistoryDetail({
            history_group_id: groupId,
            last_end: 0,
            last_count: LOAD_CHAT_LIMIT,
        });
        console.log('historyUpdate', data, groupId);
            const result: ChatMessage[] = res.details ?? [];
            if (!result || result.length === 0) return;
            const replayData = await loadReplyData(res.details);
            if (!checkCanSync(data)) return;
            insertSyncMsg(replayData);
            updateLoadIndex(unSyncedHistoryIds.length);
            scrollToBottom();
        }
    };
    onActivated(() => {
        console.log('onActivated');
        sseClient.subscribe(historyUpdate);
        sseClient?.subscribe(historyUpdate);
    });
    onDeactivated(() => {
        console.log('onDeactivated');
        sseClient.unsubscribe(historyUpdate);
        sseClient?.unsubscribe(historyUpdate);
    });
    // onUnmounted(() => {
src/constants/index.ts
@@ -6,6 +6,7 @@
//#region ====================== 后端地址 ======================
export const MAIN_URL = window.globalConfig.WebApiUrl.MainUrl;
export const SSE_URL = window.globalConfig.WebApiUrl.SSEUrl;
export const AUTH_URL = window.globalConfig.WebApiUrl.AuthUrl;
export const SECONDARY_URL = window.globalConfig.WebApiUrl?.SecondaryUrl;
export const TERTIARY_URL = window.globalConfig.WebApiUrl?.TertiaryUrl;
src/stores/global.ts
@@ -1,4 +1,4 @@
import { MAIN_URL } from '../constants';
import { SSE_URL } from '../constants';
import { accessSessionKey } from '../utils/request';
import { SSEClient } from '../utils/sse/SSEClient';
import { Local } from '../utils/storage';
@@ -9,10 +9,12 @@
 */
const connectMsgSyncService = () => {
    if (!Local.get(accessSessionKey)) return;
    if (!SSE_URL) return;
    // 创建实例
    const sseClient = new SSEClient(
        `${MAIN_URL}chat/connect_broadcast_chat`,
        // `https://widev.cpolar.top/sse/chat/connect_broadcast_chat`,
        // `${MAIN_URL}events`
        SSE_URL
    );
    sseClient.connect({
        websessionid: Local.get(accessSessionKey),
src/types/global.d.ts
@@ -59,7 +59,8 @@
            SecondaryUrl?: string;
            /** @description 副副... url */
            TertiaryUrl?: string;
            /** @description sse url */
            SSEUrl: string;
            /** @description 权限 url */
            AuthUrl: string;
            /** @description 分享 url */