From 079a01ee781db8e75d498cedf27b7ea65fded3c6 Mon Sep 17 00:00:00 2001 From: Youzini-afk <13153778771cx@gmail.com> Date: Sun, 29 Mar 2026 17:01:47 +0800 Subject: [PATCH] refactor: extract event binding and panel bridge modules --- event-binding.js | 109 ++++++++++++++++++ index.js | 286 +++++++++++++++++++---------------------------- panel-bridge.js | 74 ++++++++++++ 3 files changed, 300 insertions(+), 169 deletions(-) create mode 100644 event-binding.js create mode 100644 panel-bridge.js diff --git a/event-binding.js b/event-binding.js new file mode 100644 index 0000000..2008d16 --- /dev/null +++ b/event-binding.js @@ -0,0 +1,109 @@ +export function registerBeforeCombinePromptsController(runtime, listener) { + const makeFirst = runtime.getEventMakeFirst(); + if (typeof makeFirst === "function") { + return makeFirst( + runtime.eventTypes.GENERATE_BEFORE_COMBINE_PROMPTS, + listener, + ); + } + + runtime.console.warn("[ST-BME] eventMakeFirst 不可用,回退到普通事件注册"); + runtime.eventSource.on(runtime.eventTypes.GENERATE_BEFORE_COMBINE_PROMPTS, listener); + return null; +} + +export function registerGenerationAfterCommandsController(runtime, listener) { + const makeFirst = runtime.getEventMakeFirst(); + if (typeof makeFirst === "function") { + return makeFirst(runtime.eventTypes.GENERATION_AFTER_COMMANDS, listener); + } + + runtime.console.warn( + "[ST-BME] eventMakeFirst 不可用,GENERATION_AFTER_COMMANDS 回退到普通事件注册", + ); + runtime.eventSource.on(runtime.eventTypes.GENERATION_AFTER_COMMANDS, listener); + return null; +} + +export function scheduleSendIntentHookRetryController(runtime, delayMs = 400) { + runtime.clearTimeout(runtime.getSendIntentHookRetryTimer()); + const timer = runtime.setTimeout(() => { + runtime.setSendIntentHookRetryTimer(null); + runtime.installSendIntentHooks(); + }, delayMs); + runtime.setSendIntentHookRetryTimer(timer); +} + +export function installSendIntentHooksController(runtime) { + for (const cleanup of runtime.consumeSendIntentHookCleanup()) { + try { + cleanup(); + } catch (error) { + runtime.console.warn("[ST-BME] 清理发送意图钩子失败:", error); + } + } + + const sendButton = runtime.document.getElementById("send_but"); + const sendTextarea = runtime.document.getElementById("send_textarea"); + + if (sendButton) { + const captureSendIntent = () => { + runtime.recordRecallSendIntent(runtime.getSendTextareaValue(), "send-button"); + }; + + sendButton.addEventListener("click", captureSendIntent, true); + sendButton.addEventListener("pointerup", captureSendIntent, true); + sendButton.addEventListener("touchend", captureSendIntent, true); + runtime.pushSendIntentHookCleanup(() => { + sendButton.removeEventListener("click", captureSendIntent, true); + sendButton.removeEventListener("pointerup", captureSendIntent, true); + sendButton.removeEventListener("touchend", captureSendIntent, true); + }); + } + + if (sendTextarea) { + const captureEnterIntent = (event) => { + if ( + (event.key === "Enter" || event.key === "NumpadEnter") && + !event.shiftKey + ) { + runtime.recordRecallSendIntent( + runtime.getSendTextareaValue(), + "textarea-enter", + ); + } + }; + + sendTextarea.addEventListener("keydown", captureEnterIntent, true); + runtime.pushSendIntentHookCleanup(() => { + sendTextarea.removeEventListener("keydown", captureEnterIntent, true); + }); + } + + if (!sendButton || !sendTextarea) { + runtime.scheduleSendIntentHookRetry(); + } +} + +export function registerCoreEventHooksController(runtime) { + const { eventSource, eventTypes, handlers } = runtime; + + eventSource.on(eventTypes.CHAT_CHANGED, handlers.onChatChanged); + if (eventTypes.CHAT_LOADED) { + eventSource.on(eventTypes.CHAT_LOADED, handlers.onChatLoaded); + } + if (eventTypes.MESSAGE_SENT) { + eventSource.on(eventTypes.MESSAGE_SENT, handlers.onMessageSent); + } + + runtime.registerGenerationAfterCommands(handlers.onGenerationAfterCommands); + runtime.registerBeforeCombinePrompts(handlers.onBeforeCombinePrompts); + + eventSource.on(eventTypes.MESSAGE_RECEIVED, handlers.onMessageReceived); + eventSource.on(eventTypes.MESSAGE_DELETED, handlers.onMessageDeleted); + eventSource.on(eventTypes.MESSAGE_EDITED, handlers.onMessageEdited); + eventSource.on(eventTypes.MESSAGE_SWIPED, handlers.onMessageSwiped); + if (eventTypes.MESSAGE_UPDATED) { + eventSource.on(eventTypes.MESSAGE_UPDATED, handlers.onMessageEdited); + } +} diff --git a/index.js b/index.js index 7dfcdee..40f7027 100644 --- a/index.js +++ b/index.js @@ -56,10 +56,18 @@ import { estimateTokens, formatInjection } from "./injector.js"; import { fetchMemoryLLMModels, testLLMConnection } from "./llm.js"; import { getNodeDisplayName } from "./node-labels.js"; import { showManagedBmeNotice } from "./notice.js"; +import { + installSendIntentHooksController, + registerBeforeCombinePromptsController, + registerCoreEventHooksController, + registerGenerationAfterCommandsController, + scheduleSendIntentHookRetryController, +} from "./event-binding.js"; import { createDefaultTaskProfiles, migrateLegacyTaskProfiles, } from "./prompt-profiles.js"; +import { initializePanelBridgeController } from "./panel-bridge.js"; import { resolveConfiguredTimeoutMs } from "./request-timeout.js"; import { retrieve } from "./retriever.js"; import { @@ -914,86 +922,57 @@ function getSendTextareaValue() { } function scheduleSendIntentHookRetry(delayMs = 400) { - clearTimeout(sendIntentHookRetryTimer); - sendIntentHookRetryTimer = setTimeout(() => { - sendIntentHookRetryTimer = null; - installSendIntentHooks(); - }, delayMs); + return scheduleSendIntentHookRetryController( + { + clearTimeout, + getSendIntentHookRetryTimer: () => sendIntentHookRetryTimer, + installSendIntentHooks, + setSendIntentHookRetryTimer: (timer) => { + sendIntentHookRetryTimer = timer; + }, + setTimeout, + }, + delayMs, + ); } function registerBeforeCombinePrompts(listener) { - const makeFirst = globalThis.eventMakeFirst; - if (typeof makeFirst === "function") { - return makeFirst(event_types.GENERATE_BEFORE_COMBINE_PROMPTS, listener); - } - - console.warn("[ST-BME] eventMakeFirst 不可用,回退到普通事件注册"); - eventSource.on(event_types.GENERATE_BEFORE_COMBINE_PROMPTS, listener); - return null; + return registerBeforeCombinePromptsController( + { + console, + eventSource, + eventTypes: event_types, + getEventMakeFirst: () => globalThis.eventMakeFirst, + }, + listener, + ); } function registerGenerationAfterCommands(listener) { - const makeFirst = globalThis.eventMakeFirst; - if (typeof makeFirst === "function") { - return makeFirst(event_types.GENERATION_AFTER_COMMANDS, listener); - } - - console.warn( - "[ST-BME] eventMakeFirst 不可用,GENERATION_AFTER_COMMANDS 回退到普通事件注册", + return registerGenerationAfterCommandsController( + { + console, + eventSource, + eventTypes: event_types, + getEventMakeFirst: () => globalThis.eventMakeFirst, + }, + listener, ); - eventSource.on(event_types.GENERATION_AFTER_COMMANDS, listener); - return null; } function installSendIntentHooks() { - for (const cleanup of sendIntentHookCleanup.splice( - 0, - sendIntentHookCleanup.length, - )) { - try { - cleanup(); - } catch (error) { - console.warn("[ST-BME] 清理发送意图钩子失败:", error); - } - } - - const sendButton = document.getElementById("send_but"); - const sendTextarea = document.getElementById("send_textarea"); - - if (sendButton) { - const captureSendIntent = () => { - recordRecallSendIntent(getSendTextareaValue(), "send-button"); - }; - - sendButton.addEventListener("click", captureSendIntent, true); - sendButton.addEventListener("pointerup", captureSendIntent, true); - sendButton.addEventListener("touchend", captureSendIntent, true); - sendIntentHookCleanup.push(() => { - sendButton.removeEventListener("click", captureSendIntent, true); - sendButton.removeEventListener("pointerup", captureSendIntent, true); - sendButton.removeEventListener("touchend", captureSendIntent, true); - }); - } - - if (sendTextarea) { - const captureEnterIntent = (event) => { - if ( - (event.key === "Enter" || event.key === "NumpadEnter") && - !event.shiftKey - ) { - recordRecallSendIntent(getSendTextareaValue(), "textarea-enter"); - } - }; - - sendTextarea.addEventListener("keydown", captureEnterIntent, true); - sendIntentHookCleanup.push(() => { - sendTextarea.removeEventListener("keydown", captureEnterIntent, true); - }); - } - - if (!sendButton || !sendTextarea) { - scheduleSendIntentHookRetry(); - } + return installSendIntentHooksController({ + console, + consumeSendIntentHookCleanup: () => + sendIntentHookCleanup.splice(0, sendIntentHookCleanup.length), + document, + getSendTextareaValue, + pushSendIntentHookCleanup: (cleanup) => { + sendIntentHookCleanup.push(cleanup); + }, + recordRecallSendIntent, + scheduleSendIntentHookRetry, + }); } // ==================== 设置管理 ==================== @@ -5146,22 +5125,23 @@ async function onReembedDirect() { ); // 注册事件钩子 - eventSource.on(event_types.CHAT_CHANGED, onChatChanged); - if (event_types.CHAT_LOADED) { - eventSource.on(event_types.CHAT_LOADED, onChatLoaded); - } - if (event_types.MESSAGE_SENT) { - eventSource.on(event_types.MESSAGE_SENT, onMessageSent); - } - registerGenerationAfterCommands(onGenerationAfterCommands); - registerBeforeCombinePrompts(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); - } + registerCoreEventHooksController({ + eventSource, + eventTypes: event_types, + handlers: { + onBeforeCombinePrompts, + onChatChanged, + onChatLoaded, + onGenerationAfterCommands, + onMessageDeleted, + onMessageEdited, + onMessageReceived, + onMessageSent, + onMessageSwiped, + }, + registerBeforeCombinePrompts, + registerGenerationAfterCommands, + }); // 加载当前聊天的图谱 clearPendingGraphLoadRetry(); @@ -5173,89 +5153,57 @@ async function onReembedDirect() { // ==================== 操控面板初始化 ==================== - 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, - getRuntimeStatus: () => getPanelRuntimeStatus(), - getLastExtractionStatus: () => lastExtractionStatus, - getLastVectorStatus: () => lastVectorStatus, - getLastRecallStatus: () => lastRecallStatus, - getLastBatchStatus: () => - currentGraph?.historyState?.lastBatchStatus || null, - getLastInjection: () => lastInjectionContent, - getRuntimeDebugSnapshot: (options = {}) => - getPanelRuntimeDebugSnapshot(options), - getGraphPersistenceState: () => getGraphPersistenceLiveState(), - 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: { - syncGraphLoad: () => - syncGraphLoadFromLiveContext({ - source: "panel-open-sync", - }), - extract: onManualExtract, - compress: onManualCompress, - sleep: onManualSleep, - synopsis: onManualSynopsis, - export: onExportGraph, - import: onImportGraph, - rebuild: onRebuild, - evolve: onManualEvolve, - testEmbedding: onTestEmbedding, - testMemoryLLM: onTestMemoryLLM, - fetchMemoryLLMModels: onFetchMemoryLLMModels, - fetchEmbeddingModels: onFetchEmbeddingModels, - rebuildVectorIndex: () => onRebuildVectorIndex(), - rebuildVectorRange: (range) => onRebuildVectorIndex(range), - reembedDirect: onReembedDirect, - reroll: onReroll, - }, - }); - - // 注入三条杠 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); - } + await initializePanelBridgeController({ + $, + actions: { + syncGraphLoad: () => + syncGraphLoadFromLiveContext({ + source: "panel-open-sync", + }), + extract: onManualExtract, + compress: onManualCompress, + sleep: onManualSleep, + synopsis: onManualSynopsis, + export: onExportGraph, + import: onImportGraph, + rebuild: onRebuild, + evolve: onManualEvolve, + testEmbedding: onTestEmbedding, + testMemoryLLM: onTestMemoryLLM, + fetchMemoryLLMModels: onFetchMemoryLLMModels, + fetchEmbeddingModels: onFetchEmbeddingModels, + rebuildVectorIndex: () => onRebuildVectorIndex(), + rebuildVectorRange: (range) => onRebuildVectorIndex(range), + reembedDirect: onReembedDirect, + reroll: onReroll, + }, + console, + document, + getGraph: () => currentGraph, + getGraphPersistenceState: () => getGraphPersistenceLiveState(), + getLastBatchStatus: () => currentGraph?.historyState?.lastBatchStatus || null, + getLastExtract: () => lastExtractedItems, + getLastExtractionStatus: () => lastExtractionStatus, + getLastInjection: () => lastInjectionContent, + getLastRecall: () => lastRecalledItems, + getLastRecallStatus: () => lastRecallStatus, + getLastVectorStatus: () => lastVectorStatus, + getPanelModule: () => _panelModule, + getRuntimeDebugSnapshot: (options = {}) => + getPanelRuntimeDebugSnapshot(options), + getRuntimeStatus: () => getPanelRuntimeStatus(), + getSettings, + getThemesModule: () => _themesModule, + importPanelModule: async () => await import("./panel.js"), + importThemesModule: async () => await import("./themes.js"), + setPanelModule: (module) => { + _panelModule = module; + }, + setThemesModule: (module) => { + _themesModule = module; + }, + updateSettings: updateModuleSettings, + }); console.log("[ST-BME] 初始化完成"); })(); diff --git a/panel-bridge.js b/panel-bridge.js new file mode 100644 index 0000000..af2e309 --- /dev/null +++ b/panel-bridge.js @@ -0,0 +1,74 @@ +function resolvePanelTheme(settings) { + return settings?.panelTheme || "crimson"; +} + +function injectOptionsMenuEntry(runtime) { + if (runtime.document.getElementById("option_st_bme_panel")) { + return; + } + + const $menuItem = runtime.$(` + + + 记忆图谱 + + `).on("click", () => { + runtime.getPanelModule()?.openPanel?.(); + runtime.$("#options").hide(); + }); + + const $optionsContent = runtime.$("#options .options-content"); + const $anchor = runtime.$("#option_toggle_logprobs"); + + if ($anchor.length > 0) { + $anchor.after($menuItem); + } else if ($optionsContent.length > 0) { + $optionsContent.append($menuItem); + } +} + +export async function initializePanelBridgeController(runtime) { + try { + const panelModule = await runtime.importPanelModule(); + const themesModule = await runtime.importThemesModule(); + runtime.setPanelModule(panelModule); + runtime.setThemesModule(themesModule); + + const settings = runtime.getSettings(); + const theme = resolvePanelTheme(settings); + themesModule.applyTheme(theme); + + await panelModule.initPanel({ + getGraph: runtime.getGraph, + getSettings: runtime.getSettings, + getLastExtract: runtime.getLastExtract, + getLastRecall: runtime.getLastRecall, + getRuntimeStatus: runtime.getRuntimeStatus, + getLastExtractionStatus: runtime.getLastExtractionStatus, + getLastVectorStatus: runtime.getLastVectorStatus, + getLastRecallStatus: runtime.getLastRecallStatus, + getLastBatchStatus: runtime.getLastBatchStatus, + getLastInjection: runtime.getLastInjection, + getRuntimeDebugSnapshot: runtime.getRuntimeDebugSnapshot, + getGraphPersistenceState: runtime.getGraphPersistenceState, + updateSettings: (patch) => { + const nextSettings = runtime.updateSettings(patch); + if (Object.prototype.hasOwnProperty.call(patch || {}, "panelTheme")) { + const nextTheme = resolvePanelTheme(nextSettings); + runtime.getThemesModule()?.applyTheme?.(nextTheme); + runtime.getPanelModule()?.updatePanelTheme?.(nextTheme); + } + return nextSettings; + }, + actions: runtime.actions, + }); + + injectOptionsMenuEntry(runtime); + runtime.console.log("[ST-BME] 操控面板初始化完成"); + } catch (panelError) { + runtime.console.error( + "[ST-BME] 操控面板加载失败(核心功能不受影响):", + panelError, + ); + } +}