import { extension_settings, getContext } from "/scripts/extensions.js";
import {
extensionName,
defaultSettings,
saveSettings,
} from "../utils/settings.js";
import { showHtmlModal } from './page-window.js';
import { configManager } from '../utils/config/ConfigManager.js';
import { ruleProfileManager, resolveHistoriographyRuleConfig } from '../utils/config/RuleProfileManager.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 getHistoriographyRuleConfig() {
return resolveHistoriographyRuleConfig(extension_settings[extensionName] || {});
}
function _escapeHtml(text) {
return String(text ?? '').replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"');
}
function _populateHistRuleProfileSelect(select, detail) {
const profiles = detail?.profiles ?? ruleProfileManager.listProfiles();
const assigned = detail?.assignments?.historiography ?? ruleProfileManager.getAssignment('historiography') ?? '';
select.innerHTML = [
'',
...profiles.map(p =>
``
),
].join('');
}
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 histRuleSelect = document.getElementById("historiography-rule-profile-select");
if (histRuleSelect) {
_populateHistRuleProfileSelect(histRuleSelect);
histRuleSelect.addEventListener("change", () => {
ruleProfileManager.setAssignment('historiography', histRuleSelect.value || null);
const name = histRuleSelect.selectedOptions[0]?.textContent || '';
toastr.info(histRuleSelect.value ? `史官提取规则已切换为「${name}」` : '史官提取规则已取消分配');
});
document.addEventListener('amily2:ruleProfilesChanged', (e) => {
_populateHistRuleProfileSelect(histRuleSelect, e.detail);
});
}
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);
}
});
}
});
// 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 = '';
}
}