diff --git a/CharacterWorldBook/src/cwb_lorebookManager.js b/CharacterWorldBook/src/cwb_lorebookManager.js index 4414138..5169b4b 100644 --- a/CharacterWorldBook/src/cwb_lorebookManager.js +++ b/CharacterWorldBook/src/cwb_lorebookManager.js @@ -1,319 +1,586 @@ +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 { logError, logDebug, showToastr, parseCustomFormat } from './cwb_utils.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'; -const { SillyTavern, TavernHelper } = window; +const { jQuery: $ } = window; -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; - } - try { - 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; +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 deleteLorebookEntries(uids) { - if (!Array.isArray(uids) || uids.length === 0) return; +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(); - try { - const context = SillyTavern.getContext(); - if (!context || !context.characterId) { - throw new Error('没有选择角色,无法删除。'); + if (settings.cwb_api_mode === 'sillytavern_preset') { + if (!settings.cwb_tavern_profile) { + showToastr('warning', '请选择SillyTavern预设。'); + return; } - const book = await getTargetWorldBook(); - if (!book) throw new Error('未找到目标世界书。'); + showToastr('success', 'API配置已保存!'); + } else { + if (!settings.cwb_api_url) { + showToastr('warning', 'API URL 不能为空。'); + return; + } + showToastr('success', 'API配置已保存!'); + } + + saveSettingsDebounced(); + loadSettings(); +} - await TavernHelper.deleteLorebookEntries(book, uids.map(Number)); - } catch (error) { - logError('删除世界书条目失败:', error); - showToastr('error', `删除失败: ${error.message}`); +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); } } -export async function saveDescriptionToLorebook(characterName, newDescription, startFloor, endFloor) { - if (!characterName?.trim()) return false; +function bindWorldBookSettings() { + const MAX_RETRIES = 10; + const RETRY_DELAY = 200; + let attempt = 0; - 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}`; + function tryBind() { + if (world_names && world_names.length > 0) { + console.log('[CWB] World books loaded, binding settings...'); + const settings = getSettings(); - const newComment = `${safeCharName}-${chatIdentifier}`; + if (settings.cwb_worldbook_target === undefined) settings.cwb_worldbook_target = 'primary'; + if (settings.cwb_custom_worldbook === undefined) settings.cwb_custom_worldbook = null; - let bookName; - if (state.worldbookTarget === 'custom' && state.customWorldBook) { - bookName = state.customWorldBook; - } else { - bookName = await TavernHelper.getCurrentCharPrimaryLorebook(); - } + const customSelectWrapper = $panel.find('#cwb_worldbook_select_wrapper'); + const bookListContainer = $panel.find('#cwb_worldbook_radio_list'); - if (!bookName) { - showToastr('error', '未能确定要写入的世界书。请检查主世界书或自定义世界书设置。'); - return false; - } + const renderWorldBookList = () => { + const worldBooks = world_names.map(name => ({ name: name.replace('.json', ''), file_name: name })); + bookListContainer.empty(); - 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; - } + 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 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 + + 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); }); - - await TavernHelper.createLorebookEntries(bookName, [newEntryData]); - } - showToastr('success', `角色 ${safeCharName} 的描述已保存到世界书。`); - return true; - } catch (error) { - logError(`保存世界书失败 for ${characterName}:`, error); - showToastr('error', `保存角色 ${safeCharName} 到世界书失败。`); - return false; - } -} + updateCustomSelectVisibility(); -export async function updateCharacterRosterLorebookEntry(processedCharacterNames, startFloor, endFloor) { - if (!Array.isArray(processedCharacterNames)) return true; + $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(); + } + }); - 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}-角色总览`; + 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()}`); + } + }); - 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; + $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 { - bookName = await TavernHelper.getCurrentCharPrimaryLorebook(); + console.error('[CWB] Failed to load world books after multiple retries.'); + $panel.find('#cwb_worldbook_radio_list').html('

加载世界书失败,请刷新页面重试。

'); } - - 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; } + + tryBind(); } +export function bindSettingsEvents($settingsPanel) { + $panel = $settingsPanel; -export async function manageAutoCardUpdateLorebookEntry() { + 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 { - if (state.worldbookTarget === 'custom' && state.customWorldBook) { - logDebug('[CWB] 使用自定义世界书模式,跳过角色总览条目的自动管理'); + const context = window.SillyTavern?.getContext?.(); + if (!context?.extensionSettings?.connectionManager?.profiles) { + showToastr('warning', '无法获取SillyTavern配置文件列表'); return; } - - const context = SillyTavern.getContext(); - if (!context || !context.characterId) { - logDebug('未选择角色,跳过世界书管理。'); - return; - } - const bookName = await getTargetWorldBook(); - if (!bookName) return; - - const entries = (await TavernHelper.getLorebookEntries(bookName)) || []; - const currentChatId = state.currentChatFileIdentifier; - if (!currentChatId || currentChatId.startsWith('unknown_chat')) { - logError(`无效的聊天标识符 "${currentChatId}"。正在中止世界书管理。`); - 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 cleanChatId = currentChatId.replace(/ imported/g, ''); - - let currentChatRosterExists = false; - const entriesToUpdate = []; - - 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; - - if (isForCurrentChat && entry.keys.includes('角色总览')) { - currentChatRosterExists = true; - } - - if (entry.enabled !== shouldBeEnabled) { - entriesToUpdate.push({ uid: entry.uid, enabled: shouldBeEnabled }); - } - } + + if (showNotification) { + showToastr('success', `已加载 ${profiles.length} 个SillyTavern预设`); } - - if (entriesToUpdate.length > 0) { - await TavernHelper.setLorebookEntries(bookName, entriesToUpdate); - logDebug(`已为聊天: ${cleanChatId} 管理了 ${entriesToUpdate.length} 个世界书条目的状态。`); - } - - if (!currentChatRosterExists) { - logDebug(`未找到聊天 "${cleanChatId}" 的名册。正在触发创建。`); - await updateCharacterRosterLorebookEntry([]); - } - + } catch (error) { - logError('管理世界书条目时出错:', error); + logError('加载SillyTavern预设失败:', error); + showToastr('error', '加载SillyTavern预设失败'); } } + +function updateUiWithSettings() { + if (!$panel) return; + const settings = getSettings(); + + $panel.find('#cwb-api-mode').val(settings.cwb_api_mode || 'openai_test'); + + const currentMode = settings.cwb_api_mode || 'openai_test'; + updateApiModeUI(currentMode); + + 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]; + } + }); + } + + 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); +}