mirror of
https://github.com/SilenceLurker/ST-Amily2-Chat-Optimisation.git
synced 2026-06-06 04:15:51 +00:00
Initial commit with CC BY-NC-ND 4.0 license
This commit is contained in:
2745
ui/bindings.js
Normal file
2745
ui/bindings.js
Normal file
File diff suppressed because it is too large
Load Diff
252
ui/drawer.js
Normal file
252
ui/drawer.js
Normal file
@@ -0,0 +1,252 @@
|
||||
import { getSlideToggleOptions } from '/script.js';
|
||||
import { slideToggle } from '/lib.js';
|
||||
import { extension_settings, renderExtensionTemplateAsync } from "/scripts/extensions.js";
|
||||
import { extensionName, defaultSettings } from "../utils/settings.js";
|
||||
import {
|
||||
checkAuthorization,
|
||||
displayExpiryInfo,
|
||||
pluginAuthStatus,
|
||||
} from "../utils/auth.js";
|
||||
import {
|
||||
updateUI,
|
||||
setAvailableModels,
|
||||
populateModelDropdown,
|
||||
applyUpdateIndicator,
|
||||
} from "./state.js";
|
||||
import { bindModalEvents } from "./bindings.js";
|
||||
import { fetchModels } from "../core/api.js";
|
||||
import { bindHistoriographyEvents } from "./historiography-bindings.js";
|
||||
import { bindHanlinyuanEvents } from "./hanlinyuan-bindings.js";
|
||||
import { bindTableEvents } from './table-bindings.js';
|
||||
import { showContentModal } from "./page-window.js";
|
||||
import { initializeRendererBindings } from "../core/tavern-helper/renderer-bindings.js";
|
||||
import { bindSuperMemoryEvents } from "../core/super-memory/bindings.js";
|
||||
const extensionFolderPath = `scripts/extensions/third-party/${extensionName}`;
|
||||
|
||||
|
||||
async function loadSettings() {
|
||||
extension_settings[extensionName] = {
|
||||
...defaultSettings,
|
||||
...(extension_settings[extensionName] || {}),
|
||||
};
|
||||
|
||||
|
||||
checkAuthorization();
|
||||
|
||||
|
||||
const autoLogin = localStorage.getItem("plugin_auto_login") === "true";
|
||||
console.log(
|
||||
`[Amily2-调试] 授权状态: ${pluginAuthStatus.authorized}, 自动登录标志: ${autoLogin}`,
|
||||
);
|
||||
if (autoLogin && pluginAuthStatus.authorized) {
|
||||
console.log("[Amily2号] 检测到有效授权,将执行自动UI更新。");
|
||||
}
|
||||
|
||||
$("#expiry_info").html(displayExpiryInfo());
|
||||
updateUI();
|
||||
|
||||
if (pluginAuthStatus.authorized && extension_settings[extensionName].apiUrl) {
|
||||
const cachedModels = localStorage.getItem("cached_models_amily2");
|
||||
if (cachedModels) {
|
||||
const models = JSON.parse(cachedModels);
|
||||
console.log(`[Amily2号] 从缓存加载模型列表 (${models.length}个)`);
|
||||
setAvailableModels(models);
|
||||
populateModelDropdown();
|
||||
} else {
|
||||
toastr.info("正在自动加载模型列表...", "Amily2号");
|
||||
setTimeout(async () => {
|
||||
const models = await fetchModels();
|
||||
if (models.length > 0) {
|
||||
setAvailableModels(models);
|
||||
localStorage.setItem("cached_models_amily2", JSON.stringify(models));
|
||||
populateModelDropdown();
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function initializePanel(contentPanel, errorContainer) {
|
||||
if (contentPanel.data("initialized")) return;
|
||||
|
||||
try {
|
||||
const modalContent = await $.get(`${extensionFolderPath}/assets/amily2-modal.html`);
|
||||
contentPanel.html(modalContent);
|
||||
const mainContainer = contentPanel.find('#amily2_chat_optimiser');
|
||||
|
||||
if (mainContainer.length) {
|
||||
const additionalFeaturesContent = await $.get(`${extensionFolderPath}/assets/amily-additional-features/Amily2-AdditionalFeatures.html`);
|
||||
const additionalPanelHtml = `<div id="amily2_additional_features_panel" style="display: none;">${additionalFeaturesContent}</div>`;
|
||||
mainContainer.append(additionalPanelHtml);
|
||||
|
||||
const textOptimizationContent = await $.get(`${extensionFolderPath}/assets/Amily2-TextOptimization.html`);
|
||||
const textOptimizationPanelHtml = `<div id="amily2_text_optimization_panel" style="display: none;">${textOptimizationContent}</div>`;
|
||||
mainContainer.append(textOptimizationPanelHtml);
|
||||
|
||||
const hanlinyuanContent = await $.get(`${extensionFolderPath}/assets/amily-hanlinyuan-system/hanlinyuan.html`);
|
||||
const hanlinyuanPanelHtml = `<div id="amily2_hanlinyuan_panel" style="display: none;">${hanlinyuanContent}</div>`;
|
||||
mainContainer.append(hanlinyuanPanelHtml);
|
||||
|
||||
const memorisationFormsContent = await $.get(`${extensionFolderPath}/assets/amily-data-table/Memorisation-forms.html`);
|
||||
const memorisationFormsPanelHtml = `<div id="amily2_memorisation_forms_panel" style="display: none;">${memorisationFormsContent}</div>`;
|
||||
mainContainer.append(memorisationFormsPanelHtml);
|
||||
|
||||
const plotOptimizationContent = await $.get(`${extensionFolderPath}/assets/Amily2-optimization.html`);
|
||||
const plotOptimizationPanelHtml = `<div id="amily2_plot_optimization_panel" style="display: none;">${plotOptimizationContent}</div>`;
|
||||
mainContainer.append(plotOptimizationPanelHtml);
|
||||
|
||||
const cwbContent = await $.get(`${extensionFolderPath}/CharacterWorldBook/cwb_settings.html`);
|
||||
const cwbPanelHtml = `<div id="amily2_character_world_book_panel" style="display: none;">${cwbContent}</div>`;
|
||||
mainContainer.append(cwbPanelHtml);
|
||||
|
||||
const worldEditorContent = await $.get(`${extensionFolderPath}/WorldEditor.html`);
|
||||
const worldEditorPanelHtml = `<div id="amily2_world_editor_panel" style="display: none;">${worldEditorContent}</div>`;
|
||||
mainContainer.append(worldEditorPanelHtml);
|
||||
|
||||
const glossaryContent = await $.get(`${extensionFolderPath}/assets/amily-glossary-system/amily2-glossary.html`);
|
||||
const glossaryPanelHtml = `<div id="amily2_glossary_panel" style="display: none;">${glossaryContent}</div>`;
|
||||
mainContainer.append(glossaryPanelHtml);
|
||||
|
||||
const rendererContent = await $.get(`${extensionFolderPath}/core/tavern-helper/renderer.html`);
|
||||
const rendererPanelHtml = `<div id="amily2_renderer_panel" style="display: none;">${rendererContent}</div>`;
|
||||
mainContainer.append(rendererPanelHtml);
|
||||
|
||||
const superMemoryContent = await $.get(`${extensionFolderPath}/core/super-memory/index.html`);
|
||||
const superMemoryPanelHtml = `<div id="amily2_super_memory_panel" style="display: none;">${superMemoryContent}</div>`;
|
||||
mainContainer.append(superMemoryPanelHtml);
|
||||
|
||||
// 在面板创建后,加载世界书编辑器脚本
|
||||
const worldEditorScriptId = 'world-editor-script';
|
||||
if (!document.getElementById(worldEditorScriptId)) {
|
||||
const worldEditorScript = document.createElement("script");
|
||||
worldEditorScript.id = worldEditorScriptId;
|
||||
worldEditorScript.type = "module"; // 必须作为模块加载
|
||||
worldEditorScript.src = `${extensionFolderPath}/WorldEditor/WorldEditor.js?v=${Date.now()}`;
|
||||
document.head.appendChild(worldEditorScript);
|
||||
}
|
||||
}
|
||||
|
||||
bindModalEvents();
|
||||
bindHistoriographyEvents();
|
||||
await loadSettings();
|
||||
bindHanlinyuanEvents();
|
||||
bindTableEvents();
|
||||
initializeRendererBindings();
|
||||
bindSuperMemoryEvents();
|
||||
contentPanel.data("initialized", true);
|
||||
console.log("[Amily-重构] 宫殿模块已按蓝图竣工。");
|
||||
applyUpdateIndicator();
|
||||
} catch (error) {
|
||||
console.error("[Amily-建设部] 紧急报告:加载模块化蓝图时发生意外:", error);
|
||||
const errorMessage = errorContainer
|
||||
? '<p style="color:red; padding:10px; border:1px solid red; border-radius:5px;">紧急报告:在扩展区域建造Amily2号府邸时发生意外。</p>'
|
||||
: '<p style="color:red; padding: 20px;">紧急报告:无法加载Amily2号府邸内饰。</p>';
|
||||
|
||||
if (errorContainer) {
|
||||
errorContainer.append(errorMessage);
|
||||
} else {
|
||||
contentPanel.html(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toggleDrawerFallback() {
|
||||
const drawerIcon = $('#amily2_drawer_icon');
|
||||
const contentPanel = $('#amily2_drawer_content');
|
||||
if (drawerIcon.hasClass('openIcon') && !contentPanel.is(':visible')) {
|
||||
drawerIcon.removeClass('openIcon').addClass('closedIcon');
|
||||
}
|
||||
if (drawerIcon.hasClass('closedIcon')) {
|
||||
$('.openDrawer').not(contentPanel).not('.pinnedOpen').addClass('resizing').each((_, el) => {
|
||||
slideToggle(el, {
|
||||
...getSlideToggleOptions(),
|
||||
onAnimationEnd: function (el) {
|
||||
el.closest('.drawer-content').classList.remove('resizing');
|
||||
},
|
||||
});
|
||||
});
|
||||
$('.openIcon').not(drawerIcon).not('.drawerPinnedOpen').toggleClass('closedIcon openIcon');
|
||||
$('.openDrawer').not(contentPanel).not('.pinnedOpen').toggleClass('closedDrawer openDrawer');
|
||||
|
||||
drawerIcon.toggleClass('closedIcon openIcon');
|
||||
contentPanel.toggleClass('closedDrawer openDrawer');
|
||||
|
||||
contentPanel.addClass('resizing').each((_, el) => {
|
||||
slideToggle(el, {
|
||||
...getSlideToggleOptions(),
|
||||
onAnimationEnd: function (el) {
|
||||
el.closest('.drawer-content').classList.remove('resizing');
|
||||
},
|
||||
});
|
||||
});
|
||||
} else {
|
||||
drawerIcon.toggleClass('openIcon closedIcon');
|
||||
contentPanel.toggleClass('openDrawer closedDrawer');
|
||||
|
||||
contentPanel.addClass('resizing').each((_, el) => {
|
||||
slideToggle(el, {
|
||||
...getSlideToggleOptions(),
|
||||
onAnimationEnd: function (el) {
|
||||
el.closest('.drawer-content').classList.remove('resizing');
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export async function createDrawer() {
|
||||
const settings = extension_settings[extensionName];
|
||||
const location = settings.iconLocation || 'topbar';
|
||||
|
||||
if (location === 'topbar') {
|
||||
if ($("#amily2_main_drawer").length > 0) return;
|
||||
|
||||
const amily2DrawerHtml = `
|
||||
<div id="amily2_main_drawer" class="drawer">
|
||||
<div class="drawer-toggle" data-drawer="amily2_drawer_content">
|
||||
<div id="amily2_drawer_icon" class="drawer-icon fa-solid fa-magic fa-fw closedIcon interactable" title="Amily2号优化助手" tabindex="0"></div>
|
||||
</div>
|
||||
<div id="amily2_drawer_content" class="drawer-content closedDrawer">
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
$("#sys-settings-button").after(amily2DrawerHtml);
|
||||
|
||||
const contentPanel = $("#amily2_drawer_content");
|
||||
await initializePanel(contentPanel);
|
||||
|
||||
try {
|
||||
const { doNavbarIconClick } = await import('/script.js');
|
||||
if (typeof doNavbarIconClick === 'function') {
|
||||
$('#amily2_main_drawer .drawer-toggle').on('click', doNavbarIconClick);
|
||||
console.log('[Amily2-兼容性] 检测到新版环境,已绑定官方点击事件。');
|
||||
} else {
|
||||
throw new Error('doNavbarIconClick is not a function');
|
||||
}
|
||||
} catch (error) {
|
||||
$('#amily2_main_drawer .drawer-toggle').on('click', toggleDrawerFallback);
|
||||
console.log('[Amily2-兼容性] 检测到旧版环境 (无法导入 doNavbarIconClick),已绑定后备点击事件。');
|
||||
}
|
||||
|
||||
} else if (location === 'extensions') {
|
||||
if ($("#extensions_settings2 #amily2_chat_optimiser").length > 0) return;
|
||||
const amilyFrameHtml = `
|
||||
<div id="amily2_extension_frame">
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b><i class="fas fa-crown" style="color: #ffc107;"></i> Amily2号 优化中枢</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content" style="display: none;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const frame = $(amilyFrameHtml);
|
||||
$('#extensions_settings2').append(frame);
|
||||
const contentPanel = frame.find('.inline-drawer-content');
|
||||
initializePanel(contentPanel, frame);
|
||||
}
|
||||
}
|
||||
1
ui/hanlinyuan-bindings.js
Normal file
1
ui/hanlinyuan-bindings.js
Normal file
File diff suppressed because one or more lines are too long
661
ui/historiography-bindings.js
Normal file
661
ui/historiography-bindings.js
Normal file
@@ -0,0 +1,661 @@
|
||||
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 {
|
||||
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 = `
|
||||
<dialog class="popup wide_dialogue_popup large_dialogue_popup">
|
||||
<div class="popup-body">
|
||||
<h4 style="margin-top:0; color: #eee; border-bottom: 1px solid rgba(255,255,255,0.2); padding-bottom: 10px;">正在编辑: ${selectedText}</h4>
|
||||
<div class="popup-content" style="height: 70vh;"><div class="height100p wide100p flex-container"><textarea class="height100p wide100p maximized_textarea text_pole"></textarea></div></div>
|
||||
<div class="popup-controls"><div class="popup-button-ok menu_button menu_button_primary interactable">保存并关闭</div><div class="popup-button-cancel menu_button interactable" style="margin-left: 10px;">取消</div></div>
|
||||
</div>
|
||||
</dialog>`;
|
||||
|
||||
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 = '<i class="fas fa-stop-circle"></i> 停止远征';
|
||||
expeditionExecuteBtn.className = 'menu_button small_button interactable danger';
|
||||
break;
|
||||
case 'paused':
|
||||
expeditionExecuteBtn.innerHTML = '<i class="fas fa-play-circle"></i> 继续远征';
|
||||
expeditionExecuteBtn.className = 'menu_button small_button interactable success';
|
||||
break;
|
||||
case 'idle':
|
||||
default:
|
||||
expeditionExecuteBtn.innerHTML = '<i class="fas fa-flag-checkered"></i> 开始远征';
|
||||
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 = '<option value="">正在翻阅旧档...</option>';
|
||||
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 = '<option value="">未发现归档史册</option>';
|
||||
}
|
||||
};
|
||||
|
||||
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 = '<option value="">正在遍览帝国疆域...</option>';
|
||||
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 = '<option value="">未发现任何国史馆</option>';
|
||||
}
|
||||
};
|
||||
|
||||
const updateLoreList = async () => {
|
||||
const selectedWb = largeWbSelector.value;
|
||||
if (!selectedWb) {
|
||||
largeLoreSelector.innerHTML = '<option value="">请先选择国史馆</option>';
|
||||
return;
|
||||
}
|
||||
largeLoreSelector.innerHTML = '<option value="">正在检阅史册...</option>';
|
||||
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 = '<option value="">此国史馆为空</option>';
|
||||
}
|
||||
};
|
||||
|
||||
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 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';
|
||||
});
|
||||
}
|
||||
|
||||
// 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' },
|
||||
{ id: 'amily2_ngms_model', key: 'ngmsModel' }
|
||||
];
|
||||
|
||||
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_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('<i class="fas fa-spinner fa-spin"></i> 测试中');
|
||||
|
||||
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('<i class="fas fa-spinner fa-spin"></i> 获取中');
|
||||
|
||||
try {
|
||||
const models = await fetchNgmsModels();
|
||||
|
||||
if (models && models.length > 0) {
|
||||
// 清空并填充模型下拉框
|
||||
modelSelect.innerHTML = '<option value="">-- 请选择模型 --</option>';
|
||||
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 = '<option value="">-- 加载中 --</option>';
|
||||
|
||||
try {
|
||||
const context = getContext();
|
||||
const tavernProfiles = context.extensionSettings?.connectionManager?.profiles || [];
|
||||
|
||||
select.innerHTML = '<option value="">-- 请选择预设 --</option>';
|
||||
|
||||
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 = '<option value="">未找到可用预设</option>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Amily2号-Ngms] 加载SillyTavern预设失败:', error);
|
||||
select.innerHTML = '<option value="">加载失败</option>';
|
||||
}
|
||||
}
|
||||
|
||||
function showHistoriographyExclusionRulesModal() {
|
||||
const rules = extension_settings[extensionName].historiographyExclusionRules || [];
|
||||
|
||||
const createRuleRowHtml = (rule = { start: '', end: '' }, index) => `
|
||||
<div class="hly-exclusion-rule-row" data-index="${index}">
|
||||
<input type="text" class="hly-imperial-brush" value="${rule.start}" placeholder="开始字符, 如 <!--">
|
||||
<span>到</span>
|
||||
<input type="text" class="hly-imperial-brush" value="${rule.end}" placeholder="结束字符, 如 -->">
|
||||
<button class="hly-delete-rule-btn" title="删除此规则">×</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const rulesHtml = rules.map(createRuleRowHtml).join('');
|
||||
|
||||
const modalHtml = `
|
||||
<div id="historiography-exclusion-rules-container">
|
||||
<p class="hly-notes">在这里定义需要从提取内容中排除的文本片段。例如,排除HTML注释,可以设置开始字符为 \`<!--\`,结束字符为 \`-->\`。</p>
|
||||
<div id="historiography-rules-list">${rulesHtml}</div>
|
||||
<button id="historiography-add-rule-btn" class="hly-action-button" style="margin-top: 10px;">
|
||||
<i class="fas fa-plus"></i> 添加新规则
|
||||
</button>
|
||||
</div>
|
||||
<style>
|
||||
.hly-exclusion-rule-row { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
|
||||
.hly-exclusion-rule-row input { flex-grow: 1; }
|
||||
.hly-delete-rule-btn { background: #c0392b; color: white; border: none; border-radius: 50%; width: 24px; height: 24px; cursor: pointer; font-size: 16px; line-height: 24px; text-align: center; padding: 0; }
|
||||
</style>
|
||||
`;
|
||||
|
||||
showHtmlModal('编辑内容排除规则', modalHtml, {
|
||||
okText: '保存规则',
|
||||
onOk: (dialogElement) => {
|
||||
const newRules = [];
|
||||
dialogElement.find('.hly-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 });
|
||||
}
|
||||
});
|
||||
extension_settings[extensionName].historiographyExclusionRules = newRules;
|
||||
saveSettings();
|
||||
toastr.success('内容排除规则已保存。', '圣旨已达');
|
||||
},
|
||||
onShow: (dialogElement) => {
|
||||
const rulesList = dialogElement.find('#historiography-rules-list');
|
||||
|
||||
dialogElement.find('#historiography-add-rule-btn').on('click', () => {
|
||||
const newIndex = rulesList.children().length;
|
||||
const newRowHtml = createRuleRowHtml({ start: '', end: '' }, newIndex);
|
||||
rulesList.append(newRowHtml);
|
||||
});
|
||||
|
||||
rulesList.on('click', '.hly-delete-rule-btn', function() {
|
||||
$(this).closest('.hly-exclusion-rule-row').remove();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
629
ui/message-table-renderer.js
Normal file
629
ui/message-table-renderer.js
Normal file
@@ -0,0 +1,629 @@
|
||||
import { getMemoryState, getHighlights } from '../core/table-system/manager.js';
|
||||
import { extension_settings } from '/scripts/extensions.js';
|
||||
import { extensionName } from '../utils/settings.js';
|
||||
import { getContext } from '/scripts/extensions.js';
|
||||
|
||||
const TABLE_CONTAINER_ID = 'amily2-chat-table-container';
|
||||
const isTouchDevice = () => window.matchMedia('(pointer: coarse)').matches;
|
||||
|
||||
// 【V155.3】注入真正的游戏UI样式 (侧边栏+内容区)
|
||||
function injectChatTableStyles() {
|
||||
if (document.getElementById('amily2-chat-table-styles')) return;
|
||||
const style = document.createElement('style');
|
||||
style.id = 'amily2-chat-table-styles';
|
||||
style.textContent = `
|
||||
/* 主容器:游戏面板风格 */
|
||||
#amily2-chat-table-container {
|
||||
display: flex !important; /* 强制 Flex 布局 */
|
||||
flex-direction: row !important; /* 强制横向排列 */
|
||||
min-height: 300px;
|
||||
max-height: 80vh;
|
||||
background: rgba(12, 14, 20, 0.95);
|
||||
border: 2px solid #3a4a5e;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.8), inset 0 0 30px rgba(0, 0, 0, 0.5);
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
color: #c0c0c0;
|
||||
margin-top: 15px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
/* 装饰性角落 */
|
||||
#amily2-chat-table-container::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0; left: 0;
|
||||
width: 20px; height: 20px;
|
||||
border-top: 2px solid #00bfff;
|
||||
border-left: 2px solid #00bfff;
|
||||
border-radius: 6px 0 0 0;
|
||||
z-index: 2;
|
||||
}
|
||||
#amily2-chat-table-container::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0; right: 0;
|
||||
width: 20px; height: 20px;
|
||||
border-bottom: 2px solid #00bfff;
|
||||
border-right: 2px solid #00bfff;
|
||||
border-radius: 0 0 6px 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* 侧边栏:导航菜单 */
|
||||
.amily2-game-sidebar {
|
||||
width: 140px; /* 加宽以显示文字 */
|
||||
background: rgba(20, 25, 35, 0.9);
|
||||
border-right: 1px solid #3a4a5e;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch; /* 拉伸以填满宽度 */
|
||||
padding: 10px;
|
||||
gap: 8px;
|
||||
overflow-y: auto;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.amily2-game-tab {
|
||||
height: 40px;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
justify-content: flex-start; /* 左对齐 */
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
cursor: pointer;
|
||||
color: #7a8a9e;
|
||||
transition: all 0.2s ease;
|
||||
position: relative;
|
||||
border: 1px solid transparent;
|
||||
font-size: 0.9em;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.amily2-game-tab i {
|
||||
width: 24px;
|
||||
text-align: center;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.amily2-game-tab:hover {
|
||||
color: #e0e0e0;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.amily2-game-tab.active {
|
||||
color: #fff;
|
||||
background: linear-gradient(90deg, rgba(0, 191, 255, 0.25), transparent);
|
||||
border-left: 3px solid #00bfff;
|
||||
text-shadow: 0 0 8px rgba(0, 191, 255, 0.8);
|
||||
box-shadow: inset 5px 0 10px -5px rgba(0, 191, 255, 0.3);
|
||||
}
|
||||
|
||||
.amily2-game-tab.active::after {
|
||||
display: none; /* 移除原来的三角形指示器 */
|
||||
}
|
||||
|
||||
/* 内容区 */
|
||||
.amily2-game-content {
|
||||
position: absolute;
|
||||
left: 140px; top: 0; bottom: 0; right: 0;
|
||||
overflow: hidden;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* 扫描线效果 */
|
||||
.amily2-game-content::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.1) 50%);
|
||||
background-size: 100% 4px;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.amily2-game-panel {
|
||||
display: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
z-index: 10; /* 确保最高层级 */
|
||||
}
|
||||
|
||||
.amily2-game-panel.active {
|
||||
display: block !important;
|
||||
animation: amily2-panel-fade 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes amily2-panel-fade {
|
||||
from { opacity: 0; transform: translateY(5px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* 面板标题 */
|
||||
.amily2-panel-title {
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
color: #00bfff;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid rgba(0, 191, 255, 0.3);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.amily2-panel-title i {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
/* 表格布局 (RPG风格) */
|
||||
.amily2-table-wrapper {
|
||||
width: 100%;
|
||||
overflow-x: auto; /* 允许横向滚动 */
|
||||
-webkit-overflow-scrolling: touch;
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(100, 149, 237, 0.15);
|
||||
background: rgba(30, 35, 45, 0.4);
|
||||
}
|
||||
|
||||
.amily2-data-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.9em;
|
||||
white-space: nowrap; /* 防止换行,保持表格整洁 */
|
||||
}
|
||||
|
||||
.amily2-data-table th,
|
||||
.amily2-data-table td {
|
||||
padding: 10px 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.amily2-data-table th {
|
||||
background: rgba(20, 25, 35, 0.8);
|
||||
color: #00bfff;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
border-bottom: 2px solid rgba(0, 191, 255, 0.3);
|
||||
}
|
||||
|
||||
.amily2-data-table tr:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.amily2-data-table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.amily2-data-table td {
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
/* 第一列特殊样式 (通常是名称/ID) */
|
||||
.amily2-data-table td:first-child {
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
border-left: 3px solid transparent;
|
||||
}
|
||||
|
||||
.amily2-data-table tr:hover td:first-child {
|
||||
border-left-color: #00bfff;
|
||||
}
|
||||
|
||||
/* 滚动条 */
|
||||
.amily2-game-sidebar::-webkit-scrollbar,
|
||||
.amily2-game-panel::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
.amily2-game-sidebar::-webkit-scrollbar-track,
|
||||
.amily2-game-panel::-webkit-scrollbar-track {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.amily2-game-sidebar::-webkit-scrollbar-thumb,
|
||||
.amily2-game-panel::-webkit-scrollbar-thumb {
|
||||
background: #3a4a5e;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* 移动端适配 */
|
||||
@media (max-width: 768px) {
|
||||
#amily2-chat-table-container {
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
min-height: 400px;
|
||||
}
|
||||
.amily2-game-sidebar {
|
||||
width: 100% !important;
|
||||
height: 50px !important;
|
||||
flex-direction: row;
|
||||
padding: 0 10px;
|
||||
border-right: none;
|
||||
border-bottom: 1px solid #3a4a5e;
|
||||
overflow-x: auto;
|
||||
top: 30px !important;
|
||||
bottom: auto !important;
|
||||
}
|
||||
.amily2-game-content {
|
||||
left: 0 !important;
|
||||
top: 80px !important;
|
||||
}
|
||||
.amily2-game-tab {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.amily2-game-tab.active::after {
|
||||
right: auto;
|
||||
bottom: -8px;
|
||||
top: auto;
|
||||
left: 50%;
|
||||
transform: translateX(-50%) rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* 折叠功能样式 */
|
||||
#amily2-chat-table-container.collapsed {
|
||||
min-height: 30px !important;
|
||||
height: 30px !important;
|
||||
resize: none !important;
|
||||
overflow: hidden !important;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
#amily2-chat-table-container.collapsed .amily2-game-sidebar,
|
||||
#amily2-chat-table-container.collapsed .amily2-game-content {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.amily2-table-toggle {
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0;
|
||||
height: 30px;
|
||||
background: rgba(20, 25, 35, 0.95);
|
||||
border-bottom: 1px solid #3a4a5e;
|
||||
color: #00bfff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
z-index: 100;
|
||||
font-size: 0.85em;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.amily2-table-toggle:hover {
|
||||
background: rgba(40, 50, 70, 1);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.amily2-table-toggle i {
|
||||
margin-right: 8px;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
#amily2-chat-table-container:not(.collapsed) .amily2-table-toggle i {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
function getTableIcon(tableName) {
|
||||
const lowerName = tableName.toLowerCase();
|
||||
if (lowerName.includes('时空') || lowerName.includes('时间') || lowerName.includes('time') || lowerName.includes('clock')) return 'fa-clock';
|
||||
if (lowerName.includes('角色') || lowerName.includes('人物') || lowerName.includes('char') || lowerName.includes('person')) return 'fa-user';
|
||||
if (lowerName.includes('关系') || lowerName.includes('relation') || lowerName.includes('social')) return 'fa-users';
|
||||
if (lowerName.includes('任务') || lowerName.includes('目标') || lowerName.includes('quest') || lowerName.includes('mission')) return 'fa-tasks';
|
||||
if (lowerName.includes('物品') || lowerName.includes('道具') || lowerName.includes('item') || lowerName.includes('inventory')) return 'fa-box-open';
|
||||
if (lowerName.includes('技能') || lowerName.includes('能力') || lowerName.includes('skill') || lowerName.includes('ability')) return 'fa-bolt';
|
||||
if (lowerName.includes('设定') || lowerName.includes('世界') || lowerName.includes('setting') || lowerName.includes('world')) return 'fa-book';
|
||||
if (lowerName.includes('总结') || lowerName.includes('大纲') || lowerName.includes('summary') || lowerName.includes('outline')) return 'fa-file-alt';
|
||||
if (lowerName.includes('日志') || lowerName.includes('log') || lowerName.includes('record')) return 'fa-clipboard-list';
|
||||
return 'fa-table';
|
||||
}
|
||||
|
||||
function renderTablesToHtml(tables, highlights) {
|
||||
if (!tables || tables.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// 过滤掉空表格
|
||||
const activeTables = tables.map((t, i) => ({...t, originalIndex: i})).filter(t => t.rows && t.rows.length > 0);
|
||||
if (activeTables.length === 0) return '';
|
||||
|
||||
// Toggle 按钮
|
||||
const toggleHtml = `
|
||||
<div class="amily2-table-toggle" title="点击展开/收起">
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
<span>表格面板 / Data Panel</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 使用绝对定位强制布局,这是最稳妥的方式,不受 Flex 环境影响
|
||||
// top: 30px 留给 toggle 按钮
|
||||
let sidebarHtml = '<div class="amily2-game-sidebar" style="position: absolute; left: 0; top: 30px; bottom: 0; width: 140px; overflow-y: auto; border-right: 1px solid #3a4a5e;">';
|
||||
let contentHtml = '<div class="amily2-game-content" style="position: absolute; left: 140px; top: 30px; bottom: 0; right: 0; overflow: hidden;">';
|
||||
|
||||
activeTables.forEach((table, index) => {
|
||||
const isActive = index === 0 ? 'active' : '';
|
||||
const icon = getTableIcon(table.name);
|
||||
|
||||
// 侧边栏按钮 (现在包含文字)
|
||||
sidebarHtml += `
|
||||
<div class="amily2-game-tab ${isActive}" data-target="game-panel-${index}" title="${table.name}">
|
||||
<i class="fas ${icon}"></i>
|
||||
<span class="tab-text">${table.name}</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 内容面板 (表格渲染)
|
||||
let tableHtml = '';
|
||||
|
||||
// 构建表头
|
||||
const theadHtml = `
|
||||
<thead>
|
||||
<tr>
|
||||
${table.headers.map(header => `<th>${header}</th>`).join('')}
|
||||
</tr>
|
||||
</thead>
|
||||
`;
|
||||
|
||||
// 构建表体
|
||||
let tbodyHtml = '<tbody>';
|
||||
table.rows.forEach((row, rowIndex) => {
|
||||
const rowStatus = table.rowStatuses ? table.rowStatuses[rowIndex] : 'normal';
|
||||
if (rowStatus === 'pending-deletion') return;
|
||||
|
||||
tbodyHtml += '<tr>';
|
||||
row.forEach((cell, colIndex) => {
|
||||
const highlightKey = `${table.originalIndex}-${rowIndex}-${colIndex}`;
|
||||
const isHighlighted = highlights.has(highlightKey);
|
||||
const style = isHighlighted ? 'style="color: #00ff7f; font-weight: bold;"' : '';
|
||||
tbodyHtml += `<td ${style}>${cell}</td>`;
|
||||
});
|
||||
tbodyHtml += '</tr>';
|
||||
});
|
||||
tbodyHtml += '</tbody>';
|
||||
|
||||
tableHtml = `
|
||||
<div class="amily2-table-wrapper">
|
||||
<table class="amily2-data-table">
|
||||
${theadHtml}
|
||||
${tbodyHtml}
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
|
||||
contentHtml += `
|
||||
<div id="game-panel-${index}" class="amily2-game-panel ${isActive}">
|
||||
<div class="amily2-panel-title"><i class="fas ${icon}"></i> ${table.name}</div>
|
||||
${tableHtml}
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
sidebarHtml += '</div>';
|
||||
contentHtml += '</div>';
|
||||
|
||||
return `<div class="amily2-game-ui-wrapper">${toggleHtml}${sidebarHtml}${contentHtml}</div>`;
|
||||
}
|
||||
|
||||
function removeTableContainer() {
|
||||
const existingContainer = document.getElementById(TABLE_CONTAINER_ID);
|
||||
if (existingContainer) {
|
||||
existingContainer.remove();
|
||||
}
|
||||
}
|
||||
|
||||
function bindSwipePreventer(container) {
|
||||
if (!isTouchDevice()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let touchstartX = 0;
|
||||
let touchstartY = 0;
|
||||
|
||||
container.addEventListener('touchstart', function(event) {
|
||||
touchstartX = event.changedTouches[0].screenX;
|
||||
touchstartY = event.changedTouches[0].screenY;
|
||||
}, { passive: true });
|
||||
|
||||
container.addEventListener('touchmove', function(event) {
|
||||
const touchendX = event.changedTouches[0].screenX;
|
||||
const touchendY = event.changedTouches[0].screenY;
|
||||
|
||||
const deltaX = Math.abs(touchendX - touchstartX);
|
||||
const deltaY = Math.abs(touchendY - touchstartY);
|
||||
|
||||
if (deltaX > deltaY) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
}, { passive: false });
|
||||
}
|
||||
|
||||
export function updateOrInsertTableInChat() {
|
||||
injectChatTableStyles(); // 确保样式已注入
|
||||
|
||||
setTimeout(() => {
|
||||
const context = getContext();
|
||||
if (!context || !context.chat || context.chat.length < 2) {
|
||||
removeTableContainer();
|
||||
return;
|
||||
}
|
||||
|
||||
const settings = extension_settings[extensionName];
|
||||
removeTableContainer();
|
||||
|
||||
if (!settings || !settings.show_table_in_chat) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tables = getMemoryState();
|
||||
|
||||
if (!tables || tables.every(t => !t.rows || t.rows.length === 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const highlights = getHighlights();
|
||||
const htmlContent = renderTablesToHtml(tables, highlights);
|
||||
|
||||
if (!htmlContent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lastMessage = document.querySelector('.last_mes .mes_text');
|
||||
if (lastMessage) {
|
||||
const container = document.createElement('div');
|
||||
container.id = TABLE_CONTAINER_ID;
|
||||
|
||||
// 强制内联样式,使用相对定位作为绝对定位子元素的锚点
|
||||
container.style.cssText = `
|
||||
display: block !important; /* 不再依赖 Flex */
|
||||
min-height: 300px;
|
||||
max-height: 80vh;
|
||||
background: rgba(12, 14, 20, 0.95);
|
||||
border: 2px solid #3a4a5e;
|
||||
border-radius: 8px;
|
||||
margin-top: 15px;
|
||||
overflow: hidden;
|
||||
position: relative; /* 关键:作为定位锚点 */
|
||||
resize: vertical;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
container.innerHTML = htmlContent;
|
||||
container.classList.add('collapsed'); // 默认折叠
|
||||
|
||||
// On mobile devices, add a specific class to enable horizontal scrolling via CSS
|
||||
if (isTouchDevice()) {
|
||||
container.classList.add('mobile-table-view');
|
||||
container.style.flexDirection = 'column'; // 移动端保持垂直
|
||||
}
|
||||
|
||||
lastMessage.appendChild(container);
|
||||
bindSwipePreventer(container);
|
||||
|
||||
// 绑定鼠标滚轮横向滚动事件
|
||||
const tableWrappers = container.querySelectorAll('.amily2-table-wrapper');
|
||||
tableWrappers.forEach(wrapper => {
|
||||
wrapper.addEventListener('wheel', (e) => {
|
||||
if (e.deltaY !== 0) {
|
||||
e.preventDefault();
|
||||
wrapper.scrollLeft += e.deltaY;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 绑定折叠按钮事件
|
||||
const toggleBtn = container.querySelector('.amily2-table-toggle');
|
||||
if (toggleBtn) {
|
||||
toggleBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
container.classList.toggle('collapsed');
|
||||
});
|
||||
}
|
||||
|
||||
// 【V155.3】绑定游戏UI的交互事件
|
||||
const tabs = container.querySelectorAll('.amily2-game-tab');
|
||||
const panels = container.querySelectorAll('.amily2-game-panel');
|
||||
|
||||
tabs.forEach(tab => {
|
||||
tab.addEventListener('click', (e) => {
|
||||
e.stopPropagation(); // 防止触发消息点击
|
||||
|
||||
// 移除所有激活状态
|
||||
tabs.forEach(t => t.classList.remove('active'));
|
||||
panels.forEach(p => p.classList.remove('active'));
|
||||
|
||||
// 激活当前
|
||||
tab.classList.add('active');
|
||||
const targetId = tab.dataset.target;
|
||||
const targetPanel = container.querySelector(`#${targetId}`);
|
||||
if (targetPanel) targetPanel.classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
} else {
|
||||
console.warn('[Amily2] 未找到最后一条消息的容器,无法插入表格。');
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
|
||||
let chatObserver = null;
|
||||
const debouncedUpdate = debounce(updateOrInsertTableInChat, 100);
|
||||
|
||||
export function startContinuousRendering() {
|
||||
if (chatObserver) {
|
||||
console.log('[Amily2] Continuous rendering is already active.');
|
||||
return;
|
||||
}
|
||||
|
||||
const chatContainer = document.getElementById('chat');
|
||||
if (!chatContainer) {
|
||||
console.error('[Amily2] Could not find chat container to observe.');
|
||||
setTimeout(startContinuousRendering, 500);
|
||||
return;
|
||||
}
|
||||
|
||||
const observerConfig = { childList: true };
|
||||
|
||||
chatObserver = new MutationObserver((mutationsList, observer) => {
|
||||
for (const mutation of mutationsList) {
|
||||
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
|
||||
let messageAdded = false;
|
||||
mutation.addedNodes.forEach(node => {
|
||||
if (node.nodeType === 1 && node.classList.contains('mes')) {
|
||||
messageAdded = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (messageAdded) {
|
||||
debouncedUpdate();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
chatObserver.observe(chatContainer, observerConfig);
|
||||
console.log('[Amily2] Started continuous table rendering.');
|
||||
updateOrInsertTableInChat();
|
||||
}
|
||||
|
||||
export function stopContinuousRendering() {
|
||||
if (chatObserver) {
|
||||
chatObserver.disconnect();
|
||||
chatObserver = null;
|
||||
removeTableContainer();
|
||||
console.log('[Amily2] Stopped continuous table rendering.');
|
||||
}
|
||||
}
|
||||
356
ui/optimization-progress.js
Normal file
356
ui/optimization-progress.js
Normal file
@@ -0,0 +1,356 @@
|
||||
import { extensionName } from '../utils/settings.js';
|
||||
|
||||
let modalInstance = null;
|
||||
let trackState = {
|
||||
main: { step: 0, text: '准备就绪...', active: false, fillEl: null, textEl: null },
|
||||
concurrent: { step: 0, text: '等待启动...', active: false, fillEl: null, textEl: null }
|
||||
};
|
||||
const totalEstimatedSteps = 5;
|
||||
|
||||
let messageQueues = {
|
||||
main: [],
|
||||
concurrent: []
|
||||
};
|
||||
let isProcessingQueues = {
|
||||
main: false,
|
||||
concurrent: false
|
||||
};
|
||||
const MIN_DISPLAY_TIME = 800;
|
||||
|
||||
function createModalHtml() {
|
||||
return `
|
||||
<div id="amily2-progress-bar-container">
|
||||
<!-- 主模型轨道 (LLM-A) -->
|
||||
<div class="progress-track-container" id="amily2-track-main">
|
||||
<div class="progress-header">
|
||||
<div class="progress-status">
|
||||
<i class="fas fa-brain" style="color: #9e8aff;"></i>
|
||||
<span class="track-label">主意识</span>
|
||||
<span class="track-text" id="amily2-text-main">准备就绪...</span>
|
||||
</div>
|
||||
<button id="amily2-progress-cancel" title="中止任务">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="progress-track">
|
||||
<div class="progress-fill" id="amily2-fill-main" style="width: 0%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 并发模型轨道 (LLM-B) - 初始隐藏 -->
|
||||
<div class="progress-track-container" id="amily2-track-concurrent" style="display: none; margin-top: 12px; border-top: 1px solid rgba(255,255,255,0.1); padding-top: 12px;">
|
||||
<div class="progress-header">
|
||||
<div class="progress-status">
|
||||
<i class="fas fa-project-diagram" style="color: #6495ed;"></i>
|
||||
<span class="track-label">潜意识</span>
|
||||
<span class="track-text" id="amily2-text-concurrent">等待启动...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress-track">
|
||||
<div class="progress-fill" id="amily2-fill-concurrent" style="width: 0%; background: linear-gradient(90deg, #6495ed, #00d2ff);"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function addStyling() {
|
||||
const styleId = 'amily2-progress-modal-style';
|
||||
if (document.getElementById(styleId)) return;
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.id = styleId;
|
||||
style.innerHTML = `
|
||||
#amily2-progress-bar-container {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 420px;
|
||||
max-width: 90vw;
|
||||
background: rgba(30, 30, 40, 0.85);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 12px;
|
||||
padding: 12px 16px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
||||
z-index: 2147483647 !important;
|
||||
font-family: "Segoe UI", "Microsoft YaHei", sans-serif;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
opacity: 0;
|
||||
transform: translate(-50%, -20px); /* 初始位置偏上,用于入场动画 */
|
||||
}
|
||||
|
||||
#amily2-progress-bar-container.visible {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, 0);
|
||||
}
|
||||
|
||||
.progress-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.progress-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: var(--smart-theme-body-color, #eee);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.track-label {
|
||||
font-size: 11px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
background: rgba(255,255,255,0.1);
|
||||
color: rgba(255,255,255,0.8);
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.track-text {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
#amily2-progress-cancel {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
border-radius: 50%;
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
#amily2-progress-cancel:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
color: #ff6b6b;
|
||||
}
|
||||
|
||||
.progress-track {
|
||||
height: 4px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--smart-theme-color, #9e8aff), #b3a4ff);
|
||||
border-radius: 2px;
|
||||
transition: width 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 0 10px rgba(158, 138, 255, 0.4);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 进度条光效动画 */
|
||||
.progress-fill::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
rgba(255, 255, 255, 0.4),
|
||||
transparent
|
||||
);
|
||||
transform: translateX(-100%);
|
||||
animation: shimmer 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
100% {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
export function showPlotOptimizationProgress(cancellationState) {
|
||||
if (modalInstance) {
|
||||
hidePlotOptimizationProgress();
|
||||
}
|
||||
|
||||
addStyling();
|
||||
document.body.insertAdjacentHTML('beforeend', createModalHtml());
|
||||
|
||||
modalInstance = document.getElementById('amily2-progress-bar-container');
|
||||
|
||||
trackState.main.fillEl = document.getElementById('amily2-fill-main');
|
||||
trackState.main.textEl = document.getElementById('amily2-text-main');
|
||||
trackState.main.step = 0;
|
||||
trackState.main.active = true;
|
||||
|
||||
trackState.concurrent.fillEl = document.getElementById('amily2-fill-concurrent');
|
||||
trackState.concurrent.textEl = document.getElementById('amily2-text-concurrent');
|
||||
trackState.concurrent.step = 0;
|
||||
trackState.concurrent.active = false;
|
||||
|
||||
const cancelButton = document.getElementById('amily2-progress-cancel');
|
||||
|
||||
messageQueues = {
|
||||
main: [],
|
||||
concurrent: []
|
||||
};
|
||||
isProcessingQueues = {
|
||||
main: false,
|
||||
concurrent: false
|
||||
};
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
if (modalInstance) {
|
||||
modalInstance.classList.add('visible');
|
||||
}
|
||||
});
|
||||
|
||||
if (cancellationState && cancelButton) {
|
||||
cancelButton.addEventListener('click', () => {
|
||||
cancellationState.isCancelled = true;
|
||||
if (trackState.main.textEl) trackState.main.textEl.textContent = "正在中止任务...";
|
||||
if (trackState.main.fillEl) trackState.main.fillEl.style.backgroundColor = "#ff6b6b";
|
||||
toastr.info("记忆管理任务已请求中止。");
|
||||
setTimeout(hidePlotOptimizationProgress, 800);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function updatePlotOptimizationProgress(message, isDone = false, isSkipped = false) {
|
||||
if (message.includes('记忆重构完成') || message.includes('所有任务已完成')) {
|
||||
messageQueues.main = [];
|
||||
messageQueues.concurrent = [];
|
||||
performUpdate(message, isDone, isSkipped);
|
||||
setTimeout(hidePlotOptimizationProgress, 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
const isConcurrent = message.includes('(LLM-B)') || message.includes('(并发模型)');
|
||||
const queueType = isConcurrent ? 'concurrent' : 'main';
|
||||
|
||||
messageQueues[queueType].push({ message, isDone, isSkipped });
|
||||
|
||||
processQueue(queueType);
|
||||
}
|
||||
|
||||
async function processQueue(queueType) {
|
||||
if (isProcessingQueues[queueType] || messageQueues[queueType].length === 0) return;
|
||||
|
||||
isProcessingQueues[queueType] = true;
|
||||
|
||||
while (messageQueues[queueType].length > 0) {
|
||||
const { message, isDone, isSkipped } = messageQueues[queueType].shift();
|
||||
|
||||
performUpdate(message, isDone, isSkipped);
|
||||
const isLongRunningTaskStart = message.includes('请求') && !isDone && !isSkipped;
|
||||
|
||||
if (!isLongRunningTaskStart) {
|
||||
await new Promise(resolve => setTimeout(resolve, MIN_DISPLAY_TIME));
|
||||
} else {
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
}
|
||||
}
|
||||
|
||||
isProcessingQueues[queueType] = false;
|
||||
}
|
||||
|
||||
function performUpdate(message, isDone, isSkipped) {
|
||||
if (!modalInstance) return;
|
||||
|
||||
if (message === '初始化任务...' || message === '所有任务已完成') {
|
||||
if (trackState.main.textEl) trackState.main.textEl.textContent = message;
|
||||
return;
|
||||
}
|
||||
|
||||
const isConcurrent = message.includes('(LLM-B)') || message.includes('(并发模型)');
|
||||
const track = isConcurrent ? trackState.concurrent : trackState.main;
|
||||
const trackId = isConcurrent ? 'amily2-track-concurrent' : 'amily2-track-main';
|
||||
|
||||
if (isConcurrent && !track.active) {
|
||||
track.active = true;
|
||||
const trackEl = document.getElementById(trackId);
|
||||
if (trackEl) trackEl.style.display = 'block';
|
||||
}
|
||||
const cleanMessage = message.replace(/\(LLM-[AB]\)|\(主模型\)|\(并发模型\)/g, '').trim();
|
||||
|
||||
if (isDone || isSkipped) {
|
||||
track.step++;
|
||||
let percentage = Math.min((track.step / totalEstimatedSteps) * 100, 95);
|
||||
|
||||
if (message.includes('记忆重构完成') || message.includes('所有任务已完成')) {
|
||||
percentage = 100;
|
||||
if (trackState.concurrent.active && trackState.concurrent.fillEl) {
|
||||
trackState.concurrent.fillEl.style.width = '100%';
|
||||
if (trackState.concurrent.textEl) trackState.concurrent.textEl.textContent = '同步完成 ✅';
|
||||
}
|
||||
}
|
||||
|
||||
// 特殊处理:并发模型的最后一步
|
||||
if (isConcurrent && (message.includes('深度逻辑推演') || message.includes('计算情感最优解'))) {
|
||||
percentage = 100;
|
||||
}
|
||||
|
||||
// 特殊处理:主模型的最后一步(在记忆重构之前)
|
||||
if (!isConcurrent && (message.includes('核心意识同步') || message.includes('等待灵魂共鸣'))) {
|
||||
percentage = 100;
|
||||
}
|
||||
|
||||
if (track.fillEl) {
|
||||
track.fillEl.style.width = `${percentage}%`;
|
||||
}
|
||||
|
||||
if (track.textEl) {
|
||||
track.textEl.textContent = `${cleanMessage} ${isSkipped ? '⚪' : '✅'}`;
|
||||
}
|
||||
} else {
|
||||
if (track.textEl) {
|
||||
track.textEl.textContent = cleanMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function hidePlotOptimizationProgress() {
|
||||
// 重置消息队列
|
||||
messageQueues = {
|
||||
main: [],
|
||||
concurrent: []
|
||||
};
|
||||
isProcessingQueues = {
|
||||
main: false,
|
||||
concurrent: false
|
||||
};
|
||||
|
||||
if (modalInstance) {
|
||||
modalInstance.classList.remove('visible');
|
||||
setTimeout(() => {
|
||||
if (modalInstance) {
|
||||
modalInstance.remove();
|
||||
modalInstance = null;
|
||||
// 清理引用
|
||||
trackState.main.fillEl = null;
|
||||
trackState.main.textEl = null;
|
||||
trackState.concurrent.fillEl = null;
|
||||
trackState.concurrent.textEl = null;
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
169
ui/page-window.js
Normal file
169
ui/page-window.js
Normal file
@@ -0,0 +1,169 @@
|
||||
import { messageFormatting } from '/script.js';
|
||||
|
||||
function loadShowdown() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (window.showdown) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/showdown/2.1.0/showdown.min.js';
|
||||
script.onload = resolve;
|
||||
script.onerror = reject;
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export async function showContentModal(title, contentUrl) {
|
||||
try {
|
||||
|
||||
await loadShowdown();
|
||||
|
||||
const markdownContent = await $.get(contentUrl);
|
||||
|
||||
const converter = new showdown.Converter({
|
||||
tables: true,
|
||||
strikethrough: true,
|
||||
ghCodeBlocks: true
|
||||
});
|
||||
const htmlContent = converter.makeHtml(markdownContent);
|
||||
|
||||
const dialogHtml = `
|
||||
<dialog class="popup wide_dialogue_popup">
|
||||
<div class="popup-body">
|
||||
<h3 style="margin-top:0; color: #eee; border-bottom: 1px solid rgba(255,255,255,0.2); padding-bottom: 10px;">
|
||||
<i class="fas fa-book-open" style="color: #58a6ff;"></i> ${title}
|
||||
</h3>
|
||||
<div class="popup-content" style="height: 60vh; overflow-y: auto; background: rgba(0,0,0,0.2); padding: 15px; border-radius: 5px;">
|
||||
<div class="mes_text">${htmlContent}</div>
|
||||
</div>
|
||||
<div class="popup-controls"><div class="popup-button-ok menu_button menu_button_primary interactable">朕已阅</div></div>
|
||||
</div>
|
||||
</dialog>`;
|
||||
|
||||
const dialogElement = $(dialogHtml).appendTo('body');
|
||||
const closeDialog = () => {
|
||||
dialogElement[0].close();
|
||||
dialogElement.remove();
|
||||
};
|
||||
dialogElement.find('.popup-button-ok').on('click', closeDialog);
|
||||
dialogElement[0].showModal();
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[Amily-翰林院] 紧急报告:加载教程内容 [${title}] 时发生意外:`, error);
|
||||
toastr.error(`无法加载教程: ${error.message}`, "翰林院回报");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function showHtmlModal(title, htmlContent, options = {}) {
|
||||
const {
|
||||
okText = '确认',
|
||||
cancelText = '取消',
|
||||
onOk,
|
||||
onCancel,
|
||||
onShow,
|
||||
showCancel = true,
|
||||
} = options;
|
||||
|
||||
const buttonsHtml = `
|
||||
${showCancel ? `<button class="popup-button-cancel menu_button secondary interactable">${cancelText}</button>` : ''}
|
||||
<button class="popup-button-ok menu_button menu_button_primary interactable">${okText}</button>
|
||||
`;
|
||||
|
||||
const dialogHtml = `
|
||||
<dialog class="popup wide_dialogue_popup">
|
||||
<div class="popup-body">
|
||||
<h3 style="margin-top:0; color: #eee; border-bottom: 1px solid rgba(255,255,255,0.2); padding-bottom: 10px;">
|
||||
<i class="fas fa-edit" style="color: #58a6ff;"></i> ${title}
|
||||
</h3>
|
||||
<div class="popup-content" style="height: 60vh; overflow-y: auto; background: rgba(0,0,0,0.2); padding: 15px; border-radius: 5px;">
|
||||
${htmlContent}
|
||||
</div>
|
||||
<div class="popup-controls" style="display: flex; justify-content: flex-end; gap: 10px;">${buttonsHtml}</div>
|
||||
</div>
|
||||
</dialog>`;
|
||||
|
||||
const dialogElement = $(dialogHtml).appendTo('body');
|
||||
|
||||
const closeDialog = () => {
|
||||
dialogElement[0].close();
|
||||
dialogElement.remove();
|
||||
};
|
||||
|
||||
dialogElement.find('.popup-button-ok').on('click', () => {
|
||||
if (onOk) {
|
||||
const shouldClose = onOk(dialogElement);
|
||||
if (shouldClose !== false) {
|
||||
closeDialog();
|
||||
}
|
||||
} else {
|
||||
closeDialog();
|
||||
}
|
||||
});
|
||||
|
||||
if (showCancel) {
|
||||
dialogElement.find('.popup-button-cancel').on('click', () => {
|
||||
if (onCancel) {
|
||||
onCancel();
|
||||
}
|
||||
closeDialog();
|
||||
});
|
||||
}
|
||||
|
||||
dialogElement[0].showModal();
|
||||
if (onShow) {
|
||||
onShow(dialogElement);
|
||||
}
|
||||
return dialogElement;
|
||||
}
|
||||
|
||||
|
||||
function escapeHtml(text) {
|
||||
if (!text) return '';
|
||||
return text
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
export function showSummaryModal(summaryText, callbacks) {
|
||||
const { onConfirm, onRegenerate, onCancel } = callbacks;
|
||||
|
||||
const modalHtml = `
|
||||
<div class="historiographer-summary-modal">
|
||||
<textarea class="text_pole" style="width: 100%; height: 50vh; resize: vertical;">${escapeHtml(summaryText)}</textarea>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const dialogElement = showHtmlModal('预览与修订', modalHtml, {
|
||||
okText: '确认写入',
|
||||
cancelText: '取消写入',
|
||||
showCancel: true,
|
||||
onOk: (dialog) => {
|
||||
const editedText = dialog.find('textarea').val();
|
||||
if (onConfirm) {
|
||||
onConfirm(editedText);
|
||||
}
|
||||
|
||||
},
|
||||
onCancel: () => {
|
||||
if (onCancel) {
|
||||
onCancel();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const regenerateButton = $('<button class="menu_button secondary interactable" style="margin-right: auto;">重新生成</button>');
|
||||
regenerateButton.on('click', () => {
|
||||
if (onRegenerate) {
|
||||
dialogElement[0].close();
|
||||
onRegenerate(dialogElement);
|
||||
}
|
||||
});
|
||||
|
||||
dialogElement.find('.popup-controls').prepend(regenerateButton);
|
||||
}
|
||||
239
ui/state.js
Normal file
239
ui/state.js
Normal file
@@ -0,0 +1,239 @@
|
||||
import { extension_settings } from "/scripts/extensions.js";
|
||||
import { characters, this_chid } from '/script.js';
|
||||
import { extensionName, defaultSettings } from "../utils/settings.js";
|
||||
import { pluginAuthStatus } from "../utils/auth.js";
|
||||
|
||||
|
||||
|
||||
let availableModels = [];
|
||||
let latestUpdateInfo = null;
|
||||
let newVersionAvailable = false;
|
||||
|
||||
export function setUpdateInfo(isNew, updateInfo) {
|
||||
newVersionAvailable = isNew;
|
||||
latestUpdateInfo = updateInfo;
|
||||
}
|
||||
|
||||
|
||||
export function applyUpdateIndicator() {
|
||||
if (newVersionAvailable) {
|
||||
$('#amily2_update_indicator').show();
|
||||
$('#amily2_update_button_new').show();
|
||||
} else {
|
||||
$('#amily2_update_indicator').hide();
|
||||
$('#amily2_update_button_new').hide();
|
||||
}
|
||||
}
|
||||
|
||||
export function getLatestUpdateInfo() {
|
||||
return latestUpdateInfo;
|
||||
}
|
||||
|
||||
export function setAvailableModels(models) {
|
||||
availableModels = models;
|
||||
}
|
||||
|
||||
|
||||
export function populateModelDropdown() {
|
||||
const modelSelect = $("#amily2_model");
|
||||
const modelNotes = $("#amily2_model_notes");
|
||||
|
||||
modelSelect.empty();
|
||||
const currentModel = extension_settings[extensionName]?.model || "";
|
||||
|
||||
if (availableModels.length === 0) {
|
||||
modelSelect.append('<option value="">无可用模型,请刷新</option>');
|
||||
modelNotes.html(
|
||||
'<span style="color: #ff9800;">请检查API配置后点击"刷新模型"</span>',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultOption = $("<option></option>").val("").text("-- 选择模型 --");
|
||||
modelSelect.append(defaultOption);
|
||||
|
||||
availableModels.forEach((model) => {
|
||||
const option = $("<option></option>").val(model).text(model);
|
||||
if (model === currentModel) {
|
||||
option.attr("selected", "selected");
|
||||
}
|
||||
modelSelect.append(option);
|
||||
});
|
||||
|
||||
if (currentModel && modelSelect.val() === currentModel) {
|
||||
modelNotes.html(`已选择: <strong>${currentModel}</strong>`);
|
||||
} else {
|
||||
modelNotes.html(`已加载 ${availableModels.length} 个可用模型`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function updateUI() {
|
||||
if (!pluginAuthStatus.authorized) {
|
||||
$("#auth_panel").show();
|
||||
$(".plugin-features").hide();
|
||||
} else {
|
||||
$("#auth_panel").hide();
|
||||
$(".plugin-features").show();
|
||||
|
||||
const settings = extension_settings[extensionName];
|
||||
if (!settings) return;
|
||||
|
||||
$("#amily2_api_provider").val(settings.apiProvider || 'openai');
|
||||
$("#amily2_api_url").val(settings.apiUrl);
|
||||
$("#amily2_api_url").attr('type', 'text');
|
||||
$("#amily2_api_key").val(settings.apiKey);
|
||||
$("#amily2_model").val(settings.model);
|
||||
$("#amily2_preset_selector").val(settings.tavernProfile);
|
||||
|
||||
$("#amily2_api_provider").trigger('change');
|
||||
|
||||
|
||||
$("#amily2_max_tokens").val(settings.maxTokens);
|
||||
$("#amily2_max_tokens_value").text(settings.maxTokens);
|
||||
$("#amily2_temperature").val(settings.temperature);
|
||||
$("#amily2_temperature_value").text(settings.temperature);
|
||||
$("#amily2_context_messages").val(settings.contextMessages);
|
||||
$("#amily2_context_messages_value").text(settings.contextMessages);
|
||||
$("#amily2_optimization_target_tag").val(settings.optimizationTargetTag);
|
||||
|
||||
|
||||
$("#amily2_optimization_enabled").prop(
|
||||
"checked",
|
||||
settings.optimizationEnabled,
|
||||
);
|
||||
$("#amily2_optimization_exclusion_enabled").prop(
|
||||
"checked",
|
||||
settings.optimizationExclusionEnabled,
|
||||
);
|
||||
$("#amily2_show_optimization_toast").prop(
|
||||
"checked",
|
||||
settings.showOptimizationToast,
|
||||
);
|
||||
$("#amily2_suppress_toast").prop("checked", settings.suppressToast);
|
||||
|
||||
|
||||
$("#amily2_system_prompt").val(settings.systemPrompt);
|
||||
$("#amily2_main_prompt").val(settings.mainPrompt);
|
||||
$("#amily2_output_format_prompt").val(settings.outputFormatPrompt);
|
||||
$("#amily2_summarization_prompt").val(settings.summarizationPrompt);
|
||||
|
||||
|
||||
$("#amily2_summarization_enabled").prop(
|
||||
"checked",
|
||||
settings.summarizationEnabled,
|
||||
);
|
||||
$(
|
||||
`input[name="amily2_lorebook_target"][value="${settings.lorebookTarget}"]`,
|
||||
).prop("checked", true);
|
||||
|
||||
$(`input[name="amily2_icon_location"][value="${settings.iconLocation}"]`).prop("checked", true);
|
||||
$("#amily2_auto_hide_enabled").prop("checked", settings.autoHideEnabled);
|
||||
$("#amily2_auto_hide_summarized_enabled").prop("checked", settings.autoHideSummarizedEnabled);
|
||||
$("#amily2_auto_hide_threshold").val(settings.autoHideThreshold);
|
||||
$("#amily2_auto_hide_threshold_value").text(settings.autoHideThreshold);
|
||||
$('#amily2_lore_activation_mode').val(settings.loreActivationMode);
|
||||
$('#amily2_lore_insertion_position').val(settings.loreInsertionPosition);
|
||||
$('#amily2_lore_depth_input').val(settings.loreDepth);
|
||||
if (settings.loreInsertionPosition === 'at_depth') {
|
||||
$('#amily2_lore_depth_container').show();
|
||||
} else {
|
||||
$('#amily2_lore_depth_container').hide();
|
||||
}
|
||||
if (settings.historiographySmallAutoEnable !== undefined) {
|
||||
$('#amily2_mhb_small_auto_enabled').prop('checked', settings.historiographySmallAutoEnable);
|
||||
}
|
||||
if (settings.historiographySmallTriggerThreshold !== undefined) {
|
||||
$('#amily2_mhb_small_trigger_count').val(settings.historiographySmallTriggerThreshold);
|
||||
}
|
||||
// 同步渲染器开关状态
|
||||
if (settings.amily_render_enabled !== undefined) {
|
||||
$('#amily-render-enable-toggle').prop('checked', settings.amily_render_enabled);
|
||||
}
|
||||
|
||||
// 同步渲染深度设置
|
||||
if (settings.render_depth !== undefined) {
|
||||
$('#render-depth').val(settings.render_depth);
|
||||
}
|
||||
|
||||
populateModelDropdown();
|
||||
updatePlotOptimizationUI();
|
||||
|
||||
// Restore collapsible sections state
|
||||
$('.collapsible').each(function() {
|
||||
const section = $(this);
|
||||
const legend = section.find('.collapsible-legend');
|
||||
const content = section.find('.collapsible-content');
|
||||
const icon = legend.find('.collapse-icon');
|
||||
const sectionId = legend.text().trim();
|
||||
const isCollapsed = extension_settings[extensionName][`collapsible_${sectionId}_collapsed`] ?? true;
|
||||
|
||||
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');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// =====================================================================
|
||||
// ======== 【剧情优化】 - UI状态管理 ========
|
||||
// =====================================================================
|
||||
|
||||
function getMergedPlotOptSettings() {
|
||||
const character = (characters && typeof this_chid !== 'undefined' && characters[this_chid]) ? characters[this_chid] : null;
|
||||
const globalSettings = extension_settings[extensionName] || defaultSettings;
|
||||
const characterSettings = character?.data?.extensions?.[extensionName] || {};
|
||||
|
||||
return { ...globalSettings, ...characterSettings };
|
||||
}
|
||||
|
||||
|
||||
export function updatePlotOptimizationUI() {
|
||||
const settings = getMergedPlotOptSettings();
|
||||
if (!settings) return;
|
||||
|
||||
$('#amily2_opt_enabled').prop('checked', settings.plotOpt_enabled);
|
||||
$('#amily2_opt_ejs_enabled').prop('checked', settings.plotOpt_ejsEnabled);
|
||||
$('#amily2_opt_worldbook_enabled').prop('checked', settings.plotOpt_worldbook_enabled);
|
||||
$('#amily2_opt_table_enabled').prop('checked', settings.plotOpt_tableEnabled);
|
||||
|
||||
$('#amily2_opt_main_prompt').val(settings.plotOpt_mainPrompt);
|
||||
$('#amily2_opt_system_prompt').val(settings.plotOpt_systemPrompt);
|
||||
$('#amily2_opt_final_system_directive').val(settings.plotOpt_finalSystemDirective);
|
||||
|
||||
$('#amily2_opt_rate_main').val(settings.plotOpt_rateMain);
|
||||
$('#amily2_opt_rate_personal').val(settings.plotOpt_ratePersonal);
|
||||
$('#amily2_opt_rate_erotic').val(settings.plotOpt_rateErotic);
|
||||
$('#amily2_opt_rate_cuckold').val(settings.plotOpt_rateCuckold);
|
||||
|
||||
const sliders = {
|
||||
'#amily2_opt_context_limit': 'plotOpt_contextLimit',
|
||||
'#amily2_opt_worldbook_char_limit': 'plotOpt_worldbookCharLimit',
|
||||
};
|
||||
|
||||
for (const sliderId in sliders) {
|
||||
const key = sliders[sliderId];
|
||||
const value = settings[key];
|
||||
const valueDisplayId = `${sliderId}_value`;
|
||||
|
||||
if (value !== undefined) {
|
||||
$(sliderId).val(value);
|
||||
$(valueDisplayId).text(value);
|
||||
}
|
||||
}
|
||||
|
||||
const worldbookSource = settings.plotOpt_worldbookSource || 'character';
|
||||
$(`input[name="amily2_opt_worldbook_source"][value="${worldbookSource}"]`).prop('checked', true);
|
||||
|
||||
const lastUsedPresetName = settings.plotOpt_lastUsedPresetName;
|
||||
if (lastUsedPresetName && $('#amily2_opt_prompt_preset_select option[value="' + lastUsedPresetName + '"]').length > 0) {
|
||||
$('#amily2_opt_prompt_preset_select').val(lastUsedPresetName);
|
||||
}
|
||||
|
||||
console.log(`[${extensionName}] (state.js) 剧情优化UI已根据合并设置更新。`);
|
||||
}
|
||||
2301
ui/table-bindings.js
Normal file
2301
ui/table-bindings.js
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user