From 6a5168f8ed35bbcfa1173ad34aa70b1898fef84f Mon Sep 17 00:00:00 2001
From: Wx-2025 <351320169@qq.com>
Date: Sun, 19 Oct 2025 01:39:05 +0800
Subject: [PATCH] Update GT_bindings.js
---
glossary/GT_bindings.js | 395 +++++++++++++++++++++++++++++++++++++---
1 file changed, 365 insertions(+), 30 deletions(-)
diff --git a/glossary/GT_bindings.js b/glossary/GT_bindings.js
index 025e73d..4048fe0 100644
--- a/glossary/GT_bindings.js
+++ b/glossary/GT_bindings.js
@@ -1,10 +1,15 @@
import { extension_settings, getContext } from "/scripts/extensions.js";
import { saveSettingsDebounced } 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, recognizeChapters, processNovel } from './index.js';
+import { handleFileUpload, processNovel } from './index.js';
import { SETTINGS_KEY as PRESET_SETTINGS_KEY } from '../PresetSettings/config.js';
+const moduleState = {
+ selectedWorldBook: '',
+};
+
function updateAndSaveSetting(key, value) {
if (!extension_settings[extensionName]) {
extension_settings[extensionName] = {};
@@ -49,10 +54,9 @@ function loadSettingsToUI() {
}
});
- const sybdToggle = document.getElementById('amily2_sybd_enabled');
const sybdContent = document.getElementById('amily2_sybd_content');
- if (sybdToggle && sybdContent) {
- sybdContent.classList.toggle('amily2-content-hidden', !sybdToggle.checked);
+ if (sybdContent) {
+ sybdContent.classList.remove('amily2-content-hidden');
}
const apiModeSelect = document.getElementById('amily2_sybd_api_mode');
@@ -87,9 +91,6 @@ function bindAutoSaveEvents() {
updateAndSaveSetting(key, value);
- if (key === 'sybdEnabled') {
- document.getElementById('amily2_sybd_content').classList.toggle('amily2-content-hidden', !value);
- }
if (key === 'sybdApiMode') {
updateConfigVisibility(value);
}
@@ -204,6 +205,178 @@ function bindManualActionEvents() {
}
}
+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');
@@ -223,6 +396,9 @@ function bindTabEvents() {
}
});
+ if (tabId === 'context') {
+ renderWorldBookEntries();
+ }
});
});
}
@@ -230,46 +406,190 @@ function bindTabEvents() {
function bindNovelProcessEvents() {
const fileInput = document.getElementById('novel-file-input');
const fileLabel = document.querySelector('label[for="novel-file-input"]');
- const recognizeBtn = document.getElementById('novel-recognize-chapters');
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 = true;
+ 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();
+ event.preventDefault();
+ fileInput.click();
});
fileInput.addEventListener('change', (event) => {
- handleFileUpload(event.target.files[0]);
+ handleFileUpload(event.target.files[0], (content) => {
+ fileContent = content;
+ updateChunks();
+ });
});
}
- if (recognizeBtn) {
- recognizeBtn.addEventListener('click', async () => {
- const originalHtml = recognizeBtn.innerHTML;
- recognizeBtn.disabled = true;
- recognizeBtn.innerHTML = ' 识别中...';
-
- await recognizeChapters();
-
- recognizeBtn.disabled = false;
- recognizeBtn.innerHTML = originalHtml;
- });
+ if (chunkSizeInput) {
+ chunkSizeInput.addEventListener('input', updateChunks);
}
+
if (processBtn) {
processBtn.addEventListener('click', async () => {
- const originalHtml = processBtn.innerHTML;
- processBtn.disabled = true;
- processBtn.innerHTML = ' 处理中...';
-
- await processNovel();
-
- processBtn.disabled = false;
- processBtn.innerHTML = originalHtml;
+ 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();
+ }
});
}
}
+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];
+ }
+}
+
+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 || '';
+
+ 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) {
@@ -283,6 +603,21 @@ export function bindGlossaryEvents() {
bindManualActionEvents();
bindTabEvents();
bindNovelProcessEvents();
+ 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事件绑定完成 (最终重构版)。');