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'; const moduleState = { selectedWorldBook: '', }; function updateAndSaveSetting(key, value) { if (!extension_settings[extensionName]) { extension_settings[extensionName] = {}; } extension_settings[extensionName][key] = value; saveSettingsDebounced(); console.log(`[Amily2-术语表] 设置项 '${key}' 已更新为: ${JSON.stringify(value)}`); } function loadSettingsToUI() { const settings = extension_settings[extensionName] || {}; const container = document.getElementById('amily2_glossary_panel'); if (!container) return; 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); } } 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) => `