import { extension_settings, getContext } from "/scripts/extensions.js"; import { extensionName, defaultSettings, saveSettings, } from "../utils/settings.js"; import { showHtmlModal } from './page-window.js'; import { applyExclusionRules, extractBlocksByTags } from '../core/utils/rag-tag-extractor.js'; import { configManager } from '../utils/config/ConfigManager.js'; import { getAvailableWorldbooks, getLoresForWorldbook, executeManualSummary, executeRefinement, executeExpedition, stopExpedition, archiveCurrentLedger, getArchivedLedgers, restoreArchivedLedger } from "../core/historiographer.js"; import { getNgmsApiSettings, testNgmsApiConnection, fetchNgmsModels } from "../core/api/Ngms_api.js"; function setupPromptEditor(type) { const selector = document.getElementById( `amily2_mhb_${type}_prompt_selector`, ); const editor = document.getElementById(`amily2_mhb_${type}_editor`); const saveBtn = document.getElementById(`amily2_mhb_${type}_save_button`); const restoreBtn = document.getElementById( `amily2_mhb_${type}_restore_button`, ); const jailbreakKey = type === "small" ? "historiographySmallJailbreakPrompt" : "historiographyLargeJailbreakPrompt"; const mainPromptKey = type === "small" ? "historiographySmallSummaryPrompt" : "historiographyLargeRefinePrompt"; const updateEditorView = () => { const selected = selector.value; if (selected === "jailbreak") { editor.value = extension_settings[extensionName][jailbreakKey]; } else { editor.value = extension_settings[extensionName][mainPromptKey]; } }; selector.addEventListener("change", updateEditorView); saveBtn.addEventListener("click", () => { const selected = selector.value; if (selected === "jailbreak") { extension_settings[extensionName][jailbreakKey] = editor.value; } else { extension_settings[extensionName][mainPromptKey] = editor.value; } if (saveSettings()) { toastr.success( `${type === "small" ? "微言录" : "宏史卷"}的${selected === "jailbreak" ? "破限谕旨" : "纲要"}已保存!`, ); } }); restoreBtn.addEventListener("click", () => { const selected = selector.value; if (selected === "jailbreak") { editor.value = defaultSettings[jailbreakKey]; } else { editor.value = defaultSettings[mainPromptKey]; } toastr.info("已恢复为默认谕旨,请点击“保存当前”以确认。"); }); updateEditorView(); const expandBtn = document.getElementById(`amily2_mhb_${type}_expand_editor`); expandBtn.addEventListener('click', () => { const selectedValue = selector.value; const selectedText = selector.options[selector.selectedIndex].text; const currentContent = editor.value; const dialogHtml = ` `; const dialogElement = $(dialogHtml).appendTo('body'); const dialogTextarea = dialogElement.find('textarea'); dialogTextarea.val(currentContent); const closeDialog = () => { dialogElement[0].close(); dialogElement.remove(); }; dialogElement.find('.popup-button-ok').on('click', () => { const newContent = dialogTextarea.val(); editor.value = newContent; if (selectedValue === "jailbreak") { extension_settings[extensionName][jailbreakKey] = newContent; } else { extension_settings[extensionName][mainPromptKey] = newContent; } if (saveSettings()) { toastr.success(`${type === 'small' ? '微言录' : '宏史卷'}的${selectedText}已镌刻!`); } closeDialog(); }); dialogElement.find('.popup-button-cancel').on('click', closeDialog); dialogElement[0].showModal(); }); } export function bindHistoriographyEvents() { console.log("[Amily2号-工部] 【敕史局】的专属工匠已就位..."); setupPromptEditor("small"); setupPromptEditor("large"); // ========== 🛰️ Ngms API 系统绑定 ========== bindNgmsApiEvents(); // ========== 📜 微言录 (Small Summary) 绑定 (无改动) ========== const smallStartFloor = document.getElementById("amily2_mhb_small_start_floor"); const smallEndFloor = document.getElementById("amily2_mhb_small_end_floor"); const smallExecuteBtn = document.getElementById("amily2_mhb_small_manual_execute"); const smallAutoEnable = document.getElementById("amily2_mhb_small_auto_enabled"); const smallTriggerThreshold = document.getElementById("amily2_mhb_small_trigger_count"); const writeToLorebook = document.getElementById("historiography_write_to_lorebook"); const ingestToRag = document.getElementById("historiography_ingest_to_rag"); smallExecuteBtn.addEventListener("click", () => { const start = parseInt(smallStartFloor.value, 10); const end = parseInt(smallEndFloor.value, 10); if (isNaN(start) || isNaN(end) || start <= 0 || end <= 0 || start > end) { toastr.error("请输入有效的起始和结束楼层!", "圣谕有误"); return; } executeManualSummary(start, end); }); smallAutoEnable.addEventListener("change", (event) => { extension_settings[extensionName].historiographySmallAutoEnable = event.target.checked; saveSettings(); }); smallTriggerThreshold.addEventListener("change", (event) => { const value = parseInt(event.target.value, 10); if (isNaN(value) || value < 1) { event.target.value = defaultSettings.historiographySmallTriggerThreshold; toastr.warning("远征阈值必须是大于0的数字。已重置。", "圣谕有误"); return; } extension_settings[extensionName].historiographySmallTriggerThreshold = value; saveSettings(); }); const retentionCount = document.getElementById("historiography_retention_count"); retentionCount.addEventListener("change", (event) => { const value = parseInt(event.target.value, 10); if (isNaN(value) || value < 0) { event.target.value = defaultSettings.historiographyRetentionCount; toastr.warning("保留层数必须是大于或等于0的数字。已重置。", "圣谕有误"); return; } extension_settings[extensionName].historiographyRetentionCount = value; saveSettings(); }); writeToLorebook.addEventListener("change", (event) => { extension_settings[extensionName].historiographyWriteToLorebook = event.target.checked; saveSettings(); }); ingestToRag.addEventListener("change", (event) => { extension_settings[extensionName].historiographyIngestToRag = event.target.checked; saveSettings(); }); smallAutoEnable.checked = extension_settings[extensionName].historiographySmallAutoEnable ?? false; smallTriggerThreshold.value = extension_settings[extensionName].historiographySmallTriggerThreshold ?? 30; retentionCount.value = extension_settings[extensionName].historiographyRetentionCount ?? 5; writeToLorebook.checked = extension_settings[extensionName].historiographyWriteToLorebook ?? true; ingestToRag.checked = extension_settings[extensionName].historiographyIngestToRag ?? false; const autoSummaryInteractive = document.getElementById("historiography_auto_summary_interactive"); autoSummaryInteractive.checked = extension_settings[extensionName].historiographyAutoSummaryInteractive ?? false; autoSummaryInteractive.addEventListener("change", (event) => { extension_settings[extensionName].historiographyAutoSummaryInteractive = event.target.checked; saveSettings(); }); // ========== 🏷️ 标签与排除规则绑定 (新增) ========== const tagExtractionToggle = document.getElementById("historiography-tag-extraction-toggle"); const tagInputContainer = document.getElementById("historiography-tag-input-container"); const tagInput = document.getElementById("historiography-tag-input"); const exclusionRulesBtn = document.getElementById("historiography-exclusion-rules-btn"); tagExtractionToggle.checked = extension_settings[extensionName].historiographyTagExtractionEnabled ?? false; tagInput.value = extension_settings[extensionName].historiographyTags ?? ''; tagInputContainer.style.display = tagExtractionToggle.checked ? 'block' : 'none'; tagExtractionToggle.addEventListener("change", (event) => { const isEnabled = event.target.checked; extension_settings[extensionName].historiographyTagExtractionEnabled = isEnabled; tagInputContainer.style.display = isEnabled ? 'block' : 'none'; saveSettings(); }); tagInput.addEventListener("change", (event) => { extension_settings[extensionName].historiographyTags = event.target.value; saveSettings(); }); exclusionRulesBtn.addEventListener("click", showHistoriographyExclusionRulesModal); const expeditionExecuteBtn = document.getElementById("amily2_mhb_small_expedition_execute"); const updateExpeditionButtonUI = (state) => { expeditionExecuteBtn.dataset.state = state; switch (state) { case 'running': expeditionExecuteBtn.innerHTML = ' 停止远征'; expeditionExecuteBtn.className = 'menu_button small_button interactable danger'; break; case 'paused': expeditionExecuteBtn.innerHTML = ' 继续远征'; expeditionExecuteBtn.className = 'menu_button small_button interactable success'; break; case 'idle': default: expeditionExecuteBtn.innerHTML = ' 开始远征'; expeditionExecuteBtn.className = 'menu_button small_button interactable'; break; } }; document.addEventListener('amily2-expedition-state-change', (e) => { const { isRunning, manualStop } = e.detail; if (isRunning) { updateExpeditionButtonUI('running'); } else if (manualStop) { updateExpeditionButtonUI('paused'); } else { updateExpeditionButtonUI('idle'); } }); expeditionExecuteBtn.addEventListener("click", () => { const currentState = expeditionExecuteBtn.dataset.state || 'idle'; if (currentState === 'running') { stopExpedition(); } else { executeExpedition(); } }); updateExpeditionButtonUI('idle'); // ========== 📚 史册归档与回溯 绑定 ========== const archiveCurrentBtn = document.getElementById("amily2_mhb_archive_current"); const archiveSelector = document.getElementById("amily2_mhb_archive_selector"); const refreshArchivesBtn = document.getElementById("amily2_mhb_refresh_archives"); const restoreArchiveBtn = document.getElementById("amily2_mhb_restore_archive"); const updateArchiveList = async () => { archiveSelector.innerHTML = ''; const archives = await getArchivedLedgers(); archiveSelector.innerHTML = ""; // 清空 if (archives && archives.length > 0) { archives.forEach((arch) => { const option = document.createElement("option"); option.value = arch.key; option.textContent = arch.comment; archiveSelector.appendChild(option); }); } else { archiveSelector.innerHTML = ''; } }; archiveCurrentBtn.addEventListener("click", async () => { if (confirm("确定要归档当前的【对话流水总帐】并停用它吗?\n这将允许您开始一段全新的历史记录。")) { const success = await archiveCurrentLedger(); if (success) { updateArchiveList(); // 归档成功后刷新列表 } } }); refreshArchivesBtn.addEventListener("click", updateArchiveList); restoreArchiveBtn.addEventListener("click", async () => { const selectedKey = archiveSelector.value; if (!selectedKey) { toastr.warning("请先选择一个要回溯的史册!", "圣谕不明"); return; } if (confirm("确定要回溯选中的史册吗?\n当前的活跃史册(如果有)将被自动归档。")) { await restoreArchivedLedger(selectedKey); updateArchiveList(); // 回溯后刷新列表 } }); // ========== 💎 宏史卷 (史册精炼) 绑定 ========== const largeWbSelector = document.getElementById( "amily2_mhb_large_worldbook_selector", ); const largeLoreSelector = document.getElementById( "amily2_mhb_large_lore_selector", ); const largeRefreshWbBtn = document.getElementById( "amily2_mhb_large_refresh_worldbooks", ); const largeRefreshLoresBtn = document.getElementById( "amily2_mhb_large_refresh_lores", ); const largeRefineBtn = document.getElementById( "amily2_mhb_large_refine_execute", ); const updateWorldbookList = async () => { largeWbSelector.innerHTML = ''; const worldbooks = await getAvailableWorldbooks(); largeWbSelector.innerHTML = ""; // 清空 if (worldbooks && worldbooks.length > 0) { worldbooks.forEach((wb) => { const option = document.createElement("option"); option.value = wb; option.textContent = wb; largeWbSelector.appendChild(option); }); largeWbSelector.dispatchEvent(new Event("change")); } else { largeWbSelector.innerHTML = ''; } }; const updateLoreList = async () => { const selectedWb = largeWbSelector.value; if (!selectedWb) { largeLoreSelector.innerHTML = ''; return; } largeLoreSelector.innerHTML = ''; const lores = await getLoresForWorldbook(selectedWb); largeLoreSelector.innerHTML = ""; // 清空 if (lores && lores.length > 0) { lores.forEach((lore) => { const option = document.createElement("option"); option.value = lore.key; option.textContent = `[${lore.key}] ${lore.comment}`; largeLoreSelector.appendChild(option); }); } else { largeLoreSelector.innerHTML = ''; } }; largeRefreshWbBtn.addEventListener("click", updateWorldbookList); largeWbSelector.addEventListener("change", updateLoreList); largeRefreshLoresBtn.addEventListener("click", updateLoreList); largeRefineBtn.addEventListener("click", () => { const worldbook = largeWbSelector.value; const loreKey = largeLoreSelector.value; if (!worldbook || !loreKey) { toastr.error("请先选择一个国史馆及其中的史册条目!", "圣谕不全"); return; } executeRefinement(worldbook, loreKey); }); const vectorizeSummaryContent = document.getElementById("amily2_vectorize_summary_content"); vectorizeSummaryContent.checked = extension_settings[extensionName].historiographyVectorizeSummary ?? false; vectorizeSummaryContent.addEventListener("change", (event) => { extension_settings[extensionName].historiographyVectorizeSummary = event.target.checked; saveSettings(); }); } // ========== Ngms API 事件绑定函数 ========== function bindNgmsApiEvents() { console.log("[Amily2号-Ngms工部] 正在绑定Ngms API事件..."); const updateAndSaveSetting = (key, value) => { console.log(`[Amily2-Ngms令] 收到指令: 将 [${key}] 设置为 ->`, value); if (!extension_settings[extensionName]) { extension_settings[extensionName] = {}; } extension_settings[extensionName][key] = value; saveSettings(); console.log(`[Amily2-Ngms录] [${key}] 的新状态已保存。`); }; // Ngms API 开关控制 const ngmsToggle = document.getElementById('amily2_ngms_enabled'); const ngmsFakeStreamToggle = document.getElementById('amily2_ngms_fakestream_enabled'); const ngmsContent = document.getElementById('amily2_ngms_content'); if (ngmsToggle && ngmsContent) { ngmsToggle.checked = extension_settings[extensionName].ngmsEnabled ?? false; ngmsContent.style.display = ngmsToggle.checked ? 'block' : 'none'; ngmsToggle.addEventListener('change', function() { const isEnabled = this.checked; updateAndSaveSetting('ngmsEnabled', isEnabled); ngmsContent.style.display = isEnabled ? 'block' : 'none'; }); } if (ngmsFakeStreamToggle) { ngmsFakeStreamToggle.checked = extension_settings[extensionName].ngmsFakeStreamEnabled ?? false; ngmsFakeStreamToggle.addEventListener('change', function() { updateAndSaveSetting('ngmsFakeStreamEnabled', this.checked); }); } // API模式切换 const apiModeSelect = document.getElementById('amily2_ngms_api_mode'); const compatibleConfig = document.getElementById('amily2_ngms_compatible_config'); const presetConfig = document.getElementById('amily2_ngms_preset_config'); if (apiModeSelect && compatibleConfig && presetConfig) { apiModeSelect.value = extension_settings[extensionName].ngmsApiMode || 'openai_test'; const updateConfigVisibility = (mode) => { if (mode === 'sillytavern_preset') { compatibleConfig.style.display = 'none'; presetConfig.style.display = 'block'; loadNgmsTavernPresets(); } else { compatibleConfig.style.display = 'block'; presetConfig.style.display = 'none'; } }; updateConfigVisibility(apiModeSelect.value); apiModeSelect.addEventListener('change', function() { updateAndSaveSetting('ngmsApiMode', this.value); updateConfigVisibility(this.value); }); } // API配置字段绑定 const apiFields = [ { id: 'amily2_ngms_api_url', key: 'ngmsApiUrl' }, { id: 'amily2_ngms_api_key', key: 'ngmsApiKey', sensitive: true }, { id: 'amily2_ngms_model', key: 'ngmsModel' } ]; apiFields.forEach(field => { const element = document.getElementById(field.id); if (element) { // 敏感字段(API Key)从 configManager(localStorage)读取 element.value = field.sensitive ? (configManager.get(field.key) || '') : (extension_settings[extensionName][field.key] || ''); element.addEventListener('change', function() { if (field.sensitive) { configManager.set(field.key, this.value); } else { updateAndSaveSetting(field.key, this.value); } }); } }); // 滑块控件绑定 const sliderFields = [ { id: 'amily2_ngms_max_tokens', key: 'ngmsMaxTokens', defaultValue: 4000 }, { id: 'amily2_ngms_temperature', key: 'ngmsTemperature', 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_ngms_tavern_profile'); if (tavernProfileSelect) { tavernProfileSelect.value = extension_settings[extensionName].ngmsTavernProfile || ''; tavernProfileSelect.addEventListener('change', function() { updateAndSaveSetting('ngmsTavernProfile', this.value); }); } // 测试连接按钮 const testButton = document.getElementById('amily2_ngms_test_connection'); if (testButton) { testButton.addEventListener('click', async function() { const button = $(this); const originalHtml = button.html(); button.prop('disabled', true).html(' 测试中'); try { await testNgmsApiConnection(); } catch (error) { console.error('[Amily2号-Ngms] 测试连接失败:', error); } finally { button.prop('disabled', false).html(originalHtml); } }); } // 获取模型按钮 const fetchModelsButton = document.getElementById('amily2_ngms_fetch_models'); const modelSelect = document.getElementById('amily2_ngms_model_select'); const modelInput = document.getElementById('amily2_ngms_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 fetchNgmsModels(); 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('ngmsModel', selectedModel); console.log(`[Amily2-Ngms] 已选择模型: ${selectedModel}`); }); toastr.success(`成功获取 ${models.length} 个模型`, 'Ngms 模型获取'); } else { toastr.warning('未获取到任何模型', 'Ngms 模型获取'); } } catch (error) { console.error('[Amily2号-Ngms] 获取模型列表失败:', error); toastr.error(`获取模型失败: ${error.message}`, 'Ngms 模型获取'); } finally { button.prop('disabled', false).html(originalHtml); } }); } } // 加载SillyTavern预设列表 async function loadNgmsTavernPresets() { const select = document.getElementById('amily2_ngms_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号-Ngms] 加载SillyTavern预设失败:', error); select.innerHTML = ''; } } function showHistoriographyExclusionRulesModal() { const rules = extension_settings[extensionName].historiographyExclusionRules || []; const createRuleRowHtml = (rule = { start: '', end: '' }, index) => `
在这里定义需要从提取内容中排除的文本片段。例如,排除HTML注释,可以设置开始字符为 \`\`。