diff --git a/CharacterWorldBook/src/cwb_lorebookManager.js b/CharacterWorldBook/src/cwb_lorebookManager.js index 5169b4b..4414138 100644 --- a/CharacterWorldBook/src/cwb_lorebookManager.js +++ b/CharacterWorldBook/src/cwb_lorebookManager.js @@ -1,586 +1,319 @@ -import { extension_settings } from '/scripts/extensions.js'; -import { extensionName } from '../../utils/settings.js'; -import { saveSettingsDebounced } from '/script.js'; -import { world_names } from '/scripts/world-info.js'; import { state } from './cwb_state.js'; -import { cwbCompleteDefaultSettings } from './cwb_config.js'; -import { logError, showToastr, escapeHtml, compareVersions, isCwbEnabled } from './cwb_utils.js'; -import { fetchModelsAndConnect, updateApiStatusDisplay } from './cwb_apiService.js'; -import { checkForUpdates } from './cwb_updater.js'; -import { handleManualUpdateCard, startBatchUpdate, handleFloorRangeUpdate } from './cwb_core.js'; -import { initializeCharCardViewer } from './cwb_uiManager.js'; -import { CHAR_CARD_VIEWER_BUTTON_ID } from './cwb_state.js'; +import { logError, logDebug, showToastr, parseCustomFormat } from './cwb_utils.js'; -const { jQuery: $ } = window; +const { SillyTavern, TavernHelper } = window; -const CWB_BOOLEAN_SETTINGS_OVERRIDE_KEY = 'cwb_boolean_settings_override'; -let $panel; - -const getSettings = () => extension_settings[extensionName]; - -function updateControlsLockState() { - if (!$panel) return; - const settings = getSettings(); - const isMasterEnabled = settings.cwb_master_enabled; - - const $controlsToToggle = $panel.find('input, textarea, select, button').not('#cwb_master_enabled-checkbox, #amily2_back_to_main_from_cwb'); - - if (isMasterEnabled) { - $controlsToToggle.prop('disabled', false); - $panel.find('.settings-group').not('.master-control-group').css('opacity', '1'); - } else { - $controlsToToggle.prop('disabled', true); - $panel.find('.settings-group').not('.master-control-group').css('opacity', '0.5'); +export async function getTargetWorldBook() { + logDebug('[CWB-DIAGNOSTIC] getTargetWorldBook called. Current state:', { + target: state.worldbookTarget, + book: state.customWorldBook + }); + if (state.worldbookTarget === 'custom' && state.customWorldBook) { + return state.customWorldBook; } -} - -function saveApiConfig() { - const settings = getSettings(); - settings.cwb_api_mode = $panel.find('#cwb-api-mode').val(); - settings.cwb_api_url = $panel.find('#cwb-api-url').val().trim(); - settings.cwb_api_key = $panel.find('#cwb-api-key').val(); - settings.cwb_api_model = $panel.find('#cwb-api-model').val(); - settings.cwb_tavern_profile = $panel.find('#cwb-tavern-profile').val(); - - if (settings.cwb_api_mode === 'sillytavern_preset') { - if (!settings.cwb_tavern_profile) { - showToastr('warning', '请选择SillyTavern预设。'); - return; - } - showToastr('success', 'API配置已保存!'); - } else { - if (!settings.cwb_api_url) { - showToastr('warning', 'API URL 不能为空。'); - return; - } - showToastr('success', 'API配置已保存!'); - } - - saveSettingsDebounced(); - loadSettings(); -} - -function clearApiConfig() { - const settings = getSettings(); - settings.cwb_api_url = ''; - settings.cwb_api_key = ''; - settings.cwb_api_model = ''; - saveSettingsDebounced(); - state.customApiConfig.url = ''; - state.customApiConfig.apiKey = ''; - state.customApiConfig.model = ''; - updateUiWithSettings(); - updateApiStatusDisplay($panel); - showToastr('info', 'API配置已清除!'); -} - -function saveBreakArmorPrompt() { - const newPrompt = $panel.find('#cwb-break-armor-prompt-textarea').val().trim(); - if (!newPrompt) { - showToastr('warning', '破甲预设不能为空。'); - return; - } - getSettings().cwb_break_armor_prompt = newPrompt; - state.currentBreakArmorPrompt = newPrompt; - saveSettingsDebounced(); - showToastr('success', '破甲预设已保存!'); -} - -function resetBreakArmorPrompt() { - getSettings().cwb_break_armor_prompt = cwbCompleteDefaultSettings.cwb_break_armor_prompt; - state.currentBreakArmorPrompt = cwbCompleteDefaultSettings.cwb_break_armor_prompt; - saveSettingsDebounced(); - updateUiWithSettings(); - showToastr('info', '破甲预设已恢复为默认值!'); -} - -function saveCharCardPrompt() { - const newPrompt = $panel.find('#cwb-char-card-prompt-textarea').val().trim(); - if (!newPrompt) { - showToastr('warning', '角色卡预设不能为空。'); - return; - } - getSettings().cwb_char_card_prompt = newPrompt; - state.currentCharCardPrompt = newPrompt; - saveSettingsDebounced(); - showToastr('success', '角色卡预设已保存!'); -} - -function resetCharCardPrompt() { - getSettings().cwb_char_card_prompt = cwbCompleteDefaultSettings.cwb_char_card_prompt; - state.currentCharCardPrompt = cwbCompleteDefaultSettings.cwb_char_card_prompt; - saveSettingsDebounced(); - updateUiWithSettings(); - showToastr('info', '角色卡预设已恢复为默认值!'); -} - -function saveAutoUpdateThreshold() { - const valStr = $panel.find('#cwb-auto-update-threshold').val(); - const newT = parseInt(valStr, 10); - if (!isNaN(newT) && newT >= 1) { - getSettings().cwb_auto_update_threshold = newT; - state.autoUpdateThreshold = newT; - saveSettingsDebounced(); - showToastr('success', '自动更新阈值已保存!'); - } else { - showToastr('warning', `阈值 "${valStr}" 无效。`); - $panel.find('#cwb-auto-update-threshold').val(getSettings().cwb_auto_update_threshold); - } -} - -function bindWorldBookSettings() { - const MAX_RETRIES = 10; - const RETRY_DELAY = 200; - let attempt = 0; - - function tryBind() { - if (world_names && world_names.length > 0) { - console.log('[CWB] World books loaded, binding settings...'); - const settings = getSettings(); - - if (settings.cwb_worldbook_target === undefined) settings.cwb_worldbook_target = 'primary'; - if (settings.cwb_custom_worldbook === undefined) settings.cwb_custom_worldbook = null; - - const customSelectWrapper = $panel.find('#cwb_worldbook_select_wrapper'); - const bookListContainer = $panel.find('#cwb_worldbook_radio_list'); - - const renderWorldBookList = () => { - const worldBooks = world_names.map(name => ({ name: name.replace('.json', ''), file_name: name })); - bookListContainer.empty(); - - if (worldBooks.length > 0) { - worldBooks.forEach(book => { - const div = $('
').attr('title', book.name); - const radio = $('') - .attr('id', `cwb-wb-radio-${book.file_name}`) - .val(book.file_name) - .prop('checked', settings.cwb_custom_worldbook === book.file_name); - const label = $('').attr('for', `cwb-wb-radio-${book.file_name}`).text(book.name); - div.append(radio).append(label); - bookListContainer.append(div); - }); - } else { - bookListContainer.html('没有找到世界书。
'); - } - }; - - const updateCustomSelectVisibility = () => { - const isCustom = settings.cwb_worldbook_target === 'custom'; - customSelectWrapper.toggle(isCustom); - if (isCustom) { - renderWorldBookList(); - } - }; - - $panel.find('input[name="cwb_worldbook_target"]').each(function() { - $(this).prop('checked', $(this).val() === settings.cwb_worldbook_target); - }); - updateCustomSelectVisibility(); - - $panel.off('change.cwb_worldbook_target').on('change.cwb_worldbook_target', 'input[name="cwb_worldbook_target"]', function() { - if ($(this).prop('checked')) { - settings.cwb_worldbook_target = $(this).val(); - state.worldbookTarget = $(this).val(); - updateCustomSelectVisibility(); - saveSettingsDebounced(); - } - }); - - bookListContainer.off('change.cwb_worldbook_selection').on('change.cwb_worldbook_selection', 'input[name="cwb_worldbook_selection"]', function() { - const radio = $(this); - if (radio.prop('checked')) { - settings.cwb_custom_worldbook = radio.val(); - state.customWorldBook = radio.val(); - saveSettingsDebounced(); - showToastr('info', `已选择世界书: ${radio.next('label').text()}`); - } - }); - - $panel.off('click.cwb_refresh_worldbooks').on('click.cwb_refresh_worldbooks', '#cwb_refresh_worldbooks', renderWorldBookList); - - } else if (attempt < MAX_RETRIES) { - attempt++; - console.log(`[CWB] World books not ready, retrying... (Attempt ${attempt})`); - setTimeout(tryBind, RETRY_DELAY); - } else { - console.error('[CWB] Failed to load world books after multiple retries.'); - $panel.find('#cwb_worldbook_radio_list').html('加载世界书失败,请刷新页面重试。
'); - } - } - - tryBind(); -} - -export function bindSettingsEvents($settingsPanel) { - $panel = $settingsPanel; - - bindWorldBookSettings(); - $panel.on('click', '.sinan-nav-item', function () { - const $this = $(this); - const tabId = $this.data('tab'); - - $panel.find('.sinan-nav-item').removeClass('active'); - $this.addClass('active'); - $panel.find('.sinan-tab-pane').removeClass('active'); - $panel.find(`#cwb-${tabId}-tab`).addClass('active'); - }); - $panel.on('change', '#cwb-api-mode', function() { - const selectedMode = $(this).val(); - - // 自动保存API模式设置 - getSettings().cwb_api_mode = selectedMode; - saveSettingsDebounced(); - - updateApiModeUI(selectedMode); - if (selectedMode === 'sillytavern_preset') { - loadSillyTavernPresets(true); - } - - showToastr('success', `API模式已切换为: ${selectedMode === 'sillytavern_preset' ? 'SillyTavern预设' : '全兼容'}`); - }); - $panel.on('change', '#cwb-tavern-profile', function() { - const selectedProfile = $(this).val(); - - // 自动保存SillyTavern预设选择 - getSettings().cwb_tavern_profile = selectedProfile; - saveSettingsDebounced(); - - if (selectedProfile) { - console.log(`[CWB] 选择了预设: ${selectedProfile}`); - showToastr('success', `SillyTavern预设已选择: ${selectedProfile}`); - } - - updateApiStatusDisplay($panel); - }); - // 添加API字段的实时保存 - $panel.on('input', '#cwb-api-url', function() { - const apiUrl = $(this).val().trim(); - - // 同时更新设置和状态 - getSettings().cwb_api_url = apiUrl; - state.customApiConfig.url = apiUrl; - - saveSettingsDebounced(); - updateApiStatusDisplay($panel); - - console.log('[CWB] API URL已更新 - 设置:', getSettings().cwb_api_url, ', 状态:', state.customApiConfig.url); - }); - - $panel.on('input', '#cwb-api-key', function() { - const apiKey = $(this).val(); - - // 同时更新设置和状态 - getSettings().cwb_api_key = apiKey; - state.customApiConfig.apiKey = apiKey; - - saveSettingsDebounced(); - - console.log('[CWB] API Key已更新 - 设置长度:', getSettings().cwb_api_key?.length || 0, ', 状态长度:', state.customApiConfig.apiKey?.length || 0); - }); - - $panel.on('change', '#cwb-api-model', function() { - const model = $(this).val(); - - // 同时更新设置和状态 - getSettings().cwb_api_model = model; - state.customApiConfig.model = model; - - saveSettingsDebounced(); - updateApiStatusDisplay($panel); - - console.log('[CWB] 模型已更新 - 设置:', getSettings().cwb_api_model, ', 状态:', state.customApiConfig.model); - - if (model) { - showToastr('success', `模型已选择: ${model}`); - } - }); - - $panel.on('click', '#cwb-load-models', () => fetchModelsAndConnect($panel)); - - $panel.on('click', '#cwb-save-break-armor-prompt', saveBreakArmorPrompt); - $panel.on('click', '#cwb-reset-break-armor-prompt', resetBreakArmorPrompt); - $panel.on('click', '#cwb-save-char-card-prompt', saveCharCardPrompt); - $panel.on('click', '#cwb-reset-char-card-prompt', resetCharCardPrompt); - - $panel.on('click', '#cwb-save-auto-update-threshold', saveAutoUpdateThreshold); - $panel.on('click', '#cwb-manual-update-card', () => handleManualUpdateCard($panel)); - $panel.on('click', '#cwb-batch-update-card', () => startBatchUpdate($panel)); - $panel.on('click', '#cwb-floor-range-update', () => handleFloorRangeUpdate($panel)); - $panel.on('click', '#cwb-check-for-updates', () => checkForUpdates(true, $panel)); - - $panel.on('click', '#cwb-auto-update-enabled', function () { - const $checkbox = $(this).find('input[type="checkbox"]'); - const isChecked = !$checkbox.prop('checked'); - $checkbox.prop('checked', isChecked); - - console.log(`[CWB] Auto-update switch clicked. New state: ${isChecked}`); - getSettings().cwb_auto_update_enabled = isChecked; - - const overrides = JSON.parse(localStorage.getItem(CWB_BOOLEAN_SETTINGS_OVERRIDE_KEY) || '{}'); - overrides.cwb_auto_update_enabled = isChecked; - localStorage.setItem(CWB_BOOLEAN_SETTINGS_OVERRIDE_KEY, JSON.stringify(overrides)); - - saveSettingsDebounced(); - state.autoUpdateEnabled = isChecked; - showToastr('info', `角色卡自动更新已 ${isChecked ? '启用' : '禁用'}`); - }); - - $panel.on('click', '#cwb-viewer-enabled', function () { - const $checkbox = $(this).find('input[type="checkbox"]'); - const isChecked = !$checkbox.prop('checked'); - $checkbox.prop('checked', isChecked); - - console.log(`[CWB] Viewer switch clicked. New state: ${isChecked}`); - getSettings().cwb_viewer_enabled = isChecked; - - const overrides = JSON.parse(localStorage.getItem(CWB_BOOLEAN_SETTINGS_OVERRIDE_KEY) || '{}'); - overrides.cwb_viewer_enabled = isChecked; - localStorage.setItem(CWB_BOOLEAN_SETTINGS_OVERRIDE_KEY, JSON.stringify(overrides)); - - saveSettingsDebounced(); - - state.viewerEnabled = isChecked; - - const $viewerButton = $(`#${CHAR_CARD_VIEWER_BUTTON_ID}`); - if ($viewerButton.length > 0) { - const shouldShow = isCwbEnabled() && isChecked; - $viewerButton.toggle(shouldShow); - } - - showToastr('info', `角色卡查看器已 ${isChecked ? '启用' : '禁用'}`); - }); - - $panel.on('click', '#cwb-incremental-update-enabled', function () { - const $checkbox = $(this).find('input[type="checkbox"]'); - const isChecked = !$checkbox.prop('checked'); // Manually toggle - $checkbox.prop('checked', isChecked); - - console.log(`[CWB] Incremental update switch clicked. New state: ${isChecked}`); - getSettings().cwb_incremental_update_enabled = isChecked; - - const overrides = JSON.parse(localStorage.getItem(CWB_BOOLEAN_SETTINGS_OVERRIDE_KEY) || '{}'); - overrides.cwb_incremental_update_enabled = isChecked; - localStorage.setItem(CWB_BOOLEAN_SETTINGS_OVERRIDE_KEY, JSON.stringify(overrides)); - - saveSettingsDebounced(); - state.isIncrementalUpdateEnabled = isChecked; - showToastr('info', `增量更新模式已 ${isChecked ? '启用' : '禁用'}`); - }); - - $panel.on('click', '#cwb_master_enabled', function () { - const $checkbox = $(this).find('input[type="checkbox"]'); - const isChecked = !$checkbox.prop('checked'); - $checkbox.prop('checked', isChecked); - - console.log(`[CWB] Master switch clicked. New state: ${isChecked}`); - - getSettings().cwb_master_enabled = isChecked; - - const overrides = JSON.parse(localStorage.getItem(CWB_BOOLEAN_SETTINGS_OVERRIDE_KEY) || '{}'); - overrides.cwb_master_enabled = isChecked; - localStorage.setItem(CWB_BOOLEAN_SETTINGS_OVERRIDE_KEY, JSON.stringify(overrides)); - - state.masterEnabled = isChecked; - - saveSettingsDebounced(); - - updateControlsLockState(); - - const $viewerButton = $(`#${CHAR_CARD_VIEWER_BUTTON_ID}`); - if ($viewerButton.length > 0) { - const shouldShow = isChecked && state.viewerEnabled; - $viewerButton.toggle(shouldShow); - } - - showToastr('info', `CharacterWorldBook 已 ${isChecked ? '启用' : '禁用'}`); - - $(document).trigger('cwb:master-switch-changed', { isEnabled: isChecked }); - }); -} - -function updateApiModeUI(mode) { - const fields = { - openai: [ - 'label[for="cwb-api-url"]', - '#cwb-api-url', - 'label[for="cwb-api-key"]', - '#cwb-api-key', - 'label[for="cwb-api-model"]', - '#cwb-api-model', - '#cwb-load-models' - ], - sillytavern: [ - 'label[for="cwb-tavern-profile"]', - '#cwb-tavern-profile' - ] - }; - - if (mode === 'sillytavern_preset') { - fields.openai.forEach(selector => $panel.find(selector).hide()); - fields.sillytavern.forEach(selector => $panel.find(selector).show()); - } else { - fields.sillytavern.forEach(selector => $panel.find(selector).hide()); - fields.openai.forEach(selector => $panel.find(selector).show()); - } - - updateApiStatusDisplay($panel); -} - -function loadSillyTavernPresets(showNotification = false) { - const $profileSelect = $panel.find('#cwb-tavern-profile'); - try { - const context = window.SillyTavern?.getContext?.(); - if (!context?.extensionSettings?.connectionManager?.profiles) { - showToastr('warning', '无法获取SillyTavern配置文件列表'); + let localTavernHelper = TavernHelper; + if (!localTavernHelper) { + // TavernHelper 未定义的情况下触发,但是为什么? + (localTavernHelper = window.TavernHelper); + } + const primaryBook = await localTavernHelper.getCurrentCharPrimaryLorebook(); + if (!primaryBook) { + showToastr('error', '当前角色未设置主世界书。'); + return null; + } + return primaryBook; + } catch (error) { + logError('获取主世界书时出错:', error); + return null; + } +} + +export async function deleteLorebookEntries(uids) { + if (!Array.isArray(uids) || uids.length === 0) return; + + try { + const context = SillyTavern.getContext(); + if (!context || !context.characterId) { + throw new Error('没有选择角色,无法删除。'); + } + const book = await getTargetWorldBook(); + if (!book) throw new Error('未找到目标世界书。'); + + await TavernHelper.deleteLorebookEntries(book, uids.map(Number)); + } catch (error) { + logError('删除世界书条目失败:', error); + showToastr('error', `删除失败: ${error.message}`); + } +} + +export async function saveDescriptionToLorebook(characterName, newDescription, startFloor, endFloor) { + if (!characterName?.trim()) return false; + + try { + const context = SillyTavern.getContext(); + if (!context || !context.characterId) { + showToastr('error', '没有选择角色,无法保存到世界书。'); + return false; + } + let chatIdentifier = state.currentChatFileIdentifier || '未知聊天'; + chatIdentifier = chatIdentifier.replace(/ imported/g, ''); + + const safeCharName = characterName.replace(/[^a-zA-Z0-9\u4e00-\u9fa5·""“”_-]/g, ','); + const floorRange = `${startFloor + 1}-${endFloor + 1}`; + + const newComment = `${safeCharName}-${chatIdentifier}`; + + let bookName; + if (state.worldbookTarget === 'custom' && state.customWorldBook) { + bookName = state.customWorldBook; + } else { + bookName = await TavernHelper.getCurrentCharPrimaryLorebook(); + } + + if (!bookName) { + showToastr('error', '未能确定要写入的世界书。请检查主世界书或自定义世界书设置。'); + return false; + } + + const entries = (await TavernHelper.getLorebookEntries(bookName)) || []; + let existing = entries.find(e => + Array.isArray(e.keys) && + e.keys.includes(chatIdentifier) && + e.keys.includes(safeCharName) && + !e.keys.includes('Amily2角色总集') + ); + + const entryData = { + comment: newComment, + content: newDescription, + keys: [chatIdentifier, safeCharName, floorRange], + enabled: true, + type: 'selective', + }; + + if (existing) { + await TavernHelper.setLorebookEntries(bookName, [{ uid: existing.uid, ...entryData }]); + } else { + const cwbEntries = entries.filter(e => + Array.isArray(e.keys) && + e.keys.includes(chatIdentifier) && + !e.keys.includes('Amily2角色总集') + ); + let maxDepth = 7000; + cwbEntries.forEach(entry => { + if (entry.position === 'at_depth_as_system' && typeof entry.depth === 'number') { + if (entry.depth >= 7001 && entry.depth > maxDepth) { + maxDepth = entry.depth; + } + } + }); + + const newDepth = maxDepth + 1; + let maxOrder = 7000; + if (cwbEntries.length > 0) { + maxOrder = cwbEntries.reduce((max, entry) => { + const order = Number(entry.order); + return !isNaN(order) && order > max ? order : max; + }, 7000); + } + + const newEntryData = { + ...entryData, + order: 100, + position: 'at_depth_as_system', + depth: newDepth, + }; + + logDebug(`创建新角色条目:${safeCharName}`, { + position: newEntryData.position, + depth: newEntryData.depth, + order: newEntryData.order + }); + + await TavernHelper.createLorebookEntries(bookName, [newEntryData]); + } + showToastr('success', `角色 ${safeCharName} 的描述已保存到世界书。`); + return true; + } catch (error) { + logError(`保存世界书失败 for ${characterName}:`, error); + showToastr('error', `保存角色 ${safeCharName} 到世界书失败。`); + return false; + } +} + +export async function updateCharacterRosterLorebookEntry(processedCharacterNames, startFloor, endFloor) { + if (!Array.isArray(processedCharacterNames)) return true; + + try { + const context = SillyTavern.getContext(); + if (!context || !context.characterId) { + logDebug('未选择角色,无法更新角色名册。'); + return false; + } + let chatIdentifier = state.currentChatFileIdentifier || '未知聊天'; + if (chatIdentifier === '未知聊天') return false; + + const cleanChatId = chatIdentifier.replace(/ imported/g, ''); + const rosterEntryComment = `Amily2角色总集-${cleanChatId}-角色总览`; + + let characterCardName = '未识别到该角色卡名称'; + try { + const currentChar = context.characters[context.characterId]; + if (currentChar && currentChar.name) { + characterCardName = currentChar.name.trim(); + } + } catch (e) { + logDebug('[CWB] 无法获取角色名称,使用默认值'); + } + + const initialContentPrefix = `此为当前角色卡【${characterCardName}】中登场的角色,AI需要根据剧情让以下角色在合适的时机登场:\n\n`; + + let bookName; + if (state.worldbookTarget === 'custom' && state.customWorldBook) { + bookName = state.customWorldBook; + } else { + bookName = await TavernHelper.getCurrentCharPrimaryLorebook(); + } + + if (!bookName) { + showToastr('error', '未能确定要写入的世界书。请检查主世界书或自定义世界书设置。'); + return false; + } + + let entries = (await TavernHelper.getLorebookEntries(bookName)) || []; + let existingRosterEntry = entries.find(entry => + entry.comment === rosterEntryComment || + entry.comment === `Amily2角色总集-${chatIdentifier}-角色总览` + ); + + let existingNames = new Set(); + let oldStartFloor = 1; + let oldEndFloor = 0; + + if (existingRosterEntry) { + if (existingRosterEntry.content) { + let contentToParse = existingRosterEntry.content.replace(initialContentPrefix, ''); + + const floorMatch = contentToParse.match(/【前(\d+)楼角色世界书已更新完成】/); + if (floorMatch && floorMatch[1]) { + oldEndFloor = parseInt(floorMatch[1], 10); + } + + contentToParse.split('\n').forEach(line => { + if (line.trim().startsWith('[')) { + const nameMatch = line.match(/\[(.*?):/); + if (nameMatch && nameMatch[1]) { + existingNames.add(nameMatch[1].trim()); + } + } + }); + } + const floorRangeKey = existingRosterEntry.keys.find(k => /^\d+-\d+$/.test(k)); + if (floorRangeKey) { + [oldStartFloor] = floorRangeKey.split('-').map(Number); + } + } + + processedCharacterNames.forEach(name => existingNames.add(name.trim())); + + const newStartFloor = Math.min(oldStartFloor, startFloor + 1); + const newEndFloor = Math.max(oldEndFloor, endFloor + 1); + + const newContent = + initialContentPrefix + + [...existingNames] + .sort() + .map(name => `[${name}: (详细查看绿灯角色条目)]`) + .join('\n') + `\n\n{{// 本条勿动,【前${newEndFloor}楼角色世界书已更新完成】否则后续更新无法完成。}}`; + + const newFloorRange = `${newStartFloor}-${newEndFloor}`; + + const baseKeys = [`Amily2角色总集`, cleanChatId, `角色总览`]; + const newKeys = [...baseKeys, newFloorRange]; + + const entryData = { + content: newContent, + keys: newKeys, + type: 'constant', + position: 'before_character_definition', + depth: null, + enabled: true, + order: 9999, + prevent_recursion: true, + }; + + if (existingRosterEntry) { + await TavernHelper.setLorebookEntries(bookName, [ + { uid: existingRosterEntry.uid, comment: rosterEntryComment, ...entryData }, + ]); + } else { + await TavernHelper.createLorebookEntries(bookName, [ + { comment: rosterEntryComment, ...entryData }, + ]); + } + return true; + } catch (error) { + logError('更新角色名册条目时出错:', error); + return false; + } +} + + +export async function manageAutoCardUpdateLorebookEntry() { + try { + if (state.worldbookTarget === 'custom' && state.customWorldBook) { + logDebug('[CWB] 使用自定义世界书模式,跳过角色总览条目的自动管理'); return; } - - const profiles = context.extensionSettings.connectionManager.profiles; - - $profileSelect.empty(); - $profileSelect.append(''); - - profiles.forEach(profile => { - $profileSelect.append(``); - }); - const currentProfile = getSettings().cwb_tavern_profile; - if (currentProfile) { - $profileSelect.val(currentProfile); + + const context = SillyTavern.getContext(); + if (!context || !context.characterId) { + logDebug('未选择角色,跳过世界书管理。'); + return; } + const bookName = await getTargetWorldBook(); + if (!bookName) return; + + const entries = (await TavernHelper.getLorebookEntries(bookName)) || []; - if (showNotification) { - showToastr('success', `已加载 ${profiles.length} 个SillyTavern预设`); + const currentChatId = state.currentChatFileIdentifier; + if (!currentChatId || currentChatId.startsWith('unknown_chat')) { + logError(`无效的聊天标识符 "${currentChatId}"。正在中止世界书管理。`); + return; } - - } catch (error) { - logError('加载SillyTavern预设失败:', error); - showToastr('error', '加载SillyTavern预设失败'); - } -} + const cleanChatId = currentChatId.replace(/ imported/g, ''); -function updateUiWithSettings() { - if (!$panel) return; - const settings = getSettings(); + let currentChatRosterExists = false; + const entriesToUpdate = []; - $panel.find('#cwb-api-mode').val(settings.cwb_api_mode || 'openai_test'); + for (const entry of entries) { + if (Array.isArray(entry.keys) && (entry.keys.includes('Amily2角色总集') || entry.keys.includes(cleanChatId) || entry.keys.includes(currentChatId))) { + + const isForCurrentChat = entry.keys.includes(cleanChatId) || entry.keys.includes(currentChatId); + let shouldBeEnabled = isForCurrentChat; - const currentMode = settings.cwb_api_mode || 'openai_test'; - updateApiModeUI(currentMode); + if (isForCurrentChat && entry.keys.includes('角色总览')) { + currentChatRosterExists = true; + } - if (currentMode === 'sillytavern_preset') { - loadSillyTavernPresets(); - } - - $panel.find('#cwb-api-url').val(settings.cwb_api_url); - $panel.find('#cwb-api-key').val(settings.cwb_api_key); - $panel.find('#cwb-tavern-profile').val(settings.cwb_tavern_profile); - - const $modelSelect = $panel.find('#cwb-api-model'); - if (settings.cwb_api_model) { - $modelSelect.empty().append(``); - } else { - $modelSelect.empty().append(''); - } - updateApiStatusDisplay($panel); - - $panel.find('#cwb-break-armor-prompt-textarea').val(settings.cwb_break_armor_prompt); - $panel.find('#cwb-char-card-prompt-textarea').val(settings.cwb_char_card_prompt); - - $panel.find('#cwb-temperature').val(settings.cwb_temperature); - $panel.find('#cwb-temperature-value').text(settings.cwb_temperature); - $panel.find('#cwb-max-tokens').val(settings.cwb_max_tokens); - $panel.find('#cwb-max-tokens-value').text(settings.cwb_max_tokens); - - $panel.find('#cwb-auto-update-threshold').val(settings.cwb_auto_update_threshold); - $panel.find('#cwb_master_enabled-checkbox').prop('checked', settings.cwb_master_enabled); - $panel.find('#cwb-auto-update-enabled-checkbox').prop('checked', settings.cwb_auto_update_enabled); - $panel.find('#cwb-viewer-enabled-checkbox').prop('checked', settings.cwb_viewer_enabled); - $panel.find('#cwb-incremental-update-enabled-checkbox').prop('checked', settings.cwb_incremental_update_enabled); - - if (!$panel.find('#cwb-start-floor').val()) { - $panel.find('#cwb-start-floor').val(1); - } - if (!$panel.find('#cwb-end-floor').val()) { - $panel.find('#cwb-end-floor').val(1); - } - - $panel.find('input[name="cwb_worldbook_target"]').each(function() { - $(this).prop('checked', $(this).val() === settings.cwb_worldbook_target); - }); - if (settings.cwb_worldbook_target === 'custom') { - $panel.find('#cwb_worldbook_select_wrapper').show(); - } else { - $panel.find('#cwb_worldbook_select_wrapper').hide(); - } -} - -export function loadSettings() { - console.log('[CWB] Loading settings...'); - - const settings = getSettings(); - if (!settings) { - extension_settings[extensionName] = { ...cwbCompleteDefaultSettings }; - console.log('[CWB] Initialized default settings'); - } else { - Object.keys(cwbCompleteDefaultSettings).forEach(key => { - if (settings[key] === undefined || settings[key] === null) { - settings[key] = cwbCompleteDefaultSettings[key]; + if (entry.enabled !== shouldBeEnabled) { + entriesToUpdate.push({ uid: entry.uid, enabled: shouldBeEnabled }); + } } - }); - } - - const finalSettings = getSettings(); - const overrides = JSON.parse(localStorage.getItem(CWB_BOOLEAN_SETTINGS_OVERRIDE_KEY) || '{}'); - if (overrides.cwb_master_enabled !== undefined) { - finalSettings.cwb_master_enabled = overrides.cwb_master_enabled; - } - if (overrides.cwb_auto_update_enabled !== undefined) { - finalSettings.cwb_auto_update_enabled = overrides.cwb_auto_update_enabled; - } - if (overrides.cwb_viewer_enabled !== undefined) { - finalSettings.cwb_viewer_enabled = overrides.cwb_viewer_enabled; - } - if (overrides.cwb_incremental_update_enabled !== undefined) { - finalSettings.cwb_incremental_update_enabled = overrides.cwb_incremental_update_enabled; - } - state.masterEnabled = finalSettings.cwb_master_enabled; - state.viewerEnabled = finalSettings.cwb_viewer_enabled; - state.autoUpdateEnabled = finalSettings.cwb_auto_update_enabled; - state.isIncrementalUpdateEnabled = finalSettings.cwb_incremental_update_enabled; - - state.customApiConfig.url = finalSettings.cwb_api_url || ''; - state.customApiConfig.apiKey = finalSettings.cwb_api_key || ''; - state.customApiConfig.model = finalSettings.cwb_api_model || ''; - - state.currentBreakArmorPrompt = finalSettings.cwb_break_armor_prompt; - state.currentCharCardPrompt = finalSettings.cwb_char_card_prompt; - state.currentIncrementalCharCardPrompt = finalSettings.cwb_incremental_char_card_prompt; - - state.autoUpdateThreshold = finalSettings.cwb_auto_update_threshold; - state.worldbookTarget = finalSettings.cwb_worldbook_target; - state.customWorldBook = finalSettings.cwb_custom_worldbook; - - console.log('[CWB] State updated:', { - masterEnabled: state.masterEnabled, - viewerEnabled: state.viewerEnabled, - autoUpdateEnabled: state.autoUpdateEnabled, - worldbookTarget: state.worldbookTarget, - customWorldBook: state.customWorldBook - }); - - if ($panel) { - updateUiWithSettings(); - } - - updateControlsLockState(); - - setTimeout(() => { - const $viewerButton = $(`#${CHAR_CARD_VIEWER_BUTTON_ID}`); - if ($viewerButton.length > 0) { - const shouldShow = isCwbEnabled() && state.viewerEnabled; - $viewerButton.toggle(shouldShow); - console.log('[CWB] Viewer button visibility updated:', shouldShow); } - }, 100); + + if (entriesToUpdate.length > 0) { + await TavernHelper.setLorebookEntries(bookName, entriesToUpdate); + logDebug(`已为聊天: ${cleanChatId} 管理了 ${entriesToUpdate.length} 个世界书条目的状态。`); + } + + if (!currentChatRosterExists) { + logDebug(`未找到聊天 "${cleanChatId}" 的名册。正在触发创建。`); + await updateCharacterRosterLorebookEntry([]); + } + + } catch (error) { + logError('管理世界书条目时出错:', error); + } }