diff --git a/core/archive-manager.js b/core/archive-manager.js new file mode 100644 index 0000000..e86961e --- /dev/null +++ b/core/archive-manager.js @@ -0,0 +1,126 @@ +import { ingestTextToHanlinyuan, getSettings } from './rag-processor.js'; +import { deleteRow, insertRow, updateRow } from './table-system/manager.js'; +import { extension_settings } from '/scripts/extensions.js'; +import { extensionName } from '../utils/settings.js'; + +let isArchiving = false; + +export function initializeArchiveManager() { + document.addEventListener('AMILY2_TABLE_UPDATED', handleTableUpdate); + console.log('[归档管理器] 已启动,正在监控表格状态...'); +} + +async function handleTableUpdate(event) { + const { tableName, data, role } = event.detail; + const settings = getSettings(); + + if (!settings.archive || !settings.archive.enabled) return; + + const targetTable = settings.archive.targetTable || '总结表'; + const threshold = settings.archive.threshold || 20; + + if (tableName !== targetTable) return; + + if (isArchiving) return; + + let hasNotice = false; + + if (data.length > 0 && data[0][2] && data[0][2].includes('已自动归档')) { + hasNotice = true; + realRows = data.slice(1); + } + + if (realRows.length > threshold) { + console.log(`[归档管理器] 检测到 ${targetTable} 行数 (${realRows.length}) 超过阈值 (${threshold}),开始归档...`); + await performArchive(data, hasNotice, targetTable); + } +} + +async function performArchive(allRows, hasNotice, targetTable) { + isArchiving = true; + const settings = getSettings(); + const batchSize = settings.archive.batchSize || 10; + + try { + + const startIndex = hasNotice ? 1 : 0; + const rowsToArchive = allRows.slice(startIndex, startIndex + batchSize); + + if (rowsToArchive.length === 0) return; + + const tables = getMemoryState(); + const outlineTable = tables ? tables.find(t => t.name === '总体大纲') : null; + const outlineMap = new Map(); + + if (outlineTable && outlineTable.rows) { + outlineTable.rows.forEach(row => { + if (row[0]) outlineMap.set(row[0], row[1] || '无大纲内容'); + }); + } + + const archiveText = rowsToArchive.map(row => { + const index = row[0] || '未知索引'; + const timeSpan = row[1] || '未知时间'; + const summary = row[2] || '无内容'; + const outline = outlineMap.get(index) || '无大纲关联'; + + return `[历史总结归档] [索引: ${index}] [时间: ${timeSpan}] [大纲: ${outline}]\n${summary}`; + }).join('\n\n'); + + const fullText = archiveText; + + console.log('[归档管理器] 正在将旧总结录入翰林院...'); + + const result = await ingestTextToHanlinyuan( + fullText, + 'manual', + { sourceName: '历史总结归档' }, + (progress) => console.log(`[归档进度] ${progress.message}`) + ); + + if (result.success) { + console.log('[归档管理器] 录入成功,正在清理表格...'); + + const indicesToDelete = []; + for (let i = 0; i < rowsToArchive.length; i++) { + indicesToDelete.push(startIndex + i); + } + + for (let i = indicesToDelete.length - 1; i >= 0; i--) { + await deleteRow(findTableIndex(targetTable), indicesToDelete[i]); + } + const noticeText = `(已自动归档 ${rowsToArchive.length} 条历史记录至翰林院,可随时询问找回)`; + const noticeRowData = { + 0: 'SYSTEM', + 1: '---', + 2: noticeText + }; + + if (hasNotice) { + + await updateRow(findTableIndex(targetTable), 0, noticeRowData); + } else { + + await insertRow(findTableIndex(targetTable), 0, 'above'); + await updateRow(findTableIndex(targetTable), 0, noticeRowData); + } + + console.log('[归档管理器] 归档流程完成。'); + } else { + console.error('[归档管理器] RAG 录入失败,取消清理。', result.error); + } + + } catch (error) { + console.error('[归档管理器] 执行出错:', error); + } finally { + isArchiving = false; + } +} + +import { getMemoryState } from './table-system/manager.js'; + +function findTableIndex(name) { + const tables = getMemoryState(); + if (!tables) return -1; + return tables.findIndex(t => t.name === name); +} diff --git a/core/fractal-memory.js b/core/fractal-memory.js new file mode 100644 index 0000000..162c710 --- /dev/null +++ b/core/fractal-memory.js @@ -0,0 +1,229 @@ +import { getContext, extension_settings } from "/scripts/extensions.js"; +import { setExtensionPrompt, eventSource, event_types } from "/script.js"; +import { callAI } from "./api.js"; +import { callNgmsAI } from "./api/Ngms_api.js"; +import { extensionName } from "../utils/settings.js"; +import { getMemoryState, updateRow, insertRow, deleteRow, clearAllTables } from "./table-system/manager.js"; + +const FRACTAL_INJECTION_KEY = 'HANLINYUAN_FRACTAL_MEMORY'; +const BUFFER_SIZE = 5; +const UPDATE_INTERVAL = 5; + + + +export async function initializeFractalMemory() { + eventSource.on(event_types.MESSAGE_RECEIVED, handleMessageReceived); + console.log('[分形记忆] 系统已启动,正在构建多维记忆...'); +} + +let messageCounter = 0; + +async function handleMessageReceived() { + messageCounter++; + if (messageCounter >= UPDATE_INTERVAL) { + messageCounter = 0; + await updateSceneLayer(); + } +} + +async function updateSceneLayer() { + const context = getContext(); + const settings = extension_settings[extensionName]; + + if (!settings.fractalMemory) { + settings.fractalMemory = { + saga: "故事刚刚开始...", + arc: [], + scene: [] + }; + } + const memory = settings.fractalMemory; + + console.log('[分形记忆] 正在提取近期事态...'); + + const recentChat = context.chat.slice(-UPDATE_INTERVAL).map(m => `${m.name}: ${m.mes}`).join('\n'); + + const prompt = ` +请将以下对话总结为一句话的“场景事件”,描述发生了什么。 +要求:简洁、客观、包含关键动作。 + +【对话内容】 +${recentChat} + +【输出】 +(仅输出一句话总结) +`; + + const newEvent = await _callLLM(prompt); + if (!newEvent) return; + + console.log(`[分形记忆] 新增场景事件: ${newEvent}`); + memory.scene.push(newEvent); + + if (memory.scene.length >= BUFFER_SIZE) { + await compressSceneToArc(); + } + + context.saveSettingsDebounced(); + injectFractalMemory(); + syncToTables(); +} + +async function compressSceneToArc() { + const context = getContext(); + const settings = extension_settings[extensionName]; + const memory = settings.fractalMemory; + + console.log('[分形记忆] 场景层已满,正在压缩至篇章层...'); + + const sceneEvents = memory.scene.join('\n'); + const prompt = ` +请将以下 5 个连续的“场景事件”合并总结为一条“篇章节点”。 +这条节点应该概括这一系列事件对剧情的推动作用。 + +【场景事件列表】 +${sceneEvents} + +【输出】 +(仅输出一句话总结) +`; + + const newArcEvent = await _callLLM(prompt); + if (!newArcEvent) return; + + console.log(`[分形记忆] 新增篇章节点: ${newArcEvent}`); + + memory.arc.push(newArcEvent); + memory.scene = []; + + if (memory.arc.length >= BUFFER_SIZE) { + await compressArcToSaga(); + } +} + +async function compressArcToSaga() { + const context = getContext(); + const settings = extension_settings[extensionName]; + const memory = settings.fractalMemory; + + console.log('[分形记忆] 篇章层已满,正在重写宏观史诗...'); + + const arcEvents = memory.arc.join('\n'); + const oldSaga = memory.saga; + + const prompt = ` +请根据“旧的宏观史诗”和新发生的“篇章事件”,重写并更新整个故事的“宏观史诗”。 +宏观史诗应该是一个高度概括的段落,描述故事的起因、经过和当前状态。 + +【旧史诗】 +${oldSaga} + +【新篇章事件】 +${arcEvents} + +【输出】 +(输出一段更新后的宏观史诗,约 100-200 字) +`; + + const newSaga = await _callLLM(prompt); + if (!newSaga) return; + + console.log(`[分形记忆] 宏观史诗已更新。`); + + memory.saga = newSaga; + memory.arc = []; +} + +function syncToTables() { + const settings = extension_settings[extensionName]; + if (!settings || !settings.fractalMemory) return; + const memory = settings.fractalMemory; + const tables = getMemoryState(); + if (!tables) return; + + const targetTableName = '【系统】分形记忆'; + const tableIndex = tables.findIndex(t => t.name === targetTableName); + + if (tableIndex !== -1) { + const table = tables[tableIndex]; + const targetRows = []; + + targetRows.push({ + 0: '宏观史诗', + 1: memory.saga + }); + + memory.arc.forEach((event, i) => { + targetRows.push({ + 0: `篇章-${i+1}`, + 1: event + }); + }); + + memory.scene.forEach((event, i) => { + targetRows.push({ + 0: `场景-${i+1}`, + 1: event + }); + }); + + while (table.rows.length > targetRows.length) { + deleteRow(tableIndex, table.rows.length - 1); + } + + targetRows.forEach((rowData, i) => { + if (i < table.rows.length) { + updateRow(tableIndex, i, rowData); + } else { + insertRow(tableIndex, rowData); + } + }); + } +} + +export function injectFractalMemory() { + const settings = extension_settings[extensionName]; + if (!settings || !settings.fractalMemory) return; + + const memory = settings.fractalMemory; + + let content = `【分形记忆系统】\n`; + + content += `[宏观史诗]\n${memory.saga}\n\n`; + + if (memory.arc.length > 0) { + content += `[当前篇章]\n${memory.arc.map(e => `- ${e}`).join('\n')}\n\n`; + } + + if (memory.scene.length > 0) { + content += `[近期事态]\n${memory.scene.map(e => `- ${e}`).join('\n')}`; + } + + setExtensionPrompt( + FRACTAL_INJECTION_KEY, + content, + 0, + 4, + false, + 0 + ); +} + + +async function _callLLM(prompt) { + const settings = extension_settings[extensionName]; + const messages = [{ role: 'user', content: prompt }]; + + try { + let responseText = ''; + if (settings.ngmsEnabled) { + responseText = await callNgmsAI(messages); + } else { + responseText = await callAI(messages); + } + return responseText.trim(); + } catch (error) { + console.error('[分形记忆] AI 调用失败:', error); + return null; + } +} diff --git a/core/rag-settings.js b/core/rag-settings.js index 54e4a5a..76688dd 100644 --- a/core/rag-settings.js +++ b/core/rag-settings.js @@ -1,87 +1,98 @@ - -'use strict'; - -export const defaultSettings = { - retrieval: { - enabled: false, - apiEndpoint: 'openai', - customApiUrl: 'https://api.siliconflow.cn/v1', - apiKey: '', - embeddingModel: 'text-embedding-3-small', - notify: true, - batchSize: 50, - independentChatMemoryEnabled: false, - }, - advanced: { - chunkSize: 768, - overlap: 50, - matchThreshold: 0.5, - queryMessageCount: 2, - maxResults: 10, - }, - injection_novel: { - template: '以下内容是翰林院向量化后注入的原著小说剧情,但可能顺序会有些错乱,已经对前后做出了标识,请自行判断顺序:\n\n{{novel_text}}\n\n【以上内容是小说的原著剧情,切莫以此作为剧情进展,只是作为剧情的关联】', - position: 1, - depth: 2, - depth_role: 0, - }, - injection_chat: { - template: '以下内容是翰林院向量化后注入的聊天对话记录,但可能顺序会有些错乱,已经对前后做出了标识,请自行判断顺序:\n\n{{chat_text}}\n\n【以上内容是对话的楼层记录,切莫以此作为剧情进展,只是作为相关提示】', - position: 1, - depth: 2, - depth_role: 0, - }, - injection_lorebook: { - template: '以下内容是翰林院向量化后注入的世界书的条目内容(可能内含对话记录的总结),顺序可能会有些错乱,但已经对前后做出了标识,请自行判断顺序:\n\n{{lorebook_text}}\n\n【以上内容是从世界书中向量化后的内容,切莫以此作为剧情进展,只是作为已发生过的事情提醒】', - position: 1, - depth: 2, - depth_role: 0, - }, - injection_manual: { - template: '以下内容是翰林院向量化后用户手动注入的内容,可能顺序会有些错乱,但已经对前后做出了标识,请自行判断顺序:\n\n{{manual_text}}\n\n【以上内容为用户手动向量化注入的内容,切莫以此作为剧情进展,只是作为相关提示】', - position: 1, - depth: 2, - depth_role: 0, - }, - condensation: { - enabled: true, - layerStart: 1, - layerEnd: 10, - messageTypes: { user: true, ai: true, hidden: false }, - tagExtractionEnabled: false, - tags: '摘要', - exclusionRules: [], - }, - rerank: { - enabled: false, - url: 'https://api.siliconflow.cn/v1', - apiKey: '', - model: 'Pro/BAAI/bge-reranker-v2-m3', - top_n: 5, - hybrid_alpha: 0.7, - notify: true, - superSortEnabled: false, - priorityRetrieval: { - enabled: false, - sources: { - novel: { - enabled: false, - count: 5 - }, - chat_history: { - enabled: false, - count: 5 - }, - lorebook: { - enabled: false, - count: 5 - }, - manual: { - enabled: false, - count: 5 - } - } - }, - }, - knowledgeBases: {}, -}; + +'use strict'; + +export const defaultSettings = { + retrieval: { + enabled: false, + apiEndpoint: 'openai', + customApiUrl: 'https://api.siliconflow.cn/v1', + apiKey: '', + embeddingModel: 'text-embedding-3-small', + notify: true, + batchSize: 50, + independentChatMemoryEnabled: false, + }, + advanced: { + chunkSize: 768, + overlap: 50, + matchThreshold: 0.5, + queryMessageCount: 2, + maxResults: 10, + }, + injection_novel: { + template: '以下内容是翰林院向量化后注入的原著小说剧情,但可能顺序会有些错乱,已经对前后做出了标识,请自行判断顺序:\n\n{{novel_text}}\n\n【以上内容是小说的原著剧情,切莫以此作为剧情进展,只是作为剧情的关联】', + position: 1, + depth: 2, + depth_role: 0, + }, + injection_chat: { + template: '以下内容是翰林院向量化后注入的聊天对话记录,但可能顺序会有些错乱,已经对前后做出了标识,请自行判断顺序:\n\n{{chat_text}}\n\n【以上内容是对话的楼层记录,切莫以此作为剧情进展,只是作为相关提示】', + position: 1, + depth: 2, + depth_role: 0, + }, + injection_lorebook: { + template: '以下内容是翰林院向量化后注入的世界书的条目内容(可能内含对话记录的总结),顺序可能会有些错乱,但已经对前后做出了标识,请自行判断顺序:\n\n{{lorebook_text}}\n\n【以上内容是从世界书中向量化后的内容,切莫以此作为剧情进展,只是作为已发生过的事情提醒】', + position: 1, + depth: 2, + depth_role: 0, + }, + injection_manual: { + template: '以下内容是翰林院向量化后用户手动注入的内容,可能顺序会有些错乱,但已经对前后做出了标识,请自行判断顺序:\n\n{{manual_text}}\n\n【以上内容为用户手动向量化注入的内容,切莫以此作为剧情进展,只是作为相关提示】', + position: 1, + depth: 2, + depth_role: 0, + }, + condensation: { + enabled: true, + autoCondense: false, + preserveFloors: 10, + layerStart: 1, + layerEnd: 10, + messageTypes: { user: true, ai: true, hidden: false }, + tagExtractionEnabled: false, + tags: '摘要', + exclusionRules: [], + }, + archive: { + enabled: false, + threshold: 20, + batchSize: 10, + targetTable: '总结表' + }, + relationshipGraph: { + enabled: false, + }, + rerank: { + enabled: false, + url: 'https://api.siliconflow.cn/v1', + apiKey: '', + model: 'Pro/BAAI/bge-reranker-v2-m3', + top_n: 5, + hybrid_alpha: 0.7, + notify: true, + superSortEnabled: false, + priorityRetrieval: { + enabled: false, + sources: { + novel: { + enabled: false, + count: 5 + }, + chat_history: { + enabled: false, + count: 5 + }, + lorebook: { + enabled: false, + count: 5 + }, + manual: { + enabled: false, + count: 5 + } + } + }, + }, + knowledgeBases: {}, +};