Restore extraction visibility for managed hidden messages

This commit is contained in:
Youzini-afk
2026-04-03 14:27:28 +08:00
parent 48c8a7169c
commit 64188c2559
4 changed files with 127 additions and 27 deletions

View File

@@ -5,8 +5,20 @@
import { clampInt } from "./ui-status.js";
import { sanitizePlannerMessageText } from "./planner-tag-utils.js";
import { rollbackBatch } from "./runtime-state.js";
import { isInManagedHideRange } from "./hide-engine.js";
export function isBmeManagedHiddenMessage(
message,
{ index = null, chat = null } = {},
) {
if (
Number.isFinite(index) &&
index > 0 &&
isInManagedHideRange(index, chat)
) {
return true;
}
export function isBmeManagedHiddenMessage(message) {
return Boolean(
message?.extra &&
typeof message.extra === "object" &&
@@ -14,15 +26,24 @@ export function isBmeManagedHiddenMessage(message) {
);
}
export function isSystemMessageForExtraction(message) {
return Boolean(message?.is_system) && !isBmeManagedHiddenMessage(message);
export function isSystemMessageForExtraction(
message,
{ index = null, chat = null } = {},
) {
if (!message?.is_system) return false;
if (Number.isFinite(index) && index === 0) return true;
return !isBmeManagedHiddenMessage(message, { index, chat });
}
export function isAssistantChatMessage(message) {
export function isAssistantChatMessage(
message,
{ index = null, chat = null } = {},
) {
return (
Boolean(message) &&
!message.is_user &&
!isSystemMessageForExtraction(message)
!isSystemMessageForExtraction(message, { index, chat })
);
}
@@ -30,7 +51,7 @@ export function getAssistantTurns(chat) {
const assistantTurns = [];
// 从 index 1 开始index 0 是角色卡首条消息greeting不参与提取
for (let index = 1; index < chat.length; index++) {
if (isAssistantChatMessage(chat[index])) {
if (isAssistantChatMessage(chat[index], { index, chat })) {
assistantTurns.push(index);
}
}
@@ -53,7 +74,7 @@ export function buildExtractionMessages(chat, startIdx, endIdx, settings) {
index++
) {
const msg = chat[index];
if (isSystemMessageForExtraction(msg)) continue;
if (isSystemMessageForExtraction(msg, { index, chat })) continue;
messages.push({
seq: index,
role: msg.is_user ? "user" : "assistant",
@@ -70,7 +91,7 @@ export function getChatIndexForPlayableSeq(chat, playableSeq) {
let currentSeq = -1;
for (let index = 0; index < chat.length; index++) {
const message = chat[index];
if (isSystemMessageForExtraction(message)) continue;
if (isSystemMessageForExtraction(message, { index, chat })) continue;
currentSeq++;
if (currentSeq >= playableSeq) {
return index;
@@ -85,7 +106,7 @@ export function getChatIndexForAssistantSeq(chat, assistantSeq) {
let currentSeq = -1;
for (let index = 0; index < chat.length; index++) {
if (!isAssistantChatMessage(chat[index])) continue;
if (!isAssistantChatMessage(chat[index], { index, chat })) continue;
currentSeq++;
if (currentSeq >= assistantSeq) {
return index;

View File

@@ -546,3 +546,11 @@ export function getHideStateSnapshot() {
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);
}

View File

@@ -4360,22 +4360,6 @@ function notifyExtractionIssue(message, title = "ST-BME 提取提示") {
toastr.warning(message, title, { timeOut: 4500 });
}
function settleExtractionStatusAfterHistoryRecovery(
text = "提取完成",
meta = "",
level = "success",
) {
const currentText = String(lastExtractionStatus?.text || "");
const currentLevel = String(lastExtractionStatus?.level || "");
if (currentText !== "AI 生成中" && currentLevel !== "running") {
return;
}
setLastExtractionStatus(text, meta, level, {
syncRuntime: true,
toastKind: "",
});
}
async function fetchLocalWithTimeout(
url,
options = {},
@@ -5611,8 +5595,12 @@ const DEFAULT_TRIGGER_KEYWORDS = [
export function getSmartTriggerDecision(chat, lastProcessed, settings) {
const pendingMessages = chat
.slice(Math.max(0, (lastProcessed ?? -1) + 1))
.filter((msg) => !isSystemMessageForExtraction(msg))
.map((msg) => ({
.map((msg, offset) => ({
msg,
index: Math.max(0, (lastProcessed ?? -1) + 1) + offset,
}))
.filter(({ msg, index }) => !isSystemMessageForExtraction(msg, { index, chat }))
.map(({ msg }) => ({
role: msg.is_user ? "user" : "assistant",
content: msg.mes || "",
}))
@@ -7879,6 +7867,30 @@ async function recoverHistoryIfNeeded(trigger = "history-recovery") {
}
}
function settleExtractionStatusAfterHistoryRecovery(
text = "提取完成",
meta = "",
level = "success",
) {
const statusSnapshot =
typeof lastExtractionStatus === "object" && lastExtractionStatus
? lastExtractionStatus
: null;
if (!statusSnapshot || typeof setLastExtractionStatus !== "function") {
return;
}
const currentText = String(statusSnapshot.text || "");
const currentLevel = String(statusSnapshot.level || "");
if (currentText !== "AI 生成中" && currentLevel !== "running") {
return;
}
setLastExtractionStatus(text, meta, level, {
syncRuntime: true,
toastKind: "",
});
}
/**
* 提取管线:处理未提取的对话楼层
*/

View File

@@ -1,4 +1,9 @@
import assert from "node:assert/strict";
import {
applyHideSettings,
isInManagedHideRange,
resetHideState,
} from "../hide-engine.js";
import {
buildExtractionMessages,
getAssistantTurns,
@@ -32,6 +37,23 @@ const realSystemMessage = {
assert.equal(isSystemMessageForExtraction(realSystemMessage), true);
assert.equal(isAssistantChatMessage(realSystemMessage), false);
function createRuntime(chat, chatId = "chat-a") {
return {
chat,
chatId,
async executeSlashCommands() {
return "";
},
getContext() {
return {
chat: this.chat,
chatId: this.chatId,
executeSlashCommands: this.executeSlashCommands.bind(this),
};
},
};
}
const chat = [
{ is_user: false, is_system: true, mes: "greeting/system" },
{ is_user: true, is_system: false, mes: "user-1" },
@@ -65,4 +87,41 @@ assert.deepEqual(
"extraction should keep BME-managed hidden context but still skip real system messages",
);
resetHideState();
const autoHiddenChat = [
{ is_user: false, is_system: true, mes: "greeting/system" },
{ is_user: true, is_system: false, mes: "user-1" },
{ is_user: false, is_system: false, mes: "assistant-1" },
{ is_user: true, is_system: false, mes: "user-2" },
{ is_user: false, is_system: false, mes: "assistant-2" },
{ is_user: true, is_system: false, mes: "user-3" },
{ is_user: false, is_system: false, mes: "assistant-3" },
];
await applyHideSettings(
{ enabled: true, hide_last_n: 2 },
createRuntime(autoHiddenChat),
);
assert.equal(
isInManagedHideRange(2, autoHiddenChat),
true,
"auto-hidden ordinary floors should be queryable from hide-engine managed range",
);
assert.equal(
isSystemMessageForExtraction(autoHiddenChat[2], {
index: 2,
chat: autoHiddenChat,
}),
false,
"auto-hidden ordinary floors inside managed range should remain extractable",
);
assert.equal(
isSystemMessageForExtraction(autoHiddenChat[0], {
index: 0,
chat: autoHiddenChat,
}),
true,
"greeting/system floor should still be treated as system even if hide range starts at 0",
);
console.log("chat-history tests passed");