mirror of
https://github.com/SilenceLurker/ST-Amily2-Chat-Optimisation.git
synced 2026-06-06 20:35:51 +00:00
227 lines
8.8 KiB
JavaScript
227 lines
8.8 KiB
JavaScript
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();
|
||
}
|
||
}
|