import { extension_settings, getContext } from "/scripts/extensions.js"; import { characters, this_chid, getRequestHeaders, saveSettingsDebounced, eventSource, event_types } from "/script.js"; import { defaultSettings, extensionName, saveSettings } from "../utils/settings.js"; import { pluginAuthStatus, activatePluginAuthorization, getPasswordForDate } from "../utils/auth.js"; import { fetchModels, testApiConnection } from "../core/api.js"; import { getJqyhApiSettings, testJqyhApiConnection, fetchJqyhModels } from '../core/api/JqyhApi.js'; import { safeLorebooks, safeCharLorebooks, safeLorebookEntries, isTavernHelperAvailable } from "../core/tavernhelper-compatibility.js"; import { setAvailableModels, populateModelDropdown, getLatestUpdateInfo } from "./state.js"; import { fixCommand, testReplyChecker } from "../core/commands.js"; import { createDrawer } from '../ui/drawer.js'; import { messageFormatting } from '/script.js'; import { executeManualCommand } from '../core/autoHideManager.js'; import { showContentModal, showHtmlModal } from './page-window.js'; function displayDailyAuthCode() { const displayEl = document.getElementById('amily2_daily_code_display'); const copyBtn = document.getElementById('amily2_copy_daily_code'); if (displayEl && copyBtn) { const todayCode = getPasswordForDate(new Date()); displayEl.textContent = todayCode; copyBtn.addEventListener('click', () => { navigator.clipboard.writeText(todayCode).then(() => { toastr.success('授权码已复制到剪贴板!'); }, () => { toastr.error('复制失败,请手动复制。'); }); }); } } async function loadSillyTavernPresets() { console.log('[Amily2号-UI] 正在加载SillyTavern预设列表'); const select = $('#amily2_preset_selector'); const settings = extension_settings[extensionName] || {}; const currentProfileId = settings.tavernProfile || settings.selectedPreset; select.empty().append(new Option('-- 请选择一个酒馆预设 --', '')); try { const context = getContext(); const tavernProfiles = context.extensionSettings?.connectionManager?.profiles || []; if (!tavernProfiles || tavernProfiles.length === 0) { select.append($('', { value: '', text: '未找到酒馆预设', disabled: true })); console.warn('[Amily2号-UI] 未找到SillyTavern预设'); return; } let foundCurrentProfile = false; tavernProfiles.forEach(profile => { if (profile.api && profile.preset) { const option = new Option(profile.name || profile.id, profile.id); if (profile.id === currentProfileId) { option.selected = true; foundCurrentProfile = true; } select.append(option); } }); if (currentProfileId && !foundCurrentProfile) { toastr.warning(`之前选择的酒馆预设 "${currentProfileId}" 已不存在,请重新选择。`, "Amily2号"); const updateAndSaveSetting = (key, value) => { if (!extension_settings[extensionName]) { extension_settings[extensionName] = {}; } extension_settings[extensionName][key] = value; saveSettingsDebounced(); }; updateAndSaveSetting('selectedPreset', ''); updateAndSaveSetting('tavernProfile', ''); } else if (foundCurrentProfile) { console.log(`[Amily2号-UI] SillyTavern预设已成功恢复:${currentProfileId}`); } const validProfiles = tavernProfiles.filter(p => p.api && p.preset); console.log(`[Amily2号-UI] SillyTavern预设列表加载完成,找到 ${validProfiles.length} 个有效预设`); } catch (error) { console.error(`[Amily2号-UI] 加载酒馆API预设失败:`, error); select.append($('', { value: '', text: '加载预设失败', disabled: true })); toastr.error('无法加载酒馆API预设列表,请查看控制台。', 'Amily2号'); } } function updateApiProviderUI() { const settings = extension_settings[extensionName] || {}; const provider = settings.apiProvider || 'openai'; $('#amily2_api_provider').val(provider); $('#amily2_api_provider').trigger('change'); } function bindAmily2ModalWorldBookSettings() { if (!extension_settings[extensionName]) { extension_settings[extensionName] = {}; } const settings = extension_settings[extensionName]; const enabledCheckbox = document.getElementById('amily2_wb_enabled'); const optionsContainer = document.getElementById('amily2_wb_options_container'); const sourceRadios = document.querySelectorAll('input[name="amily2_wb_source"]'); const manualSelectWrapper = document.getElementById('amily2_wb_select_wrapper'); const bookListContainer = document.getElementById('amily2_wb_checkbox_list'); const entryListContainer = document.getElementById('amily2_wb_entry_list'); if (!enabledCheckbox || !optionsContainer || !sourceRadios.length || !manualSelectWrapper || !bookListContainer || !entryListContainer) { console.warn('[Amily2 Modal] World book UI elements not found, skipping bindings.'); return; } // Ensure settings objects exist before reading if (settings.modal_amily2_wb_selected_worldbooks === undefined) { settings.modal_amily2_wb_selected_worldbooks = []; } if (settings.modal_amily2_wb_selected_entries === undefined) { settings.modal_amily2_wb_selected_entries = {}; } const renderWorldBookEntries = async () => { entryListContainer.innerHTML = 'Loading entries...'; const source = settings.modal_wbSource || 'character'; let bookNames = []; if (source === 'manual') { bookNames = settings.modal_amily2_wb_selected_worldbooks || []; } else { if (this_chid !== undefined && this_chid >= 0 && characters[this_chid]) { try { const charLorebooks = await safeCharLorebooks({ type: 'all' }); if (charLorebooks.primary) bookNames.push(charLorebooks.primary); if (charLorebooks.additional?.length) bookNames.push(...charLorebooks.additional); } catch (error) { console.error(`[Amily2 Modal] Failed to get character world books:`, error); entryListContainer.innerHTML = 'Failed to get character world books.'; return; } } else { entryListContainer.innerHTML = 'Please load a character first.'; return; } } if (bookNames.length === 0) { entryListContainer.innerHTML = 'No world book selected or linked.'; return; } try { const allEntries = []; for (const bookName of bookNames) { const entries = await safeLorebookEntries(bookName); entries.forEach(entry => allEntries.push({ ...entry, bookName })); } entryListContainer.innerHTML = ''; if (allEntries.length === 0) { entryListContainer.innerHTML = 'No entries in the selected world book(s).'; return; } allEntries.forEach(entry => { const div = document.createElement('div'); div.className = 'checkbox-item'; div.title = `World Book: ${entry.bookName}\nUID: ${entry.uid}`; div.style.display = 'flex'; div.style.alignItems = 'center'; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.style.marginRight = '5px'; checkbox.id = `amily2-wb-entry-check-${entry.bookName}-${entry.uid}`; checkbox.dataset.book = entry.bookName; checkbox.dataset.uid = entry.uid; const isChecked = settings.modal_amily2_wb_selected_entries[entry.bookName]?.includes(String(entry.uid)); checkbox.checked = !!isChecked; const label = document.createElement('label'); label.htmlFor = checkbox.id; label.textContent = entry.comment || 'Untitled Entry'; div.appendChild(checkbox); div.appendChild(label); entryListContainer.appendChild(div); }); } catch (error) { console.error(`[Amily2 Modal] Failed to load world book entries:`, error); entryListContainer.innerHTML = 'Failed to load entries.'; } }; const renderWorldBookList = async () => { bookListContainer.innerHTML = 'Loading world books...'; try { const worldBooks = await safeLorebooks(); bookListContainer.innerHTML = ''; if (worldBooks && worldBooks.length > 0) { worldBooks.forEach(bookName => { const div = document.createElement('div'); div.className = 'checkbox-item'; div.title = bookName; div.style.display = 'flex'; div.style.alignItems = 'center'; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.style.marginRight = '5px'; checkbox.id = `amily2-wb-check-${bookName}`; checkbox.value = bookName; checkbox.checked = settings.modal_amily2_wb_selected_worldbooks.includes(bookName); const label = document.createElement('label'); label.htmlFor = `amily2-wb-check-${bookName}`; label.textContent = bookName; div.appendChild(checkbox); div.appendChild(label); bookListContainer.appendChild(div); }); } else { bookListContainer.innerHTML = 'No world books found.'; } } catch (error) { console.error(`[Amily2 Modal] Failed to load world book list:`, error); bookListContainer.innerHTML = 'Failed to load world book list.'; } renderWorldBookEntries(); }; const updateVisibility = () => { const settings = extension_settings[extensionName]; const isEnabled = enabledCheckbox.checked; optionsContainer.style.display = isEnabled ? 'block' : 'none'; if (isEnabled) { const isManual = settings.modal_wbSource === 'manual'; manualSelectWrapper.style.display = isManual ? 'block' : 'none'; renderWorldBookEntries(); if (isManual) { renderWorldBookList(); } } }; // Initial state setup enabledCheckbox.checked = settings.modal_wbEnabled ?? false; const source = settings.modal_wbSource ?? 'character'; sourceRadios.forEach(radio => { radio.checked = radio.value === source; }); updateVisibility(); // Event Listeners $(enabledCheckbox).off('change.amily2_wb').on('change.amily2_wb', () => { extension_settings[extensionName].modal_wbEnabled = enabledCheckbox.checked; saveSettingsDebounced(); updateVisibility(); }); $(sourceRadios).off('change.amily2_wb').on('change.amily2_wb', (event) => { if (event.target.checked) { extension_settings[extensionName].modal_wbSource = event.target.value; saveSettingsDebounced(); updateVisibility(); } }); $(bookListContainer).off('change.amily2_wb').on('change.amily2_wb', (event) => { if (event.target.type === 'checkbox' && event.target.id.startsWith('amily2-wb-check-')) { const checkbox = event.target; const bookName = checkbox.value; if (!settings.modal_amily2_wb_selected_worldbooks) { settings.modal_amily2_wb_selected_worldbooks = []; } if (checkbox.checked) { if (!settings.modal_amily2_wb_selected_worldbooks.includes(bookName)) { settings.modal_amily2_wb_selected_worldbooks.push(bookName); } } else { const index = settings.modal_amily2_wb_selected_worldbooks.indexOf(bookName); if (index > -1) { settings.modal_amily2_wb_selected_worldbooks.splice(index, 1); } if (settings.modal_amily2_wb_selected_entries) { delete settings.modal_amily2_wb_selected_entries[bookName]; } } saveSettingsDebounced(); renderWorldBookEntries(); } }); $(entryListContainer).off('change.amily2_wb').on('change.amily2_wb', (event) => { if (event.target.type === 'checkbox') { const checkbox = event.target; const book = checkbox.dataset.book; const uid = checkbox.dataset.uid; if (!settings.modal_amily2_wb_selected_entries) { settings.modal_amily2_wb_selected_entries = {}; } if (!settings.modal_amily2_wb_selected_entries[book]) { settings.modal_amily2_wb_selected_entries[book] = []; } const entryIndex = settings.modal_amily2_wb_selected_entries[book].indexOf(uid); if (checkbox.checked) { if (entryIndex === -1) { settings.modal_amily2_wb_selected_entries[book].push(uid); } } else { if (entryIndex > -1) { settings.modal_amily2_wb_selected_entries[book].splice(entryIndex, 1); } } if (settings.modal_amily2_wb_selected_entries[book].length === 0) { delete settings.modal_amily2_wb_selected_entries[book]; } saveSettingsDebounced(); } }); // Search and Select/Deselect All Logic const bookSearchInput = document.getElementById('amily2_wb_book_search'); const bookSelectAllBtn = document.getElementById('amily2_wb_book_select_all'); const bookDeselectAllBtn = document.getElementById('amily2_wb_book_deselect_all'); const entrySearchInput = document.getElementById('amily2_wb_entry_search'); const entrySelectAllBtn = document.getElementById('amily2_wb_entry_select_all'); const entryDeselectAllBtn = document.getElementById('amily2_wb_entry_deselect_all'); bookSearchInput.addEventListener('input', () => { const searchTerm = bookSearchInput.value.toLowerCase(); const items = bookListContainer.querySelectorAll('.checkbox-item'); items.forEach(item => { const label = item.querySelector('label'); if (label.textContent.toLowerCase().includes(searchTerm)) { item.style.display = 'flex'; } else { item.style.display = 'none'; } }); }); entrySearchInput.addEventListener('input', () => { const searchTerm = entrySearchInput.value.toLowerCase(); const items = entryListContainer.querySelectorAll('.checkbox-item'); items.forEach(item => { const label = item.querySelector('label'); if (label.textContent.toLowerCase().includes(searchTerm)) { item.style.display = 'flex'; } else { item.style.display = 'none'; } }); }); bookSelectAllBtn.addEventListener('click', () => { const checkboxes = bookListContainer.querySelectorAll('.checkbox-item input[type="checkbox"]'); checkboxes.forEach(checkbox => { if (checkbox.parentElement.style.display !== 'none' && !checkbox.checked) { $(checkbox).prop('checked', true).trigger('change'); } }); }); bookDeselectAllBtn.addEventListener('click', () => { const checkboxes = bookListContainer.querySelectorAll('.checkbox-item input[type="checkbox"]'); checkboxes.forEach(checkbox => { if (checkbox.parentElement.style.display !== 'none' && checkbox.checked) { $(checkbox).prop('checked', false).trigger('change'); } }); }); entrySelectAllBtn.addEventListener('click', () => { const checkboxes = entryListContainer.querySelectorAll('.checkbox-item input[type="checkbox"]'); checkboxes.forEach(checkbox => { if (checkbox.parentElement.style.display !== 'none' && !checkbox.checked) { $(checkbox).prop('checked', true).trigger('change'); } }); }); entryDeselectAllBtn.addEventListener('click', () => { const checkboxes = entryListContainer.querySelectorAll('.checkbox-item input[type="checkbox"]'); checkboxes.forEach(checkbox => { if (checkbox.parentElement.style.display !== 'none' && checkbox.checked) { $(checkbox).prop('checked', false).trigger('change'); } }); }); console.log('[Amily2 Modal] World book settings bound successfully.'); document.addEventListener('renderAmily2WorldBook', () => { console.log('[Amily2 Modal] Received render event from state update.'); updateVisibility(); }); eventSource.on(event_types.CHAT_CHANGED, () => { console.log('[Amily2 Modal] Chat changed, re-rendering world book entries.'); if (document.getElementById('amily2_wb_options_container')?.style.display === 'block') { renderWorldBookEntries(); } }); } export function bindModalEvents() { const refreshButton = document.getElementById('amily2_refresh_models'); if (refreshButton && !document.getElementById('amily2_test_api_connection')) { const testButton = document.createElement('button'); testButton.id = 'amily2_test_api_connection'; testButton.className = 'menu_button interactable'; testButton.innerHTML = ' 测试连接'; refreshButton.insertAdjacentElement('afterend', testButton); } initializePlotOptimizationBindings(); bindAmily2ModalWorldBookSettings(); const container = $("#amily2_drawer_content").length ? $("#amily2_drawer_content") : $("#amily2_chat_optimiser"); // Collapsible sections logic container.on('click touchend', '.collapsible-legend', function(e) { e.preventDefault(); e.stopPropagation(); const legend = $(this); // Debounce to prevent double firing on touch devices const now = Date.now(); const lastTouch = legend.data('lastTouch') || 0; if (now - lastTouch < 500) { return; } legend.data('lastTouch', now); const content = legend.siblings('.collapsible-content'); const icon = legend.find('.collapse-icon'); const isCollapsed = content.is(':visible'); if (isCollapsed) { content.hide(); icon.removeClass('fa-chevron-up').addClass('fa-chevron-down'); } else { content.show(); icon.removeClass('fa-chevron-down').addClass('fa-chevron-up'); } const sectionId = legend.text().trim(); if (!extension_settings[extensionName]) { extension_settings[extensionName] = {}; } extension_settings[extensionName][`collapsible_${sectionId}_collapsed`] = isCollapsed; saveSettingsDebounced(); }); displayDailyAuthCode(); function updateModelInputView() { const settings = extension_settings[extensionName] || {}; const forceProxy = settings.forceProxyForCustomApi === true; const model = settings.model || ''; container.find('#amily2_force_proxy').prop('checked', forceProxy); container.find('#amily2_manual_model_input').val(model); const apiKeyWrapper = container.find('#amily2_api_key_wrapper'); const autoFetchWrapper = container.find('#amily2_model_autofetch_wrapper'); const manualInput = container.find('#amily2_manual_model_input'); if (forceProxy) { apiKeyWrapper.hide(); autoFetchWrapper.show(); manualInput.hide(); } else { apiKeyWrapper.show(); autoFetchWrapper.show(); manualInput.hide(); } } if (!container.length || container.data("events-bound")) return; const snakeToCamel = (s) => s.replace(/_([a-z])/g, (g) => g[1].toUpperCase()); const updateAndSaveSetting = (key, value) => { console.log(`[Amily-谕令确认] 收到指令: 将 [${key}] 设置为 ->`, value); if (!extension_settings[extensionName]) { extension_settings[extensionName] = {}; } extension_settings[extensionName][key] = value; saveSettingsDebounced(); console.log(`[Amily-谕令镌刻] [${key}] 的新状态已保存。`); }; container .off("change.amily2.force_proxy") .on("change.amily2.force_proxy", '#amily2_force_proxy', function () { if (!pluginAuthStatus.authorized) return; updateAndSaveSetting('forceProxyForCustomApi', this.checked); updateModelInputView(); $('#amily2_refresh_models').trigger('click'); }); container .off("change.amily2.manual_model") .on("change.amily2.manual_model", '#amily2_manual_model_input', function() { if (!pluginAuthStatus.authorized) return; updateAndSaveSetting('model', this.value); toastr.success(`模型ID [${this.value}] 已自动保存!`, "Amily2号"); }); container .off("click.amily2.auth") .on("click.amily2.auth", "#auth_submit", async function () { const authCode = $("#amily2_auth_code").val().trim(); if (authCode) { await activatePluginAuthorization(authCode); } else { toastr.warning("请输入授权码", "Amily2号"); } }); container .off("click.amily2.actions") .on( "click.amily2.actions", "#amily2_refresh_models, #amily2_test_api_connection, #amily2_test, #amily2_fix_now", async function () { if (!pluginAuthStatus.authorized) return; const button = $(this); const originalHtml = button.html(); button .prop("disabled", true) .html(' 处理中'); try { switch (this.id) { case "amily2_refresh_models": const models = await fetchModels(); if (models.length > 0) { setAvailableModels(models); localStorage.setItem( "cached_models_amily2", JSON.stringify(models), ); populateModelDropdown(); } break; case "amily2_test_api_connection": await testApiConnection(); break; case "amily2_test": await testReplyChecker(); break; case "amily2_fix_now": await fixCommand(); break; } } catch (error) { console.error(`[Amily2-工部] 操作按钮 ${this.id} 执行失败:`, error); toastr.error(`操作失败: ${error.message}`, "Amily2号"); } finally { button.prop("disabled", false).html(originalHtml); } }, ); container .off("click.amily2.expand_editor") .on("click.amily2.expand_editor", "#amily2_expand_editor", function (event) { if (!pluginAuthStatus.authorized) return; event.stopPropagation(); const selectedKey = $("#amily2_prompt_selector").val(); const currentContent = $("#amily2_unified_editor").val(); const dialogHtml = ` 正在编辑: ${selectedKey} 保存并关闭取消 `; const dialogElement = $(dialogHtml).appendTo('body'); const dialogTextarea = dialogElement.find('#amily2_dialog_editor'); dialogTextarea.val(currentContent); const closeDialog = () => { dialogElement[0].close(); dialogElement.remove(); }; dialogElement.find('.popup-button-ok').on('click', () => { const newContent = dialogTextarea.val(); $("#amily2_unified_editor").val(newContent); updateAndSaveSetting(selectedKey, newContent); toastr.success(`谕令 [${selectedKey}] 已镌刻!`, "Amily2号"); closeDialog(); }); dialogElement.find('.popup-button-cancel').on('click', closeDialog); dialogElement[0].showModal(); }); container .off("click.amily2.tutorial") .on("click.amily2.tutorial", "#amily2_open_tutorial, #amily2_open_neige_tutorial", function() { if (!pluginAuthStatus.authorized) return; const tutorials = { "amily2_open_tutorial": { title: "主殿使用教程", url: "scripts/extensions/third-party/ST-Amily2-Chat-Optimisation/ZhuDian.md" }, "amily2_open_neige_tutorial": { title: "内阁使用教程", url: "scripts/extensions/third-party/ST-Amily2-Chat-Optimisation/NeiGe.md" } }; const tutorial = tutorials[this.id]; if (tutorial) { showContentModal(tutorial.title, tutorial.url); } }); container .off("click.amily2.update") .on("click.amily2.update", "#amily2_update_button", function() { $("#amily2_update_indicator").hide(); const updateInfo = getLatestUpdateInfo(); if (updateInfo && updateInfo.changelog) { const formattedChangelog = messageFormatting(updateInfo.changelog); const dialogHtml = ` 帝国最新情报 ${formattedChangelog} 朕已阅 `; const dialogElement = $(dialogHtml).appendTo('body'); const closeDialog = () => { dialogElement[0].close(); dialogElement.remove(); }; dialogElement.find('.popup-button-ok').on('click', closeDialog); dialogElement[0].showModal(); } else { toastr.info("未能获取到云端情报,请稍后再试。", "情报部回报"); } }); container .off("click.amily2.update_new") .on("click.amily2.update_new", "#amily2_update_button_new", function() { $('span[data-i18n="Manage extensions"]').first().click(); }); container .off("click.amily2.manual_command") .on( "click.amily2.manual_command", "#amily2_unhide_all_button, #amily2_manual_hide_confirm, #amily2_manual_unhide_confirm", async function () { if (!pluginAuthStatus.authorized) return; const buttonId = this.id; let commandType = ''; let params = {}; switch (buttonId) { case 'amily2_unhide_all_button': commandType = 'unhide_all'; break; case 'amily2_manual_hide_confirm': commandType = 'manual_hide'; params = { from: $('#amily2_manual_hide_from').val(), to: $('#amily2_manual_hide_to').val() }; break; case 'amily2_manual_unhide_confirm': commandType = 'manual_unhide'; params = { from: $('#amily2_manual_unhide_from').val(), to: $('#amily2_manual_unhide_to').val() }; break; } if (commandType) { await executeManualCommand(commandType, params); } } ); container .off("click.amily2.chamber_nav") .on("click.amily2.chamber_nav", "#amily2_open_plot_optimization, #amily2_open_additional_features, #amily2_open_rag_palace, #amily2_open_memorisation_forms, #amily2_open_character_world_book, #amily2_open_world_editor, #amily2_open_glossary, #amily2_open_renderer, #amily2_back_to_main_settings, #amily2_back_to_main_from_hanlinyuan, #amily2_back_to_main_from_forms, #amily2_back_to_main_from_optimization, #amily2_back_to_main_from_cwb, #amily2_back_to_main_from_world_editor, #amily2_back_to_main_from_glossary, #amily2_renderer_back_button", function () { if (!pluginAuthStatus.authorized) return; const mainPanel = container.find('.plugin-features'); const additionalPanel = container.find('#amily2_additional_features_panel'); const hanlinyuanPanel = container.find('#amily2_hanlinyuan_panel'); const memorisationFormsPanel = container.find('#amily2_memorisation_forms_panel'); const plotOptimizationPanel = container.find('#amily2_plot_optimization_panel'); const characterWorldBookPanel = container.find('#amily2_character_world_book_panel'); const worldEditorPanel = container.find('#amily2_world_editor_panel'); const glossaryPanel = container.find('#amily2_glossary_panel'); const rendererPanel = container.find('#amily2_renderer_panel'); mainPanel.hide(); additionalPanel.hide(); hanlinyuanPanel.hide(); memorisationFormsPanel.hide(); plotOptimizationPanel.hide(); characterWorldBookPanel.hide(); worldEditorPanel.hide(); glossaryPanel.hide(); rendererPanel.hide(); switch (this.id) { case 'amily2_open_renderer': rendererPanel.show(); break; case 'amily2_open_plot_optimization': plotOptimizationPanel.show(); break; case 'amily2_open_additional_features': additionalPanel.show(); break; case 'amily2_open_rag_palace': hanlinyuanPanel.show(); break; case 'amily2_open_memorisation_forms': memorisationFormsPanel.show(); break; case 'amily2_open_character_world_book': characterWorldBookPanel.show(); break; case 'amily2_open_world_editor': worldEditorPanel.show(); break; case 'amily2_open_glossary': glossaryPanel.show(); break; case 'amily2_back_to_main_settings': case 'amily2_back_to_main_from_hanlinyuan': case 'amily2_back_to_main_from_forms': case 'amily2_back_to_main_from_optimization': case 'amily2_back_to_main_from_cwb': case 'amily2_back_to_main_from_world_editor': case 'amily2_back_to_main_from_glossary': case 'amily2_renderer_back_button': mainPanel.show(); break; } }); container .off("change.amily2.checkbox") .on( "change.amily2.checkbox", 'input[type="checkbox"][id^="amily2_"]:not([id^="amily2_wb_enabled"]):not(#amily2_sybd_enabled)', function (event) { if (!pluginAuthStatus.authorized) return; const elementId = this.id; const mainToggle = $(this); const key = snakeToCamel(elementId.replace("amily2_", "")); updateAndSaveSetting(key, mainToggle.prop('checked')); if (elementId === 'amily2_optimization_exclusion_enabled' && mainToggle.prop('checked')) { const settings = extension_settings[extensionName]; const rules = settings.optimizationExclusionRules || []; const createRuleRowHtml = (rule = { start: '', end: '' }, index) => ` 到 × `; const rulesHtml = rules.map(createRuleRowHtml).join(''); const modalHtml = ` 在这里定义需要从优化内容中排除的文本片段。例如,排除HTML注释,可以设置开始字符为 \`\`。 ${rulesHtml} 添加新规则 `; showHtmlModal('编辑内容排除规则', modalHtml, { okText: '确认', cancelText: '取消', onOk: (dialog) => { const newRules = []; dialog.find('.opt-exclusion-rule-row').each(function() { const start = $(this).find('input').eq(0).val().trim(); const end = $(this).find('input').eq(1).val().trim(); if (start && end) newRules.push({ start, end }); }); updateAndSaveSetting('optimizationExclusionRules', newRules); toastr.success('排除规则已更新。', 'Amily2号'); }, onCancel: () => { } }); const modalContent = $('#optimization-exclusion-rules-container'); const rulesList = modalContent.find('#optimization-rules-list'); modalContent.find('#optimization-add-rule-btn').on('click', () => { const newIndex = rulesList.children().length; rulesList.append(createRuleRowHtml(undefined, newIndex)); }); rulesList.on('click', '.delete-rule-btn', function() { $(this).closest('.opt-exclusion-rule-row').remove(); }); } }, ); container .off("change.amily2.radio") .on( "change.amily2.radio", 'input[type="radio"][name^="amily2_"]:not([name="amily2_icon_location"]):not([name="amily2_wb_source"])', function () { if (!pluginAuthStatus.authorized) return; const key = snakeToCamel(this.name.replace("amily2_", "")); const value = $(`input[name="${this.name}"]:checked`).val(); updateAndSaveSetting(key, value); }, ); container .off("change.amily2.api_provider") .on("change.amily2.api_provider", "#amily2_api_provider", function () { if (!pluginAuthStatus.authorized) return; const provider = $(this).val(); console.log(`[Amily2号-UI] API提供商切换为: ${provider}`); updateAndSaveSetting('apiProvider', provider); const $urlWrapper = $('#amily2_api_url_wrapper'); const $keyWrapper = $('#amily2_api_key_wrapper'); const $presetWrapper = $('#amily2_preset_wrapper'); $urlWrapper.hide(); $keyWrapper.hide(); $presetWrapper.hide(); const $modelWrapper = $('#amily2_model_selector'); switch(provider) { case 'openai': case 'openai_test': $urlWrapper.show(); $keyWrapper.show(); $modelWrapper.show(); $('#amily2_api_url').attr('placeholder', 'https://api.openai.com/v1').attr('type', 'text'); $('#amily2_api_key').attr('placeholder', 'sk-...'); break; case 'google': $urlWrapper.hide(); $keyWrapper.show(); $modelWrapper.show(); $('#amily2_api_key').attr('placeholder', 'Google API Key'); break; case 'sillytavern_backend': $urlWrapper.show(); $modelWrapper.show(); $('#amily2_api_url').attr('placeholder', 'http://localhost:5000/v1').attr('type', 'text'); break; case 'sillytavern_preset': $presetWrapper.show(); $modelWrapper.hide(); loadSillyTavernPresets(); break; } $('#amily2_model').empty().append('请刷新模型列表'); }); container .off("change.amily2.text") .on("change.amily2.text", "#amily2_api_url, #amily2_api_key, #amily2_optimization_target_tag", function () { if (!pluginAuthStatus.authorized) return; const key = snakeToCamel(this.id.replace("amily2_", "")); updateAndSaveSetting(key, this.value); toastr.success(`配置 [${key}] 已自动保存!`, "Amily2号"); }); container .off("change.amily2.select") .on("change.amily2.select", "select#amily2_model, select#amily2_preset_selector", function () { if (!pluginAuthStatus.authorized) return; const key = snakeToCamel(this.id.replace("amily2_", "")); let valueToSave = this.value; if (this.id === 'amily2_preset_selector') { updateAndSaveSetting('tavernProfile', valueToSave); } else { updateAndSaveSetting(key, valueToSave); } if (this.id === 'amily2_model') { populateModelDropdown(); } }); container .off("input.amily2.range") .on( "input.amily2.range", 'input[type="range"][id^="amily2_"]', function () { if (!pluginAuthStatus.authorized) return; const key = snakeToCamel(this.id.replace("amily2_", "")); const value = this.id.includes("temperature") ? parseFloat(this.value) : parseInt(this.value, 10); $(`#${this.id}_value`).text(value); updateAndSaveSetting(key, value); }, ); const promptMap = { mainPrompt: "#amily2_main_prompt", systemPrompt: "#amily2_system_prompt", outputFormatPrompt: "#amily2_output_format_prompt", }; const selector = "#amily2_prompt_selector"; const editor = "#amily2_unified_editor"; const unifiedSaveButton = "#amily2_unified_save_button"; function updateEditorView() { if (!$(selector).length) return; const selectedKey = $(selector).val(); if (!selectedKey) return; const content = extension_settings[extensionName][selectedKey] || ""; $(editor).val(content); } container .off("change.amily2.prompt_selector") .on("change.amily2.prompt_selector", selector, updateEditorView); container .off("click.amily2.unified_save") .on("click.amily2.unified_save", unifiedSaveButton, function () { const selectedKey = $(selector).val(); if (!selectedKey) return; const newContent = $(editor).val(); updateAndSaveSetting(selectedKey, newContent); toastr.success(`谕令 [${selectedKey}] 已镌刻!`, "Amily2号"); }); container .off("click.amily2.unified_restore") .on("click.amily2.unified_restore", "#amily2_unified_restore_button", function () { const selectedKey = $(selector).val(); if (!selectedKey) return; const defaultValue = defaultSettings[selectedKey]; $(editor).val(defaultValue); updateAndSaveSetting(selectedKey, defaultValue); toastr.success(`谕令 [${selectedKey}] 已成功恢复为帝国初始蓝图。`, "Amily2号"); }); container .off("change.amily2.lore_settings") .on("change.amily2.lore_settings", 'select[id^="amily2_lore_"], input#amily2_lore_depth_input', function () { if (!pluginAuthStatus.authorized) return; let key = snakeToCamel(this.id.replace("amily2_", "")); if (key === 'loreDepthInput') { key = 'loreDepth'; } const value = (this.type === 'number') ? parseInt(this.value, 10) : this.value; updateAndSaveSetting(key, value); if (this.id === 'amily2_lore_insertion_position') { const depthContainer = $('#amily2_lore_depth_container'); if (this.value === 'at_depth') { depthContainer.slideDown(200); } else { depthContainer.slideUp(200); } } } ); container .off("click.amily2.lore_save") .on("click.amily2.lore_save", '#amily2_save_lore_settings', function () { if (!pluginAuthStatus.authorized) return; const button = $(this); const statusElement = $('#amily2_lore_save_status'); button.prop('disabled', true).html(' 已确认'); statusElement.text('圣意已在您每次更改时自动镌刻。').stop().fadeIn(); setTimeout(() => { button.prop('disabled', false).html(' 确认敕令'); statusElement.fadeOut(); }, 2500); }); setTimeout(updateEditorView, 100); updateModelInputView(); container.data("events-bound", true); // 【V60.0】新增:颜色定制UI事件绑定 const colorContainer = $("#amily2_drawer_content").length ? $("#amily2_drawer_content") : $("#amily2_chat_optimiser"); if (colorContainer.length && !colorContainer.data("color-events-bound")) { loadAndApplyCustomColors(colorContainer); colorContainer.on('input', '#amily2_bg_color, #amily2_button_color, #amily2_text_color', function() { applyAndSaveColors(colorContainer); }); // 新增:背景透明度滑块事件 colorContainer.on('input', '#amily2_bg_opacity', function() { const opacityValue = $(this).val(); $('#amily2_bg_opacity_value').text(opacityValue); document.documentElement.style.setProperty('--amily2-bg-opacity', opacityValue); if (!extension_settings[extensionName]) { extension_settings[extensionName] = {}; } extension_settings[extensionName]['bgOpacity'] = opacityValue; saveSettingsDebounced(); }); colorContainer.on('click', '#amily2_restore_colors', function() { const defaultColors = { '--amily2-bg-color': '#1e1e1e', '--amily2-button-color': '#4a4a4a', '--amily2-text-color': '#ffffff' }; colorContainer.find('#amily2_bg_color').val(defaultColors['--amily2-bg-color']); colorContainer.find('#amily2_button_color').val(defaultColors['--amily2-button-color']); colorContainer.find('#amily2_text_color').val(defaultColors['--amily2-text-color']); applyAndSaveColors(colorContainer); // 恢复默认透明度 const defaultOpacity = 0.85; $('#amily2_bg_opacity').val(defaultOpacity); $('#amily2_bg_opacity_value').text(defaultOpacity); document.documentElement.style.setProperty('--amily2-bg-opacity', defaultOpacity); if (extension_settings[extensionName]) { extension_settings[extensionName]['bgOpacity'] = defaultOpacity; saveSettingsDebounced(); } toastr.success('界面颜色与透明度已恢复为默认设置。'); }); // 新增:自定义背景图事件绑定 colorContainer.on('change', '#amily2_custom_bg_image', function(event) { const file = event.target.files[0]; if (file && file.type.startsWith('image/')) { const reader = new FileReader(); reader.onload = function(e) { const imageDataUrl = e.target.result; // 检查大小 if (imageDataUrl.length > 5 * 1024 * 1024) { // 5MB 限制 toastr.error('图片文件过大,请选择小于5MB的图片。'); return; } document.documentElement.style.setProperty('--amily2-bg-image', `url("${imageDataUrl}")`); if (!extension_settings[extensionName]) { extension_settings[extensionName] = {}; } extension_settings[extensionName]['customBgImage'] = imageDataUrl; saveSettingsDebounced(); toastr.success('自定义背景图已应用。'); }; reader.readAsDataURL(file); } }); colorContainer.on('click', '#amily2_restore_bg_image', function() { document.documentElement.style.setProperty('--amily2-bg-image', `url("${DEFAULT_BG_IMAGE_URL}")`); if (extension_settings[extensionName]) { delete extension_settings[extensionName]['customBgImage']; saveSettingsDebounced(); } $('#amily2_custom_bg_image').val(''); // 清空文件选择框 toastr.success('背景图已恢复为默认。'); }); colorContainer.data("color-events-bound", true); } } export function opt_saveAllSettings() { const panel = $('#amily2_plot_optimization_panel'); if (panel.length === 0) return; console.log(`[${extensionName}] 手动触发所有剧情优化设置的保存...`); panel.find('input[type="checkbox"], input[type="radio"], input[type="text"], input[type="password"], textarea, select').trigger('change.amily2_opt'); panel.find('input[type="range"]').trigger('change.amily2_opt'); opt_saveEnabledEntries(); toastr.info('剧情优化设置已自动保存。'); } function opt_toCamelCase(str) { return str.replace(/[-_]([a-z])/g, (g) => g[1].toUpperCase()); } function opt_updateApiUrlVisibility(panel, apiMode) { const customApiSettings = panel.find('#amily2_opt_custom_api_settings_block'); const tavernProfileSettings = panel.find('#amily2_opt_tavern_api_profile_block'); const apiUrlInput = panel.find('#amily2_opt_api_url'); customApiSettings.hide(); tavernProfileSettings.hide(); if (apiMode === 'tavern') { tavernProfileSettings.show(); } else { customApiSettings.show(); if (apiMode === 'google') { panel.find('#amily2_opt_api_url_block').hide(); const googleUrl = 'https://generativelanguage.googleapis.com'; if (apiUrlInput.val() !== googleUrl) { apiUrlInput.val(googleUrl).attr('type', 'text').trigger('change'); } } else { panel.find('#amily2_opt_api_url_block').show(); } } } function opt_updateWorldbookSourceVisibility(panel, source) { const manualSelectionWrapper = panel.find('#amily2_opt_worldbook_select_wrapper'); if (source === 'manual') { manualSelectionWrapper.show(); const selectBox = manualSelectionWrapper.find('#amily2_opt_selected_worldbooks'); selectBox.css({ 'height': 'auto', 'background-color': 'var(--bg1)', 'appearance': 'none', '-webkit-appearance': 'none' }); } else { manualSelectionWrapper.hide(); } } async function opt_loadTavernApiProfiles(panel) { const select = panel.find('#amily2_opt_tavern_api_profile_select'); const apiSettings = opt_getMergedSettings(); const currentProfileId = apiSettings.plotOpt_tavernProfile; const currentValue = select.val(); select.empty().append(new Option('-- 请选择一个酒馆预设 --', '')); try { const tavernProfiles = getContext().extensionSettings?.connectionManager?.profiles || []; if (!tavernProfiles || tavernProfiles.length === 0) { select.append($('', { value: '', text: '未找到酒馆预设', disabled: true })); return; } let foundCurrentProfile = false; tavernProfiles.forEach(profile => { if (profile.api && profile.preset) { const option = $('', { value: profile.id, text: profile.name || profile.id, selected: profile.id === currentProfileId }); select.append(option); if (profile.id === currentProfileId) { foundCurrentProfile = true; } } }); if (currentProfileId && !foundCurrentProfile) { toastr.warning(`之前选择的酒馆预设 "${currentProfileId}" 已不存在,请重新选择。`); opt_saveSetting('tavernProfile', ''); } else if (foundCurrentProfile) { select.val(currentProfileId); } } catch (error) { console.error(`[${extensionName}] 加载酒馆API预设失败:`, error); toastr.error('无法加载酒馆API预设列表,请查看控制台。'); } } const opt_characterSpecificSettings = [ 'plotOpt_worldbookSource', 'plotOpt_selectedWorldbooks', 'plotOpt_enabledWorldbookEntries' ]; async function opt_saveSetting(key, value) { if (opt_characterSpecificSettings.includes(key)) { const character = characters[this_chid]; if (!character) return; if (!character.data.extensions) character.data.extensions = {}; if (!character.data.extensions[extensionName]) character.data.extensions[extensionName] = {}; character.data.extensions[extensionName][key] = value; try { const response = await fetch('/api/characters/merge-attributes', { method: 'POST', headers: getRequestHeaders(), body: JSON.stringify({ avatar: character.avatar, data: { extensions: { [extensionName]: character.data.extensions[extensionName] } } }) }); if (!response.ok) throw new Error(`API call failed with status: ${response.status}`); console.log(`[${extensionName}] 角色卡设置已更新: ${key} ->`, value); } catch (error) { console.error(`[${extensionName}] 保存角色数据失败:`, error); toastr.error('无法保存角色卡设置,请检查控制台。'); } } else { if (!extension_settings[extensionName]) { extension_settings[extensionName] = {}; } extension_settings[extensionName][key] = value; saveSettingsDebounced(); } } function opt_getMergedSettings() { const character = characters[this_chid]; const globalSettings = extension_settings[extensionName] || defaultSettings; const characterSettings = character?.data?.extensions?.[extensionName] || {}; return { ...globalSettings, ...characterSettings }; } function opt_bindSlider(panel, sliderId, displayId) { const slider = panel.find(sliderId); const display = panel.find(displayId); display.text(slider.val()); slider.on('input', function() { display.text($(this).val()); }); } async function opt_loadWorldbooks(panel) { const container = panel.find('#amily2_opt_worldbook_checkbox_list'); const settings = opt_getMergedSettings(); const currentSelection = settings.plotOpt_selectedWorldbooks || []; container.empty(); // 移除旧的搜索框以防重复 panel.find('#amily2_opt_worldbook_search').remove(); const searchBox = $(``); container.before(searchBox); searchBox.on('input', function() { const searchTerm = $(this).val().toLowerCase(); container.find('.amily2_opt_worldbook_list_item').each(function() { const bookName = $(this).find('label').text().toLowerCase(); if (bookName.includes(searchTerm)) { $(this).show(); } else { $(this).hide(); } }); }); try { const lorebooks = await safeLorebooks(); if (!lorebooks || lorebooks.length === 0) { container.html('未找到世界书。'); return; } lorebooks.forEach(name => { const bookId = `amily2-opt-wb-check-${name.replace(/[^a-zA-Z0-9]/g, '-')}`; const isChecked = currentSelection.includes(name); const item = $(` ${name} `); container.append(item); }); } catch (error) { console.error(`[${extensionName}] 加载世界书失败:`, error); container.html('加载世界书列表失败。'); toastr.error('无法加载世界书列表,请查看控制台。'); } } async function opt_loadWorldbookEntries(panel) { const container = panel.find('#amily2_opt_worldbook_entry_list_container'); const countDisplay = panel.find('#amily2_opt_worldbook_entry_count'); container.html('加载条目中...'); countDisplay.text(''); // 移除旧的搜索框以防重复 panel.find('#amily2_opt_worldbook_entry_search').remove(); const searchBox = $(``); container.before(searchBox); searchBox.on('input', function() { const searchTerm = $(this).val().toLowerCase(); let visibleCount = 0; container.find('.amily2_opt_worldbook_entry_item').each(function() { const entryName = $(this).find('label').text().toLowerCase(); if (entryName.includes(searchTerm)) { $(this).show(); visibleCount++; } else { $(this).hide(); } }); const totalEntries = container.find('.amily2_opt_worldbook_entry_item').length; countDisplay.text(`显示 ${visibleCount} / ${totalEntries} 条目.`); }); const settings = opt_getMergedSettings(); const currentSource = settings.plotOpt_worldbookSource || 'character'; let bookNames = []; if (currentSource === 'manual') { bookNames = settings.plotOpt_selectedWorldbooks || []; } else { if (this_chid === -1 || !characters[this_chid]) { container.html('未选择角色。'); countDisplay.text(''); return; } try { const charLorebooks = await safeCharLorebooks({ type: 'all' }); if (charLorebooks.primary) bookNames.push(charLorebooks.primary); if (charLorebooks.additional?.length) bookNames.push(...charLorebooks.additional); } catch (error) { console.error(`[${extensionName}] 获取角色世界书失败:`, error); toastr.error('获取角色世界书失败。'); container.html('获取角色世界书失败。'); return; } } const selectedBooks = bookNames; let enabledEntries = settings.plotOpt_enabledWorldbookEntries || {}; let totalEntries = 0; let visibleEntries = 0; if (selectedBooks.length === 0) { container.html('请选择一个或多个世界书以查看其条目。'); return; } try { const allEntries = []; for (const bookName of selectedBooks) { const entries = await safeLorebookEntries(bookName); entries.forEach(entry => { allEntries.push({ ...entry, bookName }); }); } // 根据用户要求,只显示默认启用的条目 const enabledOnlyEntries = allEntries.filter(entry => entry.enabled); container.empty(); //totalEntries = allEntries.length; totalEntries = enabledOnlyEntries.length; if (totalEntries === 0) { //container.html('所选世界书没有条目。'); container.html('所选世界书没有(已启用的)条目。'); countDisplay.text('0 条目.'); return; } //allEntries.sort((a, b) => (a.comment || '').localeCompare(b.comment || '')).forEach(entry => { enabledOnlyEntries.sort((a, b) => (a.comment || '').localeCompare(b.comment || '')).forEach(entry => { const entryId = `amily2-opt-entry-${entry.bookName.replace(/[^a-zA-Z0-9]/g, '-')}-${entry.uid}`; const isEnabled = enabledEntries[entry.bookName]?.includes(entry.uid) ?? true; const item = $(` ${entry.comment || '无标题条目'} `); container.append(item); }); visibleEntries = container.children().length; countDisplay.text(`显示 ${visibleEntries} / ${totalEntries} 条目.`); } catch (error) { console.error(`[${extensionName}] 加载世界书条目失败:`, error); container.html('加载条目失败。'); } } function opt_saveEnabledEntries() { const panel = $('#amily2_plot_optimization_panel'); let enabledEntries = {}; panel.find('#amily2_opt_worldbook_entry_list_container input[type="checkbox"]').each(function() { const bookName = $(this).data('book'); const uid = parseInt($(this).data('uid')); if (!enabledEntries[bookName]) { enabledEntries[bookName] = []; } if ($(this).is(':checked')) { enabledEntries[bookName].push(uid); } }); const settings = opt_getMergedSettings(); if (settings.plotOpt_worldbookSource === 'manual') { const selectedBooks = settings.plotOpt_selectedWorldbooks || []; Object.keys(enabledEntries).forEach(bookName => { if (!selectedBooks.includes(bookName)) { delete enabledEntries[bookName]; } }); } opt_saveSetting('plotOpt_enabledWorldbookEntries', enabledEntries); } function opt_loadPromptPresets(panel) { const presets = extension_settings[extensionName]?.promptPresets || []; const select = panel.find('#amily2_opt_prompt_preset_select'); const settings = opt_getMergedSettings(); const lastUsedPresetName = settings.plotOpt_lastUsedPresetName; select.empty().append(new Option('-- 选择一个预设 --', '')); presets.forEach(preset => { const option = new Option(preset.name, preset.name); if (preset.name === lastUsedPresetName) { option.selected = true; } select.append(option); }); } function opt_saveCurrentPromptsAsPreset(panel) { const selectedPresetName = panel.find('#amily2_opt_prompt_preset_select').val(); let presetName; let isOverwriting = false; if (selectedPresetName) { if (confirm(`您确定要用当前编辑的提示词覆盖预设 "${selectedPresetName}" 吗?`)) { presetName = selectedPresetName; isOverwriting = true; } else { toastr.info('保存操作已取消。'); return; } } else { presetName = prompt("您正在创建一个新的预设,请输入预设名称:"); if (!presetName) { toastr.info('保存操作已取消。'); return; } } const presets = extension_settings[extensionName]?.promptPresets || []; const existingPresetIndex = presets.findIndex(p => p.name === presetName); // Ensure the cache is up-to-date before saving const currentEditorPromptKey = panel.find('#amily2_opt_prompt_selector').val(); promptCache[currentEditorPromptKey] = panel.find('#amily2_opt_prompt_editor').val(); const newPresetData = { name: presetName, mainPrompt: promptCache.main, systemPrompt: promptCache.system, finalSystemDirective: promptCache.final_system, rateMain: parseFloat(panel.find('#amily2_opt_rate_main').val()), ratePersonal: parseFloat(panel.find('#amily2_opt_rate_personal').val()), rateErotic: parseFloat(panel.find('#amily2_opt_rate_erotic').val()), rateCuckold: parseFloat(panel.find('#amily2_opt_rate_cuckold').val()) }; if (existingPresetIndex !== -1) { presets[existingPresetIndex] = newPresetData; toastr.success(`预设 "${presetName}" 已成功覆盖。`); } else { presets.push(newPresetData); toastr.success(`新预设 "${presetName}" 已成功创建。`); } opt_saveSetting('promptPresets', presets); opt_loadPromptPresets(panel); setTimeout(() => { panel.find('#amily2_opt_prompt_preset_select').val(presetName).trigger('change', { isAutomatic: false }); }, 0); } function opt_deleteSelectedPreset(panel) { const select = panel.find('#amily2_opt_prompt_preset_select'); const selectedName = select.val(); if (!selectedName) { toastr.warning('没有选择任何预设。'); return; } if (!confirm(`确定要删除预设 "${selectedName}" 吗?`)) { return; } const presets = extension_settings[extensionName]?.promptPresets || []; const indexToDelete = presets.findIndex(p => p.name === selectedName); if (indexToDelete > -1) { presets.splice(indexToDelete, 1); opt_saveSetting('promptPresets', presets); toastr.success(`预设 "${selectedName}" 已被删除。`); } else { toastr.error('找不到要删除的预设,操作可能已过期。'); } opt_loadPromptPresets(panel); select.trigger('change'); } function opt_exportPromptPresets() { const select = $('#amily2_opt_prompt_preset_select'); const selectedName = select.val(); if (!selectedName) { toastr.info('请先从下拉菜单中选择一个要导出的预设。'); return; } const presets = extension_settings[extensionName]?.promptPresets || []; const selectedPreset = presets.find(p => p.name === selectedName); if (!selectedPreset) { toastr.error('找不到选中的预设,请刷新页面后重试。'); return; } const dataToExport = [selectedPreset]; const dataStr = JSON.stringify(dataToExport, null, 2); const blob = new Blob([dataStr], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `amily2_opt_preset_${selectedName.replace(/[^a-z0-9]/gi, '_')}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); toastr.success(`预设 "${selectedName}" 已成功导出。`); } function opt_importPromptPresets(file, panel) { if (!file) return; const reader = new FileReader(); reader.onload = function(e) { try { const importedPresets = JSON.parse(e.target.result); if (!Array.isArray(importedPresets)) { throw new Error('JSON文件格式不正确,根节点必须是一个数组。'); } let currentPresets = extension_settings[extensionName]?.promptPresets || []; let importedCount = 0; let overwrittenCount = 0; importedPresets.forEach(preset => { if (preset && typeof preset.name === 'string' && preset.name.length > 0) { const presetData = { name: preset.name, mainPrompt: preset.mainPrompt || '', systemPrompt: preset.systemPrompt || '', finalSystemDirective: preset.finalSystemDirective || '', rateMain: preset.rateMain ?? 1.0, ratePersonal: preset.ratePersonal ?? 1.0, rateErotic: preset.rateErotic ?? 1.0, rateCuckold: preset.rateCuckold ?? 1.0 }; const existingIndex = currentPresets.findIndex(p => p.name === preset.name); if (existingIndex !== -1) { currentPresets[existingIndex] = presetData; overwrittenCount++; } else { currentPresets.push(presetData); importedCount++; } } }); if (importedCount > 0 || overwrittenCount > 0) { const selectedPresetBeforeImport = panel.find('#amily2_opt_prompt_preset_select').val(); opt_saveSetting('promptPresets', currentPresets); opt_loadPromptPresets(panel); panel.find('#amily2_opt_prompt_preset_select').val(selectedPresetBeforeImport); panel.find('#amily2_opt_prompt_preset_select').trigger('change'); let messages = []; if (importedCount > 0) messages.push(`成功导入 ${importedCount} 个新预设。`); if (overwrittenCount > 0) messages.push(`成功覆盖 ${overwrittenCount} 个同名预设。`); toastr.success(messages.join(' ')); } else { toastr.warning('未找到可导入的有效预设。'); } } catch (error) { console.error(`[${extensionName}] 导入预设失败:`, error); toastr.error(`导入失败: ${error.message}`, '错误'); } finally { panel.find('#amily2_opt_preset_file_input').val(''); } }; reader.readAsText(file); } function opt_loadSettings(panel) { const settings = opt_getMergedSettings(); panel.find('#amily2_opt_enabled').prop('checked', settings.plotOpt_enabled); panel.find('#amily2_opt_table_enabled').prop('checked', settings.plotOpt_tableEnabled); panel.find('#amily2_opt_ejs_enabled').prop('checked', settings.plotOpt_ejsEnabled); panel.find(`input[name="amily2_opt_api_mode"][value="${settings.plotOpt_apiMode}"]`).prop('checked', true); panel.find('#amily2_opt_tavern_api_profile_select').val(settings.plotOpt_tavernProfile); panel.find(`input[name="amily2_opt_worldbook_source"][value="${settings.plotOpt_worldbookSource || 'character'}"]`).prop('checked', true); panel.find('#amily2_opt_worldbook_enabled').prop('checked', settings.plotOpt_worldbookEnabled); panel.find('#amily2_opt_api_url').val(settings.plotOpt_apiUrl); panel.find('#amily2_opt_api_key').val(settings.plotOpt_apiKey); const modelInput = panel.find('#amily2_opt_model'); const modelSelect = panel.find('#amily2_opt_model_select'); modelInput.val(settings.plotOpt_model); modelSelect.empty(); if (settings.plotOpt_model) { modelSelect.append(new Option(settings.plotOpt_model, settings.plotOpt_model, true, true)); } else { modelSelect.append(new Option('<-请先获取模型', '', true, true)); } panel.find('#amily2_opt_max_tokens').val(settings.plotOpt_max_tokens); panel.find('#amily2_opt_temperature').val(settings.plotOpt_temperature); panel.find('#amily2_opt_top_p').val(settings.plotOpt_top_p); panel.find('#amily2_opt_presence_penalty').val(settings.plotOpt_presence_penalty); panel.find('#amily2_opt_frequency_penalty').val(settings.plotOpt_frequency_penalty); panel.find('#amily2_opt_context_turn_count').val(settings.plotOpt_contextTurnCount); panel.find('#amily2_opt_worldbook_char_limit').val(settings.plotOpt_worldbookCharLimit); panel.find('#amily2_opt_context_limit').val(settings.plotOpt_contextLimit); panel.find('#amily2_opt_rate_main').val(settings.plotOpt_rateMain); panel.find('#amily2_opt_rate_personal').val(settings.plotOpt_ratePersonal); panel.find('#amily2_opt_rate_erotic').val(settings.plotOpt_rateErotic); panel.find('#amily2_opt_rate_cuckold').val(settings.plotOpt_rateCuckold); opt_loadPromptPresets(panel); const lastUsedPresetName = settings.plotOpt_lastUsedPresetName; const initFunc = panel.data('initAmily2PromptEditor'); if (initFunc) { initFunc(); } // After loading presets and initializing the editor, trigger a "light" change event // to update UI elements like the delete button, without reloading all the data. if (lastUsedPresetName && panel.find('#amily2_opt_prompt_preset_select').val() === lastUsedPresetName) { setTimeout(() => { panel.find('#amily2_opt_prompt_preset_select').trigger('change', { isAutomatic: true, noLoad: true }); }, 0); } opt_updateApiUrlVisibility(panel, settings.plotOpt_apiMode); opt_updateWorldbookSourceVisibility(panel, settings.plotOpt_worldbookSource || 'character'); opt_bindSlider(panel, '#amily2_opt_max_tokens', '#amily2_opt_max_tokens_value'); opt_bindSlider(panel, '#amily2_opt_temperature', '#amily2_opt_temperature_value'); opt_bindSlider(panel, '#amily2_opt_top_p', '#amily2_opt_top_p_value'); opt_bindSlider(panel, '#amily2_opt_presence_penalty', '#amily2_opt_presence_penalty_value'); opt_bindSlider(panel, '#amily2_opt_frequency_penalty', '#amily2_opt_frequency_penalty_value'); opt_bindSlider(panel, '#amily2_opt_context_turn_count', '#amily2_opt_context_turn_count_value'); opt_bindSlider(panel, '#amily2_opt_worldbook_char_limit', '#amily2_opt_worldbook_char_limit_value'); opt_bindSlider(panel, '#amily2_opt_context_limit', '#amily2_opt_context_limit_value'); opt_loadWorldbooks(panel).then(() => { opt_loadWorldbookEntries(panel); }); opt_loadTavernApiProfiles(panel); } const promptCache = { main: '', system: '', final_system: '' }; export function initializePlotOptimizationBindings() { const panel = $('#amily2_plot_optimization_panel'); if (panel.length === 0 || panel.data('events-bound')) { return; } // Tab switching logic panel.find('.sinan-navigation-deck').on('click', '.sinan-nav-item', function() { const tabButton = $(this); const tabName = tabButton.data('tab'); const contentWrapper = panel.find('.sinan-content-wrapper'); // Deactivate all tabs and panes panel.find('.sinan-nav-item').removeClass('active'); contentWrapper.find('.sinan-tab-pane').removeClass('active'); // Activate the clicked tab and corresponding pane tabButton.addClass('active'); contentWrapper.find(`#sinan-${tabName}-tab`).addClass('active'); }); // Unified prompt editor logic function updateEditorFromCache() { const selectedPrompt = panel.find('#amily2_opt_prompt_selector').val(); if (selectedPrompt) { panel.find('#amily2_opt_prompt_editor').val(promptCache[selectedPrompt]); } } // Make it available for opt_loadSettings panel.data('initAmily2PromptEditor', function() { const settings = opt_getMergedSettings(); const lastUsedPresetName = settings.plotOpt_lastUsedPresetName; const presets = settings.promptPresets || []; const lastUsedPreset = presets.find(p => p.name === lastUsedPresetName); if (lastUsedPreset) { // If a valid preset was last used, load its data into the cache promptCache.main = lastUsedPreset.mainPrompt || defaultSettings.plotOpt_mainPrompt; promptCache.system = lastUsedPreset.systemPrompt || defaultSettings.plotOpt_systemPrompt; promptCache.final_system = lastUsedPreset.finalSystemDirective || defaultSettings.plotOpt_finalSystemDirective; } else { // Otherwise, load from the base settings (non-preset values) promptCache.main = settings.plotOpt_mainPrompt || defaultSettings.plotOpt_mainPrompt; promptCache.system = settings.plotOpt_systemPrompt || defaultSettings.plotOpt_systemPrompt; promptCache.final_system = settings.plotOpt_finalSystemDirective || defaultSettings.plotOpt_finalSystemDirective; } updateEditorFromCache(); panel.find('#amily2_opt_prompt_editor').data('current-prompt', panel.find('#amily2_opt_prompt_selector').val()); }); panel.on('change', '#amily2_opt_prompt_selector', function() { const previousPromptKey = panel.find('#amily2_opt_prompt_editor').data('current-prompt'); if (previousPromptKey) { const previousValue = panel.find('#amily2_opt_prompt_editor').val(); promptCache[previousPromptKey] = previousValue; const keyMap = { main: 'plotOpt_mainPrompt', system: 'plotOpt_systemPrompt', final_system: 'plotOpt_finalSystemDirective' }; opt_saveSetting(keyMap[previousPromptKey], previousValue); } const selectedPrompt = $(this).val(); panel.find('#amily2_opt_prompt_editor').val(promptCache[selectedPrompt]); panel.find('#amily2_opt_prompt_editor').data('current-prompt', selectedPrompt); }); panel.on('input', '#amily2_opt_prompt_editor', function() { const currentPrompt = panel.find('#amily2_opt_prompt_selector').val(); const currentValue = $(this).val(); promptCache[currentPrompt] = currentValue; const keyMap = { main: 'plotOpt_mainPrompt', system: 'plotOpt_systemPrompt', final_system: 'plotOpt_finalSystemDirective' }; opt_saveSetting(keyMap[currentPrompt], currentValue); }); panel.on('click', '#amily2_opt_reset_main_prompt', function() { const defaultValue = defaultSettings.plotOpt_mainPrompt; promptCache.main = defaultValue; updateEditorFromCache(); opt_saveSetting('plotOpt_mainPrompt', defaultValue); toastr.info('主提示词已恢复为默认值。'); }); panel.on('click', '#amily2_opt_reset_system_prompt', function() { const defaultValue = defaultSettings.plotOpt_systemPrompt; promptCache.system = defaultValue; updateEditorFromCache(); opt_saveSetting('plotOpt_systemPrompt', defaultValue); toastr.info('拦截任务指令已恢复为默认值。'); }); panel.on('click', '#amily2_opt_reset_final_system_directive', function() { const defaultValue = defaultSettings.plotOpt_finalSystemDirective; promptCache.final_system = defaultValue; updateEditorFromCache(); opt_saveSetting('plotOpt_finalSystemDirective', defaultValue); toastr.info('最终注入指令已恢复为默认值。'); }); opt_loadSettings(panel); bindJqyhApiEvents(); eventSource.on(event_types.CHAT_CHANGED, () => { console.log(`[${extensionName}] 检测到角色/聊天切换,正在刷新剧情优化设置UI...`); opt_loadSettings(panel); }); const handleSettingChange = function(element) { const el = $(element); const key_part = (element.name || element.id).replace('amily2_opt_', ''); const key = 'plotOpt_' + key_part.replace(/_([a-z])/g, (g) => g[1].toUpperCase()); let value = element.type === 'checkbox' ? element.checked : el.val(); if (key === 'plotOpt_selected_worldbooks' && !Array.isArray(value)) { value = el.val() || []; } const floatKeys = ['plotOpt_temperature', 'plotOpt_top_p', 'plotOpt_presence_penalty', 'plotOpt_frequency_penalty', 'plotOpt_rateMain', 'plotOpt_ratePersonal', 'plotOpt_rateErotic', 'plotOpt_rateCuckold']; if (floatKeys.includes(key) && value !== '') { value = parseFloat(value); } else if (element.type === 'range' || element.type === 'number') { if (value !== '') value = parseInt(value, 10); } if (value !== '' || element.type === 'checkbox') { opt_saveSetting(key, value); } if (key === 'plotOpt_api_mode') { opt_updateApiUrlVisibility(panel, value); } if (element.name === 'amily2_opt_worldbook_source') { opt_updateWorldbookSourceVisibility(panel, value); opt_loadWorldbookEntries(panel); } }; const allInputSelectors = [ 'input[type="checkbox"]', 'input[type="radio"]', 'select:not(#amily2_opt_model_select)', 'input[type="text"]', 'input[type="password"]', 'textarea', 'input[type="range"]', 'input[type="number"]' ].join(', '); panel.on('input.amily2_opt change.amily2_opt', allInputSelectors, function() { handleSettingChange(this); }); panel.on('change.amily2_opt', '#amily2_opt_model_select', function() { const selectedModel = $(this).val(); if (selectedModel) { panel.find('#amily2_opt_model').val(selectedModel).trigger('change'); } }); panel.on('click.amily2_opt', '#amily2_opt_refresh_tavern_api_profiles', () => { opt_loadTavernApiProfiles(panel); }); panel.on('change.amily2_opt', '#amily2_opt_tavern_api_profile_select', function() { const value = $(this).val(); opt_saveSetting('tavernProfile', value); }); panel.find('#amily2_opt_import_prompt_presets').on('click', () => panel.find('#amily2_opt_preset_file_input').click()); panel.find('#amily2_opt_export_prompt_presets').on('click', () => opt_exportPromptPresets()); panel.find('#amily2_opt_save_prompt_preset').on('click', () => opt_saveCurrentPromptsAsPreset(panel)); panel.find('#amily2_opt_delete_prompt_preset').on('click', () => opt_deleteSelectedPreset(panel)); panel.on('change.amily2_opt', '#amily2_opt_preset_file_input', function(e) { opt_importPromptPresets(e.target.files[0], panel); }); panel.on('change.amily2_opt', '#amily2_opt_prompt_preset_select', function(event, data) { const selectedName = $(this).val(); const deleteBtn = panel.find('#amily2_opt_delete_prompt_preset'); const isAutomatic = data && data.isAutomatic; const noLoad = data && data.noLoad; console.log('[Amily2-Debug] Preset select changed:', selectedName, 'isAutomatic:', isAutomatic, 'noLoad:', noLoad); opt_saveSetting('plotOpt_lastUsedPresetName', selectedName); console.log('[Amily2-Debug] After saving, extension_settings contains:', extension_settings[extensionName]?.plotOpt_lastUsedPresetName); // On initial load, we might not need to reload all the data, just update the UI state. if (noLoad) { if (selectedName) deleteBtn.show(); else deleteBtn.hide(); return; } if (!selectedName) { deleteBtn.hide(); opt_saveSetting('lastUsedPresetName', ''); return; } const presets = extension_settings[extensionName]?.promptPresets || []; const selectedPreset = presets.find(p => p.name === selectedName); if (selectedPreset) { // Update cache with preset values promptCache.main = selectedPreset.mainPrompt || defaultSettings.plotOpt_mainPrompt; promptCache.system = selectedPreset.systemPrompt || defaultSettings.plotOpt_systemPrompt; promptCache.final_system = selectedPreset.finalSystemDirective || defaultSettings.plotOpt_finalSystemDirective; // Update the editor to show the content of the currently selected prompt type const initFunc = panel.data('initAmily2PromptEditor'); if (initFunc) { initFunc(); } // Save the new prompt values to the main settings opt_saveSetting('plotOpt_mainPrompt', promptCache.main); opt_saveSetting('plotOpt_systemPrompt', promptCache.system); opt_saveSetting('plotOpt_finalSystemDirective', promptCache.final_system); panel.find('#amily2_opt_rate_main').val(selectedPreset.rateMain ?? 1.0).trigger('change'); panel.find('#amily2_opt_rate_personal').val(selectedPreset.ratePersonal ?? 1.0).trigger('change'); panel.find('#amily2_opt_rate_erotic').val(selectedPreset.rateErotic ?? 1.0).trigger('change'); panel.find('#amily2_opt_rate_cuckold').val(selectedPreset.rateCuckold ?? 1.0).trigger('change'); if (!isAutomatic) { toastr.success(`已加载预设 "${selectedName}"。`); } deleteBtn.show(); } else { deleteBtn.hide(); } }); panel.data('events-bound', true); console.log(`[${extensionName}] 剧情优化UI事件已成功绑定,自动保存已激活。`); panel.on('click.amily2_opt', '#amily2_opt_refresh_worldbooks', () => { opt_loadWorldbooks(panel).then(() => { opt_loadWorldbookEntries(panel); }); }); panel.on('change.amily2_opt', '#amily2_opt_worldbook_checkbox_list input[type="checkbox"]', async function() { const selected = []; panel.find('#amily2_opt_worldbook_checkbox_list input:checked').each(function() { selected.push($(this).val()); }); await opt_saveSetting('plotOpt_selectedWorldbooks', selected); await opt_loadWorldbookEntries(panel); }); panel.on('change.amily2_opt', '#amily2_opt_worldbook_entry_list_container input[type="checkbox"]', () => { opt_saveEnabledEntries(); }); panel.on('click.amily2_opt', '#amily2_opt_worldbook_entry_select_all', () => { panel.find('#amily2_opt_worldbook_entry_list_container input[type="checkbox"]').prop('checked', true); opt_saveEnabledEntries(); }); panel.on('click.amily2_opt', '#amily2_opt_worldbook_entry_deselect_all', () => { panel.find('#amily2_opt_worldbook_entry_list_container input[type="checkbox"]').prop('checked', false); opt_saveEnabledEntries(); }); } // ========== Jqyh API 事件绑定函数 ========== function bindJqyhApiEvents() { console.log("[Amily2号-Jqyh工部] 正在绑定Jqyh API事件..."); const updateAndSaveSetting = (key, value) => { console.log(`[Amily2-Jqyh令] 收到指令: 将 [${key}] 设置为 ->`, value); if (!extension_settings[extensionName]) { extension_settings[extensionName] = {}; } extension_settings[extensionName][key] = value; saveSettingsDebounced(); console.log(`[Amily2-Jqyh录] [${key}] 的新状态已保存。`); }; // Jqyh API 开关控制 const jqyhToggle = document.getElementById('amily2_jqyh_enabled'); const jqyhContent = document.getElementById('amily2_jqyh_content'); if (jqyhToggle && jqyhContent) { jqyhToggle.checked = extension_settings[extensionName].jqyhEnabled ?? false; jqyhContent.style.display = jqyhToggle.checked ? 'block' : 'none'; jqyhToggle.addEventListener('change', function() { const isEnabled = this.checked; updateAndSaveSetting('jqyhEnabled', isEnabled); jqyhContent.style.display = isEnabled ? 'block' : 'none'; }); } // API模式切换 const apiModeSelect = document.getElementById('amily2_jqyh_api_mode'); const compatibleConfig = document.getElementById('amily2_jqyh_compatible_config'); const presetConfig = document.getElementById('amily2_jqyh_preset_config'); if (apiModeSelect && compatibleConfig && presetConfig) { apiModeSelect.value = extension_settings[extensionName].jqyhApiMode || 'openai_test'; const updateConfigVisibility = (mode) => { if (mode === 'sillytavern_preset') { compatibleConfig.style.display = 'none'; presetConfig.style.display = 'block'; loadJqyhTavernPresets(); } else { compatibleConfig.style.display = 'block'; presetConfig.style.display = 'none'; } }; updateConfigVisibility(apiModeSelect.value); apiModeSelect.addEventListener('change', function() { updateAndSaveSetting('jqyhApiMode', this.value); updateConfigVisibility(this.value); }); } // API配置字段绑定 const apiFields = [ { id: 'amily2_jqyh_api_url', key: 'jqyhApiUrl' }, { id: 'amily2_jqyh_api_key', key: 'jqyhApiKey' }, { id: 'amily2_jqyh_model', key: 'jqyhModel' } ]; apiFields.forEach(field => { const element = document.getElementById(field.id); if (element) { element.value = extension_settings[extensionName][field.key] || ''; element.addEventListener('change', function() { updateAndSaveSetting(field.key, this.value); }); } }); // 滑块控件绑定 const sliderFields = [ { id: 'amily2_jqyh_max_tokens', key: 'jqyhMaxTokens', defaultValue: 4000 }, { id: 'amily2_jqyh_temperature', key: 'jqyhTemperature', defaultValue: 0.7 } ]; sliderFields.forEach(field => { const slider = document.getElementById(field.id); const display = document.getElementById(field.id + '_value'); if (slider && display) { const value = extension_settings[extensionName][field.key] || field.defaultValue; slider.value = value; display.textContent = value; slider.addEventListener('input', function() { const newValue = parseFloat(this.value); display.textContent = newValue; updateAndSaveSetting(field.key, newValue); }); } }); // SillyTavern预设选择器 const tavernProfileSelect = document.getElementById('amily2_jqyh_tavern_profile'); if (tavernProfileSelect) { tavernProfileSelect.value = extension_settings[extensionName].jqyhTavernProfile || ''; tavernProfileSelect.addEventListener('change', function() { updateAndSaveSetting('jqyhTavernProfile', this.value); }); } // 测试连接按钮 const testButton = document.getElementById('amily2_jqyh_test_connection'); if (testButton) { testButton.addEventListener('click', async function() { const button = $(this); const originalHtml = button.html(); button.prop('disabled', true).html(' 测试中'); try { await testJqyhApiConnection(); } catch (error) { console.error('[Amily2号-Jqyh] 测试连接失败:', error); } finally { button.prop('disabled', false).html(originalHtml); } }); } const fetchModelsButton = document.getElementById('amily2_jqyh_fetch_models'); const modelSelect = document.getElementById('amily2_jqyh_model_select'); const modelInput = document.getElementById('amily2_jqyh_model'); if (fetchModelsButton && modelSelect && modelInput) { fetchModelsButton.addEventListener('click', async function() { const button = $(this); const originalHtml = button.html(); button.prop('disabled', true).html(' 获取中'); try { const models = await fetchJqyhModels(); if (models && models.length > 0) { modelSelect.innerHTML = '-- 请选择模型 --'; models.forEach(model => { const option = document.createElement('option'); option.value = model.id || model.name || model; option.textContent = model.name || model.id || model; modelSelect.appendChild(option); }); modelSelect.style.display = 'block'; modelInput.style.display = 'none'; modelSelect.addEventListener('change', function() { const selectedModel = this.value; modelInput.value = selectedModel; updateAndSaveSetting('jqyhModel', selectedModel); console.log(`[Amily2-Jqyh] 已选择模型: ${selectedModel}`); }); toastr.success(`成功获取 ${models.length} 个模型`, 'Jqyh 模型获取'); } else { toastr.warning('未获取到任何模型', 'Jqyh 模型获取'); } } catch (error) { console.error('[Amily2号-Jqyh] 获取模型列表失败:', error); toastr.error(`获取模型失败: ${error.message}`, 'Jqyh 模型获取'); } finally { button.prop('disabled', false).html(originalHtml); } }); } } async function loadJqyhTavernPresets() { const select = document.getElementById('amily2_jqyh_tavern_profile'); if (!select) return; const currentValue = select.value; select.innerHTML = '-- 加载中 --'; try { const context = getContext(); const tavernProfiles = context.extensionSettings?.connectionManager?.profiles || []; select.innerHTML = '-- 请选择预设 --'; if (tavernProfiles.length > 0) { tavernProfiles.forEach(profile => { if (profile.api && profile.preset) { const option = document.createElement('option'); option.value = profile.id; option.textContent = profile.name || profile.id; if (profile.id === currentValue) { option.selected = true; } select.appendChild(option); } }); } else { select.innerHTML = '未找到可用预设'; } } catch (error) { console.error('[Amily2号-Jqyh] 加载SillyTavern预设失败:', error); select.innerHTML = '加载失败'; } } $(document).on('change', 'input[name="amily2_icon_location"]', function() { if (!pluginAuthStatus.authorized) return; const newLocation = $(this).val(); extension_settings[extensionName]['iconLocation'] = newLocation; saveSettingsDebounced(); console.log(`[Amily-禁卫军] 收到迁都指令 -> ${newLocation}。圣意已存档。`); toastr.info(`正在将帝国徽记迁往 [${newLocation === 'topbar' ? '顶栏' : '扩展区'}]...`, "迁都令", { timeOut: 2000 }); $('#amily2_main_drawer').remove(); $(document).off("mousedown.amily2Drawer"); $('#amily2_extension_frame').remove(); setTimeout(createDrawer, 50); }); const DEFAULT_BG_IMAGE_URL = "https://cdn.jsdelivr.net/gh/Wx-2025/ST-Amily2-images@main/img/Amily-2.png"; function applyAndSaveColors(container) { const bgColor = container.find('#amily2_bg_color').val(); const btnColor = container.find('#amily2_button_color').val(); const textColor = container.find('#amily2_text_color').val(); const colors = { '--amily2-bg-color': bgColor, '--amily2-button-color': btnColor, '--amily2-text-color': textColor }; Object.entries(colors).forEach(([key, value]) => { document.documentElement.style.setProperty(key, value, 'important'); }); if (!extension_settings[extensionName]) { extension_settings[extensionName] = {}; } extension_settings[extensionName]['customColors'] = colors; saveSettingsDebounced(); } function loadAndApplyCustomColors(container) { const savedColors = extension_settings[extensionName]?.customColors; if (savedColors) { container.find('#amily2_bg_color').val(savedColors['--amily2-bg-color']); container.find('#amily2_button_color').val(savedColors['--amily2-button-color']); container.find('#amily2_text_color').val(savedColors['--amily2-text-color']); applyAndSaveColors(container); } const savedOpacity = extension_settings[extensionName]?.bgOpacity; if (savedOpacity !== undefined) { $('#amily2_bg_opacity').val(savedOpacity); $('#amily2_bg_opacity_value').text(savedOpacity); document.documentElement.style.setProperty('--amily2-bg-opacity', savedOpacity); } const savedBgImage = extension_settings[extensionName]?.customBgImage; const imageUrl = savedBgImage ? `url("${savedBgImage}")` : `url("${DEFAULT_BG_IMAGE_URL}")`; document.documentElement.style.setProperty('--amily2-bg-image', imageUrl); }
Loading entries...
Failed to get character world books.
Please load a character first.
No world book selected or linked.
No entries in the selected world book(s).
Failed to load entries.
Loading world books...
No world books found.
Failed to load world book list.
在这里定义需要从优化内容中排除的文本片段。例如,排除HTML注释,可以设置开始字符为 \`\`。
未找到世界书。
加载世界书列表失败。
加载条目中...
未选择角色。
获取角色世界书失败。
请选择一个或多个世界书以查看其条目。
所选世界书没有条目。
所选世界书没有(已启用的)条目。
加载条目失败。