mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
122 lines
3.0 KiB
JavaScript
122 lines
3.0 KiB
JavaScript
import { isSystemMessageForExtraction } from "./chat-history.js";
|
||
|
||
export const DEFAULT_TRIGGER_KEYWORDS = [
|
||
"突然",
|
||
"没想到",
|
||
"原来",
|
||
"其实",
|
||
"发现",
|
||
"背叛",
|
||
"死亡",
|
||
"复活",
|
||
"恢复记忆",
|
||
"失忆",
|
||
"告白",
|
||
"暴露",
|
||
"秘密",
|
||
"计划",
|
||
"规则",
|
||
"契约",
|
||
"位置",
|
||
"地点",
|
||
"离开",
|
||
"来到",
|
||
];
|
||
|
||
export function getSmartTriggerDecision(
|
||
chat,
|
||
lastProcessed,
|
||
settings,
|
||
endFloor = null,
|
||
) {
|
||
const safeChat = Array.isArray(chat) ? chat : [];
|
||
const startFloor = Math.max(0, (lastProcessed ?? -1) + 1);
|
||
const normalizedEndFloor =
|
||
endFloor == null || endFloor === ""
|
||
? null
|
||
: Number.isFinite(Number(endFloor))
|
||
? Math.max(startFloor - 1, Math.floor(Number(endFloor)))
|
||
: null;
|
||
const pendingMessages = safeChat
|
||
.slice(
|
||
startFloor,
|
||
normalizedEndFloor == null ? undefined : normalizedEndFloor + 1,
|
||
)
|
||
.map((msg, offset) => ({
|
||
msg,
|
||
index: startFloor + offset,
|
||
}))
|
||
.filter(({ msg, index }) =>
|
||
!isSystemMessageForExtraction(msg, { index, chat: safeChat }),
|
||
)
|
||
.map(({ msg }) => ({
|
||
role: msg.is_user ? "user" : "assistant",
|
||
content: msg.mes || "",
|
||
}))
|
||
.filter((msg) => msg.content.trim().length > 0);
|
||
|
||
if (pendingMessages.length === 0) {
|
||
return { triggered: false, score: 0, reasons: [] };
|
||
}
|
||
|
||
const reasons = [];
|
||
let score = 0;
|
||
const combinedText = pendingMessages.map((message) => message.content).join("\n");
|
||
|
||
const keywordHits = DEFAULT_TRIGGER_KEYWORDS.filter((keyword) =>
|
||
combinedText.includes(keyword),
|
||
);
|
||
if (keywordHits.length > 0) {
|
||
score += Math.min(2, keywordHits.length);
|
||
reasons.push(`关键词: ${keywordHits.slice(0, 3).join(", ")}`);
|
||
}
|
||
|
||
const customPatterns = String(settings?.triggerPatterns || "")
|
||
.split(/\r?\n|,/)
|
||
.map((item) => item.trim())
|
||
.filter(Boolean);
|
||
for (const pattern of customPatterns) {
|
||
try {
|
||
const regex = new RegExp(pattern, "i");
|
||
if (regex.test(combinedText)) {
|
||
score += 2;
|
||
reasons.push(`自定义触发: ${pattern}`);
|
||
break;
|
||
}
|
||
} catch {
|
||
// 忽略无效正则,避免影响主流程
|
||
}
|
||
}
|
||
|
||
const roleSwitchCount = pendingMessages.reduce((count, message, index) => {
|
||
if (index === 0) return count;
|
||
return count + (message.role !== pendingMessages[index - 1].role ? 1 : 0);
|
||
}, 0);
|
||
if (roleSwitchCount >= 2) {
|
||
score += 1;
|
||
reasons.push("多轮往返互动");
|
||
}
|
||
|
||
const punctuationHits = (combinedText.match(/[!?!?]/g) || []).length;
|
||
if (punctuationHits >= 2) {
|
||
score += 1;
|
||
reasons.push("情绪/冲突波动");
|
||
}
|
||
|
||
const entityLikeHits =
|
||
combinedText.match(
|
||
/[A-Z][a-z]{2,}|[\u4e00-\u9fff]{2,6}(先生|小姐|王国|城|镇|村|学院|组织|公司|小队|军团)/g,
|
||
) || [];
|
||
if (entityLikeHits.length > 0) {
|
||
score += 1;
|
||
reasons.push("疑似新实体/新地点");
|
||
}
|
||
|
||
const threshold = Math.max(1, settings?.smartTriggerThreshold || 2);
|
||
return {
|
||
triggered: score >= threshold,
|
||
score,
|
||
reasons,
|
||
};
|
||
}
|