From 268e6ef4951263e0711b859fabc3218f27399313 Mon Sep 17 00:00:00 2001
From: Wx-2025 <351320169@qq.com>
Date: Sun, 30 Nov 2025 23:00:13 +0800
Subject: [PATCH] Add files via upload
---
super-memory/bindings.js | 89 +++++++++++++
super-memory/index.html | 85 ++++++++++++
super-memory/lorebook-bridge.js | 228 ++++++++++++++++++++++++++++++++
super-memory/manager.js | 203 ++++++++++++++++++++++++++++
super-memory/smart-indexer.js | 77 +++++++++++
5 files changed, 682 insertions(+)
create mode 100644 super-memory/bindings.js
create mode 100644 super-memory/index.html
create mode 100644 super-memory/lorebook-bridge.js
create mode 100644 super-memory/manager.js
create mode 100644 super-memory/smart-indexer.js
diff --git a/super-memory/bindings.js b/super-memory/bindings.js
new file mode 100644
index 0000000..591de3f
--- /dev/null
+++ b/super-memory/bindings.js
@@ -0,0 +1,89 @@
+import { extensionName } from "../../utils/settings.js";
+import { extension_settings } from "/scripts/extensions.js";
+import { saveSettingsDebounced } from "/script.js";
+import { initializeSuperMemory } from "./manager.js";
+
+export function bindSuperMemoryEvents() {
+ const panel = $('#amily2_super_memory_panel');
+ if (panel.length === 0) return;
+
+ panel.on('click', '.sm-nav-item', function() {
+ const tab = $(this).data('tab');
+
+ panel.find('.sm-nav-item').removeClass('active');
+ $(this).addClass('active');
+
+ panel.find('.sm-tab-pane').removeClass('active');
+ panel.find(`#sm-${tab}-tab`).addClass('active');
+ });
+
+ panel.on('change', 'input[type="checkbox"]', function() {
+ if (!extension_settings[extensionName]) extension_settings[extensionName] = {};
+
+ const id = this.id;
+ let key = null;
+
+ if (id === 'sm-system-enabled') key = 'super_memory_enabled';
+ if (id === 'sm-bridge-enabled') key = 'superMemory_bridgeEnabled';
+
+ if (key) {
+ extension_settings[extensionName][key] = this.checked;
+ saveSettingsDebounced();
+ console.log(`[Amily2-SuperMemory] Setting updated: ${key} = ${this.checked}`);
+ }
+ });
+
+ panel.on('change', 'input[type="number"], input[type="text"]', function() {
+ if (!extension_settings[extensionName]) extension_settings[extensionName] = {};
+
+ const id = this.id;
+ let key = null;
+
+ if (id === 'sm-index-depth') key = 'superMemory_indexDepth';
+ if (id === 'sm-detail-depth') key = 'superMemory_detailDepth';
+
+ if (key) {
+ let value = this.value;
+ if (this.type === 'number') value = parseInt(value, 10);
+
+ extension_settings[extensionName][key] = value;
+ saveSettingsDebounced();
+ console.log(`[Amily2-SuperMemory] Setting updated: ${key} = ${value}`);
+ }
+ });
+
+ loadSuperMemorySettings();
+
+ console.log('[Amily2-SuperMemory] Events bound successfully.');
+}
+
+function loadSuperMemorySettings() {
+ const settings = extension_settings[extensionName] || {};
+
+ $('#sm-system-enabled').prop('checked', settings.super_memory_enabled ?? false);
+ $('#sm-bridge-enabled').prop('checked', settings.superMemory_bridgeEnabled ?? false);
+
+ $('#sm-index-depth').val(settings.superMemory_indexDepth ?? 0);
+ $('#sm-detail-depth').val(settings.superMemory_detailDepth ?? 2);
+}
+
+window.sm_initializeSystem = async function() {
+ toastr.info('超级记忆系统正在初始化...');
+ $('#sm-system-status').text('初始化中...').css('color', 'yellow');
+
+ try {
+ await initializeSuperMemory();
+ toastr.success('超级记忆系统初始化完成。');
+ } catch (error) {
+ console.error(error);
+ toastr.error('初始化失败,请检查控制台。');
+ $('#sm-system-status').text('错误').css('color', 'red');
+ }
+};
+
+window.sm_purgeMemory = function() {
+ if (confirm('您确定要清空所有超级记忆数据吗?')) {
+ toastr.warning('记忆已清空。');
+ $('#sm-system-status').text('未初始化').css('color', '#ffc107');
+ }
+};
diff --git a/super-memory/index.html b/super-memory/index.html
new file mode 100644
index 0000000..dcb5240
--- /dev/null
+++ b/super-memory/index.html
@@ -0,0 +1,85 @@
+
+
+
+
+
+
究极长期记忆 (Super Memory)
+
欢迎来到 Amily2 的核心记忆中枢。这里掌管着世界的记忆,连接着每一个角色、每一个物品与每一段传说。
+
通过“三级金字塔”注入策略,我们将实现极致的 Token 节省与无限的记忆深度。
+
+
+
+
+
+
+
+
+
+
diff --git a/super-memory/lorebook-bridge.js b/super-memory/lorebook-bridge.js
new file mode 100644
index 0000000..dfdcfcf
--- /dev/null
+++ b/super-memory/lorebook-bridge.js
@@ -0,0 +1,228 @@
+import { amilyHelper } from "../tavern-helper/main.js";
+import { extension_settings, getContext } from "/scripts/extensions.js";
+import { extensionName } from "../../utils/settings.js";
+import { this_chid, characters } from "/script.js";
+
+function getMemoryBookName() {
+ let charName = "Global";
+ const context = getContext();
+
+ if (this_chid !== undefined && characters[this_chid]) {
+ charName = characters[this_chid].name;
+ } else if (context.characterId !== undefined && characters[context.characterId]) {
+ charName = characters[context.characterId].name;
+ }
+
+ const safeCharName = charName.replace(/[<>:"/\\|?*]/g, '_');
+ return `Amily2_Memory_${safeCharName}`;
+}
+
+export async function syncToLorebook(tableName, data, indexText, role, headers, rowStatuses, depth = 100) {
+ console.log(`[Amily2-Bridge] 开始同步表格: ${tableName} (Depth: ${depth})`);
+
+ await ensureMemoryBook();
+
+ const bookName = getMemoryBookName();
+
+ let entries = await amilyHelper.getLorebookEntries(bookName);
+ if (!entries) entries = [];
+
+ const entriesToUpdate = [];
+ const entriesToCreate = [];
+
+ const processEntry = (comment, keys, content, type = 'selective', enabled = true) => {
+ const existingEntry = entries.find(e => e.comment === comment);
+ if (existingEntry) {
+ existingEntry.content = content;
+ existingEntry.key = keys;
+ existingEntry.order = depth;
+
+ if (type === 'constant') {
+ existingEntry.constant = true;
+ } else {
+ existingEntry.constant = false;
+ }
+ if (existingEntry.enabled !== undefined) {
+ existingEntry.enabled = enabled;
+ } else {
+ existingEntry.disable = !enabled;
+ }
+ entriesToUpdate.push(existingEntry);
+ } else {
+ entriesToCreate.push({
+ comment: comment,
+ keys: keys,
+ content: content,
+ type: type,
+ position: 1,
+ order: depth,
+ enabled: enabled
+ });
+ }
+ };
+
+ if (role === 'database') {
+ const indexKey = [`Amily_Index_${tableName}`];
+ const indexComment = `[Amily2] Index for ${tableName}`;
+ let indexContent = `【${tableName} 索引】\n${indexText.trim()}`;
+
+ if (tableName.includes("大纲")) {
+ indexContent += `\n\n【Amily2-前瞻性预测指令】\n为了实现“提前一轮触发绿灯世界书”的高级记忆回流,请在生成本轮回复时,根据当前剧情走向,**大胆预测并自然提及**下一轮可能出现的索引编码。可自行增加一个标签并使用html注释掉该编码。这将激活系统的预加载机制,确保剧情发展的连贯性。\n`;
+ }
+
+ processEntry(indexComment, indexKey, indexContent, 'constant');
+ }
+
+ data.forEach((row, index) => {
+ if (!row || row.length === 0) return;
+
+ const primaryVal = row[0];
+
+ if (!primaryVal) return;
+
+ const isPendingDeletion = rowStatuses && rowStatuses[index] === 'pending-deletion';
+ const isEnabled = !isPendingDeletion;
+
+ const triggerKeys = [primaryVal];
+ const entryComment = `[Amily2] Detail: ${tableName} - ${primaryVal}`;
+
+ let finalHeaders = headers;
+ if (!finalHeaders || finalHeaders.length < row.length) {
+ finalHeaders = [];
+ for(let i=0; i 0) {
+ console.log(`[Amily2-Bridge] 清理 ${entriesToDelete.length} 个废弃条目...`);
+ await amilyHelper.deleteLorebookEntries(bookName, entriesToDelete);
+ }
+
+ if (entriesToUpdate.length > 0) {
+ console.log(`[Amily2-Bridge] 更新 ${entriesToUpdate.length} 个条目...`);
+ await amilyHelper.setLorebookEntries(bookName, entriesToUpdate);
+ }
+
+ if (entriesToCreate.length > 0) {
+ console.log(`[Amily2-Bridge] 创建 ${entriesToCreate.length} 个新条目...`);
+ await amilyHelper.createLorebookEntries(bookName, entriesToCreate);
+ }
+ console.log(`[Amily2-Bridge] 同步完成: ${tableName}`);
+}
+
+export async function ensureMemoryBook() {
+ const bookName = getMemoryBookName();
+ const books = await amilyHelper.getLorebooks();
+
+ if (!books.includes(bookName)) {
+ console.log(`[Amily2-Bridge] 创建角色专用世界书: ${bookName}`);
+ await amilyHelper.createLorebook(bookName);
+ }
+
+ const settings = extension_settings[extensionName] || {};
+ const shouldBind = settings.superMemory_autoBind === true;
+
+ if (shouldBind && bookName.startsWith("Amily2_Memory_") && bookName !== "Amily2_Memory_Global") {
+ console.log(`[Amily2-Bridge] 自动绑定世界书到当前角色...`);
+ await amilyHelper.bindLorebookToCharacter(bookName);
+ } else if (!shouldBind) {
+ console.log(`[Amily2-Bridge] 跳过自动绑定 (设置已禁用)。请手动在世界书管理中激活: ${bookName}`);
+ }
+}
+
+function createEntryTemplate() {
+ return {
+ uid: Date.now() + Math.floor(Math.random() * 1000),
+ key: [],
+ keysecondary: [],
+ comment: "",
+ content: "",
+ constant: false,
+ selective: true,
+ order: 100,
+ position: 1,
+ enabled: true
+ };
+}
+
+export async function updateTransientHint(hint) {
+ console.log('[Amily2-Bridge] 更新瞬时记忆提示...');
+ await ensureMemoryBook();
+ const bookName = getMemoryBookName();
+
+ const comment = "[Amily2] Active Memory Hint";
+ const content = hint ? `\n\n【重要记忆回响】\n${hint}\n\n` : "";
+ const enabled = !!hint;
+
+ let entries = await amilyHelper.getLorebookEntries(bookName);
+ if (!entries) entries = [];
+
+ const existingEntry = entries.find(e => e.comment === comment);
+
+ if (existingEntry) {
+ existingEntry.content = content;
+ existingEntry.enabled = enabled;
+ existingEntry.order = 0;
+ existingEntry.constant = true;
+
+ await amilyHelper.setLorebookEntries(bookName, [existingEntry]);
+ } else if (hint) {
+ const newEntry = {
+ comment: comment,
+ keys: [],
+ content: content,
+ constant: true,
+ selective: false,
+ order: 0,
+ position: 0,
+ enabled: true
+ };
+ await amilyHelper.createLorebookEntries(bookName, [newEntry]);
+ }
+
+ console.log(`[Amily2-Bridge] 瞬时记忆提示已${enabled ? '启用' : '清除'}。`);
+}
diff --git a/super-memory/manager.js b/super-memory/manager.js
new file mode 100644
index 0000000..38f633d
--- /dev/null
+++ b/super-memory/manager.js
@@ -0,0 +1,203 @@
+import { extension_settings, getContext } from "/scripts/extensions.js";
+import { extensionName } from "../../utils/settings.js";
+import { amilyHelper } from "../tavern-helper/main.js";
+import { generateIndex } from "./smart-indexer.js";
+import { syncToLorebook, ensureMemoryBook, updateTransientHint } from "./lorebook-bridge.js";
+import { getMemoryState, loadMemoryState, saveMemoryState } from "../table-system/manager.js";
+import { eventSource, event_types } from "/script.js";
+
+let isInitialized = false;
+let updateQueue = [];
+let isProcessing = false;
+let lastChatId = null;
+
+const METADATA_KEY = 'Amily2_Memory_Data';
+
+export async function initializeSuperMemory() {
+ const settings = extension_settings[extensionName] || {};
+ if (settings.super_memory_enabled === false) {
+ console.log('[Amily2-SuperMemory] 功能已禁用 (super_memory_enabled = false)。');
+ if (window.$) $('#sm-system-status').text('已禁用').css('color', 'gray');
+ return;
+ }
+
+ if (isInitialized) return;
+ console.log('[Amily2-SuperMemory] 初始化核心管理器...');
+
+ if (!amilyHelper) {
+ console.error('[Amily2-SuperMemory] 致命错误:AmilyHelper 未就绪。');
+ return;
+ }
+
+ document.addEventListener('AMILY2_TABLE_UPDATED', handleTableUpdate);
+
+ eventSource.on(event_types.CHAT_CHANGED, async () => {
+ const settings = extension_settings[extensionName] || {};
+ if (settings.super_memory_enabled === false) return;
+
+ console.log('[Amily2-SuperMemory] 检测到聊天切换,正在刷新记忆状态...');
+ await checkWorldBookStatus();
+
+ await tryRestoreStateFromMetadata();
+
+ await forceSyncAll();
+ });
+
+ await checkWorldBookStatus();
+
+ await tryRestoreStateFromMetadata();
+
+ await forceSyncAll();
+
+ isInitialized = true;
+ console.log('[Amily2-SuperMemory] 核心管理器初始化完成。');
+
+ if (window.$) {
+ $('#sm-system-status').text('运行中').css('color', '#4caf50');
+ }
+}
+
+async function checkWorldBookStatus() {
+ try {
+ await ensureMemoryBook();
+ } catch (error) {
+ console.error('[Amily2-SuperMemory] 检查世界书状态失败:', error);
+ }
+}
+
+function handleTableUpdate(event) {
+ const settings = extension_settings[extensionName] || {};
+ if (settings.super_memory_enabled === false) return;
+
+ const { tableName, data, role, hint, headers, rowStatuses } = event.detail;
+ console.log(`[Amily2-SuperMemory] 检测到表格更新: ${tableName} (Role: ${role})`);
+
+ updateQueue.push({ tableName, data, role, hint, headers, rowStatuses });
+ processQueue();
+}
+
+async function processQueue() {
+ if (isProcessing || updateQueue.length === 0) return;
+ isProcessing = true;
+
+ try {
+ while (updateQueue.length > 0) {
+ const task = updateQueue.shift();
+ await processUpdateTask(task);
+ }
+
+ await saveStateToMetadata();
+
+ } catch (error) {
+ console.error('[Amily2-SuperMemory] 处理更新队列失败:', error);
+ } finally {
+ isProcessing = false;
+ }
+}
+
+async function processUpdateTask(task) {
+ const { tableName, data, role, hint, headers, rowStatuses } = task;
+
+ const activeData = data.filter((_, i) => !rowStatuses || rowStatuses[i] !== 'pending-deletion');
+ const indexText = generateIndex(activeData, role, tableName);
+
+ const allTables = getMemoryState();
+ const tableIndex = allTables.findIndex(t => t.name === tableName);
+ const depth = 8001 + (tableIndex >= 0 ? tableIndex : 99);
+
+ await syncToLorebook(tableName, data, indexText, role, headers, rowStatuses, depth);
+
+ if (hint) {
+ console.log(`[Amily2-SuperMemory] 应用主动记忆提示: ${hint}`);
+ await updateTransientHint(hint);
+ }
+
+ console.log(`[Amily2-SuperMemory] 任务完成: ${tableName}`);
+
+ updateDashboardCounters();
+}
+
+async function saveStateToMetadata() {
+ const context = getContext();
+ if (!context.chat || context.chat.length === 0) return;
+
+ const lastMsgIndex = context.chat.length - 1;
+ const lastMsg = context.chat[lastMsgIndex];
+
+ const currentState = getMemoryState();
+
+ if (!lastMsg.metadata) lastMsg.metadata = {};
+
+ lastMsg.metadata[METADATA_KEY] = JSON.parse(JSON.stringify(currentState));
+
+ if (context.saveChat) {
+ await context.saveChat();
+ }
+
+ console.log(`[Amily2-SuperMemory] 状态已保存至消息 #${lastMsgIndex}`);
+}
+
+export async function tryRestoreStateFromMetadata() {
+ const context = getContext();
+ if (!context.chat || context.chat.length === 0) return;
+
+ let foundState = null;
+ let foundIndex = -1;
+
+ for (let i = context.chat.length - 1; i >= 0; i--) {
+ const msg = context.chat[i];
+ if (msg.metadata && msg.metadata[METADATA_KEY]) {
+ foundState = msg.metadata[METADATA_KEY];
+ foundIndex = i;
+ break;
+ }
+ }
+
+ if (foundState) {
+ console.log(`[Amily2-SuperMemory] 发现历史状态 (Msg #${foundIndex}),正在恢复...`);
+ if (typeof loadMemoryState === 'function') {
+ loadMemoryState(foundState);
+ await forceSyncAll();
+ } else {
+ console.warn('[Amily2-SuperMemory] table-system 缺少 loadMemoryState 方法,无法恢复状态。');
+ }
+ } else {
+ console.log('[Amily2-SuperMemory] 未在聊天记录中发现历史状态,使用默认/当前状态。');
+ }
+}
+
+function updateDashboardCounters() {
+ const tables = getMemoryState();
+ if (tables && window.$) {
+ $('#sm-index-count').text(`${tables.length} 个索引`);
+ const totalRows = tables.reduce((acc, t) => acc + (t.rows ? t.rows.length : 0), 0);
+ $('#sm-detail-count').text(`${totalRows} 个详情`);
+ }
+}
+
+export async function forceSyncAll() {
+ console.log('[Amily2-SuperMemory] 正在执行全量同步...');
+ const tables = getMemoryState();
+
+ if (!tables || tables.length === 0) {
+ console.warn('[Amily2-SuperMemory] 没有可同步的表格数据。');
+ return;
+ }
+
+ for (const table of tables) {
+ let role = 'database';
+ if (table.name.includes('时空') || table.name.includes('世界钟')) role = 'anchor';
+ if (table.name.includes('日志') || table.name.includes('Log')) role = 'log';
+
+ updateQueue.push({
+ tableName: table.name,
+ data: table.rows,
+ headers: table.headers,
+ rowStatuses: table.rowStatuses || [],
+ role: role
+ });
+ }
+
+ await processQueue();
+ console.log('[Amily2-SuperMemory] 全量同步完成。');
+}
diff --git a/super-memory/smart-indexer.js b/super-memory/smart-indexer.js
new file mode 100644
index 0000000..57cd6a7
--- /dev/null
+++ b/super-memory/smart-indexer.js
@@ -0,0 +1,77 @@
+export function generateIndex(data, role, tableName = "") {
+ if (!Array.isArray(data) || data.length === 0) {
+ return "";
+ }
+
+ const headers = Object.keys(data[0]);
+ if (headers.length === 0) return "";
+
+ const indexColumns = identifyIndexColumns(data, headers);
+
+ let indexLines = [];
+ indexLines.push(`| ${indexColumns.join(' | ')} |`);
+ indexLines.push(`| ${indexColumns.map(() => '---').join(' | ')} |`);
+
+ let processedData = [...data];
+
+ const firstColKey = headers[0];
+ const firstColVal = data[0] ? data[0][firstColKey] : '';
+ const isIndexCol = (firstColKey && (firstColKey.includes('索引') || firstColKey.includes('Index'))) ||
+ (typeof firstColVal === 'string' && /^\s*M\d+/.test(firstColVal)) ||
+ (tableName && (tableName.includes('总结') || tableName.includes('大纲')));
+
+ if (isIndexCol) {
+ processedData.sort((a, b) => {
+ const valA = String(a[firstColKey] || '');
+ const valB = String(b[firstColKey] || '');
+ return valA.localeCompare(valB, undefined, { numeric: true });
+ });
+ }
+
+ for (const row of processedData) {
+ const lineParts = indexColumns.map(col => {
+ let val = row[col];
+ if (val === undefined || val === null) return "";
+ val = String(val).trim();
+ if (val.length > 15) val = val.substring(0, 12) + "...";
+ return val;
+ });
+ indexLines.push(`| ${lineParts.join(' | ')} |`);
+ }
+
+ return indexLines.join('\n');
+}
+
+function identifyIndexColumns(data, headers) {
+ if (headers.length <= 2) return headers;
+
+ const candidates = [];
+ const maxColumns = 3;
+
+ for (const header of headers) {
+ if (candidates.length >= maxColumns) break;
+
+ let totalLen = 0;
+ let count = 0;
+ for (const row of data) {
+ if (row[header]) {
+ totalLen += String(row[header]).length;
+ count++;
+ }
+ }
+ const avgLen = count > 0 ? totalLen / count : 0;
+
+ const isLongText = avgLen > 20;
+ const isBlacklisted = /desc|bio|detail|history|经历|描述|详情/i.test(header);
+
+ if (!isLongText && !isBlacklisted) {
+ candidates.push(header);
+ }
+ }
+
+ if (candidates.length === 0) {
+ return headers.slice(0, Math.min(headers.length, maxColumns));
+ }
+
+ return candidates;
+}