From 5945bdf282c4723e565668ad7a29f5aa328b859b Mon Sep 17 00:00:00 2001
From: Wx-2025 <351320169@qq.com>
Date: Tue, 6 Jan 2026 10:38:23 +0800
Subject: [PATCH] Update table-bindings.js
---
ui/table-bindings.js | 2733 ++++++++++--------------------------------
1 file changed, 600 insertions(+), 2133 deletions(-)
diff --git a/ui/table-bindings.js b/ui/table-bindings.js
index ad0aff9..75bd1ba 100644
--- a/ui/table-bindings.js
+++ b/ui/table-bindings.js
@@ -1,2187 +1,654 @@
-import * as TableManager from '../core/table-system/manager.js';
-import { log } from '../core/table-system/logger.js';
-import { extension_settings, getContext } from '/scripts/extensions.js';
+import { getMemoryState, getHighlights } from '../core/table-system/manager.js';
+import { extension_settings } from '/scripts/extensions.js';
import { extensionName } from '../utils/settings.js';
-import { updateOrInsertTableInChat } from './message-table-renderer.js';
-import { saveSettingsDebounced } from '/script.js';
-import { startBatchFilling } from '../core/table-system/batch-filler.js';
-import { showHtmlModal } from './page-window.js';
-import { DEFAULT_AI_RULE_TEMPLATE, DEFAULT_AI_FLOW_TEMPLATE } from '../core/table-system/settings.js';
-import { world_names, loadWorldInfo } from '/scripts/world-info.js';
-import { safeCharLorebooks, safeLorebookEntries } from '../core/tavernhelper-compatibility.js';
-import { characters, this_chid, eventSource, event_types } from "/script.js";
-import { fetchNccsModels, testNccsApiConnection } from '../core/api/NccsApi.js';
-import { showGraphVisualization } from '../core/relationship-graph/visualizer.js';
-import { escapeHTML } from '../utils/utils.js';
+import { getContext } from '/scripts/extensions.js';
+const TABLE_CONTAINER_ID = 'amily2-chat-table-container';
const isTouchDevice = () => window.matchMedia('(pointer: coarse)').matches;
-const getAllTablesContainer = () => document.getElementById('all-tables-container');
-let isResizing = false;
+// 【V155.3】注入真正的游戏UI样式 (侧边栏+内容区)
+function injectChatTableStyles() {
+ if (document.getElementById('amily2-chat-table-styles')) return;
+ const style = document.createElement('style');
+ style.id = 'amily2-chat-table-styles';
+ style.textContent = `
+ /* 主容器:游戏面板风格 */
+ #amily2-chat-table-container {
+ display: flex !important; /* 强制 Flex 布局 */
+ flex-direction: row !important; /* 强制横向排列 */
+ min-height: 300px;
+ max-height: 80vh;
+ background: rgba(12, 14, 20, 0.95);
+ border: 2px solid #3a4a5e;
+ border-radius: 8px;
+ box-shadow: 0 0 20px rgba(0, 0, 0, 0.8), inset 0 0 30px rgba(0, 0, 0, 0.5);
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+ color: #c0c0c0;
+ margin-top: 15px;
+ overflow: hidden;
+ position: relative;
+ resize: vertical;
+ }
+ /* 装饰性角落 */
+ #amily2-chat-table-container::before {
+ content: '';
+ position: absolute;
+ top: 0; left: 0;
+ width: 20px; height: 20px;
+ border-top: 2px solid #00bfff;
+ border-left: 2px solid #00bfff;
+ border-radius: 6px 0 0 0;
+ z-index: 2;
+ }
+ #amily2-chat-table-container::after {
+ content: '';
+ position: absolute;
+ bottom: 0; right: 0;
+ width: 20px; height: 20px;
+ border-bottom: 2px solid #00bfff;
+ border-right: 2px solid #00bfff;
+ border-radius: 0 0 6px 0;
+ z-index: 2;
+ }
-function toggleRowContextMenu(event) {
- event.preventDefault();
- event.stopPropagation();
+ /* 侧边栏:导航菜单 */
+ .amily2-game-sidebar {
+ width: 140px; /* 加宽以显示文字 */
+ background: rgba(20, 25, 35, 0.9);
+ border-right: 1px solid #3a4a5e;
+ display: flex;
+ flex-direction: column;
+ align-items: stretch; /* 拉伸以填满宽度 */
+ padding: 10px;
+ gap: 8px;
+ overflow-y: auto;
+ flex-shrink: 0;
+ }
- const targetTd = event.target.closest('td.index-col');
- if (!targetTd) return;
+ .amily2-game-tab {
+ height: 40px;
+ border-radius: 6px;
+ display: flex;
+ justify-content: flex-start; /* 左对齐 */
+ align-items: center;
+ padding: 0 10px;
+ cursor: pointer;
+ color: #7a8a9e;
+ transition: all 0.2s ease;
+ position: relative;
+ border: 1px solid transparent;
+ font-size: 0.9em;
+ font-weight: 600;
+ }
- const tableWrapper = targetTd.closest('.amily2-table-wrapper');
- if (!tableWrapper) return;
+ .amily2-game-tab i {
+ width: 24px;
+ text-align: center;
+ margin-right: 8px;
+ }
- const isActive = targetTd.classList.contains('amily2-menu-open');
- document.querySelectorAll('.amily2-menu-open').forEach(openEl => {
- if (openEl !== targetTd) {
- openEl.classList.remove('amily2-menu-open');
- openEl.style.zIndex = '';
- openEl.style.position = '';
- const otherWrapper = openEl.closest('.amily2-table-wrapper');
- if (otherWrapper) {
- otherWrapper.style.overflowX = 'auto';
- otherWrapper.style.zIndex = '';
- otherWrapper.style.position = '';
+ .amily2-game-tab:hover {
+ color: #e0e0e0;
+ background: rgba(255, 255, 255, 0.05);
+ }
+
+ .amily2-game-tab.active {
+ color: #fff;
+ background: linear-gradient(90deg, rgba(0, 191, 255, 0.25), transparent);
+ border-left: 3px solid #00bfff;
+ text-shadow: 0 0 8px rgba(0, 191, 255, 0.8);
+ box-shadow: inset 5px 0 10px -5px rgba(0, 191, 255, 0.3);
+ }
+
+ .amily2-game-tab.active::after {
+ display: none; /* 移除原来的三角形指示器 */
+ }
+
+ /* 内容区 */
+ .amily2-game-content {
+ position: absolute;
+ left: 140px; top: 0; bottom: 0; right: 0;
+ overflow: hidden;
+ background: transparent;
+ display: flex;
+ flex-direction: column;
+ z-index: 10;
+ }
+
+ /* 扫描线效果 */
+ .amily2-game-content::before {
+ content: '';
+ position: absolute;
+ top: 0; left: 0; right: 0; bottom: 0;
+ background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.1) 50%);
+ background-size: 100% 4px;
+ pointer-events: none;
+ z-index: 1;
+ opacity: 0.3;
+ }
+
+ .amily2-game-panel {
+ display: none;
+ width: 100%;
+ height: 100%;
+ padding: 20px;
+ overflow-y: auto;
+ box-sizing: border-box;
+ position: relative;
+ z-index: 10; /* 确保最高层级 */
+ }
+
+ .amily2-game-panel.active {
+ display: block !important;
+ animation: amily2-panel-fade 0.3s ease-out;
+ }
+
+ @keyframes amily2-panel-fade {
+ from { opacity: 0; transform: translateY(5px); }
+ to { opacity: 1; transform: translateY(0); }
+ }
+
+ /* 面板标题 */
+ .amily2-panel-title {
+ font-size: 1.2em;
+ font-weight: bold;
+ color: #00bfff;
+ margin-bottom: 15px;
+ padding-bottom: 8px;
+ border-bottom: 2px solid rgba(0, 191, 255, 0.3);
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ display: flex;
+ align-items: center;
+ }
+
+ .amily2-panel-title i {
+ margin-right: 10px;
+ }
+
+ /* 卡片式布局 (RPG风格) */
+ .amily2-game-cards-container {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ }
+
+ .amily2-game-card {
+ background: rgba(30, 35, 45, 0.6);
+ border: 1px solid rgba(100, 149, 237, 0.15);
+ border-radius: 6px;
+ padding: 12px;
+ position: relative;
+ transition: all 0.2s ease;
+ }
+
+ .amily2-game-card:hover {
+ background: rgba(40, 50, 70, 0.8);
+ border-color: rgba(0, 191, 255, 0.4);
+ box-shadow: 0 0 15px rgba(0, 0, 0, 0.3);
+ transform: translateX(2px);
+ }
+
+ .amily2-game-card::before {
+ content: '';
+ position: absolute;
+ left: 0; top: 10px; bottom: 10px;
+ width: 3px;
+ background: #00bfff;
+ border-radius: 0 2px 2px 0;
+ opacity: 0.5;
+ }
+
+ .amily2-card-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 8px;
+ padding-bottom: 5px;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
+ }
+
+ .amily2-card-title {
+ font-size: 1.1em;
+ font-weight: bold;
+ color: #00bfff;
+ text-shadow: 0 0 5px rgba(0, 191, 255, 0.3);
+ }
+
+ .amily2-card-body {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+ gap: 8px 15px;
+ }
+
+ .amily2-card-attr {
+ display: flex;
+ flex-direction: column;
+ font-size: 0.9em;
+ }
+
+ .amily2-card-label {
+ color: #5a6a7e;
+ font-size: 0.8em;
+ text-transform: uppercase;
+ margin-bottom: 2px;
+ }
+
+ .amily2-card-value {
+ color: #e0e0e0;
+ }
+
+ /* 滚动条 */
+ .amily2-game-sidebar::-webkit-scrollbar,
+ .amily2-game-panel::-webkit-scrollbar {
+ width: 4px;
+ }
+ .amily2-game-sidebar::-webkit-scrollbar-track,
+ .amily2-game-panel::-webkit-scrollbar-track {
+ background: rgba(0, 0, 0, 0.2);
+ }
+ .amily2-game-sidebar::-webkit-scrollbar-thumb,
+ .amily2-game-panel::-webkit-scrollbar-thumb {
+ background: #3a4a5e;
+ border-radius: 2px;
+ }
+
+ /* 移动端适配 */
+ @media (max-width: 768px) {
+ #amily2-chat-table-container {
+ flex-direction: column;
+ height: auto;
+ min-height: 400px;
+ }
+ .amily2-game-sidebar {
+ width: 100% !important;
+ height: 50px !important;
+ flex-direction: row;
+ padding: 0 10px;
+ border-right: none;
+ border-bottom: 1px solid #3a4a5e;
+ overflow-x: auto;
+ top: 30px !important;
+ bottom: auto !important;
+ }
+ .amily2-game-content {
+ left: 0 !important;
+ top: 80px !important;
+ }
+ .amily2-game-tab {
+ flex-shrink: 0;
+ }
+ .amily2-game-tab.active::after {
+ right: auto;
+ bottom: -8px;
+ top: auto;
+ left: 50%;
+ transform: translateX(-50%) rotate(90deg);
}
}
- });
- targetTd.classList.toggle('amily2-menu-open');
+ /* 折叠功能样式 */
+ #amily2-chat-table-container.collapsed {
+ min-height: 30px !important;
+ height: 30px !important;
+ resize: none !important;
+ overflow: hidden !important;
+ border-bottom: none;
+ }
- if (targetTd.classList.contains('amily2-menu-open')) {
- tableWrapper.style.overflowX = 'visible';
- tableWrapper.style.position = 'relative';
- tableWrapper.style.zIndex = '10';
- targetTd.style.position = 'relative';
- targetTd.style.zIndex = '100';
- } else {
- tableWrapper.style.overflowX = 'auto';
- tableWrapper.style.position = '';
- tableWrapper.style.zIndex = '';
- targetTd.style.position = '';
- targetTd.style.zIndex = '';
+ #amily2-chat-table-container.collapsed .amily2-game-sidebar,
+ #amily2-chat-table-container.collapsed .amily2-game-content {
+ display: none !important;
+ }
+
+ .amily2-table-toggle {
+ position: absolute;
+ top: 0; left: 0; right: 0;
+ height: 30px;
+ background: rgba(20, 25, 35, 0.95);
+ border-bottom: 1px solid #3a4a5e;
+ color: #00bfff;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ z-index: 100;
+ font-size: 0.85em;
+ font-weight: bold;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ transition: all 0.2s;
+ }
+
+ .amily2-table-toggle:hover {
+ background: rgba(40, 50, 70, 1);
+ color: #fff;
+ }
+
+ .amily2-table-toggle i {
+ margin-right: 8px;
+ transition: transform 0.3s;
+ }
+
+ #amily2-chat-table-container:not(.collapsed) .amily2-table-toggle i {
+ transform: rotate(180deg);
+ }
+ `;
+ document.head.appendChild(style);
+}
+
+function getTableIcon(tableName) {
+ const lowerName = tableName.toLowerCase();
+ if (lowerName.includes('时空') || lowerName.includes('时间') || lowerName.includes('time') || lowerName.includes('clock')) return 'fa-clock';
+ if (lowerName.includes('角色') || lowerName.includes('人物') || lowerName.includes('char') || lowerName.includes('person')) return 'fa-user';
+ if (lowerName.includes('关系') || lowerName.includes('relation') || lowerName.includes('social')) return 'fa-users';
+ if (lowerName.includes('任务') || lowerName.includes('目标') || lowerName.includes('quest') || lowerName.includes('mission')) return 'fa-tasks';
+ if (lowerName.includes('物品') || lowerName.includes('道具') || lowerName.includes('item') || lowerName.includes('inventory')) return 'fa-box-open';
+ if (lowerName.includes('技能') || lowerName.includes('能力') || lowerName.includes('skill') || lowerName.includes('ability')) return 'fa-bolt';
+ if (lowerName.includes('设定') || lowerName.includes('世界') || lowerName.includes('setting') || lowerName.includes('world')) return 'fa-book';
+ if (lowerName.includes('总结') || lowerName.includes('大纲') || lowerName.includes('summary') || lowerName.includes('outline')) return 'fa-file-alt';
+ if (lowerName.includes('日志') || lowerName.includes('log') || lowerName.includes('record')) return 'fa-clipboard-list';
+ return 'fa-table';
+}
+
+function renderTablesToHtml(tables, highlights) {
+ if (!tables || tables.length === 0) {
+ return '';
}
- const closeMenu = (e) => {
- if (!targetTd.contains(e.target)) {
- targetTd.classList.remove('amily2-menu-open');
- targetTd.style.position = '';
- targetTd.style.zIndex = '';
- tableWrapper.style.overflowX = 'auto';
- tableWrapper.style.position = '';
- tableWrapper.style.zIndex = '';
- document.removeEventListener('click', closeMenu, true);
- }
- };
+ // 过滤掉空表格
+ const activeTables = tables.map((t, i) => ({...t, originalIndex: i})).filter(t => t.rows && t.rows.length > 0);
+ if (activeTables.length === 0) return '';
- if (targetTd.classList.contains('amily2-menu-open')) {
- setTimeout(() => {
- document.addEventListener('click', closeMenu, true);
- }, 0);
+ // Toggle 按钮
+ const toggleHtml = `
+
+
+ 表格面板 / Data Panel
+
+ `;
+
+ // 使用绝对定位强制布局,这是最稳妥的方式,不受 Flex 环境影响
+ // top: 30px 留给 toggle 按钮
+ let sidebarHtml = '';
+
+ return `${toggleHtml}${sidebarHtml}${contentHtml}
`;
+}
+
+function removeTableContainer() {
+ const existingContainer = document.getElementById(TABLE_CONTAINER_ID);
+ if (existingContainer) {
+ existingContainer.remove();
}
}
-
-function toggleColumnContextMenu(event) {
- if (isResizing || event.target.classList.contains('amily2-resizer')) {
+function bindSwipePreventer(container) {
+ if (!isTouchDevice()) {
return;
}
- event.preventDefault();
- event.stopPropagation();
- const targetTh = event.target.closest('th');
- if (!targetTh) return;
+ let touchstartX = 0;
+ let touchstartY = 0;
- const tableWrapper = targetTh.closest('.amily2-table-wrapper');
- if (!tableWrapper) return;
+ container.addEventListener('touchstart', function(event) {
+ touchstartX = event.changedTouches[0].screenX;
+ touchstartY = event.changedTouches[0].screenY;
+ }, { passive: true });
- const isActive = targetTh.classList.contains('amily2-menu-open');
+ container.addEventListener('touchmove', function(event) {
+ const touchendX = event.changedTouches[0].screenX;
+ const touchendY = event.changedTouches[0].screenY;
- document.querySelectorAll('th.amily2-menu-open').forEach(openTh => {
- if (openTh !== targetTh) {
- openTh.classList.remove('amily2-menu-open');
- const otherWrapper = openTh.closest('.amily2-table-wrapper');
- if (otherWrapper) {
- otherWrapper.style.overflowX = 'auto';
- otherWrapper.style.zIndex = '';
- otherWrapper.style.position = '';
- }
+ const deltaX = Math.abs(touchendX - touchstartX);
+ const deltaY = Math.abs(touchendY - touchstartY);
+
+ if (deltaX > deltaY) {
+ event.stopPropagation();
}
- });
-
- targetTh.classList.toggle('amily2-menu-open');
-
- if (targetTh.classList.contains('amily2-menu-open')) {
- tableWrapper.style.overflowX = 'visible';
- tableWrapper.style.position = 'relative';
- tableWrapper.style.zIndex = '10';
- } else {
- tableWrapper.style.overflowX = 'auto';
- tableWrapper.style.position = '';
- tableWrapper.style.zIndex = '';
- }
-
- const closeMenu = (e) => {
- if (!targetTh.contains(e.target)) {
- targetTh.classList.remove('amily2-menu-open');
- tableWrapper.style.overflowX = 'auto';
- tableWrapper.style.position = '';
- tableWrapper.style.zIndex = '';
- document.removeEventListener('click', closeMenu, true);
- }
- };
-
- if (targetTh.classList.contains('amily2-menu-open')) {
- setTimeout(() => {
- document.addEventListener('click', closeMenu, true);
- }, 0);
- }
+ }, { passive: false });
}
-
-function toggleHeaderIndexContextMenu(event) {
- event.preventDefault();
- event.stopPropagation();
-
- const targetTh = event.target.closest('th.index-col');
- if (!targetTh) return;
-
- const menu = targetTh.querySelector('.amily2-context-menu');
- if (!menu) return;
-
- const isActive = menu.classList.contains('amily2-menu-active');
-
- document.querySelectorAll('.amily2-context-menu.amily2-menu-active').forEach(activeMenu => {
- activeMenu.classList.remove('amily2-menu-active');
- });
-
- if (!isActive) {
- menu.classList.add('amily2-menu-active');
- }
-
- const closeMenu = (e) => {
- if (!menu.contains(e.target)) {
- menu.classList.remove('amily2-menu-active');
- document.removeEventListener('click', closeMenu, true);
- }
- };
+export function updateOrInsertTableInChat() {
+ injectChatTableStyles(); // 确保样式已注入
setTimeout(() => {
- if (menu.classList.contains('amily2-menu-active')) {
- document.addEventListener('click', closeMenu, true);
+ const context = getContext();
+ if (!context || !context.chat || context.chat.length < 2) {
+ removeTableContainer();
+ return;
+ }
+
+ const settings = extension_settings[extensionName];
+ removeTableContainer();
+
+ if (!settings || !settings.show_table_in_chat) {
+ return;
+ }
+
+ const tables = getMemoryState();
+
+ if (!tables || tables.every(t => !t.rows || t.rows.length === 0)) {
+ return;
+ }
+
+ const highlights = getHighlights();
+ const htmlContent = renderTablesToHtml(tables, highlights);
+
+ if (!htmlContent) {
+ return;
+ }
+
+ const lastMessage = document.querySelector('.last_mes .mes_text');
+ if (lastMessage) {
+ const container = document.createElement('div');
+ container.id = TABLE_CONTAINER_ID;
+
+ // 强制内联样式,使用相对定位作为绝对定位子元素的锚点
+ container.style.cssText = `
+ display: block !important; /* 不再依赖 Flex */
+ min-height: 300px;
+ max-height: 80vh;
+ background: rgba(12, 14, 20, 0.95);
+ border: 2px solid #3a4a5e;
+ border-radius: 8px;
+ margin-top: 15px;
+ overflow: hidden;
+ position: relative; /* 关键:作为定位锚点 */
+ resize: vertical;
+ width: 100%;
+ `;
+
+ container.innerHTML = htmlContent;
+ container.classList.add('collapsed'); // 默认折叠
+
+ // On mobile devices, add a specific class to enable horizontal scrolling via CSS
+ if (isTouchDevice()) {
+ container.classList.add('mobile-table-view');
+ container.style.flexDirection = 'column'; // 移动端保持垂直
+ }
+
+ lastMessage.appendChild(container);
+ bindSwipePreventer(container);
+
+ // 绑定折叠按钮事件
+ const toggleBtn = container.querySelector('.amily2-table-toggle');
+ if (toggleBtn) {
+ toggleBtn.addEventListener('click', (e) => {
+ e.stopPropagation();
+ container.classList.toggle('collapsed');
+ });
+ }
+
+ // 【V155.3】绑定游戏UI的交互事件
+ const tabs = container.querySelectorAll('.amily2-game-tab');
+ const panels = container.querySelectorAll('.amily2-game-panel');
+
+ tabs.forEach(tab => {
+ tab.addEventListener('click', (e) => {
+ e.stopPropagation(); // 防止触发消息点击
+
+ // 移除所有激活状态
+ tabs.forEach(t => t.classList.remove('active'));
+ panels.forEach(p => p.classList.remove('active'));
+
+ // 激活当前
+ tab.classList.add('active');
+ const targetId = tab.dataset.target;
+ const targetPanel = container.querySelector(`#${targetId}`);
+ if (targetPanel) targetPanel.classList.add('active');
+ });
+ });
+
+ } else {
+ console.warn('[Amily2] 未找到最后一条消息的容器,无法插入表格。');
}
}, 0);
}
-
-function showInputDialog({ title, label, currentValue, placeholder, onSave }) {
- const dialogHtml = `
- `;
-
- const dialogElement = $(dialogHtml).appendTo('body');
- const input = dialogElement.find('#generic-input');
-
- const closeDialog = () => {
- dialogElement[0].close();
- dialogElement.remove();
+function debounce(func, wait) {
+ let timeout;
+ return function executedFunction(...args) {
+ const later = () => {
+ clearTimeout(timeout);
+ func(...args);
+ };
+ clearTimeout(timeout);
+ timeout = setTimeout(later, wait);
};
-
- const save = () => {
- const newValue = input.val().trim();
- if (newValue && newValue !== currentValue) {
- onSave(newValue);
- } else if (!newValue) {
- toastr.warning('名称不能为空!');
- input.focus();
- return;
- }
- closeDialog();
- };
-
- dialogElement.find('.popup-button-ok').on('click', save);
- dialogElement.find('.popup-button-cancel').on('click', closeDialog);
- input.on('keypress', (e) => { if (e.which === 13) save(); });
- input.on('keydown', (e) => { if (e.which === 27) closeDialog(); });
-
- dialogElement[0].showModal();
- input.focus().select();
}
+let chatObserver = null;
+const debouncedUpdate = debounce(updateOrInsertTableInChat, 100);
-function showColumnNameEditor(tableIndex, colIndex, currentName) {
- showInputDialog({
- title: '编辑列名',
- label: '列名:',
- currentValue: currentName,
- placeholder: '请输入列名...',
- onSave: (newName) => {
- TableManager.updateHeader(tableIndex, colIndex, newName);
- renderTables();
- toastr.success(`列名已更新为 "${newName}"`);
- }
- });
-}
-
-
-function showTableNameEditor(tableIndex, currentName) {
- showInputDialog({
- title: '编辑表名',
- label: '表名:',
- currentValue: currentName,
- placeholder: '请输入表名...',
- onSave: (newName) => {
- TableManager.renameTable(tableIndex, newName);
- renderTables();
- toastr.success(`表名已更新为 "${newName}"`);
- }
- });
-}
-
-
-function positionContextMenu(menu, trigger) {
- menu.style.position = 'absolute';
- menu.style.zIndex = '10000';
- menu.style.left = '0';
- menu.style.right = 'auto';
- menu.style.marginTop = '';
- menu.style.marginBottom = '';
- menu.style.maxHeight = '';
- menu.style.overflowY = '';
-
- const viewportHeight = window.innerHeight;
- const triggerRect = trigger.getBoundingClientRect();
- const menuHeight = 200;
- const scrollContainer = trigger.closest('.hly-scroll');
- const containerRect = scrollContainer ? scrollContainer.getBoundingClientRect() : { top: 0, bottom: viewportHeight };
-
- const spaceBelow = Math.min(viewportHeight, containerRect.bottom) - triggerRect.bottom;
- const spaceAbove = triggerRect.top - Math.max(0, containerRect.top);
-
- if (spaceBelow < menuHeight && spaceAbove > spaceBelow) {
- menu.style.top = 'auto';
- menu.style.bottom = '100%';
- menu.style.marginBottom = '2px';
- } else {
- menu.style.top = '100%';
- menu.style.bottom = 'auto';
- menu.style.marginTop = '2px';
- }
-
- const menuWidth = 160;
- const table = trigger.closest('table');
- const tableWrapper = table ? table.closest('div[style*="overflowX"]') : null;
-
- if (tableWrapper) {
- const wrapperRect = tableWrapper.getBoundingClientRect();
- const triggerLeftInWrapper = triggerRect.left - wrapperRect.left;
-
- if (triggerLeftInWrapper + menuWidth > wrapperRect.width - 20) {
- menu.style.left = 'auto';
- menu.style.right = '0';
- }
- }
-}
-
-
-export function renderTables() {
- let tables = TableManager.getMemoryState();
- if (!tables) {
- log('内存状态为空,从聊天记录加载作为后备。', 'warn');
- tables = TableManager.loadTables();
- }
-
- const container = getAllTablesContainer();
-
- if (!tables || !container) {
- console.error('[内存储司-工部] 缺少表格数据或容器,无法渲染。');
+export function startContinuousRendering() {
+ if (chatObserver) {
+ console.log('[Amily2] Continuous rendering is already active.');
return;
}
- const highlights = TableManager.getHighlights();
- const updatedTables = TableManager.getUpdatedTables();
- const fragment = document.createDocumentFragment();
-
- const placeholder = document.getElementById('add-table-placeholder');
- if (placeholder) {
- placeholder.remove();
+ const chatContainer = document.getElementById('chat');
+ if (!chatContainer) {
+ console.error('[Amily2] Could not find chat container to observe.');
+ setTimeout(startContinuousRendering, 500);
+ return;
}
- tables.forEach((tableData, tableIndex) => {
- const header = document.createElement('div');
- header.className = 'amily2-table-header-container';
- const title = document.createElement('h3');
- if (updatedTables.has(tableIndex)) {
- title.classList.add('table-updated');
- }
- title.innerHTML = ` ${escapeHTML(tableData.name)}`;
- const controls = document.createElement('div');
- controls.className = 'table-controls';
+ const observerConfig = { childList: true };
- const moveUpBtn = tableIndex > 0 ? `` : '';
- const moveDownBtn = tableIndex < tables.length - 1 ? `` : '';
-
- controls.innerHTML = `
- ${moveUpBtn}
- ${moveDownBtn}
-
-
- `;
- header.appendChild(title);
- header.appendChild(controls);
- fragment.appendChild(header);
-
- const tableWrapper = document.createElement('div');
- tableWrapper.className = 'amily2-table-wrapper';
-
- const tableElement = document.createElement('table');
- tableElement.id = `amily2-table-${tableIndex}`;
- tableElement.dataset.tableIndex = tableIndex;
-
- const colgroup = document.createElement('colgroup');
- const indexCol = document.createElement('col');
- indexCol.style.width = '40px';
- colgroup.appendChild(indexCol);
-
- if (tableData.headers) {
- tableData.headers.forEach((_, colIndex) => {
- const col = document.createElement('col');
- const colWidth = (tableData.columnWidths && tableData.columnWidths[colIndex]) ? tableData.columnWidths[colIndex] : 90;
- col.style.width = `${colWidth}px`;
- colgroup.appendChild(col);
- });
- }
- tableElement.appendChild(colgroup);
-
- let totalWidth = 0;
- const cols = colgroup.querySelectorAll('col');
- cols.forEach(col => {
- totalWidth += parseInt(col.style.width, 10);
- });
- tableElement.style.minWidth = '100%';
- if (totalWidth > 0) {
- tableElement.style.width = `${Math.max(totalWidth, 0)}px`;
- tableElement.style.minWidth = `${totalWidth}px`;
- tableElement.style.width = '100%';
- }
-
- const thead = tableElement.createTHead();
- const headerRow = thead.insertRow();
-
- const indexTh = document.createElement('th');
- indexTh.className = 'index-col';
- indexTh.textContent = '#';
- indexTh.style.cursor = 'pointer';
- indexTh.title = '点击添加第一行';
-
- if (!tableData.rows || tableData.rows.length === 0) {
- const headerMenu = document.createElement('div');
- headerMenu.className = 'amily2-context-menu amily2-header-menu';
- headerMenu.style.display = 'none'; // 默认隐藏
-
- const addRowButton = document.createElement('button');
- addRowButton.innerHTML = ' 创建第一行';
- addRowButton.className = 'menu_button small_button';
- addRowButton.addEventListener('click', (e) => {
- e.stopPropagation();
- TableManager.addRow(tableIndex);
- renderTables();
- });
-
- headerMenu.appendChild(addRowButton);
- indexTh.appendChild(headerMenu);
-
- indexTh.addEventListener('click', (e) => {
- e.preventDefault();
- e.stopPropagation();
- console.log('Header # clicked for table', tableIndex);
-
- TableManager.addRow(tableIndex);
- renderTables();
- toastr.success('已添加第一行');
- });
- }
-
- headerRow.appendChild(indexTh);
-
- tableData.headers.forEach((headerText, colIndex) => {
- const th = document.createElement('th');
- th.dataset.colIndex = colIndex;
- th.style.cursor = 'pointer';
-
- const headerContent = document.createElement('span');
- headerContent.className = 'amily2-header-text';
- headerContent.textContent = headerText; // textContent is safe
- th.appendChild(headerContent);
-
- const menu = document.createElement('div');
- menu.className = 'amily2-context-menu';
-
- const actions = [
- { label: '向左移动', action: 'move-left', icon: 'fa-arrow-left' },
- { label: '向右移动', action: 'move-right', icon: 'fa-arrow-right' },
- { label: '在左加列', action: 'add-left', icon: 'fa-plus-circle' },
- { label: '在右加列', action: 'add-right', icon: 'fa-plus-circle' },
- { label: '编辑列名', action: 'rename', icon: 'fa-pen' },
- { label: '删除该列', action: 'delete', icon: 'fa-trash-alt', isDanger: true }
- ];
-
- actions.forEach(({ label, action, icon, isDanger }) => {
- const button = document.createElement('button');
- button.textContent = label;
- button.className = 'menu_button small_button';
- if (isDanger) button.classList.add('danger');
-
- button.addEventListener('click', (e) => {
- e.stopPropagation();
- switch (action) {
- case 'move-left':
- TableManager.moveColumn(tableIndex, colIndex, 'left');
- break;
- case 'move-right':
- TableManager.moveColumn(tableIndex, colIndex, 'right');
- break;
- case 'add-left':
- TableManager.insertColumn(tableIndex, colIndex, 'left');
- break;
- case 'add-right':
- TableManager.insertColumn(tableIndex, colIndex, 'right');
- break;
- case 'rename':
- showColumnNameEditor(tableIndex, colIndex, headerText);
- break;
- case 'delete':
- if (confirm(`您确定要删除 “${headerText}” 列吗?`)) {
- TableManager.deleteColumn(tableIndex, colIndex);
- }
- break;
- }
- renderTables();
- });
- menu.appendChild(button);
- });
-
- th.appendChild(menu);
-
- const resizer = document.createElement('div');
- resizer.className = 'amily2-resizer';
- th.appendChild(resizer);
-
- const startResize = (startEvent) => {
- startEvent.preventDefault();
- startEvent.stopPropagation();
-
- isResizing = true;
-
- const table = startEvent.target.closest('table');
- const th = startEvent.target.parentElement;
- const col = table.querySelector(`colgroup > col:nth-child(${th.cellIndex + 1})`);
-
- const isTouchEvent = startEvent.type.startsWith('touch');
- const startX = isTouchEvent ? startEvent.touches[0].clientX : startEvent.clientX;
- const startWidth = th.offsetWidth;
-
- const onMove = (moveEvent) => {
- const currentX = isTouchEvent ? moveEvent.touches[0].clientX : moveEvent.clientX;
- const newWidth = startWidth + (currentX - startX);
- if (newWidth > 50) {
- col.style.width = `${newWidth}px`;
- }
- };
-
- const onEnd = () => {
- document.removeEventListener('mousemove', onMove);
- document.removeEventListener('mouseup', onEnd);
- document.removeEventListener('touchmove', onMove);
- document.removeEventListener('touchend', onEnd);
-
- const finalWidth = parseInt(col.style.width, 10);
- TableManager.updateColumnWidth(tableIndex, colIndex, finalWidth);
-
- setTimeout(() => { isResizing = false; }, 0);
- };
-
- if (isTouchEvent) {
- document.addEventListener('touchmove', onMove, { passive: false });
- document.addEventListener('touchend', onEnd);
- } else {
- document.addEventListener('mousemove', onMove);
- document.addEventListener('mouseup', onEnd);
- }
- };
-
- resizer.addEventListener('mousedown', startResize);
- resizer.addEventListener('touchstart', startResize, { passive: false });
-
- headerRow.appendChild(th);
- });
-
- const tbody = tableElement.createTBody();
- if (tableData.rows && tableData.rows.length > 0) {
- tableData.rows.forEach((rowData, rowIndex) => {
- const row = tbody.insertRow();
- row.dataset.rowIndex = rowIndex;
-
- // 【延迟删除】根据行状态添加样式
- const rowStatus = tableData.rowStatuses ? tableData.rowStatuses[rowIndex] : 'normal';
- if (rowStatus === 'pending-deletion') {
- row.classList.add('pending-deletion-row');
- }
-
- const indexCell = row.insertCell();
- indexCell.className = 'index-col';
-
- const rowIndexSpan = document.createElement('span');
- rowIndexSpan.textContent = rowIndex + 1;
- indexCell.appendChild(rowIndexSpan);
-
- const menu = document.createElement('div');
- menu.className = 'amily2-context-menu amily2-row-context-menu';
-
- let actions;
-
- if (rowStatus === 'pending-deletion') {
- actions = [
- { label: '恢复该行', action: 'restore-row', icon: 'fa-undo', isSuccess: true, btnClass: 'restore-row-btn' }
- ];
- } else {
- actions = [
- { label: '向上移动', action: 'move-up', icon: 'fa-arrow-up', btnClass: 'move-row-up-btn' },
- { label: '向下移动', action: 'move-down', icon: 'fa-arrow-down', btnClass: 'move-row-down-btn' },
- { label: '在上加行', action: 'add-above', icon: 'fa-plus-circle', btnClass: 'add-row-above-btn' },
- { label: '在下加行', action: 'add-below', icon: 'fa-plus-circle', btnClass: 'add-row-below-btn' },
- { label: '删除该行', action: 'delete-row', icon: 'fa-trash-alt', isDanger: true, btnClass: 'delete-row-btn' }
- ];
- }
-
- actions.forEach(({ label, action, icon, isDanger, isSuccess }) => {
- const button = document.createElement('button');
- button.innerHTML = ` ${label}`;
- button.className = 'menu_button small_button';
- if (isDanger) button.classList.add('danger');
- if (isSuccess) button.classList.add('success'); // Use a success style for restore
-
- button.addEventListener('click', (e) => {
- e.stopPropagation();
-
- switch (action) {
- case 'move-up':
- TableManager.moveRow(tableIndex, rowIndex, 'up');
- break;
- case 'move-down':
- TableManager.moveRow(tableIndex, rowIndex, 'down');
- break;
- case 'add-above':
- TableManager.insertRow(tableIndex, rowIndex, 'above');
- break;
- case 'add-below':
- TableManager.insertRow(tableIndex, rowIndex, 'below');
- break;
- case 'delete-row':
- TableManager.deleteRow(tableIndex, rowIndex);
- break;
- case 'restore-row':
- TableManager.restoreRow(tableIndex, rowIndex);
- break;
- }
- if (action === 'delete-row' || action === 'restore-row') {
- } else {
- renderTables();
- }
- });
- menu.appendChild(button);
- });
- indexCell.appendChild(menu);
-
- rowData.forEach((cellData, colIndex) => {
- const cell = row.insertCell();
-
- const cellContent = document.createElement('div');
- cellContent.className = 'amily2-cell-content';
- cellContent.textContent = cellData;
- cell.appendChild(cellContent);
-
- if (rowStatus !== 'pending-deletion' && !isTouchDevice()) {
- cell.setAttribute('contenteditable', 'true');
- }
- cell.dataset.colIndex = colIndex;
- cell.dataset.label = tableData.headers[colIndex] || '';
-
- const highlightKey = `${tableIndex}-${rowIndex}-${colIndex}`;
- if (highlights.has(highlightKey)) {
- cell.classList.add('cell-highlight');
+ chatObserver = new MutationObserver((mutationsList, observer) => {
+ for (const mutation of mutationsList) {
+ if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
+ let messageAdded = false;
+ mutation.addedNodes.forEach(node => {
+ if (node.nodeType === 1 && node.classList.contains('mes')) {
+ messageAdded = true;
}
});
- });
+
+ if (messageAdded) {
+ debouncedUpdate();
+ return;
+ }
+ }
}
- tableWrapper.appendChild(tableElement);
- fragment.appendChild(tableWrapper);
});
- container.innerHTML = '';
- container.appendChild(fragment);
-
- if (placeholder) {
- container.appendChild(placeholder);
- }
-
+ chatObserver.observe(chatContainer, observerConfig);
+ console.log('[Amily2] Started continuous table rendering.');
updateOrInsertTableInChat();
}
-
-function openTableRuleEditor() {
- const settings = extension_settings[extensionName];
- const tags = settings.table_tags_to_extract || '';
- const exclusionRules = settings.table_exclusion_rules || [];
-
- const rulesHtml = exclusionRules.map((rule, index) => `
-
-
- -
-
-
-
- `).join('');
-
- const modalHtml = `
-
-
-
-
- 仅提取指定XML标签的内容,例如填“content”,即提取...中的内容。
-
-
-
-
${rulesHtml}
-
-
移除所有被起始和结束标记包裹的内容(例如 OOC 部分)。
-
-
- `;
-
- const dialog = showHtmlModal('配置独立提取规则', modalHtml, {
- onOk: () => {
- const newTags = document.getElementById('table-tags-input').value;
- updateAndSaveTableSetting('table_tags_to_extract', newTags);
-
- const newExclusionRules = [];
- document.querySelectorAll('#exclusion-rules-list .exclusion-rule-item').forEach(item => {
- const start = item.querySelector('.rule-start').value.trim();
- const end = item.querySelector('.rule-end').value.trim();
- if (start && end) {
- newExclusionRules.push({ start, end });
- }
- });
- updateAndSaveTableSetting('table_exclusion_rules', newExclusionRules);
- toastr.success('独立提取规则已保存。');
- },
- onShow: (dialogElement) => {
- const rulesList = dialogElement.find('#exclusion-rules-list');
-
- dialogElement.find('#add-exclusion-rule-btn').on('click', () => {
- const newIndex = rulesList.children().length;
- const newItemHtml = `
-
-
- -
-
-
-
`;
- rulesList.append(newItemHtml);
- });
-
- rulesList.on('click', '.remove-rule-btn', function() {
- $(this).closest('.exclusion-rule-item').remove();
- });
- }
- });
-}
-
-function openRuleEditor(tableIndex) {
- const tables = TableManager.getMemoryState();
- if (!tables || !tables[tableIndex]) return;
- const table = tables[tableIndex];
-
- if (table.charLimitRule && !table.charLimitRules) {
- table.charLimitRules = {};
- if (table.charLimitRule.columnIndex !== -1) {
- table.charLimitRules[table.charLimitRule.columnIndex] = table.charLimitRule.limit;
- }
- }
- const charLimitRules = table.charLimitRules || {};
-
- const renderCharLimitRules = (rules) => {
- return Object.entries(rules).map(([colIndex, limit]) => {
- const header = table.headers[colIndex] || `未知列 (${colIndex})`;
- return `
-
- ${escapeHTML(header)}: 不超过 ${limit} 字
-
-
- `;
- }).join('');
- };
-
- const getColumnOptions = (rules) => {
- return table.headers.map((header, index) => {
- if (rules[index]) return '';
- return ``;
- }).join('');
- };
-
- const dialogHtml = `
- `;
-
- const dialogElement = $(dialogHtml).appendTo('body');
-
- const closeDialog = () => {
- dialogElement[0].close();
- dialogElement.remove();
- };
-
- const refreshRuleUI = () => {
- const currentRules = JSON.parse(dialogElement.find('#current-char-limit-rules').attr('data-rules') || '{}');
- dialogElement.find('#current-char-limit-rules').html(renderCharLimitRules(currentRules));
- dialogElement.find('#new-rule-column-select').html(`${getColumnOptions(currentRules)}`);
- };
-
- dialogElement.find('#current-char-limit-rules').attr('data-rules', JSON.stringify(charLimitRules));
-
- dialogElement.on('click', '#add-char-limit-rule-btn', () => {
- const selectedColumn = parseInt(dialogElement.find('#new-rule-column-select').val(), 10);
- const limitValue = parseInt(dialogElement.find('#new-rule-limit-input').val(), 10);
-
- if (selectedColumn === -1) {
- toastr.warning('请选择一个列。');
- return;
- }
-
- if (isNaN(limitValue) || limitValue < 0) {
- toastr.warning('请输入一个有效的字数限制(大于等于0)。');
- return;
- }
-
- const currentRules = JSON.parse(dialogElement.find('#current-char-limit-rules').attr('data-rules') || '{}');
-
- if (limitValue > 0) {
- currentRules[selectedColumn] = limitValue;
- dialogElement.find('#current-char-limit-rules').attr('data-rules', JSON.stringify(currentRules));
- refreshRuleUI();
- } else {
- toastr.info('字数限制为0表示不设置规则。');
- }
- });
-
- dialogElement.on('click', '.remove-char-limit-rule-btn', function() {
- const colIndexToRemove = $(this).data('col-index');
- const currentRules = JSON.parse(dialogElement.find('#current-char-limit-rules').attr('data-rules') || '{}');
- delete currentRules[colIndexToRemove];
- dialogElement.find('#current-char-limit-rules').attr('data-rules', JSON.stringify(currentRules));
- refreshRuleUI();
- });
-
- dialogElement.find('.popup-button-ok').on('click', () => {
- const newCharLimitRules = JSON.parse(dialogElement.find('#current-char-limit-rules').attr('data-rules') || '{}');
- const rowLimitValue = parseInt(dialogElement.find('#rule-row-limit-value').val(), 10);
- const simplifyThresholdValue = parseInt(dialogElement.find('#rule-simplify-threshold').val(), 10);
-
- const newRules = {
- note: dialogElement.find('#rule-note').val(),
- rule_add: dialogElement.find('#rule-add').val(),
- rule_delete: dialogElement.find('#rule-delete').val(),
- rule_update: dialogElement.find('#rule-update').val(),
- charLimitRules: newCharLimitRules,
- rowLimitRule: rowLimitValue,
- simplifyRowThreshold: simplifyThresholdValue, // 保存新设置
- };
- TableManager.updateTableRules(tableIndex, newRules);
- closeDialog();
- });
-
- dialogElement.find('.popup-button-cancel').on('click', closeDialog);
- dialogElement[0].showModal();
-}
-
-
-function bindInjectionSettings() {
- const settings = extension_settings[extensionName];
-
- const masterSwitchCheckbox = document.getElementById('table-system-master-switch');
- const enabledCheckbox = document.getElementById('table-injection-enabled');
- const optimizationCheckbox = document.getElementById('context-optimization-enabled'); // 【V144.0】
- const positionSelect = document.getElementById('table-injection-position');
- const depthInput = document.getElementById('table-injection-depth');
- const roleRadioGroup = document.querySelectorAll('input[name="table-injection-role"]');
-
- if (!masterSwitchCheckbox || !enabledCheckbox || !positionSelect || !depthInput || !roleRadioGroup.length) {
- return;
- }
-
- const updateInjectionUI = () => {
- const position = positionSelect.value;
- const masterEnabled = masterSwitchCheckbox.checked;
-
- const isChatInjection = position === '1';
-
- enabledCheckbox.disabled = !masterEnabled;
- positionSelect.disabled = !masterEnabled;
- depthInput.disabled = !masterEnabled || !isChatInjection;
- roleRadioGroup.forEach(radio => radio.disabled = !masterEnabled || !isChatInjection);
-
- const enabledOpacity = masterEnabled ? '1' : '0.5';
- enabledCheckbox.style.opacity = enabledOpacity;
- if (enabledCheckbox.closest('.control-block-with-switch')) {
- enabledCheckbox.closest('.control-block-with-switch').style.opacity = enabledOpacity;
- }
-
- positionSelect.style.opacity = enabledOpacity;
- if (positionSelect.previousElementSibling) {
- positionSelect.previousElementSibling.style.opacity = enabledOpacity;
- }
-
- const depthOpacity = masterEnabled && isChatInjection ? '1' : '0.5';
- depthInput.style.opacity = depthOpacity;
- if (depthInput.previousElementSibling) {
- depthInput.previousElementSibling.style.opacity = depthOpacity;
- }
-
- const roleOpacity = masterEnabled && isChatInjection ? '1' : '0.5';
- const roleGroupContainer = document.getElementById('table-role-system')?.closest('.radio-group');
- if (roleGroupContainer) {
- roleGroupContainer.style.opacity = roleOpacity;
- if (roleGroupContainer.previousElementSibling) {
- roleGroupContainer.previousElementSibling.style.opacity = roleOpacity;
- }
- }
-
- const fillingModeRadios = document.querySelectorAll('input[name="filling-mode"]');
- fillingModeRadios.forEach(radio => {
- radio.disabled = !masterEnabled;
- const label = radio.closest('label');
- if (label) {
- label.style.opacity = masterEnabled ? '1' : '0.5';
- }
- });
-
- const fillButton = document.getElementById('fill-table-now-btn');
- if (fillButton) {
- fillButton.disabled = !masterEnabled;
- fillButton.style.opacity = masterEnabled ? '1' : '0.5';
- }
- };
-
- masterSwitchCheckbox.checked = settings.table_system_enabled !== false;
- enabledCheckbox.checked = settings.table_injection_enabled;
- if (optimizationCheckbox) { // 【V144.0】
- optimizationCheckbox.checked = settings.context_optimization_enabled !== false;
- }
- positionSelect.value = settings.injection.position;
- depthInput.value = settings.injection.depth;
- roleRadioGroup.forEach(radio => {
- if (parseInt(radio.value, 10) === settings.injection.role) {
- radio.checked = true;
- }
- });
-
- updateInjectionUI();
-
- if (masterSwitchCheckbox.dataset.eventsBound) return;
-
- masterSwitchCheckbox.addEventListener('change', () => {
- settings.table_system_enabled = masterSwitchCheckbox.checked;
- saveSettingsDebounced();
- updateInjectionUI();
-
- const statusText = masterSwitchCheckbox.checked ? '已启用' : '已禁用';
- toastr.info(`表格系统总开关${statusText}。`);
- log(`表格系统总开关${statusText}。`, 'info');
- });
-
- enabledCheckbox.addEventListener('change', () => {
- settings.table_injection_enabled = enabledCheckbox.checked;
- saveSettingsDebounced();
- });
-
- // 【V144.0】
- if (optimizationCheckbox) {
- optimizationCheckbox.addEventListener('change', () => {
- settings.context_optimization_enabled = optimizationCheckbox.checked;
- saveSettingsDebounced();
- toastr.info(`上下文优化(世界书合并)已${optimizationCheckbox.checked ? '启用' : '禁用'}。`);
- });
- }
-
- positionSelect.addEventListener('change', () => {
- settings.injection.position = parseInt(positionSelect.value, 10);
- saveSettingsDebounced();
-
- updateInjectionUI();
- });
-
- depthInput.addEventListener('input', () => {
- settings.injection.depth = parseInt(depthInput.value, 10);
- saveSettingsDebounced();
- });
-
- roleRadioGroup.forEach(radio => {
- radio.addEventListener('change', () => {
- if (radio.checked) {
- settings.injection.role = parseInt(radio.value, 10);
- saveSettingsDebounced();
- }
- });
- });
-
- masterSwitchCheckbox.dataset.eventsBound = 'true';
- log('表格注入设置已成功绑定。', 'success');
-}
-
-
-function updateAndSaveTableSetting(key, value) {
- if (!extension_settings[extensionName]) {
- extension_settings[extensionName] = {};
- }
- extension_settings[extensionName][key] = value;
- saveSettingsDebounced();
-}
-
-function bindWorldBookSettings() {
- const settings = extension_settings[extensionName];
-
- if (settings.table_worldbook_enabled === undefined) settings.table_worldbook_enabled = false;
- if (settings.table_worldbook_char_limit === undefined) settings.table_worldbook_char_limit = 30000;
- if (settings.table_worldbook_source === undefined) settings.table_worldbook_source = 'character';
- if (settings.table_selected_worldbooks === undefined) settings.table_selected_worldbooks = [];
- if (settings.table_selected_entries === undefined) settings.table_selected_entries = {};
-
- const enabledCheckbox = document.getElementById('table_worldbook_enabled');
- const limitSlider = document.getElementById('table_worldbook_char_limit');
- const limitValueSpan = document.getElementById('table_worldbook_char_limit_value');
- const sourceRadios = document.querySelectorAll('input[name="table_worldbook_source"]');
- const manualSelectWrapper = document.getElementById('table_worldbook_select_wrapper');
- const refreshButton = document.getElementById('table_refresh_worldbooks');
- const bookListContainer = document.getElementById('table_worldbook_checkbox_list');
- const entryListContainer = document.getElementById('table_worldbook_entry_list');
-
- if (!enabledCheckbox || !limitSlider || !limitValueSpan || !sourceRadios.length || !manualSelectWrapper || !refreshButton || !bookListContainer || !entryListContainer) {
- log('无法找到世界书设置的相关UI元素,绑定失败。', 'warn');
- return;
- }
-
- const saveSelectedEntries = () => {
- const selected = {};
- entryListContainer.querySelectorAll('input[type="checkbox"]:checked').forEach(cb => {
- const book = cb.dataset.book;
- const uid = cb.dataset.uid;
- if (!selected[book]) {
- selected[book] = [];
- }
- selected[book].push(uid);
- });
- settings.table_selected_entries = selected;
- saveSettingsDebounced();
- };
-
- const renderWorldBookEntries = async () => {
- entryListContainer.innerHTML = '加载条目中...
';
- const source = settings.table_worldbook_source || 'character';
- let bookNames = [];
-
- if (source === 'manual') {
- bookNames = settings.table_selected_worldbooks || [];
- } else {
- if (this_chid !== undefined && this_chid >= 0 && characters[this_chid]) {
- try {
- const charLorebooks = await safeCharLorebooks({ type: 'all' });
- if (charLorebooks.primary) bookNames.push(charLorebooks.primary);
- if (charLorebooks.additional?.length) bookNames.push(...charLorebooks.additional);
- } catch (error) {
- console.error(`[内存储司] 获取角色世界书失败:`, error);
- entryListContainer.innerHTML = '获取角色世界书失败。
';
- return;
- }
- } else {
- entryListContainer.innerHTML = '请先加载一个角色。
';
- return;
- }
- }
-
- if (bookNames.length === 0) {
- entryListContainer.innerHTML = '未选择或绑定世界书。
';
- return;
- }
-
- try {
- const allEntries = [];
- for (const bookName of bookNames) {
- const entries = await safeLorebookEntries(bookName);
- entries.forEach(entry => allEntries.push({ ...entry, bookName }));
- }
-
- entryListContainer.innerHTML = '';
- if (allEntries.length === 0) {
- entryListContainer.innerHTML = '所选世界书中没有条目。
';
- return;
- }
-
- allEntries.forEach(entry => {
- const div = document.createElement('div');
- div.className = 'checkbox-item';
- div.title = `世界书: ${entry.bookName}\nUID: ${entry.uid}`;
-
- const checkbox = document.createElement('input');
- checkbox.type = 'checkbox';
- checkbox.id = `wb-entry-check-${entry.bookName}-${entry.uid}`;
- checkbox.dataset.book = entry.bookName;
- checkbox.dataset.uid = entry.uid;
-
- const isChecked = settings.table_selected_entries[entry.bookName]?.includes(String(entry.uid));
- checkbox.checked = !!isChecked;
-
- const label = document.createElement('label');
- label.htmlFor = checkbox.id;
- label.textContent = entry.comment || '无标题条目'; // textContent is safe
-
- div.appendChild(checkbox);
- div.appendChild(label);
- entryListContainer.appendChild(div);
- });
- } catch (error) {
- console.error(`[内存储司] 加载世界书条目失败:`, error);
- entryListContainer.innerHTML = '加载条目失败。
';
- }
- };
-
- const renderWorldBookList = () => {
- const worldBooks = world_names.map(name => ({ name: name.replace('.json', ''), file_name: name }));
- bookListContainer.innerHTML = '';
- if (worldBooks && worldBooks.length > 0) {
- worldBooks.forEach(book => {
- const div = document.createElement('div');
- div.className = 'checkbox-item';
- div.title = book.name;
-
- const checkbox = document.createElement('input');
- checkbox.type = 'checkbox';
- checkbox.id = `wb-check-${book.file_name}`;
- checkbox.value = book.file_name;
- checkbox.checked = settings.table_selected_worldbooks.includes(book.file_name);
-
- checkbox.addEventListener('change', () => {
- if (checkbox.checked) {
- if (!settings.table_selected_worldbooks.includes(book.file_name)) {
- settings.table_selected_worldbooks.push(book.file_name);
- }
- } else {
- settings.table_selected_worldbooks = settings.table_selected_worldbooks.filter(name => name !== book.file_name);
- }
- saveSettingsDebounced();
- renderWorldBookEntries();
- });
-
- const label = document.createElement('label');
- label.htmlFor = `wb-check-${book.file_name}`;
- label.textContent = book.name; // textContent is safe
-
- div.appendChild(checkbox);
- div.appendChild(label);
- bookListContainer.appendChild(div);
- });
- } else {
- bookListContainer.innerHTML = '没有找到世界书。
';
- }
- renderWorldBookEntries();
- };
-
- const updateManualSelectVisibility = () => {
- const isManual = settings.table_worldbook_source === 'manual';
- manualSelectWrapper.style.display = isManual ? 'block' : 'none';
- renderWorldBookEntries();
- if (isManual) {
- renderWorldBookList();
- }
- };
-
- enabledCheckbox.checked = settings.table_worldbook_enabled;
- limitSlider.value = settings.table_worldbook_char_limit;
- limitValueSpan.textContent = settings.table_worldbook_char_limit;
- sourceRadios.forEach(radio => {
- radio.checked = radio.value === settings.table_worldbook_source;
- });
-
- updateManualSelectVisibility();
-
- if (enabledCheckbox.dataset.eventsBound) return;
-
- enabledCheckbox.addEventListener('change', () => {
- settings.table_worldbook_enabled = enabledCheckbox.checked;
- saveSettingsDebounced();
- });
-
- limitSlider.addEventListener('input', () => { limitValueSpan.textContent = limitSlider.value; });
- limitSlider.addEventListener('change', () => {
- settings.table_worldbook_char_limit = parseInt(limitSlider.value, 10);
- saveSettingsDebounced();
- });
-
- sourceRadios.forEach(radio => {
- radio.addEventListener('change', () => {
- if (radio.checked) {
- settings.table_worldbook_source = radio.value;
- updateManualSelectVisibility();
- saveSettingsDebounced();
- }
- });
- });
-
- refreshButton.addEventListener('click', renderWorldBookList);
- entryListContainer.addEventListener('change', (event) => {
- if (event.target.type === 'checkbox') {
- saveSelectedEntries();
- }
- });
-
- enabledCheckbox.dataset.eventsBound = 'true';
- log('世界书设置已成功绑定。', 'success');
-}
-
-export function bindTableEvents() {
- const panel = document.getElementById('amily2_memorisation_forms_panel');
- if (!panel || panel.dataset.eventsBound) {
- return;
- }
- log('开始为表格视图绑定交互事件...', 'info');
-
- const fillingModeRadios = panel.querySelectorAll('input[name="filling-mode"]');
- const secondaryFillerControls = document.getElementById('secondary-filler-controls');
-
- const contextSlider = document.getElementById('secondary-filler-context');
- const batchSlider = document.getElementById('secondary-filler-batch');
- const bufferSlider = document.getElementById('secondary-filler-buffer');
-
- const independentRulesContainer = document.getElementById('table-independent-rules-container');
- const independentRulesToggle = document.getElementById('table-independent-rules-enabled');
- const configureRulesBtn = document.getElementById('table-configure-rules-btn');
-
- const updateFillingModeUI = () => {
- const currentMode = extension_settings[extensionName]?.filling_mode || 'main-api';
- fillingModeRadios.forEach(radio => {
- radio.checked = (radio.value === currentMode);
- });
-
- const isSecondaryMode = currentMode === 'secondary-api';
-
- if (secondaryFillerControls) {
- secondaryFillerControls.style.display = isSecondaryMode ? 'block' : 'none';
- }
-
- if (independentRulesContainer) {
- independentRulesContainer.style.display = 'flex';
- }
-
- if (independentRulesToggle && configureRulesBtn) {
- configureRulesBtn.style.display = independentRulesToggle.checked ? 'block' : 'none';
- }
- };
-
- fillingModeRadios.forEach(radio => {
- radio.addEventListener('change', function() {
- const selectedMode = this.value;
- updateAndSaveTableSetting('filling_mode', selectedMode);
-
- let modeName = '原始填表';
- if (selectedMode === 'secondary-api') modeName = '分步填表';
- if (selectedMode === 'optimized') modeName = '优化中填表';
-
- toastr.info(`填表模式已切换为 ${modeName}。`);
- updateFillingModeUI();
- });
- });
-
- if (contextSlider) {
- const value = extension_settings[extensionName]?.secondary_filler_context || 2;
- contextSlider.value = value;
-
- contextSlider.addEventListener('change', function() {
- updateAndSaveTableSetting('secondary_filler_context', parseInt(this.value, 10));
- toastr.info(`上下文深度已设置为 ${this.value}。`);
- });
- }
-
- if (batchSlider) {
- const value = extension_settings[extensionName]?.secondary_filler_batch || 0;
- batchSlider.value = value;
-
- batchSlider.addEventListener('change', function() {
- updateAndSaveTableSetting('secondary_filler_batch', parseInt(this.value, 10));
- toastr.info(`填表批次已设置为 ${this.value}。`);
- });
- }
-
- if (bufferSlider) {
- const value = extension_settings[extensionName]?.secondary_filler_buffer || 0;
- bufferSlider.value = value;
-
- bufferSlider.addEventListener('change', function() {
- updateAndSaveTableSetting('secondary_filler_buffer', parseInt(this.value, 10));
- toastr.info(`保留楼层已设置为 ${this.value}。`);
- });
- }
-
- if (independentRulesToggle) {
- independentRulesToggle.checked = extension_settings[extensionName]?.table_independent_rules_enabled ?? false;
- independentRulesToggle.addEventListener('change', () => {
- updateAndSaveTableSetting('table_independent_rules_enabled', independentRulesToggle.checked);
- updateFillingModeUI();
- });
- }
-
- updateFillingModeUI();
-
- if (configureRulesBtn) {
- configureRulesBtn.addEventListener('click', openTableRuleEditor);
- }
-
- const renderAll = () => {
- renderTables();
- bindInjectionSettings();
- bindTemplateEditors();
- };
-
- renderAll();
- bindWorldBookSettings();
- bindBatchFillButton(); // 【新增】绑定批量填表按钮
- bindFloorFillButtons(); // 【新增】绑定楼层填表按钮
- bindReorganizeButton(); // 【新增】绑定重新整理按钮
- bindClearRecordsButton(); // 【新增】绑定清除记录按钮
- bindNccsApiEvents(); // 【新增】绑定Nccs API系统事件
- bindChatTableDisplaySetting(); // 【新增】绑定聊天内表格显示开关
-
- const navDeck = document.querySelector('#amily2_memorisation_forms_panel .sinan-navigation-deck');
- if (navDeck) {
- navDeck.addEventListener('click', (event) => {
- const target = event.target.closest('.sinan-nav-item');
- if (!target) return;
-
- const tabName = target.dataset.tab;
- if (!tabName) return;
-
- const container = target.closest('.settings-group');
- if (!container) return;
-
- container.querySelectorAll('.sinan-nav-item').forEach(btn => btn.classList.remove('active'));
- target.classList.add('active');
- container.querySelectorAll('.sinan-tab-pane').forEach(pane => pane.classList.remove('active'));
- const activePane = container.querySelector(`#sinan-${tabName}-tab`);
- if (activePane) {
- activePane.classList.add('active');
- }
- });
- }
-
- const openGraphBtn = document.getElementById('amily2-open-relationship-graph-btn');
- const exportBtn = document.getElementById('amily2-export-preset-btn');
- const exportFullBtn = document.getElementById('amily2-export-preset-full-btn');
- const importBtn = document.getElementById('amily2-import-preset-btn');
- const importGlobalBtn = document.getElementById('amily2-import-global-preset-btn');
- const clearGlobalBtn = document.getElementById('amily2-clear-global-preset-btn');
-
- if (openGraphBtn) {
- openGraphBtn.addEventListener('click', () => {
- showGraphVisualization();
- });
- }
-
- if (exportBtn) {
- exportBtn.addEventListener('click', () => TableManager.exportPreset());
- }
- if (exportFullBtn) {
- exportFullBtn.addEventListener('click', () => TableManager.exportPresetFull());
- }
- if (importBtn) {
- importBtn.addEventListener('click', () => TableManager.importPreset(renderAll));
- }
- if (importGlobalBtn) {
- importGlobalBtn.addEventListener('click', () => {
-
- const isEmpty = TableManager.isCurrentTablesEmpty();
- TableManager.importGlobalPreset(() => {
- if (isEmpty) {
- TableManager.loadTables();
- renderAll();
- }
- });
- });
- }
- if (clearGlobalBtn) {
- clearGlobalBtn.addEventListener('click', () => {
- const isEmpty = TableManager.isCurrentTablesEmpty();
- TableManager.clearGlobalPreset();
- if (isEmpty) {
- TableManager.loadTables();
- renderAll();
- }
- });
- }
-
- const clearAllBtn = document.getElementById('amily2-clear-all-tables-btn');
- if (clearAllBtn) {
- clearAllBtn.addEventListener('click', () => {
- if (confirm('【确认】您确定要清空所有表格的剧情内容吗?此操作将保留表格结构,但会删除所有已填写的行。')) {
- TableManager.clearAllTables();
- renderAll();
- }
- });
- }
-
-
- const addTablePlaceholder = document.getElementById('add-table-placeholder');
- if (addTablePlaceholder) {
- addTablePlaceholder.addEventListener('click', () => {
- const newName = prompt('请输入新表格的名称:', '新表格');
- if (newName && newName.trim()) {
- TableManager.addTable(newName.trim());
- renderAll();
- }
- });
- }
-
-
- const allTablesContainer = getAllTablesContainer();
- if (allTablesContainer) {
- allTablesContainer.addEventListener('click', (event) => {
- const th = event.target.closest('th');
- if (th && th.classList.contains('index-col')) {
- toggleHeaderIndexContextMenu(event);
- return;
- }
- if (th && !th.classList.contains('index-col')) {
- toggleColumnContextMenu(event);
- return;
- }
-
- const td = event.target.closest('td.index-col');
- if (td) {
- toggleRowContextMenu(event);
- return;
- }
-
- const renameIcon = event.target.closest('.table-rename-icon');
- if (renameIcon) {
- const tableIndex = parseInt(renameIcon.dataset.tableIndex, 10);
- const tables = TableManager.getMemoryState();
- const currentName = tables[tableIndex]?.name || '';
- showTableNameEditor(tableIndex, currentName);
- return;
- }
-
- const target = event.target.closest('button');
- if (!target) return;
-
- const tableIndex = parseInt(target.dataset.tableIndex, 10);
-
- if (target.matches('.add-row-btn')) {
- TableManager.addRow(tableIndex);
- renderAll();
- } else if (target.matches('.add-col-btn')) {
- TableManager.addColumn(tableIndex);
- renderAll();
- } else if (target.matches('.move-table-up-btn') || target.matches('.move-table-down-btn')) {
- const direction = target.classList.contains('move-table-up-btn') ? 'up' : 'down';
- TableManager.moveTable(tableIndex, direction);
- renderAll();
- } else if (target.matches('.edit-rules-btn')) {
- openRuleEditor(tableIndex);
- } else if (target.matches('.delete-table-btn')) {
- const tables = TableManager.getMemoryState();
- const tableName = tables[tableIndex]?.name || '未知表格';
- if (confirm(`【最终警告】您确定要永久废黜表格 “[${tableName}]” 吗?此操作不可逆!`)) {
- TableManager.deleteTable(tableIndex);
- renderAll();
- }
- }
- });
-
- if (isTouchDevice()) {
- let lastTap = 0;
- let lastTapTarget = null;
- allTablesContainer.addEventListener('touchstart', (event) => {
- const target = event.target.closest('td');
- if (!target || target.dataset.colIndex === undefined) return;
-
- const currentTime = new Date().getTime();
- const tapLength = currentTime - lastTap;
- if (tapLength < 300 && tapLength > 0 && lastTapTarget === target) {
- event.preventDefault();
- if (target.getAttribute('contenteditable') !== 'true') {
- target.setAttribute('contenteditable', 'true');
- setTimeout(() => target.focus(), 0);
- }
- }
- lastTap = currentTime;
- lastTapTarget = target;
- });
- }
-
- allTablesContainer.addEventListener('blur', (event) => {
- const target = event.target;
- if (target.tagName !== 'TD' || target.getAttribute('contenteditable') !== 'true') return;
-
- if (isTouchDevice()) {
- target.setAttribute('contenteditable', 'false');
- }
-
- const tableElement = target.closest('table');
- if (!tableElement) return;
-
- const tableIndex = parseInt(tableElement.dataset.tableIndex, 10);
- const rowIndex = parseInt(target.closest('tr').dataset.rowIndex, 10);
- const colIndex = parseInt(target.dataset.colIndex, 10);
- const newValue = target.textContent;
-
- // Correctly save scroll positions before re-rendering
- const tableWrapper = tableElement.closest('.amily2-table-wrapper');
- const hScroll = tableWrapper ? tableWrapper.scrollLeft : 0;
- const vScroll = allTablesContainer.scrollTop;
-
- TableManager.addHighlight(tableIndex, rowIndex, colIndex);
- const dataToUpdate = { [colIndex]: newValue };
- TableManager.updateRow(tableIndex, rowIndex, dataToUpdate);
-
- renderAll();
-
- // Correctly restore scroll positions after re-rendering
- const newTableWrapper = document.getElementById(`amily2-table-${tableIndex}`)?.closest('.amily2-table-wrapper');
- if (newTableWrapper) {
- newTableWrapper.scrollLeft = hScroll;
- }
- allTablesContainer.scrollTop = vScroll;
-
- }, true);
- }
-
- panel.dataset.eventsBound = 'true';
- log('表格视图交互事件已成功绑定。', 'success');
-
- eventSource.on(event_types.CHAT_CHANGED, () => {
- console.log(`[${extensionName}] 检测到角色/聊天切换,正在刷新表格系统UI和世界书设置...`);
- renderAll();
-
- setTimeout(() => {
- const settings = extension_settings[extensionName];
- if (settings && settings.table_worldbook_enabled) {
- try {
- bindWorldBookSettings();
- console.log(`[${extensionName}] 世界书设置已刷新`);
- } catch (error) {
- console.error(`[${extensionName}] 刷新世界书设置时出错:`, error);
- }
- }
- }, 100);
- });
-}
-
-function bindBatchFillButton() {
- const fillButton = document.getElementById('fill-table-now-btn');
- if (fillButton) {
- if (fillButton.dataset.batchEventBound) return;
-
- fillButton.addEventListener('click', (event) => {
- const settings = extension_settings[extensionName];
- const tableSystemEnabled = settings.table_system_enabled !== false;
-
- if (!tableSystemEnabled) {
- event.preventDefault();
- toastr.warning('表格系统总开关已关闭,请先启用总开关。');
- return;
- }
-
- startBatchFilling();
- });
-
- fillButton.dataset.batchEventBound = 'true';
- log('"立即填表"按钮已成功绑定。', 'success');
+export function stopContinuousRendering() {
+ if (chatObserver) {
+ chatObserver.disconnect();
+ chatObserver = null;
+ removeTableContainer();
+ console.log('[Amily2] Stopped continuous table rendering.');
}
}
-
-function bindReorganizeButton() {
- const reorganizeBtn = document.getElementById('reorganize-table-btn');
-
- if (reorganizeBtn) {
- if (reorganizeBtn.dataset.reorganizeEventBound) return;
-
- reorganizeBtn.addEventListener('click', async (event) => {
- const settings = extension_settings[extensionName];
- const tableSystemEnabled = settings.table_system_enabled !== false;
-
- if (!tableSystemEnabled) {
- event.preventDefault();
- toastr.warning('表格系统总开关已关闭,请先启用总开关。');
- return;
- }
-
- const tables = TableManager.getMemoryState();
- if (!tables || tables.length === 0) {
- toastr.warning('当前没有表格可供整理。');
- return;
- }
-
- // 构建表格选择列表 HTML
- const tableListHtml = tables.map((table, index) => `
-
-
-
-
- `).join('');
-
- const modalHtml = `
-
-
建议:最好一次只选择一个表格,或少数几个相关联的表格进行整理。一次性处理过多表格可能会导致AI混淆或遗漏信息。
-
请勾选需要AI重新整理和去重的表格:
-
- ${tableListHtml}
-
-
-
-
-
-
- `;
-
- showHtmlModal('选择要整理的表格', modalHtml, {
- onOk: async (dialogElement) => {
- const selectedIndices = [];
- dialogElement.find('input[type="checkbox"]:checked').each(function() {
- selectedIndices.push(parseInt($(this).val(), 10));
- });
-
- if (selectedIndices.length === 0) {
- toastr.warning('请至少选择一个表格。');
- return false; // 阻止关闭弹窗
- }
-
- try {
- const { reorganizeTableContent } = await import('../core/table-system/reorganizer.js');
- await reorganizeTableContent(selectedIndices);
- } catch (error) {
- console.error('[内存储司] 重新整理功能导入失败:', error);
- toastr.error('重新整理功能启动失败,请检查系统状态。');
- }
- },
- onShow: (dialogElement) => {
- dialogElement.find('#reorg-select-all').on('click', () => {
- dialogElement.find('input[type="checkbox"]').prop('checked', true);
- });
- dialogElement.find('#reorg-deselect-all').on('click', () => {
- dialogElement.find('input[type="checkbox"]').prop('checked', false);
- });
- }
- });
- });
-
- reorganizeBtn.dataset.reorganizeEventBound = 'true';
- log('"重新整理"按钮已成功绑定。', 'success');
- }
-}
-
-function bindClearRecordsButton() {
- const clearBtn = document.getElementById('clear-records-btn');
- const floorInput = document.getElementById('clear-records-before-floor');
-
- if (clearBtn && floorInput) {
- if (clearBtn.dataset.clearEventBound) return;
-
- clearBtn.addEventListener('click', async () => {
- const floorIndex = parseInt(floorInput.value, 10);
- if (isNaN(floorIndex) || floorIndex < 0) {
- toastr.warning('请输入有效的楼层号。');
- return;
- }
-
- if (confirm(`【警告】您确定要清除第 ${floorIndex} 楼之前的所有表格记录吗?\n\n此操作将永久删除这些消息中存储的表格快照,无法恢复。当前最新的表格状态不会受影响。`)) {
- try {
- const { clearTableRecordsBefore } = await import('../core/table-system/cleaner.js');
- const count = await clearTableRecordsBefore(floorIndex);
- toastr.success(`已成功清除 ${count} 条消息中的表格记录。`);
- } catch (error) {
- console.error('[内存储司] 清除记录失败:', error);
- toastr.error('清除记录失败,请检查控制台日志。');
- }
- }
- });
-
- clearBtn.dataset.clearEventBound = 'true';
- log('"清除记录"按钮已成功绑定。', 'success');
- }
-}
-
-
-function bindFloorFillButtons() {
- const selectedFloorsBtn = document.getElementById('fill-selected-floors-btn');
- const currentFloorBtn = document.getElementById('fill-current-floor-btn');
- const rollbackBtn = document.getElementById('rollback-and-refill-btn');
-
- if (selectedFloorsBtn) {
-
- if (selectedFloorsBtn.dataset.floorEventBound) return;
-
- selectedFloorsBtn.addEventListener('click', (event) => {
- const settings = extension_settings[extensionName];
- const tableSystemEnabled = settings.table_system_enabled !== false;
-
- if (!tableSystemEnabled) {
- event.preventDefault();
- toastr.warning('表格系统总开关已关闭,请先启用总开关。');
- return;
- }
-
- const startFloorInput = document.getElementById('floor-start-input');
- const endFloorInput = document.getElementById('floor-end-input');
-
- const startFloor = parseInt(startFloorInput.value, 10);
- const endFloor = parseInt(endFloorInput.value, 10);
-
- if (!startFloor || !endFloor) {
- toastr.warning('请输入有效的起始楼层和结束楼层。');
- return;
- }
-
- if (startFloor > endFloor) {
- toastr.warning('起始楼层不能大于结束楼层。');
- return;
- }
-
- if (startFloor < 1) {
- toastr.warning('楼层不能小于1。');
- return;
- }
-
- import('../core/table-system/batch-filler.js').then(module => {
- module.startFloorRangeFilling(startFloor, endFloor);
- });
- });
-
- selectedFloorsBtn.dataset.floorEventBound = 'true';
- log('"选定楼层填表"按钮已成功绑定。', 'success');
- }
-
- if (currentFloorBtn) {
- if (currentFloorBtn.dataset.currentEventBound) return;
-
- currentFloorBtn.addEventListener('click', (event) => {
- const settings = extension_settings[extensionName];
- const tableSystemEnabled = settings.table_system_enabled !== false;
-
- if (!tableSystemEnabled) {
- event.preventDefault();
- toastr.warning('表格系统总开关已关闭,请先启用总开关。');
- return;
- }
-
- import('../core/table-system/batch-filler.js').then(module => {
- module.startCurrentFloorFilling();
- });
- });
-
- currentFloorBtn.dataset.currentEventBound = 'true';
- log('"填当前楼层"按钮已成功绑定。', 'success');
- }
-
- if (rollbackBtn) {
- if (rollbackBtn.dataset.rollbackEventBound) return;
-
- rollbackBtn.addEventListener('click', async (event) => {
- const settings = extension_settings[extensionName];
- const tableSystemEnabled = settings.table_system_enabled !== false;
-
- if (!tableSystemEnabled) {
- event.preventDefault();
- toastr.warning('表格系统总开关已关闭,请先启用总开关。');
- return;
- }
-
- if (confirm('您确定要将表格状态回退到上一楼,并使用最新消息重新填表吗?')) {
- try {
- await TableManager.rollbackAndRefill();
- } catch (error) {
- console.error('[内存储司] 回退重填功能失败:', error);
- toastr.error('回退重填失败,请检查系统状态。');
- }
- }
- });
-
- rollbackBtn.dataset.rollbackEventBound = 'true';
- log('"回退重填"按钮已成功绑定。', 'success');
- }
-}
-
-function bindTemplateEditors() {
- const ruleEditor = document.getElementById('ai-rule-template-editor');
- const ruleSaveBtn = document.getElementById('ai-rule-template-save-btn');
- const ruleRestoreBtn = document.getElementById('ai-rule-template-restore-btn');
-
- const flowEditor = document.getElementById('ai-flow-template-editor');
- const flowSaveBtn = document.getElementById('ai-flow-template-save-btn');
- const flowRestoreBtn = document.getElementById('ai-flow-template-restore-btn');
-
- if (!ruleEditor || !flowEditor || !ruleSaveBtn || !flowSaveBtn) {
- log('无法找到指令模板编辑器或其按钮,绑定失败。', 'warn');
- return;
- }
-
- if (ruleSaveBtn.dataset.templateEventsBound) {
- return;
- }
-
- ruleEditor.value = TableManager.getBatchFillerRuleTemplate();
- flowEditor.value = TableManager.getBatchFillerFlowTemplate();
-
- ruleSaveBtn.addEventListener('click', () => {
- TableManager.saveBatchFillerRuleTemplate(ruleEditor.value);
- toastr.success('规则提示词已保存。');
- log('批量填表-规则提示词已保存。', 'success');
- });
-
- flowSaveBtn.addEventListener('click', () => {
- TableManager.saveBatchFillerFlowTemplate(flowEditor.value);
- toastr.success('流程提示词已保存。');
- log('批量填表-流程提示词已保存。', 'success');
- });
-
- ruleRestoreBtn.addEventListener('click', () => {
- if (confirm('您确定要将规则提示词恢复为默认设置吗?')) {
- ruleEditor.value = DEFAULT_AI_RULE_TEMPLATE;
- TableManager.saveBatchFillerRuleTemplate(ruleEditor.value);
- toastr.info('规则提示词已恢复为默认。');
- log('批量填表-规则提示词已恢复默认。', 'info');
- }
- });
-
- flowRestoreBtn.addEventListener('click', () => {
- if (confirm('您确定要将流程提示词恢复为默认设置吗?')) {
- flowEditor.value = DEFAULT_AI_FLOW_TEMPLATE;
- TableManager.saveBatchFillerFlowTemplate(flowEditor.value);
- toastr.info('流程提示词已恢复为默认。');
- log('批量填表-流程提示词已恢复默认。', 'info');
- }
- });
-
- ruleSaveBtn.dataset.templateEventsBound = 'true';
- flowSaveBtn.dataset.templateEventsBound = 'true';
- log('指令模板编辑器已成功绑定。', 'success');
-}
-
-function bindNccsApiEvents() {
- const settings = extension_settings[extensionName];
-
- if (settings.nccsEnabled === undefined) settings.nccsEnabled = false;
- if (settings.nccsApiMode === undefined) settings.nccsApiMode = 'openai_test';
- if (settings.nccsApiUrl === undefined) settings.nccsApiUrl = 'https://api.openai.com/v1';
- if (settings.nccsApiKey === undefined) settings.nccsApiKey = '';
- if (settings.nccsModel === undefined) settings.nccsModel = '';
- if (settings.nccsMaxTokens === undefined) settings.nccsMaxTokens = 2000;
- if (settings.nccsTemperature === undefined) settings.nccsTemperature = 0.7;
- if (settings.nccsTavernProfile === undefined) settings.nccsTavernProfile = '';
-
- const enabledToggle = document.getElementById('nccs-api-enabled');
- const configDiv = document.getElementById('nccs-api-config');
- const modeSelect = document.getElementById('nccs-api-mode');
- const urlInput = document.getElementById('nccs-api-url');
- const keyInput = document.getElementById('nccs-api-key');
- const modelInput = document.getElementById('nccs-api-model');
- const maxTokensSlider = document.getElementById('nccs-max-tokens');
- const maxTokensValue = document.getElementById('nccs-max-tokens-value');
- const temperatureSlider = document.getElementById('nccs-temperature');
- const temperatureValue = document.getElementById('nccs-temperature-value');
- const presetSelect = document.getElementById('nccs-sillytavern-preset');
- const testButton = document.getElementById('nccs-test-connection');
- const fetchModelsButton = document.getElementById('nccs-fetch-models');
-
- if (!enabledToggle || !configDiv) return;
-
- enabledToggle.checked = settings.nccsEnabled;
- if (modeSelect) modeSelect.value = settings.nccsApiMode;
- if (urlInput) urlInput.value = settings.nccsApiUrl;
- if (keyInput) keyInput.value = settings.nccsApiKey;
- if (modelInput) modelInput.value = settings.nccsModel;
- if (maxTokensSlider) {
- maxTokensSlider.value = settings.nccsMaxTokens;
- if (maxTokensValue) maxTokensValue.textContent = settings.nccsMaxTokens;
- }
- if (temperatureSlider) {
- temperatureSlider.value = settings.nccsTemperature;
- if (temperatureValue) temperatureValue.textContent = settings.nccsTemperature;
- }
- if (presetSelect) presetSelect.value = settings.nccsTavernProfile || '';
-
- const updateConfigVisibility = () => {
- configDiv.style.display = enabledToggle.checked ? 'block' : 'none';
- };
- updateConfigVisibility();
-
- const updateModeBasedVisibility = () => {
- if (!modeSelect) return;
- const isSillyTavernMode = modeSelect.value === 'sillytavern_preset';
- const isOpenAIMode = modeSelect.value === 'openai_test';
-
- const presetContainer = presetSelect?.closest('.amily2_opt_settings_block');
- if (presetContainer) {
- presetContainer.style.display = isSillyTavernMode ? 'block' : 'none';
- }
-
- const fieldsToHideInPresetMode = [
- { element: urlInput, containerId: null },
- { element: keyInput, containerId: null },
- { element: modelInput, containerId: null },
- { element: maxTokensSlider, containerId: null },
- { element: temperatureSlider, containerId: null }
- ];
-
- fieldsToHideInPresetMode.forEach(({ element }) => {
- if (element) {
- const container = element.closest('.amily2_opt_settings_block');
- if (container) {
- container.style.display = isSillyTavernMode ? 'none' : 'block';
- }
- }
- });
-
- const buttonsContainer = testButton?.closest('.nccs-button-row');
- if (buttonsContainer) {
- buttonsContainer.style.display = 'flex';
- }
- };
- updateModeBasedVisibility();
-
- enabledToggle.addEventListener('change', () => {
- settings.nccsEnabled = enabledToggle.checked;
- saveSettingsDebounced();
- updateConfigVisibility();
- log(`Nccs API ${enabledToggle.checked ? '已启用' : '已禁用'}`, 'info');
- });
-
- if (modeSelect) {
- modeSelect.addEventListener('change', () => {
- settings.nccsApiMode = modeSelect.value;
- saveSettingsDebounced();
- updateModeBasedVisibility();
- log(`Nccs API模式已切换为: ${modeSelect.value}`, 'info');
- });
- }
-
- if (urlInput) {
- const saveUrl = () => {
- settings.nccsApiUrl = urlInput.value;
- saveSettingsDebounced();
- };
-
- urlInput.addEventListener('blur', saveUrl);
- }
-
- if (keyInput) {
- const saveKey = () => {
- settings.nccsApiKey = keyInput.value;
- saveSettingsDebounced();
- };
-
- keyInput.addEventListener('blur', saveKey);
- }
-
- if (modelInput) {
- const saveModel = () => {
- settings.nccsModel = modelInput.value;
- saveSettingsDebounced();
- };
-
- modelInput.addEventListener('blur', saveModel);
- modelInput.addEventListener('input', saveModel);
- }
-
- if (maxTokensSlider && maxTokensValue) {
- maxTokensSlider.addEventListener('input', () => {
- maxTokensValue.textContent = maxTokensSlider.value;
- });
- maxTokensSlider.addEventListener('change', () => {
- settings.nccsMaxTokens = parseInt(maxTokensSlider.value);
- saveSettingsDebounced();
- });
- }
-
- if (temperatureSlider && temperatureValue) {
- temperatureSlider.addEventListener('input', () => {
- temperatureValue.textContent = temperatureSlider.value;
- });
- temperatureSlider.addEventListener('change', () => {
- settings.nccsTemperature = parseFloat(temperatureSlider.value);
- saveSettingsDebounced();
- });
- }
-
- if (presetSelect) {
- presetSelect.addEventListener('change', () => {
- settings.nccsTavernProfile = presetSelect.value;
- saveSettingsDebounced();
- });
- }
-
- if (testButton) {
- testButton.addEventListener('click', async () => {
- testButton.disabled = true;
- testButton.innerHTML = ' 测试中...';
-
- try {
- const success = await testNccsApiConnection();
- if (success) {
- toastr.success('Nccs API连接测试成功!');
- log('Nccs API连接测试成功', 'success');
- } else {
- toastr.error('Nccs API连接测试失败,请检查配置');
- log('Nccs API连接测试失败', 'error');
- }
- } catch (error) {
- toastr.error('Nccs API连接测试出错:' + error.message);
- log('Nccs API连接测试出错:' + error.message, 'error');
- } finally {
- testButton.disabled = false;
- testButton.innerHTML = ' 测试连接';
- }
- });
- }
-
- if (fetchModelsButton) {
- fetchModelsButton.addEventListener('click', async () => {
- fetchModelsButton.disabled = true;
- fetchModelsButton.innerHTML = ' 获取中...';
-
- if (urlInput) {
- settings.nccsApiUrl = urlInput.value;
- }
- if (keyInput) {
- settings.nccsApiKey = keyInput.value;
- }
- saveSettingsDebounced();
-
- try {
- const models = await fetchNccsModels();
- if (models && models.length > 0) {
- let modelSelect = document.getElementById('nccs-api-model-select');
- if (!modelSelect) {
- modelSelect = document.createElement('select');
- modelSelect.id = 'nccs-api-model-select';
- modelSelect.className = 'text_pole';
- modelInput.parentNode.insertBefore(modelSelect, modelInput.nextSibling);
- }
-
- modelSelect.innerHTML = '';
- models.forEach(model => {
- const option = document.createElement('option');
- option.value = model.id || model.name;
- option.textContent = model.name || model.id;
- if ((model.id || model.name) === settings.nccsModel) {
- option.selected = true;
- }
- modelSelect.appendChild(option);
- });
-
- modelInput.style.display = 'none';
- modelSelect.style.display = 'block';
-
- modelSelect.addEventListener('change', () => {
- const selectedModel = modelSelect.value;
- settings.nccsModel = selectedModel;
- modelInput.value = selectedModel;
- saveSettingsDebounced();
- });
-
- toastr.success(`成功获取 ${models.length} 个模型`);
- log(`Nccs API获取到 ${models.length} 个模型`, 'success');
- } else {
- toastr.warning('未获取到可用模型');
- log('Nccs API未获取到可用模型', 'warn');
- }
- } catch (error) {
- toastr.error('获取模型失败:' + error.message);
- log('Nccs API获取模型失败:' + error.message, 'error');
- } finally {
- fetchModelsButton.disabled = false;
- fetchModelsButton.innerHTML = ' 获取模型';
- }
- });
- }
-
- const loadSillyTavernPresets = async () => {
- if (!presetSelect) return;
- try {
- const context = getContext();
- if (!context?.extensionSettings?.connectionManager?.profiles) {
- throw new Error('无法获取SillyTavern配置文件列表');
- }
-
- const profiles = context.extensionSettings.connectionManager.profiles;
-
- const currentProfileId = settings.nccsTavernProfile;
-
- presetSelect.innerHTML = '';
- presetSelect.appendChild(new Option('选择预设', '', false, false));
-
- if (profiles && profiles.length > 0) {
- profiles.forEach(profile => {
- const isSelected = profile.id === currentProfileId;
- const option = new Option(profile.name, profile.id, isSelected, isSelected);
- presetSelect.appendChild(option);
- });
- log(`成功加载 ${profiles.length} 个SillyTavern配置文件`, 'success');
- } else {
- log('未找到可用的SillyTavern配置文件', 'warn');
- }
- } catch (error) {
- log('加载SillyTavern预设失败:' + error.message, 'error');
- }
- };
-
- if (modeSelect && presetSelect) {
- modeSelect.addEventListener('change', () => {
- if (modeSelect.value === 'sillytavern_preset') {
- loadSillyTavernPresets();
- }
- });
-
- if (settings.nccsApiMode === 'sillytavern_preset') {
- loadSillyTavernPresets();
- }
- }
-
- log('Nccs API事件绑定完成', 'success');
-}
-
-function bindChatTableDisplaySetting() {
- const settings = extension_settings[extensionName];
- const showInChatToggle = document.getElementById('show-table-in-chat-toggle');
- const continuousRenderToggle = document.getElementById('render-on-every-message-toggle');
-
- if (!showInChatToggle || !continuousRenderToggle) {
- log('找不到聊天内表格相关的开关,绑定失败。', 'warn');
- return;
- }
-
- showInChatToggle.checked = settings.show_table_in_chat === true;
- continuousRenderToggle.checked = settings.render_on_every_message === true;
-
- const updateContinuousRenderState = () => {
- if (showInChatToggle.checked) {
- continuousRenderToggle.disabled = false;
- continuousRenderToggle.closest('.control-block-with-switch').style.opacity = '1';
- } else {
- continuousRenderToggle.disabled = true;
- continuousRenderToggle.closest('.control-block-with-switch').style.opacity = '0.5';
- }
- };
-
- updateContinuousRenderState();
-
- showInChatToggle.addEventListener('change', () => {
- settings.show_table_in_chat = showInChatToggle.checked;
- saveSettingsDebounced();
- toastr.info(`聊天内表格显示已${showInChatToggle.checked ? '开启' : '关闭'}。`);
- updateContinuousRenderState();
- });
-
- continuousRenderToggle.addEventListener('change', () => {
- settings.render_on_every_message = continuousRenderToggle.checked;
- saveSettingsDebounced();
- toastr.info(`持续渲染最新消息功能已${continuousRenderToggle.checked ? '开启' : '关闭'}。请切换聊天以应用更改。`);
- });
-
- log('聊天内表格显示设置及其依赖关系已成功绑定。', 'success');
-}