import { getContext } from '/scripts/extensions.js';
import { state, SCRIPT_ID_PREFIX } from './cwb_state.js';
import { logDebug, logError, showToastr, escapeHtml, cleanChatName, parseCustomFormat, buildCustomFormat, isCwbEnabled } from './cwb_utils.js';
import { callCustomOpenAI } from './cwb_apiService.js';
import { saveDescriptionToLorebook, updateCharacterRosterLorebookEntry, manageAutoCardUpdateLorebookEntry, getTargetWorldBook } from './cwb_lorebookManager.js';
import { extractBlocksByTags, applyExclusionRules } from '../../core/utils/rag-tag-extractor.js';
import { getExtensionSettings } from '../../utils/settings.js';
import { getPresetPrompts, getMixedOrder } from '../../PresetSettings/index.js';
import { generateRandomSeed } from '../../core/api.js';
import { getChatIdentifier } from '../../core/lore.js';
import { safeLorebookEntries } from '../../core/tavernhelper-compatibility.js';
import { amilyHelper } from '../../core/tavern-helper/main.js';
const { SillyTavern, jQuery, characters } = window;
let isUpdatingCard = false;
let isBatchUpdating = false;
let manualBatchStopRequested = false;
let currentBatchNum = 0;
let totalBatchesNum = 0;
const MAX_BATCH_RETRIES = 2;
export async function updateCardUpdateStatusDisplay($panel) {
if (!$panel || !$panel.length) return;
const $statusDisplay = $panel.find(`#${SCRIPT_ID_PREFIX}-card-update-status-display`);
const $totalMessagesDisplay = $panel.find(`#${SCRIPT_ID_PREFIX}-total-messages-display`);
$totalMessagesDisplay.text(`上下文总层数: ${state.allChatMessages.length}`);
if (!state.currentChatFileIdentifier || state.currentChatFileIdentifier.startsWith('unknown_chat')) {
$statusDisplay.text('当前聊天未知,无法获取更新状态。');
return;
}
try {
const context = SillyTavern.getContext();
if (!context || !context.characterId) {
$statusDisplay.text('没有选择角色。');
return;
}
const bookName = await getTargetWorldBook();
if (!bookName) {
$statusDisplay.text('当前角色未设置主世界书或自定义世界书。');
return;
}
const entries = await safeLorebookEntries(bookName);
const entryPrefixForCurrentChat = `角色卡更新-${state.currentChatFileIdentifier}-`;
let latestEntryToShow = null;
let maxEndFloorOverall = -1;
for (const entry of entries) {
if (entry.comment && entry.comment.startsWith(entryPrefixForCurrentChat)) {
const match = entry.comment.match(/-(\d+)-(\d+)$/);
if (match && match[2]) {
const endFloor = parseInt(match[2], 10);
if (endFloor > maxEndFloorOverall) {
maxEndFloorOverall = endFloor;
latestEntryToShow = entry;
}
}
}
}
if (latestEntryToShow) {
const commentParts = latestEntryToShow.comment.split('-');
const charNameInComment = commentParts.slice(2, -2).join('-');
const startFloorStr = commentParts[commentParts.length - 2];
const endFloorStr = commentParts[commentParts.length - 1];
$statusDisplay.html(
`最新更新: 角色 ${escapeHtml(charNameInComment)} (基于楼层 ${startFloorStr}-${endFloorStr})`
);
} else {
$statusDisplay.text('当前聊天信息尚未在世界书中更新。');
}
} catch (e) {
logError('加载/解析世界书条目以更新UI状态时失败:', e);
$statusDisplay.text('获取世界书更新状态时出错。');
}
}
async function loadAllChatMessages($panel) {
logDebug('尝试使用 getContext() 加载所有聊天消息...');
if (!SillyTavern) {
logError('SillyTavern API 不可用。');
state.allChatMessages = [];
return;
}
try {
const context = SillyTavern.getContext();
const chat = context?.chat || [];
if (chat.length === 0) {
logDebug('聊天为空,无需加载消息。');
state.allChatMessages = [];
} else {
state.allChatMessages = chat.map((msg, idx) => ({
...msg,
message: msg.mes,
id: idx
}));
}
logDebug(`成功为 ${state.currentChatFileIdentifier} 加载了 ${state.allChatMessages.length} 条消息。`);
await updateCardUpdateStatusDisplay($panel);
} catch (error) {
logError('使用 getContext() 获取聊天消息时发生严重错误:', error);
showToastr('error', '获取聊天记录时发生内部错误。');
state.allChatMessages = [];
}
}
function processChatMessages(messages) {
if (!messages || !Array.isArray(messages) || messages.length === 0) {
logDebug('[CWB] processChatMessages: 没有可处理的消息。');
return '';
}
logDebug(`[CWB] processChatMessages: 开始处理 ${messages.length} 条消息。`);
try {
const mainSettings = getExtensionSettings();
if (!mainSettings) {
logError('[CWB] 无法访问主扩展设置。将使用原始消息。');
return messages.map(msg => `${msg.is_user ? SillyTavern?.name1 || '用户' : msg.name || '角色'}: ${msg.message}`).join('\n\n');
}
const useTagExtraction = mainSettings.historiographyTagExtractionEnabled ?? false;
const tagsToExtract = useTagExtraction ? (mainSettings.historiographyTags || '').split(',').map(t => t.trim()).filter(Boolean) : [];
const exclusionRules = mainSettings.historiographyExclusionRules || [];
logDebug(`[CWB] 标签提取: ${useTagExtraction}, 标签: ${tagsToExtract.join(', ')}, 排除规则: ${exclusionRules.length}`);
if (!useTagExtraction && exclusionRules.length === 0) {
logDebug('[CWB] 未激活任何处理规则。返回合并后的原始消息。');
return messages.map(msg => `${msg.is_user ? SillyTavern?.name1 || '用户' : msg.name || '角色'}: ${msg.message}`).join('\n\n');
}
const processedMessages = messages.map((msg) => {
let content = msg.message;
if (useTagExtraction && tagsToExtract.length > 0) {
const blocks = extractBlocksByTags(content, tagsToExtract);
if (blocks.length > 0) {
content = blocks.join('\n\n');
}
}
content = applyExclusionRules(content, exclusionRules);
if (!content.trim()) return null;
return `【${msg.is_user ? SillyTavern?.name1 || '用户' : msg.name || '角色'}】:\n${content.trim()}`;
}).filter(Boolean);
logDebug(`[CWB] processChatMessages: 处理完成。${messages.length} -> ${processedMessages.length} 条有效消息。`);
return processedMessages.join('\n\n');
} catch (error) {
logError('[CWB] processChatMessages 中发生错误:', error);
return messages.map(msg => `${msg.is_user ? SillyTavern?.name1 || '用户' : msg.name || '角色'}: ${msg.message}`).join('\n\n');
}
}
async function proceedWithCardUpdate($panel, messagesToUse) {
const statusUpdater = text => {
if ($panel && $panel.length) {
$panel.find(`#${SCRIPT_ID_PREFIX}-status-message`).text(text);
}
};
statusUpdater('正在生成角色卡描述...');
try {
const mode = state.isIncrementalUpdateEnabled ? 'cwb_summarizer_incremental' : 'cwb_summarizer';
const presetPrompts = await getPresetPrompts(mode);
const order = getMixedOrder(mode) || [];
const messages = [
{ role: 'system', content: generateRandomSeed() }
];
let promptCounter = 0;
let existingData = {};
if (state.isIncrementalUpdateEnabled) {
statusUpdater('增量更新模式:正在获取现有角色数据...');
try {
const bookName = await getTargetWorldBook();
if (bookName) {
const entries = (await safeLorebookEntries(bookName)) || [];
let chatIdentifier = state.currentChatFileIdentifier.replace(/ imported/g, '');
const messagesText = messagesToUse.map(m => {
const name = m.name || '';
const content = m.message || '';
return `${name}\n${content}`;
}).join('\n').toLowerCase();
const characterEntries = entries.filter(e =>
e.enabled &&
Array.isArray(e.keys) &&
e.keys.includes(chatIdentifier) &&
!e.keys.includes('Amily2角色总集')
);
for (const entry of characterEntries) {
try {
const keysToCheck = entry.keys.filter(k => k !== chatIdentifier);
if (entry.secondary_keys && Array.isArray(entry.secondary_keys)) {
keysToCheck.push(...entry.secondary_keys);
}
let isTriggered = false;
if (keysToCheck.length > 0) {
isTriggered = keysToCheck.some(key => messagesText.includes(key.toLowerCase()));
}
if (isTriggered) {
const parsedData = parseCustomFormat(entry.content);
const entryCharName = parsedData?.name?.trim() || parsedData?.CI?.name?.trim() || parsedData?.core_identity?.name?.trim();
if (entryCharName) {
existingData[entryCharName] = entry.content;
}
}
} catch (parseError) {
logError(`解析现有角色条目时出错 (UID: ${entry.uid}):`, parseError);
}
}
logDebug(`为 '${chatIdentifier}' 找到了 ${Object.keys(existingData).length} 个被触发的现有角色条目。`);
}
} catch (e) {
logError('在增量更新中获取现有角色数据时出错:', e);
showToastr('error', '获取旧档案失败,请检查控制台。');
}
}
for (const item of order) {
if (item.type === 'prompt') {
if (presetPrompts && presetPrompts[promptCounter]) {
messages.push(presetPrompts[promptCounter]);
promptCounter++;
}
} else if (item.type === 'conditional') {
switch (item.id) {
case 'cwb_break_armor_prompt':
if (state.currentBreakArmorPrompt) {
messages.push({ role: "system", content: state.currentBreakArmorPrompt });
}
break;
case 'cwb_char_card_prompt':
if (state.currentCharCardPrompt) {
messages.push({ role: "system", content: state.currentCharCardPrompt });
}
break;
case 'oldFiles':
if (state.isIncrementalUpdateEnabled) {
let oldFilesContent = "【旧档案】\n";
if (Object.keys(existingData).length > 0) {
for (const charName in existingData) {
oldFilesContent += `${existingData[charName]}\n`;
}
} else {
oldFilesContent += "无\n";
}
messages.push({ role: 'user', content: oldFilesContent });
}
break;
case 'newContext':
const processedText = processChatMessages(messagesToUse);
let newContextContent = "";
if (state.isIncrementalUpdateEnabled) {
newContextContent = "【新对话】\n";
} else {
newContextContent = "最近的聊天记录摘要:\n";
}
if (processedText) {
newContextContent += processedText;
} else {
newContextContent += "(无有效对话内容)";
}
if (!state.isIncrementalUpdateEnabled) {
newContextContent += "\n\n请根据以上聊天记录更新角色描述:";
}
messages.push({ role: 'user', content: newContextContent });
break;
}
}
}
statusUpdater('正在调用AI生成角色卡...');
const aiResponse = await callCustomOpenAI(messages);
if (!aiResponse) throw new Error('AI未能生成有效描述。');
const endFloor_0idx = state.allChatMessages.length - 1;
const startFloor_0idx = Math.max(0, state.allChatMessages.length - messagesToUse.length);
const characterBlocks = aiResponse.split(/(?=\[--Amily2::CHAR_START--\])/).filter(block => block.trim());
if (characterBlocks.length === 0) throw new Error('AI未能生成任何角色描述块。');
let allSucceeded = true;
let processedNames = [];
for (const block of characterBlocks) {
const trimmedBlock = block.trim();
if (!trimmedBlock) continue;
const parsedData = parseCustomFormat(trimmedBlock);
const charName = (parsedData?.name?.trim() || parsedData?.CI?.name?.trim() || parsedData?.core_identity?.name?.trim()) || 'UnknownCharacter';
if (charName === 'UnknownCharacter') {
logError('无法在块中找到角色名:', trimmedBlock);
continue;
}
const success = await saveDescriptionToLorebook(charName, trimmedBlock, startFloor_0idx, endFloor_0idx);
if (success) {
processedNames.push(charName);
} else {
allSucceeded = false;
}
}
if (processedNames.length > 0) {
await updateCharacterRosterLorebookEntry([...new Set(processedNames)], startFloor_0idx, endFloor_0idx);
statusUpdater(`已为 ${processedNames.length} 个角色更新描述!`);
} else {
throw new Error('AI生成了内容,但未能成功提取任何有效的角色卡。');
}
updateCardUpdateStatusDisplay($panel);
return allSucceeded;
} catch (error) {
logError('角色卡更新过程出错:', error);
showToastr('error', `更新失败: ${error.message}`);
statusUpdater('错误:更新失败。');
return false;
}
}
async function triggerAutomaticUpdate($panel) {
logDebug(`检查是否需要更新。总消息数: ${state.allChatMessages.length}, 自动更新启用: ${state.autoUpdateEnabled}`);
if (!isCwbEnabled()) {
logDebug('更新检查已跳过 - CharacterWorldBook总开关已关闭。');
return;
}
if (!state.autoUpdateEnabled || isUpdatingCard || !state.customApiConfig.url || !state.customApiConfig.model || state.allChatMessages.length === 0) {
logDebug('更新检查已跳过(未启用、正在更新、未配置或无消息)。');
return;
}
let maxEndFloorInLorebook = 0;
try {
const context = SillyTavern.getContext();
if (!context || !context.characterId) {
logDebug('角色上下文未准备好,跳过自动更新的世界书检查。');
return;
}
const bookName = await getTargetWorldBook();
if (bookName) {
const entries = (await safeLorebookEntries(bookName)) || [];
const cleanChatId = state.currentChatFileIdentifier.replace(/ imported/g, '');
const rosterEntry = entries.find(e =>
Array.isArray(e.keys) &&
e.keys.includes('Amily2角色总集') &&
e.keys.includes(cleanChatId)
);
if (rosterEntry && rosterEntry.content) {
const floorMatch = rosterEntry.content.match(/【前(\d+)楼角色世界书已更新完成】/);
if (floorMatch && floorMatch[1]) {
maxEndFloorInLorebook = parseInt(floorMatch[1], 10);
} else {
// Fallback for older entries
const floorRangeKey = rosterEntry.keys.find(k => /^\d+-\d+$/.test(k));
if (floorRangeKey) {
maxEndFloorInLorebook = parseInt(floorRangeKey.split('-')[1], 10);
}
}
}
}
} catch (e) {
logError('从世界书获取最大结束楼层时出错:', e);
}
const unupdatedCount = state.allChatMessages.length - maxEndFloorInLorebook;
logDebug(`未更新消息数: ${unupdatedCount} (阈值: ${state.autoUpdateThreshold}). 上次更新楼层: ${maxEndFloorInLorebook}.`);
if (unupdatedCount >= state.autoUpdateThreshold) {
showToastr('info', `检测到 ${unupdatedCount} 条新消息,将自动更新角色卡。`);
const messagesToUse = state.allChatMessages.slice(maxEndFloorInLorebook);
isUpdatingCard = true;
await proceedWithCardUpdate($panel, messagesToUse);
isUpdatingCard = false;
}
}
export async function getLatestChatName() {
let attempts = 0;
const maxAttempts = 50;
const interval = 100;
while (attempts < maxAttempts) {
const context = getContext();
if (context && context.chatId) {
return context.chatId;
}
await new Promise((resolve) => setTimeout(resolve, interval));
attempts++;
}
logError("[CWB] 长时间等待后,仍无法确定聊天ID。");
return "unknown_chat_timeout";
}
export async function handleMessageReceived($panel) {
if (!isCwbEnabled('消息接收处理')) {
return;
}
const context = SillyTavern.getContext();
if (!context || !context.chat || !context.chat.length === 0) return;
const latestMessage = context.chat[context.chat.length - 1];
if (!latestMessage || latestMessage.is_user) {
return;
}
await loadAllChatMessages($panel);
await triggerAutomaticUpdate($panel);
}
export async function resetScriptStateForNewChat($panel, newChatName) {
logDebug(`为新聊天重置脚本状态: "${newChatName}"`);
state.allChatMessages = [];
state.currentChatFileIdentifier = newChatName || 'unknown_chat_fallback';
await loadAllChatMessages($panel);
logDebug('状态重置完成。');
}
function updateBatchButtonState($panel, state, batchNum = 0, attemptNum = 0) {
if (!$panel || !$panel.length) return;
const $button = $panel.find('#cwb-batch-update-card');
const $progress = $panel.find('#cwb-batch-progress');
if (!$button.length) return;
switch (state) {
case 'processing':
let attemptText = attemptNum > 0 ? ` (尝试 ${attemptNum + 1})` : '';
$button.text(`点击停止 (${batchNum}/${totalBatchesNum})${attemptText}`);
$button.prop('disabled', false);
$progress.show().text(`正在处理批次 ${batchNum}/${totalBatchesNum}...`);
isBatchUpdating = true;
break;
case 'stopping':
$button.text('正在停止...');
$button.prop('disabled', true);
$progress.text('正在停止批量更新...');
break;
case 'paused':
$button.text('继续批量更新');
$button.prop('disabled', false);
$progress.text('批量更新已暂停,点击继续...');
isBatchUpdating = true;
break;
case 'error':
$button.text('继续批量更新 (出错)');
$button.prop('disabled', false);
$progress.text('批量更新出错,请检查后继续...');
isBatchUpdating = true;
break;
case 'idle':
default:
$button.text('立即批量更新');
$button.prop('disabled', false);
$progress.hide();
isBatchUpdating = false;
currentBatchNum = 0;
manualBatchStopRequested = false;
break;
}
}
function getMessagesForFloorRange(startFloor, endFloor) {
if (!state.allChatMessages || state.allChatMessages.length === 0) {
return [];
}
// 转换为0-based索引
const startIndex = Math.max(0, startFloor - 1);
const endIndex = Math.min(state.allChatMessages.length, endFloor);
if (startIndex >= endIndex) {
return [];
}
return state.allChatMessages.slice(startIndex, endIndex);
}
async function runBatchUpdateAttempt($panel, batchNum, attemptNum) {
try {
if (manualBatchStopRequested) {
logDebug(`批次 ${batchNum} 在开始前被手动停止。`);
updateBatchButtonState($panel, 'paused');
return;
}
updateBatchButtonState($panel, 'processing', batchNum, attemptNum);
const startFloor = (batchNum - 1) * state.autoUpdateThreshold + 1;
const endFloor = Math.min(startFloor + state.autoUpdateThreshold - 1, state.allChatMessages.length);
logDebug(`正在处理批次 ${batchNum}/${totalBatchesNum} (楼层 ${startFloor}-${endFloor}, 尝试 ${attemptNum + 1}/${MAX_BATCH_RETRIES + 1})`);
const messagesToProcess = getMessagesForFloorRange(startFloor, endFloor);
if (!messagesToProcess || messagesToProcess.length === 0) {
throw new Error('指定范围内无有效消息可处理。');
}
const success = await proceedWithCardUpdate($panel, messagesToProcess);
if (!success) {
throw new Error('角色卡更新失败。');
}
logDebug(`批次 ${batchNum} 处理成功。`);
currentBatchNum = batchNum;
setTimeout(() => processNextBatch($panel), 1000);
} catch (error) {
logError(`批次 ${batchNum} 尝试 ${attemptNum + 1} 失败: ${error.message}`);
if (attemptNum >= MAX_BATCH_RETRIES) {
logError(`批次 ${batchNum} 已达到最大重试次数,任务暂停。`);
showToastr('error', `批次 ${batchNum} 多次失败,请检查网络或API设置后手动继续。`);
currentBatchNum = batchNum - 1;
updateBatchButtonState($panel, 'error');
} else {
logDebug(`将在3秒后自动重试批次 ${batchNum}...`);
setTimeout(() => runBatchUpdateAttempt($panel, batchNum, attemptNum + 1), 3000);
}
}
}
async function processNextBatch($panel) {
if (manualBatchStopRequested) {
logDebug(`批次 ${currentBatchNum + 1} 在开始前被手动停止。`);
updateBatchButtonState($panel, 'paused');
return;
}
if (currentBatchNum >= totalBatchesNum) {
logDebug('所有批次处理完毕!');
showToastr('success', '批量更新完成!');
updateBatchButtonState($panel, 'idle');
return;
}
await runBatchUpdateAttempt($panel, currentBatchNum + 1, 0);
}
export async function startBatchUpdate($panel) {
if (!isCwbEnabled()) {
showToastr('warning', 'CharacterWorldBook总开关已关闭,无法执行批量更新。');
return;
}
await loadAllChatMessages($panel);
if (!state.customApiConfig.url || !state.customApiConfig.model) {
showToastr('warning', '请先配置API信息。');
return;
}
if (isBatchUpdating) {
const $button = $panel.find('#cwb-batch-update-card');
if ($button.text().startsWith('点击停止')) {
manualBatchStopRequested = true;
updateBatchButtonState($panel, 'stopping');
logDebug('批量更新停止请求已发出!将在当前批次完成后暂停。');
} else if ($button.text().startsWith('继续批量更新')) {
manualBatchStopRequested = false;
logDebug('从上次暂停处继续批量更新...');
await processNextBatch($panel);
}
return;
}
manualBatchStopRequested = false;
if (state.allChatMessages.length === 0) {
showToastr('info', '当前没有聊天记录,无需更新。');
return;
}
totalBatchesNum = Math.ceil(state.allChatMessages.length / state.autoUpdateThreshold);
currentBatchNum = 0;
logDebug(`准备开始批量更新任务,共 ${totalBatchesNum} 个批次。`);
showToastr('info', `开始批量更新,共 ${totalBatchesNum} 个批次...`);
await processNextBatch($panel);
}
export async function handleFloorRangeUpdate($panel) {
if (!isCwbEnabled()) {
showToastr('warning', 'CharacterWorldBook总开关已关闭,无法执行楼层范围更新。');
return;
}
await loadAllChatMessages($panel);
if (isUpdatingCard || isBatchUpdating) {
showToastr('info', '已有更新任务在进行中。');
return;
}
if (!state.customApiConfig.url || !state.customApiConfig.model) {
showToastr('warning', '请先配置API信息。');
return;
}
const startFloor = parseInt($panel.find('#cwb-start-floor').val(), 10);
const endFloor = parseInt($panel.find('#cwb-end-floor').val(), 10);
if (!startFloor || !endFloor || startFloor <= 0 || endFloor <= 0) {
showToastr('warning', '请输入有效的楼层范围。');
return;
}
if (startFloor > endFloor) {
showToastr('warning', '起始楼层不能大于结束楼层。');
return;
}
if (state.allChatMessages.length === 0) {
showToastr('info', '当前没有聊天记录,无需更新。');
return;
}
if (endFloor > state.allChatMessages.length) {
showToastr('warning', `结束楼层 ${endFloor} 超出了当前聊天记录长度 ${state.allChatMessages.length}。`);
return;
}
const messagesToProcess = getMessagesForFloorRange(startFloor, endFloor);
if (!messagesToProcess || messagesToProcess.length === 0) {
showToastr('warning', '指定楼层范围内没有有效内容可处理。');
return;
}
isUpdatingCard = true;
const $button = $panel.find('#cwb-floor-range-update');
$button.prop('disabled', true).text('更新中...');
try {
logDebug(`开始处理楼层 ${startFloor}-${endFloor} 的内容...`);
const success = await proceedWithCardUpdate($panel, messagesToProcess);
if (success) {
showToastr('success', `楼层 ${startFloor}-${endFloor} 更新完成!`);
}
} finally {
isUpdatingCard = false;
$button.prop('disabled', false).text('楼层范围更新');
}
}
export async function manualUpdateLogic($panel = null) {
if (!isCwbEnabled()) {
logDebug('手动更新已跳过 - CharacterWorldBook总开关已关闭。');
return;
}
if (isUpdatingCard) {
showToastr('info', '已有更新任务在进行中。');
return;
}
if (!state.customApiConfig.url || !state.customApiConfig.model) {
showToastr('warning', '请先配置API信息。');
return;
}
isUpdatingCard = true;
await loadAllChatMessages($panel);
const depth = state.scanDepth || state.autoUpdateThreshold || 6;
const messagesToProcess = state.allChatMessages.slice(-depth);
await proceedWithCardUpdate($panel, messagesToProcess);
isUpdatingCard = false;
logDebug('手动更新完成。');
}
export async function handleManualUpdateCard($panel) {
const $button = $panel.find(`#${SCRIPT_ID_PREFIX}-manual-update-card`);
$button.prop('disabled', true).text('更新中...');
await manualUpdateLogic($panel);
$button.prop('disabled', false).text('立即更新角色描述');
}
export async function handleLegacyFormatConversion($panel) {
if (!isCwbEnabled()) {
showToastr('warning', 'CharacterWorldBook总开关已关闭。');
return;
}
const $button = $panel.find('#cwb-legacy-auto-update');
$button.prop('disabled', true).html(' 转换中...');
try {
const bookName = await getTargetWorldBook();
if (!bookName) {
showToastr('warning', '未找到目标世界书。');
return;
}
const entries = await safeLorebookEntries(bookName);
let updatedCount = 0;
const entriesToUpdate = [];
for (const entry of entries) {
if (!entry.content || !entry.content.includes('[--Amily2::CHAR_START--]')) continue;
try {
const parsed = parseCustomFormat(entry.content);
if (!parsed || Object.keys(parsed).length === 0) continue;
let hasChanges = false;
const newData = {};
// Helper to rename keys
const renameKey = (obj, oldKey, newKey) => {
if (obj[oldKey] !== undefined) {
obj[newKey] = obj[oldKey];
delete obj[oldKey];
return true;
}
return false;
};
// Helper to rename sub-keys
const renameSubKeys = (parentObj, parentKey, mapping) => {
if (parentObj[parentKey]) {
let subChanged = false;
for (const [oldSub, newSub] of Object.entries(mapping)) {
if (renameKey(parentObj[parentKey], oldSub, newSub)) {
subChanged = true;
}
}
return subChanged;
}
return false;
};
// Copy parsed data to newData to avoid mutating original if needed (though parseCustomFormat returns new obj)
Object.assign(newData, JSON.parse(JSON.stringify(parsed)));
// 1. Rename Top Level Modules
if (renameKey(newData, 'core_identity', 'CI')) hasChanges = true;
if (renameKey(newData, 'physical_imprint', 'PI')) hasChanges = true;
if (renameKey(newData, 'psyche_profile', 'PP')) hasChanges = true;
if (renameKey(newData, 'social_matrix', 'SM')) hasChanges = true;
if (renameKey(newData, 'narrative_essence', 'NE')) hasChanges = true;
// 2. Rename Sub-keys
// CI
if (renameSubKeys(newData, 'CI', {
'archetype': 'arch',
'gender': 'gen',
'current_status': 'status'
})) hasChanges = true;
// PI
if (renameSubKeys(newData, 'PI', {
'first_impression': 'first',
'key_features': 'feat',
'mannerisms': 'manner'
})) hasChanges = true;
// PP
if (renameSubKeys(newData, 'PP', {
'description': 'desc',
'motivation': 'mot',
'values': 'val',
'inner_conflict': 'conf'
})) hasChanges = true;
// SM
if (renameSubKeys(newData, 'SM', {
'interaction_style': 'style',
'skills': 'skill',
'reputation': 'rep'
})) hasChanges = true;
// NE
if (newData.NE) {
// core_traits -> trait
if (newData.NE.core_traits) {
newData.NE.trait = newData.NE.core_traits.map(t => {
const newT = { ...t };
renameKey(newT, 'definition', 'def');
renameKey(newT, 'evidence', 'evid');
return newT;
});
delete newData.NE.core_traits;
hasChanges = true;
}
// verbal_patterns -> verb
if (newData.NE.verbal_patterns) {
newData.NE.verb = { ...newData.NE.verbal_patterns };
delete newData.NE.verbal_patterns;
renameKey(newData.NE.verb, 'style_summary', 'style');
renameKey(newData.NE.verb, 'quotes', 'quote');
hasChanges = true;
}
// key_relationships -> rel
if (newData.NE.key_relationships) {
newData.NE.rel = newData.NE.key_relationships.map(r => {
const newR = { ...r };
renameKey(newR, 'summary', 'sum');
return newR;
});
delete newData.NE.key_relationships;
hasChanges = true;
}
}
if (hasChanges) {
const newContent = buildCustomFormat(newData);
entriesToUpdate.push({
uid: entry.uid,
content: newContent
});
updatedCount++;
}
} catch (e) {
logError(`转换条目失败 (UID: ${entry.uid}):`, e);
}
}
if (updatedCount > 0) {
await amilyHelper.setLorebookEntries(bookName, entriesToUpdate);
showToastr('success', `成功转换了 ${updatedCount} 个旧版格式条目!`);
} else {
showToastr('info', '没有发现需要转换的旧版格式条目。');
}
} catch (error) {
logError('旧版格式转换失败:', error);
showToastr('error', `转换失败: ${error.message}`);
} finally {
$button.prop('disabled', false).html(' 旧版格式转换');
}
}
export async function initializeCore($panel) {
const initialChatName = await getLatestChatName();
await resetScriptStateForNewChat($panel, initialChatName);
logDebug('CWB 核心已初始化。基于事件的检查已激活。');
}