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 `${body}
源头关系目标
`; + } 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 `${header}${body}
`; + } catch { + return `
${content}
`; + } + } + return `
${content}
`; + }; + + entryElement.innerHTML = ` +
+ ${title} +
+ + + +
+
+
${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事件绑定完成 (最终重构版)。');