import { createDrawer, showPlotOptimizationProgress, updatePlotOptimizationProgress, hidePlotOptimizationProgress, registerSlashCommands, onMessageReceived, handleTableUpdate, processPlotOptimization, getContext, extension_settings, characters, this_chid, eventSource, event_types, saveSettingsDebounced, injectTableData, generateTableContent, initializeRagProcessor, loadTables, clearHighlights, rollbackAndRefill, rollbackState, commitPendingDeletions, saveStateToMessage, getMemoryState, clearUpdatedTables, fillWithSecondaryApi, renderTables, log, checkForUpdates, fetchMessageBoardContent, setUpdateInfo, applyUpdateIndicator, pluginVersion, extensionName, defaultSettings, configManager, apiProfileManager, checkAuthorization, refreshUserInfo, tableSystemDefaultSettings, manageLorebookEntriesForChat, cwbDefaultSettings, updateOrInsertTableInChat, startContinuousRendering, stopContinuousRendering, initializeRenderer, initializeApiListener, registerApiHandler, amilyHelper, initializeAmilyHelper, registerContextOptimizerMacros, resetContextBuffer, initializeSuperMemory } from './imports.js'; import { initializeAmilyBus } from './SL/bus/Amily2Bus.js'; const DOMPURIFY_CDN = "https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.2.7/purify.min.js"; function loadExternalScript(url, globalName) { return new Promise((resolve, reject) => { if (window[globalName]) { resolve(window[globalName]); return; } const existingScript = document.querySelector(`script[src="${url}"]`); if (existingScript) { existingScript.addEventListener('load', () => resolve(window[globalName])); existingScript.addEventListener('error', reject); return; } const script = document.createElement('script'); script.src = url; script.async = true; script.onload = () => { console.log(`[Amily2-核心] 外部库加载成功: ${globalName}`); resolve(window[globalName]); }; script.onerror = (err) => { console.error(`[Amily2-核心] 外部库加载失败: ${globalName}`, err); reject(err); }; document.head.appendChild(script); }); } const STYLE_SETTINGS_KEY = 'amily2_custom_styles'; const STYLE_ROOT_SELECTOR = '#amily2_memorisation_forms_panel'; let styleRoot = null; function getStyleRoot() { if (!styleRoot) { styleRoot = document.querySelector(STYLE_ROOT_SELECTOR); } return styleRoot; } function applyStyles(styleObject) { const root = getStyleRoot(); if (!root || !styleObject) return; delete styleObject._comment; for (const [key, value] of Object.entries(styleObject)) { if (key.startsWith('--am2-')) { root.style.setProperty(key, value); } } } function loadAndApplyStyles() { const savedStyles = extension_settings[extensionName]?.[STYLE_SETTINGS_KEY]; if (savedStyles && typeof savedStyles === 'object' && Object.keys(savedStyles).length > 0) { applyStyles(savedStyles); } } function saveStyles(styleObject) { if (!extension_settings[extensionName]) { extension_settings[extensionName] = {}; } extension_settings[extensionName][STYLE_SETTINGS_KEY] = styleObject; saveSettingsDebounced(); } function resetToDefaultStyles() { const root = getStyleRoot(); if (!root) return; const savedStyles = extension_settings[extensionName]?.[STYLE_SETTINGS_KEY]; if (savedStyles && typeof savedStyles === 'object') { for (const key of Object.keys(savedStyles)) { if (key.startsWith('--am2-')) { root.style.removeProperty(key); } } } saveStyles(null); toastr.success('已恢复默认界面样式。'); } function getDefaultCssVars() { return { "--am2-font-size-base": "14px", "--am2-gap-main": "10px", "--am2-padding-main": "8px 5px", "--am2-container-bg": "rgba(0,0,0,0.1)", "--am2-container-border": "1px solid rgba(255, 255, 255, 0.2)", "--am2-container-border-radius": "12px", "--am2-container-padding": "10px", "--am2-container-shadow": "inset 0 0 15px rgba(0,0,0,0.2)", "--am2-title-font-size": "1.1em", "--am2-title-font-weight": "bold", "--am2-title-text-shadow": "0 0 5px rgba(200, 200, 255, 0.3)", "--am2-title-gradient-start": "#c0bde4", "--am2-title-gradient-end": "#dfdff0", "--am2-title-icon-color": "#9e8aff", "--am2-title-icon-margin": "10px", "--am2-table-bg": "rgba(0,0,0,0.2)", "--am2-table-border": "1px solid rgba(255, 255, 255, 0.25)", "--am2-table-cell-padding": "6px 8px", "--am2-table-cell-font-size": "0.95em", "--am2-header-bg": "rgba(255, 255, 255, 0.1)", "--am2-header-color": "#e0e0e0", "--am2-header-editable-bg": "rgba(172, 216, 255, 0.1)", "--am2-header-editable-focus-bg": "rgba(172, 216, 255, 0.25)", "--am2-header-editable-focus-outline": "1px solid #79b8ff", "--am2-cell-editable-bg": "rgba(255, 255, 172, 0.1)", "--am2-cell-editable-focus-bg": "rgba(255, 255, 172, 0.25)", "--am2-cell-editable-focus-outline": "1px solid #ffc107", "--am2-index-col-bg": "rgba(0, 0, 0, 0.3) !important", "--am2-index-col-color": "#aaa !important", "--am2-index-col-width": "40px", "--am2-index-col-padding": "10px 5px !important", "--am2-controls-gap": "5px", "--am2-controls-margin-bottom": "10px", "--am2-cell-highlight-bg": "rgba(144, 238, 144, 0.3)" }; } function exportStyles() { const root = getStyleRoot(); if (!root) { toastr.error('无法导出样式:找不到根元素。'); return; } const computedStyle = getComputedStyle(root); const stylesToExport = {}; const defaultVars = getDefaultCssVars(); for (const key of Object.keys(defaultVars)) { stylesToExport[key] = computedStyle.getPropertyValue(key).trim(); } const blob = new Blob([JSON.stringify(stylesToExport, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `Amily2-Theme-${new Date().toISOString().slice(0, 10)}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); toastr.success('主题文件已开始下载。', '导出成功'); } function importStyles() { const input = document.createElement('input'); input.type = 'file'; input.accept = '.json'; input.style.display = 'none'; const cleanup = () => { if (document.body.contains(input)) { document.body.removeChild(input); } }; input.onchange = e => { const file = e.target.files[0]; if (!file) { cleanup(); return; } const reader = new FileReader(); reader.onload = event => { try { const importedStyles = JSON.parse(event.target.result); if (typeof importedStyles !== 'object' || Array.isArray(importedStyles)) { throw new Error('无效的JSON格式。'); } applyStyles(importedStyles); saveStyles(importedStyles); toastr.success('主题已成功导入并应用!'); } catch (error) { toastr.error(`导入失败:${error.message}`, '错误'); } finally { cleanup(); } }; reader.readAsText(file); }; document.body.appendChild(input); input.click(); } function compareVersions(v1, v2) { const parts1 = v1.split('.').map(Number); const parts2 = v2.split('.').map(Number); const len = Math.max(parts1.length, parts2.length); for (let i = 0; i < len; i++) { const p1 = parts1[i] || 0; const p2 = parts2[i] || 0; if (p1 > p2) return true; if (p1 < p2) return false; } return false; } async function handleUpdateCheck() { console.log("【Amily2号】帝国已就绪,现派遣外交官,为陛下探查外界新情报..."); const updateInfo = await checkForUpdates(); if (updateInfo && updateInfo.version) { const isNew = compareVersions(updateInfo.version, pluginVersion); if(isNew) { console.log(`【Amily2号-情报部】捷报!发现新版本: ${updateInfo.version}。情报已转交内务府。`); } else { console.log(`【Amily2号-情报部】一切安好,帝国已是最新版本。情报已转交内务府备案。`); } setUpdateInfo(isNew, updateInfo); applyUpdateIndicator(); } } function sanitizeHTML(html) { if (window.DOMPurify) { return window.DOMPurify.sanitize(html, { ALLOWED_TAGS: ['b', 'i', 'u', 'em', 'strong', 'a', 'p', 'br', 'span', 'div', 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'font', 'blockquote', 'code', 'pre', 'hr', 'img', 'table', 'thead', 'tbody', 'tr', 'th', 'td'], ALLOWED_ATTR: ['href', 'target', 'style', 'class', 'color', 'size', 'src', 'alt', 'title', 'width', 'height', 'align'], FORBID_TAGS: ['script', 'style', 'iframe', 'frame', 'object', 'embed', 'form', 'input', 'textarea', 'button', 'select', 'option'], FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover', 'onmouseout', 'onmousedown', 'onmouseup', 'ondblclick', 'onkeydown', 'onkeypress', 'onkeyup', 'onfocus', 'onblur', 'onchange', 'onsubmit', 'onreset', 'onselect', 'oncontextmenu'], ADD_ATTR: ['target'], }); } const tempDiv = document.createElement('div'); tempDiv.innerHTML = html; const allowedTags = ['b', 'i', 'u', 'em', 'strong', 'a', 'p', 'br', 'span', 'div', 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'font']; const allowedAttrs = ['href', 'target', 'style', 'class', 'color', 'size']; const elements = tempDiv.querySelectorAll('*'); const allElements = tempDiv.getElementsByTagName('*'); for (let i = allElements.length - 1; i >= 0; i--) { const el = allElements[i]; const tagName = el.tagName.toLowerCase(); if (!allowedTags.includes(tagName)) { el.parentNode.removeChild(el); continue; } // 移除所有属性,只保留允许的 const attrs = Array.from(el.attributes); for (const attr of attrs) { const attrName = attr.name.toLowerCase(); if (!allowedAttrs.includes(attrName)) { el.removeAttribute(attr.name); } else if (attrName === 'href') { // 检查 href 是否包含 javascript: if (attr.value.toLowerCase().trim().startsWith('javascript:')) { el.removeAttribute('href'); } } else if (attrName.startsWith('on')) { // 双重保险,移除所有事件处理器 el.removeAttribute(attr.name); } } } return tempDiv.innerHTML; } async function handleMessageBoard() { const updateMessage = async () => { try { const messageData = await fetchMessageBoardContent(); if (messageData && messageData.message) { const messageBoard = $('#amily2_message_board'); const messageContent = $('#amily2_message_content'); // 使用净化后的 HTML,防止 XSS 攻击 const safeContent = sanitizeHTML(messageData.message); messageContent.html(safeContent); messageBoard.show(); console.log("【Amily2号-内务府】已成功获取并展示来自陛下的最新圣谕。"); } } catch (error) { console.error("【Amily2号-内务府】获取留言板失败:", error); } }; await updateMessage(); setInterval(updateMessage, 300000); // 5分钟刷新一次(从60秒改为300秒) } function loadPluginStyles() { const loadStyleFile = (fileName) => { const styleId = `amily2-style-${fileName.split('.')[0]}`; if (document.getElementById(styleId)) return; const extensionPath = `scripts/extensions/third-party/${extensionName}/assets/${fileName}?v=${Date.now()}`; const link = document.createElement("link"); link.id = styleId; link.rel = "stylesheet"; link.type = "text/css"; link.href = extensionPath; document.head.appendChild(link); console.log(`[Amily2号-皇家制衣局] 已为帝国披上华服: ${fileName}`); }; // 颁布三道制衣圣谕 loadStyleFile("style.css"); // 【第一道圣谕】为帝国主体宫殿披上通用华服 loadStyleFile("historiography.css"); // 【第二道圣谕】为敕史局披上其专属华服 loadStyleFile("amily-hanlinyuan-system/hanlinyuan.css"); // 【第三道圣谕】为翰林院披上其专属华服 loadStyleFile("amily-glossary-system/amily2-glossary.css"); // 【新圣谕】为术语表披上其专属华服 loadStyleFile("amily-data-table/table.css"); // 【第四道圣谕】为内存储司披上其专属华服 loadStyleFile("optimization.css"); // 【第五道圣谕】为剧情优化披上其专属华服 loadStyleFile("renderer.css"); // 【新圣谕】为渲染器披上其专属华服 // loadStyleFile("iframe-renderer.css"); // 【新圣谕】为iframe渲染内容披上其专属华服 loadStyleFile("renderer.css"); // 【新圣谕】为iframe渲染内容披上其专属华服 loadStyleFile("super-memory.css"); // 【新圣谕】为超级记忆披上其专属华服 // 【第六道圣谕】为角色世界书披上其专属华服 const cwbStyleId = 'cwb-feature-style'; if (!document.getElementById(cwbStyleId)) { const cwbLink = document.createElement("link"); cwbLink.id = cwbStyleId; cwbLink.rel = "stylesheet"; cwbLink.type = "text/css"; cwbLink.href = `scripts/extensions/third-party/${extensionName}/CharacterWorldBook/cwb_style.css?v=${Date.now()}`; document.head.appendChild(cwbLink); console.log(`[Amily2号-皇家制衣局] 已为角色世界书披上华服: cwb_style.css`); } // 【第七道圣谕】为世界编辑器披上其专属华服 const worldEditorStyleId = 'world-editor-style'; if (!document.getElementById(worldEditorStyleId)) { const worldEditorLink = document.createElement("link"); worldEditorLink.id = worldEditorStyleId; worldEditorLink.rel = "stylesheet"; worldEditorLink.type = "text/css"; worldEditorLink.href = `scripts/extensions/third-party/${extensionName}/WorldEditor/WorldEditor.css?v=${Date.now()}`; document.head.appendChild(worldEditorLink); console.log(`[Amily2号-皇家制衣局] 已为世界编辑器披上华服: WorldEditor.css`); } } window.addEventListener('message', function (event) { // 处理头像获取请求 if (event.data && event.data.type === 'getAvatars') { // 【兼容性修复】如果 LittleWhiteBox 激活,则不处理此消息,避免冲突 if (window.isXiaobaixEnabled) { return; } const userAvatar = `/characters/${getContext().userCharacter?.avatar ?? ''}`; const charAvatar = `/characters/${getContext().characters[this_chid]?.avatar ?? ''}`; event.source.postMessage({ source: 'amily2-host', type: 'avatars', urls: { user: userAvatar, char: charAvatar } }, '*'); return; } // 处理来自 iframe 的交互事件 if (event.data && event.data.source === 'amily2-iframe') { const { action, detail } = event.data; console.log(`[Amily2-主窗口] 收到来自iframe的动作: ${action}`, detail); switch (action) { case 'sendMessage': if (detail && detail.message) { $('#send_textarea').val(detail.message).trigger('input'); $('#send_but').trigger('click'); console.log(`[Amily2-主窗口] 已发送消息: ${detail.message}`); } break; case 'showToast': if (detail && detail.message && window.toastr) { const toastType = detail.type || 'info'; if (typeof window.toastr[toastType] === 'function') { window.toastr[toastType](detail.message, detail.title || '通知'); } } break; case 'buttonClick': console.log(`[Amily2-主窗口] 按钮被点击:`, detail); if (window.toastr) { window.toastr.info(`按钮 "${detail.buttonId || '未知'}" 被点击`, 'iframe交互'); } break; default: console.warn(`[Amily2-主窗口] 未知的动作类型: ${action}`); } } }); window.addEventListener("error", (event) => { const stackTrace = event.error?.stack || ""; if (stackTrace.includes("ST-Amily2-Chat-Optimisation")) { console.error("[Amily2-全局卫队] 捕获到严重错误:", event.error); toastr.error(`Amily2插件错误: ${event.error?.message || "未知错误"}`, "严重错误", { timeOut: 10000 }); } }); let isProcessingPlotOptimization = false; /** * 加载必要的外部库(如 DOMPurify)。 * 如果加载失败,会回退到内置的简单净化器。 */ function loadExternalLibraries() { loadExternalScript(DOMPURIFY_CDN, 'DOMPurify').catch(e => console.warn("[Amily2] DOMPurify 加载失败,将使用内置净化器:", e)); } /** * 初始化上下文优化器模块。 * 优先注册宏,确保其在其他处理之前生效。 */ function initializeContextOptimizer() { try { console.log("[Amily2号-开国大典] 步骤0:优先注册上下文优化器..."); registerContextOptimizerMacros(); } catch (e) { console.error("[Amily2号-开国大典] 上下文优化器注册失败:", e); } } /** * 异步初始化“密折司”模块。 * 该模块通常用于处理机密或特殊的后台逻辑。 */ async function initializeMiZheSi() { try { await import("./MiZheSi/index.js"); console.log("[Amily2号-开国大典] 密折司模块已就位。"); } catch (e) { console.error("[Amily2号-开国大典] 密折司加载失败:", e); } } /** * 注册所有与 SillyTavern 交互的 API 处理器。 * 包括消息获取、设置、删除,以及 Lorebook 管理等功能。 */ function registerAllApiHandlers() { initializeApiListener(); registerApiHandler('getChatMessages', async (data) => amilyHelper.getChatMessages(data.range, data.options)); registerApiHandler('setChatMessages', async (data) => amilyHelper.setChatMessages(data.messages, data.options)); registerApiHandler('setChatMessage', async (data) => { const field_values = data.field_values || data.content; const message_id = data.message_id !== undefined ? data.message_id : data.index; const options = data.options || {}; console.log('[Amily2-API] setChatMessage 收到参数:', { field_values, message_id, options, raw_data: data }); return await amilyHelper.setChatMessage(field_values, message_id, options); }); registerApiHandler('createChatMessages', async (data) => amilyHelper.createChatMessages(data.messages, data.options)); registerApiHandler('deleteChatMessages', async (data) => amilyHelper.deleteChatMessages(data.ids, data.options)); registerApiHandler('getLorebooks', async (data) => amilyHelper.getLorebooks()); registerApiHandler('getCharLorebooks', async (data) => amilyHelper.getCharLorebooks(data.options)); registerApiHandler('getLorebookEntries', async (data) => amilyHelper.getLorebookEntries(data.bookName)); registerApiHandler('setLorebookEntries', async (data) => amilyHelper.setLorebookEntries(data.bookName, data.entries)); registerApiHandler('createLorebookEntries', async (data) => amilyHelper.createLorebookEntries(data.bookName, data.entries)); registerApiHandler('createLorebook', async (data) => amilyHelper.createLorebook(data.bookName)); registerApiHandler('triggerSlash', async (data) => amilyHelper.triggerSlash(data.command)); registerApiHandler('getLastMessageId', async (data) => amilyHelper.getLastMessageId()); registerApiHandler('toastr', async (data) => { if (window.toastr && typeof window.toastr[data.type] === 'function') { window.toastr[data.type](data.message, data.title); } return true; }); registerApiHandler('switchSwipe', async (data) => { const { messageIndex, swipeIndex } = data; const messages = await amilyHelper.getChatMessages(messageIndex, { include_swipes: true }); if (messages && messages.length > 0 && messages[0].swipes) { const content = messages[0].swipes[swipeIndex]; if (content !== undefined) { await amilyHelper.setChatMessages([{ message_id: messageIndex, message: content }], { refresh: 'affected' }); const context = getContext(); if (context.chat[messageIndex]) { context.chat[messageIndex].swipe_id = swipeIndex; } return { success: true, message: `已切换至开场白 ${swipeIndex}` }; } } throw new Error(`无法切换到开场白 ${swipeIndex}`); }); } /** * 合并插件的默认设置与用户设置。 * 确保即使在升级后,新增加的设置项也有默认值。 */ function mergePluginSettings() { if (!extension_settings[extensionName]) { extension_settings[extensionName] = {}; } const combinedDefaultSettings = { ...defaultSettings, ...tableSystemDefaultSettings, ...cwbDefaultSettings, render_on_every_message: false, amily_render_enabled: false }; for (const key in combinedDefaultSettings) { if (extension_settings[extensionName][key] === undefined) { extension_settings[extensionName][key] = combinedDefaultSettings[key]; } } console.log("[Amily2号-帝国枢密院] 帝国基本法已确认,档案室已与国库对接完毕。"); } /** * 注册用于表格内容的 SillyTavern 宏。 * 允许在 Prompt 中使用 {{Amily2EditContent}} 来插入动态生成的表格数据。 */ function registerTableMacros() { console.log("[Amily2号-开国大典] 步骤3.8:注册表格占位符宏..."); try { eventSource.on(event_types.GENERATION_STARTED, () => { resetContextBuffer(); if (isProcessingPlotOptimization) { console.warn("[Amily2-剧情优化] 检测到生成开始,但优化标志位仍为 true。这可能是并发生成或状态未及时重置。"); } }); const context = getContext(); if (context && typeof context.registerMacro === 'function') { context.registerMacro('Amily2EditContent', () => { const content = generateTableContent(); if (content) { window.AMILY2_MACRO_REPLACED = true; } return content; }); console.log('[Amily2-核心引擎] 已成功注册表格占位符宏: {{Amily2EditContent}}'); } else { console.warn('[Amily2-核心引擎] 无法注册表格宏,可能是 SillyTavern 版本不兼容。'); } } catch (error) { console.error('[Amily2-核心引擎] 注册表格宏时发生错误:', error); } } /** * 处理用户发送消息前的逻辑(剧情优化)。 * 拦截消息发送,进行剧情梳理和总结,然后注入到 Prompt 中。 * * @param {string} type - 触发类型 (例如 'send') * @param {object} params - 参数对象 * @param {boolean} dryRun - 是否为试运行 * @returns {Promise} - 返回 false 以阻止默认行为(如果已异步处理),或不做阻拦。 */ async function onPlotGenerationAfterCommands(type, params, dryRun) { clearUpdatedTables(); if (isProcessingPlotOptimization) { console.log("[Amily2-剧情优化] 优化正在进行中,拦截重复触发。"); return; } console.log("[Amily2-剧情优化] Generation after commands triggered", { type, params, dryRun }); if (type === 'regenerate' || dryRun) { console.log("[Amily2-剧情优化] Skipping due to regenerate or dryRun."); return false; } const globalSettings = extension_settings[extensionName]; if (globalSettings?.plotOpt_enabled === false) return false; const isJqyhEnabled = globalSettings?.jqyhEnabled === true; const hasProfile = !!apiProfileManager.getAssignment('main') || !!apiProfileManager.getAssignment('plotOpt'); const hasLegacyConfig = !!globalSettings?.apiUrl || !!globalSettings?.tavernProfile || !!globalSettings?.plotOpt_apiUrl || !!globalSettings?.plotOpt_tavernProfile; if (!isJqyhEnabled && !hasProfile && !hasLegacyConfig) { console.log("[Amily2-剧情优化] 优化已启用,但未配置任何可用的 API(无 Profile 分配亦无独立配置)。"); return false; } let userMessage = $('#send_textarea').val(); let isFromTextarea = true; const context = getContext(); if (!userMessage) { if (context.chat && context.chat.length > 0) { const lastMsg = context.chat[context.chat.length - 1]; if (lastMsg.is_user) { userMessage = lastMsg.mes; isFromTextarea = false; console.log("[Amily2-剧情优化] Detected empty textarea, processing last user message."); } } } if (!userMessage) return false; isProcessingPlotOptimization = true; const cancellationState = { isCancelled: false }; showPlotOptimizationProgress(cancellationState); const onProgress = (message, isDone = false, isSkipped = false) => { updatePlotOptimizationProgress(message, isDone, isSkipped); }; try { const cancellationPromise = new Promise((_, reject) => { const checkCancel = setInterval(() => { if (cancellationState.isCancelled) { clearInterval(checkCancel); reject(new Error("Optimization cancelled by user")); } }, 100); }); const contextTurnCount = globalSettings.plotOpt_contextLimit || 10; const contextSource = isFromTextarea ? context.chat : context.chat.slice(0, -1); const slicedContext = contextTurnCount > 0 ? contextSource.slice(-contextTurnCount) : contextSource; const optimizationPromise = processPlotOptimization({ mes: userMessage }, slicedContext, cancellationState, onProgress); const result = await Promise.race([optimizationPromise, cancellationPromise]); if (cancellationState.isCancelled) throw new Error("Optimization cancelled by user"); if (result && result.contentToAppend) { const finalMessage = userMessage + '\n' + result.contentToAppend; if (params && typeof params === 'object') { try { if (params.prompt) params.prompt = finalMessage; if (Array.isArray(params.messages)) { const lastMsg = params.messages[params.messages.length - 1]; if (lastMsg && lastMsg.role === 'user') { lastMsg.content = finalMessage; } } } catch (e) { console.warn("[Amily2-剧情优化] 尝试修改 params 失败:", e); } } if (isFromTextarea) { $('#send_textarea').val(finalMessage).trigger('input'); } else { const targetMessageId = context.chat.length - 1; await amilyHelper.setChatMessage(finalMessage, targetMessageId, { refresh: 'none' }); } toastr.success('剧情优化已完成并注入,继续生成...', '操作成功'); isProcessingPlotOptimization = false; hidePlotOptimizationProgress(); return false; } else { console.log("[Amily2-剧情优化] Plot optimization returned no result. Sending original message."); isProcessingPlotOptimization = false; hidePlotOptimizationProgress(); return false; } } catch (error) { if (cancellationState.isCancelled || error.message === "Optimization cancelled by user") { console.log("[Amily2-剧情优化] 优化流程已被用户中止。发送原始消息。"); toastr.warning('记忆管理任务已中止。', '操作取消', { timeOut: 2000 }); } else { console.error(`[Amily2-剧情优化] 处理发送前事件时出错:`, error); toastr.error('记忆管理处理失败,将发送原始消息。', '错误'); } isProcessingPlotOptimization = false; hidePlotOptimizationProgress(); return false; } } /** * 注册核心事件监听器。 * 包含对消息接收、编辑、删除、滑动等事件的处理,以及剧情优化的触发。 */ function registerEventListeners() { console.log("[Amily2号-开国大典] 步骤四:部署帝国哨兵网络..."); if (!window.amily2EventsRegistered) { eventSource.on(event_types.GENERATION_AFTER_COMMANDS, onPlotGenerationAfterCommands); eventSource.on(event_types.MESSAGE_RECEIVED, onMessageReceived); eventSource.on(event_types.IMPERSONATE_READY, onMessageReceived); // handleTableUpdate for MESSAGE_RECEIVED removed — now handled by pipeline Stage 3 inside onMessageReceived eventSource.on(event_types.MESSAGE_SWIPED, async (chat_id) => { const context = getContext(); if (context.chat.length < 2) { log('【监察系统】检测到消息滑动,但聊天记录不足,已跳过状态回退。', 'info'); return; } log('【监察系统】检测到消息滑动 (SWIPED),开始执行状态回退...', 'warn'); rollbackState(); const latestMessage = context.chat[chat_id] || context.chat[context.chat.length - 1]; if (latestMessage.is_user) { log('【监察系统】滑动后最新消息是用户,跳过填表。', 'info'); renderTables(); return; } const settings = extension_settings[extensionName]; const fillingMode = settings.filling_mode || 'main-api'; if (fillingMode === 'main-api') { log(`【监察系统】主填表模式,回退后强制刷新消息ID: ${chat_id}。`, 'info'); await handleTableUpdate(chat_id, true); } else if (fillingMode === 'secondary-api' || fillingMode === 'optimized') { log('【监察系统】分步/优化模式,回退后强制二次填表最新消息。', 'info'); await fillWithSecondaryApi(latestMessage, true); } else { log('【监察系统】未配置填表模式,跳过填表。', 'info'); } renderTables(); log('【监察系统】滑动后填表完成,UI 已刷新。', 'success'); }); eventSource.on(event_types.MESSAGE_EDITED, (mes_id) => { handleTableUpdate(mes_id); updateOrInsertTableInChat(); }); eventSource.on(event_types.CHAT_CHANGED, () => { window.lastPreOptimizationResult = null; document.dispatchEvent(new CustomEvent('preOptimizationTextUpdated')); manageLorebookEntriesForChat(); setTimeout(() => { log("【监察系统】检测到“朝代更迭”(CHAT_CHANGED),开始重修史书并刷新宫殿...", 'info'); clearHighlights(); clearUpdatedTables(); loadTables(); renderTables(); if (extension_settings[extensionName].render_on_every_message) { startContinuousRendering(); } else { stopContinuousRendering(); } }, 100); }); eventSource.on(event_types.MESSAGE_DELETED, (message, index) => { log(`【监察系统】检测到消息 ${index} 被删除,开始精确回滚UI状态。`, 'warn'); clearHighlights(); loadTables(index); renderTables(); }); eventSource.on(event_types.MESSAGE_RECEIVED, updateOrInsertTableInChat); eventSource.on(event_types.chat_updated, updateOrInsertTableInChat); window.amily2EventsRegistered = true; } } /** * 执行 Amily2 的统一注入逻辑。 * 同时兼容表格数据注入和 RAG 上下文重排。 * @param {...any} args - 传递给 injectTableData 和 rearrangeChat 的参数 */ async function executeAmily2Injection(...args) { console.log('[Amily2-核心引擎] 开始执行统一注入 (聊天长度:', args[0]?.length || 0, ')'); try { await injectTableData(...args); } catch (error) { console.error('[Amily2-内存储司] 表格注入失败:', error); } if (window.hanlinyuanRagProcessor && typeof window.hanlinyuanRagProcessor.rearrangeChat === 'function') { try { console.log('[Amily2-核心引擎] 执行内置RAG注入。'); await window.hanlinyuanRagProcessor.rearrangeChat(...args); } catch (error) { console.error('[Amily2-翰林院] RAG注入失败:', error); } } } /** * 初始化 RAG 处理器并设置注入策略。 * 覆盖 `vectors_rearrangeChat` 以确保 Amily2 的注入逻辑优先执行。 */ function initializeRagAndInjection() { console.log("[Amily2号-开国大典] 步骤五:初始化RAG处理器..."); try { initializeRagProcessor(); console.log('[Amily2-翰林院] RAG处理器已成功初始化'); } catch (error) { console.error('[Amily2-翰林院] RAG处理器初始化失败:', error); } console.log("[Amily2号-开国大典] 步骤六:智能冲突检测与注入策略..."); console.log('[Amily2-策略] 采用“完全主导”策略,覆盖 `vectors_rearrangeChat`。'); window['vectors_rearrangeChat'] = executeAmily2Injection; if (window['amily2HanlinyuanInjector']) { window['amily2HanlinyuanInjector'] = null; } } /** * 执行部署完成后的后续任务。 * 包括:版本检查、在线人数统计、本地联动、超级记忆初始化、渲染器启动和主题应用。 */ function performPostDeploymentTasks() { console.log("【Amily2号】帝国秩序已完美建立。Amily2号的府邸已恭候陛下的莅临。"); if (checkAuthorization()) { const userType = localStorage.getItem("plugin_user_type") || "未知"; const userNote = localStorage.getItem("plugin_user_note"); const displayNote = userNote || userType; toastr.success(`欢迎回来!授权状态有效 (用户: ${displayNote})`, "Amily2 插件已就绪"); refreshUserInfo().then(data => { if (data && data.note && data.note !== userNote) { console.log("[Amily2] 用户信息已更新:", data.note); } }).catch(e => console.warn("[Amily2] 后台刷新用户信息失败:", e)); } console.log("[Amily2号-开国大典] 步骤七:初始化版本显示系统..."); if (typeof window.amily2Updater !== 'undefined') { setTimeout(() => { console.log("[Amily2号-版本系统] 正在启动版本检测器..."); window.amily2Updater.initialize(); }, 2000); } else { console.warn("[Amily2号-版本系统] 版本检测器未找到,可能加载失败"); } handleUpdateCheck(); handleMessageBoard(); initializeOnlineTracker(); initializeLocalLinkage(); setTimeout(() => initializeSuperMemory(), 3000); initializeRenderer(); if (extension_settings[extensionName].render_on_every_message) { startContinuousRendering(); } setTimeout(() => { try { loadAndApplyStyles(); const importThemeBtn = document.getElementById('amily2-import-theme-btn'); const exportThemeBtn = document.getElementById('amily2-export-theme-btn'); const resetThemeBtn = document.getElementById('amily2-reset-theme-btn'); if (importThemeBtn) importThemeBtn.addEventListener('click', importStyles); if (exportThemeBtn) exportThemeBtn.addEventListener('click', exportStyles); if (resetThemeBtn) resetThemeBtn.addEventListener('click', resetToDefaultStyles); log('【凤凰阁】内联主题系统已通过延迟加载成功初始化并绑定事件。', 'success'); } catch (error) { log(`【凤凰阁】内联主题系统初始化失败: ${error}`, 'error'); } }, 500); } /** * Amily2 核心部署流程(开国大典)。 * 只有当 SillyTavern 基础 UI 加载完成后才会执行此函数。 * 负责按顺序初始化插件的各个子系统。 */ async function runAmily2Deployment() { console.log("[Amily2号-帝国枢密院] SillyTavern宫殿主体已确认,开国大典正式开始!"); try { console.log("[Amily2号-开国大典] 步骤一:为宫殿披上华服..."); loadPluginStyles(); console.log("[Amily2号-开国大典] 步骤二:皇家仪仗队就位..."); await registerSlashCommands(); console.log("[Amily2号-开国大典] 步骤三:开始召唤府邸(模块注册式架构)..."); await createDrawer(); // Glossary 和 CWB 的初始化已由 ModuleRegistry 在 mount 阶段完成, // 不再需要 waitForGlossaryPanelAndBindEvents / waitForCwbPanelAndInitialize 轮询。 registerTableMacros(); registerEventListeners(); initializeRagAndInjection(); performPostDeploymentTasks(); } catch (error) { console.error("!!!【开国大典失败】在执行系列法令时发生严重错误:", error); } } jQuery(async () => { console.log("[Amily2号-帝国枢密院] 开始执行开国大典..."); initializeAmilyBus(); loadExternalLibraries(); initializeContextOptimizer(); await initializeMiZheSi(); registerAllApiHandlers(); initializeAmilyHelper(); mergePluginSettings(); configManager.migrate(); // 将 extension_settings 中残留的敏感字段迁移到 localStorage let attempts = 0; const maxAttempts = 100; const checkInterval = 100; const targetSelector = "#sys-settings-button"; const deploymentInterval = setInterval(async () => { if ($(targetSelector).length > 0) { clearInterval(deploymentInterval); await runAmily2Deployment(); } else { attempts++; if (attempts >= maxAttempts) { clearInterval(deploymentInterval); console.error(`[Amily2号] 部署失败:等待 ${targetSelector} 超时。`); } } }, checkInterval); }); function applyMessageLimit() { const limit = window.amily2MaxMessages; if (!limit) return; const chatContainer = document.getElementById('chat'); if (!chatContainer) return; const messages = Array.from(chatContainer.getElementsByClassName('mes')); const total = messages.length; if (total <= limit) { // 如果消息数未超标,确保所有消息可见 messages.forEach(el => el.style.display = ''); return; } // 隐藏旧消息,保留最后 limit 条 const hideCount = total - limit; for (let i = 0; i < total; i++) { if (i < hideCount) { messages[i].style.setProperty('display', 'none', 'important'); } else { messages[i].style.removeProperty('display'); } } console.log(`[Amily2-性能优化] 已隐藏 ${hideCount} 条旧消息,仅显示最近 ${limit} 条。`); } // 监听聊天更新事件以应用限制 eventSource.on(event_types.MESSAGE_RECEIVED, () => setTimeout(applyMessageLimit, 100)); eventSource.on(event_types.chat_updated, () => setTimeout(applyMessageLimit, 100)); function initializeOnlineTracker() { const wsUrl = 'wss://amilyservice.amily49.cc'; let ws = null; let reconnectTimer = null; let isConnecting = false; function mountTracker() { const $drawerContent = $('#amily2_drawer_content'); if ($drawerContent.length === 0 || !$drawerContent.data('initialized')) { setTimeout(mountTracker, 1000); return; } if ($('#amily2-online-tracker').length > 0) return; const $container = $('
'); $container.html('Connecting...'); $drawerContent.prepend($container); connect(); } function connect() { // 单例模式检查:如果已有连接且处于连接中或打开状态,则不重复创建 if (ws && (ws.readyState === WebSocket.CONNECTING || ws.readyState === WebSocket.OPEN)) { console.log('[Amily2-在线统计] 连接已存在,跳过创建'); return; } // 防止短时间内重复调用 if (isConnecting) return; isConnecting = true; // 清理旧连接 if (ws) { try { ws.close(); } catch (e) {} ws = null; } try { console.log('[Amily2-在线统计] 开始建立连接...'); ws = new WebSocket(wsUrl); ws.onopen = () => { console.log('[Amily2-在线统计] 已连接到服务器'); isConnecting = false; if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; } }; ws.onmessage = (event) => { try { const data = JSON.parse(event.data); if (data.type === 'online_count') { $('#amily2-online-count').text(`${data.count} 人在线`); } } catch (e) { console.error('[Amily2-在线统计] 解析消息失败:', e); } }; ws.onclose = () => { console.log('[Amily2-在线统计] 连接断开'); $('#amily2-online-count').text('离线'); isConnecting = false; ws = null; // 延迟重连,而不是立即循环 if (!reconnectTimer) { reconnectTimer = setTimeout(() => { reconnectTimer = null; connect(); }, 5000); } }; ws.onerror = (err) => { console.warn('[Amily2-在线统计] 连接错误:', err); // onerror 通常会触发 onclose,所以这里不需要额外的重连逻辑,交给 onclose 处理 }; } catch (e) { console.error('[Amily2-在线统计] 初始化失败:', e); isConnecting = false; if (!reconnectTimer) { reconnectTimer = setTimeout(() => { reconnectTimer = null; connect(); }, 5000); } } } // 启动挂载流程 mountTracker(); } function initializeLocalLinkage() { const wsUrl = 'ws://127.0.0.1:2086'; let ws = null; let retryCount = 0; const maxRetries = 5; function connect() { if (retryCount >= maxRetries) { console.log('[Amily2-本地联动] 达到最大重试次数,停止连接本地服务。'); return; } console.log('[Amily2-本地联动] 尝试连接本地联动服务...'); ws = new WebSocket(wsUrl); ws.onopen = () => { console.log('[Amily2-本地联动] 已连接到启动器服务'); if (window.toastr) toastr.success('已连接到 Amily 启动器', '本地联动'); retryCount = 0; // 连接成功,重置计数 }; ws.onmessage = async (event) => { try { const data = JSON.parse(event.data); if (data.type === 'command') { console.log('[Amily2-本地联动] 收到指令:', data.command, data.args); if (data.command === 'triggerSlash') { if (window.AmilyHelper) { await window.AmilyHelper.triggerSlash(data.args.content); } } else if (data.command === 'cleanOldMessages') { const keep = parseInt(data.args.keep) || 50; if (window.AmilyHelper) { const total = window.AmilyHelper.getLastMessageId() + 1; if (total > keep) { const deleteCount = total - keep; // 生成要删除的 ID 列表 (0 到 deleteCount - 1) const idsToDelete = Array.from({length: deleteCount}, (_, i) => i); await window.AmilyHelper.deleteChatMessages(idsToDelete, { refresh: 'all' }); if (window.toastr) window.toastr.success(`已清理 ${deleteCount} 条旧消息,保留最近 ${keep} 条`, '清理完成'); } else { if (window.toastr) window.toastr.info('消息数量未超过保留限制,无需清理', '无需清理'); } } } else if (data.command === 'setMaxMessages') { const limit = parseInt(data.args.limit); if (!isNaN(limit) && limit > 0) { window.amily2MaxMessages = limit; applyMessageLimit(); if (window.toastr) window.toastr.success(`已限制显示最近 ${limit} 条消息`, '性能优化'); } } // 这里可以扩展更多指令 } } catch (e) { console.error('[Amily2-本地联动] 处理消息失败:', e); } }; ws.onclose = () => { console.log('[Amily2-本地联动] 连接断开'); retryCount++; if (retryCount < maxRetries) { console.log(`[Amily2-本地联动] ${5}秒后尝试重连 (${retryCount}/${maxRetries})`); setTimeout(connect, 5000); } else { console.log('[Amily2-本地联动] 已停止重连尝试。'); } }; ws.onerror = (err) => { // console.warn('[Amily2-本地联动] 连接错误:', err); }; } connect(); }