// ST-BME: 主入口 // 事件钩子、设置管理、流程调度 import { eventSource, event_types, getRequestHeaders, saveSettingsDebounced, } from "../../../../script.js"; import { extension_settings, getContext, saveMetadataDebounced, } from "../../../extensions.js"; import { compressAll, sleepCycle } from "./compressor.js"; import { evolveMemories } from "./evolution.js"; import { extractMemories, generateReflection, generateSynopsis, } from "./extractor.js"; import { createEmptyGraph, deserializeGraph, exportGraph, getGraphStats, importGraph, getNode, } from "./graph.js"; import { estimateTokens, formatInjection } from "./injector.js"; import { testLLMConnection } from "./llm.js"; import { retrieve } from "./retriever.js"; import { appendBatchJournal, buildRecoveryResult, clearHistoryDirty, cloneGraphSnapshot, createBatchJournalEntry, detectHistoryMutation, findJournalRecoveryPoint, markHistoryDirty, normalizeGraphRuntimeState, snapshotProcessedMessageHashes, } from "./runtime-state.js"; import { DEFAULT_NODE_SCHEMA, validateSchema } from "./schema.js"; import { BACKEND_VECTOR_SOURCES, getVectorConfigFromSettings, getVectorIndexStats, isBackendVectorConfig, isDirectVectorConfig, syncGraphVectorIndex, testVectorConnection, validateVectorConfig, } from "./vector-index.js"; // 操控面板模块(动态加载,防止加载失败崩溃整个扩展) let _panelModule = null; let _themesModule = null; const MODULE_NAME = "st_bme"; const GRAPH_METADATA_KEY = "st_bme_graph"; const SERVER_SETTINGS_FILENAME = "st-bme-settings.json"; const SERVER_SETTINGS_URL = `/user/files/${SERVER_SETTINGS_FILENAME}`; // ==================== 默认设置 ==================== 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, // 记忆 LLM(留空时复用当前酒馆模型) llmApiUrl: "", llmApiKey: "", llmModel: "", // Embedding API 配置 embeddingApiUrl: "", embeddingApiKey: "", embeddingModel: "text-embedding-3-small", embeddingTransportMode: "backend", embeddingBackendSource: "openai", embeddingBackendModel: "text-embedding-3-small", embeddingBackendApiUrl: "", embeddingAutoSuffix: true, // 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 次提取后反思 // UI 面板 panelTheme: "crimson", // 面板主题 crimson|cyan|amber|violet }; // ==================== 状态 ==================== let currentGraph = null; let isExtracting = false; let isRecalling = false; let lastInjectionContent = ""; let lastExtractedItems = []; // 最近提取的节点(面板展示用) let lastRecalledItems = []; // 最近召回的节点(面板展示用) let extractionCount = 0; // v2: 提取次数计数器(定期触发概要/遗忘/反思) let serverSettingsSaveTimer = null; let isRecoveringHistory = false; let lastHistoryWarningAt = 0; function getNodeDisplayName(node) { return ( node?.fields?.name || node?.fields?.title || node?.fields?.summary || node?.fields?.insight || node?.id || "—" ); } function toPanelNodeItem(node, meta = "") { return { id: node.id, type: node.type, name: getNodeDisplayName(node), meta, }; } function updateLastExtractedItems(nodeIds = []) { if (!currentGraph || !Array.isArray(nodeIds)) { lastExtractedItems = []; return; } lastExtractedItems = nodeIds .map((id) => getNode(currentGraph, id)) .filter(Boolean) .slice(-5) .reverse() .map((node) => toPanelNodeItem( node, `seq ${node.seqRange?.[1] ?? node.seq ?? 0} · ${new Date( node.createdTime || Date.now(), ).toLocaleTimeString()}`, ), ); } function updateLastRecalledItems(nodeIds = []) { if (!currentGraph || !Array.isArray(nodeIds)) { lastRecalledItems = []; return; } lastRecalledItems = nodeIds .map((id) => getNode(currentGraph, id)) .filter(Boolean) .slice(0, 8) .map((node) => toPanelNodeItem( node, `imp ${node.importance ?? 5} · seq ${node.seqRange?.[1] ?? node.seq ?? 0}`, ), ); } // ==================== 设置管理 ==================== 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() { return getVectorConfigFromSettings(getSettings()); } function getCurrentChatId(context = getContext()) { return String( context?.chatId || context?.getCurrentChatId?.() || "", ); } function ensureCurrentGraphRuntimeState() { if (!currentGraph) { currentGraph = createEmptyGraph(); } currentGraph = normalizeGraphRuntimeState(currentGraph, getCurrentChatId()); return currentGraph; } function clearInjectionState() { lastInjectionContent = ""; lastRecalledItems = []; try { const context = getContext(); context.setExtensionPrompt(MODULE_NAME, "", 1, 0); } catch (error) { console.warn("[ST-BME] 清理旧注入失败:", error); } } async function recordGraphMutation({ beforeSnapshot, processedRange = null, artifactTags = [], syncRange = null, } = {}) { ensureCurrentGraphRuntimeState(); const vectorSync = await syncVectorState({ force: true, purge: isBackendVectorConfig(getEmbeddingConfig()) && !syncRange, range: syncRange, }); const afterSnapshot = cloneGraphSnapshot(currentGraph); const effectiveRange = Array.isArray(processedRange) ? processedRange : [ getLastProcessedAssistantFloor(), getLastProcessedAssistantFloor(), ]; appendBatchJournal( currentGraph, createBatchJournalEntry(beforeSnapshot, afterSnapshot, { processedRange: effectiveRange, postProcessArtifacts: computePostProcessArtifacts( beforeSnapshot, afterSnapshot, artifactTags, ), vectorHashesInserted: vectorSync?.insertedHashes || [], }), ); saveGraphToChat(); return vectorSync; } function markVectorStateDirty(reason = "向量状态已标记为待重建") { if (!currentGraph) return; ensureCurrentGraphRuntimeState(); currentGraph.vectorIndexState.dirty = true; currentGraph.vectorIndexState.lastWarning = reason; } function updateProcessedHistorySnapshot(chat, lastProcessedAssistantFloor) { ensureCurrentGraphRuntimeState(); currentGraph.historyState.lastProcessedAssistantFloor = lastProcessedAssistantFloor; currentGraph.historyState.processedMessageHashes = snapshotProcessedMessageHashes( chat, lastProcessedAssistantFloor, ); currentGraph.lastProcessedSeq = lastProcessedAssistantFloor; } function computePostProcessArtifacts(beforeSnapshot, afterSnapshot, extraTags = []) { const beforeNodeIds = new Set((beforeSnapshot?.nodes || []).map((node) => node.id)); const afterNodes = afterSnapshot?.nodes || []; const tags = new Set(extraTags.filter(Boolean)); for (const node of afterNodes) { if (!beforeNodeIds.has(node.id)) { if (node.type === "synopsis") tags.add("synopsis"); if (node.type === "reflection") tags.add("reflection"); if (node.level > 0) tags.add("compression"); } } const beforeNodes = new Map((beforeSnapshot?.nodes || []).map((node) => [node.id, node])); for (const node of afterNodes) { const beforeNode = beforeNodes.get(node.id); if (!beforeNode) continue; if (!beforeNode.archived && node.archived) { tags.add(node.level > 0 ? "compression-archive" : "sleep/archive"); } } return [...tags]; } async function syncVectorState({ force = false, purge = false, range = null, } = {}) { ensureCurrentGraphRuntimeState(); const config = getEmbeddingConfig(); const validation = validateVectorConfig(config); if (!validation.valid) { currentGraph.vectorIndexState.lastWarning = validation.error; currentGraph.vectorIndexState.dirty = true; return { insertedHashes: [], stats: getVectorIndexStats(currentGraph), error: validation.error, }; } try { return await syncGraphVectorIndex(currentGraph, config, { chatId: getCurrentChatId(), force, purge, range, }); } catch (error) { markVectorStateDirty(error?.message || "向量同步失败"); console.error("[ST-BME] 向量同步失败:", error); return { insertedHashes: [], stats: getVectorIndexStats(currentGraph), error: String(error), }; } } async function ensureVectorReadyIfNeeded(reason = "vector-ready-check") { if (!currentGraph) return; ensureCurrentGraphRuntimeState(); if (!currentGraph.vectorIndexState?.dirty) return; const config = getEmbeddingConfig(); const validation = validateVectorConfig(config); if (!validation.valid) return; const result = await syncVectorState({ force: true, purge: isBackendVectorConfig(config), }); currentGraph.vectorIndexState.lastWarning = ""; saveGraphToChat(); console.log("[ST-BME] 向量状态已自动修复:", reason, result.stats); } async function resetVectorStateForConfigChange(reason = "向量配置已变更") { if (!currentGraph) return; ensureCurrentGraphRuntimeState(); markVectorStateDirty(reason); currentGraph.vectorIndexState.hashToNodeId = {}; currentGraph.vectorIndexState.nodeToHash = {}; currentGraph.vectorIndexState.lastStats = { total: 0, indexed: 0, stale: 0, pending: 0, }; saveGraphToChat(); } function getPersistedSettingsSnapshot(settings = getSettings()) { const persisted = {}; for (const key of Object.keys(defaultSettings)) { persisted[key] = settings[key]; } return persisted; } function mergePersistedSettings(loaded = {}) { const merged = { ...defaultSettings }; for (const key of Object.keys(defaultSettings)) { if (Object.prototype.hasOwnProperty.call(loaded, key)) { merged[key] = loaded[key]; } } return merged; } function encodeBase64Utf8(text) { const bytes = new TextEncoder().encode(String(text ?? "")); const chunkSize = 0x8000; let binary = ""; for (let i = 0; i < bytes.length; i += chunkSize) { binary += String.fromCharCode(...bytes.subarray(i, i + chunkSize)); } return btoa(binary); } async function loadServerSettings() { try { const response = await fetch( `${SERVER_SETTINGS_URL}?t=${Date.now()}`, { cache: "no-store" }, ); if (response.status === 404) { return; } if (!response.ok) { throw new Error(response.statusText || `HTTP ${response.status}`); } const loaded = await response.json(); if (loaded && typeof loaded === "object" && !Array.isArray(loaded)) { extension_settings[MODULE_NAME] = mergePersistedSettings(loaded); saveSettingsDebounced(); } } catch (error) { console.warn("[ST-BME] 读取服务端设置失败,回退到本地运行时设置:", error); } } async function saveServerSettings(settings = getSettings()) { const payload = JSON.stringify( getPersistedSettingsSnapshot(settings), null, 2, ); const response = await fetch("/api/files/upload", { method: "POST", headers: getRequestHeaders(), body: JSON.stringify({ name: SERVER_SETTINGS_FILENAME, data: encodeBase64Utf8(payload), }), }); if (!response.ok) { const message = await response.text().catch(() => response.statusText); throw new Error(message || `HTTP ${response.status}`); } } function scheduleServerSettingsSave() { clearTimeout(serverSettingsSaveTimer); serverSettingsSaveTimer = setTimeout(async () => { try { await saveServerSettings(); } catch (error) { console.error("[ST-BME] 保存服务端设置失败:", error); } }, 300); } function updateModuleSettings(patch = {}) { const vectorConfigKeys = new Set([ "embeddingApiUrl", "embeddingApiKey", "embeddingModel", "embeddingTransportMode", "embeddingBackendSource", "embeddingBackendModel", "embeddingBackendApiUrl", "embeddingAutoSuffix", ]); const settings = getSettings(); Object.assign(settings, patch); extension_settings[MODULE_NAME] = settings; saveSettingsDebounced(); if ( Object.prototype.hasOwnProperty.call(patch, "enabled") && patch.enabled === false ) { try { const context = getContext(); context.setExtensionPrompt(MODULE_NAME, "", 1, 0); lastInjectionContent = ""; lastRecalledItems = []; } catch (error) { console.warn("[ST-BME] 关闭插件时清理注入失败:", error); } } if (Object.keys(patch).some((key) => vectorConfigKeys.has(key))) { void resetVectorStateForConfigChange("Embedding 配置已变更,向量索引待重建"); } scheduleServerSettingsSave(); return settings; } // ==================== 图状态持久化 ==================== function loadGraphFromChat() { const context = getContext(); const chatId = getCurrentChatId(context); if (!context.chatMetadata) { currentGraph = normalizeGraphRuntimeState(createEmptyGraph(), chatId); lastExtractedItems = []; lastRecalledItems = []; lastInjectionContent = ""; return; } const savedData = context.chatMetadata[GRAPH_METADATA_KEY]; if (savedData) { currentGraph = normalizeGraphRuntimeState(deserializeGraph(savedData), chatId); console.log("[ST-BME] 从聊天数据加载图谱:", getGraphStats(currentGraph)); } else { currentGraph = normalizeGraphRuntimeState(createEmptyGraph(), chatId); } extractionCount = 0; lastExtractedItems = []; updateLastRecalledItems(currentGraph.lastRecallResult || []); lastInjectionContent = ""; } function saveGraphToChat() { const context = getContext(); if (!context.chatMetadata || !currentGraph) return; ensureCurrentGraphRuntimeState(); 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)); } function getCurrentChatSeq(context = getContext()) { const chat = context?.chat; if (Array.isArray(chat) && chat.length > 0) { return chat.length - 1; } return currentGraph?.lastProcessedSeq ?? 0; } async function handleExtractionSuccess(result, endIdx, settings) { const postProcessArtifacts = []; extractionCount++; updateLastExtractedItems(result.newNodeIds || []); if (settings.enableEvolution && result.newNodeIds?.length > 0) { try { await evolveMemories({ graph: currentGraph, newNodeIds: result.newNodeIds, embeddingConfig: getEmbeddingConfig(), options: { neighborCount: settings.evoNeighborCount }, }); postProcessArtifacts.push("evolution"); } catch (e) { console.error("[ST-BME] 记忆进化失败:", e); } } if (settings.enableSynopsis && extractionCount % settings.synopsisEveryN === 0) { try { await generateSynopsis({ graph: currentGraph, schema: getSchema(), currentSeq: endIdx, }); postProcessArtifacts.push("synopsis"); } catch (e) { console.error("[ST-BME] 概要生成失败:", e); } } if ( settings.enableReflection && extractionCount % settings.reflectEveryN === 0 ) { try { await generateReflection({ graph: currentGraph, currentSeq: endIdx, }); postProcessArtifacts.push("reflection"); } catch (e) { console.error("[ST-BME] 反思生成失败:", e); } } if (settings.enableSleepCycle && extractionCount % settings.sleepEveryN === 0) { try { sleepCycle(currentGraph, settings); postProcessArtifacts.push("sleep"); } catch (e) { console.error("[ST-BME] 主动遗忘失败:", e); } } const compressionResult = await compressAll( currentGraph, getSchema(), getEmbeddingConfig(), ); if (compressionResult.created > 0 || compressionResult.archived > 0) { postProcessArtifacts.push("compression"); } const vectorSync = await syncVectorState(); return { postProcessArtifacts, vectorHashesInserted: vectorSync?.insertedHashes || [], vectorStats: vectorSync?.stats || getVectorIndexStats(currentGraph), }; } function getAssistantTurns(chat) { const assistantTurns = []; for (let index = 0; index < chat.length; index++) { if (chat[index].is_user === false && !chat[index].is_system) { assistantTurns.push(index); } } return assistantTurns; } function buildExtractionMessages(chat, startIdx, endIdx, settings) { const contextTurns = clampInt(settings.extractContextTurns, 2, 0, 20); const contextStart = Math.max(0, startIdx - contextTurns * 2); const messages = []; for (let index = contextStart; index <= endIdx && index < chat.length; index++) { const msg = chat[index]; if (msg.is_system) continue; messages.push({ seq: index, role: msg.is_user ? "user" : "assistant", content: msg.mes || "", }); } return messages; } function getLastProcessedAssistantFloor() { ensureCurrentGraphRuntimeState(); return Number.isFinite(currentGraph?.historyState?.lastProcessedAssistantFloor) ? currentGraph.historyState.lastProcessedAssistantFloor : -1; } function notifyHistoryDirty(dirtyFrom, reason) { const now = Date.now(); if (now - lastHistoryWarningAt < 3000) return; lastHistoryWarningAt = now; toastr.warning( `检测到楼层历史变化,将从楼层 ${dirtyFrom} 之后自动恢复图谱`, reason || "ST-BME 历史回退保护", ); } function inspectHistoryMutation(trigger = "history-change") { if (!currentGraph) return { dirty: false, earliestAffectedFloor: null, reason: "" }; ensureCurrentGraphRuntimeState(); const context = getContext(); const chat = context?.chat; const detection = detectHistoryMutation(chat, currentGraph.historyState); if (detection.dirty) { clearInjectionState(); markHistoryDirty( currentGraph, detection.earliestAffectedFloor, detection.reason || trigger, ); saveGraphToChat(); notifyHistoryDirty(detection.earliestAffectedFloor, detection.reason); return detection; } if (trigger === "message-edited" || trigger === "message-swiped") { clearInjectionState(); } return detection; } async function purgeCurrentVectorCollection() { if (!currentGraph?.vectorIndexState?.collectionId) return; const response = await fetch("/api/vector/purge", { method: "POST", headers: getRequestHeaders(), body: JSON.stringify({ collectionId: currentGraph.vectorIndexState.collectionId, }), }); if (!response.ok) { const message = await response.text().catch(() => response.statusText); throw new Error(message || `HTTP ${response.status}`); } } async function prepareVectorStateForReplay(fullReset = false) { ensureCurrentGraphRuntimeState(); const config = getEmbeddingConfig(); if (isBackendVectorConfig(config)) { try { await purgeCurrentVectorCollection(); } catch (error) { console.warn("[ST-BME] 清理后端向量索引失败,继续本地恢复:", error); } currentGraph.vectorIndexState.hashToNodeId = {}; currentGraph.vectorIndexState.nodeToHash = {}; currentGraph.vectorIndexState.dirty = true; currentGraph.vectorIndexState.lastWarning = "历史恢复后需要重建后端向量索引"; return; } if (fullReset) { currentGraph.vectorIndexState.hashToNodeId = {}; currentGraph.vectorIndexState.nodeToHash = {}; currentGraph.vectorIndexState.dirty = true; currentGraph.vectorIndexState.lastWarning = "历史恢复后需要重嵌当前聊天向量"; } } async function executeExtractionBatch({ chat, startIdx, endIdx, settings, smartTriggerDecision = null, } = {}) { ensureCurrentGraphRuntimeState(); const lastProcessed = getLastProcessedAssistantFloor(); const beforeSnapshot = cloneGraphSnapshot(currentGraph); const messages = buildExtractionMessages(chat, startIdx, endIdx, settings); 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) { return { success: false, result, effects: null }; } const effects = await handleExtractionSuccess(result, endIdx, settings); updateProcessedHistorySnapshot(chat, endIdx); const afterSnapshot = cloneGraphSnapshot(currentGraph); const postProcessArtifacts = computePostProcessArtifacts( beforeSnapshot, afterSnapshot, effects?.postProcessArtifacts || [], ); appendBatchJournal( currentGraph, createBatchJournalEntry(beforeSnapshot, afterSnapshot, { processedRange: [startIdx, endIdx], postProcessArtifacts, vectorHashesInserted: effects?.vectorHashesInserted || [], }), ); saveGraphToChat(); return { success: true, result, effects }; } async function replayExtractionFromHistory(chat, settings) { let replayedBatches = 0; while (true) { const pendingAssistantTurns = getAssistantTurns(chat).filter( (index) => index > getLastProcessedAssistantFloor(), ); if (pendingAssistantTurns.length === 0) break; const extractEvery = clampInt(settings.extractEvery, 1, 1, 50); const batchAssistantTurns = pendingAssistantTurns.slice(0, extractEvery); const startIdx = batchAssistantTurns[0]; const endIdx = batchAssistantTurns[batchAssistantTurns.length - 1]; const batchResult = await executeExtractionBatch({ chat, startIdx, endIdx, settings, }); if (!batchResult.success) { throw new Error("历史恢复回放过程中出现提取失败"); } replayedBatches++; } return replayedBatches; } async function recoverHistoryIfNeeded(trigger = "history-recovery") { if (!currentGraph || isRecoveringHistory) { return !isRecoveringHistory; } ensureCurrentGraphRuntimeState(); const context = getContext(); const chat = context?.chat; if (!Array.isArray(chat)) return true; const detection = inspectHistoryMutation(trigger); const dirtyFrom = currentGraph?.historyState?.historyDirtyFrom; if (!detection.dirty && !Number.isFinite(dirtyFrom)) { return true; } isRecoveringHistory = true; clearInjectionState(); const chatId = getCurrentChatId(context); const settings = getSettings(); const initialDirtyFrom = Number.isFinite(dirtyFrom) ? dirtyFrom : detection.earliestAffectedFloor; let replayedBatches = 0; let usedFullRebuild = false; try { const recoveryPoint = findJournalRecoveryPoint(currentGraph, initialDirtyFrom); if (recoveryPoint) { currentGraph = normalizeGraphRuntimeState( recoveryPoint.snapshotBefore, chatId, ); } else { currentGraph = normalizeGraphRuntimeState(createEmptyGraph(), chatId); usedFullRebuild = true; } await prepareVectorStateForReplay(usedFullRebuild); replayedBatches = await replayExtractionFromHistory(chat, settings); clearHistoryDirty( currentGraph, buildRecoveryResult(usedFullRebuild ? "full-rebuild" : "replayed", { fromFloor: initialDirtyFrom, batches: replayedBatches, reason: detection.reason || currentGraph?.historyState?.lastMutationReason || trigger, }), ); saveGraphToChat(); toastr.success( usedFullRebuild ? "历史变化已触发全量重建" : "历史变化已完成受影响后缀恢复", ); return true; } catch (error) { console.error("[ST-BME] 历史恢复失败,尝试全量重建:", error); try { currentGraph = normalizeGraphRuntimeState(createEmptyGraph(), chatId); await prepareVectorStateForReplay(true); replayedBatches = await replayExtractionFromHistory(chat, settings); clearHistoryDirty( currentGraph, buildRecoveryResult("full-rebuild", { fromFloor: 0, batches: replayedBatches, reason: `恢复失败后兜底全量重建: ${error?.message || error}`, }), ); saveGraphToChat(); toastr.warning("历史恢复已退化为全量重建"); return true; } catch (fallbackError) { currentGraph.historyState.lastRecoveryResult = buildRecoveryResult("failed", { fromFloor: initialDirtyFrom, reason: String(fallbackError), }); saveGraphToChat(); toastr.error(`历史恢复失败: ${fallbackError?.message || fallbackError}`); return false; } } finally { isRecoveringHistory = false; } } /** * 提取管线:处理未提取的对话楼层 */ async function runExtraction() { if (isExtracting || !currentGraph) return; const settings = getSettings(); if (!settings.enabled) return; if (!(await recoverHistoryIfNeeded("auto-extract"))) return; await ensureVectorReadyIfNeeded("pre-extract"); const context = getContext(); const chat = context.chat; if (!chat || chat.length === 0) return; const assistantTurns = getAssistantTurns(chat); const lastProcessed = getLastProcessedAssistantFloor(); 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 batchResult = await executeExtractionBatch({ chat, startIdx, endIdx, settings, smartTriggerDecision, }); if (!batchResult.success) { console.warn("[ST-BME] 提取批次未返回有效结果"); } } 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; if (!(await recoverHistoryIfNeeded("pre-recall"))) return; await ensureVectorReadyIfNeeded("pre-recall"); 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; updateLastRecalledItems(result.selectedNodeIds || []); saveGraphToChat(); } catch (e) { console.error("[ST-BME] 召回失败:", e); } finally { isRecalling = false; } } // ==================== 事件钩子 ==================== function onChatChanged() { loadGraphFromChat(); clearInjectionState(); } function onMessageDeleted() { inspectHistoryMutation("message-deleted"); } function onMessageEdited() { inspectHistoryMutation("message-edited"); } function onMessageSwiped() { inspectHistoryMutation("message-swiped"); } 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; const context = getContext(); const chat = context?.chat; if (!Array.isArray(chat)) { toastr.warning("当前聊天上下文不可用,无法重建"); return; } currentGraph = normalizeGraphRuntimeState(createEmptyGraph(), getCurrentChatId()); currentGraph.batchJournal = []; clearInjectionState(); await prepareVectorStateForReplay(true); await replayExtractionFromHistory(chat, getSettings()); clearHistoryDirty( currentGraph, buildRecoveryResult("full-rebuild", { fromFloor: 0, batches: currentGraph.batchJournal.length, reason: "用户手动触发全量重建", }), ); saveGraphToChat(); toastr.success("图谱与向量索引已按当前聊天全量重建"); } async function onManualCompress() { if (!currentGraph) return; const beforeSnapshot = cloneGraphSnapshot(currentGraph); const result = await compressAll( currentGraph, getSchema(), getEmbeddingConfig(), false, ); await recordGraphMutation({ beforeSnapshot, artifactTags: ["compression"], }); 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 = normalizeGraphRuntimeState( importGraph(text), getCurrentChatId(), ); markVectorStateDirty("导入图谱后需要重建向量索引"); extractionCount = 0; lastExtractedItems = []; updateLastRecalledItems(currentGraph.lastRecallResult || []); clearInjectionState(); 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(); const validation = validateVectorConfig(config); if (!validation.valid) { toastr.warning(validation.error); return; } toastr.info("正在测试 Embedding API 连通性..."); const result = await testVectorConnection(config, getCurrentChatId()); if (result.success) { toastr.success(`连接成功!向量维度: ${result.dimensions}`); } else { toastr.error(`连接失败: ${result.error}`); } } async function onTestMemoryLLM() { toastr.info("正在测试记忆 LLM 连通性..."); const result = await testLLMConnection(); if (result.success) { toastr.success(`连接成功!模式: ${result.mode}`); } else { toastr.error(`连接失败: ${result.error}`); } } async function onManualExtract() { if (isExtracting) return; if (!(await recoverHistoryIfNeeded("manual-extract"))) return; await ensureVectorReadyIfNeeded("manual-extract"); if (!currentGraph) currentGraph = normalizeGraphRuntimeState(createEmptyGraph(), getCurrentChatId()); const context = getContext(); const chat = context.chat; if (!Array.isArray(chat) || chat.length === 0) { toastr.info("当前聊天为空,暂无可提取内容"); return; } const assistantTurns = getAssistantTurns(chat); const lastProcessed = getLastProcessedAssistantFloor(); const pendingAssistantTurns = assistantTurns.filter((i) => i > lastProcessed); if (pendingAssistantTurns.length === 0) { toastr.info("没有待提取的新回复"); return; } const startIdx = pendingAssistantTurns[0]; const endIdx = pendingAssistantTurns[pendingAssistantTurns.length - 1]; const settings = getSettings(); isExtracting = true; try { const batchResult = await executeExtractionBatch({ chat, startIdx, endIdx, settings, }); if (!batchResult.success) { toastr.warning("手动提取未返回有效结果"); return; } toastr.success( `提取完成:新建 ${batchResult.result.newNodes},更新 ${batchResult.result.updatedNodes},新边 ${batchResult.result.newEdges}`, ); } catch (e) { console.error("[ST-BME] 手动提取失败:", e); toastr.error(`手动提取失败: ${e.message || e}`); } finally { isExtracting = false; } } async function onManualSleep() { if (!currentGraph) return; const beforeSnapshot = cloneGraphSnapshot(currentGraph); const result = sleepCycle(currentGraph, getSettings()); await recordGraphMutation({ beforeSnapshot, artifactTags: ["sleep"], }); toastr.info(`执行完成:归档 ${result.forgotten} 个节点`); } async function onManualSynopsis() { if (!currentGraph) return; const beforeSnapshot = cloneGraphSnapshot(currentGraph); await generateSynopsis({ graph: currentGraph, schema: getSchema(), currentSeq: getCurrentChatSeq(), }); await recordGraphMutation({ beforeSnapshot, artifactTags: ["synopsis"], }); toastr.success("概要生成完成"); } async function onManualEvolve() { if (!currentGraph) return; const candidateIds = lastExtractedItems.map((item) => item.id).filter(Boolean); if (candidateIds.length === 0) { toastr.info("暂无最近提取节点可用于进化"); return; } const beforeSnapshot = cloneGraphSnapshot(currentGraph); const result = await evolveMemories({ graph: currentGraph, newNodeIds: candidateIds, embeddingConfig: getEmbeddingConfig(), options: { neighborCount: getSettings().evoNeighborCount }, }); await recordGraphMutation({ beforeSnapshot, artifactTags: ["evolution"], }); toastr.success( `进化完成:${result.evolved} 次进化,${result.connections} 条链接,${result.updates} 个回溯更新`, ); } async function onRebuildVectorIndex(range = null) { ensureCurrentGraphRuntimeState(); const config = getEmbeddingConfig(); const validation = validateVectorConfig(config); if (!validation.valid) { toastr.warning(validation.error); return; } const result = await syncVectorState({ force: true, purge: isBackendVectorConfig(config) && !range, range, }); saveGraphToChat(); toastr.success( range ? `范围向量重建完成:indexed=${result.stats.indexed}, pending=${result.stats.pending}` : `当前聊天向量重建完成:indexed=${result.stats.indexed}, pending=${result.stats.pending}`, ); } async function onReembedDirect() { const config = getEmbeddingConfig(); if (!isDirectVectorConfig(config)) { toastr.info("当前不是直连模式,无需执行重嵌"); return; } await onRebuildVectorIndex(); } // ==================== 设置 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() { await loadServerSettings(); // 注册事件钩子 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); eventSource.on(event_types.MESSAGE_DELETED, onMessageDeleted); eventSource.on(event_types.MESSAGE_EDITED, onMessageEdited); eventSource.on(event_types.MESSAGE_SWIPED, onMessageSwiped); if (event_types.MESSAGE_UPDATED) { eventSource.on(event_types.MESSAGE_UPDATED, onMessageEdited); } // 加载当前聊天的图谱 loadGraphFromChat(); // ==================== 操控面板初始化 ==================== try { // 动态加载面板模块 _panelModule = await import("./panel.js"); _themesModule = await import("./themes.js"); // 应用主题 const settings = getSettings(); _themesModule.applyTheme(settings.panelTheme || "crimson"); // 初始化操控面板 await _panelModule.initPanel({ getGraph: () => currentGraph, getSettings: () => getSettings(), getLastExtract: () => lastExtractedItems, getLastRecall: () => lastRecalledItems, getLastInjection: () => lastInjectionContent, updateSettings: (patch) => { const settings = updateModuleSettings(patch); if (Object.prototype.hasOwnProperty.call(patch, "panelTheme")) { _themesModule?.applyTheme(settings.panelTheme || "crimson"); _panelModule?.updatePanelTheme(settings.panelTheme || "crimson"); } return settings; }, actions: { extract: onManualExtract, compress: onManualCompress, sleep: onManualSleep, synopsis: onManualSynopsis, export: onExportGraph, import: onImportGraph, rebuild: onRebuild, evolve: onManualEvolve, testEmbedding: onTestEmbedding, testMemoryLLM: onTestMemoryLLM, rebuildVectorIndex: () => onRebuildVectorIndex(), rebuildVectorRange: (range) => onRebuildVectorIndex(range), reembedDirect: onReembedDirect, }, }); // 注入三条杠 Options 菜单按钮 if (!document.getElementById("option_st_bme_panel")) { const $menuItem = $(` 记忆图谱 `).on("click", () => { _panelModule?.openPanel(); $("#options").hide(); }); const $optionsContent = $("#options .options-content"); const $anchor = $("#option_toggle_logprobs"); if ($anchor.length > 0) { $anchor.after($menuItem); } else if ($optionsContent.length > 0) { $optionsContent.append($menuItem); } } console.log("[ST-BME] 操控面板初始化完成"); } catch (panelError) { console.error("[ST-BME] 操控面板加载失败(核心功能不受影响):", panelError); } console.log("[ST-BME] 初始化完成"); })();