Files
ST-Amily2-Chat-Optimisation…/core/lore.js
2025-07-12 17:50:22 +08:00

227 lines
8.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { getContext } from "/scripts/extensions.js";
import { characters, eventSource, event_types } from "/script.js";
import { loadWorldInfo, createNewWorldInfo, createWorldInfoEntry, saveWorldInfo, world_names } from "/scripts/world-info.js";
export const LOREBOOK_PREFIX = "Amily2档案-";
export const DEDICATED_LOREBOOK_NAME = "Amily2号-国史馆";
export const INTRODUCTORY_TEXT =
"【Amily2号自动档案】\n此卷宗由Amily2号优化助手自动生成并维护记录核心事件脉络。\n---\n";
export async function getChatIdentifier() {
let attempts = 0;
const maxAttempts = 50;
const interval = 100;
while (attempts < maxAttempts) {
try {
const context = getContext();
if (context && context.characterId) {
const character = characters[context.characterId];
if (character && character.avatar) {
return `char-${character.avatar.replace(/\.(png|webp|jpg|jpeg|gif)$/, "")}`;
}
return `char-${context.characterId}`;
}
if (context && context.chat_filename) {
const fileName = context.chat_filename.split(/[\\/]/).pop();
return fileName.replace(/\.jsonl?$/, "");
}
} catch (error) {
console.warn(
`[Amily2-户籍管理处] 等待上下文时发生轻微错误 (尝试次数 ${attempts + 1}):`,
error.message,
);
}
await new Promise((resolve) => setTimeout(resolve, interval));
attempts++;
}
console.error("[Amily2-国史馆] 户籍管理处在长时间等待后,仍无法确定户籍。");
toastr.warning(
"Amily2号无法确定当前聊天身份世界书功能将受影响。",
"上下文错误",
);
return "unknown_chat_timeout";
}
export async function findLatestSummaryLore(lorebookName, chatIdentifier) {
try {
const bookData = await loadWorldInfo(lorebookName);
if (!bookData || !bookData.entries) {
return null;
}
const entriesArray = Object.values(bookData.entries);
const uniqueLoreName = `${LOREBOOK_PREFIX}${chatIdentifier}`;
return (
entriesArray.find(
(entry) => entry.comment === uniqueLoreName && !entry.disable,
) || null
);
} catch (error) {
console.error(
`[Amily2-国史馆] 钦差大臣在 '${lorebookName}' 检索时发生错误:`,
error,
);
return null;
}
}
export async function getCombinedWorldbookContent(lorebookName) {
if (!lorebookName) return "";
try {
const bookData = await loadWorldInfo(lorebookName);
if (!bookData || !bookData.entries) {
return "";
}
const activeContents = Object.values(bookData.entries)
.filter((entry) => !entry.disable)
.map((entry) => `[条目: ${entry.comment || "无标题"}]\n${entry.content}`);
return activeContents.join("\n\n---\n\n");
} catch (error) {
console.error(
`[Amily2-国史馆] 钦差大臣在整合 '${lorebookName}' 时发生错误:`,
error,
);
toastr.error(`读取世界书 '${lorebookName}' 失败!`, "档案整合错误");
return "";
}
}
async function refreshWorldbookListOnly(newBookName = null) {
console.log("[Amily2号-工部-v1.3] 执行“圣谕广播”式UI更新...");
try {
if (newBookName) {
if (Array.isArray(world_names) && !world_names.includes(newBookName)) {
world_names.push(newBookName);
world_names.sort();
console.log(`[Amily2号-工部] 已将《${newBookName}》注入前端数据模型。`);
} else {
console.log(`[Amily2号-工部] 《${newBookName}》已存在于数据模型中,跳过注入。`);
}
}
if (
eventSource &&
typeof eventSource.emit === "function" &&
event_types.CHARACTER_PAGE_LOADED
) {
console.log(`[Amily2号-工部] 正在广播事件: ${event_types.CHARACTER_PAGE_LOADED}`);
eventSource.emit(event_types.CHARACTER_PAGE_LOADED);
console.log("[Amily2号-工部] “character_page_loaded”事件已广播UI应已响应刷新。");
} else {
console.error("[Amily2号] 致命错误: eventSource 或 event_types.CHARACTER_PAGE_LOADED 未找到。无法广播刷新事件。");
toastr.error("Amily2号无法触发UI刷新。", "核心事件系统缺失");
}
} catch (error) {
console.error("[Amily2号-工部] “圣谕广播”式刷新失败:", error);
}
}
export async function writeSummaryToLorebook(pendingData) {
if (!pendingData || !pendingData.summary || !pendingData.sourceAiMessageTimestamp || !pendingData.settings) {
console.warn("[Amily2-国史馆] 接到一份残缺的待办文书,写入任务已中止。", pendingData);
return;
}
const context = getContext();
const chat = context.chat;
let isSourceMessageValid = false;
let sourceMessageCandidate = null;
for (let i = chat.length - 2; i >= 0; i--) {
if (!chat[i].is_user) { sourceMessageCandidate = chat[i]; break; }
}
if (sourceMessageCandidate && sourceMessageCandidate.send_date === pendingData.sourceAiMessageTimestamp) {
isSourceMessageValid = true;
}
if (!isSourceMessageValid) {
console.log("[Amily2号-逆时寻踪] 裁决: 源消息已被修改或删除,遵旨废黜过时总结。");
return;
}
const { summary: summaryToCommit, settings } = pendingData;
console.groupCollapsed(`[Amily2号-存档任务-v21.0 最终圣旨版] ${new Date().toLocaleTimeString()}`);
console.time("总结写入总耗时");
try {
const chatIdentifier = await getChatIdentifier();
const character = characters[context.characterId];
let targetLorebookName = null;
let isNewBook = false;
switch (settings.target) {
case "character_main":
targetLorebookName = character?.data?.extensions?.world;
if (!targetLorebookName) {
toastr.warning("角色未绑定主世界书,总结写入任务已中止。", "Amily2号");
console.groupEnd();
return;
}
break;
case "dedicated":
targetLorebookName = `${DEDICATED_LOREBOOK_NAME}-${chatIdentifier}`;
break;
default:
toastr.error(`收到未知的写入指令: "${settings.target}"`, "Amily2号");
console.groupEnd();
return;
}
if (!world_names.includes(targetLorebookName)) {
await createNewWorldInfo(targetLorebookName);
isNewBook = true;
}
const uniqueLoreName = `${LOREBOOK_PREFIX}${chatIdentifier}`;
const bookData = await loadWorldInfo(targetLorebookName);
if (!bookData) {
toastr.error(`无法加载世界书《${targetLorebookName}`, "Amily2号");
console.groupEnd();
return;
}
const existingEntry = Object.values(bookData.entries).find(e => e.comment === uniqueLoreName && !e.disable);
if (existingEntry) {
const existingContent = existingEntry.content.replace(INTRODUCTORY_TEXT, "").trim();
const lines = existingContent ? existingContent.split("\n") : [];
const nextNumber = lines.length + 1;
existingEntry.content += `\n${nextNumber}. ${summaryToCommit}`;
} else {
const positionMap = {
'before_char': 0, 'after_char': 1, 'before_an': 2,
'after_an': 3, 'at_depth': 4
};
const finalKeywords = settings.keywords.split(',').map(k => k.trim()).filter(Boolean);
const isConstant = settings.activationMode === 'always';
const newEntry = createWorldInfoEntry(targetLorebookName, bookData);
Object.assign(newEntry, {
comment: uniqueLoreName,
content: `${INTRODUCTORY_TEXT}1. ${summaryToCommit}`,
key: finalKeywords,
constant: isConstant,
position: positionMap[settings.insertionPosition] ?? 4,
depth: settings.depth,
disable: false,
});
}
await saveWorldInfo(targetLorebookName, bookData, true);
console.log(`[史官司] 总结已遵旨写入《${targetLorebookName}》文件。`);
if (isNewBook) {
await refreshWorldbookListOnly(targetLorebookName);
toastr.success(`已创建并写入新档案《${targetLorebookName}》!`, "Amily2号");
}
} catch (error) {
console.error("[Amily2号-写入失败] 写入流程发生意外错误:", error);
toastr.error("后台写入总结时发生错误。", "Amily2号");
} finally {
console.timeEnd("总结写入总耗时");
console.groupEnd();
}
}