mirror of
https://github.com/SilenceLurker/ST-Amily2-Chat-Optimisation.git
synced 2026-06-06 14:45:51 +00:00
Add files via upload
This commit is contained in:
289
glossary/GT_bindings.js
Normal file
289
glossary/GT_bindings.js
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
import { extension_settings, getContext } from "/scripts/extensions.js";
|
||||||
|
import { saveSettingsDebounced } from "/script.js";
|
||||||
|
import { extensionName } from "../utils/settings.js";
|
||||||
|
import { testSybdApiConnection, fetchSybdModels } from '../core/api/SybdApi.js';
|
||||||
|
import { handleFileUpload, recognizeChapters, processNovel } from './index.js';
|
||||||
|
import { SETTINGS_KEY as PRESET_SETTINGS_KEY } from '../PresetSettings/config.js';
|
||||||
|
|
||||||
|
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 sybdToggle = document.getElementById('amily2_sybd_enabled');
|
||||||
|
const sybdContent = document.getElementById('amily2_sybd_content');
|
||||||
|
if (sybdToggle && sybdContent) {
|
||||||
|
sybdContent.classList.toggle('amily2-content-hidden', !sybdToggle.checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 === 'sybdEnabled') {
|
||||||
|
document.getElementById('amily2_sybd_content').classList.toggle('amily2-content-hidden', !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 = '<option value="">-- 加载中 --</option>';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const context = getContext();
|
||||||
|
const tavernProfiles = context.extensionSettings?.connectionManager?.profiles || [];
|
||||||
|
|
||||||
|
select.innerHTML = '<option value="">-- 请选择预设 --</option>';
|
||||||
|
|
||||||
|
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 = '<option value="">未找到可用预设</option>';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Amily2-术语表] 加载SillyTavern预设失败:', error);
|
||||||
|
select.innerHTML = '<option value="">加载失败</option>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = '<i class="fas fa-spinner fa-spin"></i> 测试中';
|
||||||
|
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 = '<i class="fas fa-spinner fa-spin"></i> 获取中';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const models = await fetchSybdModels();
|
||||||
|
if (models && models.length > 0) {
|
||||||
|
modelSelect.innerHTML = '<option value="">-- 请选择模型 --</option>';
|
||||||
|
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 }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
|
|
||||||
|
if (fileLabel && fileInput) {
|
||||||
|
fileLabel.addEventListener('click', (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
fileInput.click();
|
||||||
|
});
|
||||||
|
fileInput.addEventListener('change', (event) => {
|
||||||
|
handleFileUpload(event.target.files[0]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recognizeBtn) {
|
||||||
|
recognizeBtn.addEventListener('click', async () => {
|
||||||
|
const originalHtml = recognizeBtn.innerHTML;
|
||||||
|
recognizeBtn.disabled = true;
|
||||||
|
recognizeBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 识别中...';
|
||||||
|
|
||||||
|
await recognizeChapters();
|
||||||
|
|
||||||
|
recognizeBtn.disabled = false;
|
||||||
|
recognizeBtn.innerHTML = originalHtml;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (processBtn) {
|
||||||
|
processBtn.addEventListener('click', async () => {
|
||||||
|
const originalHtml = processBtn.innerHTML;
|
||||||
|
processBtn.disabled = true;
|
||||||
|
processBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 处理中...';
|
||||||
|
|
||||||
|
await processNovel();
|
||||||
|
|
||||||
|
processBtn.disabled = false;
|
||||||
|
processBtn.innerHTML = originalHtml;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
panel.dataset.eventsBound = 'true';
|
||||||
|
console.log('[Amily2-术语表] UI事件绑定完成 (最终重构版)。');
|
||||||
|
}
|
||||||
105
glossary/executor.js
Normal file
105
glossary/executor.js
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import { callSybdAI } from '../core/api/SybdApi.js';
|
||||||
|
import { getTargetWorldBook, syncNovelLorebookEntries } from '../CharacterWorldBook/src/cwb_lorebookManager.js';
|
||||||
|
import { getPresetPrompts, getMixedOrder } from '../PresetSettings/index.js';
|
||||||
|
import { generateRandomSeed } from '../core/api.js';
|
||||||
|
|
||||||
|
const { TavernHelper } = window;
|
||||||
|
|
||||||
|
function parseStructuredResponse(responseText) {
|
||||||
|
const entries = [];
|
||||||
|
const entryRegex = /【(.*?)】.*?\[START_TABLE\]([\s\S]*?)\[END_TABLE\]/g;
|
||||||
|
let match;
|
||||||
|
|
||||||
|
while ((match = entryRegex.exec(responseText)) !== null) {
|
||||||
|
const title = match[1].trim();
|
||||||
|
const content = match[2].trim();
|
||||||
|
if (title && content) {
|
||||||
|
entries.push({ title, content });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function executeNovelProcessing(recognizedChapters, batchSize, forceNew, updateStatusCallback) {
|
||||||
|
if (recognizedChapters.length === 0) {
|
||||||
|
updateStatusCallback('没有可处理的章节。', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStatusCallback('开始处理小说...', 'info');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const bookName = await getTargetWorldBook();
|
||||||
|
if (!bookName) throw new Error('无法确定目标世界书。');
|
||||||
|
let existingEntriesContent = '当前世界书为空。';
|
||||||
|
if (!forceNew) {
|
||||||
|
const allEntries = (await TavernHelper.getLorebookEntries(bookName)) || [];
|
||||||
|
const managedEntries = allEntries.filter(e => e.comment?.startsWith(`[Amily2小说处理]`));
|
||||||
|
if (managedEntries.length > 0) {
|
||||||
|
existingEntriesContent = managedEntries.map(entry => {
|
||||||
|
return `【${entry.keyword}】\n[START_TABLE]\n${entry.content}\n[END_TABLE]`;
|
||||||
|
}).join('\n\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < recognizedChapters.length; i += batchSize) {
|
||||||
|
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') {
|
||||||
|
switch (item.id) {
|
||||||
|
case 'existingLore':
|
||||||
|
messages.push({ role: 'user', content: `# 已有世界书条目\n\n${existingEntriesContent}` });
|
||||||
|
break;
|
||||||
|
case 'chapterContent':
|
||||||
|
messages.push({ role: 'user', content: `# 最新章节内容\n\n${chapterContent}\n\n请根据以上信息,分析并输出需要新增或更新的世界书条目。` });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (messages.length <= 1) {
|
||||||
|
throw new Error('未能根据预设构建有效的API请求。');
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await callSybdAI(messages);
|
||||||
|
if (!response || response.trim() === '无需更新') {
|
||||||
|
updateStatusCallback(`批次 ${Math.floor(i / batchSize) + 1} 无需更新。`, 'info');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const structuredData = parseStructuredResponse(response);
|
||||||
|
if (structuredData.length === 0) {
|
||||||
|
updateStatusCallback(`批次 ${Math.floor(i / batchSize) + 1} 未提取到有效信息。`, 'info');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await syncNovelLorebookEntries(bookName, structuredData);
|
||||||
|
existingEntriesContent = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStatusCallback('小说处理完成!', 'success');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('处理小说时发生严重错误:', error);
|
||||||
|
updateStatusCallback(`处理失败: ${error.message}`, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
126
glossary/index.js
Normal file
126
glossary/index.js
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
import { executeNovelProcessing } from './executor.js';
|
||||||
|
|
||||||
|
let novelText = null;
|
||||||
|
let recognizedChaptersList = [];
|
||||||
|
|
||||||
|
const getNovelFileInput = () => document.getElementById('novel-file-input');
|
||||||
|
const getChapterRegexInput = () => document.getElementById('novel-chapter-regex');
|
||||||
|
const getRecognizeBtn = () => document.getElementById('novel-recognize-chapters');
|
||||||
|
const getProcessBtn = () => document.getElementById('novel-confirm-and-process');
|
||||||
|
const getChapterPreview = () => document.getElementById('novel-chapter-preview');
|
||||||
|
const getChapterCount = () => document.getElementById('novel-chapter-count');
|
||||||
|
const getStatusDisplay = () => document.getElementById('novel-process-status');
|
||||||
|
const getPresetSelect = () => document.getElementById('novel-preset-select');
|
||||||
|
const getBatchSizeInput = () => document.getElementById('novel-batch-size');
|
||||||
|
const getForceNewCheckbox = () => document.getElementById('novel-force-new');
|
||||||
|
|
||||||
|
export function updateStatus(message, type = 'info') {
|
||||||
|
const statusDisplay = getStatusDisplay();
|
||||||
|
if (statusDisplay) {
|
||||||
|
statusDisplay.textContent = message;
|
||||||
|
statusDisplay.style.color = type === 'error' ? '#ff8a8a' : (type === 'success' ? '#8aff8a' : '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetChapterUI() {
|
||||||
|
const preview = getChapterPreview();
|
||||||
|
const count = getChapterCount();
|
||||||
|
const processBtn = getProcessBtn();
|
||||||
|
if (preview) preview.innerHTML = '<small>请先上传文件并识别章节...</small>';
|
||||||
|
if (count) count.textContent = '0';
|
||||||
|
if (processBtn) processBtn.disabled = true;
|
||||||
|
recognizedChaptersList = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleFileUpload(file) {
|
||||||
|
if (!file || !file.type.startsWith('text/')) {
|
||||||
|
updateStatus('请选择一个有效的 .txt 文件。', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (event) => {
|
||||||
|
novelText = event.target.result;
|
||||||
|
updateStatus(`文件 "${file.name}" 已成功加载。请点击“识别章节”。`, 'success');
|
||||||
|
resetChapterUI();
|
||||||
|
};
|
||||||
|
reader.onerror = () => {
|
||||||
|
updateStatus(`读取文件 "${file.name}" 时发生错误。`, 'error');
|
||||||
|
novelText = null;
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function recognizeChapters() {
|
||||||
|
if (!novelText) {
|
||||||
|
updateStatus('请先上传一个小说文件。', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const regexInput = getChapterRegexInput();
|
||||||
|
const customRegex = regexInput.value.trim();
|
||||||
|
const defaultRegex = '(^\\s*(?:(?:第|卷)\\s*[一二三四五六七八九十百千万零〇\\d]+\\s*[章回节部篇]|Chapter\\s+\\d+|\\d+\\s*[.、]|序章|楔子|引子|序幕|尾声|终章|后记|番外)\\s*.*)';
|
||||||
|
let finalRegex;
|
||||||
|
try {
|
||||||
|
finalRegex = new RegExp(customRegex || defaultRegex, 'gm');
|
||||||
|
} catch (e) {
|
||||||
|
updateStatus('无效的正则表达式。', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStatus('正在识别章节...', 'info');
|
||||||
|
recognizedChaptersList = [];
|
||||||
|
const matches = [...novelText.matchAll(finalRegex)];
|
||||||
|
|
||||||
|
if (matches.length > 0) {
|
||||||
|
for (let i = 0; i < matches.length; i++) {
|
||||||
|
const currentMatch = matches[i];
|
||||||
|
const nextMatch = matches[i + 1];
|
||||||
|
|
||||||
|
const title = currentMatch[0].trim();
|
||||||
|
const startIndex = currentMatch.index + currentMatch[0].length;
|
||||||
|
const endIndex = nextMatch ? nextMatch.index : novelText.length;
|
||||||
|
|
||||||
|
const content = novelText.substring(startIndex, endIndex).trim();
|
||||||
|
|
||||||
|
if (title) {
|
||||||
|
recognizedChaptersList.push({ title, content });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const preview = getChapterPreview();
|
||||||
|
const count = getChapterCount();
|
||||||
|
const processBtn = getProcessBtn();
|
||||||
|
|
||||||
|
if (preview) {
|
||||||
|
preview.innerHTML = recognizedChaptersList.map((chap, index) => `<div>${index + 1}. ${chap.title}</div>`).join('');
|
||||||
|
}
|
||||||
|
if (count) {
|
||||||
|
count.textContent = recognizedChaptersList.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recognizedChaptersList.length > 0) {
|
||||||
|
processBtn.disabled = false;
|
||||||
|
updateStatus(`成功识别 ${recognizedChaptersList.length} 个章节。请预览并确认。`, 'success');
|
||||||
|
} else {
|
||||||
|
updateStatus('未能识别出章节。请尝试调整正则表达式或检查文件内容。', 'error');
|
||||||
|
processBtn.disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function processNovel() {
|
||||||
|
const processBtn = getProcessBtn();
|
||||||
|
processBtn.disabled = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const batchSize = parseInt(getBatchSizeInput().value, 10);
|
||||||
|
const forceNew = getForceNewCheckbox().checked;
|
||||||
|
|
||||||
|
await executeNovelProcessing(recognizedChaptersList, batchSize, forceNew, updateStatus);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('处理小说时发生UI层错误:', error);
|
||||||
|
updateStatus(`处理失败: ${error.message}`, 'error');
|
||||||
|
} finally {
|
||||||
|
processBtn.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user