勾选要导入的世界书,插件将自动检测并处理这些世界书
`;
document.body.appendChild(modal);
// 绑定事件
document
.getElementById("mm-selector-close")
.addEventListener("click", hideWorldBookSelector);
document
.getElementById("mm-selector-cancel")
.addEventListener("click", hideWorldBookSelector);
document
.getElementById("mm-selector-confirm")
.addEventListener("click", confirmImportWorldBooks);
}
async function showWorldBookSelector() {
createWorldBookSelectorModal();
const modal = document.getElementById("mm-worldbook-selector-modal");
const listContainer = document.getElementById("mm-selector-list");
// 应用当前主题
const settings = getGlobalSettings();
const theme = settings.theme || "default";
if (theme !== "default") {
modal.setAttribute("data-mm-theme", theme);
if (theme.startsWith('starry-')) {
createStarsLayer(modal);
}
}
modal.classList.add("mm-modal-visible");
listContainer.innerHTML =
'';
html += '
总结世界书
';
for (const book of summaryBooks) {
const bookConfig = config?.summaryConfigs?.[book.name];
const hasConfig = !!bookConfig;
const eventsCount = bookConfig?.maxHistoryEvents || 15;
const relevanceThreshold = bookConfig?.relevanceThreshold || 0.6;
const apiModel = bookConfig?.model || "未配置";
const entryCount = book.entries
? Object.keys(book.entries).length
: 0;
const statusClass = hasConfig ? "mm-chip-ok" : "mm-chip-warning";
html += `
${book.name}
${entryCount}
`;
}
html += "
";
}
// 未识别类型的世界书
if (unknownBooks.length > 0) {
html += '';
html += '
未识别的世界书
';
for (const book of unknownBooks) {
const entryCount = book.entries
? Object.keys(book.entries).length
: 0;
// SillyTavern 使用 disable 字段,disable !== true 表示启用
const enabledCount = book.entries
? Object.values(book.entries).filter((e) => e.disable !== true)
.length
: 0;
html += `
${book.name}
条目
${entryCount}
启用
${enabledCount}
无法识别类型。请确保条目的 comment 字段包含【分类名】格式
`;
}
html += "
";
}
listContainer.innerHTML = html;
} catch (error) {
Logger.error("刷新世界书列表失败:", error);
listContainer.innerHTML = `
${sources.map((source) => `
${SOURCE_LABELS[source] || source}
`).join("")}
`;
const header = card.querySelector(".mm-collapse-header");
header.addEventListener("click", () => {
card.classList.toggle("expanded");
const arrow = card.querySelector(".mm-collapse-arrow");
if (arrow) {
arrow.classList.toggle("fa-chevron-up", card.classList.contains("expanded"));
arrow.classList.toggle("fa-chevron-down", !card.classList.contains("expanded"));
}
});
container.appendChild(card);
initFlowSourceDrag(card.querySelector(".mm-flow-source-list"));
});
}
function initFlowSourceDrag(listContainer) {
if (!listContainer) return;
let draggedItem = null;
listContainer.querySelectorAll(".mm-flow-source-item").forEach((item) => {
item.addEventListener("dragstart", (e) => {
draggedItem = item;
item.classList.add("mm-dragging");
e.dataTransfer.effectAllowed = "move";
});
item.addEventListener("dragend", () => {
item.classList.remove("mm-dragging");
draggedItem = null;
listContainer.querySelectorAll(".mm-flow-source-item").forEach((i) => {
i.classList.remove("mm-drag-over-top", "mm-drag-over-bottom");
});
// 拖拽结束后自动保存
autoSaveFlowConfig();
});
item.addEventListener("dragover", (e) => {
e.preventDefault();
if (!draggedItem || draggedItem === item) return;
const rect = item.getBoundingClientRect();
const midY = rect.top + rect.height / 2;
item.classList.remove("mm-drag-over-top", "mm-drag-over-bottom");
item.classList.add(e.clientY < midY ? "mm-drag-over-top" : "mm-drag-over-bottom");
});
item.addEventListener("dragleave", () => {
item.classList.remove("mm-drag-over-top", "mm-drag-over-bottom");
});
item.addEventListener("drop", (e) => {
e.preventDefault();
if (!draggedItem || draggedItem === item) return;
const rect = item.getBoundingClientRect();
if (e.clientY < rect.top + rect.height / 2) {
listContainer.insertBefore(draggedItem, item);
} else {
listContainer.insertBefore(draggedItem, item.nextSibling);
}
item.classList.remove("mm-drag-over-top", "mm-drag-over-bottom");
});
});
}
// 自动保存流程配置(静默保存,不关闭弹窗)
function autoSaveFlowConfig() {
const container = document.getElementById("mm-flow-config-list");
if (!container) return;
const newOrder = {};
container.querySelectorAll(".mm-flow-source-list").forEach((list) => {
const category = list.dataset.category;
const sources = [];
list.querySelectorAll(".mm-flow-source-item").forEach((item) => {
sources.push(item.dataset.source);
});
if (sources.length > 0) {
newOrder[category] = sources;
}
});
const settings = getGlobalSettings();
settings.promptPartsOrder = newOrder;
saveGlobalSettings(settings);
Logger.debug("[流程配置] 已自动保存来源排序配置");
}
function saveFlowConfig() {
const container = document.getElementById("mm-flow-config-list");
if (!container) return;
const newOrder = {};
container.querySelectorAll(".mm-flow-source-list").forEach((list) => {
const category = list.dataset.category;
const sources = [];
list.querySelectorAll(".mm-flow-source-item").forEach((item) => {
sources.push(item.dataset.source);
});
if (sources.length > 0) {
newOrder[category] = sources;
}
});
const settings = getGlobalSettings();
settings.promptPartsOrder = newOrder;
saveGlobalSettings(settings);
Logger.log("[流程配置] 已保存来源排序配置", newOrder);
hideFlowConfigModal();
}
function resetFlowConfig() {
if (!confirm("确定要恢复默认流程配置吗?这将清除所有自定义排序。")) return;
const settings = getGlobalSettings();
settings.promptPartsOrder = {};
saveGlobalSettings(settings);
Logger.log("[流程配置] 已恢复默认流程配置");
renderFlowConfigList();
}
// 流程配置弹窗拖拽缩放功能
function initFlowConfigResize() {
const modal = document.getElementById("mm-flow-config-modal");
const resizeHandle = document.getElementById("mm-flow-config-resize");
if (!modal || !resizeHandle) return;
const modalContent = modal.querySelector(".mm-flow-config-modal-content");
if (!modalContent) return;
let isResizing = false;
let startY = 0;
let startHeight = 0;
function handleResizeStart(e) {
isResizing = true;
// 支持触摸事件
startY = e.touches ? e.touches[0].clientY : e.clientY;
// 获取当前计算后的高度
startHeight = modalContent.getBoundingClientRect().height;
document.body.style.cursor = "ns-resize";
document.body.style.userSelect = "none";
e.preventDefault();
}
function handleResizeMove(e) {
if (!isResizing) return;
// 支持触摸事件
const clientY = e.touches ? e.touches[0].clientY : e.clientY;
const deltaY = clientY - startY;
const newHeight = Math.max(300, Math.min(startHeight + deltaY, window.innerHeight * 0.9));
modalContent.style.height = `${newHeight}px`;
e.preventDefault();
}
function handleResizeEnd() {
if (isResizing) {
isResizing = false;
document.body.style.cursor = "";
document.body.style.userSelect = "";
}
}
// 鼠标事件
resizeHandle.addEventListener("mousedown", handleResizeStart);
document.addEventListener("mousemove", handleResizeMove);
document.addEventListener("mouseup", handleResizeEnd);
// 触摸事件
resizeHandle.addEventListener("touchstart", handleResizeStart, { passive: false });
document.addEventListener("touchmove", handleResizeMove, { passive: false });
document.addEventListener("touchend", handleResizeEnd);
}
// 提示词编辑器相关函数
let currentPromptFile = "";
let currentPromptData = null; // 解析后的JSON数据
let currentField = "mainPrompt"; // 当前编辑的字段
let originalPromptDataSnapshot = null; // 用于检测未保存更改
function showPromptEditor() {
const modal = document.getElementById("mm-prompt-editor-modal");
if (modal) {
modal.classList.add("mm-modal-visible");
// 初始化标签状态
const keywordsBtn = document.getElementById("mm-prompt-type-keywords");
const historicalBtn = document.getElementById("mm-prompt-type-historical");
const plotOptimizeBtn = document.getElementById("mm-prompt-type-plot-optimize");
if (keywordsBtn && historicalBtn && plotOptimizeBtn) {
keywordsBtn.classList.toggle("mm-tab-active", currentPromptType === "keywords");
historicalBtn.classList.toggle("mm-tab-active", currentPromptType === "historical");
plotOptimizeBtn.classList.toggle("mm-tab-active", currentPromptType === "plot-optimize");
}
loadPromptFiles(currentPromptType);
initResizableEditor();
// 如果有已加载的数据,恢复编辑器内容和快照
if (currentPromptData && currentPromptFile) {
const editorEl = document.getElementById("mm-prompt-editor");
if (editorEl) {
const promptItem = Array.isArray(currentPromptData)
? currentPromptData[0]
: currentPromptData;
editorEl.value = promptItem[currentField] || "";
}
// 更新字段下拉框
const fieldSelectEl = document.getElementById("mm-prompt-field-select");
if (fieldSelectEl) {
fieldSelectEl.value = currentField;
}
// 重新保存快照
savePromptDataSnapshot();
}
}
}
/**
* 检查是否有未保存的更改
*/
function hasUnsavedChanges() {
if (!currentPromptData || !originalPromptDataSnapshot) return false;
// 先同步当前编辑器内容到数据
const editorEl = document.getElementById("mm-prompt-editor");
if (editorEl && currentPromptData) {
const promptItem = Array.isArray(currentPromptData)
? currentPromptData[0]
: currentPromptData;
promptItem[currentField] = editorEl.value;
}
// 比较当前数据和原始快照
const currentSnapshot = JSON.stringify(currentPromptData);
return currentSnapshot !== originalPromptDataSnapshot;
}
/**
* 保存当前数据快照(用于检测更改)
*/
function savePromptDataSnapshot() {
if (currentPromptData) {
originalPromptDataSnapshot = JSON.stringify(currentPromptData);
}
}
function hidePromptEditor(forceClose = false) {
// 检查未保存更改
if (!forceClose && hasUnsavedChanges()) {
if (!confirm("有未保存的更改,确定要关闭吗?")) {
return false;
}
}
const modal = document.getElementById("mm-prompt-editor-modal");
if (modal) {
modal.classList.remove("mm-modal-visible");
// 注意:不再清空 currentPromptFile 和 currentPromptData
// 保留选中状态,下次打开时可以继续编辑
currentField = "mainPrompt";
originalPromptDataSnapshot = null;
// 清理 resize 事件监听器
if (resizeHandlerCleanup) {
resizeHandlerCleanup();
resizeHandlerCleanup = null;
}
}
return true;
}
function switchPromptField(field) {
if (!currentPromptData || !field) return;
// 保存当前字段的内容到数据中
const editorEl = document.getElementById("mm-prompt-editor");
if (editorEl) {
const currentContent = editorEl.value;
const promptItem = Array.isArray(currentPromptData)
? currentPromptData[0]
: currentPromptData;
promptItem[currentField] = currentContent;
}
// 切换到新字段
currentField = field;
// 更新编辑器内容和标签
const promptItem = Array.isArray(currentPromptData)
? currentPromptData[0]
: currentPromptData;
const fieldContent = promptItem[currentField] || "";
if (editorEl) {
editorEl.value = fieldContent;
}
const fieldLabelEl = document.getElementById("mm-current-field-label");
if (fieldLabelEl) {
const fieldLabels = {
mainPrompt: "主提示词内容",
systemPrompt: "辅助提示词内容",
finalSystemDirective: "最终注入词内容",
};
fieldLabelEl.innerHTML = `${fieldLabels[currentField]}