mirror of
https://github.com/SilenceLurker/ST-Amily2-Chat-Optimisation.git
synced 2026-06-06 20:35:51 +00:00
Add files via upload
This commit is contained in:
126
core/archive-manager.js
Normal file
126
core/archive-manager.js
Normal file
@@ -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);
|
||||||
|
}
|
||||||
229
core/fractal-memory.js
Normal file
229
core/fractal-memory.js
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,87 +1,98 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
export const defaultSettings = {
|
export const defaultSettings = {
|
||||||
retrieval: {
|
retrieval: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
apiEndpoint: 'openai',
|
apiEndpoint: 'openai',
|
||||||
customApiUrl: 'https://api.siliconflow.cn/v1',
|
customApiUrl: 'https://api.siliconflow.cn/v1',
|
||||||
apiKey: '',
|
apiKey: '',
|
||||||
embeddingModel: 'text-embedding-3-small',
|
embeddingModel: 'text-embedding-3-small',
|
||||||
notify: true,
|
notify: true,
|
||||||
batchSize: 50,
|
batchSize: 50,
|
||||||
independentChatMemoryEnabled: false,
|
independentChatMemoryEnabled: false,
|
||||||
},
|
},
|
||||||
advanced: {
|
advanced: {
|
||||||
chunkSize: 768,
|
chunkSize: 768,
|
||||||
overlap: 50,
|
overlap: 50,
|
||||||
matchThreshold: 0.5,
|
matchThreshold: 0.5,
|
||||||
queryMessageCount: 2,
|
queryMessageCount: 2,
|
||||||
maxResults: 10,
|
maxResults: 10,
|
||||||
},
|
},
|
||||||
injection_novel: {
|
injection_novel: {
|
||||||
template: '以下内容是翰林院向量化后注入的原著小说剧情,但可能顺序会有些错乱,已经对前后做出了标识,请自行判断顺序:\n\n{{novel_text}}\n\n【以上内容是小说的原著剧情,切莫以此作为剧情进展,只是作为剧情的关联】',
|
template: '以下内容是翰林院向量化后注入的原著小说剧情,但可能顺序会有些错乱,已经对前后做出了标识,请自行判断顺序:\n\n{{novel_text}}\n\n【以上内容是小说的原著剧情,切莫以此作为剧情进展,只是作为剧情的关联】',
|
||||||
position: 1,
|
position: 1,
|
||||||
depth: 2,
|
depth: 2,
|
||||||
depth_role: 0,
|
depth_role: 0,
|
||||||
},
|
},
|
||||||
injection_chat: {
|
injection_chat: {
|
||||||
template: '以下内容是翰林院向量化后注入的聊天对话记录,但可能顺序会有些错乱,已经对前后做出了标识,请自行判断顺序:\n\n{{chat_text}}\n\n【以上内容是对话的楼层记录,切莫以此作为剧情进展,只是作为相关提示】',
|
template: '以下内容是翰林院向量化后注入的聊天对话记录,但可能顺序会有些错乱,已经对前后做出了标识,请自行判断顺序:\n\n{{chat_text}}\n\n【以上内容是对话的楼层记录,切莫以此作为剧情进展,只是作为相关提示】',
|
||||||
position: 1,
|
position: 1,
|
||||||
depth: 2,
|
depth: 2,
|
||||||
depth_role: 0,
|
depth_role: 0,
|
||||||
},
|
},
|
||||||
injection_lorebook: {
|
injection_lorebook: {
|
||||||
template: '以下内容是翰林院向量化后注入的世界书的条目内容(可能内含对话记录的总结),顺序可能会有些错乱,但已经对前后做出了标识,请自行判断顺序:\n\n{{lorebook_text}}\n\n【以上内容是从世界书中向量化后的内容,切莫以此作为剧情进展,只是作为已发生过的事情提醒】',
|
template: '以下内容是翰林院向量化后注入的世界书的条目内容(可能内含对话记录的总结),顺序可能会有些错乱,但已经对前后做出了标识,请自行判断顺序:\n\n{{lorebook_text}}\n\n【以上内容是从世界书中向量化后的内容,切莫以此作为剧情进展,只是作为已发生过的事情提醒】',
|
||||||
position: 1,
|
position: 1,
|
||||||
depth: 2,
|
depth: 2,
|
||||||
depth_role: 0,
|
depth_role: 0,
|
||||||
},
|
},
|
||||||
injection_manual: {
|
injection_manual: {
|
||||||
template: '以下内容是翰林院向量化后用户手动注入的内容,可能顺序会有些错乱,但已经对前后做出了标识,请自行判断顺序:\n\n{{manual_text}}\n\n【以上内容为用户手动向量化注入的内容,切莫以此作为剧情进展,只是作为相关提示】',
|
template: '以下内容是翰林院向量化后用户手动注入的内容,可能顺序会有些错乱,但已经对前后做出了标识,请自行判断顺序:\n\n{{manual_text}}\n\n【以上内容为用户手动向量化注入的内容,切莫以此作为剧情进展,只是作为相关提示】',
|
||||||
position: 1,
|
position: 1,
|
||||||
depth: 2,
|
depth: 2,
|
||||||
depth_role: 0,
|
depth_role: 0,
|
||||||
},
|
},
|
||||||
condensation: {
|
condensation: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
layerStart: 1,
|
autoCondense: false,
|
||||||
layerEnd: 10,
|
preserveFloors: 10,
|
||||||
messageTypes: { user: true, ai: true, hidden: false },
|
layerStart: 1,
|
||||||
tagExtractionEnabled: false,
|
layerEnd: 10,
|
||||||
tags: '摘要',
|
messageTypes: { user: true, ai: true, hidden: false },
|
||||||
exclusionRules: [],
|
tagExtractionEnabled: false,
|
||||||
},
|
tags: '摘要',
|
||||||
rerank: {
|
exclusionRules: [],
|
||||||
enabled: false,
|
},
|
||||||
url: 'https://api.siliconflow.cn/v1',
|
archive: {
|
||||||
apiKey: '',
|
enabled: false,
|
||||||
model: 'Pro/BAAI/bge-reranker-v2-m3',
|
threshold: 20,
|
||||||
top_n: 5,
|
batchSize: 10,
|
||||||
hybrid_alpha: 0.7,
|
targetTable: '总结表'
|
||||||
notify: true,
|
},
|
||||||
superSortEnabled: false,
|
relationshipGraph: {
|
||||||
priorityRetrieval: {
|
enabled: false,
|
||||||
enabled: false,
|
},
|
||||||
sources: {
|
rerank: {
|
||||||
novel: {
|
enabled: false,
|
||||||
enabled: false,
|
url: 'https://api.siliconflow.cn/v1',
|
||||||
count: 5
|
apiKey: '',
|
||||||
},
|
model: 'Pro/BAAI/bge-reranker-v2-m3',
|
||||||
chat_history: {
|
top_n: 5,
|
||||||
enabled: false,
|
hybrid_alpha: 0.7,
|
||||||
count: 5
|
notify: true,
|
||||||
},
|
superSortEnabled: false,
|
||||||
lorebook: {
|
priorityRetrieval: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
count: 5
|
sources: {
|
||||||
},
|
novel: {
|
||||||
manual: {
|
enabled: false,
|
||||||
enabled: false,
|
count: 5
|
||||||
count: 5
|
},
|
||||||
}
|
chat_history: {
|
||||||
}
|
enabled: false,
|
||||||
},
|
count: 5
|
||||||
},
|
},
|
||||||
knowledgeBases: {},
|
lorebook: {
|
||||||
};
|
enabled: false,
|
||||||
|
count: 5
|
||||||
|
},
|
||||||
|
manual: {
|
||||||
|
enabled: false,
|
||||||
|
count: 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
knowledgeBases: {},
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user