// @ts-nocheck import { event_types, user_avatar, getCurrentChatId } from "../../../../../script.js"; import { getContext } from "../../../../st-context.js"; import { power_user } from "../../../../power-user.js"; import { createModuleEvents } from "../core/event-manager.js"; import { xbLog } from "../core/debug-core.js"; const SOURCE_TAG = 'xiaobaix-host'; /** * Context Bridge — 模板 iframe 上下文桥接服务 * * 功能: * 1. iframe 发送 iframe-ready / request-context → 插件推送上下文快照 * 2. 酒馆事件实时转发到所有模板 iframe * 3. 延迟投递队列:iframe 销毁后的事件暂存,待下一个 iframe 连接时投递 */ class ContextBridgeService { constructor() { this._attached = false; this._listener = null; this._previousChatId = null; /** @type {Array<{type: string, event: string, payload: object}>} */ this._pendingEvents = []; this._events = createModuleEvents('contextBridge'); } // ===== 生命周期 ===== init() { if (this._attached) return; try { xbLog.info('contextBridge', 'init'); } catch {} try { this._previousChatId = getCurrentChatId(); } catch {} const self = this; this._listener = function (event) { try { self._handleMessage(event); } catch (e) { try { xbLog.error('contextBridge', 'message handler error', e); } catch {} } }; // eslint-disable-next-line no-restricted-syntax -- bridge listener for iframe-ready/request-context window.addEventListener('message', this._listener); this._attachEventForwarding(); this._attached = true; } cleanup() { if (!this._attached) return; try { xbLog.info('contextBridge', 'cleanup'); } catch {} try { window.removeEventListener('message', this._listener); } catch {} this._listener = null; this._events.cleanup(); this._pendingEvents.length = 0; this._previousChatId = null; this._attached = false; } // ===== 消息处理 ===== _handleMessage(event) { const data = event && event.data; if (!data || typeof data !== 'object') return; const type = data.type; if (type !== 'iframe-ready' && type !== 'request-context') return; // 找到发送消息的 iframe 元素 const iframe = this._findIframeBySource(event.source); if (!iframe) return; const msgIndex = this._getMsgIndexForIframe(iframe); if (msgIndex < 0) return; // iframe-ready 时先投递积压的延迟事件 if (type === 'iframe-ready') { while (this._pendingEvents.length > 0) { const pending = this._pendingEvents.shift(); // eslint-disable-next-line no-restricted-syntax -- delivering queued events to newly ready iframe try { event.source?.postMessage(pending, '*'); } catch {} } } // 推送上下文快照 const snapshot = this._buildContextSnapshot(msgIndex); // eslint-disable-next-line no-restricted-syntax -- sending context snapshot to requesting iframe try { event.source?.postMessage(snapshot, '*'); } catch {} } /** * 遍历 DOM 查找 contentWindow 匹配的 iframe * @param {Window} source * @returns {HTMLIFrameElement|null} */ _findIframeBySource(source) { if (!source) return null; const iframes = document.querySelectorAll('iframe.xiaobaix-iframe'); for (const iframe of iframes) { try { if (iframe.contentWindow === source) return iframe; } catch {} } return null; } /** * 从 iframe 的 DOM 位置获取消息楼层索引 * @param {HTMLIFrameElement} iframe * @returns {number} */ _getMsgIndexForIframe(iframe) { const mesBlock = iframe.closest('.mes'); if (!mesBlock) return -1; const mesid = mesBlock.getAttribute('mesid'); if (mesid == null) return -1; return parseInt(mesid, 10); } // ===== 上下文快照 ===== /** * @param {number} msgIndex * @returns {object} */ _buildContextSnapshot(msgIndex) { const ctx = getContext(); const chat = ctx.chat || []; const msg = chat[msgIndex]; return { type: 'st-context', chatId: getCurrentChatId() || null, characterId: ctx.characterId ?? null, characterName: ctx.name2 || '', userName: ctx.name1 || '', userPersona: power_user?.persona_description || '', userAvatar: user_avatar || '', msgIndex: msgIndex, swipeId: msg?.swipe_id ?? 0, totalSwipes: msg?.swipes?.length ?? 1, totalMessages: chat.length, isGroupChat: !!ctx.groupId, groupId: ctx.groupId ?? null, }; } // ===== 事件广播 ===== /** * 向所有活跃的模板 iframe 广播事件 * @param {string} eventName * @param {object} payload */ _broadcastToTemplateIframes(eventName, payload) { const iframes = document.querySelectorAll('.mes iframe.xiaobaix-iframe'); const message = { type: 'st-event', source: SOURCE_TAG, event: eventName, payload }; for (const iframe of iframes) { // eslint-disable-next-line no-restricted-syntax -- broadcasting event to template iframes try { iframe.contentWindow?.postMessage(message, '*'); } catch {} } } // ===== 事件转发注册 ===== _attachEventForwarding() { const self = this; // ---- 消息级事件 ---- // 消息删除(截断式):原生 payload = chat.length(删除后剩余消息数) this._events.on(event_types.MESSAGE_DELETED, (remainingCount) => { self._broadcastToTemplateIframes('message_deleted', { fromIndex: remainingCount, }); }); // Swipe 切换:原生 payload = chat.length - 1(最后一条消息索引) this._events.on(event_types.MESSAGE_SWIPED, (msgIndex) => { const ctx = getContext(); const msg = ctx.chat?.[msgIndex]; self._broadcastToTemplateIframes('message_swiped', { msgIndex: msgIndex, newSwipeId: msg?.swipe_id ?? 0, totalSwipes: msg?.swipes?.length ?? 1, }); }); // 消息发送:原生 payload = insertAt(消息索引) this._events.on(event_types.MESSAGE_SENT, (msgIndex) => { self._broadcastToTemplateIframes('message_sent', { msgIndex }); }); // AI 回复完成:原生 payload = chat_id(消息索引) this._events.on(event_types.MESSAGE_RECEIVED, (msgIndex) => { self._broadcastToTemplateIframes('message_received', { msgIndex }); }); // 消息编辑:原生 payload = this_edit_mes_id(消息索引) this._events.on(event_types.MESSAGE_EDITED, (msgIndex) => { self._broadcastToTemplateIframes('message_edited', { msgIndex }); }); // ---- 聊天级事件 ---- // 聊天切换:原生 payload = getCurrentChatId() this._events.on(event_types.CHAT_CHANGED, (newChatId) => { self._broadcastToTemplateIframes('chat_id_changed', { newChatId: newChatId, previousChatId: self._previousChatId, }); self._previousChatId = newChatId; }); // 新聊天创建(含分支检测):原生 payload = 无 this._events.on(event_types.CHAT_CREATED, () => { const ctx = getContext(); const newLength = (ctx.chat || []).length; const isBranch = newLength > 1; self._broadcastToTemplateIframes('chat_created', { chatId: getCurrentChatId() || null, isBranch: isBranch, branchFromChatId: isBranch ? self._previousChatId : null, branchPointIndex: isBranch ? newLength - 1 : null, }); }); // ---- 延迟投递事件(入队,不广播)---- // 聊天删除:原生 payload = 聊天文件名(不含 .jsonl) this._events.on(event_types.CHAT_DELETED, (chatFileName) => { self._pendingEvents.push({ type: 'st-event', source: SOURCE_TAG, event: 'chat_deleted', payload: { chatId: chatFileName, timestamp: Date.now() }, }); }); // 群聊删除 this._events.on(event_types.GROUP_CHAT_DELETED, (chatFileName) => { self._pendingEvents.push({ type: 'st-event', source: SOURCE_TAG, event: 'group_chat_deleted', payload: { chatId: chatFileName, timestamp: Date.now() }, }); }); } } // ===== 模块级实例与导出 ===== const contextBridgeService = new ContextBridgeService(); export function initContextBridge() { contextBridgeService.init(); } export function cleanupContextBridge() { contextBridgeService.cleanup(); } // ===== 自初始化(与 call-generate-service.js 模式一致)===== if (typeof window !== 'undefined') { window.LittleWhiteBox = window.LittleWhiteBox || {}; window.LittleWhiteBox.contextBridge = contextBridgeService; try { initContextBridge(); } catch (e) {} try { window.addEventListener('xiaobaixEnabledChanged', (e) => { try { const enabled = e && e.detail && e.detail.enabled === true; if (enabled) initContextBridge(); else cleanupContextBridge(); } catch {} }); document.addEventListener('xiaobaixEnabledChanged', (e) => { try { const enabled = e && e.detail && e.detail.enabled === true; if (enabled) initContextBridge(); else cleanupContextBridge(); } catch {} }); window.addEventListener('beforeunload', () => { try { cleanupContextBridge(); } catch {} }); } catch {} }