mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-06-13 18:31:16 +08:00
Merge branch 'Youzini-afk:main' into main
This commit is contained in:
@@ -129,6 +129,9 @@ export function registerCoreEventHooksController(runtime) {
|
||||
if (eventTypes.GENERATION_STARTED) {
|
||||
bind(eventTypes.GENERATION_STARTED, handlers.onGenerationStarted);
|
||||
}
|
||||
if (eventTypes.GENERATION_ENDED) {
|
||||
bind(eventTypes.GENERATION_ENDED, handlers.onGenerationEnded);
|
||||
}
|
||||
|
||||
const beforeCombineCleanup = runtime.registerBeforeCombinePrompts(
|
||||
handlers.onBeforeCombinePrompts,
|
||||
|
||||
303
hide-engine.js
Normal file
303
hide-engine.js
Normal file
@@ -0,0 +1,303 @@
|
||||
// ST-BME: 隐藏旧楼层引擎
|
||||
// 通过临时把旧楼层标记为 is_system=true,让宿主主回复与 ST-BME 自己的聊天读取一起跳过这些楼层。
|
||||
|
||||
const hideState = {
|
||||
managedChatRef: null,
|
||||
hiddenIndices: new Set(),
|
||||
lastProcessedLength: 0,
|
||||
scheduledTimer: null,
|
||||
};
|
||||
|
||||
function getTimerApi(runtime = {}) {
|
||||
return {
|
||||
setTimeout:
|
||||
typeof runtime.setTimeout === "function"
|
||||
? runtime.setTimeout.bind(runtime)
|
||||
: globalThis.setTimeout.bind(globalThis),
|
||||
clearTimeout:
|
||||
typeof runtime.clearTimeout === "function"
|
||||
? runtime.clearTimeout.bind(runtime)
|
||||
: globalThis.clearTimeout.bind(globalThis),
|
||||
};
|
||||
}
|
||||
|
||||
function getJquery(runtime = {}) {
|
||||
if (typeof runtime.$ === "function") return runtime.$;
|
||||
if (typeof globalThis.$ === "function") return globalThis.$;
|
||||
return null;
|
||||
}
|
||||
|
||||
function getCurrentChat(runtime = {}) {
|
||||
try {
|
||||
const context =
|
||||
typeof runtime.getContext === "function" ? runtime.getContext() : null;
|
||||
return Array.isArray(context?.chat) ? context.chat : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeHideSettings(settings = {}) {
|
||||
return {
|
||||
enabled: Boolean(settings.enabled),
|
||||
hideLastN: Math.max(
|
||||
0,
|
||||
Math.trunc(
|
||||
Number(
|
||||
settings.hideLastN ??
|
||||
settings.hide_last_n ??
|
||||
settings.keepLastN ??
|
||||
settings.keep_last_n ??
|
||||
0,
|
||||
) || 0,
|
||||
),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function syncSystemAttribute(chat, indices = [], value = "true", runtime = {}) {
|
||||
if (!Array.isArray(indices) || indices.length === 0) return;
|
||||
if (getCurrentChat(runtime) !== chat) return;
|
||||
|
||||
const jq = getJquery(runtime);
|
||||
if (!jq) return;
|
||||
|
||||
const selector = indices.map((index) => `.mes[mesid="${index}"]`).join(",");
|
||||
if (!selector) return;
|
||||
jq(selector).attr("is_system", value);
|
||||
}
|
||||
|
||||
function unhideTrackedChat(chat, runtime = {}) {
|
||||
if (!Array.isArray(chat) || hideState.hiddenIndices.size === 0) {
|
||||
return { shownCount: 0 };
|
||||
}
|
||||
|
||||
const toShow = [];
|
||||
for (const index of hideState.hiddenIndices) {
|
||||
const message = chat[index];
|
||||
if (!message || message.is_system !== true) continue;
|
||||
message.is_system = false;
|
||||
toShow.push(index);
|
||||
}
|
||||
|
||||
syncSystemAttribute(chat, toShow, "false", runtime);
|
||||
return { shownCount: toShow.length };
|
||||
}
|
||||
|
||||
function swapManagedChat(nextChat, runtime = {}) {
|
||||
const previousChat = hideState.managedChatRef;
|
||||
if (previousChat && previousChat !== nextChat) {
|
||||
unhideTrackedChat(previousChat, runtime);
|
||||
hideState.hiddenIndices.clear();
|
||||
hideState.lastProcessedLength = 0;
|
||||
}
|
||||
hideState.managedChatRef = nextChat;
|
||||
}
|
||||
|
||||
export function runFullHideCheck(settings = {}, runtime = {}) {
|
||||
const normalized = normalizeHideSettings(settings);
|
||||
const chat = getCurrentChat(runtime);
|
||||
if (!chat || chat.length === 0) {
|
||||
resetHideState(runtime);
|
||||
return {
|
||||
active: false,
|
||||
hiddenCount: 0,
|
||||
shownCount: 0,
|
||||
managedCount: 0,
|
||||
chatLength: 0,
|
||||
};
|
||||
}
|
||||
|
||||
swapManagedChat(chat, runtime);
|
||||
|
||||
if (!normalized.enabled || normalized.hideLastN <= 0) {
|
||||
const { shownCount } = unhideTrackedChat(chat, runtime);
|
||||
hideState.hiddenIndices.clear();
|
||||
hideState.lastProcessedLength = chat.length;
|
||||
return {
|
||||
active: false,
|
||||
hiddenCount: 0,
|
||||
shownCount,
|
||||
managedCount: 0,
|
||||
chatLength: chat.length,
|
||||
};
|
||||
}
|
||||
|
||||
const visibleStart =
|
||||
normalized.hideLastN >= chat.length
|
||||
? 0
|
||||
: Math.max(0, chat.length - normalized.hideLastN);
|
||||
const desiredHiddenIndices = new Set();
|
||||
const managedHiddenIndices = new Set();
|
||||
const toHide = [];
|
||||
const toShow = [];
|
||||
|
||||
for (let index = 0; index < chat.length; index++) {
|
||||
const message = chat[index];
|
||||
if (!message) continue;
|
||||
|
||||
const shouldHide = index < visibleStart;
|
||||
const isHidden = message.is_system === true;
|
||||
const wasHiddenByBme = hideState.hiddenIndices.has(index);
|
||||
|
||||
if (shouldHide) {
|
||||
desiredHiddenIndices.add(index);
|
||||
if (wasHiddenByBme || !isHidden) {
|
||||
managedHiddenIndices.add(index);
|
||||
}
|
||||
if (!isHidden) {
|
||||
message.is_system = true;
|
||||
toHide.push(index);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isHidden && wasHiddenByBme) {
|
||||
message.is_system = false;
|
||||
toShow.push(index);
|
||||
}
|
||||
}
|
||||
|
||||
syncSystemAttribute(chat, [...desiredHiddenIndices], "true", runtime);
|
||||
syncSystemAttribute(chat, toShow, "false", runtime);
|
||||
|
||||
hideState.hiddenIndices = managedHiddenIndices;
|
||||
hideState.lastProcessedLength = chat.length;
|
||||
|
||||
return {
|
||||
active: true,
|
||||
hiddenCount: toHide.length,
|
||||
shownCount: toShow.length,
|
||||
managedCount: managedHiddenIndices.size,
|
||||
chatLength: chat.length,
|
||||
};
|
||||
}
|
||||
|
||||
export function runIncrementalHideCheck(settings = {}, runtime = {}) {
|
||||
const normalized = normalizeHideSettings(settings);
|
||||
const chat = getCurrentChat(runtime);
|
||||
if (!chat || chat.length === 0) {
|
||||
resetHideState(runtime);
|
||||
return {
|
||||
active: false,
|
||||
hiddenCount: 0,
|
||||
shownCount: 0,
|
||||
managedCount: 0,
|
||||
chatLength: 0,
|
||||
incremental: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
hideState.managedChatRef !== chat ||
|
||||
!normalized.enabled ||
|
||||
normalized.hideLastN <= 0
|
||||
) {
|
||||
return {
|
||||
...runFullHideCheck(normalized, runtime),
|
||||
incremental: false,
|
||||
};
|
||||
}
|
||||
|
||||
const chatLength = chat.length;
|
||||
const previousLength = hideState.lastProcessedLength;
|
||||
if (chatLength <= previousLength) {
|
||||
return {
|
||||
...runFullHideCheck(normalized, runtime),
|
||||
incremental: false,
|
||||
};
|
||||
}
|
||||
|
||||
const previousVisibleStart =
|
||||
previousLength > 0 ? Math.max(0, previousLength - normalized.hideLastN) : 0;
|
||||
const nextVisibleStart = Math.max(0, chatLength - normalized.hideLastN);
|
||||
const toHide = [];
|
||||
|
||||
if (nextVisibleStart > previousVisibleStart) {
|
||||
for (let index = previousVisibleStart; index < nextVisibleStart; index++) {
|
||||
const message = chat[index];
|
||||
if (!message || message.is_system === true) continue;
|
||||
message.is_system = true;
|
||||
hideState.hiddenIndices.add(index);
|
||||
toHide.push(index);
|
||||
}
|
||||
}
|
||||
|
||||
syncSystemAttribute(chat, toHide, "true", runtime);
|
||||
hideState.lastProcessedLength = chatLength;
|
||||
|
||||
return {
|
||||
active: true,
|
||||
hiddenCount: toHide.length,
|
||||
shownCount: 0,
|
||||
managedCount: hideState.hiddenIndices.size,
|
||||
chatLength,
|
||||
incremental: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function applyHideSettings(settings = {}, runtime = {}) {
|
||||
return runFullHideCheck(settings, runtime);
|
||||
}
|
||||
|
||||
export function scheduleHideSettingsApply(
|
||||
settings = {},
|
||||
runtime = {},
|
||||
delayMs = 120,
|
||||
) {
|
||||
const timers = getTimerApi(runtime);
|
||||
if (hideState.scheduledTimer) {
|
||||
timers.clearTimeout(hideState.scheduledTimer);
|
||||
hideState.scheduledTimer = null;
|
||||
}
|
||||
|
||||
const snapshot = normalizeHideSettings(settings);
|
||||
hideState.scheduledTimer = timers.setTimeout(() => {
|
||||
hideState.scheduledTimer = null;
|
||||
applyHideSettings(snapshot, runtime);
|
||||
}, Math.max(0, Math.trunc(Number(delayMs) || 0)));
|
||||
}
|
||||
|
||||
export function unhideAll(runtime = {}) {
|
||||
const timers = getTimerApi(runtime);
|
||||
if (hideState.scheduledTimer) {
|
||||
timers.clearTimeout(hideState.scheduledTimer);
|
||||
hideState.scheduledTimer = null;
|
||||
}
|
||||
|
||||
const managedChat = hideState.managedChatRef;
|
||||
const { shownCount } = unhideTrackedChat(managedChat, runtime);
|
||||
hideState.hiddenIndices.clear();
|
||||
hideState.lastProcessedLength = Array.isArray(managedChat)
|
||||
? managedChat.length
|
||||
: 0;
|
||||
|
||||
return {
|
||||
active: false,
|
||||
shownCount,
|
||||
managedCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
export function resetHideState(runtime = {}) {
|
||||
const timers = getTimerApi(runtime);
|
||||
if (hideState.scheduledTimer) {
|
||||
timers.clearTimeout(hideState.scheduledTimer);
|
||||
hideState.scheduledTimer = null;
|
||||
}
|
||||
|
||||
const managedChat = hideState.managedChatRef;
|
||||
unhideTrackedChat(managedChat, runtime);
|
||||
hideState.managedChatRef = null;
|
||||
hideState.hiddenIndices.clear();
|
||||
hideState.lastProcessedLength = 0;
|
||||
}
|
||||
|
||||
export function getHideStateSnapshot() {
|
||||
return {
|
||||
hasManagedChat: Boolean(hideState.managedChatRef),
|
||||
managedHiddenCount: hideState.hiddenIndices.size,
|
||||
lastProcessedLength: hideState.lastProcessedLength,
|
||||
scheduled: Boolean(hideState.scheduledTimer),
|
||||
};
|
||||
}
|
||||
182
index.js
182
index.js
@@ -81,6 +81,14 @@ import {
|
||||
writeChatMetadataPatch,
|
||||
writeGraphShadowSnapshot,
|
||||
} from "./graph-persistence.js";
|
||||
import {
|
||||
applyHideSettings,
|
||||
getHideStateSnapshot,
|
||||
resetHideState,
|
||||
runIncrementalHideCheck,
|
||||
scheduleHideSettingsApply,
|
||||
unhideAll,
|
||||
} from "./hide-engine.js";
|
||||
import {
|
||||
createEmptyGraph,
|
||||
deserializeGraph,
|
||||
@@ -333,6 +341,8 @@ function readRuntimeDebugSnapshot() {
|
||||
const defaultSettings = {
|
||||
enabled: true,
|
||||
timeoutMs: 300000,
|
||||
hideOldMessagesEnabled: false,
|
||||
hideOldMessagesKeepLastN: 12,
|
||||
|
||||
// 提取设置
|
||||
extractEvery: 1, // 每 N 条 assistant 回复提取一次
|
||||
@@ -2246,6 +2256,105 @@ function getSettings() {
|
||||
return mergedSettings;
|
||||
}
|
||||
|
||||
function getMessageHideSettings(settings = null) {
|
||||
let sourceSettings = settings;
|
||||
if (!sourceSettings || typeof sourceSettings !== "object") {
|
||||
try {
|
||||
sourceSettings =
|
||||
typeof getSettings === "function" ? getSettings() : {};
|
||||
} catch {
|
||||
sourceSettings = {};
|
||||
}
|
||||
}
|
||||
return {
|
||||
enabled: Boolean(sourceSettings.hideOldMessagesEnabled),
|
||||
hide_last_n: Math.max(
|
||||
0,
|
||||
Math.trunc(Number(sourceSettings.hideOldMessagesKeepLastN ?? 0) || 0),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function getHideRuntimeAdapters() {
|
||||
return {
|
||||
$,
|
||||
clearTimeout,
|
||||
getContext,
|
||||
setTimeout,
|
||||
};
|
||||
}
|
||||
|
||||
function applyMessageHideNow(reason = "manual-apply") {
|
||||
try {
|
||||
const result = applyHideSettings(
|
||||
getMessageHideSettings(),
|
||||
getHideRuntimeAdapters(),
|
||||
);
|
||||
console.log("[ST-BME] 已应用旧楼层隐藏:", reason, result);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.warn("[ST-BME] 应用旧楼层隐藏失败:", reason, error);
|
||||
return {
|
||||
active: false,
|
||||
error: error instanceof Error ? error.message : String(error || "未知错误"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleMessageHideApply(reason = "scheduled", delayMs = 120) {
|
||||
try {
|
||||
scheduleHideSettingsApply(
|
||||
getMessageHideSettings(),
|
||||
getHideRuntimeAdapters(),
|
||||
delayMs,
|
||||
);
|
||||
} catch (error) {
|
||||
console.warn("[ST-BME] 调度旧楼层隐藏失败:", reason, error);
|
||||
}
|
||||
}
|
||||
|
||||
function runIncrementalMessageHide(reason = "incremental") {
|
||||
try {
|
||||
const result = runIncrementalHideCheck(
|
||||
getMessageHideSettings(),
|
||||
getHideRuntimeAdapters(),
|
||||
);
|
||||
if (result?.active) {
|
||||
console.log("[ST-BME] 已增量更新旧楼层隐藏:", reason, result);
|
||||
}
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.warn("[ST-BME] 增量更新旧楼层隐藏失败:", reason, error);
|
||||
return {
|
||||
active: false,
|
||||
error: error instanceof Error ? error.message : String(error || "未知错误"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function clearMessageHideState(reason = "reset") {
|
||||
try {
|
||||
resetHideState(getHideRuntimeAdapters());
|
||||
console.log("[ST-BME] 已重置旧楼层隐藏状态:", reason);
|
||||
} catch (error) {
|
||||
console.warn("[ST-BME] 重置旧楼层隐藏状态失败:", reason, error);
|
||||
}
|
||||
}
|
||||
|
||||
function clearAllHiddenMessages(reason = "manual-clear") {
|
||||
try {
|
||||
const result = unhideAll(getHideRuntimeAdapters());
|
||||
console.log("[ST-BME] 已取消全部旧楼层隐藏:", reason, result);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.warn("[ST-BME] 取消全部旧楼层隐藏失败:", reason, error);
|
||||
return {
|
||||
active: false,
|
||||
error: error instanceof Error ? error.message : String(error || "未知错误"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function initializeHostCapabilityBridge(options = {}) {
|
||||
try {
|
||||
initializeHostAdapter({
|
||||
@@ -2324,6 +2433,7 @@ export function getPanelRuntimeDebugSnapshot(options = {}) {
|
||||
|
||||
return {
|
||||
hostCapabilities,
|
||||
messageHiding: getHideStateSnapshot(),
|
||||
runtimeDebug: readRuntimeDebugSnapshot(),
|
||||
};
|
||||
}
|
||||
@@ -4732,6 +4842,10 @@ function updateModuleSettings(patch = {}) {
|
||||
"embeddingBackendApiUrl",
|
||||
"embeddingAutoSuffix",
|
||||
]);
|
||||
const messageHideKeys = new Set([
|
||||
"hideOldMessagesEnabled",
|
||||
"hideOldMessagesKeepLastN",
|
||||
]);
|
||||
const settings = getSettings();
|
||||
Object.assign(settings, patch);
|
||||
extension_settings[MODULE_NAME] = settings;
|
||||
@@ -4779,6 +4893,15 @@ function updateModuleSettings(patch = {}) {
|
||||
);
|
||||
}
|
||||
|
||||
if (Object.keys(patch).some((key) => messageHideKeys.has(key))) {
|
||||
const hideSettings = getMessageHideSettings(settings);
|
||||
if (!hideSettings.enabled || hideSettings.hide_last_n <= 0) {
|
||||
clearAllHiddenMessages("settings-updated-disable");
|
||||
} else {
|
||||
scheduleMessageHideApply("settings-updated", 30);
|
||||
}
|
||||
}
|
||||
|
||||
scheduleServerSettingsSave();
|
||||
return settings;
|
||||
}
|
||||
@@ -7752,6 +7875,9 @@ async function runRecall(options = {}) {
|
||||
// ==================== 事件钩子 ====================
|
||||
|
||||
function onChatChanged() {
|
||||
if (typeof clearMessageHideState === "function") {
|
||||
clearMessageHideState("chat-changed");
|
||||
}
|
||||
const result = onChatChangedController({
|
||||
abortAllRunningStages,
|
||||
clearCoreEventBindingState,
|
||||
@@ -7796,6 +7922,10 @@ function onChatChanged() {
|
||||
}
|
||||
});
|
||||
|
||||
if (typeof scheduleMessageHideApply === "function") {
|
||||
scheduleMessageHideApply("chat-changed", 220);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -7817,11 +7947,15 @@ function onChatLoaded() {
|
||||
}
|
||||
});
|
||||
|
||||
if (typeof scheduleMessageHideApply === "function") {
|
||||
scheduleMessageHideApply("chat-loaded", 180);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function onMessageSent(messageId) {
|
||||
return onMessageSentController(
|
||||
const result = onMessageSentController(
|
||||
{
|
||||
getContext,
|
||||
recordRecallSentUserMessage,
|
||||
@@ -7829,10 +7963,14 @@ function onMessageSent(messageId) {
|
||||
},
|
||||
messageId,
|
||||
);
|
||||
if (typeof scheduleMessageHideApply === "function") {
|
||||
scheduleMessageHideApply("message-sent", 40);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function onMessageDeleted(chatLengthOrMessageId, meta = null) {
|
||||
return onMessageDeletedController(
|
||||
const result = onMessageDeletedController(
|
||||
{
|
||||
invalidateRecallAfterHistoryMutation,
|
||||
refreshPersistedRecallMessageUi: schedulePersistedRecallMessageUiRefresh,
|
||||
@@ -7841,10 +7979,14 @@ function onMessageDeleted(chatLengthOrMessageId, meta = null) {
|
||||
chatLengthOrMessageId,
|
||||
meta,
|
||||
);
|
||||
if (typeof scheduleMessageHideApply === "function") {
|
||||
scheduleMessageHideApply("message-deleted", 80);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function onMessageEdited(messageId, meta = null) {
|
||||
return onMessageEditedController(
|
||||
const result = onMessageEditedController(
|
||||
{
|
||||
invalidateRecallAfterHistoryMutation,
|
||||
refreshPersistedRecallMessageUi: schedulePersistedRecallMessageUiRefresh,
|
||||
@@ -7853,10 +7995,14 @@ function onMessageEdited(messageId, meta = null) {
|
||||
messageId,
|
||||
meta,
|
||||
);
|
||||
if (typeof scheduleMessageHideApply === "function") {
|
||||
scheduleMessageHideApply("message-edited", 80);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function onMessageSwiped(messageId, meta = null) {
|
||||
return onMessageSwipedController(
|
||||
const result = onMessageSwipedController(
|
||||
{
|
||||
invalidateRecallAfterHistoryMutation,
|
||||
refreshPersistedRecallMessageUi: schedulePersistedRecallMessageUiRefresh,
|
||||
@@ -7865,6 +8011,10 @@ function onMessageSwiped(messageId, meta = null) {
|
||||
messageId,
|
||||
meta,
|
||||
);
|
||||
if (typeof scheduleMessageHideApply === "function") {
|
||||
scheduleMessageHideApply("message-swiped", 80);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function onGenerationStarted(type, params = {}, dryRun = false) {
|
||||
@@ -7882,6 +8032,12 @@ function onGenerationStarted(type, params = {}, dryRun = false) {
|
||||
);
|
||||
}
|
||||
|
||||
function onGenerationEnded(_chatLength = null) {
|
||||
if (typeof scheduleMessageHideApply === "function") {
|
||||
scheduleMessageHideApply("generation-ended", 180);
|
||||
}
|
||||
}
|
||||
|
||||
async function onGenerationAfterCommands(type, params = {}, dryRun = false) {
|
||||
return await onGenerationAfterCommandsController(
|
||||
{
|
||||
@@ -7926,7 +8082,7 @@ async function onBeforeCombinePrompts(promptData = null) {
|
||||
}
|
||||
|
||||
function onMessageReceived(messageId = null, type = "") {
|
||||
return onMessageReceivedController({
|
||||
const result = onMessageReceivedController({
|
||||
console,
|
||||
createRecallInputRecord,
|
||||
getContext,
|
||||
@@ -7951,6 +8107,20 @@ function onMessageReceived(messageId = null, type = "") {
|
||||
pendingRecallSendIntent = record;
|
||||
},
|
||||
}, messageId, type);
|
||||
|
||||
const hideSettings =
|
||||
typeof getMessageHideSettings === "function"
|
||||
? getMessageHideSettings()
|
||||
: null;
|
||||
if (
|
||||
hideSettings?.enabled &&
|
||||
hideSettings?.hide_last_n > 0 &&
|
||||
typeof runIncrementalMessageHide === "function"
|
||||
) {
|
||||
runIncrementalMessageHide("message-received");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ==================== UI 操作 ====================
|
||||
@@ -8245,6 +8415,7 @@ async function onReembedDirect() {
|
||||
initializeHostCapabilityBridge();
|
||||
installSendIntentHooks();
|
||||
autoSyncOnVisibility(buildBmeSyncRuntimeOptions());
|
||||
scheduleMessageHideApply("init", 180);
|
||||
|
||||
// 注册事件钩子
|
||||
registerCoreEventHooksController({
|
||||
@@ -8257,6 +8428,7 @@ async function onReembedDirect() {
|
||||
onChatChanged,
|
||||
onChatLoaded,
|
||||
onGenerationAfterCommands,
|
||||
onGenerationEnded,
|
||||
onGenerationStarted,
|
||||
onMessageDeleted,
|
||||
onMessageEdited,
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
"test:p0": "node tests/p0-regressions.mjs",
|
||||
"test:runtime-history": "node tests/runtime-history.mjs",
|
||||
"test:graph-persistence": "node tests/graph-persistence.mjs",
|
||||
"test:hide-engine": "node tests/hide-engine.mjs",
|
||||
"test:indexeddb-persistence": "node tests/indexeddb-persistence.mjs",
|
||||
"test:indexeddb-sync": "node tests/indexeddb-sync.mjs",
|
||||
"test:indexeddb-migration": "node tests/indexeddb-migration.mjs",
|
||||
"test:indexeddb": "npm run test:indexeddb-persistence && npm run test:indexeddb-sync && npm run test:indexeddb-migration",
|
||||
"test:persistence-matrix": "npm run test:p0 && npm run test:runtime-history && npm run test:graph-persistence && npm run test:indexeddb",
|
||||
"test:all": "npm run test:persistence-matrix",
|
||||
"check": "node --check index.js && node --check bme-db.js && node --check panel.js && node --check ui-status.js && node --check event-binding.js"
|
||||
"check": "node --check index.js && node --check bme-db.js && node --check hide-engine.js && node --check panel.js && node --check ui-status.js && node --check event-binding.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"triviumdb": "^0.4.41"
|
||||
|
||||
60
panel.html
60
panel.html
@@ -952,6 +952,66 @@
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bme-config-card">
|
||||
<div class="bme-config-card-head">
|
||||
<div>
|
||||
<div class="bme-config-card-title">隐藏旧楼层</div>
|
||||
<div class="bme-config-card-subtitle">
|
||||
不删除聊天内容,只是把较早楼层临时标成系统消息,让主回复和 ST-BME 自己读取聊天时一起跳过它们。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<label
|
||||
class="bme-toggle-item"
|
||||
for="bme-setting-hide-old-messages-enabled"
|
||||
>
|
||||
<span class="bme-toggle-copy">
|
||||
<span class="bme-toggle-title">启用旧楼层隐藏</span>
|
||||
<span class="bme-toggle-desc">
|
||||
适合长聊天控 token。切聊天会自动重新应用,不会删除原楼层。
|
||||
</span>
|
||||
</span>
|
||||
<input
|
||||
id="bme-setting-hide-old-messages-enabled"
|
||||
type="checkbox"
|
||||
/>
|
||||
</label>
|
||||
<div class="bme-config-row">
|
||||
<label for="bme-setting-hide-old-messages-keep-last-n">
|
||||
保留最近 N 条消息
|
||||
</label>
|
||||
<input
|
||||
id="bme-setting-hide-old-messages-keep-last-n"
|
||||
class="bme-config-input"
|
||||
type="number"
|
||||
min="0"
|
||||
max="200"
|
||||
placeholder="0 = 不隐藏"
|
||||
/>
|
||||
</div>
|
||||
<div class="bme-config-help">
|
||||
设置修改后会自动生效。`0` 表示不隐藏;“取消全部隐藏”会立即把当前聊天里由 ST-BME 隐藏的楼层恢复。
|
||||
</div>
|
||||
<div class="bme-config-actions">
|
||||
<button
|
||||
class="bme-config-secondary-btn"
|
||||
id="bme-apply-hide-settings"
|
||||
type="button"
|
||||
>
|
||||
<i class="fa-solid fa-eye-slash"></i>
|
||||
<span>重新应用当前隐藏</span>
|
||||
</button>
|
||||
<button
|
||||
class="bme-config-secondary-btn"
|
||||
id="bme-clear-hide-settings"
|
||||
type="button"
|
||||
>
|
||||
<i class="fa-solid fa-eye"></i>
|
||||
<span>取消全部隐藏</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
39
panel.js
39
panel.js
@@ -1420,6 +1420,10 @@ function _refreshConfigTab() {
|
||||
const settings = _getSettings?.() || {};
|
||||
|
||||
_setCheckboxValue("bme-setting-enabled", settings.enabled ?? true);
|
||||
_setCheckboxValue(
|
||||
"bme-setting-hide-old-messages-enabled",
|
||||
settings.hideOldMessagesEnabled ?? false,
|
||||
);
|
||||
_setCheckboxValue(
|
||||
"bme-setting-recall-enabled",
|
||||
settings.recallEnabled ?? true,
|
||||
@@ -1495,6 +1499,10 @@ function _refreshConfigTab() {
|
||||
);
|
||||
|
||||
_setInputValue("bme-setting-extract-every", settings.extractEvery ?? 1);
|
||||
_setInputValue(
|
||||
"bme-setting-hide-old-messages-keep-last-n",
|
||||
settings.hideOldMessagesKeepLastN ?? 12,
|
||||
);
|
||||
_setInputValue(
|
||||
"bme-setting-extract-context-turns",
|
||||
settings.extractContextTurns ?? 2,
|
||||
@@ -1695,6 +1703,9 @@ function _bindConfigControls() {
|
||||
_patchSettings({ enabled: checked });
|
||||
_refreshGuardedConfigStates();
|
||||
});
|
||||
bindCheckbox("bme-setting-hide-old-messages-enabled", (checked) => {
|
||||
_patchSettings({ hideOldMessagesEnabled: checked });
|
||||
});
|
||||
bindCheckbox("bme-setting-recall-enabled", (checked) => {
|
||||
_patchSettings({ recallEnabled: checked });
|
||||
_refreshGuardedConfigStates();
|
||||
@@ -1768,6 +1779,13 @@ function _bindConfigControls() {
|
||||
bindNumber("bme-setting-extract-every", 1, 1, 50, (value) =>
|
||||
_patchSettings({ extractEvery: value }),
|
||||
);
|
||||
bindNumber(
|
||||
"bme-setting-hide-old-messages-keep-last-n",
|
||||
12,
|
||||
0,
|
||||
200,
|
||||
(value) => _patchSettings({ hideOldMessagesKeepLastN: value }),
|
||||
);
|
||||
bindNumber("bme-setting-extract-context-turns", 2, 0, 20, (value) =>
|
||||
_patchSettings({ extractContextTurns: value }),
|
||||
);
|
||||
@@ -2024,6 +2042,27 @@ function _bindConfigControls() {
|
||||
card.dataset.bmeBound = "true";
|
||||
});
|
||||
|
||||
document
|
||||
.getElementById("bme-apply-hide-settings")
|
||||
?.addEventListener("click", () => {
|
||||
const settings = _getSettings?.() || {};
|
||||
_patchSettings({
|
||||
hideOldMessagesEnabled: settings.hideOldMessagesEnabled ?? false,
|
||||
hideOldMessagesKeepLastN: settings.hideOldMessagesKeepLastN ?? 12,
|
||||
});
|
||||
toastr.success("当前聊天的隐藏设置已重新应用", "ST-BME");
|
||||
});
|
||||
document
|
||||
.getElementById("bme-clear-hide-settings")
|
||||
?.addEventListener("click", () => {
|
||||
_patchSettings({
|
||||
hideOldMessagesEnabled: false,
|
||||
hideOldMessagesKeepLastN: 0,
|
||||
});
|
||||
_setCheckboxValue("bme-setting-hide-old-messages-enabled", false);
|
||||
_setInputValue("bme-setting-hide-old-messages-keep-last-n", 0);
|
||||
toastr.info("已取消当前聊天里由 ST-BME 应用的隐藏", "ST-BME");
|
||||
});
|
||||
document
|
||||
.getElementById("bme-test-llm")
|
||||
?.addEventListener("click", async () => {
|
||||
|
||||
119
tests/hide-engine.mjs
Normal file
119
tests/hide-engine.mjs
Normal file
@@ -0,0 +1,119 @@
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import {
|
||||
applyHideSettings,
|
||||
getHideStateSnapshot,
|
||||
resetHideState,
|
||||
runIncrementalHideCheck,
|
||||
unhideAll,
|
||||
} from "../hide-engine.js";
|
||||
|
||||
function createRuntime(chat) {
|
||||
const domWrites = [];
|
||||
return {
|
||||
chat,
|
||||
domWrites,
|
||||
getContext() {
|
||||
return { chat: this.chat };
|
||||
},
|
||||
$(selector) {
|
||||
return {
|
||||
attr(name, value) {
|
||||
domWrites.push({ selector, name, value });
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function testApplyAndUnhidePreservesOriginalSystemMessages() {
|
||||
const chat = [
|
||||
{ mes: "原系统", is_system: true },
|
||||
{ mes: "用户1", is_user: true, is_system: false },
|
||||
{ mes: "助手1", is_user: false, is_system: false },
|
||||
{ mes: "用户2", is_user: true, is_system: false },
|
||||
{ mes: "助手2", is_user: false, is_system: false },
|
||||
];
|
||||
const runtime = createRuntime(chat);
|
||||
|
||||
const applyResult = applyHideSettings(
|
||||
{ enabled: true, hide_last_n: 2 },
|
||||
runtime,
|
||||
);
|
||||
assert.equal(applyResult.active, true);
|
||||
assert.equal(chat[0].is_system, true);
|
||||
assert.equal(chat[1].is_system, true);
|
||||
assert.equal(chat[2].is_system, true);
|
||||
assert.equal(chat[3].is_system, false);
|
||||
assert.equal(chat[4].is_system, false);
|
||||
assert.equal(applyResult.managedCount, 2);
|
||||
|
||||
const unhideResult = unhideAll(runtime);
|
||||
assert.equal(unhideResult.active, false);
|
||||
assert.equal(chat[0].is_system, true, "原系统消息不应被恢复");
|
||||
assert.equal(chat[1].is_system, false);
|
||||
assert.equal(chat[2].is_system, false);
|
||||
}
|
||||
|
||||
function testResetRestoresPreviousManagedChat() {
|
||||
const oldChat = [
|
||||
{ mes: "用户1", is_user: true, is_system: false },
|
||||
{ mes: "助手1", is_user: false, is_system: false },
|
||||
{ mes: "用户2", is_user: true, is_system: false },
|
||||
{ mes: "助手2", is_user: false, is_system: false },
|
||||
];
|
||||
const newChat = [
|
||||
{ mes: "新用户", is_user: true, is_system: false },
|
||||
{ mes: "新助手", is_user: false, is_system: false },
|
||||
];
|
||||
const runtime = createRuntime(oldChat);
|
||||
|
||||
applyHideSettings({ enabled: true, hide_last_n: 1 }, runtime);
|
||||
assert.equal(oldChat[0].is_system, true);
|
||||
assert.equal(oldChat[1].is_system, true);
|
||||
assert.equal(oldChat[2].is_system, true);
|
||||
|
||||
runtime.chat = newChat;
|
||||
resetHideState(runtime);
|
||||
|
||||
assert.equal(oldChat[0].is_system, false);
|
||||
assert.equal(oldChat[1].is_system, false);
|
||||
assert.equal(oldChat[2].is_system, false);
|
||||
assert.deepEqual(getHideStateSnapshot(), {
|
||||
hasManagedChat: false,
|
||||
managedHiddenCount: 0,
|
||||
lastProcessedLength: 0,
|
||||
scheduled: false,
|
||||
});
|
||||
}
|
||||
|
||||
function testIncrementalHideOnlyHidesNewOverflowMessages() {
|
||||
const chat = [
|
||||
{ mes: "用户1", is_user: true, is_system: false },
|
||||
{ mes: "助手1", is_user: false, is_system: false },
|
||||
{ mes: "用户2", is_user: true, is_system: false },
|
||||
];
|
||||
const runtime = createRuntime(chat);
|
||||
|
||||
applyHideSettings({ enabled: true, hide_last_n: 2 }, runtime);
|
||||
assert.equal(chat[0].is_system, true);
|
||||
assert.equal(chat[1].is_system, false);
|
||||
assert.equal(chat[2].is_system, false);
|
||||
|
||||
chat.push({ mes: "助手2", is_user: false, is_system: false });
|
||||
const result = runIncrementalHideCheck(
|
||||
{ enabled: true, hide_last_n: 2 },
|
||||
runtime,
|
||||
);
|
||||
assert.equal(result.incremental, true);
|
||||
assert.equal(result.hiddenCount, 1);
|
||||
assert.equal(chat[1].is_system, true);
|
||||
assert.equal(chat[2].is_system, false);
|
||||
assert.equal(chat[3].is_system, false);
|
||||
}
|
||||
|
||||
testApplyAndUnhidePreservesOriginalSystemMessages();
|
||||
testResetRestoresPreviousManagedChat();
|
||||
testIncrementalHideOnlyHidesNewOverflowMessages();
|
||||
|
||||
console.log("hide-engine tests passed");
|
||||
Reference in New Issue
Block a user