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 核心已初始化。基于事件的检查已激活。'); }