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 += `| 源头 | 关系 | 目标 |
|---|
${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 => `${content}`;
- }
- }
- return `${content}`;
- };
-
- entryElement.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) => - `