diff --git a/api.js b/api.js index 2f8aa54..08d1ee3 100644 --- a/api.js +++ b/api.js @@ -1,3 +1,4 @@ +// @ts-nocheck // api.js // [重构] 核心API模块,支持“自定义API”和“酒馆预设”两种模式 import { getRequestHeaders } from '/script.js'; @@ -40,11 +41,7 @@ function normalizeApiResponse(responseData) { /** * 主API调用入口,根据设置选择不同的模式 */ -<<<<<<< HEAD -export async function callInterceptionApi(messages, apiSettings, abortSignal) { -======= export async function callInterceptionApi(messages, apiSettings, abortSignal = null) { ->>>>>>> 9d9294a0040fefed67bebf2d6763acb7f1f2d288 // messages 已经在 index.js 中构建完成,直接使用 // apiSettings 包含API连接配置 diff --git a/index.js b/index.js index 37ebf44..e24ca84 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,4 @@ +// @ts-nocheck // 剧情规划大师 - 聊天优化插件 // 由Cline移植并重构 @@ -13,8 +14,6 @@ console.log('[剧情优化大师] v3.5.1 Loading... (Timestamp: ' + Date.now() + const extension_name = 'quick-response-force'; let isProcessing = false; let tempPlotToSave = null; // [架构重构] 用于在生成和消息创建之间临时存储plot -let currentAbortController_QRF = null; // [新增] 用于中止正在进行的AI请求(剧情规划) -let wasStoppedByUser_QRF = false; // [新增] 标记本次规划是否被用户手动终止 /** * [新增] 从世界书中直接提取数据库生成的 OutlineTable(总结大纲/总体大纲)条目内容,作为 $5 的兜底来源。 @@ -668,56 +667,6 @@ async function runOptimizationLogic(userMessage) { return null; // 插件未启用,直接返回 } -<<<<<<< HEAD - // 重置中止控制器与标志(参考数据库版本的终止按钮行为) - wasStoppedByUser_QRF = false; - currentAbortController_QRF = new AbortController(); - - // 创建带“终止”按钮的 Toast - const toastMsg = ` -
- 正在规划剧情... - -
- `; - $toast = toastr.info(toastMsg, '剧情规划大师', { - timeOut: 0, - extendedTimeOut: 0, - escapeHtml: false, - tapToDismiss: false, - closeButton: false, - progressBar: false, - }); - - // 绑定终止按钮(优先绑定当前 toast 内按钮,避免误绑到旧 toast) - setTimeout(() => { - const $abortBtn = ($toast && $toast.find) ? $toast.find('.qrf-abort-btn') : $('.qrf-abort-btn'); - if ($abortBtn.length > 0) { - $abortBtn.off('click').on('click', function (e) { - e.preventDefault(); - e.stopPropagation(); - - wasStoppedByUser_QRF = true; - if (currentAbortController_QRF) currentAbortController_QRF.abort(); - - try { - if ($toast) toastr.clear($toast); - const $toastDom = $(this).closest('.toast'); - if ($toastDom && $toastDom.length) $toastDom.remove(); - } catch (err) {} - - // 强制释放锁,避免卡死 - isProcessing = false; - - // 用户主动终止属于正常流程,不弹“错误” - setTimeout(() => toastr.info('规划任务已被用户中止。', '提示', { timeOut: 1500 }), 300); - }); - } - }, 0); -======= // 重置中止控制器 abortController = new AbortController(); @@ -735,7 +684,6 @@ async function runOptimizationLogic(userMessage) { escapeHtml: false, tapToDismiss: false }); ->>>>>>> 9d9294a0040fefed67bebf2d6763acb7f1f2d288 const context = getContext(); const character = characters[this_chid]; @@ -989,7 +937,9 @@ async function runOptimizationLogic(userMessage) { for (let i = 0; i < relayFlows.length; i++) { const flow = relayFlows[i]; if (!flow.enabled) continue; - if (wasStoppedByUser_QRF) throw new Error('TaskAbortedByUser'); + if (abortController && abortController.signal.aborted) { + throw new Error('TaskAbortedByUser'); + } $toast.find('.toastr-message').text(`正在规划剧情... (接力流程:${flow.injectKey})`); @@ -1005,7 +955,7 @@ async function runOptimizationLogic(userMessage) { const overrides = flow.apiProfileId ? getApiProfileOverrides(flow.apiProfileId) : null; const flowApiSettings = { ...apiSettings, ...(overrides || {}), extractTags: '' }; - const flowResult = await callInterceptionApi(flowMessages, flowApiSettings, currentAbortController_QRF?.signal); + const flowResult = await callInterceptionApi(flowMessages, flowApiSettings, abortController?.signal); // null 表示中止或错误:不覆盖旧输出 if (flowResult !== null && flowResult !== undefined) { // 每个流程可配置独立的标签摘取:注入/保存的是提取后的内容;留空则保存全量 @@ -1066,13 +1016,7 @@ async function runOptimizationLogic(userMessage) { if (minLength > 0) { for (let i = 0; i < maxRetries; i++) { -<<<<<<< HEAD - if (wasStoppedByUser_QRF) { - throw new Error('TaskAbortedByUser'); - } -======= checkAbort(); ->>>>>>> 9d9294a0040fefed67bebf2d6763acb7f1f2d288 $toast.find('.toastr-message').text(`正在规划剧情... (尝试 ${i + 1}/${maxRetries})`); if (willUseMainApiGenerateRaw) { @@ -1080,14 +1024,9 @@ async function runOptimizationLogic(userMessage) { } // 直接传递构建好的 messages 数组 -<<<<<<< HEAD - const tempMessage = await callInterceptionApi(messages, finalApiSettings, currentAbortController_QRF?.signal); -======= const tempMessage = await callInterceptionApi(messages, finalApiSettings, abortController.signal); checkAbort(); // API 调用后再次检查 - ->>>>>>> 9d9294a0040fefed67bebf2d6763acb7f1f2d288 if (tempMessage && tempMessage.length >= minLength) { processedMessage = tempMessage; if ($toast) toastr.clear($toast); @@ -1100,19 +1039,12 @@ async function runOptimizationLogic(userMessage) { } } } else { -<<<<<<< HEAD - if (wasStoppedByUser_QRF) { - throw new Error('TaskAbortedByUser'); - } - processedMessage = await callInterceptionApi(messages, finalApiSettings, currentAbortController_QRF?.signal); -======= checkAbort(); if (willUseMainApiGenerateRaw) { planningGuard.ignoreNextGenerationEndedCount++; } processedMessage = await callInterceptionApi(messages, finalApiSettings, abortController.signal); checkAbort(); ->>>>>>> 9d9294a0040fefed67bebf2d6763acb7f1f2d288 } if (processedMessage) { @@ -1129,49 +1061,11 @@ async function runOptimizationLogic(userMessage) { .map(t => t.trim()) .filter(t => t); if (tagNames.length > 0) { -<<<<<<< HEAD const extracted = extractLastBlocksPerTag(processedMessage, tagNames); if (extracted) { messageForTavern = extracted; console.log(`[${extension_name}] 成功按标签分别摘取最后一次匹配: ${tagNames.join(', ')}`); toastr.info(`已成功按标签分别摘取最后一次匹配并注入。`, '标签摘取'); -======= - const extractedParts = []; - - // [健全性] 仅提取“最后一组”标签的内容(支持处理类似 123456 的异常嵌套) - // 规则:找到最后一个 ,再回溯找到它之前最近的 ,只取两者之间的内容。 - const extractLastTagContent = (text, rawTagName) => { - if (!text || !rawTagName) return null; - const tagName = String(rawTagName).trim(); - if (!tagName) return null; - - const lower = text.toLowerCase(); - const open = `<${tagName.toLowerCase()}>`; - const close = ``; - - const closeIdx = lower.lastIndexOf(close); - if (closeIdx === -1) return null; - - const openIdx = lower.lastIndexOf(open, closeIdx); - if (openIdx === -1) return null; - - const contentStart = openIdx + open.length; - const content = text.slice(contentStart, closeIdx); - return content; - }; - - tagNames.forEach(tagName => { - const content = extractLastTagContent(processedMessage, tagName); - if (content !== null) { - extractedParts.push(`<${tagName}>${content}`); - } - }); - - if (extractedParts.length > 0) { - messageForTavern = extractedParts.join('\n\n'); - console.log(`[${extension_name}] 成功摘取标签: ${tagNames.join(', ')}`); - toastr.info(`已成功摘取 [${tagNames.join(', ')}] 标签内容并注入。`, '标签摘取'); ->>>>>>> 9d9294a0040fefed67bebf2d6763acb7f1f2d288 } else { console.log(`[${extension_name}] 在回复中未找到指定标签: ${tagNames.join(', ')}`); } @@ -1194,16 +1088,10 @@ async function runOptimizationLogic(userMessage) { return null; } } catch (error) { -<<<<<<< HEAD if (error?.message === 'TaskAbortedByUser') { - // 用户主动终止:正常流程,不提示“规划失败” if ($toast) toastr.clear($toast); - return null; -======= - if (error.message === 'TaskAbortedByUser') { - // 用户中止,返回特殊标记对象 - return { aborted: true }; ->>>>>>> 9d9294a0040fefed67bebf2d6763acb7f1f2d288 + // 用户中止,返回特殊标记对象 + return { aborted: true }; } console.error(`[${extension_name}] 在核心优化逻辑中发生错误:`, error); if ($toast) toastr.clear($toast); @@ -1394,9 +1282,11 @@ jQuery(async () => { // 首次加载时,执行一次预设加载和数据清理 loadPresetAndCleanCharacterData(); - const intervalId = setInterval(async () => { - // 确保UI和TavernHelper都已加载 - if ($('#extensions_settings').length > 0 && window.TavernHelper) { + const intervalId = setInterval(async () => { + // 确保UI和TavernHelper都已加载(兼容不同布局) + const hasSettingsContainer = + $('#extensions_settings').length > 0 || $('#extensions_settings2').length > 0; + if (hasSettingsContainer && window.TavernHelper) { clearInterval(intervalId); try { loadPluginStyles(); diff --git a/manifest.json b/manifest.json index 0bda359..f307cd0 100644 --- a/manifest.json +++ b/manifest.json @@ -1,17 +1,13 @@ { "name": "剧情优化大师", "display_name": "剧情优化大师", -<<<<<<< HEAD - "version": "2.1.2", -======= - "version": "3.5.1", ->>>>>>> 9d9294a0040fefed67bebf2d6763acb7f1f2d288 + "version": "3.5.2", "author": "Cline", "description": "一个独立的聊天优化插件,通过在生成前预处理用户输入,增强上下文和关键词,从而提升故事叙述的质量。", "minSillyTavernVersion": "1.10.0", "requires": [], "loading_order": 101, - "js": "index.js", + "js": "index.js?v=3.5.2", "css": "style.css", "persistent_files": [ "settings.json" diff --git a/settings.html b/settings.html index 53794a3..2eefa5a 100644 --- a/settings.html +++ b/settings.html @@ -163,8 +163,6 @@

剧情优化大师 - 设置 -<<<<<<< HEAD -
@@ -251,14 +249,6 @@

剧情优化大师 - 设置 内容设置 -======= -

- - - - diff --git a/ui/bindings.js b/ui/bindings.js index a2bba5a..411cfce 100644 --- a/ui/bindings.js +++ b/ui/bindings.js @@ -1,3 +1,4 @@ +// @ts-nocheck // 剧情优化大师 - UI数据绑定模块 // 由Cline参照 '优化/' 插件的健壮性实践重构 @@ -465,7 +466,6 @@ export async function loadWorldbookEntries(panel) { container.empty(); totalEntries = allEntries.length; -<<<<<<< HEAD // [新功能] 迁移逻辑:首次加载时,将当前所有条目设为未选中(加入禁用列表), // 从而实现“默认全不勾选,新增条目自动勾选”的效果。 if (this_chid !== -1 && characters[this_chid]) { @@ -513,9 +513,6 @@ export async function loadWorldbookEntries(panel) { } } } - -======= ->>>>>>> 9d9294a0040fefed67bebf2d6763acb7f1f2d288 if (totalEntries === 0) { container.html('

所选世界书没有条目。

'); countDisplay.text('0 条目.'); @@ -1908,11 +1905,10 @@ export function initializeBindings() { panel.find('#qrf_extract_tags').val(presetData.extractTags); panel.find('#qrf_min_length').val(presetData.minLength); panel.find('#qrf_context_turn_count').val(presetData.contextTurnCount); -<<<<<<< HEAD renderRelayFlows(panel, presetData.relayFlows); -======= panel.find('#qrf_worldbook_char_limit').val(presetData.worldbookCharLimit); ->>>>>>> 9d9294a0040fefed67bebf2d6763acb7f1f2d288 + panel.find('#qrf_worldbook_char_limit').val(presetData.worldbookCharLimit); + renderRelayFlows(panel, presetData.relayFlows); // 2. 直接、同步地覆盖apiSettings中的内容 // saveSetting现在是异步的,我们需要等待它完成 diff --git a/ui/drawer.js b/ui/drawer.js index 8e53ca9..175e2ec 100644 --- a/ui/drawer.js +++ b/ui/drawer.js @@ -27,8 +27,15 @@ export async function createDrawer() { `; - // 将面板添加到SillyTavern的扩展设置区域 - $('#extensions_settings2').append(extensionHtml); + // 将面板添加到SillyTavern的扩展设置区域(兼容不同布局) + const settingsContainer = $('#extensions_settings2').length + ? $('#extensions_settings2') + : $('#extensions_settings'); + if (!settingsContainer.length) { + console.warn(`[${extensionName}] 未找到扩展设置容器,无法挂载设置面板。`); + return; + } + settingsContainer.append(extensionHtml); try { const contentWrapper = $('#qrf_extension_frame .inline-drawer-content'); diff --git a/utils/settings.js b/utils/settings.js index ac3d1a8..7a5d044 100644 --- a/utils/settings.js +++ b/utils/settings.js @@ -133,7 +133,6 @@ export const defaultSettings = { rateErotic: 0, rateCuckold: 10, selectedWorldbooks: [], // 新增:存储选中的世界书 -<<<<<<< HEAD disabledWorldbookEntries: '__ALL_SELECTED__', // [功能更新] 默认全选所有条目,支持屏蔽词过滤 // [新增] 接力思考流程:多套提示词链式执行,并把输出缓存到 $A1/$A2... 供后续提示词注入 // 数据结构: @@ -146,9 +145,6 @@ export const defaultSettings = { // lastOutput: string, // 最新一次执行输出(会被覆盖) // }> relayFlows: [], -======= - disabledWorldbookEntries: '__ALL_SELECTED__', // 默认全选,若有取消勾选则记录 { worldbookName: [uid1, uid2] } ->>>>>>> 9d9294a0040fefed67bebf2d6763acb7f1f2d288 prompts: [ { id: 1764467961649,