714 lines
25 KiB
JavaScript
714 lines
25 KiB
JavaScript
import { extension_settings, getContext } from "../../../../extensions.js";
|
||
import { createModuleEvents, event_types } from "../core/event-manager.js";
|
||
import { EXT_ID } from "../core/constants.js";
|
||
import { xbLog, CacheRegistry } from "../core/debug-core.js";
|
||
import { replaceXbGetVarInString } from "./variables/var-commands.js";
|
||
import { executeSlashCommand } from "../core/slash-command.js";
|
||
import { default_user_avatar, default_avatar } from "../../../../../script.js";
|
||
import { getIframeBaseScript, getWrapperScript } from "../core/wrapper-inline.js";
|
||
import { postToIframe, getIframeTargetOrigin, getTrustedOrigin } from "../core/iframe-messaging.js";
|
||
const MODULE_ID = 'iframeRenderer';
|
||
const events = createModuleEvents(MODULE_ID);
|
||
|
||
let isGenerating = false;
|
||
const winMap = new Map();
|
||
let lastHeights = new WeakMap();
|
||
const blobUrls = new WeakMap();
|
||
const hashToBlobUrl = new Map();
|
||
const hashToBlobBytes = new Map();
|
||
const blobLRU = [];
|
||
const BLOB_CACHE_LIMIT = 32;
|
||
let lastApplyTs = 0;
|
||
let pendingHeight = null;
|
||
let pendingRec = null;
|
||
|
||
CacheRegistry.register(MODULE_ID, {
|
||
name: 'Blob URL 缓存',
|
||
getSize: () => hashToBlobUrl.size,
|
||
getBytes: () => {
|
||
let bytes = 0;
|
||
hashToBlobBytes.forEach(v => { bytes += Number(v) || 0; });
|
||
return bytes;
|
||
},
|
||
clear: () => {
|
||
clearBlobCaches();
|
||
hashToBlobBytes.clear();
|
||
},
|
||
getDetail: () => Array.from(hashToBlobUrl.keys()),
|
||
});
|
||
|
||
function getSettings() {
|
||
return extension_settings[EXT_ID] || {};
|
||
}
|
||
|
||
function ensureHideCodeStyle(enable) {
|
||
const id = 'xiaobaix-hide-code';
|
||
const old = document.getElementById(id);
|
||
if (!enable) {
|
||
old?.remove();
|
||
return;
|
||
}
|
||
if (old) return;
|
||
const hideCodeStyle = document.createElement('style');
|
||
hideCodeStyle.id = id;
|
||
hideCodeStyle.textContent = `
|
||
.xiaobaix-active .mes_text pre { display: none !important; }
|
||
.xiaobaix-active .mes_text pre.xb-show { display: block !important; }
|
||
`;
|
||
document.head.appendChild(hideCodeStyle);
|
||
}
|
||
|
||
function setActiveClass(enable) {
|
||
document.body.classList.toggle('xiaobaix-active', !!enable);
|
||
}
|
||
|
||
function djb2(str) {
|
||
let h = 5381;
|
||
for (let i = 0; i < str.length; i++) {
|
||
h = ((h << 5) + h) ^ str.charCodeAt(i);
|
||
}
|
||
return (h >>> 0).toString(16);
|
||
}
|
||
|
||
function shouldRenderContentByBlock(codeBlock) {
|
||
if (!codeBlock) return false;
|
||
const content = (codeBlock.textContent || '').trim().toLowerCase();
|
||
if (!content) return false;
|
||
return content.includes('<!doctype') || content.includes('<html') || content.includes('<script');
|
||
}
|
||
|
||
function generateUniqueId() {
|
||
return `xiaobaix-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
||
}
|
||
|
||
function setIframeBlobHTML(iframe, fullHTML, codeHash) {
|
||
const existing = hashToBlobUrl.get(codeHash);
|
||
if (existing) {
|
||
iframe.src = existing;
|
||
blobUrls.set(iframe, existing);
|
||
return;
|
||
}
|
||
const blob = new Blob([fullHTML], { type: 'text/html' });
|
||
const url = URL.createObjectURL(blob);
|
||
iframe.src = url;
|
||
blobUrls.set(iframe, url);
|
||
hashToBlobUrl.set(codeHash, url);
|
||
try { hashToBlobBytes.set(codeHash, blob.size || 0); } catch {}
|
||
blobLRU.push(codeHash);
|
||
while (blobLRU.length > BLOB_CACHE_LIMIT) {
|
||
const old = blobLRU.shift();
|
||
const u = hashToBlobUrl.get(old);
|
||
hashToBlobUrl.delete(old);
|
||
hashToBlobBytes.delete(old);
|
||
try { URL.revokeObjectURL(u); } catch (e) {}
|
||
}
|
||
}
|
||
|
||
function releaseIframeBlob(iframe) {
|
||
try {
|
||
const url = blobUrls.get(iframe);
|
||
if (url) URL.revokeObjectURL(url);
|
||
blobUrls.delete(iframe);
|
||
} catch (e) {}
|
||
}
|
||
|
||
export function clearBlobCaches() {
|
||
try { xbLog.info(MODULE_ID, '清空 Blob 缓存'); } catch {}
|
||
hashToBlobUrl.forEach(u => { try { URL.revokeObjectURL(u); } catch {} });
|
||
hashToBlobUrl.clear();
|
||
hashToBlobBytes.clear();
|
||
blobLRU.length = 0;
|
||
}
|
||
|
||
function buildResourceHints(html) {
|
||
const urls = Array.from(new Set((html.match(/https?:\/\/[^"'()\s]+/gi) || [])
|
||
.map(u => { try { return new URL(u).origin; } catch { return null; } })
|
||
.filter(Boolean)));
|
||
let hints = "";
|
||
const maxHosts = 6;
|
||
for (let i = 0; i < Math.min(urls.length, maxHosts); i++) {
|
||
const origin = urls[i];
|
||
hints += `<link rel="dns-prefetch" href="${origin}">`;
|
||
hints += `<link rel="preconnect" href="${origin}" crossorigin>`;
|
||
}
|
||
let preload = "";
|
||
const font = (html.match(/https?:\/\/[^"'()\s]+\.(?:woff2|woff|ttf|otf)/i) || [])[0];
|
||
if (font) {
|
||
const type = font.endsWith(".woff2") ? "font/woff2" : font.endsWith(".woff") ? "font/woff" : font.endsWith(".ttf") ? "font/ttf" : "font/otf";
|
||
preload += `<link rel="preload" as="font" href="${font}" type="${type}" crossorigin fetchpriority="high">`;
|
||
}
|
||
const css = (html.match(/https?:\/\/[^"'()\s]+\.css/i) || [])[0];
|
||
if (css) {
|
||
preload += `<link rel="preload" as="style" href="${css}" crossorigin fetchpriority="high">`;
|
||
}
|
||
const img = (html.match(/https?:\/\/[^"'()\s]+\.(?:png|jpg|jpeg|webp|gif|svg)/i) || [])[0];
|
||
if (img) {
|
||
preload += `<link rel="preload" as="image" href="${img}" crossorigin fetchpriority="high">`;
|
||
}
|
||
return hints + preload;
|
||
}
|
||
|
||
function buildWrappedHtml(html) {
|
||
const settings = getSettings();
|
||
const wrapperToggle = settings.wrapperIframe ?? true;
|
||
const origin = typeof location !== 'undefined' && location.origin ? location.origin : '';
|
||
const baseTag = settings.useBlob ? `<base href="${origin}/">` : "";
|
||
const headHints = buildResourceHints(html);
|
||
const vhFix = `<style>html,body{height:auto!important;min-height:0!important;max-height:none!important}.profile-container,[style*="100vh"]{height:auto!important;min-height:600px!important}[style*="height:100%"]{height:auto!important;min-height:100%!important}</style>`;
|
||
|
||
// 内联脚本,按顺序:wrapper(callGenerate) -> base(高度+STscript)
|
||
const scripts = wrapperToggle
|
||
? `<script>${getWrapperScript()}${getIframeBaseScript()}</script>`
|
||
: `<script>${getIframeBaseScript()}</script>`;
|
||
|
||
if (html.includes('<html') && html.includes('</html')) {
|
||
if (html.includes('<head>'))
|
||
return html.replace('<head>', `<head>${scripts}${baseTag}${headHints}${vhFix}`);
|
||
if (html.includes('</head>'))
|
||
return html.replace('</head>', `${scripts}${baseTag}${headHints}${vhFix}</head>`);
|
||
return html.replace('<body', `<head>${scripts}${baseTag}${headHints}${vhFix}</head><body`);
|
||
}
|
||
|
||
return `<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
${scripts}
|
||
${baseTag}
|
||
${headHints}
|
||
${vhFix}
|
||
<style>html,body{margin:0;padding:0;background:transparent}</style>
|
||
</head>
|
||
<body>${html}</body></html>`;
|
||
}
|
||
|
||
function getOrCreateWrapper(preEl) {
|
||
let wrapper = preEl.previousElementSibling;
|
||
if (!wrapper || !wrapper.classList.contains('xiaobaix-iframe-wrapper')) {
|
||
wrapper = document.createElement('div');
|
||
wrapper.className = 'xiaobaix-iframe-wrapper';
|
||
wrapper.style.cssText = 'margin:0;';
|
||
preEl.parentNode.insertBefore(wrapper, preEl);
|
||
}
|
||
return wrapper;
|
||
}
|
||
|
||
function registerIframeMapping(iframe, wrapper) {
|
||
const tryMap = () => {
|
||
try {
|
||
if (iframe && iframe.contentWindow) {
|
||
winMap.set(iframe.contentWindow, { iframe, wrapper });
|
||
return true;
|
||
}
|
||
} catch (e) {}
|
||
return false;
|
||
};
|
||
if (tryMap()) return;
|
||
let tries = 0;
|
||
const t = setInterval(() => {
|
||
tries++;
|
||
if (tryMap() || tries > 20) clearInterval(t);
|
||
}, 25);
|
||
}
|
||
|
||
function resolveAvatarUrls() {
|
||
const origin = typeof location !== 'undefined' && location.origin ? location.origin : '';
|
||
const toAbsUrl = (relOrUrl) => {
|
||
if (!relOrUrl) return '';
|
||
const s = String(relOrUrl);
|
||
if (/^(data:|blob:|https?:)/i.test(s)) return s;
|
||
if (s.startsWith('User Avatars/')) {
|
||
return `${origin}/${s}`;
|
||
}
|
||
const encoded = s.split('/').map(seg => encodeURIComponent(seg)).join('/');
|
||
return `${origin}/${encoded.replace(/^\/+/, '')}`;
|
||
};
|
||
const pickSrc = (selectors) => {
|
||
for (const sel of selectors) {
|
||
const el = document.querySelector(sel);
|
||
if (el) {
|
||
const highRes = el.getAttribute('data-izoomify-url');
|
||
if (highRes) return highRes;
|
||
if (el.src) return el.src;
|
||
}
|
||
}
|
||
return '';
|
||
};
|
||
let user = pickSrc([
|
||
'#user_avatar_block img',
|
||
'#avatar_user img',
|
||
'.user_avatar img',
|
||
'img#avatar_user',
|
||
'.st-user-avatar img'
|
||
]) || default_user_avatar;
|
||
const m = String(user).match(/\/thumbnail\?type=persona&file=([^&]+)/i);
|
||
if (m) {
|
||
user = `User Avatars/${decodeURIComponent(m[1])}`;
|
||
}
|
||
const ctx = getContext?.() || {};
|
||
const chId = ctx.characterId ?? ctx.this_chid;
|
||
const ch = Array.isArray(ctx.characters) ? ctx.characters[chId] : null;
|
||
let char = ch?.avatar || default_avatar;
|
||
if (char && !/^(data:|blob:|https?:)/i.test(char)) {
|
||
char = String(char).includes('/') ? char.replace(/^\/+/, '') : `characters/${char}`;
|
||
}
|
||
return { user: toAbsUrl(user), char: toAbsUrl(char) };
|
||
}
|
||
|
||
function handleIframeMessage(event) {
|
||
const data = event.data || {};
|
||
let rec = winMap.get(event.source);
|
||
|
||
if (!rec || !rec.iframe) {
|
||
const iframes = document.querySelectorAll('iframe.xiaobaix-iframe');
|
||
for (const iframe of iframes) {
|
||
if (iframe.contentWindow === event.source) {
|
||
rec = { iframe, wrapper: iframe.parentElement };
|
||
winMap.set(event.source, rec);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (rec && rec.iframe && typeof data.height === 'number') {
|
||
const next = Math.max(0, Number(data.height) || 0);
|
||
if (next < 1) return;
|
||
const prev = lastHeights.get(rec.iframe) || 0;
|
||
if (!data.force && Math.abs(next - prev) < 1) return;
|
||
if (data.force) {
|
||
lastHeights.set(rec.iframe, next);
|
||
requestAnimationFrame(() => { rec.iframe.style.height = `${next}px`; });
|
||
return;
|
||
}
|
||
pendingHeight = next;
|
||
pendingRec = rec;
|
||
const now = performance.now();
|
||
const dt = now - lastApplyTs;
|
||
if (dt >= 50) {
|
||
lastApplyTs = now;
|
||
const h = pendingHeight, r = pendingRec;
|
||
pendingHeight = null;
|
||
pendingRec = null;
|
||
lastHeights.set(r.iframe, h);
|
||
requestAnimationFrame(() => { r.iframe.style.height = `${h}px`; });
|
||
} else {
|
||
setTimeout(() => {
|
||
if (pendingRec && pendingHeight != null) {
|
||
lastApplyTs = performance.now();
|
||
const h = pendingHeight, r = pendingRec;
|
||
pendingHeight = null;
|
||
pendingRec = null;
|
||
lastHeights.set(r.iframe, h);
|
||
requestAnimationFrame(() => { r.iframe.style.height = `${h}px`; });
|
||
}
|
||
}, Math.max(0, 50 - dt));
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (data && data.type === 'runCommand') {
|
||
const replyOrigin = (typeof event.origin === 'string' && event.origin) ? event.origin : getTrustedOrigin();
|
||
executeSlashCommand(data.command)
|
||
.then(result => event.source.postMessage({
|
||
source: 'xiaobaix-host',
|
||
type: 'commandResult',
|
||
id: data.id,
|
||
result
|
||
}, replyOrigin))
|
||
.catch(err => event.source.postMessage({
|
||
source: 'xiaobaix-host',
|
||
type: 'commandError',
|
||
id: data.id,
|
||
error: err.message || String(err)
|
||
}, replyOrigin));
|
||
return;
|
||
}
|
||
|
||
if (data && data.type === 'getAvatars') {
|
||
const replyOrigin = (typeof event.origin === 'string' && event.origin) ? event.origin : getTrustedOrigin();
|
||
try {
|
||
const urls = resolveAvatarUrls();
|
||
event.source?.postMessage({ source: 'xiaobaix-host', type: 'avatars', urls }, replyOrigin);
|
||
} catch (e) {
|
||
event.source?.postMessage({ source: 'xiaobaix-host', type: 'avatars', urls: { user: '', char: '' } }, replyOrigin);
|
||
}
|
||
return;
|
||
}
|
||
}
|
||
|
||
export function renderHtmlInIframe(htmlContent, container, preElement) {
|
||
const settings = getSettings();
|
||
try {
|
||
const originalHash = djb2(htmlContent);
|
||
|
||
if (settings.variablesCore?.enabled && typeof replaceXbGetVarInString === 'function') {
|
||
try {
|
||
htmlContent = replaceXbGetVarInString(htmlContent);
|
||
} catch (e) {
|
||
console.warn('xbgetvar 宏替换失败:', e);
|
||
}
|
||
}
|
||
|
||
const iframe = document.createElement('iframe');
|
||
iframe.id = generateUniqueId();
|
||
iframe.className = 'xiaobaix-iframe';
|
||
iframe.style.cssText = 'width:100%;border:none;background:transparent;overflow:hidden;height:0;margin:0;padding:0;display:block;contain:layout paint style;will-change:height;min-height:50px';
|
||
iframe.setAttribute('frameborder', '0');
|
||
iframe.setAttribute('scrolling', 'no');
|
||
iframe.loading = 'eager';
|
||
|
||
if (settings.sandboxMode) {
|
||
iframe.setAttribute('sandbox', 'allow-scripts');
|
||
}
|
||
|
||
const wrapper = getOrCreateWrapper(preElement);
|
||
wrapper.querySelectorAll('.xiaobaix-iframe').forEach(old => {
|
||
try { old.src = 'about:blank'; } catch (e) {}
|
||
releaseIframeBlob(old);
|
||
old.remove();
|
||
});
|
||
|
||
const codeHash = djb2(htmlContent);
|
||
const full = buildWrappedHtml(htmlContent);
|
||
|
||
if (settings.useBlob) {
|
||
setIframeBlobHTML(iframe, full, codeHash);
|
||
} else {
|
||
iframe.srcdoc = full;
|
||
}
|
||
|
||
wrapper.appendChild(iframe);
|
||
preElement.classList.remove('xb-show');
|
||
preElement.style.display = 'none';
|
||
registerIframeMapping(iframe, wrapper);
|
||
|
||
try {
|
||
const targetOrigin = getIframeTargetOrigin(iframe);
|
||
postToIframe(iframe, { type: 'probe' }, null, targetOrigin);
|
||
} catch (e) {}
|
||
preElement.dataset.xbFinal = 'true';
|
||
preElement.dataset.xbHash = originalHash;
|
||
|
||
return iframe;
|
||
} catch (err) {
|
||
console.error('[iframeRenderer] 渲染失败:', err);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
export function processCodeBlocks(messageElement, forceFinal = true) {
|
||
const settings = getSettings();
|
||
if (!settings.enabled) return;
|
||
if (settings.renderEnabled === false) return;
|
||
|
||
try {
|
||
const codeBlocks = messageElement.querySelectorAll('pre > code');
|
||
const ctx = getContext();
|
||
const lastId = ctx.chat?.length - 1;
|
||
const mesEl = messageElement.closest('.mes');
|
||
const mesId = mesEl ? Number(mesEl.getAttribute('mesid')) : null;
|
||
|
||
if (isGenerating && mesId === lastId && !forceFinal) return;
|
||
|
||
codeBlocks.forEach(codeBlock => {
|
||
const preElement = codeBlock.parentElement;
|
||
const should = shouldRenderContentByBlock(codeBlock);
|
||
const html = codeBlock.textContent || '';
|
||
const hash = djb2(html);
|
||
const isFinal = preElement.dataset.xbFinal === 'true';
|
||
const same = preElement.dataset.xbHash === hash;
|
||
|
||
if (isFinal && same) return;
|
||
|
||
if (should) {
|
||
renderHtmlInIframe(html, preElement.parentNode, preElement);
|
||
} else {
|
||
preElement.classList.add('xb-show');
|
||
preElement.removeAttribute('data-xbfinal');
|
||
preElement.removeAttribute('data-xbhash');
|
||
preElement.style.display = '';
|
||
}
|
||
preElement.dataset.xiaobaixBound = 'true';
|
||
});
|
||
} catch (err) {
|
||
console.error('[iframeRenderer] processCodeBlocks 失败:', err);
|
||
}
|
||
}
|
||
|
||
export function processExistingMessages() {
|
||
const settings = getSettings();
|
||
if (!settings.enabled) return;
|
||
document.querySelectorAll('.mes_text').forEach(el => processCodeBlocks(el, true));
|
||
try { shrinkRenderedWindowFull(); } catch (e) {}
|
||
}
|
||
|
||
export function processMessageById(messageId, forceFinal = true) {
|
||
const messageElement = document.querySelector(`div.mes[mesid="${messageId}"] .mes_text`);
|
||
if (!messageElement) return;
|
||
processCodeBlocks(messageElement, forceFinal);
|
||
try { shrinkRenderedWindowForLastMessage(); } catch (e) {}
|
||
}
|
||
|
||
export function invalidateMessage(messageId) {
|
||
const el = document.querySelector(`div.mes[mesid="${messageId}"] .mes_text`);
|
||
if (!el) return;
|
||
el.querySelectorAll('.xiaobaix-iframe-wrapper').forEach(w => {
|
||
w.querySelectorAll('.xiaobaix-iframe').forEach(ifr => {
|
||
try { ifr.src = 'about:blank'; } catch (e) {}
|
||
releaseIframeBlob(ifr);
|
||
});
|
||
w.remove();
|
||
});
|
||
el.querySelectorAll('pre').forEach(pre => {
|
||
pre.classList.remove('xb-show');
|
||
pre.removeAttribute('data-xbfinal');
|
||
pre.removeAttribute('data-xbhash');
|
||
delete pre.dataset.xbFinal;
|
||
delete pre.dataset.xbHash;
|
||
pre.style.display = '';
|
||
delete pre.dataset.xiaobaixBound;
|
||
});
|
||
}
|
||
|
||
export function invalidateAll() {
|
||
document.querySelectorAll('.xiaobaix-iframe-wrapper').forEach(w => {
|
||
w.querySelectorAll('.xiaobaix-iframe').forEach(ifr => {
|
||
try { ifr.src = 'about:blank'; } catch (e) {}
|
||
releaseIframeBlob(ifr);
|
||
});
|
||
w.remove();
|
||
});
|
||
document.querySelectorAll('.mes_text pre').forEach(pre => {
|
||
pre.classList.remove('xb-show');
|
||
pre.removeAttribute('data-xbfinal');
|
||
pre.removeAttribute('data-xbhash');
|
||
delete pre.dataset.xbFinal;
|
||
delete pre.dataset.xbHash;
|
||
delete pre.dataset.xiaobaixBound;
|
||
pre.style.display = '';
|
||
});
|
||
clearBlobCaches();
|
||
winMap.clear();
|
||
lastHeights = new WeakMap();
|
||
}
|
||
|
||
function shrinkRenderedWindowForLastMessage() {
|
||
const settings = getSettings();
|
||
if (!settings.enabled) return;
|
||
if (settings.renderEnabled === false) return;
|
||
const max = Number.isFinite(settings.maxRenderedMessages) && settings.maxRenderedMessages > 0
|
||
? settings.maxRenderedMessages
|
||
: 0;
|
||
if (max <= 0) return;
|
||
const ctx = getContext?.();
|
||
const chatArr = ctx?.chat;
|
||
if (!Array.isArray(chatArr) || chatArr.length === 0) return;
|
||
const lastId = chatArr.length - 1;
|
||
if (lastId < 0) return;
|
||
const keepFrom = Math.max(0, lastId - max + 1);
|
||
const mesList = document.querySelectorAll('div.mes');
|
||
for (const mes of mesList) {
|
||
const mesIdAttr = mes.getAttribute('mesid');
|
||
if (mesIdAttr == null) continue;
|
||
const mesId = Number(mesIdAttr);
|
||
if (!Number.isFinite(mesId)) continue;
|
||
if (mesId >= keepFrom) break;
|
||
const mesText = mes.querySelector('.mes_text');
|
||
if (!mesText) continue;
|
||
mesText.querySelectorAll('.xiaobaix-iframe-wrapper').forEach(w => {
|
||
w.querySelectorAll('.xiaobaix-iframe').forEach(ifr => {
|
||
try { ifr.src = 'about:blank'; } catch (e) {}
|
||
releaseIframeBlob(ifr);
|
||
});
|
||
w.remove();
|
||
});
|
||
mesText.querySelectorAll('pre[data-xiaobaix-bound="true"]').forEach(pre => {
|
||
pre.classList.remove('xb-show');
|
||
pre.removeAttribute('data-xbfinal');
|
||
pre.removeAttribute('data-xbhash');
|
||
delete pre.dataset.xbFinal;
|
||
delete pre.dataset.xbHash;
|
||
delete pre.dataset.xiaobaixBound;
|
||
pre.style.display = '';
|
||
});
|
||
}
|
||
}
|
||
|
||
function shrinkRenderedWindowFull() {
|
||
const settings = getSettings();
|
||
if (!settings.enabled) return;
|
||
if (settings.renderEnabled === false) return;
|
||
const max = Number.isFinite(settings.maxRenderedMessages) && settings.maxRenderedMessages > 0
|
||
? settings.maxRenderedMessages
|
||
: 0;
|
||
if (max <= 0) return;
|
||
const ctx = getContext?.();
|
||
const chatArr = ctx?.chat;
|
||
if (!Array.isArray(chatArr) || chatArr.length === 0) return;
|
||
const lastId = chatArr.length - 1;
|
||
const keepFrom = Math.max(0, lastId - max + 1);
|
||
const mesList = document.querySelectorAll('div.mes');
|
||
for (const mes of mesList) {
|
||
const mesIdAttr = mes.getAttribute('mesid');
|
||
if (mesIdAttr == null) continue;
|
||
const mesId = Number(mesIdAttr);
|
||
if (!Number.isFinite(mesId)) continue;
|
||
if (mesId >= keepFrom) continue;
|
||
const mesText = mes.querySelector('.mes_text');
|
||
if (!mesText) continue;
|
||
mesText.querySelectorAll('.xiaobaix-iframe-wrapper').forEach(w => {
|
||
w.querySelectorAll('.xiaobaix-iframe').forEach(ifr => {
|
||
try { ifr.src = 'about:blank'; } catch (e) {}
|
||
releaseIframeBlob(ifr);
|
||
});
|
||
w.remove();
|
||
});
|
||
mesText.querySelectorAll('pre[data-xiaobaix-bound="true"]').forEach(pre => {
|
||
pre.classList.remove('xb-show');
|
||
pre.removeAttribute('data-xbfinal');
|
||
pre.removeAttribute('data-xbhash');
|
||
delete pre.dataset.xbFinal;
|
||
delete pre.dataset.xbHash;
|
||
delete pre.dataset.xiaobaixBound;
|
||
pre.style.display = '';
|
||
});
|
||
}
|
||
}
|
||
|
||
let messageListenerBound = false;
|
||
|
||
export function initRenderer() {
|
||
const settings = getSettings();
|
||
if (!settings.enabled) return;
|
||
|
||
try { xbLog.info(MODULE_ID, 'initRenderer'); } catch {}
|
||
|
||
if (settings.renderEnabled !== false) {
|
||
ensureHideCodeStyle(true);
|
||
setActiveClass(true);
|
||
}
|
||
|
||
events.on(event_types.GENERATION_STARTED, () => {
|
||
isGenerating = true;
|
||
});
|
||
|
||
events.on(event_types.GENERATION_ENDED, () => {
|
||
isGenerating = false;
|
||
const ctx = getContext();
|
||
const lastId = ctx.chat?.length - 1;
|
||
if (lastId != null && lastId >= 0) {
|
||
setTimeout(() => {
|
||
processMessageById(lastId, true);
|
||
}, 60);
|
||
}
|
||
});
|
||
|
||
events.on(event_types.MESSAGE_RECEIVED, (data) => {
|
||
setTimeout(() => {
|
||
const messageId = typeof data === 'object' ? data.messageId : data;
|
||
if (messageId != null) {
|
||
processMessageById(messageId, true);
|
||
}
|
||
}, 300);
|
||
});
|
||
|
||
events.on(event_types.MESSAGE_UPDATED, (data) => {
|
||
const messageId = typeof data === 'object' ? data.messageId : data;
|
||
if (messageId != null) {
|
||
processMessageById(messageId, true);
|
||
}
|
||
});
|
||
|
||
events.on(event_types.MESSAGE_EDITED, (data) => {
|
||
const messageId = typeof data === 'object' ? data.messageId : data;
|
||
if (messageId != null) {
|
||
processMessageById(messageId, true);
|
||
}
|
||
});
|
||
|
||
events.on(event_types.MESSAGE_DELETED, (data) => {
|
||
const messageId = typeof data === 'object' ? data.messageId : data;
|
||
if (messageId != null) {
|
||
invalidateMessage(messageId);
|
||
}
|
||
});
|
||
|
||
events.on(event_types.MESSAGE_SWIPED, (data) => {
|
||
setTimeout(() => {
|
||
const messageId = typeof data === 'object' ? data.messageId : data;
|
||
if (messageId != null) {
|
||
processMessageById(messageId, true);
|
||
}
|
||
}, 10);
|
||
});
|
||
|
||
events.on(event_types.USER_MESSAGE_RENDERED, (data) => {
|
||
setTimeout(() => {
|
||
const messageId = typeof data === 'object' ? data.messageId : data;
|
||
if (messageId != null) {
|
||
processMessageById(messageId, true);
|
||
}
|
||
}, 10);
|
||
});
|
||
|
||
events.on(event_types.CHARACTER_MESSAGE_RENDERED, (data) => {
|
||
setTimeout(() => {
|
||
const messageId = typeof data === 'object' ? data.messageId : data;
|
||
if (messageId != null) {
|
||
processMessageById(messageId, true);
|
||
}
|
||
}, 10);
|
||
});
|
||
|
||
events.on(event_types.CHAT_CHANGED, () => {
|
||
isGenerating = false;
|
||
invalidateAll();
|
||
setTimeout(() => {
|
||
processExistingMessages();
|
||
}, 100);
|
||
});
|
||
|
||
if (!messageListenerBound) {
|
||
// eslint-disable-next-line no-restricted-syntax -- message bridge for iframe renderers.
|
||
window.addEventListener('message', handleIframeMessage);
|
||
messageListenerBound = true;
|
||
}
|
||
|
||
setTimeout(processExistingMessages, 100);
|
||
}
|
||
|
||
export function cleanupRenderer() {
|
||
try { xbLog.info(MODULE_ID, 'cleanupRenderer'); } catch {}
|
||
events.cleanup();
|
||
if (messageListenerBound) {
|
||
window.removeEventListener('message', handleIframeMessage);
|
||
messageListenerBound = false;
|
||
}
|
||
|
||
ensureHideCodeStyle(false);
|
||
setActiveClass(false);
|
||
|
||
document.querySelectorAll('pre[data-xiaobaix-bound="true"]').forEach(pre => {
|
||
pre.classList.remove('xb-show');
|
||
pre.removeAttribute('data-xbfinal');
|
||
pre.removeAttribute('data-xbhash');
|
||
delete pre.dataset.xbFinal;
|
||
delete pre.dataset.xbHash;
|
||
pre.style.display = '';
|
||
delete pre.dataset.xiaobaixBound;
|
||
});
|
||
|
||
invalidateAll();
|
||
isGenerating = false;
|
||
pendingHeight = null;
|
||
pendingRec = null;
|
||
lastApplyTs = 0;
|
||
}
|
||
|
||
export function isCurrentlyGenerating() {
|
||
return isGenerating;
|
||
}
|
||
|
||
export { shrinkRenderedWindowFull, shrinkRenderedWindowForLastMessage };
|