feat: add message hiding assistant

This commit is contained in:
Youzini-afk
2026-04-02 16:05:29 +08:00
parent c711ff17f7
commit 03ec52d4c5
7 changed files with 703 additions and 6 deletions

303
hide-engine.js Normal file
View 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),
};
}