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 `${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'); - - 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 `${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'); + + 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) {