mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 14:20:35 +08:00
569 lines
16 KiB
JavaScript
569 lines
16 KiB
JavaScript
// ST-BME: old-message hide engine
|
|
// Uses the host's native /hide and /unhide slash commands instead of
|
|
// mutating chat messages into is_system=true.
|
|
|
|
const hideState = {
|
|
managedChatRef: null,
|
|
managedChatKey: null,
|
|
managedSystemIndices: new Set(),
|
|
hiddenRangeEnd: -1,
|
|
lastProcessedLength: 0,
|
|
scheduledTimer: null,
|
|
operationVersion: 0,
|
|
};
|
|
|
|
const BME_HIDE_HASH_MARKER = "__st_bme_hide_managed";
|
|
|
|
function getTimerApi(runtime = {}) {
|
|
const rawSetTimeout =
|
|
typeof runtime.setTimeout === "function"
|
|
? runtime.setTimeout
|
|
: globalThis.setTimeout;
|
|
const rawClearTimeout =
|
|
typeof runtime.clearTimeout === "function"
|
|
? runtime.clearTimeout
|
|
: globalThis.clearTimeout;
|
|
|
|
return {
|
|
setTimeout(...args) {
|
|
return Reflect.apply(rawSetTimeout, globalThis, args);
|
|
},
|
|
clearTimeout(...args) {
|
|
return Reflect.apply(rawClearTimeout, globalThis, args);
|
|
},
|
|
};
|
|
}
|
|
|
|
function notifyRuntimeHideStateChanged(runtime = {}) {
|
|
try {
|
|
runtime.refreshPanelLiveState?.();
|
|
} catch {
|
|
}
|
|
}
|
|
|
|
function getCurrentContext(runtime = {}) {
|
|
try {
|
|
return typeof runtime.getContext === "function" ? runtime.getContext() : null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function getCurrentChatInfo(runtime = {}) {
|
|
const context = getCurrentContext(runtime);
|
|
return {
|
|
chat: Array.isArray(context?.chat) ? context.chat : null,
|
|
chatId:
|
|
context?.chatId != null && context.chatId !== ""
|
|
? String(context.chatId)
|
|
: null,
|
|
};
|
|
}
|
|
|
|
function getCurrentChatKey(runtime = {}) {
|
|
const { chat, chatId } = getCurrentChatInfo(runtime);
|
|
if (chatId) return chatId;
|
|
return Array.isArray(chat) ? chat : null;
|
|
}
|
|
|
|
function getSlashExecutor(runtime = {}) {
|
|
if (typeof runtime.executeSlashCommands === "function") {
|
|
return runtime.executeSlashCommands.bind(runtime);
|
|
}
|
|
|
|
const context = getCurrentContext(runtime);
|
|
if (typeof context?.executeSlashCommands === "function") {
|
|
return context.executeSlashCommands.bind(context);
|
|
}
|
|
|
|
if (typeof globalThis.executeSlashCommands === "function") {
|
|
return globalThis.executeSlashCommands.bind(globalThis);
|
|
}
|
|
|
|
if (typeof globalThis.executeSlashCommandsOnChatInput === "function") {
|
|
return globalThis.executeSlashCommandsOnChatInput.bind(globalThis);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
async function executeSlashCommand(command, runtime = {}) {
|
|
const executor = getSlashExecutor(runtime);
|
|
if (!executor) {
|
|
throw new Error("executeSlashCommands is not available");
|
|
}
|
|
|
|
return await executor(command, true);
|
|
}
|
|
|
|
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 getJquery(runtime = {}) {
|
|
if (typeof runtime.$ === "function") return runtime.$;
|
|
if (typeof globalThis.$ === "function") return globalThis.$;
|
|
return null;
|
|
}
|
|
|
|
function syncSystemAttribute(chat, indices = [], value = "true", runtime = {}) {
|
|
if (!Array.isArray(chat) || !Array.isArray(indices) || indices.length === 0) {
|
|
return;
|
|
}
|
|
|
|
const currentChat = getCurrentChatInfo(runtime).chat;
|
|
if (currentChat !== 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 calcHideRange(chatLength, hideLastN) {
|
|
if (!Number.isFinite(chatLength) || chatLength <= 0 || hideLastN <= 0) {
|
|
return null;
|
|
}
|
|
|
|
const visibleStart =
|
|
hideLastN >= chatLength ? 0 : Math.max(0, chatLength - hideLastN);
|
|
const hideEnd = visibleStart - 1;
|
|
if (hideEnd < 0) return null;
|
|
|
|
return {
|
|
start: 0,
|
|
end: hideEnd,
|
|
};
|
|
}
|
|
|
|
function beginOperation() {
|
|
hideState.operationVersion += 1;
|
|
return hideState.operationVersion;
|
|
}
|
|
|
|
function isOperationCurrent(version) {
|
|
return version === hideState.operationVersion;
|
|
}
|
|
|
|
function clearScheduledTimer(runtime = {}) {
|
|
const timers = getTimerApi(runtime);
|
|
if (hideState.scheduledTimer) {
|
|
timers.clearTimeout(hideState.scheduledTimer);
|
|
hideState.scheduledTimer = null;
|
|
}
|
|
}
|
|
|
|
function clearManagedState() {
|
|
hideState.managedChatRef = null;
|
|
hideState.managedChatKey = null;
|
|
hideState.managedSystemIndices.clear();
|
|
hideState.hiddenRangeEnd = -1;
|
|
hideState.lastProcessedLength = 0;
|
|
}
|
|
|
|
function isManagedSystemMessage(message) {
|
|
return Boolean(
|
|
message?.is_system === true &&
|
|
message?.extra &&
|
|
typeof message.extra === "object" &&
|
|
message.extra[BME_HIDE_HASH_MARKER] === true,
|
|
);
|
|
}
|
|
|
|
function collectManagedSystemIndices(chat) {
|
|
if (!Array.isArray(chat) || chat.length === 0) return [];
|
|
const indices = [];
|
|
for (let index = 0; index < chat.length; index++) {
|
|
if (isManagedSystemMessage(chat[index])) {
|
|
indices.push(index);
|
|
}
|
|
}
|
|
return indices;
|
|
}
|
|
|
|
function hydrateManagedStateFromChat(
|
|
chat,
|
|
chatKey = getCurrentChatKey(),
|
|
{ bootstrapLength = false } = {},
|
|
) {
|
|
if (!Array.isArray(chat)) {
|
|
hideState.managedSystemIndices.clear();
|
|
hideState.hiddenRangeEnd = -1;
|
|
return { managedCount: 0, hiddenRangeEnd: -1 };
|
|
}
|
|
|
|
const managedIndices = collectManagedSystemIndices(chat);
|
|
hideState.managedSystemIndices.clear();
|
|
for (const index of managedIndices) {
|
|
hideState.managedSystemIndices.add(index);
|
|
}
|
|
|
|
hideState.managedChatRef = chat;
|
|
hideState.managedChatKey = chatKey;
|
|
hideState.hiddenRangeEnd =
|
|
managedIndices.length > 0 ? managedIndices[managedIndices.length - 1] : -1;
|
|
if (managedIndices.length > 0 && bootstrapLength) {
|
|
hideState.lastProcessedLength = chat.length;
|
|
}
|
|
|
|
return {
|
|
managedCount: managedIndices.length,
|
|
hiddenRangeEnd: hideState.hiddenRangeEnd,
|
|
};
|
|
}
|
|
|
|
function restoreManagedSystemFlags(chat, runtime = {}) {
|
|
if (!Array.isArray(chat)) {
|
|
hideState.managedSystemIndices.clear();
|
|
return 0;
|
|
}
|
|
|
|
if (hideState.managedSystemIndices.size === 0) {
|
|
hydrateManagedStateFromChat(chat, getCurrentChatKey(runtime), {
|
|
bootstrapLength: false,
|
|
});
|
|
}
|
|
if (hideState.managedSystemIndices.size === 0) {
|
|
return 0;
|
|
}
|
|
|
|
const restored = [];
|
|
for (const index of hideState.managedSystemIndices) {
|
|
const message = chat[index];
|
|
if (!message || message.is_system !== true) continue;
|
|
message.is_system = false;
|
|
if (message.extra && typeof message.extra === "object") {
|
|
delete message.extra[BME_HIDE_HASH_MARKER];
|
|
if (Object.keys(message.extra).length === 0) {
|
|
delete message.extra;
|
|
}
|
|
}
|
|
restored.push(index);
|
|
}
|
|
|
|
syncSystemAttribute(chat, restored, "false", runtime);
|
|
hideState.managedSystemIndices.clear();
|
|
return restored.length;
|
|
}
|
|
|
|
function markManagedSystemRange(chat, start, end, runtime = {}) {
|
|
if (!Array.isArray(chat) || start > end) return 0;
|
|
|
|
const marked = [];
|
|
for (let index = start; index <= end && index < chat.length; index++) {
|
|
const message = chat[index];
|
|
if (!message || message.is_system === true) continue;
|
|
message.is_system = true;
|
|
const extra =
|
|
message.extra && typeof message.extra === "object" ? message.extra : {};
|
|
extra[BME_HIDE_HASH_MARKER] = true;
|
|
message.extra = extra;
|
|
hideState.managedSystemIndices.add(index);
|
|
marked.push(index);
|
|
}
|
|
|
|
syncSystemAttribute(chat, marked, "true", runtime);
|
|
return marked.length;
|
|
}
|
|
|
|
function adoptManagedChat(chat, chatKey, runtime = {}) {
|
|
const previousChat = hideState.managedChatRef;
|
|
if (previousChat && previousChat !== chat) {
|
|
restoreManagedSystemFlags(previousChat, runtime);
|
|
hideState.hiddenRangeEnd = -1;
|
|
hideState.lastProcessedLength = 0;
|
|
}
|
|
|
|
hideState.managedChatRef = chat;
|
|
hideState.managedChatKey = chatKey;
|
|
}
|
|
|
|
function buildResult({
|
|
active = false,
|
|
hiddenCount = 0,
|
|
shownCount = 0,
|
|
chatLength = 0,
|
|
incremental = false,
|
|
stale = false,
|
|
} = {}) {
|
|
return {
|
|
active,
|
|
hiddenCount,
|
|
shownCount,
|
|
managedCount: active ? hiddenCount : 0,
|
|
chatLength,
|
|
incremental,
|
|
stale,
|
|
};
|
|
}
|
|
|
|
async function unhideCurrentRange(runtime = {}, version = null, options = {}) {
|
|
const { chat } = getCurrentChatInfo(runtime);
|
|
const chatLength = Array.isArray(chat) ? chat.length : 0;
|
|
const full = Boolean(options.full);
|
|
const previousEnd = full
|
|
? Math.max(-1, chatLength - 1)
|
|
: Math.min(hideState.hiddenRangeEnd, chatLength - 1);
|
|
if (previousEnd < 0) {
|
|
return { shownCount: 0, chatLength };
|
|
}
|
|
|
|
await executeSlashCommand(`/unhide 0-${previousEnd}`, runtime);
|
|
if (!isOperationCurrent(version ?? hideState.operationVersion)) {
|
|
return { shownCount: 0, chatLength, stale: true };
|
|
}
|
|
|
|
return { shownCount: previousEnd + 1, chatLength };
|
|
}
|
|
|
|
async function runHideApply(settings = {}, runtime = {}, options = {}) {
|
|
const normalized = normalizeHideSettings(settings);
|
|
const { incrementalPreferred = false, version = beginOperation() } = options;
|
|
const chatInfo = getCurrentChatInfo(runtime);
|
|
const chat = chatInfo.chat;
|
|
const chatLength = Array.isArray(chat) ? chat.length : 0;
|
|
|
|
if (!chat || chatLength === 0) {
|
|
clearManagedState();
|
|
return buildResult();
|
|
}
|
|
|
|
const chatKey = getCurrentChatKey(runtime);
|
|
const previousChatKey = hideState.managedChatKey;
|
|
const hadTrackedState =
|
|
hideState.managedSystemIndices.size > 0 ||
|
|
hideState.hiddenRangeEnd >= 0 ||
|
|
(Number.isFinite(hideState.lastProcessedLength) &&
|
|
hideState.lastProcessedLength > 0);
|
|
adoptManagedChat(chat, chatKey, runtime);
|
|
hydrateManagedStateFromChat(chat, chatKey, {
|
|
bootstrapLength: !hadTrackedState,
|
|
});
|
|
const sameChat =
|
|
previousChatKey !== null && chatKey !== null && previousChatKey === chatKey;
|
|
const previousHiddenEnd = hideState.hiddenRangeEnd;
|
|
const previousLength =
|
|
sameChat && Number.isFinite(hideState.lastProcessedLength)
|
|
? hideState.lastProcessedLength
|
|
: 0;
|
|
hideState.lastProcessedLength = chatLength;
|
|
|
|
if (!normalized.enabled || normalized.hideLastN <= 0) {
|
|
if (previousHiddenEnd >= 0) {
|
|
const { shownCount } = await unhideCurrentRange(runtime, version);
|
|
if (!isOperationCurrent(version)) {
|
|
return buildResult({ chatLength, shownCount, stale: true });
|
|
}
|
|
restoreManagedSystemFlags(chat, runtime);
|
|
hideState.hiddenRangeEnd = -1;
|
|
return buildResult({ chatLength, shownCount });
|
|
}
|
|
|
|
restoreManagedSystemFlags(chat, runtime);
|
|
hideState.hiddenRangeEnd = -1;
|
|
return buildResult({ chatLength });
|
|
}
|
|
|
|
const nextRange = calcHideRange(chatLength, normalized.hideLastN);
|
|
if (!nextRange) {
|
|
if (previousHiddenEnd >= 0) {
|
|
const { shownCount } = await unhideCurrentRange(runtime, version);
|
|
if (!isOperationCurrent(version)) {
|
|
return buildResult({ chatLength, shownCount, stale: true });
|
|
}
|
|
restoreManagedSystemFlags(chat, runtime);
|
|
hideState.hiddenRangeEnd = -1;
|
|
return buildResult({ chatLength, shownCount });
|
|
}
|
|
|
|
restoreManagedSystemFlags(chat, runtime);
|
|
hideState.hiddenRangeEnd = -1;
|
|
return buildResult({
|
|
active: true,
|
|
hiddenCount: 0,
|
|
chatLength,
|
|
});
|
|
}
|
|
|
|
if (
|
|
incrementalPreferred &&
|
|
sameChat &&
|
|
previousHiddenEnd >= 0 &&
|
|
chatLength > previousLength &&
|
|
previousLength > 0
|
|
) {
|
|
const previousRange = calcHideRange(previousLength, normalized.hideLastN);
|
|
const canExtendOnly =
|
|
previousRange &&
|
|
previousRange.end === previousHiddenEnd &&
|
|
nextRange.end >= previousHiddenEnd;
|
|
if (canExtendOnly && nextRange.end > previousHiddenEnd) {
|
|
const start = previousHiddenEnd + 1;
|
|
const end = nextRange.end;
|
|
await executeSlashCommand(`/hide ${start}-${end}`, runtime);
|
|
if (!isOperationCurrent(version)) {
|
|
return buildResult({ chatLength, stale: true });
|
|
}
|
|
|
|
markManagedSystemRange(chat, start, end, runtime);
|
|
hideState.hiddenRangeEnd = end;
|
|
return buildResult({
|
|
active: true,
|
|
hiddenCount: end + 1,
|
|
shownCount: 0,
|
|
chatLength,
|
|
incremental: true,
|
|
});
|
|
}
|
|
}
|
|
|
|
let shownCount = 0;
|
|
if (previousHiddenEnd >= 0) {
|
|
const unhideResult = await unhideCurrentRange(runtime, version);
|
|
if (!isOperationCurrent(version)) {
|
|
return buildResult({
|
|
chatLength,
|
|
shownCount: unhideResult.shownCount ?? 0,
|
|
stale: true,
|
|
});
|
|
}
|
|
shownCount = unhideResult.shownCount ?? 0;
|
|
}
|
|
restoreManagedSystemFlags(chat, runtime);
|
|
|
|
await executeSlashCommand(`/hide ${nextRange.start}-${nextRange.end}`, runtime);
|
|
if (!isOperationCurrent(version)) {
|
|
return buildResult({ chatLength, shownCount, stale: true });
|
|
}
|
|
|
|
markManagedSystemRange(chat, nextRange.start, nextRange.end, runtime);
|
|
hideState.hiddenRangeEnd = nextRange.end;
|
|
hideState.lastProcessedLength = chatLength;
|
|
|
|
return buildResult({
|
|
active: true,
|
|
hiddenCount: nextRange.end + 1,
|
|
shownCount,
|
|
chatLength,
|
|
incremental: false,
|
|
});
|
|
}
|
|
|
|
export async function runFullHideCheck(settings = {}, runtime = {}) {
|
|
return await runHideApply(settings, runtime, {
|
|
incrementalPreferred: false,
|
|
version: beginOperation(),
|
|
});
|
|
}
|
|
|
|
export async function runIncrementalHideCheck(settings = {}, runtime = {}) {
|
|
return await runHideApply(settings, runtime, {
|
|
incrementalPreferred: true,
|
|
version: beginOperation(),
|
|
});
|
|
}
|
|
|
|
export async function applyHideSettings(settings = {}, runtime = {}) {
|
|
return await runFullHideCheck(settings, runtime);
|
|
}
|
|
|
|
export function scheduleHideSettingsApply(
|
|
settings = {},
|
|
runtime = {},
|
|
delayMs = 120,
|
|
) {
|
|
clearScheduledTimer(runtime);
|
|
|
|
const timers = getTimerApi(runtime);
|
|
const snapshot = normalizeHideSettings(settings);
|
|
hideState.scheduledTimer = timers.setTimeout(() => {
|
|
hideState.scheduledTimer = null;
|
|
void applyHideSettings(snapshot, runtime)
|
|
.then(() => {
|
|
notifyRuntimeHideStateChanged(runtime);
|
|
})
|
|
.catch((error) => {
|
|
console.warn?.("[ST-BME] scheduled hide apply failed", error);
|
|
});
|
|
}, Math.max(0, Math.trunc(Number(delayMs) || 0)));
|
|
notifyRuntimeHideStateChanged(runtime);
|
|
}
|
|
|
|
export async function unhideAll(runtime = {}) {
|
|
clearScheduledTimer(runtime);
|
|
const version = beginOperation();
|
|
const chatInfo = getCurrentChatInfo(runtime);
|
|
const chatLength = Array.isArray(chatInfo.chat) ? chatInfo.chat.length : 0;
|
|
|
|
if (chatLength === 0) {
|
|
hideState.lastProcessedLength = chatLength;
|
|
hideState.hiddenRangeEnd = -1;
|
|
hideState.managedChatKey = getCurrentChatKey(runtime);
|
|
return buildResult({ chatLength });
|
|
}
|
|
|
|
hydrateManagedStateFromChat(chatInfo.chat, getCurrentChatKey(runtime), {
|
|
bootstrapLength: false,
|
|
});
|
|
const { shownCount } = await unhideCurrentRange(runtime, version, {
|
|
full: true,
|
|
});
|
|
if (!isOperationCurrent(version)) {
|
|
return buildResult({ chatLength, shownCount, stale: true });
|
|
}
|
|
|
|
restoreManagedSystemFlags(chatInfo.chat, runtime);
|
|
hideState.hiddenRangeEnd = -1;
|
|
hideState.lastProcessedLength = chatLength;
|
|
hideState.managedChatRef = chatInfo.chat;
|
|
hideState.managedChatKey = getCurrentChatKey(runtime);
|
|
|
|
return buildResult({ chatLength, shownCount });
|
|
}
|
|
|
|
export function resetHideState(runtime = {}) {
|
|
clearScheduledTimer(runtime);
|
|
beginOperation();
|
|
const chatInfo = getCurrentChatInfo(runtime);
|
|
if (Array.isArray(chatInfo.chat)) {
|
|
hydrateManagedStateFromChat(chatInfo.chat, chatInfo.chatId || null, {
|
|
bootstrapLength: false,
|
|
});
|
|
}
|
|
restoreManagedSystemFlags(hideState.managedChatRef, runtime);
|
|
clearManagedState();
|
|
}
|
|
|
|
export function getHideStateSnapshot() {
|
|
return {
|
|
hasManagedChat: hideState.managedChatRef !== null,
|
|
managedHiddenCount: hideState.hiddenRangeEnd >= 0 ? hideState.hiddenRangeEnd + 1 : 0,
|
|
lastProcessedLength: hideState.lastProcessedLength,
|
|
scheduled: Boolean(hideState.scheduledTimer),
|
|
};
|
|
}
|
|
|
|
export function isInManagedHideRange(index, chat = null) {
|
|
if (!Number.isFinite(index) || index < 0) return false;
|
|
if (!hideState.managedChatRef) return false;
|
|
if (Array.isArray(chat) && chat !== hideState.managedChatRef) return false;
|
|
|
|
return hideState.managedSystemIndices.has(index);
|
|
}
|