From 2a739c5afb49acbbaa50bcd68358b19aa17700d5 Mon Sep 17 00:00:00 2001
From: Wx-2025 <351320169@qq.com>
Date: Sun, 19 Oct 2025 17:16:58 +0800
Subject: [PATCH 1/5] Update GT_bindings.js
---
glossary/GT_bindings.js | 771 +++++++++-------------------------------
1 file changed, 163 insertions(+), 608 deletions(-)
diff --git a/glossary/GT_bindings.js b/glossary/GT_bindings.js
index 901efd2..b4d2b22 100644
--- a/glossary/GT_bindings.js
+++ b/glossary/GT_bindings.js
@@ -1,630 +1,185 @@
-import { extension_settings, getContext } from "/scripts/extensions.js";
-import { saveSettingsDebounced, eventSource, event_types } from "/script.js";
-import { world_names } from "/scripts/world-info.js";
-import { extensionName } from "../utils/settings.js";
-import { testSybdApiConnection, fetchSybdModels } from '../core/api/SybdApi.js';
-import { handleFileUpload, processNovel } from './index.js';
-import { SETTINGS_KEY as PRESET_SETTINGS_KEY } from '../PresetSettings/config.js';
+import { callSybdAI } from '../core/api/SybdApi.js';
+import { extensionName } from '../utils/settings.js';
+import { getPresetPrompts, getMixedOrder } from '../PresetSettings/index.js';
+import { generateRandomSeed } from '../core/api.js';
+import { safeLorebookEntries, safeUpdateLorebookEntries, compatibleWriteToLorebook } from '../core/tavernhelper-compatibility.js';
-const moduleState = {
- selectedWorldBook: '',
-};
-
-function updateAndSaveSetting(key, value) {
- if (!extension_settings[extensionName]) {
- extension_settings[extensionName] = {};
+function buildContextFromEntries(entries) {
+ if (!entries || entries.length === 0) {
+ return '当前世界书为空。';
}
- extension_settings[extensionName][key] = value;
- saveSettingsDebounced();
- console.log(`[Amily2-术语表] 设置项 '${key}' 已更新为: ${JSON.stringify(value)}`);
+
+ const mappedContent = entries.map(entry => {
+ if (!Array.isArray(entry.keys) || entry.keys.length < 2) {
+ return null;
+ }
+ const name = entry.keys[1];
+ return `[--START_TABLE--]\n[name]:${name}\n${entry.content}\n[--END_TABLE--]`;
+ }).filter(Boolean).join('\n\n');
+
+ return mappedContent || '当前世界书为空。';
}
-function loadSettingsToUI() {
- const settings = extension_settings[extensionName] || {};
- const container = document.getElementById('amily2_glossary_panel');
- if (!container) return;
+function parseStructuredResponse(responseText) {
+ const entries = [];
+ const entryRegex = /\[--START_TABLE--\]\s*\[name\]:(.*?)\n([\s\S]*?)\[--END_TABLE--\]/g;
+ let match;
- const inputs = container.querySelectorAll('[data-setting-key]');
- inputs.forEach(target => {
- const key = target.dataset.settingKey;
- const value = settings[key];
-
- if (value === undefined) {
- let defaultValue;
- if (target.type === 'checkbox') {
- defaultValue = target.checked;
- } else if (target.type === 'range') {
- defaultValue = target.dataset.type === 'float' ? parseFloat(target.value) : parseInt(target.value, 10);
- } else {
- defaultValue = target.value;
- }
- updateAndSaveSetting(key, defaultValue);
- return;
- };
-
- if (target.type === 'checkbox') {
- target.checked = value;
- } else if (target.type === 'range') {
- target.value = value;
- const valueDisplay = document.getElementById(`${target.id}_value`);
- if (valueDisplay) valueDisplay.textContent = value;
+ while ((match = entryRegex.exec(responseText)) !== null) {
+ const title = match[1].trim();
+ const content = match[2].trim();
+ if (title && content) {
+ entries.push({ title, content });
}
- else {
- target.value = value;
- }
- });
-
- const sybdContent = document.getElementById('amily2_sybd_content');
- if (sybdContent) {
- sybdContent.classList.remove('amily2-content-hidden');
- }
-
- const apiModeSelect = document.getElementById('amily2_sybd_api_mode');
- if (apiModeSelect) {
- updateConfigVisibility(apiModeSelect.value);
- }
-}
-
-function bindAutoSaveEvents() {
- const container = document.getElementById('amily2_glossary_panel');
- if (!container) return;
-
- const handler = (event) => {
- const target = event.target;
- const key = target.dataset.settingKey;
- if (!key) return;
-
- let value;
- const type = target.dataset.type || 'string';
-
- if (target.type === 'checkbox') {
- value = target.checked;
- } else {
- value = target.value;
- }
-
- switch (type) {
- case 'integer': value = parseInt(value, 10); break;
- case 'float': value = parseFloat(value); break;
- case 'boolean': value = (typeof value === 'boolean') ? value : (value === 'true'); break;
- }
-
- updateAndSaveSetting(key, value);
-
- if (key === 'sybdApiMode') {
- updateConfigVisibility(value);
- }
- if (target.type === 'range') {
- document.getElementById(`${target.id}_value`).textContent = value;
- }
- };
-
- container.addEventListener('change', handler);
- container.addEventListener('input', (event) => {
- if (event.target.type === 'range') handler(event);
- });
-}
-
-function updateConfigVisibility(mode) {
- const compatibleConfig = document.getElementById('amily2_sybd_compatible_config');
- const presetConfig = document.getElementById('amily2_sybd_preset_config');
-
- if (mode === 'sillytavern_preset') {
- compatibleConfig.style.display = 'none';
- presetConfig.style.display = 'block';
- loadTavernPresets();
- } else {
- compatibleConfig.style.display = 'block';
- presetConfig.style.display = 'none';
- }
-}
-
-async function loadTavernPresets() {
- const select = document.getElementById('amily2_sybd_tavern_profile');
- if (!select) return;
-
- const currentValue = extension_settings[extensionName]?.sybdTavernProfile || '';
- select.innerHTML = '';
-
- try {
- const context = getContext();
- const tavernProfiles = context.extensionSettings?.connectionManager?.profiles || [];
-
- select.innerHTML = '';
-
- if (tavernProfiles.length > 0) {
- tavernProfiles.forEach(profile => {
- if (profile.api && profile.preset) {
- const option = new Option(profile.name || profile.id, profile.id);
- select.add(option);
- }
- });
- select.value = currentValue;
- } else {
- select.innerHTML = '';
- }
- } catch (error) {
- console.error('[Amily2-术语表] 加载SillyTavern预设失败:', error);
- select.innerHTML = '';
- }
-}
-
-function bindManualActionEvents() {
- const testBtn = document.getElementById('amily2_sybd_test_connection');
- if (testBtn) {
- testBtn.addEventListener('click', async () => {
- const originalHtml = testBtn.innerHTML;
- testBtn.disabled = true;
- testBtn.innerHTML = ' 测试中';
- await testSybdApiConnection();
- testBtn.disabled = false;
- testBtn.innerHTML = originalHtml;
- });
- }
-
- const fetchBtn = document.getElementById('amily2_sybd_fetch_models');
- const modelSelect = document.getElementById('amily2_sybd_model_select');
- const modelInput = document.getElementById('amily2_sybd_model');
-
- if (fetchBtn && modelSelect && modelInput) {
- fetchBtn.addEventListener('click', async () => {
- const originalHtml = fetchBtn.innerHTML;
- fetchBtn.disabled = true;
- fetchBtn.innerHTML = ' 获取中';
-
- try {
- const models = await fetchSybdModels();
- if (models && models.length > 0) {
- modelSelect.innerHTML = '';
- models.forEach(model => {
- const option = new Option(model.name || model.id, model.id);
- modelSelect.add(option);
- });
-
- modelSelect.style.display = 'block';
- modelInput.style.display = 'none';
- toastr.success(`成功获取 ${models.length} 个模型`);
- } else {
- toastr.warning('未获取到任何模型');
- }
- } catch (error) {
- toastr.error(`获取模型失败: ${error.message}`);
- } finally {
- fetchBtn.disabled = false;
- fetchBtn.innerHTML = originalHtml;
- }
- });
-
- modelSelect.addEventListener('change', () => {
- const selectedModel = modelSelect.value;
- if (selectedModel) {
- modelInput.value = selectedModel;
- modelInput.dispatchEvent(new Event('change', { bubbles: true }));
- }
- });
- }
-}
-
-async function renderWorldBookEntries() {
- const container = document.getElementById('world-book-entries-display');
- if (!container) return;
-
- const selectedBook = moduleState.selectedWorldBook;
- if (!selectedBook) {
- container.innerHTML = '
请先在“小说处理”标签页中选择一个世界书。
';
- return;
- }
-
- container.innerHTML = ' 正在加载条目...
';
-
- try {
- const { TavernHelper } = window;
- if (!TavernHelper) {
- container.innerHTML = 'TavernHelper 未找到!
';
- return;
- }
-
- const allEntries = await TavernHelper.getLorebookEntries(selectedBook);
- let managedEntries = allEntries.filter(e => e.comment?.startsWith('[Amily2小说处理]'));
-
- if (managedEntries.length === 0) {
- container.innerHTML = '未找到由小说处理功能生成的条目。
';
- return;
- }
-
- container.innerHTML = '';
-
- const summaryEntries = managedEntries.filter(e => e.comment.replace('[Amily2小说处理]', '').trim().startsWith('章节内容概述'));
- const otherEntries = managedEntries.filter(e => !e.comment.replace('[Amily2小说处理]', '').trim().startsWith('章节内容概述'));
- const sortedEntries = otherEntries.concat(summaryEntries);
-
- sortedEntries.forEach(entry => {
- const entryElement = document.createElement('div');
- entryElement.className = 'world-book-entry-item';
- entryElement.dataset.entryId = entry.uid;
-
- const title = entry.comment.replace('[Amily2小说处理]', '').trim();
-
- const renderContent = (content) => {
- const trimmedContent = content.trim();
- if (trimmedContent.startsWith('graph') || trimmedContent.startsWith('flowchart')) {
- try {
- const lines = trimmedContent.split('\n').map(l => l.trim()).filter(l => l.includes('-->') || l.includes('--'));
- let body = '';
- lines.forEach(line => {
- if (line.startsWith('flowchart')) return;
- let source = '', rel = '', target = '';
-
- let match = line.match(/(.+?)\s*--\s*"(.*?)"\s*-->(.+)/);
- if (match) {
- [source, rel, target] = [match[1], match[2], match[3]];
- } else {
- match = line.match(/(.+?)\s*-->\s*\|(.*?)\|(.+)/);
- if (match) {
- [source, rel, target] = [match[1], match[2], match[3]];
- } else {
- match = line.match(/(.+?)\s*-->(.+)/);
- if (match) {
- [source, target] = [match[1], match[2]];
- rel = '(直接关联)';
- }
- }
- }
-
- if (source && target) {
- body += `| ${source.trim()} | ${rel.trim()} | ${target.trim().replace(';','')} |
`;
- }
- });
- return ``;
- } catch {
- return `${content}`;
- }
- }
- if (trimmedContent.includes('|') && trimmedContent.includes('\n')) {
- try {
- const rows = trimmedContent.split('\n').filter(row => row.trim() && row.includes('|'));
- let header = '';
- let body = '';
- let isHeaderRow = true;
- rows.forEach(rowStr => {
- if (rowStr.includes('---')) return;
- const cells = rowStr.split('|').filter(c => c.trim()).map(cell => `${cell.trim()} | `).join('');
- if (isHeaderRow) {
- header += `${cells.replace(/| /g, ' | ').replace(/<\/td>/g, ' | ')}
`;
- isHeaderRow = false;
- } else {
- body += `${cells}
`;
- }
- });
- return ``;
- } catch {
- return `${content}`;
- }
- }
- return `${content}`;
- };
-
- entryElement.innerHTML = `
-
- ${renderContent(entry.content)}
-
-
-
- `;
-
- const editBtn = entryElement.querySelector('.edit-entry-btn');
- const saveBtn = entryElement.querySelector('.save-entry-btn');
- const cancelBtn = entryElement.querySelector('.cancel-entry-btn');
- const displayDiv = entryElement.querySelector('.entry-content-display');
- const editorDiv = entryElement.querySelector('.entry-content-editor');
- const textarea = editorDiv.querySelector('textarea');
- const originalContent = entry.content;
-
- editBtn.addEventListener('click', () => {
- displayDiv.style.display = 'none';
- editorDiv.style.display = 'block';
- saveBtn.style.display = 'inline-block';
- cancelBtn.style.display = 'inline-block';
- editBtn.style.display = 'none';
- });
-
- const hideEditor = () => {
- displayDiv.style.display = 'block';
- editorDiv.style.display = 'none';
- saveBtn.style.display = 'none';
- cancelBtn.style.display = 'none';
- editBtn.style.display = 'inline-block';
- };
-
- cancelBtn.addEventListener('click', () => {
- textarea.value = originalContent;
- hideEditor();
- });
-
- saveBtn.addEventListener('click', async () => {
- const newContent = textarea.value;
-
- displayDiv.innerHTML = renderContent(newContent);
- hideEditor();
-
- try {
- const { TavernHelper } = window;
- const entryToUpdate = { uid: entry.uid, content: newContent };
- await TavernHelper.setLorebookEntries(selectedBook, [entryToUpdate]);
- toastr.success(`条目 "${title}" 已保存。`);
- entry.content = newContent;
- } catch (error) {
- displayDiv.innerHTML = renderContent(originalContent);
- console.error('保存世界书条目失败:', error);
- toastr.error(`保存失败: ${error.message}`);
- }
- });
-
- container.appendChild(entryElement);
- });
-
- } catch (error) {
- console.error('加载世界书条目失败:', error);
- container.innerHTML = `加载失败: ${error.message}
`;
- }
-}
-
-
-function bindTabEvents() {
- const tabs = document.querySelectorAll('.glossary-tab');
- const contents = document.querySelectorAll('.glossary-content');
-
- tabs.forEach(tab => {
- tab.addEventListener('click', () => {
- const tabId = tab.dataset.tab;
-
- tabs.forEach(t => t.classList.remove('active'));
- tab.classList.add('active');
-
- contents.forEach(content => {
- if (content.id === `glossary-content-${tabId}`) {
- content.classList.add('active');
- } else {
- content.classList.remove('active');
- }
- });
-
- if (tabId === 'context') {
- renderWorldBookEntries();
- }
- });
- });
-}
-
-function bindNovelProcessEvents() {
- const fileInput = document.getElementById('novel-file-input');
- const fileLabel = document.querySelector('label[for="novel-file-input"]');
- const processBtn = document.getElementById('novel-confirm-and-process');
- const chunkSizeInput = document.getElementById('novel-chunk-size');
- const chunkCountEl = document.getElementById('novel-chunk-count');
- const chunkPreviewEl = document.getElementById('novel-chunk-preview');
-
- let fileContent = '';
- let processingState = {
- chunks: [],
- batchSize: 1,
- forceNew: false,
- selectedWorldBook: '',
- currentIndex: 0,
- isAborted: false,
- isRunning: false,
- lastStatus: 'idle',
- };
-
- function updateChunks() {
- if (!fileContent) return;
- const chunkSize = parseInt(chunkSizeInput.value, 10) || 5000;
- const newChunks = [];
- for (let i = 0; i < fileContent.length; i += chunkSize) {
- newChunks.push({ title: `Part ${i/chunkSize + 1}`, content: fileContent.substring(i, i + chunkSize) });
- }
- processingState.chunks = newChunks;
-
- chunkCountEl.textContent = newChunks.length;
- chunkPreviewEl.innerHTML = newChunks.map((chunk, index) =>
- `块 ${index + 1}: ${chunk.content.substring(0, 100)}...
`
- ).join('');
-
- resetProcessing();
}
- function resetProcessing() {
- processingState.currentIndex = 0;
- processingState.isAborted = false;
- processingState.isRunning = false;
- processingState.lastStatus = 'idle';
- updateButtonUI();
- }
-
- function updateButtonUI() {
- if (processingState.isRunning) {
- processBtn.disabled = false;
- processBtn.innerHTML = ' 请求中止';
- processBtn.classList.add('danger');
- } else {
- processBtn.classList.remove('danger');
- switch (processingState.lastStatus) {
- case 'paused':
- processBtn.innerHTML = ' 继续处理';
- processBtn.disabled = false;
- break;
- case 'failed':
- processBtn.innerHTML = ' 重试处理';
- processBtn.disabled = false;
- break;
- case 'success':
- processBtn.innerHTML = ' 处理完成';
- processBtn.disabled = true;
- break;
- case 'idle':
- default:
- processBtn.innerHTML = '确认并开始处理';
- processBtn.disabled = processingState.chunks.length === 0;
- break;
- }
- }
- }
-
- async function startOrResumeProcessing() {
- if (processingState.isRunning) return;
-
- processingState.isRunning = true;
- processingState.isAborted = false;
- updateButtonUI();
-
- processingState.forceNew = document.getElementById('novel-force-new').checked;
- processingState.batchSize = 1;
- processingState.selectedWorldBook = moduleState.selectedWorldBook;
-
- try {
- const result = await processNovel(processingState);
- if (result === 'paused') {
- processingState.lastStatus = 'paused';
- } else if (result === 'success') {
- processingState.lastStatus = 'success';
- processingState.currentIndex = 0;
- }
- } catch (error) {
- processingState.lastStatus = 'failed';
- processingState.isAborted = true;
- } finally {
- processingState.isRunning = false;
- updateButtonUI();
- }
- }
-
- if (fileLabel && fileInput) {
- fileLabel.addEventListener('click', (event) => {
- event.preventDefault();
- fileInput.click();
- });
- fileInput.addEventListener('change', (event) => {
- handleFileUpload(event.target.files[0], (content) => {
- fileContent = content;
- updateChunks();
- });
- });
- }
-
- if (chunkSizeInput) {
- chunkSizeInput.addEventListener('input', updateChunks);
- }
-
-
- if (processBtn) {
- processBtn.addEventListener('click', async () => {
- if (processingState.isRunning) {
- processingState.isAborted = true;
- processBtn.innerHTML = ' 正在中止...';
- processBtn.disabled = true;
- } else {
- if (processingState.lastStatus === 'success') {
- resetProcessing();
- }
- if (processingState.lastStatus === 'idle' || processingState.lastStatus === 'success') {
- processingState.currentIndex = 0;
- }
- startOrResumeProcessing();
- }
- });
- }
+ return entries;
}
-function isTavernHelperAvailable() {
- return typeof window.TavernHelper !== 'undefined' &&
- window.TavernHelper !== null &&
- typeof window.TavernHelper.getLorebooks === 'function';
-}
-async function safeLorebooks() {
- try {
- if (isTavernHelperAvailable()) {
- return await window.TavernHelper.getLorebooks();
- }
- return [...world_names];
- } catch (error) {
- console.error('[Amily2-兼容性] 获取世界书列表失败:', error);
- return [...world_names];
+export async function executeNovelProcessing(processingState, updateStatusCallback) {
+ const { chunks: recognizedChapters, batchSize, forceNew, selectedWorldBook } = processingState;
+
+ if (recognizedChapters.length === 0) {
+ updateStatusCallback('没有可处理的章节。', 'error');
+ throw new Error('没有可处理的章节。');
}
-}
-async function loadWorldBooks() {
- const select = document.getElementById('novel-world-book-select');
- if (!select) return;
-
- const { extension_settings } = window;
- const savedBook = extension_settings[extensionName]?.selectedWorldBook;
- moduleState.selectedWorldBook = savedBook || '';
+ updateStatusCallback('开始处理小说...', 'info');
try {
- const allBooks = await safeLorebooks();
- select.innerHTML = '';
-
- if (allBooks && allBooks.length > 0) {
- allBooks.forEach(bookName => {
- const option = new Option(bookName, bookName);
- select.add(option);
- });
-
- if (savedBook && allBooks.includes(savedBook)) {
- select.value = savedBook;
- }
- } else {
- select.innerHTML = '';
+ const bookName = selectedWorldBook;
+ if (!bookName) {
+ throw new Error('请先在设置中选择一个目标世界书。');
}
- } catch (error) {
- console.error('[Amily2-术语表] 加载世界书失败:', error);
- select.innerHTML = '';
- }
-}
-export function bindGlossaryEvents() {
- const panel = document.getElementById('amily2_glossary_panel');
- if (!panel || panel.dataset.eventsBound) {
- return;
- }
+ const allEntries = (await safeLorebookEntries(bookName)) || [];
+ const managedEntries = allEntries.filter(e => e.comment?.startsWith('[Amily2小说处理]') || e.comment?.startsWith('[Amily2-Glossary]'));
+ const localManagedEntries = [...managedEntries];
- console.log('[Amily2-术语表] 开始绑定UI事件 (最终重构版)...');
+ let existingEntriesContent = '当前世界书为空。';
+ if (!forceNew) {
+ existingEntriesContent = buildContextFromEntries(localManagedEntries);
+ }
- loadSettingsToUI();
- bindAutoSaveEvents();
- bindManualActionEvents();
- bindTabEvents();
- bindNovelProcessEvents();
- loadWorldBooks();
-
- // 监听角色加载事件,以确保 world_names 可用
- eventSource.on(event_types.CHARACTER_PAGE_LOADED, () => {
- console.log('[Amily2-术语表] 检测到角色加载,重新加载世界书列表以确保同步。');
- loadWorldBooks();
- });
-
- const worldBookSelect = document.getElementById('novel-world-book-select');
- if (worldBookSelect) {
- worldBookSelect.addEventListener('change', () => {
- const selectedValue = worldBookSelect.value;
- updateAndSaveSetting('selectedWorldBook', selectedValue);
- moduleState.selectedWorldBook = selectedValue;
-
- const contextTab = document.querySelector('.glossary-tab[data-tab="context"]');
- if (contextTab && contextTab.classList.contains('active')) {
- renderWorldBookEntries();
+ for (let i = processingState.currentIndex; i < recognizedChapters.length; i += batchSize) {
+ if (processingState.isAborted) {
+ updateStatusCallback(`处理已中止。当前进度: ${i}/${recognizedChapters.length}`, 'info');
+ return 'paused';
}
- });
- }
+ processingState.currentIndex = i;
- panel.dataset.eventsBound = 'true';
- console.log('[Amily2-术语表] UI事件绑定完成 (最终重构版)。');
+ const batch = recognizedChapters.slice(i, i + batchSize);
+ const progress = `(${i + batch.length}/${recognizedChapters.length})`;
+ updateStatusCallback(`正在处理批次 ${Math.floor(i / batchSize) + 1}... ${progress}`, 'info');
+
+ const chapterContent = batch.map(c => `## ${c.title}\n${c.content}`).join('\n\n---\n\n');
+ const order = getMixedOrder('novel_processor') || [];
+ const presetPrompts = await getPresetPrompts('novel_processor');
+ const messages = [{ role: 'system', content: generateRandomSeed() }];
+
+ let promptCounter = 0;
+ for (const item of order) {
+ if (item.type === 'prompt') {
+ if (presetPrompts && presetPrompts[promptCounter]) {
+ messages.push(presetPrompts[promptCounter]);
+ promptCounter++;
+ }
+ } else if (item.type === 'conditional') {
+ if (item.id === 'existingLore') {
+ messages.push({ role: 'user', content: `# 已有世界书条目\n\n${existingEntriesContent}` });
+ } else if (item.id === 'chapterContent') {
+ messages.push({ role: 'user', content: `# 最新章节内容\n\n${chapterContent}\n\n请根据以上信息,分析并输出需要新增或更新的世界书条目。` });
+ }
+ }
+ }
+
+ if (messages.length <= 1) throw new Error('未能根据预设构建有效的API请求。');
+
+ const response = await callSybdAI(messages);
+ if (!response) {
+ throw new Error(`API调用失败,批次 ${Math.floor(i / batchSize) + 1} 未收到响应。`);
+ }
+ if (response.trim() === '无需更新') {
+ updateStatusCallback(`批次 ${Math.floor(i / batchSize) + 1} 无需更新。`, 'info');
+ continue;
+ }
+
+ const structuredData = parseStructuredResponse(response);
+ if (structuredData.length === 0) {
+ throw new Error(`未能从API响应中提取有效信息,批次 ${Math.floor(i / batchSize) + 1}。`);
+ }
+
+ const entriesToUpdate = [];
+ const entriesToCreate = [];
+ const fixedNovelEntries = ['世界观设定', '时间线', '角色关系网', '角色总览'];
+
+ let maxPart = 0;
+ localManagedEntries.forEach(entry => {
+ const match = entry.comment.match(/章节内容概述-第(\d+)部分/);
+ if (match && parseInt(match[1], 10) > maxPart) maxPart = parseInt(match[1], 10);
+ });
+ let nextPart = maxPart + 1;
+
+ for (const entry of structuredData) {
+ const { title, content } = entry;
+ let comment;
+ let keys;
+
+ if (title === '章节内容概述') {
+ comment = `[Amily2小说处理] ${title}-第${nextPart}部分`;
+ keys = [`小说处理`, title, `第${nextPart}部分`];
+ const newEntryData = { keys, content, comment, enabled: true, order: 100, position: 'before_char' };
+ entriesToCreate.push(newEntryData);
+ localManagedEntries.push({ uid: -1, ...newEntryData, keys: keys });
+ nextPart++;
+ continue;
+ }
+
+ if (fixedNovelEntries.includes(title)) {
+ comment = `[Amily2小说处理] ${title}`;
+ keys = [`小说处理`, title];
+ } else {
+ comment = `[Amily2-Glossary] ${title}`;
+ keys = [`自定义条目`, title];
+ }
+
+ const existingEntry = localManagedEntries.find(e => e.comment === comment);
+ const loreData = { keys, content, comment, enabled: true, order: 100, position: 'before_char' };
+
+ if (existingEntry) {
+ entriesToUpdate.push({ uid: existingEntry.uid, ...loreData });
+ Object.assign(existingEntry, { ...loreData, keys: keys });
+ } else {
+ entriesToCreate.push(loreData);
+ localManagedEntries.push({ uid: -1, ...loreData, keys: keys });
+ }
+ }
+
+ if (entriesToUpdate.length > 0) {
+ await safeUpdateLorebookEntries(bookName, entriesToUpdate);
+ updateStatusCallback(`更新了 ${entriesToUpdate.length} 个世界书条目。`, 'info');
+ }
+ if (entriesToCreate.length > 0) {
+ for (const entry of entriesToCreate) {
+ await compatibleWriteToLorebook(bookName, entry.comment, () => entry.content, {
+ keys: entry.keys,
+ isConstant: false,
+ insertion_position: 'before_char',
+ depth: 100,
+ });
+ }
+ updateStatusCallback(`创建了 ${entriesToCreate.length} 个新世界书条目。`, 'success');
+ }
+
+ existingEntriesContent = buildContextFromEntries(localManagedEntries);
+ }
+
+ updateStatusCallback('小说处理完成!', 'success');
+ return 'success';
+ } catch (error) {
+ console.error('处理小说时发生严重错误:', error);
+ updateStatusCallback(`处理失败: ${error.message}`, 'error');
+ throw error;
+ }
}
From a3a7fdac207380ced770c94af3088515e187841c Mon Sep 17 00:00:00 2001
From: Wx-2025 <351320169@qq.com>
Date: Sun, 19 Oct 2025 17:17:13 +0800
Subject: [PATCH 2/5] Update GT_bindings.js
---
glossary/GT_bindings.js | 755 +++++++++++++++++++++++++++++++---------
1 file changed, 591 insertions(+), 164 deletions(-)
diff --git a/glossary/GT_bindings.js b/glossary/GT_bindings.js
index b4d2b22..a14f70d 100644
--- a/glossary/GT_bindings.js
+++ b/glossary/GT_bindings.js
@@ -1,185 +1,612 @@
-import { callSybdAI } from '../core/api/SybdApi.js';
-import { extensionName } from '../utils/settings.js';
-import { getPresetPrompts, getMixedOrder } from '../PresetSettings/index.js';
-import { generateRandomSeed } from '../core/api.js';
-import { safeLorebookEntries, safeUpdateLorebookEntries, compatibleWriteToLorebook } from '../core/tavernhelper-compatibility.js';
+import { extension_settings, getContext } from "/scripts/extensions.js";
+import { saveSettingsDebounced, eventSource, event_types } from "/script.js";
+import { extensionName } from "../utils/settings.js";
+import { safeLorebooks } from '../core/tavernhelper-compatibility.js';
+import { testSybdApiConnection, fetchSybdModels } from '../core/api/SybdApi.js';
+import { handleFileUpload, processNovel } from './index.js';
+import { SETTINGS_KEY as PRESET_SETTINGS_KEY } from '../PresetSettings/config.js';
-function buildContextFromEntries(entries) {
- if (!entries || entries.length === 0) {
- return '当前世界书为空。';
+const moduleState = {
+ selectedWorldBook: '',
+};
+
+function updateAndSaveSetting(key, value) {
+ if (!extension_settings[extensionName]) {
+ extension_settings[extensionName] = {};
}
-
- const mappedContent = entries.map(entry => {
- if (!Array.isArray(entry.keys) || entry.keys.length < 2) {
- return null;
- }
- const name = entry.keys[1];
- return `[--START_TABLE--]\n[name]:${name}\n${entry.content}\n[--END_TABLE--]`;
- }).filter(Boolean).join('\n\n');
-
- return mappedContent || '当前世界书为空。';
+ extension_settings[extensionName][key] = value;
+ saveSettingsDebounced();
+ console.log(`[Amily2-术语表] 设置项 '${key}' 已更新为: ${JSON.stringify(value)}`);
}
-function parseStructuredResponse(responseText) {
- const entries = [];
- const entryRegex = /\[--START_TABLE--\]\s*\[name\]:(.*?)\n([\s\S]*?)\[--END_TABLE--\]/g;
- let match;
+function loadSettingsToUI() {
+ const settings = extension_settings[extensionName] || {};
+ const container = document.getElementById('amily2_glossary_panel');
+ if (!container) return;
- while ((match = entryRegex.exec(responseText)) !== null) {
- const title = match[1].trim();
- const content = match[2].trim();
- if (title && content) {
- entries.push({ title, content });
+ const inputs = container.querySelectorAll('[data-setting-key]');
+ inputs.forEach(target => {
+ const key = target.dataset.settingKey;
+ const value = settings[key];
+
+ if (value === undefined) {
+ let defaultValue;
+ if (target.type === 'checkbox') {
+ defaultValue = target.checked;
+ } else if (target.type === 'range') {
+ defaultValue = target.dataset.type === 'float' ? parseFloat(target.value) : parseInt(target.value, 10);
+ } else {
+ defaultValue = target.value;
+ }
+ updateAndSaveSetting(key, defaultValue);
+ return;
+ };
+
+ if (target.type === 'checkbox') {
+ target.checked = value;
+ } else if (target.type === 'range') {
+ target.value = value;
+ const valueDisplay = document.getElementById(`${target.id}_value`);
+ if (valueDisplay) valueDisplay.textContent = value;
}
+ else {
+ target.value = value;
+ }
+ });
+
+ const sybdContent = document.getElementById('amily2_sybd_content');
+ if (sybdContent) {
+ sybdContent.classList.remove('amily2-content-hidden');
+ }
+
+ const apiModeSelect = document.getElementById('amily2_sybd_api_mode');
+ if (apiModeSelect) {
+ updateConfigVisibility(apiModeSelect.value);
}
-
- return entries;
}
+function bindAutoSaveEvents() {
+ const container = document.getElementById('amily2_glossary_panel');
+ if (!container) return;
-export async function executeNovelProcessing(processingState, updateStatusCallback) {
- const { chunks: recognizedChapters, batchSize, forceNew, selectedWorldBook } = processingState;
+ const handler = (event) => {
+ const target = event.target;
+ const key = target.dataset.settingKey;
+ if (!key) return;
- if (recognizedChapters.length === 0) {
- updateStatusCallback('没有可处理的章节。', 'error');
- throw new Error('没有可处理的章节。');
+ let value;
+ const type = target.dataset.type || 'string';
+
+ if (target.type === 'checkbox') {
+ value = target.checked;
+ } else {
+ value = target.value;
+ }
+
+ switch (type) {
+ case 'integer': value = parseInt(value, 10); break;
+ case 'float': value = parseFloat(value); break;
+ case 'boolean': value = (typeof value === 'boolean') ? value : (value === 'true'); break;
+ }
+
+ updateAndSaveSetting(key, value);
+
+ if (key === 'sybdApiMode') {
+ updateConfigVisibility(value);
+ }
+ if (target.type === 'range') {
+ document.getElementById(`${target.id}_value`).textContent = value;
+ }
+ };
+
+ container.addEventListener('change', handler);
+ container.addEventListener('input', (event) => {
+ if (event.target.type === 'range') handler(event);
+ });
+}
+
+function updateConfigVisibility(mode) {
+ const compatibleConfig = document.getElementById('amily2_sybd_compatible_config');
+ const presetConfig = document.getElementById('amily2_sybd_preset_config');
+
+ if (mode === 'sillytavern_preset') {
+ compatibleConfig.style.display = 'none';
+ presetConfig.style.display = 'block';
+ loadTavernPresets();
+ } else {
+ compatibleConfig.style.display = 'block';
+ presetConfig.style.display = 'none';
}
+}
- updateStatusCallback('开始处理小说...', 'info');
+async function loadTavernPresets() {
+ const select = document.getElementById('amily2_sybd_tavern_profile');
+ if (!select) return;
+
+ const currentValue = extension_settings[extensionName]?.sybdTavernProfile || '';
+ select.innerHTML = '';
try {
- const bookName = selectedWorldBook;
- if (!bookName) {
- throw new Error('请先在设置中选择一个目标世界书。');
- }
-
- const allEntries = (await safeLorebookEntries(bookName)) || [];
- const managedEntries = allEntries.filter(e => e.comment?.startsWith('[Amily2小说处理]') || e.comment?.startsWith('[Amily2-Glossary]'));
- const localManagedEntries = [...managedEntries];
-
- let existingEntriesContent = '当前世界书为空。';
- if (!forceNew) {
- existingEntriesContent = buildContextFromEntries(localManagedEntries);
- }
-
- for (let i = processingState.currentIndex; i < recognizedChapters.length; i += batchSize) {
- if (processingState.isAborted) {
- updateStatusCallback(`处理已中止。当前进度: ${i}/${recognizedChapters.length}`, 'info');
- return 'paused';
- }
- processingState.currentIndex = i;
-
- const batch = recognizedChapters.slice(i, i + batchSize);
- const progress = `(${i + batch.length}/${recognizedChapters.length})`;
- updateStatusCallback(`正在处理批次 ${Math.floor(i / batchSize) + 1}... ${progress}`, 'info');
-
- const chapterContent = batch.map(c => `## ${c.title}\n${c.content}`).join('\n\n---\n\n');
- const order = getMixedOrder('novel_processor') || [];
- const presetPrompts = await getPresetPrompts('novel_processor');
- const messages = [{ role: 'system', content: generateRandomSeed() }];
-
- let promptCounter = 0;
- for (const item of order) {
- if (item.type === 'prompt') {
- if (presetPrompts && presetPrompts[promptCounter]) {
- messages.push(presetPrompts[promptCounter]);
- promptCounter++;
- }
- } else if (item.type === 'conditional') {
- if (item.id === 'existingLore') {
- messages.push({ role: 'user', content: `# 已有世界书条目\n\n${existingEntriesContent}` });
- } else if (item.id === 'chapterContent') {
- messages.push({ role: 'user', content: `# 最新章节内容\n\n${chapterContent}\n\n请根据以上信息,分析并输出需要新增或更新的世界书条目。` });
- }
+ const context = getContext();
+ const tavernProfiles = context.extensionSettings?.connectionManager?.profiles || [];
+
+ select.innerHTML = '';
+
+ if (tavernProfiles.length > 0) {
+ tavernProfiles.forEach(profile => {
+ if (profile.api && profile.preset) {
+ const option = new Option(profile.name || profile.id, profile.id);
+ select.add(option);
}
- }
-
- if (messages.length <= 1) throw new Error('未能根据预设构建有效的API请求。');
-
- const response = await callSybdAI(messages);
- if (!response) {
- throw new Error(`API调用失败,批次 ${Math.floor(i / batchSize) + 1} 未收到响应。`);
- }
- if (response.trim() === '无需更新') {
- updateStatusCallback(`批次 ${Math.floor(i / batchSize) + 1} 无需更新。`, 'info');
- continue;
- }
-
- const structuredData = parseStructuredResponse(response);
- if (structuredData.length === 0) {
- throw new Error(`未能从API响应中提取有效信息,批次 ${Math.floor(i / batchSize) + 1}。`);
- }
-
- const entriesToUpdate = [];
- const entriesToCreate = [];
- const fixedNovelEntries = ['世界观设定', '时间线', '角色关系网', '角色总览'];
-
- let maxPart = 0;
- localManagedEntries.forEach(entry => {
- const match = entry.comment.match(/章节内容概述-第(\d+)部分/);
- if (match && parseInt(match[1], 10) > maxPart) maxPart = parseInt(match[1], 10);
});
- let nextPart = maxPart + 1;
-
- for (const entry of structuredData) {
- const { title, content } = entry;
- let comment;
- let keys;
-
- if (title === '章节内容概述') {
- comment = `[Amily2小说处理] ${title}-第${nextPart}部分`;
- keys = [`小说处理`, title, `第${nextPart}部分`];
- const newEntryData = { keys, content, comment, enabled: true, order: 100, position: 'before_char' };
- entriesToCreate.push(newEntryData);
- localManagedEntries.push({ uid: -1, ...newEntryData, keys: keys });
- nextPart++;
- continue;
- }
-
- if (fixedNovelEntries.includes(title)) {
- comment = `[Amily2小说处理] ${title}`;
- keys = [`小说处理`, title];
- } else {
- comment = `[Amily2-Glossary] ${title}`;
- keys = [`自定义条目`, title];
- }
-
- const existingEntry = localManagedEntries.find(e => e.comment === comment);
- const loreData = { keys, content, comment, enabled: true, order: 100, position: 'before_char' };
-
- if (existingEntry) {
- entriesToUpdate.push({ uid: existingEntry.uid, ...loreData });
- Object.assign(existingEntry, { ...loreData, keys: keys });
- } else {
- entriesToCreate.push(loreData);
- localManagedEntries.push({ uid: -1, ...loreData, keys: keys });
- }
- }
-
- if (entriesToUpdate.length > 0) {
- await safeUpdateLorebookEntries(bookName, entriesToUpdate);
- updateStatusCallback(`更新了 ${entriesToUpdate.length} 个世界书条目。`, 'info');
- }
- if (entriesToCreate.length > 0) {
- for (const entry of entriesToCreate) {
- await compatibleWriteToLorebook(bookName, entry.comment, () => entry.content, {
- keys: entry.keys,
- isConstant: false,
- insertion_position: 'before_char',
- depth: 100,
- });
- }
- updateStatusCallback(`创建了 ${entriesToCreate.length} 个新世界书条目。`, 'success');
- }
-
- existingEntriesContent = buildContextFromEntries(localManagedEntries);
+ select.value = currentValue;
+ } else {
+ select.innerHTML = '';
}
-
- updateStatusCallback('小说处理完成!', 'success');
- return 'success';
} catch (error) {
- console.error('处理小说时发生严重错误:', error);
- updateStatusCallback(`处理失败: ${error.message}`, 'error');
- throw error;
+ console.error('[Amily2-术语表] 加载SillyTavern预设失败:', error);
+ select.innerHTML = '';
}
}
+
+function bindManualActionEvents() {
+ const testBtn = document.getElementById('amily2_sybd_test_connection');
+ if (testBtn) {
+ testBtn.addEventListener('click', async () => {
+ const originalHtml = testBtn.innerHTML;
+ testBtn.disabled = true;
+ testBtn.innerHTML = ' 测试中';
+ await testSybdApiConnection();
+ testBtn.disabled = false;
+ testBtn.innerHTML = originalHtml;
+ });
+ }
+
+ const fetchBtn = document.getElementById('amily2_sybd_fetch_models');
+ const modelSelect = document.getElementById('amily2_sybd_model_select');
+ const modelInput = document.getElementById('amily2_sybd_model');
+
+ if (fetchBtn && modelSelect && modelInput) {
+ fetchBtn.addEventListener('click', async () => {
+ const originalHtml = fetchBtn.innerHTML;
+ fetchBtn.disabled = true;
+ fetchBtn.innerHTML = ' 获取中';
+
+ try {
+ const models = await fetchSybdModels();
+ if (models && models.length > 0) {
+ modelSelect.innerHTML = '';
+ models.forEach(model => {
+ const option = new Option(model.name || model.id, model.id);
+ modelSelect.add(option);
+ });
+
+ modelSelect.style.display = 'block';
+ modelInput.style.display = 'none';
+ toastr.success(`成功获取 ${models.length} 个模型`);
+ } else {
+ toastr.warning('未获取到任何模型');
+ }
+ } catch (error) {
+ toastr.error(`获取模型失败: ${error.message}`);
+ } finally {
+ fetchBtn.disabled = false;
+ fetchBtn.innerHTML = originalHtml;
+ }
+ });
+
+ modelSelect.addEventListener('change', () => {
+ const selectedModel = modelSelect.value;
+ if (selectedModel) {
+ modelInput.value = selectedModel;
+ modelInput.dispatchEvent(new Event('change', { bubbles: true }));
+ }
+ });
+ }
+}
+
+async function renderWorldBookEntries() {
+ const container = document.getElementById('world-book-entries-display');
+ if (!container) return;
+
+ const selectedBook = moduleState.selectedWorldBook;
+ if (!selectedBook) {
+ container.innerHTML = '请先在“小说处理”标签页中选择一个世界书。
';
+ return;
+ }
+
+ container.innerHTML = ' 正在加载条目...
';
+
+ try {
+ const { TavernHelper } = window;
+ if (!TavernHelper) {
+ container.innerHTML = 'TavernHelper 未找到!
';
+ return;
+ }
+
+ const allEntries = await TavernHelper.getLorebookEntries(selectedBook);
+ let managedEntries = allEntries.filter(e => e.comment?.startsWith('[Amily2小说处理]'));
+
+ if (managedEntries.length === 0) {
+ container.innerHTML = '未找到由小说处理功能生成的条目。
';
+ return;
+ }
+
+ container.innerHTML = '';
+
+ const summaryEntries = managedEntries.filter(e => e.comment.replace('[Amily2小说处理]', '').trim().startsWith('章节内容概述'));
+ const otherEntries = managedEntries.filter(e => !e.comment.replace('[Amily2小说处理]', '').trim().startsWith('章节内容概述'));
+ const sortedEntries = otherEntries.concat(summaryEntries);
+
+ sortedEntries.forEach(entry => {
+ const entryElement = document.createElement('div');
+ entryElement.className = 'world-book-entry-item';
+ entryElement.dataset.entryId = entry.uid;
+
+ const title = entry.comment.replace('[Amily2小说处理]', '').trim();
+
+ const renderContent = (content) => {
+ const trimmedContent = content.trim();
+ if (trimmedContent.startsWith('graph') || trimmedContent.startsWith('flowchart')) {
+ try {
+ const lines = trimmedContent.split('\n').map(l => l.trim()).filter(l => l.includes('-->') || l.includes('--'));
+ let body = '';
+ lines.forEach(line => {
+ if (line.startsWith('flowchart')) return;
+ let source = '', rel = '', target = '';
+
+ let match = line.match(/(.+?)\s*--\s*"(.*?)"\s*-->(.+)/);
+ if (match) {
+ [source, rel, target] = [match[1], match[2], match[3]];
+ } else {
+ match = line.match(/(.+?)\s*-->\s*\|(.*?)\|(.+)/);
+ if (match) {
+ [source, rel, target] = [match[1], match[2], match[3]];
+ } else {
+ match = line.match(/(.+?)\s*-->(.+)/);
+ if (match) {
+ [source, target] = [match[1], match[2]];
+ rel = '(直接关联)';
+ }
+ }
+ }
+
+ if (source && target) {
+ body += `| ${source.trim()} | ${rel.trim()} | ${target.trim().replace(';','')} |
`;
+ }
+ });
+ return ``;
+ } catch {
+ return `${content}`;
+ }
+ }
+ if (trimmedContent.includes('|') && trimmedContent.includes('\n')) {
+ try {
+ const rows = trimmedContent.split('\n').filter(row => row.trim() && row.includes('|'));
+ let header = '';
+ let body = '';
+ let isHeaderRow = true;
+ rows.forEach(rowStr => {
+ if (rowStr.includes('---')) return;
+ const cells = rowStr.split('|').filter(c => c.trim()).map(cell => `${cell.trim()} | `).join('');
+ if (isHeaderRow) {
+ header += `${cells.replace(/| /g, ' | ').replace(/<\/td>/g, ' | ')}
`;
+ isHeaderRow = false;
+ } else {
+ body += `${cells}
`;
+ }
+ });
+ return ``;
+ } catch {
+ return `${content}`;
+ }
+ }
+ return `${content}`;
+ };
+
+ entryElement.innerHTML = `
+
+ ${renderContent(entry.content)}
+
+
+
+ `;
+
+ const editBtn = entryElement.querySelector('.edit-entry-btn');
+ const saveBtn = entryElement.querySelector('.save-entry-btn');
+ const cancelBtn = entryElement.querySelector('.cancel-entry-btn');
+ const displayDiv = entryElement.querySelector('.entry-content-display');
+ const editorDiv = entryElement.querySelector('.entry-content-editor');
+ const textarea = editorDiv.querySelector('textarea');
+ const originalContent = entry.content;
+
+ editBtn.addEventListener('click', () => {
+ displayDiv.style.display = 'none';
+ editorDiv.style.display = 'block';
+ saveBtn.style.display = 'inline-block';
+ cancelBtn.style.display = 'inline-block';
+ editBtn.style.display = 'none';
+ });
+
+ const hideEditor = () => {
+ displayDiv.style.display = 'block';
+ editorDiv.style.display = 'none';
+ saveBtn.style.display = 'none';
+ cancelBtn.style.display = 'none';
+ editBtn.style.display = 'inline-block';
+ };
+
+ cancelBtn.addEventListener('click', () => {
+ textarea.value = originalContent;
+ hideEditor();
+ });
+
+ saveBtn.addEventListener('click', async () => {
+ const newContent = textarea.value;
+
+ displayDiv.innerHTML = renderContent(newContent);
+ hideEditor();
+
+ try {
+ const { TavernHelper } = window;
+ const entryToUpdate = { uid: entry.uid, content: newContent };
+ await TavernHelper.setLorebookEntries(selectedBook, [entryToUpdate]);
+ toastr.success(`条目 "${title}" 已保存。`);
+ entry.content = newContent;
+ } catch (error) {
+ displayDiv.innerHTML = renderContent(originalContent);
+ console.error('保存世界书条目失败:', error);
+ toastr.error(`保存失败: ${error.message}`);
+ }
+ });
+
+ container.appendChild(entryElement);
+ });
+
+ } catch (error) {
+ console.error('加载世界书条目失败:', error);
+ container.innerHTML = `加载失败: ${error.message}
`;
+ }
+}
+
+
+function bindTabEvents() {
+ const tabs = document.querySelectorAll('.glossary-tab');
+ const contents = document.querySelectorAll('.glossary-content');
+
+ tabs.forEach(tab => {
+ tab.addEventListener('click', () => {
+ const tabId = tab.dataset.tab;
+
+ tabs.forEach(t => t.classList.remove('active'));
+ tab.classList.add('active');
+
+ contents.forEach(content => {
+ if (content.id === `glossary-content-${tabId}`) {
+ content.classList.add('active');
+ } else {
+ content.classList.remove('active');
+ }
+ });
+
+ if (tabId === 'context') {
+ renderWorldBookEntries();
+ }
+ });
+ });
+}
+
+function bindNovelProcessEvents() {
+ const fileInput = document.getElementById('novel-file-input');
+ const fileLabel = document.querySelector('label[for="novel-file-input"]');
+ const processBtn = document.getElementById('novel-confirm-and-process');
+ const chunkSizeInput = document.getElementById('novel-chunk-size');
+ const chunkCountEl = document.getElementById('novel-chunk-count');
+ const chunkPreviewEl = document.getElementById('novel-chunk-preview');
+
+ let fileContent = '';
+ let processingState = {
+ chunks: [],
+ batchSize: 1,
+ forceNew: false,
+ selectedWorldBook: '',
+ currentIndex: 0,
+ isAborted: false,
+ isRunning: false,
+ lastStatus: 'idle',
+ };
+
+ function updateChunks() {
+ if (!fileContent) return;
+ const chunkSize = parseInt(chunkSizeInput.value, 10) || 5000;
+ const newChunks = [];
+ for (let i = 0; i < fileContent.length; i += chunkSize) {
+ newChunks.push({ title: `Part ${i/chunkSize + 1}`, content: fileContent.substring(i, i + chunkSize) });
+ }
+ processingState.chunks = newChunks;
+
+ chunkCountEl.textContent = newChunks.length;
+ chunkPreviewEl.innerHTML = newChunks.map((chunk, index) =>
+ `块 ${index + 1}: ${chunk.content.substring(0, 100)}...
`
+ ).join('');
+
+ resetProcessing();
+ }
+
+ function resetProcessing() {
+ processingState.currentIndex = 0;
+ processingState.isAborted = false;
+ processingState.isRunning = false;
+ processingState.lastStatus = 'idle';
+ updateButtonUI();
+ }
+
+ function updateButtonUI() {
+ if (processingState.isRunning) {
+ processBtn.disabled = false;
+ processBtn.innerHTML = ' 请求中止';
+ processBtn.classList.add('danger');
+ } else {
+ processBtn.classList.remove('danger');
+ switch (processingState.lastStatus) {
+ case 'paused':
+ processBtn.innerHTML = ' 继续处理';
+ processBtn.disabled = false;
+ break;
+ case 'failed':
+ processBtn.innerHTML = ' 重试处理';
+ processBtn.disabled = false;
+ break;
+ case 'success':
+ processBtn.innerHTML = ' 处理完成';
+ processBtn.disabled = true;
+ break;
+ case 'idle':
+ default:
+ processBtn.innerHTML = '确认并开始处理';
+ processBtn.disabled = processingState.chunks.length === 0;
+ break;
+ }
+ }
+ }
+
+ async function startOrResumeProcessing() {
+ if (processingState.isRunning) return;
+
+ processingState.isRunning = true;
+ processingState.isAborted = false;
+ updateButtonUI();
+
+ processingState.forceNew = document.getElementById('novel-force-new').checked;
+ processingState.batchSize = 1;
+ processingState.selectedWorldBook = moduleState.selectedWorldBook;
+
+ try {
+ const result = await processNovel(processingState);
+ if (result === 'paused') {
+ processingState.lastStatus = 'paused';
+ } else if (result === 'success') {
+ processingState.lastStatus = 'success';
+ processingState.currentIndex = 0;
+ }
+ } catch (error) {
+ processingState.lastStatus = 'failed';
+ processingState.isAborted = true;
+ } finally {
+ processingState.isRunning = false;
+ updateButtonUI();
+ }
+ }
+
+ if (fileLabel && fileInput) {
+ fileLabel.addEventListener('click', (event) => {
+ event.preventDefault();
+ fileInput.click();
+ });
+ fileInput.addEventListener('change', (event) => {
+ handleFileUpload(event.target.files[0], (content) => {
+ fileContent = content;
+ updateChunks();
+ });
+ });
+ }
+
+ if (chunkSizeInput) {
+ chunkSizeInput.addEventListener('input', updateChunks);
+ }
+
+
+ if (processBtn) {
+ processBtn.addEventListener('click', async () => {
+ if (processingState.isRunning) {
+ processingState.isAborted = true;
+ processBtn.innerHTML = ' 正在中止...';
+ processBtn.disabled = true;
+ } else {
+ if (processingState.lastStatus === 'success') {
+ resetProcessing();
+ }
+ if (processingState.lastStatus === 'idle' || processingState.lastStatus === 'success') {
+ processingState.currentIndex = 0;
+ }
+ startOrResumeProcessing();
+ }
+ });
+ }
+}
+
+
+async function loadWorldBooks() {
+ const select = document.getElementById('novel-world-book-select');
+ if (!select) return;
+
+ const savedBook = extension_settings[extensionName]?.selectedWorldBook;
+ moduleState.selectedWorldBook = savedBook || '';
+
+ try {
+ const allBooks = await safeLorebooks();
+ select.innerHTML = '';
+
+ if (allBooks && allBooks.length > 0) {
+ allBooks.forEach(bookName => {
+ const option = new Option(bookName, bookName);
+ select.add(option);
+ });
+
+ if (savedBook && allBooks.includes(savedBook)) {
+ select.value = savedBook;
+ }
+ } else {
+ select.innerHTML = '';
+ }
+ } catch (error) {
+ console.error('[Amily2-术语表] 加载世界书失败:', error);
+ select.innerHTML = '';
+ }
+}
+
+export function bindGlossaryEvents() {
+ const panel = document.getElementById('amily2_glossary_panel');
+ if (!panel || panel.dataset.eventsBound) {
+ return;
+ }
+
+ console.log('[Amily2-术语表] 开始绑定UI事件 (最终重构版)...');
+
+ loadSettingsToUI();
+ bindAutoSaveEvents();
+ bindManualActionEvents();
+ bindTabEvents();
+ bindNovelProcessEvents();
+ loadWorldBooks();
+
+ // 监听角色加载事件,以确保 world_names 可用
+ eventSource.on(event_types.CHARACTER_PAGE_LOADED, () => {
+ console.log('[Amily2-术语表] 检测到角色加载,重新加载世界书列表以确保同步。');
+ loadWorldBooks();
+ });
+
+ const worldBookSelect = document.getElementById('novel-world-book-select');
+ if (worldBookSelect) {
+ worldBookSelect.addEventListener('change', () => {
+ const selectedValue = worldBookSelect.value;
+ updateAndSaveSetting('selectedWorldBook', selectedValue);
+ moduleState.selectedWorldBook = selectedValue;
+
+ const contextTab = document.querySelector('.glossary-tab[data-tab="context"]');
+ if (contextTab && contextTab.classList.contains('active')) {
+ renderWorldBookEntries();
+ }
+ });
+ }
+
+ panel.dataset.eventsBound = 'true';
+ console.log('[Amily2-术语表] UI事件绑定完成 (最终重构版)。');
+}
From 7e4b2b1abd00531f1e1157cafcc5732ba911bc80 Mon Sep 17 00:00:00 2001
From: Wx-2025 <351320169@qq.com>
Date: Sun, 19 Oct 2025 17:17:27 +0800
Subject: [PATCH 3/5] Update executor.js
---
glossary/executor.js | 26 ++++++++++++++++----------
1 file changed, 16 insertions(+), 10 deletions(-)
diff --git a/glossary/executor.js b/glossary/executor.js
index 0288fd9..b4d2b22 100644
--- a/glossary/executor.js
+++ b/glossary/executor.js
@@ -2,8 +2,7 @@ import { callSybdAI } from '../core/api/SybdApi.js';
import { extensionName } from '../utils/settings.js';
import { getPresetPrompts, getMixedOrder } from '../PresetSettings/index.js';
import { generateRandomSeed } from '../core/api.js';
-
-const { TavernHelper } = window;
+import { safeLorebookEntries, safeUpdateLorebookEntries, compatibleWriteToLorebook } from '../core/tavernhelper-compatibility.js';
function buildContextFromEntries(entries) {
if (!entries || entries.length === 0) {
@@ -11,10 +10,10 @@ function buildContextFromEntries(entries) {
}
const mappedContent = entries.map(entry => {
- if (!Array.isArray(entry.keyword) || entry.keyword.length < 2) {
+ if (!Array.isArray(entry.keys) || entry.keys.length < 2) {
return null;
}
- const name = entry.keyword[1];
+ const name = entry.keys[1];
return `[--START_TABLE--]\n[name]:${name}\n${entry.content}\n[--END_TABLE--]`;
}).filter(Boolean).join('\n\n');
@@ -54,7 +53,7 @@ export async function executeNovelProcessing(processingState, updateStatusCallba
throw new Error('请先在设置中选择一个目标世界书。');
}
- const allEntries = (await TavernHelper.getLorebookEntries(bookName)) || [];
+ const allEntries = (await safeLorebookEntries(bookName)) || [];
const managedEntries = allEntries.filter(e => e.comment?.startsWith('[Amily2小说处理]') || e.comment?.startsWith('[Amily2-Glossary]'));
const localManagedEntries = [...managedEntries];
@@ -132,7 +131,7 @@ export async function executeNovelProcessing(processingState, updateStatusCallba
keys = [`小说处理`, title, `第${nextPart}部分`];
const newEntryData = { keys, content, comment, enabled: true, order: 100, position: 'before_char' };
entriesToCreate.push(newEntryData);
- localManagedEntries.push({ uid: -1, ...newEntryData, keyword: keys });
+ localManagedEntries.push({ uid: -1, ...newEntryData, keys: keys });
nextPart++;
continue;
}
@@ -150,19 +149,26 @@ export async function executeNovelProcessing(processingState, updateStatusCallba
if (existingEntry) {
entriesToUpdate.push({ uid: existingEntry.uid, ...loreData });
- Object.assign(existingEntry, { ...loreData, keyword: keys });
+ Object.assign(existingEntry, { ...loreData, keys: keys });
} else {
entriesToCreate.push(loreData);
- localManagedEntries.push({ uid: -1, ...loreData, keyword: keys });
+ localManagedEntries.push({ uid: -1, ...loreData, keys: keys });
}
}
if (entriesToUpdate.length > 0) {
- await TavernHelper.setLorebookEntries(bookName, entriesToUpdate);
+ await safeUpdateLorebookEntries(bookName, entriesToUpdate);
updateStatusCallback(`更新了 ${entriesToUpdate.length} 个世界书条目。`, 'info');
}
if (entriesToCreate.length > 0) {
- await TavernHelper.createLorebookEntries(bookName, entriesToCreate);
+ for (const entry of entriesToCreate) {
+ await compatibleWriteToLorebook(bookName, entry.comment, () => entry.content, {
+ keys: entry.keys,
+ isConstant: false,
+ insertion_position: 'before_char',
+ depth: 100,
+ });
+ }
updateStatusCallback(`创建了 ${entriesToCreate.length} 个新世界书条目。`, 'success');
}
From 853ead65e0b48d87685dd0e60f62d3e117803ca3 Mon Sep 17 00:00:00 2001
From: Wx-2025 <351320169@qq.com>
Date: Sun, 19 Oct 2025 17:18:42 +0800
Subject: [PATCH 4/5] Update prese-settings.html
---
PresetSettings/prese-settings.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/PresetSettings/prese-settings.html b/PresetSettings/prese-settings.html
index a76f4de..541e9ca 100644
--- a/PresetSettings/prese-settings.html
+++ b/PresetSettings/prese-settings.html
@@ -10,7 +10,7 @@
overflow-y: auto;
border: 1px solid #444;
border-radius: 6px;
- padding: 12px 12px 100px 12px;
+ padding: 12px 12px 150px 12px;
background: #2a2a2a;
}
From c357e4cb9e624cb34bd1147add7b23c3e9a02644 Mon Sep 17 00:00:00 2001
From: Wx-2025 <351320169@qq.com>
Date: Sun, 19 Oct 2025 17:26:08 +0800
Subject: [PATCH 5/5] Update prese_state.js
---
PresetSettings/prese_state.js | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/PresetSettings/prese_state.js b/PresetSettings/prese_state.js
index 30ec597..9993522 100644
--- a/PresetSettings/prese_state.js
+++ b/PresetSettings/prese_state.js
@@ -202,7 +202,15 @@ export function loadActivePreset() {
localStorage.setItem(SETTINGS_KEY, JSON.stringify(presetManager));
toastr.info("Amily2 提示词预设已自动更新以支持最新功能。");
}
+ const novelProcessorOrder = currentMixedOrder.novel_processor || [];
+ const hasChapterContent = novelProcessorOrder.some(item => item.type === 'conditional' && item.id === 'chapterContent');
+ if (!hasChapterContent) {
+ console.log("Amily2: 检测到 novel_processor 缺少 chapterContent 条件块,正在执行迁移...");
+ currentPresets.novel_processor = JSON.parse(JSON.stringify(defaultPrompts.novel_processor));
+ currentMixedOrder.novel_processor = JSON.parse(JSON.stringify(defaultMixedOrder.novel_processor));
+ isMigrated = true;
+ }
} else {
const firstPresetName = Object.keys(presetManager.presets)[0];
if (firstPresetName) {