Files
ST-Bionic-Memory-Ecology/index.js
2026-03-23 13:37:24 +08:00

956 lines
26 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ST-BME: 主入口
// 事件钩子、设置管理、流程调度
import {
eventSource,
event_types,
saveSettingsDebounced,
} from "../../../script.js";
import {
extension_settings,
getContext,
renderExtensionTemplateAsync,
saveMetadataDebounced,
} from "../../extensions.js";
import { compressAll, sleepCycle } from "./compressor.js";
import { testConnection as testEmbeddingConnection } from "./embedding.js";
import { evolveMemories } from "./evolution.js";
import {
extractMemories,
generateReflection,
generateSynopsis,
} from "./extractor.js";
import {
createEmptyGraph,
deserializeGraph,
exportGraph,
getGraphStats,
importGraph,
} from "./graph.js";
import { estimateTokens, formatInjection } from "./injector.js";
import { retrieve } from "./retriever.js";
import { DEFAULT_NODE_SCHEMA, validateSchema } from "./schema.js";
const MODULE_NAME = "st_bme";
const GRAPH_METADATA_KEY = "st_bme_graph";
// ==================== 默认设置 ====================
const defaultSettings = {
enabled: false,
// 提取设置
extractEvery: 1, // 每 N 条 assistant 回复提取一次
extractContextTurns: 2, // 提取时包含的上下文楼层数
// 召回设置
recallEnabled: true,
recallTopK: 15, // 混合评分 Top-K
recallMaxNodes: 8, // LLM 召回最大节点数
recallEnableLLM: true, // 是否启用 LLM 精确召回
// 注入设置
injectPosition: "atDepth", // 注入位置
injectDepth: 4, // 注入深度atDepth 模式)
injectRole: 0, // 0=system, 1=user, 2=assistant
// 混合评分权重
graphWeight: 0.6,
vectorWeight: 0.3,
importanceWeight: 0.1,
// Embedding API 配置
embeddingApiUrl: "",
embeddingApiKey: "",
embeddingModel: "text-embedding-3-small",
// Schema
nodeTypeSchema: null, // null 表示使用默认
// 自定义提示词
extractPrompt: "",
// ====== v2 增强设置 ======
// ③ A-MEM 记忆进化
enableEvolution: true, // 启用记忆进化
evoNeighborCount: 5, // 近邻搜索数量
evoConsolidateEvery: 50, // 每 N 次进化后整理
// ② Mem0 精确对照
enablePreciseConflict: true, // 启用精确对照
conflictThreshold: 0.85, // 相似度阈值
// ⑨ 全局故事概要
enableSynopsis: true, // 启用全局概要
synopsisEveryN: 5, // 每 N 次提取后更新概要
// ⑥ 认知边界过滤P1
enableVisibility: false, // 启用认知边界
// ⑦ 双记忆交叉检索P1
enableCrossRecall: false, // 启用交叉检索
// ① 惊奇度分割P2
enableSmartTrigger: false, // 启用惊奇度分割
triggerPatterns: "", // 自定义触发正则
smartTriggerThreshold: 2, // 轻量触发阈值
// ⑤ 主动遗忘P2
enableSleepCycle: false, // 启用主动遗忘
forgetThreshold: 0.5, // 保留价值阈值
sleepEveryN: 10, // 每 N 次提取后执行
// ⑧ 概率触发回忆P2
enableProbRecall: false, // 启用概率触发
probRecallChance: 0.15, // 触发概率
// ⑩ 反思条目P2
enableReflection: false, // 启用反思
reflectEveryN: 10, // 每 N 次提取后反思
};
// ==================== 状态 ====================
let currentGraph = null;
let isExtracting = false;
let isRecalling = false;
let lastInjectionContent = "";
let extractionCount = 0; // v2: 提取次数计数器(定期触发概要/遗忘/反思)
// ==================== 设置管理 ====================
function getSettings() {
const mergedSettings = {
...defaultSettings,
...(extension_settings[MODULE_NAME] || {}),
};
extension_settings[MODULE_NAME] = mergedSettings;
return mergedSettings;
}
function getSchema() {
const settings = getSettings();
const schema = settings.nodeTypeSchema || DEFAULT_NODE_SCHEMA;
const validation = validateSchema(schema);
if (!validation.valid) {
console.warn("[ST-BME] Schema 非法,回退到默认 Schema:", validation.errors);
return DEFAULT_NODE_SCHEMA;
}
return schema;
}
function getEmbeddingConfig() {
const settings = getSettings();
return {
apiUrl: settings.embeddingApiUrl,
apiKey: settings.embeddingApiKey,
model: settings.embeddingModel,
};
}
// ==================== 图状态持久化 ====================
function loadGraphFromChat() {
const context = getContext();
if (!context.chatMetadata) {
currentGraph = createEmptyGraph();
return;
}
const savedData = context.chatMetadata[GRAPH_METADATA_KEY];
if (savedData) {
currentGraph = deserializeGraph(savedData);
console.log("[ST-BME] 从聊天数据加载图谱:", getGraphStats(currentGraph));
} else {
currentGraph = createEmptyGraph();
}
}
function saveGraphToChat() {
const context = getContext();
if (!context.chatMetadata || !currentGraph) return;
context.chatMetadata[GRAPH_METADATA_KEY] = currentGraph;
saveMetadataDebounced();
}
// ==================== 核心流程 ====================
const DEFAULT_TRIGGER_KEYWORDS = [
"突然",
"没想到",
"原来",
"其实",
"发现",
"背叛",
"死亡",
"复活",
"恢复记忆",
"失忆",
"告白",
"暴露",
"秘密",
"计划",
"规则",
"契约",
"位置",
"地点",
"离开",
"来到",
];
export function getSmartTriggerDecision(chat, lastProcessed, settings) {
const pendingMessages = chat
.slice(Math.max(0, (lastProcessed ?? -1) + 1))
.filter((msg) => !msg.is_system)
.map((msg) => ({
role: msg.is_user ? "user" : "assistant",
content: msg.mes || "",
}))
.filter((msg) => msg.content.trim().length > 0);
if (pendingMessages.length === 0) {
return { triggered: false, score: 0, reasons: [] };
}
const reasons = [];
let score = 0;
const combinedText = pendingMessages.map((m) => m.content).join("\n");
const keywordHits = DEFAULT_TRIGGER_KEYWORDS.filter((keyword) =>
combinedText.includes(keyword),
);
if (keywordHits.length > 0) {
score += Math.min(2, keywordHits.length);
reasons.push(`关键词: ${keywordHits.slice(0, 3).join(", ")}`);
}
const customPatterns = String(settings.triggerPatterns || "")
.split(/\r?\n|,/)
.map((s) => s.trim())
.filter(Boolean);
for (const pattern of customPatterns) {
try {
const regex = new RegExp(pattern, "i");
if (regex.test(combinedText)) {
score += 2;
reasons.push(`自定义触发: ${pattern}`);
break;
}
} catch {
// 忽略无效正则,避免影响主流程
}
}
const roleSwitchCount = pendingMessages.reduce((count, message, index) => {
if (index === 0) return count;
return count + (message.role !== pendingMessages[index - 1].role ? 1 : 0);
}, 0);
if (roleSwitchCount >= 2) {
score += 1;
reasons.push("多轮往返互动");
}
const punctuationHits = (combinedText.match(/[!?]/g) || []).length;
if (punctuationHits >= 2) {
score += 1;
reasons.push("情绪/冲突波动");
}
const entityLikeHits =
combinedText.match(
/[A-Z][a-z]{2,}|[\u4e00-\u9fff]{2,6}(先生|小姐|王国|城|镇|村|学院|组织|公司|小队|军团)/g,
) || [];
if (entityLikeHits.length > 0) {
score += 1;
reasons.push("疑似新实体/新地点");
}
const threshold = Math.max(1, settings.smartTriggerThreshold || 2);
return {
triggered: score >= threshold,
score,
reasons,
};
}
function clampInt(value, fallback, min = 0, max = Number.MAX_SAFE_INTEGER) {
const num = Number.parseInt(value, 10);
if (!Number.isFinite(num)) return fallback;
return Math.min(max, Math.max(min, num));
}
function clampFloat(value, fallback, min = 0, max = 1) {
const num = Number.parseFloat(value);
if (!Number.isFinite(num)) return fallback;
return Math.min(max, Math.max(min, num));
}
/**
* 提取管线:处理未提取的对话楼层
*/
async function runExtraction() {
if (isExtracting || !currentGraph) return;
const settings = getSettings();
if (!settings.enabled) return;
const context = getContext();
const chat = context.chat;
if (!chat || chat.length === 0) return;
// lastProcessedSeq / startSeq / endSeq 统一使用 chat 数组索引语义
const assistantTurns = [];
for (let i = 0; i < chat.length; i++) {
if (chat[i].is_user === false && !chat[i].is_system) {
assistantTurns.push(i);
}
}
const lastProcessed = Number.isFinite(currentGraph.lastProcessedSeq)
? currentGraph.lastProcessedSeq
: -1;
const unprocessedAssistantTurns = assistantTurns.filter(
(i) => i > lastProcessed,
);
if (unprocessedAssistantTurns.length === 0) return;
const extractEvery = clampInt(settings.extractEvery, 1, 1, 50);
const smartTriggerDecision = settings.enableSmartTrigger
? getSmartTriggerDecision(chat, lastProcessed, settings)
: { triggered: false, score: 0, reasons: [] };
if (
unprocessedAssistantTurns.length < extractEvery &&
!smartTriggerDecision.triggered
) {
return;
}
const batchAssistantTurns = smartTriggerDecision.triggered
? unprocessedAssistantTurns
: unprocessedAssistantTurns.slice(0, extractEvery);
const startIdx = batchAssistantTurns[0];
const endIdx = batchAssistantTurns[batchAssistantTurns.length - 1];
isExtracting = true;
try {
const contextTurns = clampInt(settings.extractContextTurns, 2, 0, 20);
const contextStart = Math.max(0, startIdx - contextTurns * 2);
const messages = [];
for (let i = contextStart; i <= endIdx && i < chat.length; i++) {
const msg = chat[i];
if (msg.is_system) continue;
messages.push({
seq: i,
role: msg.is_user ? "user" : "assistant",
content: msg.mes || "",
});
}
console.log(
`[ST-BME] 开始提取: 楼层 ${startIdx}-${endIdx}` +
(smartTriggerDecision.triggered
? ` [智能触发 score=${smartTriggerDecision.score}; ${smartTriggerDecision.reasons.join(" / ")}]`
: ""),
);
const result = await extractMemories({
graph: currentGraph,
messages,
startSeq: startIdx,
endSeq: endIdx,
lastProcessedSeq: lastProcessed,
schema: getSchema(),
embeddingConfig: getEmbeddingConfig(),
extractPrompt: settings.extractPrompt || undefined,
v2Options: {
enablePreciseConflict: settings.enablePreciseConflict,
conflictThreshold: settings.conflictThreshold,
},
});
if (result.success) {
extractionCount++;
// v2: A-MEM 记忆进化
if (settings.enableEvolution && result.newNodeIds?.length > 0) {
try {
await evolveMemories({
graph: currentGraph,
newNodeIds: result.newNodeIds,
embeddingConfig: getEmbeddingConfig(),
options: { neighborCount: settings.evoNeighborCount },
});
} catch (e) {
console.error("[ST-BME] 记忆进化失败:", e);
}
}
// v2: 全局故事概要(每 N 次提取更新一次)
if (
settings.enableSynopsis &&
extractionCount % settings.synopsisEveryN === 0
) {
try {
await generateSynopsis({
graph: currentGraph,
schema: getSchema(),
currentSeq: endIdx,
});
} catch (e) {
console.error("[ST-BME] 概要生成失败:", e);
}
}
// v2: 反思条目(每 N 次提取生成一次)
if (
settings.enableReflection &&
extractionCount % settings.reflectEveryN === 0
) {
try {
await generateReflection({
graph: currentGraph,
currentSeq: endIdx,
});
} catch (e) {
console.error("[ST-BME] 反思生成失败:", e);
}
}
// v2: 主动遗忘(每 N 次提取执行)
if (
settings.enableSleepCycle &&
extractionCount % settings.sleepEveryN === 0
) {
try {
sleepCycle(currentGraph, settings);
} catch (e) {
console.error("[ST-BME] 主动遗忘失败:", e);
}
}
// 压缩检查
await compressAll(currentGraph, getSchema(), getEmbeddingConfig());
saveGraphToChat();
}
} catch (e) {
console.error("[ST-BME] 提取失败:", e);
} finally {
isExtracting = false;
}
}
/**
* 召回管线:检索并注入记忆
*/
async function runRecall() {
if (isRecalling || !currentGraph) return;
const settings = getSettings();
if (!settings.enabled || !settings.recallEnabled) return;
const context = getContext();
const chat = context.chat;
if (!chat || chat.length === 0) return;
isRecalling = true;
try {
// 获取最新用户消息
let userMessage = "";
const recentMessages = [];
for (let i = chat.length - 1; i >= 0 && recentMessages.length < 4; i--) {
const msg = chat[i];
if (msg.is_system) continue;
if (msg.is_user && !userMessage) {
userMessage = msg.mes || "";
}
recentMessages.unshift(
`[${msg.is_user ? "user" : "assistant"}]: ${msg.mes || ""}`,
);
}
if (!userMessage) return;
console.log("[ST-BME] 开始召回");
const result = await retrieve({
graph: currentGraph,
userMessage,
recentMessages,
embeddingConfig: getEmbeddingConfig(),
schema: getSchema(),
options: {
topK: settings.recallTopK,
maxRecallNodes: settings.recallMaxNodes,
enableLLMRecall: settings.recallEnableLLM,
weights: {
graphWeight: settings.graphWeight,
vectorWeight: settings.vectorWeight,
importanceWeight: settings.importanceWeight,
},
// v2 options
enableVisibility: settings.enableVisibility ?? false,
visibilityFilter: context.name2 || null,
enableCrossRecall: settings.enableCrossRecall ?? false,
enableProbRecall: settings.enableProbRecall ?? false,
probRecallChance: settings.probRecallChance ?? 0.15,
},
});
// 格式化注入文本
const injectionText = formatInjection(result, getSchema()).trim();
lastInjectionContent = injectionText;
if (injectionText) {
const tokens = estimateTokens(injectionText);
console.log(
`[ST-BME] 注入 ${tokens} 估算 tokens, Core=${result.stats.coreCount}, Recall=${result.stats.recallCount}`,
);
}
// 无结果时也要清空旧注入,避免脏 prompt 残留
context.setExtensionPrompt(
MODULE_NAME,
injectionText,
1, // extension_prompt_types.IN_PROMPT
clampInt(settings.injectDepth, 4, 0, 9999),
);
// 保存召回结果和访问强化
currentGraph.lastRecallResult = result.selectedNodeIds;
saveGraphToChat();
} catch (e) {
console.error("[ST-BME] 召回失败:", e);
} finally {
isRecalling = false;
}
}
// ==================== 事件钩子 ====================
function onChatChanged() {
loadGraphFromChat();
lastInjectionContent = "";
}
async function onGenerationAfterCommands() {
await runExtraction();
}
async function onBeforeCombinePrompts() {
await runRecall();
}
function onMessageReceived() {
// 新消息到达,图状态可能需要更新
if (currentGraph) {
saveGraphToChat();
}
}
// ==================== UI 操作 ====================
async function onViewGraph() {
if (!currentGraph) {
toastr.warning("当前没有加载的图谱");
return;
}
const stats = getGraphStats(currentGraph);
const statsText = [
`节点: ${stats.activeNodes} 活跃 / ${stats.archivedNodes} 归档`,
`边: ${stats.totalEdges}`,
`最后处理楼层: ${stats.lastProcessedSeq}`,
`类型分布: ${
Object.entries(stats.typeCounts)
.map(([k, v]) => `${k}=${v}`)
.join(", ") || "(空)"
}`,
].join("\n");
toastr.info(statsText, "ST-BME 图谱状态", { timeOut: 10000 });
}
async function onRebuild() {
if (!confirm("确定要从当前聊天重建图谱?这将清除现有图谱数据。")) return;
currentGraph = createEmptyGraph();
saveGraphToChat();
toastr.info("图谱已重置,将在下次生成时重新提取");
}
async function onManualCompress() {
if (!currentGraph) return;
const result = await compressAll(
currentGraph,
getSchema(),
getEmbeddingConfig(),
false,
);
saveGraphToChat();
toastr.info(`压缩完成: 新建 ${result.created}, 归档 ${result.archived}`);
}
async function onExportGraph() {
if (!currentGraph) return;
const json = exportGraph(currentGraph);
const blob = new Blob([json], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `st-bme-graph-${Date.now()}.json`;
a.click();
URL.revokeObjectURL(url);
toastr.success("图谱已导出");
}
async function onImportGraph() {
const input = document.createElement("input");
input.type = "file";
input.accept = ".json";
input.onchange = async (e) => {
const file = e.target.files[0];
if (!file) return;
try {
const text = await file.text();
currentGraph = importGraph(text);
saveGraphToChat();
toastr.success("图谱已导入");
} catch (err) {
toastr.error(`导入失败: ${err.message}`);
}
};
input.click();
}
async function onViewLastInjection() {
if (!lastInjectionContent) {
toastr.info("暂无注入内容");
return;
}
// 简单弹窗显示
const popup = document.createElement("div");
popup.style.cssText =
"position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:#1a1a2e;color:#eee;padding:24px;border-radius:12px;max-width:80vw;max-height:80vh;overflow:auto;z-index:99999;white-space:pre-wrap;font-size:13px;box-shadow:0 8px 32px rgba(0,0,0,0.5);";
popup.textContent = lastInjectionContent;
const close = document.createElement("button");
close.textContent = "关闭";
close.style.cssText =
"position:absolute;top:8px;right:12px;background:#e94560;color:white;border:none;padding:4px 12px;border-radius:4px;cursor:pointer;";
close.onclick = () => popup.remove();
popup.appendChild(close);
document.body.appendChild(popup);
}
async function onTestEmbedding() {
const config = getEmbeddingConfig();
if (!config.apiUrl || !config.apiKey) {
toastr.warning("请先配置 Embedding API 地址和 Key");
return;
}
toastr.info("正在测试 Embedding API 连通性...");
const result = await testEmbeddingConnection(config);
if (result.success) {
toastr.success(`连接成功!向量维度: ${result.dimensions}`);
} else {
toastr.error(`连接失败: ${result.error}`);
}
}
// ==================== 设置 UI ====================
function bindSettingsUI() {
const settings = getSettings();
// 开关
$("#st_bme_enabled")
.prop("checked", settings.enabled)
.on("change", function () {
settings.enabled = $(this).prop("checked");
saveSettingsDebounced();
});
// 提取频率
$("#st_bme_extract_every")
.val(settings.extractEvery)
.on("input", function () {
settings.extractEvery = clampInt($(this).val(), 1, 1, 50);
saveSettingsDebounced();
});
$("#st_bme_extract_context_turns")
.val(settings.extractContextTurns)
.on("input", function () {
settings.extractContextTurns = clampInt($(this).val(), 2, 0, 20);
saveSettingsDebounced();
});
// 召回开关
$("#st_bme_recall_enabled")
.prop("checked", settings.recallEnabled)
.on("change", function () {
settings.recallEnabled = $(this).prop("checked");
saveSettingsDebounced();
});
// LLM 精确召回
$("#st_bme_recall_llm")
.prop("checked", settings.recallEnableLLM)
.on("change", function () {
settings.recallEnableLLM = $(this).prop("checked");
saveSettingsDebounced();
});
$("#st_bme_recall_top_k")
.val(settings.recallTopK)
.on("input", function () {
settings.recallTopK = clampInt($(this).val(), 15, 1, 100);
saveSettingsDebounced();
});
$("#st_bme_recall_max_nodes")
.val(settings.recallMaxNodes)
.on("input", function () {
settings.recallMaxNodes = clampInt($(this).val(), 8, 1, 50);
saveSettingsDebounced();
});
// 注入深度
$("#st_bme_inject_depth")
.val(settings.injectDepth)
.on("input", function () {
settings.injectDepth = clampInt($(this).val(), 4, 0, 9999);
saveSettingsDebounced();
});
// 评分权重
$("#st_bme_graph_weight")
.val(settings.graphWeight)
.on("input", function () {
settings.graphWeight = clampFloat($(this).val(), 0.6, 0, 1);
saveSettingsDebounced();
});
$("#st_bme_vector_weight")
.val(settings.vectorWeight)
.on("input", function () {
settings.vectorWeight = clampFloat($(this).val(), 0.3, 0, 1);
saveSettingsDebounced();
});
$("#st_bme_importance_weight")
.val(settings.importanceWeight)
.on("input", function () {
settings.importanceWeight = clampFloat($(this).val(), 0.1, 0, 1);
saveSettingsDebounced();
});
// Embedding API
$("#st_bme_embed_url")
.val(settings.embeddingApiUrl)
.on("input", function () {
settings.embeddingApiUrl = $(this).val().trim();
saveSettingsDebounced();
});
$("#st_bme_embed_key")
.val(settings.embeddingApiKey)
.on("input", function () {
settings.embeddingApiKey = $(this).val().trim();
saveSettingsDebounced();
});
$("#st_bme_embed_model")
.val(settings.embeddingModel)
.on("input", function () {
settings.embeddingModel = $(this).val().trim();
saveSettingsDebounced();
});
// 操作按钮
$("#st_bme_btn_view_graph").on("click", onViewGraph);
$("#st_bme_btn_rebuild").on("click", onRebuild);
$("#st_bme_btn_compress").on("click", onManualCompress);
$("#st_bme_btn_export").on("click", onExportGraph);
$("#st_bme_btn_import").on("click", onImportGraph);
$("#st_bme_btn_view_injection").on("click", onViewLastInjection);
$("#st_bme_btn_test_embed").on("click", onTestEmbedding);
// ====== v2 增强设置 UI 绑定 ======
// P0: 记忆进化
$("#st_bme_evolution")
.prop("checked", settings.enableEvolution)
.on("change", function () {
settings.enableEvolution = $(this).prop("checked");
saveSettingsDebounced();
});
$("#st_bme_evo_neighbors")
.val(settings.evoNeighborCount)
.on("input", function () {
settings.evoNeighborCount = clampInt($(this).val(), 5, 1, 20);
saveSettingsDebounced();
});
$("#st_bme_evo_consolidate_every")
.val(settings.evoConsolidateEvery)
.on("input", function () {
settings.evoConsolidateEvery = clampInt($(this).val(), 50, 1, 500);
saveSettingsDebounced();
});
// P0: 精确对照
$("#st_bme_precise_conflict")
.prop("checked", settings.enablePreciseConflict)
.on("change", function () {
settings.enablePreciseConflict = $(this).prop("checked");
saveSettingsDebounced();
});
$("#st_bme_conflict_threshold")
.val(settings.conflictThreshold)
.on("input", function () {
settings.conflictThreshold = clampFloat($(this).val(), 0.85, 0.5, 0.99);
saveSettingsDebounced();
});
// P0: 全局概要
$("#st_bme_synopsis")
.prop("checked", settings.enableSynopsis)
.on("change", function () {
settings.enableSynopsis = $(this).prop("checked");
saveSettingsDebounced();
});
$("#st_bme_synopsis_every")
.val(settings.synopsisEveryN)
.on("input", function () {
settings.synopsisEveryN = clampInt($(this).val(), 5, 1, 100);
saveSettingsDebounced();
});
// P1: 认知边界
$("#st_bme_visibility")
.prop("checked", settings.enableVisibility ?? false)
.on("change", function () {
settings.enableVisibility = $(this).prop("checked");
saveSettingsDebounced();
});
// P1: 交叉检索
$("#st_bme_cross_recall")
.prop("checked", settings.enableCrossRecall ?? false)
.on("change", function () {
settings.enableCrossRecall = $(this).prop("checked");
saveSettingsDebounced();
});
// P2: 惊奇度分割
$("#st_bme_smart_trigger")
.prop("checked", settings.enableSmartTrigger)
.on("change", function () {
settings.enableSmartTrigger = $(this).prop("checked");
saveSettingsDebounced();
});
$("#st_bme_trigger_patterns")
.val(settings.triggerPatterns || "")
.on("input", function () {
settings.triggerPatterns = $(this).val();
saveSettingsDebounced();
});
$("#st_bme_smart_trigger_threshold")
.val(settings.smartTriggerThreshold)
.on("input", function () {
settings.smartTriggerThreshold = clampInt($(this).val(), 2, 1, 10);
saveSettingsDebounced();
});
// P2: 主动遗忘
$("#st_bme_sleep_cycle")
.prop("checked", settings.enableSleepCycle)
.on("change", function () {
settings.enableSleepCycle = $(this).prop("checked");
saveSettingsDebounced();
});
$("#st_bme_forget_threshold")
.val(settings.forgetThreshold)
.on("input", function () {
settings.forgetThreshold = clampFloat($(this).val(), 0.5, 0.1, 1);
saveSettingsDebounced();
});
$("#st_bme_sleep_every")
.val(settings.sleepEveryN)
.on("input", function () {
settings.sleepEveryN = clampInt($(this).val(), 10, 1, 200);
saveSettingsDebounced();
});
// P2: 概率触发
$("#st_bme_prob_recall")
.prop("checked", settings.enableProbRecall)
.on("change", function () {
settings.enableProbRecall = $(this).prop("checked");
saveSettingsDebounced();
});
$("#st_bme_prob_chance")
.val(settings.probRecallChance)
.on("input", function () {
settings.probRecallChance = clampFloat($(this).val(), 0.15, 0.01, 0.5);
saveSettingsDebounced();
});
// P2: 反思条目
$("#st_bme_reflection")
.prop("checked", settings.enableReflection)
.on("change", function () {
settings.enableReflection = $(this).prop("checked");
saveSettingsDebounced();
});
$("#st_bme_reflect_every")
.val(settings.reflectEveryN)
.on("input", function () {
settings.reflectEveryN = clampInt($(this).val(), 10, 1, 200);
saveSettingsDebounced();
});
}
// ==================== 初始化 ====================
(async function init() {
// 加载设置面板 HTML
const settingsHtml = await renderExtensionTemplateAsync(
"third-party/st-bme",
"settings",
);
$("#extensions_settings2").append(settingsHtml);
// 绑定 UI
bindSettingsUI();
// 注册事件钩子
eventSource.on(event_types.CHAT_CHANGED, onChatChanged);
eventSource.on(
event_types.GENERATION_AFTER_COMMANDS,
onGenerationAfterCommands,
);
eventSource.on(
event_types.GENERATE_BEFORE_COMBINE_PROMPTS,
onBeforeCombinePrompts,
);
eventSource.on(event_types.MESSAGE_RECEIVED, onMessageReceived);
// 加载当前聊天的图谱
loadGraphFromChat();
console.log("[ST-BME] 初始化完成");
})();