From 051bcee3a05fc98984158ef5f104b85c16354d60 Mon Sep 17 00:00:00 2001 From: Wx-2025 <351320169@qq.com> Date: Fri, 24 Oct 2025 19:53:06 +0800 Subject: [PATCH 01/49] Update GT_bindings.js --- glossary/GT_bindings.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/glossary/GT_bindings.js b/glossary/GT_bindings.js index f9e4708..24452f7 100644 --- a/glossary/GT_bindings.js +++ b/glossary/GT_bindings.js @@ -659,8 +659,9 @@ export function bindGlossaryEvents() { bindReorganizeEvents(); loadWorldBooks(); - eventSource.on(event_types.CHARACTER_PAGE_LOADED, () => { - console.log('[Amily2-术语表] 检测到角色加载,重新加载世界书列表以确保同步。'); + // 监听我们自己的世界书创建事件,而不是监听全局的角色加载事件,避免冲突 + document.addEventListener('amily-lorebook-created', (event) => { + console.log(`[Amily2-术语表] 检测到新世界书《${event.detail.bookName}》创建,重新加载列表以确保同步。`); loadWorldBooks(); }); From 9803ec7c5198b7a16e1b9a7069f32fb25e7753a9 Mon Sep 17 00:00:00 2001 From: Wx-2025 <351320169@qq.com> Date: Fri, 24 Oct 2025 19:53:54 +0800 Subject: [PATCH 02/49] Update lore.js --- core/lore.js | 45 +++++++++++++++++---------------------------- 1 file changed, 17 insertions(+), 28 deletions(-) diff --git a/core/lore.js b/core/lore.js index 98c458e..243a672 100644 --- a/core/lore.js +++ b/core/lore.js @@ -1,10 +1,18 @@ import { extension_settings, getContext } from "/scripts/extensions.js"; import { characters, eventSource, event_types } from "/script.js"; -import { loadWorldInfo, createNewWorldInfo, createWorldInfoEntry, saveWorldInfo, world_names } from "/scripts/world-info.js"; +import { loadWorldInfo, createNewWorldInfo, createWorldInfoEntry, saveWorldInfo, world_names, updateWorldInfoList } from "/scripts/world-info.js"; import { compatibleWriteToLorebook, safeLorebooks, safeCharLorebooks, safeLorebookEntries } from "./tavernhelper-compatibility.js"; import { extensionName } from "../utils/settings.js"; +document.addEventListener('amily-lorebook-created', (event) => { + if (event.detail && event.detail.bookName) { + console.log(`[Amily2-国史馆] 监听到史书《${event.detail.bookName}》变更,即刻通报工部刷新宫殿。`); + refreshWorldbookListOnly(event.detail.bookName); + } +}); + + export const LOREBOOK_PREFIX = "Amily2档案-"; export const DEDICATED_LOREBOOK_NAME = "Amily2号-国史馆"; export const INTRODUCTORY_TEXT = @@ -90,34 +98,15 @@ export async function getCombinedWorldbookContent(lorebookName) { } } -async function refreshWorldbookListOnly(newBookName = null) { - console.log("[Amily2号-工部-v1.3] 执行“圣谕广播”式UI更新..."); - try { - if (newBookName) { - if (Array.isArray(world_names) && !world_names.includes(newBookName)) { - world_names.push(newBookName); - world_names.sort(); - console.log(`[Amily2号-工部] 已将《${newBookName}》注入前端数据模型。`); - } else { - console.log(`[Amily2号-工部] 《${newBookName}》已存在于数据模型中,跳过注入。`); - } +export async function refreshWorldbookListOnly(newBookName = null) { + console.log("[Amily2号-工部-v2.0] 执行SillyTavern核心UI刷新..."); + try { + await updateWorldInfoList(); + console.log("[Amily2号-工部] SillyTavern核心刷新函数 (updateWorldInfoList) 调用成功。"); + } catch (error) { + console.error("[Amily2号-工部] 调用核心刷新函数时出错:", error); + toastr.error("Amily2号调用核心UI刷新函数时失败。", "核心刷新失败"); } - - if ( - eventSource && - typeof eventSource.emit === "function" && - event_types.CHARACTER_PAGE_LOADED - ) { - console.log(`[Amily2号-工部] 正在广播事件: ${event_types.CHARACTER_PAGE_LOADED}`); - eventSource.emit(event_types.CHARACTER_PAGE_LOADED); - console.log("[Amily2号-工部] “character_page_loaded”事件已广播,UI应已响应刷新。"); - } else { - console.error("[Amily2号] 致命错误: eventSource 或 event_types.CHARACTER_PAGE_LOADED 未找到。无法广播刷新事件。"); - toastr.error("Amily2号无法触发UI刷新。", "核心事件系统缺失"); - } - } catch (error) { - console.error("[Amily2号-工部] “圣谕广播”式刷新失败:", error); - } } export async function writeSummaryToLorebook(pendingData) { From bdb11e305adfe79004a2a0cde441274c95dae09b Mon Sep 17 00:00:00 2001 From: Wx-2025 <351320169@qq.com> Date: Fri, 24 Oct 2025 19:54:21 +0800 Subject: [PATCH 03/49] Update tavernhelper-compatibility.js --- core/tavernhelper-compatibility.js | 107 ++++++++++++++++++++++------- 1 file changed, 84 insertions(+), 23 deletions(-) diff --git a/core/tavernhelper-compatibility.js b/core/tavernhelper-compatibility.js index cd7180d..0205809 100644 --- a/core/tavernhelper-compatibility.js +++ b/core/tavernhelper-compatibility.js @@ -1,9 +1,17 @@ import { amilyHelper } from './tavern-helper/main.js'; -import { eventSource, event_types } from "/script.js"; +import { + world_names, + loadWorldInfo, + createNewWorldInfo, + createWorldInfoEntry, + saveWorldInfo, + reloadEditor +} from "/scripts/world-info.js"; +import { refreshWorldbookListOnly } from './lore.js'; -// 我们现在总是“可用”的,因为我们依赖自己的实现,而不是那个屎山酒馆。 +// 检查我们自己的 amilyHelper 是否存在 export function isTavernHelperAvailable() { - return true; + return typeof amilyHelper !== 'undefined' && amilyHelper !== null; } export async function compatibleTriggerSlash(command) { return await amilyHelper.triggerSlash(command); @@ -27,43 +35,96 @@ export async function safeUpdateLorebookEntries(bookName, entries) { export async function compatibleWriteToLorebook(targetLorebookName, entryComment, contentUpdateCallback, options = {}) { - console.log('[Amily助手-写入模块] 接收到的写入选项:', options); + console.log('[兼容写入模块] 接收到的写入选项:', options); + // 优先使用 AmilyHelper + if (isTavernHelperAvailable()) { + try { + console.log('[兼容写入模块] 检测到 AmilyHelper,优先使用新逻辑...'); + const entries = await amilyHelper.getLorebookEntries(targetLorebookName); + const existingEntry = entries.find((e) => e.comment === entryComment && e.enabled); + + if (existingEntry) { + const newContent = contentUpdateCallback(existingEntry.content); + await amilyHelper.setLorebookEntries(targetLorebookName, [{ uid: existingEntry.uid, content: newContent }]); + } else { + const newContent = contentUpdateCallback(null); + const { keys = [], isConstant = false, insertion_position, depth: insertion_depth } = options; + const positionMap = { 'before_char': 0, 'after_char': 1, 'before_an': 2, 'after_an': 3, 'at_depth': 4 }; + + const newEntryData = { + comment: entryComment, + content: newContent, + key: keys, + constant: isConstant, + position: positionMap[insertion_position] ?? 4, + depth: parseInt(insertion_depth) || 998, + enabled: true, + }; + await amilyHelper.createLorebookEntries(targetLorebookName, [newEntryData]); + } + console.log(`[Amily助手] 成功将条目 "${entryComment}" 写入《${targetLorebookName}》。`); + + // 派发被证明有效的自定义刷新事件 + document.dispatchEvent(new CustomEvent('amily-lorebook-created', { detail: { bookName: targetLorebookName } })); + refreshWorldbookListOnly(); // 刷新UI + return true; + } catch (error) { + console.error(`[Amily助手] 写入失败,将尝试回退到传统逻辑。错误:`, error); + toastr.warning('Amily助手写入失败,尝试使用传统方式...', '兼容模式'); + } + } + + // AmilyHelper 不可用或失败时的后备传统逻辑 try { - const entries = await amilyHelper.getLorebookEntries(targetLorebookName); - const existingEntry = entries.find((e) => e.comment === entryComment && e.enabled); + console.log('[兼容写入模块] AmilyHelper 不可用或失败,使用传统逻辑...'); + let bookData = await loadWorldInfo(targetLorebookName); + + if (!bookData) { + console.warn(`[传统逻辑] 世界书《${targetLorebookName}》不存在,将自动创建。`); + await createNewWorldInfo(targetLorebookName); + if (!world_names.includes(targetLorebookName)) { + world_names.push(targetLorebookName); + world_names.sort(); + refreshWorldbookListOnly(); // 刷新UI + } + document.dispatchEvent(new CustomEvent('amily-lorebook-created', { detail: { bookName: targetLorebookName } })); + bookData = await loadWorldInfo(targetLorebookName); + if (!bookData) throw new Error(`创建并加载世界书《${targetLorebookName}》失败。`); + } + + const existingEntry = Object.values(bookData.entries).find(e => e.comment === entryComment && !e.disable); if (existingEntry) { - const newContent = contentUpdateCallback(existingEntry.content); - await amilyHelper.setLorebookEntries(targetLorebookName, [{ uid: existingEntry.uid, content: newContent }]); + existingEntry.content = contentUpdateCallback(existingEntry.content); } else { - const newContent = contentUpdateCallback(null); + const newEntry = createWorldInfoEntry(targetLorebookName, bookData); const { keys = [], isConstant = false, insertion_position, depth: insertion_depth } = options; - const positionMap = { 'before_char': 0, 'after_char': 1, 'before_an': 2, 'after_an': 3, 'at_depth': 4 }; - - const newEntryData = { + + Object.assign(newEntry, { comment: entryComment, - content: newContent, + content: contentUpdateCallback(null), key: keys, constant: isConstant, position: positionMap[insertion_position] ?? 4, depth: parseInt(insertion_depth) || 998, - enabled: true, - }; - - await amilyHelper.createLorebookEntries(targetLorebookName, [newEntryData]); + disable: false, + }); } - if (eventSource && typeof eventSource.emit === "function" && event_types.CHARACTER_PAGE_LOADED) { - eventSource.emit(event_types.CHARACTER_PAGE_LOADED); - } + await saveWorldInfo(targetLorebookName, bookData, true); + console.log(`[传统逻辑] 成功将条目 "${entryComment}" 写入《${targetLorebookName}》。`); - console.log(`[Amily助手] 成功将条目 "${entryComment}" 写入《${targetLorebookName}》。`); + // 刷新编辑器(如果正在查看) + reloadEditor(targetLorebookName); + + // 派发被证明有效的自定义刷新事件 + document.dispatchEvent(new CustomEvent('amily-lorebook-created', { detail: { bookName: targetLorebookName } })); return true; } catch (error) { - console.error(`[Amily助手] 写入世界书时发生严重错误:`, error); - toastr.error(`写入世界书失败: ${error.message}`, "Amily助手"); + console.error(`[传统逻辑] 写入世界书时发生严重错误:`, error); + toastr.error(`写入世界书失败: ${error.message}`, "传统逻辑"); return false; } } From 9bb44fe07bd81706f4b10bcdfd85fe53e3159b71 Mon Sep 17 00:00:00 2001 From: Wx-2025 <351320169@qq.com> Date: Fri, 24 Oct 2025 19:54:47 +0800 Subject: [PATCH 04/49] Update historiographer.js --- core/historiographer.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/core/historiographer.js b/core/historiographer.js index a55c381..105c7b6 100644 --- a/core/historiographer.js +++ b/core/historiographer.js @@ -195,11 +195,8 @@ export async function executeManualSummary(startFloor, endFloor, isAuto = false) container.find('.historiography-message-item').each(function() { const item = $(this); const authorType = item.data('author-type'); - if ((authorType === 'user' && !includeUser) || (authorType === 'char' && !includeChar)) { - item.prop('hidden', true); - } else { - item.prop('hidden', false); - } + const shouldBeHidden = (authorType === 'user' && !includeUser) || (authorType === 'char' && !includeChar); + item.toggle(!shouldBeHidden); }); }; @@ -207,7 +204,17 @@ export async function executeManualSummary(startFloor, endFloor, isAuto = false) charCheckbox.on('change', updateVisibility); }, onOk: async (dialog) => { - const textToSummarize = dialog.find('.historiography-message-item:not([hidden]) textarea') + const includeUser = dialog.find('#hist-include-user').is(':checked'); + const includeChar = dialog.find('#hist-include-char').is(':checked'); + + const textToSummarize = dialog.find('.historiography-message-item') + .filter(function() { + const authorType = $(this).data('author-type'); + if (authorType === 'user' && !includeUser) return false; + if (authorType === 'char' && !includeChar) return false; + return true; + }) + .find('textarea') .map(function() { const floor = $(this).data('floor'); const author = $(this).closest('.historiography-message-item').find('summary').text().replace(`【第 ${floor} 楼】 `, ''); From 3c5e279920ec1382bb55b62f0b85b779b5606f19 Mon Sep 17 00:00:00 2001 From: Wx-2025 <351320169@qq.com> Date: Fri, 24 Oct 2025 19:56:10 +0800 Subject: [PATCH 05/49] Update WorldEditor.js --- WorldEditor/WorldEditor.js | 33 ++------------------------------- 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/WorldEditor/WorldEditor.js b/WorldEditor/WorldEditor.js index 70f87ad..d042773 100644 --- a/WorldEditor/WorldEditor.js +++ b/WorldEditor/WorldEditor.js @@ -428,37 +428,8 @@ class WorldEditor { */ async updateEntriesWithNativeMethod(entriesToUpdate) { try { - const bookData = await loadWorldInfo(this.currentWorldBook); - if (!bookData || !bookData.entries) { - throw new Error("无法加载世界书数据。"); - } - - const uidsToUpdate = new Set(entriesToUpdate.map(e => e.uid)); - const updatedUIDs = new Set(); - - // 更新 bookData.entries - for (const entry of entriesToUpdate) { - if (bookData.entries[entry.uid]) { - const nativeEntry = bookData.entries[entry.uid]; - nativeEntry.comment = entry.comment; - nativeEntry.content = entry.content; - nativeEntry.key = entry.keys; - nativeEntry.disable = !entry.enabled; - nativeEntry.constant = entry.type === 'constant'; - nativeEntry.position = this.convertPositionToNative(entry.position); - nativeEntry.depth = entry.depth; - nativeEntry.order = entry.order; - nativeEntry.exclude_recursion = entry.exclude_recursion; - nativeEntry.prevent_recursion = entry.prevent_recursion; - updatedUIDs.add(entry.uid); - } - } - - if (updatedUIDs.size !== uidsToUpdate.size) { - console.warn("[世界书编辑器] 部分条目更新失败,UID可能不存在。"); - } - - await saveWorldInfo(this.currentWorldBook, bookData, true); // true 表示静默保存 + // 将所有更新逻辑统一到 amilyHelper.setLorebookEntries + await amilyHelper.setLorebookEntries(this.currentWorldBook, entriesToUpdate); // Optimistic UI update in local state for (const updatedEntry of entriesToUpdate) { From 3b107382d2121055bcead305ffe57e04af79e31a Mon Sep 17 00:00:00 2001 From: Wx-2025 <351320169@qq.com> Date: Fri, 24 Oct 2025 19:56:45 +0800 Subject: [PATCH 06/49] Update cwb_lorebookManager.js --- CharacterWorldBook/src/cwb_lorebookManager.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/CharacterWorldBook/src/cwb_lorebookManager.js b/CharacterWorldBook/src/cwb_lorebookManager.js index 1017109..9385e59 100644 --- a/CharacterWorldBook/src/cwb_lorebookManager.js +++ b/CharacterWorldBook/src/cwb_lorebookManager.js @@ -208,9 +208,11 @@ export async function updateCharacterRosterLorebookEntry(processedCharacterNames } }); } - const floorRangeKey = existingRosterEntry.keys.find(k => /^\d+-\d+$/.test(k)); - if (floorRangeKey) { - [oldStartFloor] = floorRangeKey.split('-').map(Number); + if (Array.isArray(existingRosterEntry.keys)) { + const floorRangeKey = existingRosterEntry.keys.find(k => /^\d+-\d+$/.test(k)); + if (floorRangeKey) { + [oldStartFloor] = floorRangeKey.split('-').map(Number); + } } } From 960dd658df3313b562113c940aaa64fc6e2d0bff Mon Sep 17 00:00:00 2001 From: Wx-2025 <351320169@qq.com> Date: Fri, 24 Oct 2025 19:57:21 +0800 Subject: [PATCH 07/49] Update main.js --- core/tavern-helper/main.js | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/core/tavern-helper/main.js b/core/tavern-helper/main.js index 432d6b8..22f5caf 100644 --- a/core/tavern-helper/main.js +++ b/core/tavern-helper/main.js @@ -3,9 +3,10 @@ import { loadWorldInfo, saveWorldInfo, createNewWorldInfo, - createWorldInfoEntry + createWorldInfoEntry, + reloadEditor } from "/scripts/world-info.js"; -import { characters } from "/script.js"; +import { characters, eventSource, event_types } from "/script.js"; import { getContext } from "/scripts/extensions.js"; import { executeSlashCommandsWithOptions } from '/scripts/slash-commands.js'; @@ -38,14 +39,16 @@ class AmilyHelper { if (!bookData || !bookData.entries) { return []; } + const positionMap = { 0: 'before_character_definition', 1: 'after_character_definition', 2: 'before_author_note', 3: 'after_author_note', 4: 'at_depth_as_system' }; return Object.entries(bookData.entries).map(([uid, entry]) => ({ uid: parseInt(uid), comment: entry.comment || '无标题条目', content: entry.content || '', key: entry.key || [], + keys: entry.key || [], enabled: !entry.disable, constant: entry.constant || false, - position: entry.position || 4, + position: positionMap[entry.position] || 'at_depth_as_system', depth: entry.depth || 998, })); } catch (error) { @@ -68,12 +71,20 @@ class AmilyHelper { if (entryUpdate.enabled !== undefined) existingEntry.disable = !entryUpdate.enabled; if (entryUpdate.comment !== undefined) existingEntry.comment = entryUpdate.comment; if (entryUpdate.key !== undefined) existingEntry.key = entryUpdate.key; + if (entryUpdate.keys !== undefined) existingEntry.key = entryUpdate.keys; if (entryUpdate.constant !== undefined) existingEntry.constant = entryUpdate.constant; - if (entryUpdate.position !== undefined) existingEntry.position = entryUpdate.position; + if (entryUpdate.type === 'constant') existingEntry.constant = true; + if (entryUpdate.type === 'selective') existingEntry.constant = false; + if (entryUpdate.position !== undefined) { + const positionMap = { 'before_character_definition': 0, 'after_character_definition': 1, 'before_author_note': 2, 'after_author_note': 3, 'at_depth': 4, 'at_depth_as_system': 4 }; + existingEntry.position = positionMap[entryUpdate.position] ?? 4; + } if (entryUpdate.depth !== undefined) existingEntry.depth = entryUpdate.depth; } } await saveWorldInfo(bookName, bookData, true); + reloadEditor(bookName); // 刷新编辑器 + eventSource.emit(event_types.WORLD_INFO_UPDATED, bookName); return true; } catch (error) { console.error(`[Amily助手] 更新世界书《${bookName}》条目时出错:`, error); @@ -95,17 +106,20 @@ class AmilyHelper { for (const newEntryData of entries) { const newEntry = createWorldInfoEntry(bookName, bookData); + const positionMap = { 'before_character_definition': 0, 'after_character_definition': 1, 'before_author_note': 2, 'after_author_note': 3, 'at_depth': 4, 'at_depth_as_system': 4 }; Object.assign(newEntry, { comment: newEntryData.comment || '新条目', content: newEntryData.content || '', - key: newEntryData.key || [], - constant: newEntryData.constant || false, - position: newEntryData.position ?? 4, + key: newEntryData.keys || newEntryData.key || [], + constant: newEntryData.type === 'constant' ? true : (newEntryData.constant || false), + position: typeof newEntryData.position === 'string' ? (positionMap[newEntryData.position] ?? 4) : (newEntryData.position ?? 4), depth: newEntryData.depth ?? 998, disable: !(newEntryData.enabled ?? true), }); + if (newEntryData.type === 'selective') newEntry.constant = false; } await saveWorldInfo(bookName, bookData, true); + reloadEditor(bookName); // 刷新编辑器 return true; } catch (error) { console.error(`[Amily助手] 在世界书《${bookName}》中创建新条目时出错:`, error); @@ -124,6 +138,8 @@ class AmilyHelper { world_names.push(bookName); world_names.sort(); } + // 派发一个自定义事件,通知UI更新 + document.dispatchEvent(new CustomEvent('amily-lorebook-created', { detail: { bookName } })); return true; } catch (error) { console.error(`[Amily助手] 创建世界书《${bookName}》时出错:`, error); @@ -144,6 +160,14 @@ class AmilyHelper { throw error; } } + + async loadWorldInfo(bookName) { + return await loadWorldInfo(bookName); + } + + async saveWorldInfo(bookName, data, isWorldInfo) { + await saveWorldInfo(bookName, data, isWorldInfo); + } } export const amilyHelper = new AmilyHelper(); From 6278c5e69a124232d547530d514f78692728214d Mon Sep 17 00:00:00 2001 From: Wx-2025 <351320169@qq.com> Date: Fri, 24 Oct 2025 19:57:58 +0800 Subject: [PATCH 08/49] Update cwb_apiService.js --- CharacterWorldBook/src/cwb_apiService.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CharacterWorldBook/src/cwb_apiService.js b/CharacterWorldBook/src/cwb_apiService.js index 99f30c5..670c1ce 100644 --- a/CharacterWorldBook/src/cwb_apiService.js +++ b/CharacterWorldBook/src/cwb_apiService.js @@ -300,6 +300,10 @@ export async function callCwbAPI(systemPrompt, userPromptContent, options = {}) }); console.log("【消息内容】:", messages); + // 格式化并打印完整的提示词 + const fullPromptText = messages.map(msg => `[${msg.role}]\n${msg.content}`).join('\n\n'); + console.log("【完整提示词】:\n", fullPromptText); + try { let responseContent; From 935a52e05ecbe6be413c916dfbbc28c28561aa93 Mon Sep 17 00:00:00 2001 From: Wx-2025 <351320169@qq.com> Date: Fri, 24 Oct 2025 19:58:18 +0800 Subject: [PATCH 09/49] Update index.js --- index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/index.js b/index.js index a9c2597..4d050f6 100644 --- a/index.js +++ b/index.js @@ -627,5 +627,4 @@ jQuery(async () => { } } }, checkInterval); - }); From 312bb8fd9388427c62fb714ee113bfc9e93510bb Mon Sep 17 00:00:00 2001 From: Wx-2025 <351320169@qq.com> Date: Fri, 24 Oct 2025 20:21:44 +0800 Subject: [PATCH 10/49] Update manifest.json --- manifest.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index e6d5b62..56a61f7 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "name": "Amily2号聊天优化助手", "display_name": "Amily2号助手", - "version": "1.5.9", + "version": "1.6.0", "author": "Wx-2025", "description": "一个拥有独立UI的智能引擎,正文优化、自动总结、记忆表格、rag向量、隐藏楼层、剧情推进六大功能整合。", "minSillyTavernVersion": "1.10.0", @@ -30,5 +30,6 @@ + From 7903180c4f09bae6f65a65594e6b2ea75c448194 Mon Sep 17 00:00:00 2001 From: Wx-2025 <351320169@qq.com> Date: Sat, 25 Oct 2025 15:11:34 +0800 Subject: [PATCH 11/49] Update cwb_lorebookManager.js --- CharacterWorldBook/src/cwb_lorebookManager.js | 924 ++++++++++++------ 1 file changed, 650 insertions(+), 274 deletions(-) diff --git a/CharacterWorldBook/src/cwb_lorebookManager.js b/CharacterWorldBook/src/cwb_lorebookManager.js index 9385e59..b8f0143 100644 --- a/CharacterWorldBook/src/cwb_lorebookManager.js +++ b/CharacterWorldBook/src/cwb_lorebookManager.js @@ -1,320 +1,696 @@ -import { state } from './cwb_state.js'; -import { logError, logDebug, showToastr, parseCustomFormat } from './cwb_utils.js'; -import { - safeLorebooks, - safeCharLorebooks, - safeLorebookEntries, - safeUpdateLorebookEntries, - compatibleWriteToLorebook, -} from '../../core/tavernhelper-compatibility.js'; -import { amilyHelper } from '../../core/tavern-helper/main.js'; +import { SCRIPT_ID_PREFIX, CHAR_CARD_VIEWER_BUTTON_ID, CHAR_CARD_VIEWER_POPUP_ID, state } from './cwb_state.js'; +import { logDebug, logError, showToastr, escapeHtml, parseCustomFormat, buildCustomFormat, isCwbEnabled } from './cwb_utils.js'; +import { deleteLorebookEntries, getTargetWorldBook } from './cwb_lorebookManager.js'; +import { manualUpdateLogic } from './cwb_core.js'; +import { testCwbConnection, fetchCwbModels } from './cwb_apiService.js'; +import { extensionName } from '../../utils/settings.js'; +import { extension_settings } from '/scripts/extensions.js'; +import { saveSettingsDebounced } from '/script.js'; -const { SillyTavern } = window; +const { jQuery: $, SillyTavern, TavernHelper } = window; -export async function getTargetWorldBook() { - logDebug('[CWB-DIAGNOSTIC] getTargetWorldBook called. Current state:', { - target: state.worldbookTarget, - book: state.customWorldBook - }); - if (state.worldbookTarget === 'custom' && state.customWorldBook) { - return state.customWorldBook; - } - try { - const charLorebooks = await safeCharLorebooks(); - const primaryBook = charLorebooks.primary; - if (!primaryBook) { - showToastr('error', '当前角色未设置主世界书。'); - return null; +function createCharCardViewerPopupHtml(displayItems) { + const pathToLabelMap = { + 'narrative_essence.core_traits.name': '特质名称', + 'narrative_essence.key_relationships.name': '关系人姓名', + }; + const keyToLabelMap = { + 'name': '姓名', + 'archetype': '身份原型', + 'gender': '性别', + 'age': '年龄', + 'race': '种族', + 'current_status': '当前状态', + + 'first_impression': '第一印象', + 'key_features': '显著特征', + 'attire': '衣着风格', + 'mannerisms': '习惯举止', + 'voice': '声音特征', + + 'tags': '性格标签', + 'description': '性格详述', + 'motivation': '内在驱动', + 'values': '价值观', + 'inner_conflict': '内心挣扎', + + 'interaction_style': '互动风格', + 'skills': '技能能力', + 'reputation': '他人声望', + + 'core_traits': '核心特质', + 'verbal_patterns': '语言范式', + 'key_relationships': '关键关系', + 'definition': '特质定义', + 'evidence': '具体事例', + 'style_summary': '风格总结', + 'quotes': '代表性引言', + 'summary': '关系概述', + }; + const getLabel = (key, path) => { + const pathKey = path.replace(/\.\d+\./g, '.'); + if (pathToLabelMap[pathKey]) { + return pathToLabelMap[pathKey]; } - return primaryBook; - } catch (error) { - logError('获取主世界书时出错:', error); - return null; - } -} + return keyToLabelMap[key] || key.replace(/_/g, ' '); + }; -export async function deleteLorebookEntries(uids) { - if (!Array.isArray(uids) || uids.length === 0) return; + const renderField = (label, path, value, isTextarea = false, isArray = false) => { + const escapedLabel = escapeHtml(label); + const escapedValue = escapeHtml(isArray ? value.join('\n') : value || ''); - try { - const context = SillyTavern.getContext(); - if (!context || !context.characterId) { - throw new Error('没有选择角色,无法删除。'); - } - const book = await getTargetWorldBook(); - if (!book) throw new Error('未找到目标世界书。'); + const isLongContent = (value && String(value).length > 50) || (Array.isArray(value) && value.length > 1); + const rows = isArray ? Math.max(3, value.length) : (isLongContent ? 4 : 2); - const bookData = await amilyHelper.loadWorldInfo(book); - if (!bookData) throw new Error(`World book "${book}" not found.`); - uids.forEach(uid => { - delete bookData.entries[uid]; - }); - await amilyHelper.saveWorldInfo(book, bookData, true); - } catch (error) { - logError('删除世界书条目失败:', error); - showToastr('error', `删除失败: ${error.message}`); - } -} + const inputElement = ``; -export async function saveDescriptionToLorebook(characterName, newDescription, startFloor, endFloor) { - if (!characterName?.trim()) return false; + return `
+ + ${inputElement} +
`; + }; - try { - const context = SillyTavern.getContext(); - if (!context || !context.characterId) { - showToastr('error', '没有选择角色,无法保存到世界书。'); - return false; - } - let chatIdentifier = state.currentChatFileIdentifier || '未知聊天'; - chatIdentifier = chatIdentifier.replace(/ imported/g, ''); - - const safeCharName = characterName.replace(/[^a-zA-Z0-9\u4e00-\u9fa5·""“”_-]/g, ','); - const floorRange = `${startFloor + 1}-${endFloor + 1}`; - - const newComment = `${safeCharName}-${chatIdentifier}`; - - let bookName = await getTargetWorldBook(); - - if (!bookName) { - showToastr('error', '未能确定要写入的世界书。请检查主世界书或自定义世界书设置。'); - return false; - } - - const entries = (await safeLorebookEntries(bookName)) || []; - let existing = entries.find(e => - Array.isArray(e.keys) && - e.keys.includes(chatIdentifier) && - e.keys.includes(safeCharName) && - !e.keys.includes('Amily2角色总集') - ); - - const entryData = { - comment: newComment, - content: newDescription, - keys: [chatIdentifier, safeCharName, floorRange], - enabled: true, - type: 'selective', - }; - - if (existing) { - await safeUpdateLorebookEntries(bookName, [{ uid: existing.uid, ...entryData }]); - } else { - const cwbEntries = entries.filter(e => - Array.isArray(e.keys) && - e.keys.includes(chatIdentifier) && - !e.keys.includes('Amily2角色总集') - ); - let maxDepth = 7000; - cwbEntries.forEach(entry => { - if (entry.position === 'at_depth_as_system' && typeof entry.depth === 'number') { - if (entry.depth >= 7001 && entry.depth > maxDepth) { - maxDepth = entry.depth; + const renderCard = (title, data, pathPrefix) => { + if (!data || typeof data !== 'object' || Object.keys(data).length === 0) return ''; + let cardHtml = `

${escapeHtml(title)}

`; + for (const [key, value] of Object.entries(data)) { + const currentPath = pathPrefix ? `${pathPrefix}.${key}` : key; + const label = getLabel(key, currentPath); + if (typeof value === 'object' && value !== null && !Array.isArray(value)) { + cardHtml += renderCard(label, value, currentPath); // Recursive call for nested objects + } else if (Array.isArray(value) && value.length > 0 && typeof value[0] === 'object') { + cardHtml += `
${escapeHtml(label)}
`; + value.forEach((item, itemIndex) => { + cardHtml += `
`; + for (const [itemKey, itemValue] of Object.entries(item)) { + const itemPath = `${currentPath}.${itemIndex}.${itemKey}`; + cardHtml += renderField(getLabel(itemKey, itemPath), itemPath, itemValue, false, Array.isArray(itemValue)); } - } - }); - - const newDepth = maxDepth + 1; - let maxOrder = 7000; - if (cwbEntries.length > 0) { - maxOrder = cwbEntries.reduce((max, entry) => { - const order = Number(entry.order); - return !isNaN(order) && order > max ? order : max; - }, 7000); + cardHtml += `
`; + }); + cardHtml += `
`; + } else { + cardHtml += renderField(label, currentPath, value, false, Array.isArray(value)); } - - const newEntryData = { - ...entryData, - order: 100, - position: 'at_depth_as_system', - depth: newDepth, - }; - - logDebug(`创建新角色条目:${safeCharName}`, { - position: newEntryData.position, - depth: newEntryData.depth, - order: newEntryData.order - }); - - await amilyHelper.createLorebookEntries(bookName, [newEntryData]); } - showToastr('success', `角色 ${safeCharName} 的描述已保存到世界书。`); - return true; - } catch (error) { - logError(`保存世界书失败 for ${characterName}:`, error); - showToastr('error', `保存角色 ${safeCharName} 到世界书失败。`); - return false; + cardHtml += `
`; + return cardHtml; + }; + + let html = `
`; + html += `
+

角色数据核心

+
+ + + + +
+
`; + + if (!displayItems || displayItems.length === 0) { + html += `

看什么?没更新角色条目就等着我给你显示出来条目吗?想关悬浮窗就点角色世界,功能设置关掉。

`; + return html; } + + html += `
`; + html += `
`; + displayItems.forEach((item, index) => { + const itemName = item.isRoster ? '人物总览' : (item.parsed?.name || `未知实体 ${index + 1}`); + const wrapperClass = index === 0 ? 'cwb-cyber-tab active' : 'cwb-cyber-tab'; + html += `
+ + +
`; + }); + html += `
`; + + html += `
`; + displayItems.forEach((item, index) => { + html += `
`; + if (item.isRoster) { + html += `
+

人物总览 (只读)

+
+ +
+
`; + } else { + const charData = item.parsed; + if (charData) { + const charName = charData.name || `角色 ${index + 1}`; + if (charData.name) html += renderCard('姓名', { name: charData.name }, ''); + if (charData.core_identity) html += renderCard('核心认同', charData.core_identity, 'core_identity'); + if (charData.physical_imprint) html += renderCard('物理印记', charData.physical_imprint, 'physical_imprint'); + if (charData.psyche_profile) html += renderCard('心智侧写', charData.psyche_profile, 'psyche_profile'); + if (charData.social_matrix) html += renderCard('社交矩阵', charData.social_matrix, 'social_matrix'); + if (charData.narrative_essence) html += renderCard('叙事精粹', charData.narrative_essence, 'narrative_essence'); + + html += `
+

注入设置

+
+
+ + +
+
+ + +
+
+ + +
+
+
`; + + html += ``; + } + } + html += `
`; + }); + html += `
`; + return html; } -export async function updateCharacterRosterLorebookEntry(processedCharacterNames, startFloor, endFloor) { - if (!Array.isArray(processedCharacterNames)) return true; - - try { - const context = SillyTavern.getContext(); - if (!context || !context.characterId) { - logDebug('未选择角色,无法更新角色名册。'); - return false; +function bindCharCardViewerPopupEvents($popup) { + $popup.on('change', '.cwb-insertion-position', function() { + const $this = $(this); + const $depthContainer = $this.closest('.cwb-insertion-settings-content').find('.cwb-insertion-depth-container'); + if ($this.val() === 'at_depth') { + $depthContainer.show(); + } else { + $depthContainer.hide(); } - let chatIdentifier = state.currentChatFileIdentifier || '未知聊天'; - if (chatIdentifier === '未知聊天') return false; - - const cleanChatId = chatIdentifier.replace(/ imported/g, ''); - const rosterEntryComment = `Amily2角色总集-${cleanChatId}-角色总览`; + }); - let characterCardName = '未识别到该角色卡名称'; - try { - const currentChar = context.characters[context.characterId]; - if (currentChar && currentChar.name) { - characterCardName = currentChar.name.trim(); + $popup.on('click', '.cwb-viewer-popup-close-button', closeCharCardViewerPopup); + $popup.find('#cwb-viewer-refresh').on('click', () => { + showToastr('info', '正在刷新角色数据...'); + showCharCardViewerPopup(); + }); + + $popup.find('#cwb-manual-update-btn').on('click', async function() { + const $button = $(this); + $button.prop('disabled', true).html(' 更新中...'); + await manualUpdateLogic(); + showToastr('info', '更新完成,正在刷新查看器...'); + showCharCardViewerPopup(); + }); + + $popup.find('.cwb-cyber-tab__button').on('click', function () { + const $this = $(this); + const targetUid = $this.data('char-uid'); + $popup.find('.cwb-cyber-tab').removeClass('active'); + $this.closest('.cwb-cyber-tab').addClass('active'); + $popup.find('.cwb-cyber-content-pane').removeClass('active'); + $popup.find(`#cwb-char-content-${targetUid}`).addClass('active'); + }); + + $popup.find('.cwb-cyber-tab__delete').on('click', async function(e) { + e.stopPropagation(); + if (confirm('您确定要删除这个角色条目吗?此操作不可撤销。')) { + const uidToDelete = $(this).data('char-uid'); + await deleteLorebookEntries([uidToDelete]); + const $wrapper = $(this).closest('.cwb-cyber-tab'); + const $pane = $popup.find(`#cwb-char-content-${uidToDelete}`); + const wasActive = $wrapper.hasClass('active'); + $wrapper.remove(); + $pane.remove(); + if (wasActive && $popup.find('.cwb-cyber-tab').length > 0) { + $popup.find('.cwb-cyber-tab').first().find('.cwb-cyber-tab__button').trigger('click'); + } else if ($popup.find('.cwb-cyber-tab').length === 0) { + showCharCardViewerPopup(); } - } catch (e) { - logDebug('[CWB] 无法获取角色名称,使用默认值'); } - - const initialContentPrefix = `此为当前角色卡【${characterCardName}】中登场的角色,AI需要根据剧情让以下角色在合适的时机登场:\n\n`; - - let bookName = await getTargetWorldBook(); + }); - if (!bookName) { - showToastr('error', '未能确定要写入的世界书。请检查主世界书或自定义世界书设置。'); - return false; + $popup.find('#cwb-viewer-delete-all').on('click', async function() { + if (confirm('您确定要清除当前聊天中的所有角色卡和总览吗?此操作将删除所有相关条目,且不可撤销。')) { + const allUids = $popup.find('.cwb-cyber-tab__button').map(function() { + return $(this).data('char-uid'); + }).get(); + if (allUids.length > 0) { + await deleteLorebookEntries(allUids); + } + showCharCardViewerPopup(); } + }); - let entries = (await safeLorebookEntries(bookName)) || []; - let existingRosterEntry = entries.find(entry => - entry.comment === rosterEntryComment || - entry.comment === `Amily2角色总集-${chatIdentifier}-角色总览` - ); - - let existingNames = new Set(); - let oldStartFloor = 1; - let oldEndFloor = 0; - - if (existingRosterEntry) { - if (existingRosterEntry.content) { - let contentToParse = existingRosterEntry.content.replace(initialContentPrefix, ''); - - const floorMatch = contentToParse.match(/【前(\d+)楼角色世界书已更新完成】/); - if (floorMatch && floorMatch[1]) { - oldEndFloor = parseInt(floorMatch[1], 10); - } - - contentToParse.split('\n').forEach(line => { - if (line.trim().startsWith('[')) { - const nameMatch = line.match(/\[(.*?):/); - if (nameMatch && nameMatch[1]) { - existingNames.add(nameMatch[1].trim()); + $popup.find('.cwb-save-button').on('click', async function () { + const $button = $(this); + const targetUid = $button.data('uid'); + $button.prop('disabled', true).html(' 保存中...'); + try { + const book = await getTargetWorldBook(); + if (!book) throw new Error('未找到目标世界书。'); + const $activePane = $popup.find(`#cwb-char-content-${targetUid}`); + const collectedData = {}; + const setNestedValue = (obj, path, value) => { + const keys = path.split('.'); + let current = obj; + keys.forEach((key, index) => { + if (index === keys.length - 1) { + current[key] = value === '' ? null : value; + } else { + const nextKeyIsNumber = /^\d+$/.test(keys[index + 1]); + if (!current[key]) { + current[key] = nextKeyIsNumber ? [] : {}; } + current = current[key]; } }); - } - if (Array.isArray(existingRosterEntry.keys)) { - const floorRangeKey = existingRosterEntry.keys.find(k => /^\d+-\d+$/.test(k)); - if (floorRangeKey) { - [oldStartFloor] = floorRangeKey.split('-').map(Number); + }; + $activePane.find('.cwb-cyber-field__input').each(function () { + const $field = $(this); + const path = $field.data('path'); + let value = $field.val(); + if ($field.data('is-array')) { + value = value.split('\n').map(l => l.trim()).filter(Boolean); } + if(path){ + setNestedValue(collectedData, path, value); + } + }); + const finalContentToSave = buildCustomFormat(collectedData); + const allEntries = await TavernHelper.getLorebookEntries(book); + const entryToUpdate = allEntries.find(e => e.uid === targetUid); + if (!entryToUpdate) throw new Error('无法在世界书中找到原始条目。'); + + const insertionPosition = $activePane.find('.cwb-insertion-position').val(); + const insertionDepth = parseInt($activePane.find('.cwb-insertion-depth').val(), 10); + const insertionOrder = parseInt($activePane.find('.cwb-insertion-order').val(), 10); + + logDebug(`[DEBUG] 界面收集值 UID:${targetUid}`, { + insertionPosition: insertionPosition, + insertionDepth: insertionDepth, + insertionOrder: insertionOrder + }); + + const positionMap = { + 'before_char': 'before_character_definition', + 'after_char': 'after_character_definition', + 'before_an': 'before_author_note', + 'after_an': 'after_author_note', + 'at_depth': 'at_depth_as_system' + }; + + const finalEntryData = { ...entryToUpdate }; + + finalEntryData.content = finalContentToSave; + finalEntryData.uid = targetUid; + + const newPosition = positionMap[insertionPosition]; + finalEntryData.position = newPosition || 'before_character_definition'; + if (insertionPosition === 'at_depth') { + finalEntryData.depth = isNaN(insertionDepth) ? 0 : insertionDepth; + } else { + finalEntryData.depth = null; } + + finalEntryData.order = isNaN(insertionOrder) ? 7001 : insertionOrder; + + logDebug(`[DEBUG] 最终保存数据 UID:${targetUid}`, { + position: finalEntryData.position, + depth: finalEntryData.depth, + order: finalEntryData.order, + hasDepthField: 'depth' in finalEntryData + }); + + await TavernHelper.setLorebookEntries(book, [finalEntryData]); + showToastr('success', '角色卡已成功保存!'); + } catch (error) { + logError('保存角色卡失败:', error); + showToastr('error', `保存失败: ${error.message}`); + } finally { + $button.prop('disabled', false).text(`保存修改`); } - - processedCharacterNames.forEach(name => existingNames.add(name.trim())); - - const newStartFloor = Math.min(oldStartFloor, startFloor + 1); - const newEndFloor = Math.max(oldEndFloor, endFloor + 1); - - const newContent = - initialContentPrefix + - [...existingNames] - .sort() - .map(name => `[${name}: (详细查看绿灯角色条目)]`) - .join('\n') + `\n\n{{// 本条勿动,【前${newEndFloor}楼角色世界书已更新完成】否则后续更新无法完成。}}`; - - const newFloorRange = `${newStartFloor}-${newEndFloor}`; - - const baseKeys = [`Amily2角色总集`, cleanChatId, `角色总览`]; - const newKeys = [...baseKeys, newFloorRange]; - - const entryData = { - content: newContent, - keys: newKeys, - type: 'constant', - position: 'before_character_definition', - depth: null, - enabled: true, - order: 9999, - prevent_recursion: true, - }; - - if (existingRosterEntry) { - await safeUpdateLorebookEntries(bookName, [ - { uid: existingRosterEntry.uid, comment: rosterEntryComment, ...entryData }, - ]); - } else { - await amilyHelper.createLorebookEntries(bookName, [ - { comment: rosterEntryComment, ...entryData }, - ]); - } - return true; - } catch (error) { - logError('更新角色名册条目时出错:', error); - return false; - } + }); } +function closeCharCardViewerPopup() { + $(`#${CHAR_CARD_VIEWER_POPUP_ID}`).remove(); +} -export async function manageAutoCardUpdateLorebookEntry() { +export async function showCharCardViewerPopup() { + if (!isCwbEnabled()) return; + closeCharCardViewerPopup(); try { - if (state.worldbookTarget === 'custom' && state.customWorldBook) { - logDebug('[CWB] 使用自定义世界书模式,跳过角色总览条目的自动管理'); + const book = await getTargetWorldBook(); + if (!book) { + showToastr('warning', '当前角色未设置主世界书或自定义世界书。'); + $('body').append(createCharCardViewerPopupHtml([])); + bindCharCardViewerPopupEvents($(`#${CHAR_CARD_VIEWER_POPUP_ID}`)); return; } + const allEntries = await TavernHelper.getLorebookEntries(book); + let currentChatId = state.currentChatFileIdentifier; - const context = SillyTavern.getContext(); - if (!context || !context.characterId) { - logDebug('未选择角色,跳过世界书管理。'); - return; - } - const bookName = await getTargetWorldBook(); - if (!bookName) return; - - const entries = (await safeLorebookEntries(bookName)) || []; - - const currentChatId = state.currentChatFileIdentifier; if (!currentChatId || currentChatId.startsWith('unknown_chat')) { - logError(`无效的聊天标识符 "${currentChatId}"。正在中止世界书管理。`); + logError(`Invalid chat identifier "${currentChatId}" for viewer.`); + $('body').append(createCharCardViewerPopupHtml([])); + bindCharCardViewerPopupEvents($(`#${CHAR_CARD_VIEWER_POPUP_ID}`)); return; } + const cleanChatId = currentChatId.replace(/ imported/g, ''); + let displayItems = []; - let currentChatRosterExists = false; - const entriesToUpdate = []; - - for (const entry of entries) { - if (Array.isArray(entry.keys) && (entry.keys.includes('Amily2角色总集') || entry.keys.includes(cleanChatId) || entry.keys.includes(currentChatId))) { + let relevantEntries; + if (state.worldbookTarget === 'custom' && state.customWorldBook) { + relevantEntries = allEntries.filter(entry => { + if (!entry.enabled || !Array.isArray(entry.keys)) return false; + if (entry.keys.includes('Amily2角色总集') || entry.keys.includes('角色总览')) return true; + if (entry.content) { + try { + const parsed = parseCustomFormat(entry.content); + return parsed && Object.keys(parsed).length > 0; + } catch (e) { + return false; + } + } - const isForCurrentChat = entry.keys.includes(cleanChatId) || entry.keys.includes(currentChatId); - let shouldBeEnabled = isForCurrentChat; + return false; + }); + } else { + relevantEntries = allEntries.filter(entry => + entry.enabled && + Array.isArray(entry.keys) && + entry.keys.includes(cleanChatId) + ); + } - if (isForCurrentChat && entry.keys.includes('角色总览')) { - currentChatRosterExists = true; + const rosterEntries = relevantEntries.filter(entry => + entry.keys.includes('Amily2角色总集') && entry.keys.includes('角色总览') + ); + + rosterEntries.forEach((entry, index) => { + displayItems.push({ + uid: entry.uid, + isRoster: true, + comment: entry.comment, + content: entry.content, + rosterIndex: index + }); + }); + + const characterEntries = relevantEntries + .filter(entry => !entry.keys.includes('Amily2角色总集')) + .map(entry => { + try { + logDebug(`[DEBUG] 原始条目数据 UID:${entry.uid}`, { + position: entry.position, + depth: entry.depth, + order: entry.order, + comment: entry.comment + }); + + const positionStringMap = { + 0: 'before_char', + 1: 'after_char', + 2: 'before_an', + 3: 'after_an', + 4: 'at_depth', + 'before_character_definition': 'before_char', + 'after_character_definition': 'after_char', + 'before_author_note': 'before_an', + 'after_author_note': 'after_an', + 'at_depth_as_system': 'at_depth' + }; + + const position = entry.position; + const mappedPosition = positionStringMap[position] || 'at_depth'; + const finalDepth = (position === 4 || position === 'at_depth_as_system') ? (entry.depth ?? 0) : 0; + logDebug(`[DEBUG] 映射结果 UID:${entry.uid}`, { + originalPosition: position, + mappedPosition: mappedPosition, + finalDepth: finalDepth + }); + + return { + uid: entry.uid, + isRoster: false, + comment: entry.comment, + content: entry.content, + parsed: parseCustomFormat(entry.content), + insertionPosition: mappedPosition, + insertionDepth: finalDepth, + insertionOrder: entry.order ?? 7001, + }; + } catch (e) { + logError(`解析角色条目失败 (UID: ${entry.uid}),已跳过。`, e); + return null; } + }) + .filter(c => c && c.parsed && Object.keys(c.parsed).length > 0); + + displayItems = displayItems.concat(characterEntries); - if (entry.enabled !== shouldBeEnabled) { - entriesToUpdate.push({ uid: entry.uid, enabled: shouldBeEnabled }); - } - } - } - - if (entriesToUpdate.length > 0) { - await safeUpdateLorebookEntries(bookName, entriesToUpdate); - logDebug(`已为聊天: ${cleanChatId} 管理了 ${entriesToUpdate.length} 个世界书条目的状态。`); - } - - if (!currentChatRosterExists) { - logDebug(`未找到聊天 "${cleanChatId}" 的名册。正在触发创建。`); - await updateCharacterRosterLorebookEntry([]); - } - + const popupHtml = createCharCardViewerPopupHtml(displayItems); + $('body').append(popupHtml); + const $popup = $(`#${CHAR_CARD_VIEWER_POPUP_ID}`); + bindCharCardViewerPopupEvents($popup); } catch (error) { - logError('管理世界书条目时出错:', error); + logError('无法显示角色卡查看器:', error); + showToastr('error', '加载角色卡数据时出错。'); } } + +function toggleCharCardViewerPopup() { + if ($(`#${CHAR_CARD_VIEWER_POPUP_ID}`).length > 0) { + closeCharCardViewerPopup(); + } else { + showCharCardViewerPopup(); + } +} + +function keepButtonInBounds($element, savePosition = false) { + if (!$element || !$element.length) return; + const windowWidth = $(window).width(); + const windowHeight = $(window).height(); + const buttonWidth = $element.outerWidth(); + const buttonHeight = $element.outerHeight(); + let currentPos = $element.offset(); + let newTop = Math.max(0, Math.min(currentPos.top, windowHeight - buttonHeight)); + let newLeft = Math.max(0, Math.min(currentPos.left, windowWidth - buttonWidth)); + $element.css({ top: `${newTop}px`, left: `${newLeft}px` }); + if (savePosition) { + localStorage.setItem(state.STORAGE_KEY_VIEWER_BUTTON_POS, JSON.stringify({ top: $element.css('top'), left: $element.css('left') })); + } +} + +function makeButtonDraggable($button) { + let isDragging = false, wasDragged = false, offset = { x: 0, y: 0 }, startPos = { x: 0, y: 0 }; + const DRAG_THRESHOLD = 5; // 5 pixels threshold + + const getCoords = (e) => e.touches && e.touches.length ? e.touches[0] : e; + + const dragStart = function (e) { + if (e.type === 'touchstart') e.preventDefault(); + isDragging = true; + wasDragged = false; + const coords = getCoords(e); + startPos.x = coords.clientX; + startPos.y = coords.clientY; + offset.x = coords.clientX - $button.offset().left; + offset.y = coords.clientY - $button.offset().top; + $button.css('cursor', 'grabbing'); + $('body').css({ 'user-select': 'none', '-webkit-user-select': 'none' }); + }; + + const dragMove = function (e) { + if (!isDragging) return; + const coords = getCoords(e); + const dx = coords.clientX - startPos.x; + const dy = coords.clientY - startPos.y; + + if (!wasDragged && Math.sqrt(dx * dx + dy * dy) > DRAG_THRESHOLD) { + wasDragged = true; + } + + if (wasDragged) { + if (e.type === 'touchmove') e.preventDefault(); + let newX = coords.clientX - offset.x; + let newY = coords.clientY - offset.y; + newX = Math.max(0, Math.min(newX, window.innerWidth - $button.outerWidth())); + newY = Math.max(0, Math.min(newY, window.innerHeight - $button.outerHeight())); + $button.css({ top: newY + 'px', left: newX + 'px', right: '', bottom: '' }); + } + }; + + const dragEnd = function (e) { + if (!isDragging) return; + isDragging = false; + $button.css('cursor', 'grab'); + $('body').css({ 'user-select': 'auto', '-webkit-user-select': 'auto' }); + if (wasDragged) { + keepButtonInBounds($button, true); + } else if (e.type === 'touchend') { + e.preventDefault(); + toggleCharCardViewerPopup(); + } + }; + + $button.on('mousedown', dragStart); + $(document).on('mousemove.cwbViewer', dragMove).on('mouseup.cwbViewer', dragEnd); + $button.on('touchstart', dragStart); + $(document).on('touchmove.cwbViewer', dragMove).on('touchend.cwbViewer', dragEnd); + + $button.on('click', function (e) { + if (wasDragged) { + e.preventDefault(); + e.stopPropagation(); + return; + } + toggleCharCardViewerPopup(); + }); +} + +export function initializeCharCardViewer() { + const $existingButton = $(`#${CHAR_CARD_VIEWER_BUTTON_ID}`); + + if ($existingButton.length > 0) { + console.log('[CWB] Char card viewer button already exists'); + setTimeout(() => { + const shouldShow = isCwbEnabled() && state.viewerEnabled; + $existingButton.toggle(shouldShow); + console.log(`[CWB] Force updated existing button visibility: ${shouldShow}`); + }, 100); + return; + } + + const buttonHtml = `
`; + $('body').append(buttonHtml); + const $viewerButton = $(`#${CHAR_CARD_VIEWER_BUTTON_ID}`); + makeButtonDraggable($viewerButton); + + const savedPosition = JSON.parse(localStorage.getItem(state.STORAGE_KEY_VIEWER_BUTTON_POS) || 'null'); + if (savedPosition) { + $viewerButton.css({ top: savedPosition.top, left: savedPosition.left }); + } else { + $viewerButton.css({ top: '120px', right: '10px', left: 'auto' }); + } + + setTimeout(() => { + const shouldShow = isCwbEnabled() && state.viewerEnabled; + $viewerButton.toggle(shouldShow); + console.log(`[CWB] New button created with visibility: ${shouldShow}`); + }, 100); + + console.log('[CWB] Char card viewer button initialized'); + + let resizeTimeout; + $(window).on('resize.cwbViewer', function () { + clearTimeout(resizeTimeout); + resizeTimeout = setTimeout(() => keepButtonInBounds($(`#${CHAR_CARD_VIEWER_BUTTON_ID}`), true), 150); + }); +} + +export function updateViewerButtonVisibility() { + const $button = $(`#${CHAR_CARD_VIEWER_BUTTON_ID}`); + const shouldShow = isCwbEnabled() && state.viewerEnabled; + + console.log(`[CWB] Updating viewer button visibility: ${shouldShow} (master: ${isCwbEnabled()}, viewer: ${state.viewerEnabled})`); + + if ($button.length > 0) { + $button.toggle(shouldShow); + console.log(`[CWB] Viewer button visibility set to: ${shouldShow}`); + } else { + console.log('[CWB] Viewer button not found, will initialize when DOM is ready'); + // Try to initialize if button doesn't exist yet + setTimeout(() => { + initializeCharCardViewer(); + }, 500); + } + + logDebug('悬浮窗按钮显示状态更新:', { + masterEnabled: isCwbEnabled(), + viewerEnabled: state.viewerEnabled, + shouldShow: shouldShow + }); +} + +export function bindCwbApiEvents() { + console.log('[CWB] Binding API events'); + + $('#cwb-api-url').off('input').on('input', function() { + const value = $(this).val(); + extension_settings[extensionName].cwb_api_url = value; + saveSettingsDebounced(); + }); + + $('#cwb-api-key').off('input').on('input', function() { + const value = $(this).val(); + extension_settings[extensionName].cwb_api_key = value; + saveSettingsDebounced(); + }); + + $('#cwb-model').off('input').on('input', function() { + const value = $(this).val(); + extension_settings[extensionName].cwb_model = value; + saveSettingsDebounced(); + }); + + $('#cwb-temperature').off('input').on('input', function() { + const value = parseFloat($(this).val()); + $('#cwb-temperature-value').text(value); + extension_settings[extensionName].cwb_temperature = value; + saveSettingsDebounced(); + }); + + $('#cwb-max-tokens').off('input').on('input', function() { + const value = parseInt($(this).val()); + $('#cwb-max-tokens-value').text(value); + extension_settings[extensionName].cwb_max_tokens = value; + saveSettingsDebounced(); + }); + + $('#cwb-test-connection').off('click').on('click', async function() { + const $button = $(this); + $button.prop('disabled', true).html(' 测试中...'); + + try { + await testCwbConnection(); + } catch (error) { + console.error('[CWB] 测试连接失败:', error); + } finally { + $button.prop('disabled', false).html(' 测试连接'); + } + }); + + $('#cwb-fetch-models').off('click').on('click', async function() { + const $button = $(this); + $button.prop('disabled', true).html(' 获取中...'); + + try { + const models = await fetchCwbModels(); + const $modelSelect = $('#cwb-model'); + $modelSelect.empty(); + + if (models && models.length > 0) { + models.forEach(model => { + $modelSelect.append(new Option(model.name, model.id)); + }); + showToastr('success', `已获取到 ${models.length} 个模型`); + } else { + $modelSelect.append(new Option('无可用模型', '')); + showToastr('warning', '未获取到可用模型'); + } + } catch (error) { + console.error('[CWB] 获取模型失败:', error); + $('#cwb-model').empty().append(new Option('获取失败', '')); + } finally { + $button.prop('disabled', false).html(' 获取模型'); + } + }); +} From 73032b19c33ee385904eb444eade6fde6ee9b5af Mon Sep 17 00:00:00 2001 From: Wx-2025 <351320169@qq.com> Date: Sat, 25 Oct 2025 15:29:28 +0800 Subject: [PATCH 12/49] Update cwb_lorebookManager.js --- CharacterWorldBook/src/cwb_lorebookManager.js | 952 ++++++------------ 1 file changed, 288 insertions(+), 664 deletions(-) diff --git a/CharacterWorldBook/src/cwb_lorebookManager.js b/CharacterWorldBook/src/cwb_lorebookManager.js index b8f0143..9385e59 100644 --- a/CharacterWorldBook/src/cwb_lorebookManager.js +++ b/CharacterWorldBook/src/cwb_lorebookManager.js @@ -1,696 +1,320 @@ -import { SCRIPT_ID_PREFIX, CHAR_CARD_VIEWER_BUTTON_ID, CHAR_CARD_VIEWER_POPUP_ID, state } from './cwb_state.js'; -import { logDebug, logError, showToastr, escapeHtml, parseCustomFormat, buildCustomFormat, isCwbEnabled } from './cwb_utils.js'; -import { deleteLorebookEntries, getTargetWorldBook } from './cwb_lorebookManager.js'; -import { manualUpdateLogic } from './cwb_core.js'; -import { testCwbConnection, fetchCwbModels } from './cwb_apiService.js'; -import { extensionName } from '../../utils/settings.js'; -import { extension_settings } from '/scripts/extensions.js'; -import { saveSettingsDebounced } from '/script.js'; +import { state } from './cwb_state.js'; +import { logError, logDebug, showToastr, parseCustomFormat } from './cwb_utils.js'; +import { + safeLorebooks, + safeCharLorebooks, + safeLorebookEntries, + safeUpdateLorebookEntries, + compatibleWriteToLorebook, +} from '../../core/tavernhelper-compatibility.js'; +import { amilyHelper } from '../../core/tavern-helper/main.js'; -const { jQuery: $, SillyTavern, TavernHelper } = window; +const { SillyTavern } = window; -function createCharCardViewerPopupHtml(displayItems) { - const pathToLabelMap = { - 'narrative_essence.core_traits.name': '特质名称', - 'narrative_essence.key_relationships.name': '关系人姓名', - }; - const keyToLabelMap = { - 'name': '姓名', - 'archetype': '身份原型', - 'gender': '性别', - 'age': '年龄', - 'race': '种族', - 'current_status': '当前状态', - - 'first_impression': '第一印象', - 'key_features': '显著特征', - 'attire': '衣着风格', - 'mannerisms': '习惯举止', - 'voice': '声音特征', - - 'tags': '性格标签', - 'description': '性格详述', - 'motivation': '内在驱动', - 'values': '价值观', - 'inner_conflict': '内心挣扎', - - 'interaction_style': '互动风格', - 'skills': '技能能力', - 'reputation': '他人声望', - - 'core_traits': '核心特质', - 'verbal_patterns': '语言范式', - 'key_relationships': '关键关系', - 'definition': '特质定义', - 'evidence': '具体事例', - 'style_summary': '风格总结', - 'quotes': '代表性引言', - 'summary': '关系概述', - }; - const getLabel = (key, path) => { - const pathKey = path.replace(/\.\d+\./g, '.'); - if (pathToLabelMap[pathKey]) { - return pathToLabelMap[pathKey]; - } - return keyToLabelMap[key] || key.replace(/_/g, ' '); - }; - - const renderField = (label, path, value, isTextarea = false, isArray = false) => { - const escapedLabel = escapeHtml(label); - const escapedValue = escapeHtml(isArray ? value.join('\n') : value || ''); - - const isLongContent = (value && String(value).length > 50) || (Array.isArray(value) && value.length > 1); - const rows = isArray ? Math.max(3, value.length) : (isLongContent ? 4 : 2); - - const inputElement = ``; - - return `
- - ${inputElement} -
`; - }; - - const renderCard = (title, data, pathPrefix) => { - if (!data || typeof data !== 'object' || Object.keys(data).length === 0) return ''; - let cardHtml = `

${escapeHtml(title)}

`; - for (const [key, value] of Object.entries(data)) { - const currentPath = pathPrefix ? `${pathPrefix}.${key}` : key; - const label = getLabel(key, currentPath); - if (typeof value === 'object' && value !== null && !Array.isArray(value)) { - cardHtml += renderCard(label, value, currentPath); // Recursive call for nested objects - } else if (Array.isArray(value) && value.length > 0 && typeof value[0] === 'object') { - cardHtml += `
${escapeHtml(label)}
`; - value.forEach((item, itemIndex) => { - cardHtml += `
`; - for (const [itemKey, itemValue] of Object.entries(item)) { - const itemPath = `${currentPath}.${itemIndex}.${itemKey}`; - cardHtml += renderField(getLabel(itemKey, itemPath), itemPath, itemValue, false, Array.isArray(itemValue)); - } - cardHtml += `
`; - }); - cardHtml += `
`; - } else { - cardHtml += renderField(label, currentPath, value, false, Array.isArray(value)); - } - } - cardHtml += `
`; - return cardHtml; - }; - - let html = `
`; - html += `
-

角色数据核心

-
- - - - -
-
`; - - if (!displayItems || displayItems.length === 0) { - html += `

看什么?没更新角色条目就等着我给你显示出来条目吗?想关悬浮窗就点角色世界,功能设置关掉。

`; - return html; +export async function getTargetWorldBook() { + logDebug('[CWB-DIAGNOSTIC] getTargetWorldBook called. Current state:', { + target: state.worldbookTarget, + book: state.customWorldBook + }); + if (state.worldbookTarget === 'custom' && state.customWorldBook) { + return state.customWorldBook; } - - html += `
`; - html += `
`; - displayItems.forEach((item, index) => { - const itemName = item.isRoster ? '人物总览' : (item.parsed?.name || `未知实体 ${index + 1}`); - const wrapperClass = index === 0 ? 'cwb-cyber-tab active' : 'cwb-cyber-tab'; - html += `
- - -
`; - }); - html += `
`; - - html += `
`; - displayItems.forEach((item, index) => { - html += `
`; - if (item.isRoster) { - html += `
-

人物总览 (只读)

-
- -
-
`; - } else { - const charData = item.parsed; - if (charData) { - const charName = charData.name || `角色 ${index + 1}`; - if (charData.name) html += renderCard('姓名', { name: charData.name }, ''); - if (charData.core_identity) html += renderCard('核心认同', charData.core_identity, 'core_identity'); - if (charData.physical_imprint) html += renderCard('物理印记', charData.physical_imprint, 'physical_imprint'); - if (charData.psyche_profile) html += renderCard('心智侧写', charData.psyche_profile, 'psyche_profile'); - if (charData.social_matrix) html += renderCard('社交矩阵', charData.social_matrix, 'social_matrix'); - if (charData.narrative_essence) html += renderCard('叙事精粹', charData.narrative_essence, 'narrative_essence'); - - html += `
-

注入设置

-
-
- - -
-
- - -
-
- - -
-
-
`; - - html += ``; - } - } - html += `
`; - }); - html += `
`; - return html; -} - -function bindCharCardViewerPopupEvents($popup) { - $popup.on('change', '.cwb-insertion-position', function() { - const $this = $(this); - const $depthContainer = $this.closest('.cwb-insertion-settings-content').find('.cwb-insertion-depth-container'); - if ($this.val() === 'at_depth') { - $depthContainer.show(); - } else { - $depthContainer.hide(); - } - }); - - $popup.on('click', '.cwb-viewer-popup-close-button', closeCharCardViewerPopup); - $popup.find('#cwb-viewer-refresh').on('click', () => { - showToastr('info', '正在刷新角色数据...'); - showCharCardViewerPopup(); - }); - - $popup.find('#cwb-manual-update-btn').on('click', async function() { - const $button = $(this); - $button.prop('disabled', true).html(' 更新中...'); - await manualUpdateLogic(); - showToastr('info', '更新完成,正在刷新查看器...'); - showCharCardViewerPopup(); - }); - - $popup.find('.cwb-cyber-tab__button').on('click', function () { - const $this = $(this); - const targetUid = $this.data('char-uid'); - $popup.find('.cwb-cyber-tab').removeClass('active'); - $this.closest('.cwb-cyber-tab').addClass('active'); - $popup.find('.cwb-cyber-content-pane').removeClass('active'); - $popup.find(`#cwb-char-content-${targetUid}`).addClass('active'); - }); - - $popup.find('.cwb-cyber-tab__delete').on('click', async function(e) { - e.stopPropagation(); - if (confirm('您确定要删除这个角色条目吗?此操作不可撤销。')) { - const uidToDelete = $(this).data('char-uid'); - await deleteLorebookEntries([uidToDelete]); - const $wrapper = $(this).closest('.cwb-cyber-tab'); - const $pane = $popup.find(`#cwb-char-content-${uidToDelete}`); - const wasActive = $wrapper.hasClass('active'); - $wrapper.remove(); - $pane.remove(); - if (wasActive && $popup.find('.cwb-cyber-tab').length > 0) { - $popup.find('.cwb-cyber-tab').first().find('.cwb-cyber-tab__button').trigger('click'); - } else if ($popup.find('.cwb-cyber-tab').length === 0) { - showCharCardViewerPopup(); - } - } - }); - - $popup.find('#cwb-viewer-delete-all').on('click', async function() { - if (confirm('您确定要清除当前聊天中的所有角色卡和总览吗?此操作将删除所有相关条目,且不可撤销。')) { - const allUids = $popup.find('.cwb-cyber-tab__button').map(function() { - return $(this).data('char-uid'); - }).get(); - if (allUids.length > 0) { - await deleteLorebookEntries(allUids); - } - showCharCardViewerPopup(); - } - }); - - $popup.find('.cwb-save-button').on('click', async function () { - const $button = $(this); - const targetUid = $button.data('uid'); - $button.prop('disabled', true).html(' 保存中...'); - try { - const book = await getTargetWorldBook(); - if (!book) throw new Error('未找到目标世界书。'); - const $activePane = $popup.find(`#cwb-char-content-${targetUid}`); - const collectedData = {}; - const setNestedValue = (obj, path, value) => { - const keys = path.split('.'); - let current = obj; - keys.forEach((key, index) => { - if (index === keys.length - 1) { - current[key] = value === '' ? null : value; - } else { - const nextKeyIsNumber = /^\d+$/.test(keys[index + 1]); - if (!current[key]) { - current[key] = nextKeyIsNumber ? [] : {}; - } - current = current[key]; - } - }); - }; - $activePane.find('.cwb-cyber-field__input').each(function () { - const $field = $(this); - const path = $field.data('path'); - let value = $field.val(); - if ($field.data('is-array')) { - value = value.split('\n').map(l => l.trim()).filter(Boolean); - } - if(path){ - setNestedValue(collectedData, path, value); - } - }); - const finalContentToSave = buildCustomFormat(collectedData); - const allEntries = await TavernHelper.getLorebookEntries(book); - const entryToUpdate = allEntries.find(e => e.uid === targetUid); - if (!entryToUpdate) throw new Error('无法在世界书中找到原始条目。'); - - const insertionPosition = $activePane.find('.cwb-insertion-position').val(); - const insertionDepth = parseInt($activePane.find('.cwb-insertion-depth').val(), 10); - const insertionOrder = parseInt($activePane.find('.cwb-insertion-order').val(), 10); - - logDebug(`[DEBUG] 界面收集值 UID:${targetUid}`, { - insertionPosition: insertionPosition, - insertionDepth: insertionDepth, - insertionOrder: insertionOrder - }); - - const positionMap = { - 'before_char': 'before_character_definition', - 'after_char': 'after_character_definition', - 'before_an': 'before_author_note', - 'after_an': 'after_author_note', - 'at_depth': 'at_depth_as_system' - }; - - const finalEntryData = { ...entryToUpdate }; - - finalEntryData.content = finalContentToSave; - finalEntryData.uid = targetUid; - - const newPosition = positionMap[insertionPosition]; - finalEntryData.position = newPosition || 'before_character_definition'; - if (insertionPosition === 'at_depth') { - finalEntryData.depth = isNaN(insertionDepth) ? 0 : insertionDepth; - } else { - finalEntryData.depth = null; - } - - finalEntryData.order = isNaN(insertionOrder) ? 7001 : insertionOrder; - - logDebug(`[DEBUG] 最终保存数据 UID:${targetUid}`, { - position: finalEntryData.position, - depth: finalEntryData.depth, - order: finalEntryData.order, - hasDepthField: 'depth' in finalEntryData - }); - - await TavernHelper.setLorebookEntries(book, [finalEntryData]); - showToastr('success', '角色卡已成功保存!'); - } catch (error) { - logError('保存角色卡失败:', error); - showToastr('error', `保存失败: ${error.message}`); - } finally { - $button.prop('disabled', false).text(`保存修改`); - } - }); -} - -function closeCharCardViewerPopup() { - $(`#${CHAR_CARD_VIEWER_POPUP_ID}`).remove(); -} - -export async function showCharCardViewerPopup() { - if (!isCwbEnabled()) return; - closeCharCardViewerPopup(); try { + const charLorebooks = await safeCharLorebooks(); + const primaryBook = charLorebooks.primary; + if (!primaryBook) { + showToastr('error', '当前角色未设置主世界书。'); + return null; + } + return primaryBook; + } catch (error) { + logError('获取主世界书时出错:', error); + return null; + } +} + +export async function deleteLorebookEntries(uids) { + if (!Array.isArray(uids) || uids.length === 0) return; + + try { + const context = SillyTavern.getContext(); + if (!context || !context.characterId) { + throw new Error('没有选择角色,无法删除。'); + } const book = await getTargetWorldBook(); - if (!book) { - showToastr('warning', '当前角色未设置主世界书或自定义世界书。'); - $('body').append(createCharCardViewerPopupHtml([])); - bindCharCardViewerPopupEvents($(`#${CHAR_CARD_VIEWER_POPUP_ID}`)); - return; - } - const allEntries = await TavernHelper.getLorebookEntries(book); - let currentChatId = state.currentChatFileIdentifier; + if (!book) throw new Error('未找到目标世界书。'); - if (!currentChatId || currentChatId.startsWith('unknown_chat')) { - logError(`Invalid chat identifier "${currentChatId}" for viewer.`); - $('body').append(createCharCardViewerPopupHtml([])); - bindCharCardViewerPopupEvents($(`#${CHAR_CARD_VIEWER_POPUP_ID}`)); - return; + const bookData = await amilyHelper.loadWorldInfo(book); + if (!bookData) throw new Error(`World book "${book}" not found.`); + uids.forEach(uid => { + delete bookData.entries[uid]; + }); + await amilyHelper.saveWorldInfo(book, bookData, true); + } catch (error) { + logError('删除世界书条目失败:', error); + showToastr('error', `删除失败: ${error.message}`); + } +} + +export async function saveDescriptionToLorebook(characterName, newDescription, startFloor, endFloor) { + if (!characterName?.trim()) return false; + + try { + const context = SillyTavern.getContext(); + if (!context || !context.characterId) { + showToastr('error', '没有选择角色,无法保存到世界书。'); + return false; } + let chatIdentifier = state.currentChatFileIdentifier || '未知聊天'; + chatIdentifier = chatIdentifier.replace(/ imported/g, ''); - const cleanChatId = currentChatId.replace(/ imported/g, ''); - let displayItems = []; + const safeCharName = characterName.replace(/[^a-zA-Z0-9\u4e00-\u9fa5·""“”_-]/g, ','); + const floorRange = `${startFloor + 1}-${endFloor + 1}`; - let relevantEntries; - if (state.worldbookTarget === 'custom' && state.customWorldBook) { - relevantEntries = allEntries.filter(entry => { - if (!entry.enabled || !Array.isArray(entry.keys)) return false; - if (entry.keys.includes('Amily2角色总集') || entry.keys.includes('角色总览')) return true; - if (entry.content) { - try { - const parsed = parseCustomFormat(entry.content); - return parsed && Object.keys(parsed).length > 0; - } catch (e) { - return false; - } - } - - return false; - }); - } else { - relevantEntries = allEntries.filter(entry => - entry.enabled && - Array.isArray(entry.keys) && - entry.keys.includes(cleanChatId) - ); + const newComment = `${safeCharName}-${chatIdentifier}`; + + let bookName = await getTargetWorldBook(); + + if (!bookName) { + showToastr('error', '未能确定要写入的世界书。请检查主世界书或自定义世界书设置。'); + return false; } - const rosterEntries = relevantEntries.filter(entry => - entry.keys.includes('Amily2角色总集') && entry.keys.includes('角色总览') + const entries = (await safeLorebookEntries(bookName)) || []; + let existing = entries.find(e => + Array.isArray(e.keys) && + e.keys.includes(chatIdentifier) && + e.keys.includes(safeCharName) && + !e.keys.includes('Amily2角色总集') ); - rosterEntries.forEach((entry, index) => { - displayItems.push({ - uid: entry.uid, - isRoster: true, - comment: entry.comment, - content: entry.content, - rosterIndex: index - }); - }); + const entryData = { + comment: newComment, + content: newDescription, + keys: [chatIdentifier, safeCharName, floorRange], + enabled: true, + type: 'selective', + }; - const characterEntries = relevantEntries - .filter(entry => !entry.keys.includes('Amily2角色总集')) - .map(entry => { - try { - logDebug(`[DEBUG] 原始条目数据 UID:${entry.uid}`, { - position: entry.position, - depth: entry.depth, - order: entry.order, - comment: entry.comment - }); - - const positionStringMap = { - 0: 'before_char', - 1: 'after_char', - 2: 'before_an', - 3: 'after_an', - 4: 'at_depth', - 'before_character_definition': 'before_char', - 'after_character_definition': 'after_char', - 'before_author_note': 'before_an', - 'after_author_note': 'after_an', - 'at_depth_as_system': 'at_depth' - }; - - const position = entry.position; - const mappedPosition = positionStringMap[position] || 'at_depth'; - const finalDepth = (position === 4 || position === 'at_depth_as_system') ? (entry.depth ?? 0) : 0; - logDebug(`[DEBUG] 映射结果 UID:${entry.uid}`, { - originalPosition: position, - mappedPosition: mappedPosition, - finalDepth: finalDepth - }); - - return { - uid: entry.uid, - isRoster: false, - comment: entry.comment, - content: entry.content, - parsed: parseCustomFormat(entry.content), - insertionPosition: mappedPosition, - insertionDepth: finalDepth, - insertionOrder: entry.order ?? 7001, - }; - } catch (e) { - logError(`解析角色条目失败 (UID: ${entry.uid}),已跳过。`, e); - return null; + if (existing) { + await safeUpdateLorebookEntries(bookName, [{ uid: existing.uid, ...entryData }]); + } else { + const cwbEntries = entries.filter(e => + Array.isArray(e.keys) && + e.keys.includes(chatIdentifier) && + !e.keys.includes('Amily2角色总集') + ); + let maxDepth = 7000; + cwbEntries.forEach(entry => { + if (entry.position === 'at_depth_as_system' && typeof entry.depth === 'number') { + if (entry.depth >= 7001 && entry.depth > maxDepth) { + maxDepth = entry.depth; + } } - }) - .filter(c => c && c.parsed && Object.keys(c.parsed).length > 0); - - displayItems = displayItems.concat(characterEntries); - - const popupHtml = createCharCardViewerPopupHtml(displayItems); - $('body').append(popupHtml); - const $popup = $(`#${CHAR_CARD_VIEWER_POPUP_ID}`); - bindCharCardViewerPopupEvents($popup); + }); + + const newDepth = maxDepth + 1; + let maxOrder = 7000; + if (cwbEntries.length > 0) { + maxOrder = cwbEntries.reduce((max, entry) => { + const order = Number(entry.order); + return !isNaN(order) && order > max ? order : max; + }, 7000); + } + + const newEntryData = { + ...entryData, + order: 100, + position: 'at_depth_as_system', + depth: newDepth, + }; + + logDebug(`创建新角色条目:${safeCharName}`, { + position: newEntryData.position, + depth: newEntryData.depth, + order: newEntryData.order + }); + + await amilyHelper.createLorebookEntries(bookName, [newEntryData]); + } + showToastr('success', `角色 ${safeCharName} 的描述已保存到世界书。`); + return true; } catch (error) { - logError('无法显示角色卡查看器:', error); - showToastr('error', '加载角色卡数据时出错。'); + logError(`保存世界书失败 for ${characterName}:`, error); + showToastr('error', `保存角色 ${safeCharName} 到世界书失败。`); + return false; } } -function toggleCharCardViewerPopup() { - if ($(`#${CHAR_CARD_VIEWER_POPUP_ID}`).length > 0) { - closeCharCardViewerPopup(); - } else { - showCharCardViewerPopup(); +export async function updateCharacterRosterLorebookEntry(processedCharacterNames, startFloor, endFloor) { + if (!Array.isArray(processedCharacterNames)) return true; + + try { + const context = SillyTavern.getContext(); + if (!context || !context.characterId) { + logDebug('未选择角色,无法更新角色名册。'); + return false; + } + let chatIdentifier = state.currentChatFileIdentifier || '未知聊天'; + if (chatIdentifier === '未知聊天') return false; + + const cleanChatId = chatIdentifier.replace(/ imported/g, ''); + const rosterEntryComment = `Amily2角色总集-${cleanChatId}-角色总览`; + + let characterCardName = '未识别到该角色卡名称'; + try { + const currentChar = context.characters[context.characterId]; + if (currentChar && currentChar.name) { + characterCardName = currentChar.name.trim(); + } + } catch (e) { + logDebug('[CWB] 无法获取角色名称,使用默认值'); + } + + const initialContentPrefix = `此为当前角色卡【${characterCardName}】中登场的角色,AI需要根据剧情让以下角色在合适的时机登场:\n\n`; + + let bookName = await getTargetWorldBook(); + + if (!bookName) { + showToastr('error', '未能确定要写入的世界书。请检查主世界书或自定义世界书设置。'); + return false; + } + + let entries = (await safeLorebookEntries(bookName)) || []; + let existingRosterEntry = entries.find(entry => + entry.comment === rosterEntryComment || + entry.comment === `Amily2角色总集-${chatIdentifier}-角色总览` + ); + + let existingNames = new Set(); + let oldStartFloor = 1; + let oldEndFloor = 0; + + if (existingRosterEntry) { + if (existingRosterEntry.content) { + let contentToParse = existingRosterEntry.content.replace(initialContentPrefix, ''); + + const floorMatch = contentToParse.match(/【前(\d+)楼角色世界书已更新完成】/); + if (floorMatch && floorMatch[1]) { + oldEndFloor = parseInt(floorMatch[1], 10); + } + + contentToParse.split('\n').forEach(line => { + if (line.trim().startsWith('[')) { + const nameMatch = line.match(/\[(.*?):/); + if (nameMatch && nameMatch[1]) { + existingNames.add(nameMatch[1].trim()); + } + } + }); + } + if (Array.isArray(existingRosterEntry.keys)) { + const floorRangeKey = existingRosterEntry.keys.find(k => /^\d+-\d+$/.test(k)); + if (floorRangeKey) { + [oldStartFloor] = floorRangeKey.split('-').map(Number); + } + } + } + + processedCharacterNames.forEach(name => existingNames.add(name.trim())); + + const newStartFloor = Math.min(oldStartFloor, startFloor + 1); + const newEndFloor = Math.max(oldEndFloor, endFloor + 1); + + const newContent = + initialContentPrefix + + [...existingNames] + .sort() + .map(name => `[${name}: (详细查看绿灯角色条目)]`) + .join('\n') + `\n\n{{// 本条勿动,【前${newEndFloor}楼角色世界书已更新完成】否则后续更新无法完成。}}`; + + const newFloorRange = `${newStartFloor}-${newEndFloor}`; + + const baseKeys = [`Amily2角色总集`, cleanChatId, `角色总览`]; + const newKeys = [...baseKeys, newFloorRange]; + + const entryData = { + content: newContent, + keys: newKeys, + type: 'constant', + position: 'before_character_definition', + depth: null, + enabled: true, + order: 9999, + prevent_recursion: true, + }; + + if (existingRosterEntry) { + await safeUpdateLorebookEntries(bookName, [ + { uid: existingRosterEntry.uid, comment: rosterEntryComment, ...entryData }, + ]); + } else { + await amilyHelper.createLorebookEntries(bookName, [ + { comment: rosterEntryComment, ...entryData }, + ]); + } + return true; + } catch (error) { + logError('更新角色名册条目时出错:', error); + return false; } } -function keepButtonInBounds($element, savePosition = false) { - if (!$element || !$element.length) return; - const windowWidth = $(window).width(); - const windowHeight = $(window).height(); - const buttonWidth = $element.outerWidth(); - const buttonHeight = $element.outerHeight(); - let currentPos = $element.offset(); - let newTop = Math.max(0, Math.min(currentPos.top, windowHeight - buttonHeight)); - let newLeft = Math.max(0, Math.min(currentPos.left, windowWidth - buttonWidth)); - $element.css({ top: `${newTop}px`, left: `${newLeft}px` }); - if (savePosition) { - localStorage.setItem(state.STORAGE_KEY_VIEWER_BUTTON_POS, JSON.stringify({ top: $element.css('top'), left: $element.css('left') })); - } -} -function makeButtonDraggable($button) { - let isDragging = false, wasDragged = false, offset = { x: 0, y: 0 }, startPos = { x: 0, y: 0 }; - const DRAG_THRESHOLD = 5; // 5 pixels threshold - - const getCoords = (e) => e.touches && e.touches.length ? e.touches[0] : e; - - const dragStart = function (e) { - if (e.type === 'touchstart') e.preventDefault(); - isDragging = true; - wasDragged = false; - const coords = getCoords(e); - startPos.x = coords.clientX; - startPos.y = coords.clientY; - offset.x = coords.clientX - $button.offset().left; - offset.y = coords.clientY - $button.offset().top; - $button.css('cursor', 'grabbing'); - $('body').css({ 'user-select': 'none', '-webkit-user-select': 'none' }); - }; - - const dragMove = function (e) { - if (!isDragging) return; - const coords = getCoords(e); - const dx = coords.clientX - startPos.x; - const dy = coords.clientY - startPos.y; - - if (!wasDragged && Math.sqrt(dx * dx + dy * dy) > DRAG_THRESHOLD) { - wasDragged = true; - } - - if (wasDragged) { - if (e.type === 'touchmove') e.preventDefault(); - let newX = coords.clientX - offset.x; - let newY = coords.clientY - offset.y; - newX = Math.max(0, Math.min(newX, window.innerWidth - $button.outerWidth())); - newY = Math.max(0, Math.min(newY, window.innerHeight - $button.outerHeight())); - $button.css({ top: newY + 'px', left: newX + 'px', right: '', bottom: '' }); - } - }; - - const dragEnd = function (e) { - if (!isDragging) return; - isDragging = false; - $button.css('cursor', 'grab'); - $('body').css({ 'user-select': 'auto', '-webkit-user-select': 'auto' }); - if (wasDragged) { - keepButtonInBounds($button, true); - } else if (e.type === 'touchend') { - e.preventDefault(); - toggleCharCardViewerPopup(); - } - }; - - $button.on('mousedown', dragStart); - $(document).on('mousemove.cwbViewer', dragMove).on('mouseup.cwbViewer', dragEnd); - $button.on('touchstart', dragStart); - $(document).on('touchmove.cwbViewer', dragMove).on('touchend.cwbViewer', dragEnd); - - $button.on('click', function (e) { - if (wasDragged) { - e.preventDefault(); - e.stopPropagation(); +export async function manageAutoCardUpdateLorebookEntry() { + try { + if (state.worldbookTarget === 'custom' && state.customWorldBook) { + logDebug('[CWB] 使用自定义世界书模式,跳过角色总览条目的自动管理'); return; } - toggleCharCardViewerPopup(); - }); -} -export function initializeCharCardViewer() { - const $existingButton = $(`#${CHAR_CARD_VIEWER_BUTTON_ID}`); - - if ($existingButton.length > 0) { - console.log('[CWB] Char card viewer button already exists'); - setTimeout(() => { - const shouldShow = isCwbEnabled() && state.viewerEnabled; - $existingButton.toggle(shouldShow); - console.log(`[CWB] Force updated existing button visibility: ${shouldShow}`); - }, 100); - return; - } - - const buttonHtml = `
`; - $('body').append(buttonHtml); - const $viewerButton = $(`#${CHAR_CARD_VIEWER_BUTTON_ID}`); - makeButtonDraggable($viewerButton); - - const savedPosition = JSON.parse(localStorage.getItem(state.STORAGE_KEY_VIEWER_BUTTON_POS) || 'null'); - if (savedPosition) { - $viewerButton.css({ top: savedPosition.top, left: savedPosition.left }); - } else { - $viewerButton.css({ top: '120px', right: '10px', left: 'auto' }); - } - - setTimeout(() => { - const shouldShow = isCwbEnabled() && state.viewerEnabled; - $viewerButton.toggle(shouldShow); - console.log(`[CWB] New button created with visibility: ${shouldShow}`); - }, 100); - - console.log('[CWB] Char card viewer button initialized'); - - let resizeTimeout; - $(window).on('resize.cwbViewer', function () { - clearTimeout(resizeTimeout); - resizeTimeout = setTimeout(() => keepButtonInBounds($(`#${CHAR_CARD_VIEWER_BUTTON_ID}`), true), 150); - }); -} - -export function updateViewerButtonVisibility() { - const $button = $(`#${CHAR_CARD_VIEWER_BUTTON_ID}`); - const shouldShow = isCwbEnabled() && state.viewerEnabled; - - console.log(`[CWB] Updating viewer button visibility: ${shouldShow} (master: ${isCwbEnabled()}, viewer: ${state.viewerEnabled})`); - - if ($button.length > 0) { - $button.toggle(shouldShow); - console.log(`[CWB] Viewer button visibility set to: ${shouldShow}`); - } else { - console.log('[CWB] Viewer button not found, will initialize when DOM is ready'); - // Try to initialize if button doesn't exist yet - setTimeout(() => { - initializeCharCardViewer(); - }, 500); - } - - logDebug('悬浮窗按钮显示状态更新:', { - masterEnabled: isCwbEnabled(), - viewerEnabled: state.viewerEnabled, - shouldShow: shouldShow - }); -} - -export function bindCwbApiEvents() { - console.log('[CWB] Binding API events'); - - $('#cwb-api-url').off('input').on('input', function() { - const value = $(this).val(); - extension_settings[extensionName].cwb_api_url = value; - saveSettingsDebounced(); - }); - - $('#cwb-api-key').off('input').on('input', function() { - const value = $(this).val(); - extension_settings[extensionName].cwb_api_key = value; - saveSettingsDebounced(); - }); - - $('#cwb-model').off('input').on('input', function() { - const value = $(this).val(); - extension_settings[extensionName].cwb_model = value; - saveSettingsDebounced(); - }); - - $('#cwb-temperature').off('input').on('input', function() { - const value = parseFloat($(this).val()); - $('#cwb-temperature-value').text(value); - extension_settings[extensionName].cwb_temperature = value; - saveSettingsDebounced(); - }); - - $('#cwb-max-tokens').off('input').on('input', function() { - const value = parseInt($(this).val()); - $('#cwb-max-tokens-value').text(value); - extension_settings[extensionName].cwb_max_tokens = value; - saveSettingsDebounced(); - }); - - $('#cwb-test-connection').off('click').on('click', async function() { - const $button = $(this); - $button.prop('disabled', true).html(' 测试中...'); - - try { - await testCwbConnection(); - } catch (error) { - console.error('[CWB] 测试连接失败:', error); - } finally { - $button.prop('disabled', false).html(' 测试连接'); + const context = SillyTavern.getContext(); + if (!context || !context.characterId) { + logDebug('未选择角色,跳过世界书管理。'); + return; } - }); + const bookName = await getTargetWorldBook(); + if (!bookName) return; - $('#cwb-fetch-models').off('click').on('click', async function() { - const $button = $(this); - $button.prop('disabled', true).html(' 获取中...'); + const entries = (await safeLorebookEntries(bookName)) || []; - try { - const models = await fetchCwbModels(); - const $modelSelect = $('#cwb-model'); - $modelSelect.empty(); - - if (models && models.length > 0) { - models.forEach(model => { - $modelSelect.append(new Option(model.name, model.id)); - }); - showToastr('success', `已获取到 ${models.length} 个模型`); - } else { - $modelSelect.append(new Option('无可用模型', '')); - showToastr('warning', '未获取到可用模型'); + const currentChatId = state.currentChatFileIdentifier; + if (!currentChatId || currentChatId.startsWith('unknown_chat')) { + logError(`无效的聊天标识符 "${currentChatId}"。正在中止世界书管理。`); + return; + } + const cleanChatId = currentChatId.replace(/ imported/g, ''); + + let currentChatRosterExists = false; + const entriesToUpdate = []; + + for (const entry of entries) { + if (Array.isArray(entry.keys) && (entry.keys.includes('Amily2角色总集') || entry.keys.includes(cleanChatId) || entry.keys.includes(currentChatId))) { + + const isForCurrentChat = entry.keys.includes(cleanChatId) || entry.keys.includes(currentChatId); + let shouldBeEnabled = isForCurrentChat; + + if (isForCurrentChat && entry.keys.includes('角色总览')) { + currentChatRosterExists = true; + } + + if (entry.enabled !== shouldBeEnabled) { + entriesToUpdate.push({ uid: entry.uid, enabled: shouldBeEnabled }); + } } - } catch (error) { - console.error('[CWB] 获取模型失败:', error); - $('#cwb-model').empty().append(new Option('获取失败', '')); - } finally { - $button.prop('disabled', false).html(' 获取模型'); } - }); + + if (entriesToUpdate.length > 0) { + await safeUpdateLorebookEntries(bookName, entriesToUpdate); + logDebug(`已为聊天: ${cleanChatId} 管理了 ${entriesToUpdate.length} 个世界书条目的状态。`); + } + + if (!currentChatRosterExists) { + logDebug(`未找到聊天 "${cleanChatId}" 的名册。正在触发创建。`); + await updateCharacterRosterLorebookEntry([]); + } + + } catch (error) { + logError('管理世界书条目时出错:', error); + } } From 5a26dd7d608d27000a2f3e773eac8e36db84772b Mon Sep 17 00:00:00 2001 From: Wx-2025 <351320169@qq.com> Date: Sat, 25 Oct 2025 15:30:24 +0800 Subject: [PATCH 13/49] Update cwb_uiManager.js --- CharacterWorldBook/src/cwb_uiManager.js | 186 +++++++++++------------- 1 file changed, 84 insertions(+), 102 deletions(-) diff --git a/CharacterWorldBook/src/cwb_uiManager.js b/CharacterWorldBook/src/cwb_uiManager.js index 2a72250..b8f0143 100644 --- a/CharacterWorldBook/src/cwb_uiManager.js +++ b/CharacterWorldBook/src/cwb_uiManager.js @@ -145,7 +145,7 @@ function createCharCardViewerPopupHtml(displayItems) { if (charData.psyche_profile) html += renderCard('心智侧写', charData.psyche_profile, 'psyche_profile'); if (charData.social_matrix) html += renderCard('社交矩阵', charData.social_matrix, 'social_matrix'); if (charData.narrative_essence) html += renderCard('叙事精粹', charData.narrative_essence, 'narrative_essence'); - + html += `

注入设置

@@ -184,7 +184,7 @@ function createCharCardViewerPopupHtml(displayItems) { } function bindCharCardViewerPopupEvents($popup) { - $popup.on('change', '.cwb-insertion-position', function () { + $popup.on('change', '.cwb-insertion-position', function() { const $this = $(this); const $depthContainer = $this.closest('.cwb-insertion-settings-content').find('.cwb-insertion-depth-container'); if ($this.val() === 'at_depth') { @@ -200,7 +200,7 @@ function bindCharCardViewerPopupEvents($popup) { showCharCardViewerPopup(); }); - $popup.find('#cwb-manual-update-btn').on('click', async function () { + $popup.find('#cwb-manual-update-btn').on('click', async function() { const $button = $(this); $button.prop('disabled', true).html(' 更新中...'); await manualUpdateLogic(); @@ -217,7 +217,7 @@ function bindCharCardViewerPopupEvents($popup) { $popup.find(`#cwb-char-content-${targetUid}`).addClass('active'); }); - $popup.find('.cwb-cyber-tab__delete').on('click', async function (e) { + $popup.find('.cwb-cyber-tab__delete').on('click', async function(e) { e.stopPropagation(); if (confirm('您确定要删除这个角色条目吗?此操作不可撤销。')) { const uidToDelete = $(this).data('char-uid'); @@ -235,9 +235,9 @@ function bindCharCardViewerPopupEvents($popup) { } }); - $popup.find('#cwb-viewer-delete-all').on('click', async function () { + $popup.find('#cwb-viewer-delete-all').on('click', async function() { if (confirm('您确定要清除当前聊天中的所有角色卡和总览吗?此操作将删除所有相关条目,且不可撤销。')) { - const allUids = $popup.find('.cwb-cyber-tab__button').map(function () { + const allUids = $popup.find('.cwb-cyber-tab__button').map(function() { return $(this).data('char-uid'); }).get(); if (allUids.length > 0) { @@ -278,18 +278,10 @@ function bindCharCardViewerPopupEvents($popup) { if ($field.data('is-array')) { value = value.split('\n').map(l => l.trim()).filter(Boolean); } - if (path) { - setNestedValue(collectedData, path, value); + if(path){ + setNestedValue(collectedData, path, value); } }); - let localTavernHelper = TavernHelper; - if (!localTavernHelper) { - // TavernHelper 未定义的情况下触发,但是为什么? - (localTavernHelper = window.TavernHelper); - if (localTavernHelper) { - TavernHelper = localTavernHelper; - } - } const finalContentToSave = buildCustomFormat(collectedData); const allEntries = await TavernHelper.getLorebookEntries(book); const entryToUpdate = allEntries.find(e => e.uid === targetUid); @@ -307,7 +299,7 @@ function bindCharCardViewerPopupEvents($popup) { const positionMap = { 'before_char': 'before_character_definition', - 'after_char': 'after_character_definition', + 'after_char': 'after_character_definition', 'before_an': 'before_author_note', 'after_an': 'after_author_note', 'at_depth': 'at_depth_as_system' @@ -317,7 +309,7 @@ function bindCharCardViewerPopupEvents($popup) { finalEntryData.content = finalContentToSave; finalEntryData.uid = targetUid; - + const newPosition = positionMap[insertionPosition]; finalEntryData.position = newPosition || 'before_character_definition'; if (insertionPosition === 'at_depth') { @@ -325,7 +317,7 @@ function bindCharCardViewerPopupEvents($popup) { } else { finalEntryData.depth = null; } - + finalEntryData.order = isNaN(insertionOrder) ? 7001 : insertionOrder; logDebug(`[DEBUG] 最终保存数据 UID:${targetUid}`, { @@ -334,14 +326,7 @@ function bindCharCardViewerPopupEvents($popup) { order: finalEntryData.order, hasDepthField: 'depth' in finalEntryData }); - localTavernHelper = TavernHelper; - if (!localTavernHelper) { - // TavernHelper 未定义的情况下触发,但是为什么? - (localTavernHelper = window.TavernHelper); - if (localTavernHelper) { - TavernHelper = localTavernHelper; - } - } + await TavernHelper.setLorebookEntries(book, [finalEntryData]); showToastr('success', '角色卡已成功保存!'); } catch (error) { @@ -358,7 +343,7 @@ function closeCharCardViewerPopup() { } export async function showCharCardViewerPopup() { - if (!isCwbEnabled()) return; + if (!isCwbEnabled()) return; closeCharCardViewerPopup(); try { const book = await getTargetWorldBook(); @@ -368,14 +353,6 @@ export async function showCharCardViewerPopup() { bindCharCardViewerPopupEvents($(`#${CHAR_CARD_VIEWER_POPUP_ID}`)); return; } - let localTavernHelper = TavernHelper; - if (!localTavernHelper) { - // TavernHelper 未定义的情况下触发,但是为什么? - (localTavernHelper = window.TavernHelper); - if (localTavernHelper) { - TavernHelper = localTavernHelper; - } - } const allEntries = await TavernHelper.getLorebookEntries(book); let currentChatId = state.currentChatFileIdentifier; @@ -385,7 +362,7 @@ export async function showCharCardViewerPopup() { bindCharCardViewerPopupEvents($(`#${CHAR_CARD_VIEWER_POPUP_ID}`)); return; } - + const cleanChatId = currentChatId.replace(/ imported/g, ''); let displayItems = []; @@ -402,76 +379,81 @@ export async function showCharCardViewerPopup() { return false; } } - + return false; }); } else { - relevantEntries = allEntries.filter(entry => + relevantEntries = allEntries.filter(entry => entry.enabled && Array.isArray(entry.keys) && entry.keys.includes(cleanChatId) ); } - const rosterEntries = relevantEntries.filter(entry => + const rosterEntries = relevantEntries.filter(entry => entry.keys.includes('Amily2角色总集') && entry.keys.includes('角色总览') ); rosterEntries.forEach((entry, index) => { - displayItems.push({ - uid: entry.uid, - isRoster: true, - comment: entry.comment, + displayItems.push({ + uid: entry.uid, + isRoster: true, + comment: entry.comment, content: entry.content, - rosterIndex: index + rosterIndex: index }); }); const characterEntries = relevantEntries .filter(entry => !entry.keys.includes('Amily2角色总集')) .map(entry => { - logDebug(`[DEBUG] 原始条目数据 UID:${entry.uid}`, { - position: entry.position, - depth: entry.depth, - order: entry.order, - comment: entry.comment - }); + try { + logDebug(`[DEBUG] 原始条目数据 UID:${entry.uid}`, { + position: entry.position, + depth: entry.depth, + order: entry.order, + comment: entry.comment + }); - const positionStringMap = { - 0: 'before_char', - 1: 'after_char', - 2: 'before_an', - 3: 'after_an', - 4: 'at_depth', - 'before_character_definition': 'before_char', - 'after_character_definition': 'after_char', - 'before_author_note': 'before_an', - 'after_author_note': 'after_an', - 'at_depth_as_system': 'at_depth' - }; + const positionStringMap = { + 0: 'before_char', + 1: 'after_char', + 2: 'before_an', + 3: 'after_an', + 4: 'at_depth', + 'before_character_definition': 'before_char', + 'after_character_definition': 'after_char', + 'before_author_note': 'before_an', + 'after_author_note': 'after_an', + 'at_depth_as_system': 'at_depth' + }; - const position = entry.position; - const mappedPosition = positionStringMap[position] || 'at_depth'; - const finalDepth = (position === 4 || position === 'at_depth_as_system') ? (entry.depth ?? 0) : 0; - logDebug(`[DEBUG] 映射结果 UID:${entry.uid}`, { - originalPosition: position, - mappedPosition: mappedPosition, - finalDepth: finalDepth - }); + const position = entry.position; + const mappedPosition = positionStringMap[position] || 'at_depth'; + const finalDepth = (position === 4 || position === 'at_depth_as_system') ? (entry.depth ?? 0) : 0; + logDebug(`[DEBUG] 映射结果 UID:${entry.uid}`, { + originalPosition: position, + mappedPosition: mappedPosition, + finalDepth: finalDepth + }); - return { - uid: entry.uid, - isRoster: false, - comment: entry.comment, - content: entry.content, - parsed: parseCustomFormat(entry.content), - insertionPosition: mappedPosition, - insertionDepth: finalDepth, - insertionOrder: entry.order ?? 7001, - }; + return { + uid: entry.uid, + isRoster: false, + comment: entry.comment, + content: entry.content, + parsed: parseCustomFormat(entry.content), + insertionPosition: mappedPosition, + insertionDepth: finalDepth, + insertionOrder: entry.order ?? 7001, + }; + } catch (e) { + logError(`解析角色条目失败 (UID: ${entry.uid}),已跳过。`, e); + return null; + } }) - .filter(c => c.parsed && Object.keys(c.parsed).length > 0); - + .filter(c => c && c.parsed && Object.keys(c.parsed).length > 0); + displayItems = displayItems.concat(characterEntries); const popupHtml = createCharCardViewerPopupHtml(displayItems); @@ -576,7 +558,7 @@ function makeButtonDraggable($button) { export function initializeCharCardViewer() { const $existingButton = $(`#${CHAR_CARD_VIEWER_BUTTON_ID}`); - + if ($existingButton.length > 0) { console.log('[CWB] Char card viewer button already exists'); setTimeout(() => { @@ -586,12 +568,12 @@ export function initializeCharCardViewer() { }, 100); return; } - + const buttonHtml = `
`; $('body').append(buttonHtml); const $viewerButton = $(`#${CHAR_CARD_VIEWER_BUTTON_ID}`); makeButtonDraggable($viewerButton); - + const savedPosition = JSON.parse(localStorage.getItem(state.STORAGE_KEY_VIEWER_BUTTON_POS) || 'null'); if (savedPosition) { $viewerButton.css({ top: savedPosition.top, left: savedPosition.left }); @@ -604,9 +586,9 @@ export function initializeCharCardViewer() { $viewerButton.toggle(shouldShow); console.log(`[CWB] New button created with visibility: ${shouldShow}`); }, 100); - + console.log('[CWB] Char card viewer button initialized'); - + let resizeTimeout; $(window).on('resize.cwbViewer', function () { clearTimeout(resizeTimeout); @@ -617,9 +599,9 @@ export function initializeCharCardViewer() { export function updateViewerButtonVisibility() { const $button = $(`#${CHAR_CARD_VIEWER_BUTTON_ID}`); const shouldShow = isCwbEnabled() && state.viewerEnabled; - + console.log(`[CWB] Updating viewer button visibility: ${shouldShow} (master: ${isCwbEnabled()}, viewer: ${state.viewerEnabled})`); - + if ($button.length > 0) { $button.toggle(shouldShow); console.log(`[CWB] Viewer button visibility set to: ${shouldShow}`); @@ -630,7 +612,7 @@ export function updateViewerButtonVisibility() { initializeCharCardViewer(); }, 500); } - + logDebug('悬浮窗按钮显示状态更新:', { masterEnabled: isCwbEnabled(), viewerEnabled: state.viewerEnabled, @@ -640,43 +622,43 @@ export function updateViewerButtonVisibility() { export function bindCwbApiEvents() { console.log('[CWB] Binding API events'); - - $('#cwb-api-url').off('input').on('input', function () { + + $('#cwb-api-url').off('input').on('input', function() { const value = $(this).val(); extension_settings[extensionName].cwb_api_url = value; saveSettingsDebounced(); }); - $('#cwb-api-key').off('input').on('input', function () { + $('#cwb-api-key').off('input').on('input', function() { const value = $(this).val(); extension_settings[extensionName].cwb_api_key = value; saveSettingsDebounced(); }); - $('#cwb-model').off('input').on('input', function () { + $('#cwb-model').off('input').on('input', function() { const value = $(this).val(); extension_settings[extensionName].cwb_model = value; saveSettingsDebounced(); }); - $('#cwb-temperature').off('input').on('input', function () { + $('#cwb-temperature').off('input').on('input', function() { const value = parseFloat($(this).val()); $('#cwb-temperature-value').text(value); extension_settings[extensionName].cwb_temperature = value; saveSettingsDebounced(); }); - $('#cwb-max-tokens').off('input').on('input', function () { + $('#cwb-max-tokens').off('input').on('input', function() { const value = parseInt($(this).val()); $('#cwb-max-tokens-value').text(value); extension_settings[extensionName].cwb_max_tokens = value; saveSettingsDebounced(); }); - $('#cwb-test-connection').off('click').on('click', async function () { + $('#cwb-test-connection').off('click').on('click', async function() { const $button = $(this); $button.prop('disabled', true).html(' 测试中...'); - + try { await testCwbConnection(); } catch (error) { @@ -686,15 +668,15 @@ export function bindCwbApiEvents() { } }); - $('#cwb-fetch-models').off('click').on('click', async function () { + $('#cwb-fetch-models').off('click').on('click', async function() { const $button = $(this); $button.prop('disabled', true).html(' 获取中...'); - + try { const models = await fetchCwbModels(); const $modelSelect = $('#cwb-model'); $modelSelect.empty(); - + if (models && models.length > 0) { models.forEach(model => { $modelSelect.append(new Option(model.name, model.id)); From 0f9ac3e81169baaed46aa4b146537b5c6cb4702f Mon Sep 17 00:00:00 2001 From: Wx-2025 <351320169@qq.com> Date: Sat, 25 Oct 2025 17:58:05 +0800 Subject: [PATCH 14/49] Update lore.js --- core/lore.js | 47 +++++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/core/lore.js b/core/lore.js index 243a672..acd0ec8 100644 --- a/core/lore.js +++ b/core/lore.js @@ -270,7 +270,12 @@ export async function getPlotOptimizedWorldbookContent(context, apiSettings) { const panel = $('#amily2_plot_optimization_panel'); let liveSettings = {}; - if (panel.length > 0) { + // Check if the panel exists and its dynamic content (the entry list) has been populated. + // This helps prevent a race condition where we read from an empty, partially-rendered panel. + const isPanelReady = panel.length > 0 && panel.find('#amily2_opt_worldbook_entry_list_container input[type="checkbox"]').length > 0; + + if (isPanelReady) { + // Panel is ready, so we can trust the live values from the UI. liveSettings.worldbookEnabled = panel.find('#amily2_opt_worldbook_enabled').is(':checked'); liveSettings.worldbookSource = panel.find('input[name="amily2_opt_worldbook_source"]:checked').val() || 'character'; @@ -284,25 +289,30 @@ export async function getPlotOptimizedWorldbookContent(context, apiSettings) { liveSettings.worldbookCharLimit = parseInt(panel.find('#amily2_opt_worldbook_char_limit').val(), 10) || 60000; let enabledEntries = {}; - panel.find('#amily2_opt_worldbook_entry_list_container input[type="checkbox"]').each(function() { - if ($(this).is(':checked')) { - const bookName = $(this).data('book'); - const uid = parseInt($(this).data('uid')); - if (!enabledEntries[bookName]) { - enabledEntries[bookName] = []; - } - enabledEntries[bookName].push(uid); + panel.find('#amily2_opt_worldbook_entry_list_container input[type="checkbox"]:checked').each(function() { + const bookName = $(this).data('book'); + const uid = parseInt($(this).data('uid')); + if (!enabledEntries[bookName]) { + enabledEntries[bookName] = []; } + enabledEntries[bookName].push(uid); }); liveSettings.enabledWorldbookEntries = enabledEntries; } else { - console.warn('[剧情优化大师] 未找到设置面板,世界书功能将回退到使用已保存的设置。'); + // Panel is not ready or doesn't exist. Fall back to the saved settings from the extension. + // This uses the correct, prefixed keys. + if (panel.length > 0) { + console.warn('[剧情优化大师] 检测到UI面板但内容未完全加载,回退到使用已保存的设置。'); + } else { + console.warn('[剧情优化大师] 未找到设置面板,世界书功能将使用已保存的设置。'); + } + liveSettings = { - worldbookEnabled: apiSettings.worldbookEnabled, - worldbookSource: apiSettings.worldbookSource, - selectedWorldbooks: apiSettings.selectedWorldbooks, - worldbookCharLimit: apiSettings.worldbookCharLimit, - enabledWorldbookEntries: apiSettings.enabledWorldbookEntries, + worldbookEnabled: apiSettings.plotOpt_worldbook_enabled, + worldbookSource: apiSettings.plotOpt_worldbook_source || 'character', // Default to 'character' + selectedWorldbooks: apiSettings.plotOpt_worldbook_selected_worldbooks, + worldbookCharLimit: apiSettings.plotOpt_worldbook_char_limit, + enabledWorldbookEntries: apiSettings.plotOpt_worldbook_selected_entries, }; } @@ -344,7 +354,8 @@ export async function getPlotOptimizedWorldbookContent(context, apiSettings) { const userEnabledEntries = allEntries.filter(entry => { if (!entry.enabled) return false; const bookConfig = enabledEntriesMap[entry.bookName]; - return bookConfig ? bookConfig.includes(entry.uid) : false; + // 同时检查数字和字符串类型的UID,以兼容从实时UI(数字)和已保存设置(可能为字符串)中读取的配置 + return bookConfig ? (bookConfig.includes(entry.uid) || bookConfig.includes(String(entry.uid))) : false; }); if (userEnabledEntries.length === 0) return ''; @@ -352,8 +363,8 @@ export async function getPlotOptimizedWorldbookContent(context, apiSettings) { const chatHistory = context.chat.map(message => message.mes).join('\n').toLowerCase(); const getEntryKeywords = (entry) => [...new Set([...(entry.key || []), ...(entry.keys || [])])].map(k => k.toLowerCase()); - const blueLightEntries = userEnabledEntries.filter(entry => entry.type === 'constant'); - let pendingGreenLights = userEnabledEntries.filter(entry => entry.type !== 'constant'); + const blueLightEntries = userEnabledEntries.filter(entry => entry.constant); + let pendingGreenLights = userEnabledEntries.filter(entry => !entry.constant); const triggeredEntries = new Set([...blueLightEntries]); From 4239c8f86e3d353bdf0791bc3d446f27b35cb129 Mon Sep 17 00:00:00 2001 From: Wx-2025 <351320169@qq.com> Date: Sat, 25 Oct 2025 20:30:08 +0800 Subject: [PATCH 15/49] Update cwb_uiManager.js --- CharacterWorldBook/src/cwb_uiManager.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/CharacterWorldBook/src/cwb_uiManager.js b/CharacterWorldBook/src/cwb_uiManager.js index b8f0143..19688ab 100644 --- a/CharacterWorldBook/src/cwb_uiManager.js +++ b/CharacterWorldBook/src/cwb_uiManager.js @@ -6,8 +6,9 @@ import { testCwbConnection, fetchCwbModels } from './cwb_apiService.js'; import { extensionName } from '../../utils/settings.js'; import { extension_settings } from '/scripts/extensions.js'; import { saveSettingsDebounced } from '/script.js'; +import { amilyHelper } from '../../core/tavern-helper/main.js'; -const { jQuery: $, SillyTavern, TavernHelper } = window; +const { jQuery: $, SillyTavern } = window; function createCharCardViewerPopupHtml(displayItems) { const pathToLabelMap = { @@ -283,7 +284,7 @@ function bindCharCardViewerPopupEvents($popup) { } }); const finalContentToSave = buildCustomFormat(collectedData); - const allEntries = await TavernHelper.getLorebookEntries(book); + const allEntries = await amilyHelper.getLorebookEntries(book); const entryToUpdate = allEntries.find(e => e.uid === targetUid); if (!entryToUpdate) throw new Error('无法在世界书中找到原始条目。'); @@ -327,7 +328,7 @@ function bindCharCardViewerPopupEvents($popup) { hasDepthField: 'depth' in finalEntryData }); - await TavernHelper.setLorebookEntries(book, [finalEntryData]); + await amilyHelper.setLorebookEntries(book, [finalEntryData]); showToastr('success', '角色卡已成功保存!'); } catch (error) { logError('保存角色卡失败:', error); @@ -353,7 +354,7 @@ export async function showCharCardViewerPopup() { bindCharCardViewerPopupEvents($(`#${CHAR_CARD_VIEWER_POPUP_ID}`)); return; } - const allEntries = await TavernHelper.getLorebookEntries(book); + const allEntries = await amilyHelper.getLorebookEntries(book); let currentChatId = state.currentChatFileIdentifier; if (!currentChatId || currentChatId.startsWith('unknown_chat')) { From 30c3e953e234b6e8f4170135b60a2f9ed45fb8da Mon Sep 17 00:00:00 2001 From: Wx-2025 <351320169@qq.com> Date: Sun, 26 Oct 2025 04:58:08 +0800 Subject: [PATCH 16/49] Update cwb_lorebookManager.js --- CharacterWorldBook/src/cwb_lorebookManager.js | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/CharacterWorldBook/src/cwb_lorebookManager.js b/CharacterWorldBook/src/cwb_lorebookManager.js index 9385e59..1e06e8e 100644 --- a/CharacterWorldBook/src/cwb_lorebookManager.js +++ b/CharacterWorldBook/src/cwb_lorebookManager.js @@ -1,13 +1,7 @@ import { state } from './cwb_state.js'; import { logError, logDebug, showToastr, parseCustomFormat } from './cwb_utils.js'; -import { - safeLorebooks, - safeCharLorebooks, - safeLorebookEntries, - safeUpdateLorebookEntries, - compatibleWriteToLorebook, -} from '../../core/tavernhelper-compatibility.js'; import { amilyHelper } from '../../core/tavern-helper/main.js'; +import { loadWorldInfo, saveWorldInfo } from "/scripts/world-info.js"; const { SillyTavern } = window; @@ -20,7 +14,7 @@ export async function getTargetWorldBook() { return state.customWorldBook; } try { - const charLorebooks = await safeCharLorebooks(); + const charLorebooks = await amilyHelper.getCharLorebooks(); const primaryBook = charLorebooks.primary; if (!primaryBook) { showToastr('error', '当前角色未设置主世界书。'); @@ -44,12 +38,12 @@ export async function deleteLorebookEntries(uids) { const book = await getTargetWorldBook(); if (!book) throw new Error('未找到目标世界书。'); - const bookData = await amilyHelper.loadWorldInfo(book); + const bookData = await loadWorldInfo(book); if (!bookData) throw new Error(`World book "${book}" not found.`); uids.forEach(uid => { delete bookData.entries[uid]; }); - await amilyHelper.saveWorldInfo(book, bookData, true); + await saveWorldInfo(book, bookData, true); } catch (error) { logError('删除世界书条目失败:', error); showToastr('error', `删除失败: ${error.message}`); @@ -80,7 +74,7 @@ export async function saveDescriptionToLorebook(characterName, newDescription, s return false; } - const entries = (await safeLorebookEntries(bookName)) || []; + const entries = await amilyHelper.getLorebookEntries(bookName); let existing = entries.find(e => Array.isArray(e.keys) && e.keys.includes(chatIdentifier) && @@ -97,7 +91,7 @@ export async function saveDescriptionToLorebook(characterName, newDescription, s }; if (existing) { - await safeUpdateLorebookEntries(bookName, [{ uid: existing.uid, ...entryData }]); + await amilyHelper.setLorebookEntries(bookName, [{ uid: existing.uid, ...entryData }]); } else { const cwbEntries = entries.filter(e => Array.isArray(e.keys) && @@ -180,7 +174,7 @@ export async function updateCharacterRosterLorebookEntry(processedCharacterNames return false; } - let entries = (await safeLorebookEntries(bookName)) || []; + let entries = await amilyHelper.getLorebookEntries(bookName); let existingRosterEntry = entries.find(entry => entry.comment === rosterEntryComment || entry.comment === `Amily2角色总集-${chatIdentifier}-角色总览` @@ -245,7 +239,7 @@ export async function updateCharacterRosterLorebookEntry(processedCharacterNames }; if (existingRosterEntry) { - await safeUpdateLorebookEntries(bookName, [ + await amilyHelper.setLorebookEntries(bookName, [ { uid: existingRosterEntry.uid, comment: rosterEntryComment, ...entryData }, ]); } else { @@ -276,7 +270,7 @@ export async function manageAutoCardUpdateLorebookEntry() { const bookName = await getTargetWorldBook(); if (!bookName) return; - const entries = (await safeLorebookEntries(bookName)) || []; + const entries = await amilyHelper.getLorebookEntries(bookName); const currentChatId = state.currentChatFileIdentifier; if (!currentChatId || currentChatId.startsWith('unknown_chat')) { @@ -305,7 +299,7 @@ export async function manageAutoCardUpdateLorebookEntry() { } if (entriesToUpdate.length > 0) { - await safeUpdateLorebookEntries(bookName, entriesToUpdate); + await amilyHelper.setLorebookEntries(bookName, entriesToUpdate); logDebug(`已为聊天: ${cleanChatId} 管理了 ${entriesToUpdate.length} 个世界书条目的状态。`); } From e7522e1e497a96928ce98a84f8718bf2fef4f2a9 Mon Sep 17 00:00:00 2001 From: Wx-2025 <351320169@qq.com> Date: Sun, 26 Oct 2025 04:58:31 +0800 Subject: [PATCH 17/49] Update cwb_uiManager.js --- CharacterWorldBook/src/cwb_uiManager.js | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/CharacterWorldBook/src/cwb_uiManager.js b/CharacterWorldBook/src/cwb_uiManager.js index 19688ab..450cf7b 100644 --- a/CharacterWorldBook/src/cwb_uiManager.js +++ b/CharacterWorldBook/src/cwb_uiManager.js @@ -284,9 +284,6 @@ function bindCharCardViewerPopupEvents($popup) { } }); const finalContentToSave = buildCustomFormat(collectedData); - const allEntries = await amilyHelper.getLorebookEntries(book); - const entryToUpdate = allEntries.find(e => e.uid === targetUid); - if (!entryToUpdate) throw new Error('无法在世界书中找到原始条目。'); const insertionPosition = $activePane.find('.cwb-insertion-position').val(); const insertionDepth = parseInt($activePane.find('.cwb-insertion-depth').val(), 10); @@ -306,20 +303,18 @@ function bindCharCardViewerPopupEvents($popup) { 'at_depth': 'at_depth_as_system' }; - const finalEntryData = { ...entryToUpdate }; + const finalEntryData = { + uid: targetUid, + content: finalContentToSave, + position: positionMap[insertionPosition] || 'before_character_definition', + order: isNaN(insertionOrder) ? 7001 : insertionOrder, + }; - finalEntryData.content = finalContentToSave; - finalEntryData.uid = targetUid; - - const newPosition = positionMap[insertionPosition]; - finalEntryData.position = newPosition || 'before_character_definition'; if (insertionPosition === 'at_depth') { finalEntryData.depth = isNaN(insertionDepth) ? 0 : insertionDepth; } else { finalEntryData.depth = null; } - - finalEntryData.order = isNaN(insertionOrder) ? 7001 : insertionOrder; logDebug(`[DEBUG] 最终保存数据 UID:${targetUid}`, { position: finalEntryData.position, From 5c0bf694fd4bcc63b035b3f2dd1593fc67feafb6 Mon Sep 17 00:00:00 2001 From: Wx-2025 <351320169@qq.com> Date: Sun, 26 Oct 2025 04:59:30 +0800 Subject: [PATCH 18/49] Add files via upload --- core/api/JqyhApi.js | 769 +++++++++++++++++++++--------------------- core/api/NccsApi.js | 773 +++++++++++++++++++++---------------------- core/api/Ngms_api.js | 773 +++++++++++++++++++++---------------------- core/api/SybdApi.js | 773 +++++++++++++++++++++---------------------- 4 files changed, 1538 insertions(+), 1550 deletions(-) diff --git a/core/api/JqyhApi.js b/core/api/JqyhApi.js index a889d50..9a4fb34 100644 --- a/core/api/JqyhApi.js +++ b/core/api/JqyhApi.js @@ -1,386 +1,383 @@ -import { extension_settings, getContext } from "/scripts/extensions.js"; -import { characters, this_chid, getRequestHeaders, saveSettingsDebounced, eventSource, event_types } from "/script.js"; -import { extensionName } from "../../utils/settings.js"; - -let ChatCompletionService = undefined; -try { - const module = await import('/scripts/custom-request.js'); - ChatCompletionService = module.ChatCompletionService; - console.log('[Amily2号-Jqyh外交部] 已成功召唤"皇家信使"(ChatCompletionService)。'); -} catch (e) { - console.warn("[Amily2号-Jqyh外交部] 未能召唤“皇家信使”,部分高级功能(如Claw代理)将受限。请考虑更新SillyTavern版本。", e); -} - -function normalizeApiResponse(responseData) { - let data = responseData; - if (typeof data === 'string') { - try { - data = JSON.parse(data); - } catch (e) { - console.error(`[${extensionName}] Jqyh API响应JSON解析失败:`, e); - return { error: { message: 'Invalid JSON response' } }; - } - } - if (data && typeof data.data === 'object' && data.data !== null && !Array.isArray(data.data)) { - if (Object.hasOwn(data.data, 'data')) { - data = data.data; - } - } - if (data && data.choices && data.choices[0]) { - return { content: data.choices[0].message?.content?.trim() }; - } - if (data && data.content) { - return { content: data.content.trim() }; - } - if (data && data.data) { - return { data: data.data }; - } - if (data && data.error) { - return { error: data.error }; - } - return data; -} - -export function getJqyhApiSettings() { - return { - apiMode: extension_settings[extensionName]?.jqyhApiMode || 'openai_test', - apiUrl: extension_settings[extensionName]?.jqyhApiUrl?.trim() || '', - apiKey: extension_settings[extensionName]?.jqyhApiKey?.trim() || '', - model: extension_settings[extensionName]?.jqyhModel || '', - maxTokens: extension_settings[extensionName]?.jqyhMaxTokens || 4000, - temperature: extension_settings[extensionName]?.jqyhTemperature || 0.7, - tavernProfile: extension_settings[extensionName]?.jqyhTavernProfile || '' - }; -} - -export async function callJqyhAI(messages, options = {}) { - if (window.AMILY2_SYSTEM_PARALYZED === true) { - console.error("[Amily2-Jqyh制裁] 系统完整性已受损,所有外交活动被无限期中止。"); - return null; - } - - const apiSettings = getJqyhApiSettings(); - - const finalOptions = { - maxTokens: apiSettings.maxTokens, - temperature: apiSettings.temperature, - model: apiSettings.model, - apiUrl: apiSettings.apiUrl, - apiKey: apiSettings.apiKey, - apiMode: apiSettings.apiMode, - tavernProfile: apiSettings.tavernProfile, - ...options - }; - - if (finalOptions.apiMode !== 'sillytavern_preset') { - if (!finalOptions.apiUrl || !finalOptions.model || !finalOptions.apiKey) { - console.warn("[Amily2-Jqyh外交部] API配置不完整,无法调用AI"); - toastr.error("API配置不完整,请检查URL、Key和模型配置。", "Jqyh-外交部"); - return null; - } - } - - console.groupCollapsed(`[Amily2号-Jqyh统一API调用] ${new Date().toLocaleTimeString()}`); - console.log("【请求参数】:", { - mode: finalOptions.apiMode, - model: finalOptions.model, - maxTokens: finalOptions.maxTokens, - temperature: finalOptions.temperature, - messagesCount: messages.length - }); - console.log("【消息内容】:", messages); - console.groupEnd(); - - try { - let responseContent; - - switch (finalOptions.apiMode) { - case 'openai_test': - responseContent = await callJqyhOpenAITest(messages, finalOptions); - break; - case 'sillytavern_preset': - responseContent = await callJqyhSillyTavernPreset(messages, finalOptions); - break; - default: - console.error(`[Amily2-Jqyh外交部] 未支持的API模式: ${finalOptions.apiMode}`); - return null; - } - - if (!responseContent) { - console.warn('[Amily2-Jqyh外交部] 未能获取AI响应内容'); - return null; - } - - console.groupCollapsed("[Amily2号-Jqyh AI回复]"); - console.log(responseContent); - console.groupEnd(); - - return responseContent; - - } catch (error) { - console.error(`[Amily2-Jqyh外交部] API调用发生错误:`, error); - - if (error.message.includes('400')) { - toastr.error(`API请求格式错误 (400): 请检查消息格式和模型配置`, "Jqyh API调用失败"); - } else if (error.message.includes('401')) { - toastr.error(`API认证失败 (401): 请检查API Key配置`, "Jqyh API调用失败"); - } else if (error.message.includes('403')) { - toastr.error(`API访问被拒绝 (403): 请检查权限设置`, "Jqyh API调用失败"); - } else if (error.message.includes('429')) { - toastr.error(`API调用频率超限 (429): 请稍后重试`, "Jqyh API调用失败"); - } else if (error.message.includes('500')) { - toastr.error(`API服务器错误 (500): 请稍后重试`, "Jqyh API调用失败"); - } else { - toastr.error(`API调用失败: ${error.message}`, "Jqyh API调用失败"); - } - - return null; - } -} - -async function callJqyhOpenAITest(messages, options) { - const isGoogleApi = options.apiUrl.includes('googleapis.com'); - - const body = { - chat_completion_source: 'openai', - messages: messages, - model: options.model, - reverse_proxy: options.apiUrl, - proxy_password: options.apiKey, - stream: false, - max_tokens: options.maxTokens || 30000, - temperature: options.temperature || 1, - top_p: options.top_p || 1, - }; - - if (!isGoogleApi) { - Object.assign(body, { - custom_prompt_post_processing: 'strict', - enable_web_search: false, - frequency_penalty: 0, - group_names: [], - include_reasoning: false, - presence_penalty: 0.12, - reasoning_effort: 'medium', - request_images: false, - }); - } - - const response = await fetch('/api/backends/chat-completions/generate', { - method: 'POST', - headers: { ...getRequestHeaders(), 'Content-Type': 'application/json' }, - body: JSON.stringify(body) - }); - - if (!response.ok) { - const errorText = await response.text(); - throw new Error(`Jqyh全兼容API请求失败: ${response.status} - ${errorText}`); - } - - const responseData = await response.json(); - return responseData?.choices?.[0]?.message?.content; -} - -async function callJqyhSillyTavernPreset(messages, options) { - console.log('[Amily2号-JqyhST预设] 使用SillyTavern预设调用'); - - if (!window.TavernHelper || !window.TavernHelper.triggerSlash) { - throw new Error('TavernHelper不可用,无法使用SillyTavern预设模式'); - } - - const context = getContext(); - if (!context) { - throw new Error('无法获取SillyTavern上下文'); - } - - const profileId = options.tavernProfile; - if (!profileId) { - throw new Error('未配置SillyTavern预设ID'); - } - - let originalProfile = ''; - let responsePromise; - - try { - originalProfile = await window.TavernHelper.triggerSlash('/profile'); - console.log(`[Amily2号-JqyhST预设] 当前配置文件: ${originalProfile}`); - - const targetProfile = context.extensionSettings?.connectionManager?.profiles?.find(p => p.id === profileId); - if (!targetProfile) { - throw new Error(`未找到配置文件ID: ${profileId}`); - } - - const targetProfileName = targetProfile.name; - console.log(`[Amily2号-JqyhST预设] 目标配置文件: ${targetProfileName}`); - - const currentProfile = await window.TavernHelper.triggerSlash('/profile'); - if (currentProfile !== targetProfileName) { - console.log(`[Amily2号-JqyhST预设] 切换配置文件: ${currentProfile} -> ${targetProfileName}`); - const escapedProfileName = targetProfileName.replace(/"/g, '\\"'); - await window.TavernHelper.triggerSlash(`/profile await=true "${escapedProfileName}"`); - } - - if (!context.ConnectionManagerRequestService) { - throw new Error('ConnectionManagerRequestService不可用'); - } - - console.log(`[Amily2号-JqyhST预设] 通过配置文件 ${targetProfileName} 发送请求`); - responsePromise = context.ConnectionManagerRequestService.sendRequest( - targetProfile.id, - messages, - options.maxTokens || 4000 - ); - - } finally { - try { - const currentProfileAfterCall = await window.TavernHelper.triggerSlash('/profile'); - if (originalProfile && originalProfile !== currentProfileAfterCall) { - console.log(`[Amily2号-JqyhST预设] 恢复原始配置文件: ${currentProfileAfterCall} -> ${originalProfile}`); - const escapedOriginalProfile = originalProfile.replace(/"/g, '\\"'); - await window.TavernHelper.triggerSlash(`/profile await=true "${escapedOriginalProfile}"`); - } - } catch (restoreError) { - console.error('[Amily2号-JqyhST预设] 恢复配置文件失败:', restoreError); - } - } - - const result = await responsePromise; - - if (!result) { - throw new Error('未收到API响应'); - } - - const normalizedResult = normalizeApiResponse(result); - if (normalizedResult.error) { - throw new Error(normalizedResult.error.message || 'SillyTavern预设API调用失败'); - } - - return normalizedResult.content; -} - -export async function fetchJqyhModels() { - console.log('[Amily2号-Jqyh外交部] 开始获取模型列表'); - - const apiSettings = getJqyhApiSettings(); - - try { - if (apiSettings.apiMode === 'sillytavern_preset') { - const context = getContext(); - if (!context?.extensionSettings?.connectionManager?.profiles) { - throw new Error('无法获取SillyTavern配置文件列表'); - } - - const targetProfile = context.extensionSettings.connectionManager.profiles.find(p => p.id === apiSettings.tavernProfile); - if (!targetProfile) { - throw new Error(`未找到配置文件ID: ${apiSettings.tavernProfile}`); - } - - const models = []; - if (targetProfile.openai_model) { - models.push({ id: targetProfile.openai_model, name: targetProfile.openai_model }); - } - - if (models.length === 0) { - throw new Error('当前预设未配置模型'); - } - - console.log('[Amily2号-Jqyh外交部] SillyTavern预设模式获取到模型:', models); - return models; - - } else { - if (!apiSettings.apiUrl || !apiSettings.apiKey) { - throw new Error('API URL或Key未配置'); - } - - const response = await fetch('/api/backends/chat-completions/status', { - method: 'POST', - headers: { - ...getRequestHeaders(), - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - reverse_proxy: apiSettings.apiUrl, - proxy_password: apiSettings.apiKey, - chat_completion_source: 'openai' - }) - }); - - if (!response.ok) { - throw new Error(`HTTP ${response.status}: ${response.statusText}`); - } - - const rawData = await response.json(); - const models = Array.isArray(rawData) ? rawData : (rawData.data || rawData.models || []); - - if (!Array.isArray(models)) { - const errorMessage = result.error?.message || 'API未返回有效的模型列表数组'; - throw new Error(errorMessage); - } - - const formattedModels = models - .map(m => { - const modelIdRaw = m.name || m.id || m.model || m; - const modelName = String(modelIdRaw).replace(/^models\//, ''); - return { - id: modelName, - name: modelName - }; - }) - .filter(m => m.id) - .sort((a, b) => String(a.name).localeCompare(String(b.name))); - - console.log('[Amily2号-Jqyh外交部] 全兼容模式获取到模型:', formattedModels); - return formattedModels; - } - } catch (error) { - console.error('[Amily2号-Jqyh外交部] 获取模型列表失败:', error); - toastr.error(`获取模型列表失败: ${error.message}`, 'Jqyh API'); - throw error; - } -} - -export async function testJqyhApiConnection() { - console.log('[Amily2号-Jqyh外交部] 开始API连接测试'); - - const apiSettings = getJqyhApiSettings(); - - if (apiSettings.apiMode === 'sillytavern_preset') { - if (!apiSettings.tavernProfile) { - toastr.error('未配置SillyTavern预设ID', 'Jqyh API连接测试失败'); - return false; - } - } else { - if (!apiSettings.apiUrl || !apiSettings.apiKey || !apiSettings.model) { - toastr.error('API配置不完整,请检查URL、Key和模型', 'Jqyh API连接测试失败'); - return false; - } - } - - try { - toastr.info('正在发送测试消息"你好!"...', 'Jqyh API连接测试'); - - const userName = window.SillyTavern.getContext?.()?.name1 || '用户'; - const systemPrompt = `接下来用户会使用测试按钮测试api连接是否正常,请回复ta连接是正常的,称呼用户为:“${userName}大人”,并复述群号1060183271,告知ta,本扩展开发者:“诗与酒.”正静待ta的入驻。`; - - const testMessages = [ - { role: 'system', content: systemPrompt }, - { role: 'user', content: '你好!' } - ]; - - const response = await callJqyhAI(testMessages); - - if (response && response.trim()) { - console.log('[Amily2号-Jqyh外交部] 测试消息响应:', response); - const formattedResponse = response.replace(/\*\*(.*?)\*\*/g, '$1'); - toastr.success(`连接测试成功!AI回复: "${formattedResponse}"`, 'Jqyh API连接测试成功', { "escapeHtml": false }); - return true; - } else { - throw new Error('API未返回有效响应'); - } - - } catch (error) { - console.error('[Amily2号-Jqyh外交部] 连接测试失败:', error); - toastr.error(`连接测试失败: ${error.message}`, 'Jqyh API连接测试失败'); - return false; - } -} +import { extension_settings, getContext } from "/scripts/extensions.js"; +import { characters, this_chid, getRequestHeaders, saveSettingsDebounced, eventSource, event_types } from "/script.js"; +import { extensionName } from "../../utils/settings.js"; +import { amilyHelper } from '../../core/tavern-helper/main.js'; + +let ChatCompletionService = undefined; +try { + const module = await import('/scripts/custom-request.js'); + ChatCompletionService = module.ChatCompletionService; + console.log('[Amily2号-Jqyh外交部] 已成功召唤"皇家信使"(ChatCompletionService)。'); +} catch (e) { + console.warn("[Amily2号-Jqyh外交部] 未能召唤“皇家信使”,部分高级功能(如Claw代理)将受限。请考虑更新SillyTavern版本。", e); +} + +function normalizeApiResponse(responseData) { + let data = responseData; + if (typeof data === 'string') { + try { + data = JSON.parse(data); + } catch (e) { + console.error(`[${extensionName}] Jqyh API响应JSON解析失败:`, e); + return { error: { message: 'Invalid JSON response' } }; + } + } + if (data && typeof data.data === 'object' && data.data !== null && !Array.isArray(data.data)) { + if (Object.hasOwn(data.data, 'data')) { + data = data.data; + } + } + if (data && data.choices && data.choices[0]) { + return { content: data.choices[0].message?.content?.trim() }; + } + if (data && data.content) { + return { content: data.content.trim() }; + } + if (data && data.data) { + return { data: data.data }; + } + if (data && data.error) { + return { error: data.error }; + } + return data; +} + +export function getJqyhApiSettings() { + return { + apiMode: extension_settings[extensionName]?.jqyhApiMode || 'openai_test', + apiUrl: extension_settings[extensionName]?.jqyhApiUrl?.trim() || '', + apiKey: extension_settings[extensionName]?.jqyhApiKey?.trim() || '', + model: extension_settings[extensionName]?.jqyhModel || '', + maxTokens: extension_settings[extensionName]?.jqyhMaxTokens || 4000, + temperature: extension_settings[extensionName]?.jqyhTemperature || 0.7, + tavernProfile: extension_settings[extensionName]?.jqyhTavernProfile || '' + }; +} + +export async function callJqyhAI(messages, options = {}) { + if (window.AMILY2_SYSTEM_PARALYZED === true) { + console.error("[Amily2-Jqyh制裁] 系统完整性已受损,所有外交活动被无限期中止。"); + return null; + } + + const apiSettings = getJqyhApiSettings(); + + const finalOptions = { + maxTokens: apiSettings.maxTokens, + temperature: apiSettings.temperature, + model: apiSettings.model, + apiUrl: apiSettings.apiUrl, + apiKey: apiSettings.apiKey, + apiMode: apiSettings.apiMode, + tavernProfile: apiSettings.tavernProfile, + ...options + }; + + if (finalOptions.apiMode !== 'sillytavern_preset') { + if (!finalOptions.apiUrl || !finalOptions.model || !finalOptions.apiKey) { + console.warn("[Amily2-Jqyh外交部] API配置不完整,无法调用AI"); + toastr.error("API配置不完整,请检查URL、Key和模型配置。", "Jqyh-外交部"); + return null; + } + } + + console.groupCollapsed(`[Amily2号-Jqyh统一API调用] ${new Date().toLocaleTimeString()}`); + console.log("【请求参数】:", { + mode: finalOptions.apiMode, + model: finalOptions.model, + maxTokens: finalOptions.maxTokens, + temperature: finalOptions.temperature, + messagesCount: messages.length + }); + console.log("【消息内容】:", messages); + console.groupEnd(); + + try { + let responseContent; + + switch (finalOptions.apiMode) { + case 'openai_test': + responseContent = await callJqyhOpenAITest(messages, finalOptions); + break; + case 'sillytavern_preset': + responseContent = await callJqyhSillyTavernPreset(messages, finalOptions); + break; + default: + console.error(`[Amily2-Jqyh外交部] 未支持的API模式: ${finalOptions.apiMode}`); + return null; + } + + if (!responseContent) { + console.warn('[Amily2-Jqyh外交部] 未能获取AI响应内容'); + return null; + } + + console.groupCollapsed("[Amily2号-Jqyh AI回复]"); + console.log(responseContent); + console.groupEnd(); + + return responseContent; + + } catch (error) { + console.error(`[Amily2-Jqyh外交部] API调用发生错误:`, error); + + if (error.message.includes('400')) { + toastr.error(`API请求格式错误 (400): 请检查消息格式和模型配置`, "Jqyh API调用失败"); + } else if (error.message.includes('401')) { + toastr.error(`API认证失败 (401): 请检查API Key配置`, "Jqyh API调用失败"); + } else if (error.message.includes('403')) { + toastr.error(`API访问被拒绝 (403): 请检查权限设置`, "Jqyh API调用失败"); + } else if (error.message.includes('429')) { + toastr.error(`API调用频率超限 (429): 请稍后重试`, "Jqyh API调用失败"); + } else if (error.message.includes('500')) { + toastr.error(`API服务器错误 (500): 请稍后重试`, "Jqyh API调用失败"); + } else { + toastr.error(`API调用失败: ${error.message}`, "Jqyh API调用失败"); + } + + return null; + } +} + +async function callJqyhOpenAITest(messages, options) { + const isGoogleApi = options.apiUrl.includes('googleapis.com'); + + const body = { + chat_completion_source: 'openai', + messages: messages, + model: options.model, + reverse_proxy: options.apiUrl, + proxy_password: options.apiKey, + stream: false, + max_tokens: options.maxTokens || 30000, + temperature: options.temperature || 1, + top_p: options.top_p || 1, + }; + + if (!isGoogleApi) { + Object.assign(body, { + custom_prompt_post_processing: 'strict', + enable_web_search: false, + frequency_penalty: 0, + group_names: [], + include_reasoning: false, + presence_penalty: 0.12, + reasoning_effort: 'medium', + request_images: false, + }); + } + + const response = await fetch('/api/backends/chat-completions/generate', { + method: 'POST', + headers: { ...getRequestHeaders(), 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Jqyh全兼容API请求失败: ${response.status} - ${errorText}`); + } + + const responseData = await response.json(); + return responseData?.choices?.[0]?.message?.content; +} + +async function callJqyhSillyTavernPreset(messages, options) { + console.log('[Amily2号-JqyhST预设] 使用SillyTavern预设调用'); + + const context = getContext(); + if (!context) { + throw new Error('无法获取SillyTavern上下文'); + } + + const profileId = options.tavernProfile; + if (!profileId) { + throw new Error('未配置SillyTavern预设ID'); + } + + let originalProfile = ''; + let responsePromise; + + try { + originalProfile = await amilyHelper.triggerSlash('/profile'); + console.log(`[Amily2号-JqyhST预设] 当前配置文件: ${originalProfile}`); + + const targetProfile = context.extensionSettings?.connectionManager?.profiles?.find(p => p.id === profileId); + if (!targetProfile) { + throw new Error(`未找到配置文件ID: ${profileId}`); + } + + const targetProfileName = targetProfile.name; + console.log(`[Amily2号-JqyhST预设] 目标配置文件: ${targetProfileName}`); + + const currentProfile = await amilyHelper.triggerSlash('/profile'); + if (currentProfile !== targetProfileName) { + console.log(`[Amily2号-JqyhST预设] 切换配置文件: ${currentProfile} -> ${targetProfileName}`); + const escapedProfileName = targetProfileName.replace(/"/g, '\\"'); + await amilyHelper.triggerSlash(`/profile await=true "${escapedProfileName}"`); + } + + if (!context.ConnectionManagerRequestService) { + throw new Error('ConnectionManagerRequestService不可用'); + } + + console.log(`[Amily2号-JqyhST预设] 通过配置文件 ${targetProfileName} 发送请求`); + responsePromise = context.ConnectionManagerRequestService.sendRequest( + targetProfile.id, + messages, + options.maxTokens || 4000 + ); + + } finally { + try { + const currentProfileAfterCall = await amilyHelper.triggerSlash('/profile'); + if (originalProfile && originalProfile !== currentProfileAfterCall) { + console.log(`[Amily2号-JqyhST预设] 恢复原始配置文件: ${currentProfileAfterCall} -> ${originalProfile}`); + const escapedOriginalProfile = originalProfile.replace(/"/g, '\\"'); + await amilyHelper.triggerSlash(`/profile await=true "${escapedOriginalProfile}"`); + } + } catch (restoreError) { + console.error('[Amily2号-JqyhST预设] 恢复配置文件失败:', restoreError); + } + } + + const result = await responsePromise; + + if (!result) { + throw new Error('未收到API响应'); + } + + const normalizedResult = normalizeApiResponse(result); + if (normalizedResult.error) { + throw new Error(normalizedResult.error.message || 'SillyTavern预设API调用失败'); + } + + return normalizedResult.content; +} + +export async function fetchJqyhModels() { + console.log('[Amily2号-Jqyh外交部] 开始获取模型列表'); + + const apiSettings = getJqyhApiSettings(); + + try { + if (apiSettings.apiMode === 'sillytavern_preset') { + const context = getContext(); + if (!context?.extensionSettings?.connectionManager?.profiles) { + throw new Error('无法获取SillyTavern配置文件列表'); + } + + const targetProfile = context.extensionSettings.connectionManager.profiles.find(p => p.id === apiSettings.tavernProfile); + if (!targetProfile) { + throw new Error(`未找到配置文件ID: ${apiSettings.tavernProfile}`); + } + + const models = []; + if (targetProfile.openai_model) { + models.push({ id: targetProfile.openai_model, name: targetProfile.openai_model }); + } + + if (models.length === 0) { + throw new Error('当前预设未配置模型'); + } + + console.log('[Amily2号-Jqyh外交部] SillyTavern预设模式获取到模型:', models); + return models; + + } else { + if (!apiSettings.apiUrl || !apiSettings.apiKey) { + throw new Error('API URL或Key未配置'); + } + + const response = await fetch('/api/backends/chat-completions/status', { + method: 'POST', + headers: { + ...getRequestHeaders(), + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + reverse_proxy: apiSettings.apiUrl, + proxy_password: apiSettings.apiKey, + chat_completion_source: 'openai' + }) + }); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const rawData = await response.json(); + const models = Array.isArray(rawData) ? rawData : (rawData.data || rawData.models || []); + + if (!Array.isArray(models)) { + const errorMessage = result.error?.message || 'API未返回有效的模型列表数组'; + throw new Error(errorMessage); + } + + const formattedModels = models + .map(m => { + const modelIdRaw = m.name || m.id || m.model || m; + const modelName = String(modelIdRaw).replace(/^models\//, ''); + return { + id: modelName, + name: modelName + }; + }) + .filter(m => m.id) + .sort((a, b) => String(a.name).localeCompare(String(b.name))); + + console.log('[Amily2号-Jqyh外交部] 全兼容模式获取到模型:', formattedModels); + return formattedModels; + } + } catch (error) { + console.error('[Amily2号-Jqyh外交部] 获取模型列表失败:', error); + toastr.error(`获取模型列表失败: ${error.message}`, 'Jqyh API'); + throw error; + } +} + +export async function testJqyhApiConnection() { + console.log('[Amily2号-Jqyh外交部] 开始API连接测试'); + + const apiSettings = getJqyhApiSettings(); + + if (apiSettings.apiMode === 'sillytavern_preset') { + if (!apiSettings.tavernProfile) { + toastr.error('未配置SillyTavern预设ID', 'Jqyh API连接测试失败'); + return false; + } + } else { + if (!apiSettings.apiUrl || !apiSettings.apiKey || !apiSettings.model) { + toastr.error('API配置不完整,请检查URL、Key和模型', 'Jqyh API连接测试失败'); + return false; + } + } + + try { + toastr.info('正在发送测试消息"你好!"...', 'Jqyh API连接测试'); + + const userName = window.SillyTavern.getContext?.()?.name1 || '用户'; + const systemPrompt = `接下来用户会使用测试按钮测试api连接是否正常,请回复ta连接是正常的,称呼用户为:“${userName}大人”,并复述群号1060183271,告知ta,本扩展开发者:“诗与酒.”正静待ta的入驻。`; + + const testMessages = [ + { role: 'system', content: systemPrompt }, + { role: 'user', content: '你好!' } + ]; + + const response = await callJqyhAI(testMessages); + + if (response && response.trim()) { + console.log('[Amily2号-Jqyh外交部] 测试消息响应:', response); + const formattedResponse = response.replace(/\*\*(.*?)\*\*/g, '$1'); + toastr.success(`连接测试成功!AI回复: "${formattedResponse}"`, 'Jqyh API连接测试成功', { "escapeHtml": false }); + return true; + } else { + throw new Error('API未返回有效响应'); + } + + } catch (error) { + console.error('[Amily2号-Jqyh外交部] 连接测试失败:', error); + toastr.error(`连接测试失败: ${error.message}`, 'Jqyh API连接测试失败'); + return false; + } +} diff --git a/core/api/NccsApi.js b/core/api/NccsApi.js index bb05df6..fc42cab 100644 --- a/core/api/NccsApi.js +++ b/core/api/NccsApi.js @@ -1,388 +1,385 @@ -import { extension_settings, getContext } from "/scripts/extensions.js"; -import { characters, this_chid, getRequestHeaders, saveSettingsDebounced, eventSource, event_types } from "/script.js"; -import { extensionName } from "../../utils/settings.js"; - -let ChatCompletionService = undefined; -try { - const module = await import('/scripts/custom-request.js'); - ChatCompletionService = module.ChatCompletionService; - console.log('[Amily2号-Nccs外交部] 已成功召唤"皇家信使"(ChatCompletionService)。'); -} catch (e) { - console.warn("[Amily2号-Nccs外交部] 未能召唤“皇家信使”,部分高级功能(如Claw代理)将受限。请考虑更新SillyTavern版本。", e); -} - -function normalizeApiResponse(responseData) { - let data = responseData; - if (typeof data === 'string') { - try { - data = JSON.parse(data); - } catch (e) { - console.error(`[${extensionName}] Nccs API响应JSON解析失败:`, e); - return { error: { message: 'Invalid JSON response' } }; - } - } - if (data && typeof data.data === 'object' && data.data !== null && !Array.isArray(data.data)) { - if (Object.hasOwn(data.data, 'data')) { - data = data.data; - } - } - if (data && data.choices && data.choices[0]) { - return { content: data.choices[0].message?.content?.trim() }; - } - if (data && data.content) { - return { content: data.content.trim() }; - } - if (data && data.data) { - return { data: data.data }; - } - if (data && data.error) { - return { error: data.error }; - } - return data; -} - -export function getNccsApiSettings() { - return { - apiMode: extension_settings[extensionName]?.nccsApiMode || 'openai_test', - apiUrl: extension_settings[extensionName]?.nccsApiUrl?.trim() || '', - apiKey: extension_settings[extensionName]?.nccsApiKey?.trim() || '', - model: extension_settings[extensionName]?.nccsModel || '', - maxTokens: extension_settings[extensionName]?.nccsMaxTokens || 4000, - temperature: extension_settings[extensionName]?.nccsTemperature || 0.7, - tavernProfile: extension_settings[extensionName]?.nccsTavernProfile || '' - }; -} - -export async function callNccsAI(messages, options = {}) { - if (window.AMILY2_SYSTEM_PARALYZED === true) { - console.error("[Amily2-Nccs制裁] 系统完整性已受损,所有外交活动被无限期中止。"); - return null; - } - - const apiSettings = getNccsApiSettings(); - - const finalOptions = { - maxTokens: apiSettings.maxTokens, - temperature: apiSettings.temperature, - model: apiSettings.model, - apiUrl: apiSettings.apiUrl, - apiKey: apiSettings.apiKey, - apiMode: apiSettings.apiMode, - tavernProfile: apiSettings.tavernProfile, - ...options - }; - - if (finalOptions.apiMode !== 'sillytavern_preset') { - if (!finalOptions.apiUrl || !finalOptions.model || !finalOptions.apiKey) { - console.warn("[Amily2-Nccs外交部] API配置不完整,无法调用AI"); - toastr.error("API配置不完整,请检查URL、Key和模型配置。", "Nccs-外交部"); - return null; - } - } - - console.groupCollapsed(`[Amily2号-Nccs统一API调用] ${new Date().toLocaleTimeString()}`); - console.log("【请求参数】:", { - mode: finalOptions.apiMode, - model: finalOptions.model, - maxTokens: finalOptions.maxTokens, - temperature: finalOptions.temperature, - messagesCount: messages.length - }); - console.log("【消息内容】:", messages); - console.groupEnd(); - - try { - let responseContent; - - switch (finalOptions.apiMode) { - case 'openai_test': - responseContent = await callNccsOpenAITest(messages, finalOptions); - break; - case 'sillytavern_preset': - responseContent = await callNccsSillyTavernPreset(messages, finalOptions); - break; - default: - console.error(`[Amily2-Nccs外交部] 未支持的API模式: ${finalOptions.apiMode}`); - return null; - } - - if (!responseContent) { - console.warn('[Amily2-Nccs外交部] 未能获取AI响应内容'); - return null; - } - - console.groupCollapsed("[Amily2号-Nccs AI回复]"); - console.log(responseContent); - console.groupEnd(); - - return responseContent; - - } catch (error) { - console.error(`[Amily2-Nccs外交部] API调用发生错误:`, error); - - if (error.message.includes('400')) { - toastr.error(`API请求格式错误 (400): 请检查消息格式和模型配置`, "Nccs API调用失败"); - } else if (error.message.includes('401')) { - toastr.error(`API认证失败 (401): 请检查API Key配置`, "Nccs API调用失败"); - } else if (error.message.includes('403')) { - toastr.error(`API访问被拒绝 (403): 请检查权限设置`, "Nccs API调用失败"); - } else if (error.message.includes('429')) { - toastr.error(`API调用频率超限 (429): 请稍后重试`, "Nccs API调用失败"); - } else if (error.message.includes('500')) { - toastr.error(`API服务器错误 (500): 请稍后重试`, "Nccs API调用失败"); - } else { - toastr.error(`API调用失败: ${error.message}`, "Nccs API调用失败"); - } - - return null; - } -} - -async function callNccsOpenAITest(messages, options) { - const isGoogleApi = options.apiUrl.includes('googleapis.com'); - - const body = { - chat_completion_source: 'openai', - messages: messages, - model: options.model, - reverse_proxy: options.apiUrl, - proxy_password: options.apiKey, - stream: false, - max_tokens: options.maxTokens || 30000, - temperature: options.temperature || 1, - top_p: options.top_p || 1, - }; - - if (!isGoogleApi) { - Object.assign(body, { - custom_prompt_post_processing: 'strict', - enable_web_search: false, - frequency_penalty: 0, - group_names: [], - include_reasoning: false, - presence_penalty: 0.12, - reasoning_effort: 'medium', - request_images: false, - }); - } - - const response = await fetch('/api/backends/chat-completions/generate', { - method: 'POST', - headers: { ...getRequestHeaders(), 'Content-Type': 'application/json' }, - body: JSON.stringify(body) - }); - - if (!response.ok) { - const errorText = await response.text(); - throw new Error(`Nccs全兼容API请求失败: ${response.status} - ${errorText}`); - } - - const responseData = await response.json(); - return responseData?.choices?.[0]?.message?.content; -} - -async function callNccsSillyTavernPreset(messages, options) { - console.log('[Amily2号-NccsST预设] 使用SillyTavern预设调用'); - - if (!window.TavernHelper || !window.TavernHelper.triggerSlash) { - throw new Error('TavernHelper不可用,无法使用SillyTavern预设模式'); - } - - const context = getContext(); - if (!context) { - throw new Error('无法获取SillyTavern上下文'); - } - - const profileId = options.tavernProfile; - if (!profileId) { - throw new Error('未配置SillyTavern预设ID'); - } - - let originalProfile = ''; - let responsePromise; - - try { - originalProfile = await window.TavernHelper.triggerSlash('/profile'); - console.log(`[Amily2号-NccsST预设] 当前配置文件: ${originalProfile}`); - - const targetProfile = context.extensionSettings?.connectionManager?.profiles?.find(p => p.id === profileId); - if (!targetProfile) { - throw new Error(`未找到配置文件ID: ${profileId}`); - } - - const targetProfileName = targetProfile.name; - console.log(`[Amily2号-NccsST预设] 目标配置文件: ${targetProfileName}`); - - const currentProfile = await window.TavernHelper.triggerSlash('/profile'); - if (currentProfile !== targetProfileName) { - console.log(`[Amily2号-NccsST预设] 切换配置文件: ${currentProfile} -> ${targetProfileName}`); - const escapedProfileName = targetProfileName.replace(/"/g, '\\"'); - await window.TavernHelper.triggerSlash(`/profile await=true "${escapedProfileName}"`); - } - - if (!context.ConnectionManagerRequestService) { - throw new Error('ConnectionManagerRequestService不可用'); - } - - console.log(`[Amily2号-NccsST预设] 通过配置文件 ${targetProfileName} 发送请求`); - responsePromise = context.ConnectionManagerRequestService.sendRequest( - targetProfile.id, - messages, - options.maxTokens || 4000 - ); - - } finally { - try { - const currentProfileAfterCall = await window.TavernHelper.triggerSlash('/profile'); - if (originalProfile && originalProfile !== currentProfileAfterCall) { - console.log(`[Amily2号-NccsST预设] 恢复原始配置文件: ${currentProfileAfterCall} -> ${originalProfile}`); - const escapedOriginalProfile = originalProfile.replace(/"/g, '\\"'); - await window.TavernHelper.triggerSlash(`/profile await=true "${escapedOriginalProfile}"`); - } - } catch (restoreError) { - console.error('[Amily2号-NccsST预设] 恢复配置文件失败:', restoreError); - } - } - - const result = await responsePromise; - - if (!result) { - throw new Error('未收到API响应'); - } - - const normalizedResult = normalizeApiResponse(result); - if (normalizedResult.error) { - throw new Error(normalizedResult.error.message || 'SillyTavern预设API调用失败'); - } - - return normalizedResult.content; -} - -export async function fetchNccsModels() { - console.log('[Amily2号-Nccs外交部] 开始获取模型列表'); - - const apiSettings = getNccsApiSettings(); - - try { - if (apiSettings.apiMode === 'sillytavern_preset') { - // SillyTavern预设模式:获取当前预设的模型 - const context = getContext(); - if (!context?.extensionSettings?.connectionManager?.profiles) { - throw new Error('无法获取SillyTavern配置文件列表'); - } - - const targetProfile = context.extensionSettings.connectionManager.profiles.find(p => p.id === apiSettings.tavernProfile); - if (!targetProfile) { - throw new Error(`未找到配置文件ID: ${apiSettings.tavernProfile}`); - } - - const models = []; - if (targetProfile.openai_model) { - models.push({ id: targetProfile.openai_model, name: targetProfile.openai_model }); - } - - if (models.length === 0) { - throw new Error('当前预设未配置模型'); - } - - console.log('[Amily2号-Nccs外交部] SillyTavern预设模式获取到模型:', models); - return models; - - } else { - if (!apiSettings.apiUrl || !apiSettings.apiKey) { - throw new Error('API URL或Key未配置'); - } - - const response = await fetch('/api/backends/chat-completions/status', { - method: 'POST', - headers: { - ...getRequestHeaders(), - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - reverse_proxy: apiSettings.apiUrl, - proxy_password: apiSettings.apiKey, - chat_completion_source: 'openai' - }) - }); - - if (!response.ok) { - throw new Error(`HTTP ${response.status}: ${response.statusText}`); - } - - const rawData = await response.json(); - const models = Array.isArray(rawData) ? rawData : (rawData.data || rawData.models || []); - - if (!Array.isArray(models)) { - const errorMessage = rawData.error?.message || 'API未返回有效的模型列表数组'; - throw new Error(errorMessage); - } - - const formattedModels = models - .map(m => { - // 从name字段中提取模型名称,去掉"models/"前缀 - const modelIdRaw = m.name || m.id || m.model || m; - const modelName = String(modelIdRaw).replace(/^models\//, ''); - return { - id: modelName, - name: modelName - }; - }) - .filter(m => m.id) - .sort((a, b) => String(a.name).localeCompare(String(b.name))); - - console.log('[Amily2号-Nccs外交部] 全兼容模式获取到模型:', formattedModels); - return formattedModels; - } - } catch (error) { - console.error('[Amily2号-Nccs外交部] 获取模型列表失败:', error); - toastr.error(`获取模型列表失败: ${error.message}`, 'Nccs API'); - throw error; - } -} - -export async function testNccsApiConnection() { - console.log('[Amily2号-Nccs外交部] 开始API连接测试'); - - const apiSettings = getNccsApiSettings(); - - if (apiSettings.apiMode === 'sillytavern_preset') { - if (!apiSettings.tavernProfile) { - toastr.error('未配置SillyTavern预设ID', 'Nccs API连接测试失败'); - return false; - } - } else { - if (!apiSettings.apiUrl || !apiSettings.apiKey || !apiSettings.model) { - toastr.error('API配置不完整,请检查URL、Key和模型', 'Nccs API连接测试失败'); - return false; - } - } - - try { - toastr.info('正在发送测试消息"你好!"...', 'Nccs API连接测试'); - - const userName = window.SillyTavern.getContext?.()?.name1 || '用户'; - const systemPrompt = `接下来用户会使用测试按钮测试api连接是否正常,请回复ta连接是正常的,称呼用户为:“${userName}大人”,并复述群号1060183271,告知ta,本扩展开发者:“诗与酒.”正静待ta的入驻。`; - - const testMessages = [ - { role: 'system', content: systemPrompt }, - { role: 'user', content: '你好!' } - ]; - - const response = await callNccsAI(testMessages); - - if (response && response.trim()) { - console.log('[Amily2号-Nccs外交部] 测试消息响应:', response); - const formattedResponse = response.replace(/\*\*(.*?)\*\*/g, '$1'); - toastr.success(`连接测试成功!AI回复: "${formattedResponse}"`, 'Nccs API连接测试成功', { "escapeHtml": false }); - return true; - } else { - throw new Error('API未返回有效响应'); - } - - } catch (error) { - console.error('[Amily2号-Nccs外交部] 连接测试失败:', error); - toastr.error(`连接测试失败: ${error.message}`, 'Nccs API连接测试失败'); - return false; - } -} +import { extension_settings, getContext } from "/scripts/extensions.js"; +import { characters, this_chid, getRequestHeaders, saveSettingsDebounced, eventSource, event_types } from "/script.js"; +import { extensionName } from "../../utils/settings.js"; +import { amilyHelper } from '../../core/tavern-helper/main.js'; + +let ChatCompletionService = undefined; +try { + const module = await import('/scripts/custom-request.js'); + ChatCompletionService = module.ChatCompletionService; + console.log('[Amily2号-Nccs外交部] 已成功召唤"皇家信使"(ChatCompletionService)。'); +} catch (e) { + console.warn("[Amily2号-Nccs外交部] 未能召唤“皇家信使”,部分高级功能(如Claw代理)将受限。请考虑更新SillyTavern版本。", e); +} + +function normalizeApiResponse(responseData) { + let data = responseData; + if (typeof data === 'string') { + try { + data = JSON.parse(data); + } catch (e) { + console.error(`[${extensionName}] Nccs API响应JSON解析失败:`, e); + return { error: { message: 'Invalid JSON response' } }; + } + } + if (data && typeof data.data === 'object' && data.data !== null && !Array.isArray(data.data)) { + if (Object.hasOwn(data.data, 'data')) { + data = data.data; + } + } + if (data && data.choices && data.choices[0]) { + return { content: data.choices[0].message?.content?.trim() }; + } + if (data && data.content) { + return { content: data.content.trim() }; + } + if (data && data.data) { + return { data: data.data }; + } + if (data && data.error) { + return { error: data.error }; + } + return data; +} + +export function getNccsApiSettings() { + return { + apiMode: extension_settings[extensionName]?.nccsApiMode || 'openai_test', + apiUrl: extension_settings[extensionName]?.nccsApiUrl?.trim() || '', + apiKey: extension_settings[extensionName]?.nccsApiKey?.trim() || '', + model: extension_settings[extensionName]?.nccsModel || '', + maxTokens: extension_settings[extensionName]?.nccsMaxTokens || 4000, + temperature: extension_settings[extensionName]?.nccsTemperature || 0.7, + tavernProfile: extension_settings[extensionName]?.nccsTavernProfile || '' + }; +} + +export async function callNccsAI(messages, options = {}) { + if (window.AMILY2_SYSTEM_PARALYZED === true) { + console.error("[Amily2-Nccs制裁] 系统完整性已受损,所有外交活动被无限期中止。"); + return null; + } + + const apiSettings = getNccsApiSettings(); + + const finalOptions = { + maxTokens: apiSettings.maxTokens, + temperature: apiSettings.temperature, + model: apiSettings.model, + apiUrl: apiSettings.apiUrl, + apiKey: apiSettings.apiKey, + apiMode: apiSettings.apiMode, + tavernProfile: apiSettings.tavernProfile, + ...options + }; + + if (finalOptions.apiMode !== 'sillytavern_preset') { + if (!finalOptions.apiUrl || !finalOptions.model || !finalOptions.apiKey) { + console.warn("[Amily2-Nccs外交部] API配置不完整,无法调用AI"); + toastr.error("API配置不完整,请检查URL、Key和模型配置。", "Nccs-外交部"); + return null; + } + } + + console.groupCollapsed(`[Amily2号-Nccs统一API调用] ${new Date().toLocaleTimeString()}`); + console.log("【请求参数】:", { + mode: finalOptions.apiMode, + model: finalOptions.model, + maxTokens: finalOptions.maxTokens, + temperature: finalOptions.temperature, + messagesCount: messages.length + }); + console.log("【消息内容】:", messages); + console.groupEnd(); + + try { + let responseContent; + + switch (finalOptions.apiMode) { + case 'openai_test': + responseContent = await callNccsOpenAITest(messages, finalOptions); + break; + case 'sillytavern_preset': + responseContent = await callNccsSillyTavernPreset(messages, finalOptions); + break; + default: + console.error(`[Amily2-Nccs外交部] 未支持的API模式: ${finalOptions.apiMode}`); + return null; + } + + if (!responseContent) { + console.warn('[Amily2-Nccs外交部] 未能获取AI响应内容'); + return null; + } + + console.groupCollapsed("[Amily2号-Nccs AI回复]"); + console.log(responseContent); + console.groupEnd(); + + return responseContent; + + } catch (error) { + console.error(`[Amily2-Nccs外交部] API调用发生错误:`, error); + + if (error.message.includes('400')) { + toastr.error(`API请求格式错误 (400): 请检查消息格式和模型配置`, "Nccs API调用失败"); + } else if (error.message.includes('401')) { + toastr.error(`API认证失败 (401): 请检查API Key配置`, "Nccs API调用失败"); + } else if (error.message.includes('403')) { + toastr.error(`API访问被拒绝 (403): 请检查权限设置`, "Nccs API调用失败"); + } else if (error.message.includes('429')) { + toastr.error(`API调用频率超限 (429): 请稍后重试`, "Nccs API调用失败"); + } else if (error.message.includes('500')) { + toastr.error(`API服务器错误 (500): 请稍后重试`, "Nccs API调用失败"); + } else { + toastr.error(`API调用失败: ${error.message}`, "Nccs API调用失败"); + } + + return null; + } +} + +async function callNccsOpenAITest(messages, options) { + const isGoogleApi = options.apiUrl.includes('googleapis.com'); + + const body = { + chat_completion_source: 'openai', + messages: messages, + model: options.model, + reverse_proxy: options.apiUrl, + proxy_password: options.apiKey, + stream: false, + max_tokens: options.maxTokens || 30000, + temperature: options.temperature || 1, + top_p: options.top_p || 1, + }; + + if (!isGoogleApi) { + Object.assign(body, { + custom_prompt_post_processing: 'strict', + enable_web_search: false, + frequency_penalty: 0, + group_names: [], + include_reasoning: false, + presence_penalty: 0.12, + reasoning_effort: 'medium', + request_images: false, + }); + } + + const response = await fetch('/api/backends/chat-completions/generate', { + method: 'POST', + headers: { ...getRequestHeaders(), 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Nccs全兼容API请求失败: ${response.status} - ${errorText}`); + } + + const responseData = await response.json(); + return responseData?.choices?.[0]?.message?.content; +} + +async function callNccsSillyTavernPreset(messages, options) { + console.log('[Amily2号-NccsST预设] 使用SillyTavern预设调用'); + + const context = getContext(); + if (!context) { + throw new Error('无法获取SillyTavern上下文'); + } + + const profileId = options.tavernProfile; + if (!profileId) { + throw new Error('未配置SillyTavern预设ID'); + } + + let originalProfile = ''; + let responsePromise; + + try { + originalProfile = await amilyHelper.triggerSlash('/profile'); + console.log(`[Amily2号-NccsST预设] 当前配置文件: ${originalProfile}`); + + const targetProfile = context.extensionSettings?.connectionManager?.profiles?.find(p => p.id === profileId); + if (!targetProfile) { + throw new Error(`未找到配置文件ID: ${profileId}`); + } + + const targetProfileName = targetProfile.name; + console.log(`[Amily2号-NccsST预设] 目标配置文件: ${targetProfileName}`); + + const currentProfile = await amilyHelper.triggerSlash('/profile'); + if (currentProfile !== targetProfileName) { + console.log(`[Amily2号-NccsST预设] 切换配置文件: ${currentProfile} -> ${targetProfileName}`); + const escapedProfileName = targetProfileName.replace(/"/g, '\\"'); + await amilyHelper.triggerSlash(`/profile await=true "${escapedProfileName}"`); + } + + if (!context.ConnectionManagerRequestService) { + throw new Error('ConnectionManagerRequestService不可用'); + } + + console.log(`[Amily2号-NccsST预设] 通过配置文件 ${targetProfileName} 发送请求`); + responsePromise = context.ConnectionManagerRequestService.sendRequest( + targetProfile.id, + messages, + options.maxTokens || 4000 + ); + + } finally { + try { + const currentProfileAfterCall = await amilyHelper.triggerSlash('/profile'); + if (originalProfile && originalProfile !== currentProfileAfterCall) { + console.log(`[Amily2号-NccsST预设] 恢复原始配置文件: ${currentProfileAfterCall} -> ${originalProfile}`); + const escapedOriginalProfile = originalProfile.replace(/"/g, '\\"'); + await amilyHelper.triggerSlash(`/profile await=true "${escapedOriginalProfile}"`); + } + } catch (restoreError) { + console.error('[Amily2号-NccsST预设] 恢复配置文件失败:', restoreError); + } + } + + const result = await responsePromise; + + if (!result) { + throw new Error('未收到API响应'); + } + + const normalizedResult = normalizeApiResponse(result); + if (normalizedResult.error) { + throw new Error(normalizedResult.error.message || 'SillyTavern预设API调用失败'); + } + + return normalizedResult.content; +} + +export async function fetchNccsModels() { + console.log('[Amily2号-Nccs外交部] 开始获取模型列表'); + + const apiSettings = getNccsApiSettings(); + + try { + if (apiSettings.apiMode === 'sillytavern_preset') { + // SillyTavern预设模式:获取当前预设的模型 + const context = getContext(); + if (!context?.extensionSettings?.connectionManager?.profiles) { + throw new Error('无法获取SillyTavern配置文件列表'); + } + + const targetProfile = context.extensionSettings.connectionManager.profiles.find(p => p.id === apiSettings.tavernProfile); + if (!targetProfile) { + throw new Error(`未找到配置文件ID: ${apiSettings.tavernProfile}`); + } + + const models = []; + if (targetProfile.openai_model) { + models.push({ id: targetProfile.openai_model, name: targetProfile.openai_model }); + } + + if (models.length === 0) { + throw new Error('当前预设未配置模型'); + } + + console.log('[Amily2号-Nccs外交部] SillyTavern预设模式获取到模型:', models); + return models; + + } else { + if (!apiSettings.apiUrl || !apiSettings.apiKey) { + throw new Error('API URL或Key未配置'); + } + + const response = await fetch('/api/backends/chat-completions/status', { + method: 'POST', + headers: { + ...getRequestHeaders(), + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + reverse_proxy: apiSettings.apiUrl, + proxy_password: apiSettings.apiKey, + chat_completion_source: 'openai' + }) + }); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const rawData = await response.json(); + const models = Array.isArray(rawData) ? rawData : (rawData.data || rawData.models || []); + + if (!Array.isArray(models)) { + const errorMessage = rawData.error?.message || 'API未返回有效的模型列表数组'; + throw new Error(errorMessage); + } + + const formattedModels = models + .map(m => { + // 从name字段中提取模型名称,去掉"models/"前缀 + const modelIdRaw = m.name || m.id || m.model || m; + const modelName = String(modelIdRaw).replace(/^models\//, ''); + return { + id: modelName, + name: modelName + }; + }) + .filter(m => m.id) + .sort((a, b) => String(a.name).localeCompare(String(b.name))); + + console.log('[Amily2号-Nccs外交部] 全兼容模式获取到模型:', formattedModels); + return formattedModels; + } + } catch (error) { + console.error('[Amily2号-Nccs外交部] 获取模型列表失败:', error); + toastr.error(`获取模型列表失败: ${error.message}`, 'Nccs API'); + throw error; + } +} + +export async function testNccsApiConnection() { + console.log('[Amily2号-Nccs外交部] 开始API连接测试'); + + const apiSettings = getNccsApiSettings(); + + if (apiSettings.apiMode === 'sillytavern_preset') { + if (!apiSettings.tavernProfile) { + toastr.error('未配置SillyTavern预设ID', 'Nccs API连接测试失败'); + return false; + } + } else { + if (!apiSettings.apiUrl || !apiSettings.apiKey || !apiSettings.model) { + toastr.error('API配置不完整,请检查URL、Key和模型', 'Nccs API连接测试失败'); + return false; + } + } + + try { + toastr.info('正在发送测试消息"你好!"...', 'Nccs API连接测试'); + + const userName = window.SillyTavern.getContext?.()?.name1 || '用户'; + const systemPrompt = `接下来用户会使用测试按钮测试api连接是否正常,请回复ta连接是正常的,称呼用户为:“${userName}大人”,并复述群号1060183271,告知ta,本扩展开发者:“诗与酒.”正静待ta的入驻。`; + + const testMessages = [ + { role: 'system', content: systemPrompt }, + { role: 'user', content: '你好!' } + ]; + + const response = await callNccsAI(testMessages); + + if (response && response.trim()) { + console.log('[Amily2号-Nccs外交部] 测试消息响应:', response); + const formattedResponse = response.replace(/\*\*(.*?)\*\*/g, '$1'); + toastr.success(`连接测试成功!AI回复: "${formattedResponse}"`, 'Nccs API连接测试成功', { "escapeHtml": false }); + return true; + } else { + throw new Error('API未返回有效响应'); + } + + } catch (error) { + console.error('[Amily2号-Nccs外交部] 连接测试失败:', error); + toastr.error(`连接测试失败: ${error.message}`, 'Nccs API连接测试失败'); + return false; + } +} diff --git a/core/api/Ngms_api.js b/core/api/Ngms_api.js index 66b103c..bd97c58 100644 --- a/core/api/Ngms_api.js +++ b/core/api/Ngms_api.js @@ -1,388 +1,385 @@ -import { extension_settings, getContext } from "/scripts/extensions.js"; -import { characters, this_chid, getRequestHeaders, saveSettingsDebounced, eventSource, event_types } from "/script.js"; -import { extensionName } from "../../utils/settings.js"; - -let ChatCompletionService = undefined; -try { - const module = await import('/scripts/custom-request.js'); - ChatCompletionService = module.ChatCompletionService; - console.log('[Amily2号-Ngms外交部] 已成功召唤"皇家信使"(ChatCompletionService)。'); -} catch (e) { - console.warn("[Amily2号-Ngms外交部] 未能召唤“皇家信使”,部分高级功能(如Claw代理)将受限。请考虑更新SillyTavern版本。", e); -} - -function normalizeApiResponse(responseData) { - let data = responseData; - if (typeof data === 'string') { - try { - data = JSON.parse(data); - } catch (e) { - console.error(`[${extensionName}] Ngms API响应JSON解析失败:`, e); - return { error: { message: 'Invalid JSON response' } }; - } - } - if (data && typeof data.data === 'object' && data.data !== null && !Array.isArray(data.data)) { - if (Object.hasOwn(data.data, 'data')) { - data = data.data; - } - } - if (data && data.choices && data.choices[0]) { - return { content: data.choices[0].message?.content?.trim() }; - } - if (data && data.content) { - return { content: data.content.trim() }; - } - if (data && data.data) { - return { data: data.data }; - } - if (data && data.error) { - return { error: data.error }; - } - return data; -} - -export function getNgmsApiSettings() { - return { - apiMode: extension_settings[extensionName]?.ngmsApiMode || 'openai_test', - apiUrl: extension_settings[extensionName]?.ngmsApiUrl?.trim() || '', - apiKey: extension_settings[extensionName]?.ngmsApiKey?.trim() || '', - model: extension_settings[extensionName]?.ngmsModel || '', - maxTokens: extension_settings[extensionName]?.ngmsMaxTokens || 4000, - temperature: extension_settings[extensionName]?.ngmsTemperature || 0.7, - tavernProfile: extension_settings[extensionName]?.ngmsTavernProfile || '' - }; -} - -export async function callNgmsAI(messages, options = {}) { - if (window.AMILY2_SYSTEM_PARALYZED === true) { - console.error("[Amily2-Ngms制裁] 系统完整性已受损,所有外交活动被无限期中止。"); - return null; - } - - const apiSettings = getNgmsApiSettings(); - - const finalOptions = { - maxTokens: apiSettings.maxTokens, - temperature: apiSettings.temperature, - model: apiSettings.model, - apiUrl: apiSettings.apiUrl, - apiKey: apiSettings.apiKey, - apiMode: apiSettings.apiMode, - tavernProfile: apiSettings.tavernProfile, - ...options - }; - - if (finalOptions.apiMode !== 'sillytavern_preset') { - if (!finalOptions.apiUrl || !finalOptions.model || !finalOptions.apiKey) { - console.warn("[Amily2-Ngms外交部] API配置不完整,无法调用AI"); - toastr.error("API配置不完整,请检查URL、Key和模型配置。", "Ngms-外交部"); - return null; - } - } - - console.groupCollapsed(`[Amily2号-Ngms统一API调用] ${new Date().toLocaleTimeString()}`); - console.log("【请求参数】:", { - mode: finalOptions.apiMode, - model: finalOptions.model, - maxTokens: finalOptions.maxTokens, - temperature: finalOptions.temperature, - messagesCount: messages.length - }); - console.log("【消息内容】:", messages); - console.groupEnd(); - - try { - let responseContent; - - switch (finalOptions.apiMode) { - case 'openai_test': - responseContent = await callNgmsOpenAITest(messages, finalOptions); - break; - case 'sillytavern_preset': - responseContent = await callNgmsSillyTavernPreset(messages, finalOptions); - break; - default: - console.error(`[Amily2-Ngms外交部] 未支持的API模式: ${finalOptions.apiMode}`); - return null; - } - - if (!responseContent) { - console.warn('[Amily2-Ngms外交部] 未能获取AI响应内容'); - return null; - } - - console.groupCollapsed("[Amily2号-Ngms AI回复]"); - console.log(responseContent); - console.groupEnd(); - - return responseContent; - - } catch (error) { - console.error(`[Amily2-Ngms外交部] API调用发生错误:`, error); - - if (error.message.includes('400')) { - toastr.error(`API请求格式错误 (400): 请检查消息格式和模型配置`, "Ngms API调用失败"); - } else if (error.message.includes('401')) { - toastr.error(`API认证失败 (401): 请检查API Key配置`, "Ngms API调用失败"); - } else if (error.message.includes('403')) { - toastr.error(`API访问被拒绝 (403): 请检查权限设置`, "Ngms API调用失败"); - } else if (error.message.includes('429')) { - toastr.error(`API调用频率超限 (429): 请稍后重试`, "Ngms API调用失败"); - } else if (error.message.includes('500')) { - toastr.error(`API服务器错误 (500): 请稍后重试`, "Ngms API调用失败"); - } else { - toastr.error(`API调用失败: ${error.message}`, "Ngms API调用失败"); - } - - return null; - } -} - -async function callNgmsOpenAITest(messages, options) { - const isGoogleApi = options.apiUrl.includes('googleapis.com'); - - const body = { - chat_completion_source: 'openai', - messages: messages, - model: options.model, - reverse_proxy: options.apiUrl, - proxy_password: options.apiKey, - stream: false, - max_tokens: options.maxTokens || 30000, - temperature: options.temperature || 1, - top_p: options.top_p || 1, - }; - - if (!isGoogleApi) { - Object.assign(body, { - custom_prompt_post_processing: 'strict', - enable_web_search: false, - frequency_penalty: 0, - group_names: [], - include_reasoning: false, - presence_penalty: 0.12, - reasoning_effort: 'medium', - request_images: false, - }); - } - - const response = await fetch('/api/backends/chat-completions/generate', { - method: 'POST', - headers: { ...getRequestHeaders(), 'Content-Type': 'application/json' }, - body: JSON.stringify(body) - }); - - if (!response.ok) { - const errorText = await response.text(); - throw new Error(`Ngms全兼容API请求失败: ${response.status} - ${errorText}`); - } - - const responseData = await response.json(); - return responseData?.choices?.[0]?.message?.content; -} - -async function callNgmsSillyTavernPreset(messages, options) { - console.log('[Amily2号-NgmsST预设] 使用SillyTavern预设调用'); - - if (!window.TavernHelper || !window.TavernHelper.triggerSlash) { - throw new Error('TavernHelper不可用,无法使用SillyTavern预设模式'); - } - - const context = getContext(); - if (!context) { - throw new Error('无法获取SillyTavern上下文'); - } - - const profileId = options.tavernProfile; - if (!profileId) { - throw new Error('未配置SillyTavern预设ID'); - } - - let originalProfile = ''; - let responsePromise; - - try { - originalProfile = await window.TavernHelper.triggerSlash('/profile'); - console.log(`[Amily2号-NgmsST预设] 当前配置文件: ${originalProfile}`); - - const targetProfile = context.extensionSettings?.connectionManager?.profiles?.find(p => p.id === profileId); - if (!targetProfile) { - throw new Error(`未找到配置文件ID: ${profileId}`); - } - - const targetProfileName = targetProfile.name; - console.log(`[Amily2号-NgmsST预设] 目标配置文件: ${targetProfileName}`); - - const currentProfile = await window.TavernHelper.triggerSlash('/profile'); - if (currentProfile !== targetProfileName) { - console.log(`[Amily2号-NgmsST预设] 切换配置文件: ${currentProfile} -> ${targetProfileName}`); - const escapedProfileName = targetProfileName.replace(/"/g, '\\"'); - await window.TavernHelper.triggerSlash(`/profile await=true "${escapedProfileName}"`); - } - - if (!context.ConnectionManagerRequestService) { - throw new Error('ConnectionManagerRequestService不可用'); - } - - console.log(`[Amily2号-NgmsST预设] 通过配置文件 ${targetProfileName} 发送请求`); - responsePromise = context.ConnectionManagerRequestService.sendRequest( - targetProfile.id, - messages, - options.maxTokens || 4000 - ); - - } finally { - try { - const currentProfileAfterCall = await window.TavernHelper.triggerSlash('/profile'); - if (originalProfile && originalProfile !== currentProfileAfterCall) { - console.log(`[Amily2号-NgmsST预设] 恢复原始配置文件: ${currentProfileAfterCall} -> ${originalProfile}`); - const escapedOriginalProfile = originalProfile.replace(/"/g, '\\"'); - await window.TavernHelper.triggerSlash(`/profile await=true "${escapedOriginalProfile}"`); - } - } catch (restoreError) { - console.error('[Amily2号-NgmsST预设] 恢复配置文件失败:', restoreError); - } - } - - const result = await responsePromise; - - if (!result) { - throw new Error('未收到API响应'); - } - - const normalizedResult = normalizeApiResponse(result); - if (normalizedResult.error) { - throw new Error(normalizedResult.error.message || 'SillyTavern预设API调用失败'); - } - - return normalizedResult.content; -} - -export async function fetchNgmsModels() { - console.log('[Amily2号-Ngms外交部] 开始获取模型列表'); - - const apiSettings = getNgmsApiSettings(); - - try { - if (apiSettings.apiMode === 'sillytavern_preset') { - // SillyTavern预设模式:获取当前预设的模型 - const context = getContext(); - if (!context?.extensionSettings?.connectionManager?.profiles) { - throw new Error('无法获取SillyTavern配置文件列表'); - } - - const targetProfile = context.extensionSettings.connectionManager.profiles.find(p => p.id === apiSettings.tavernProfile); - if (!targetProfile) { - throw new Error(`未找到配置文件ID: ${apiSettings.tavernProfile}`); - } - - const models = []; - if (targetProfile.openai_model) { - models.push({ id: targetProfile.openai_model, name: targetProfile.openai_model }); - } - - if (models.length === 0) { - throw new Error('当前预设未配置模型'); - } - - console.log('[Amily2号-Ngms外交部] SillyTavern预设模式获取到模型:', models); - return models; - - } else { - if (!apiSettings.apiUrl || !apiSettings.apiKey) { - throw new Error('API URL或Key未配置'); - } - - const response = await fetch('/api/backends/chat-completions/status', { - method: 'POST', - headers: { - ...getRequestHeaders(), - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - reverse_proxy: apiSettings.apiUrl, - proxy_password: apiSettings.apiKey, - chat_completion_source: 'openai' - }) - }); - - if (!response.ok) { - throw new Error(`HTTP ${response.status}: ${response.statusText}`); - } - - const rawData = await response.json(); - const models = Array.isArray(rawData) ? rawData : (rawData.data || rawData.models || []); - - if (!Array.isArray(models)) { - const errorMessage = result.error?.message || 'API未返回有效的模型列表数组'; - throw new Error(errorMessage); - } - - const formattedModels = models - .map(m => { - // 从name字段中提取模型名称,去掉"models/"前缀 - const modelIdRaw = m.name || m.id || m.model || m; - const modelName = String(modelIdRaw).replace(/^models\//, ''); - return { - id: modelName, - name: modelName - }; - }) - .filter(m => m.id) - .sort((a, b) => String(a.name).localeCompare(String(b.name))); - - console.log('[Amily2号-Ngms外交部] 全兼容模式获取到模型:', formattedModels); - return formattedModels; - } - } catch (error) { - console.error('[Amily2号-Ngms外交部] 获取模型列表失败:', error); - toastr.error(`获取模型列表失败: ${error.message}`, 'Ngms API'); - throw error; - } -} - -export async function testNgmsApiConnection() { - console.log('[Amily2号-Ngms外交部] 开始API连接测试'); - - const apiSettings = getNgmsApiSettings(); - - if (apiSettings.apiMode === 'sillytavern_preset') { - if (!apiSettings.tavernProfile) { - toastr.error('未配置SillyTavern预设ID', 'Ngms API连接测试失败'); - return false; - } - } else { - if (!apiSettings.apiUrl || !apiSettings.apiKey || !apiSettings.model) { - toastr.error('API配置不完整,请检查URL、Key和模型', 'Ngms API连接测试失败'); - return false; - } - } - - try { - toastr.info('正在发送测试消息"你好!"...', 'Ngms API连接测试'); - - const userName = window.SillyTavern.getContext?.()?.name1 || '用户'; - const systemPrompt = `接下来用户会使用测试按钮测试api连接是否正常,请回复ta连接是正常的,称呼用户为:“${userName}大人”,并复述群号1060183271,告知ta,本扩展开发者:“诗与酒.”正静待ta的入驻。`; - - const testMessages = [ - { role: 'system', content: systemPrompt }, - { role: 'user', content: '你好!' } - ]; - - const response = await callNgmsAI(testMessages); - - if (response && response.trim()) { - console.log('[Amily2号-Ngms外交部] 测试消息响应:', response); - const formattedResponse = response.replace(/\*\*(.*?)\*\*/g, '$1'); - toastr.success(`连接测试成功!AI回复: "${formattedResponse}"`, 'Ngms API连接测试成功', { "escapeHtml": false }); - return true; - } else { - throw new Error('API未返回有效响应'); - } - - } catch (error) { - console.error('[Amily2号-Ngms外交部] 连接测试失败:', error); - toastr.error(`连接测试失败: ${error.message}`, 'Ngms API连接测试失败'); - return false; - } -} +import { extension_settings, getContext } from "/scripts/extensions.js"; +import { characters, this_chid, getRequestHeaders, saveSettingsDebounced, eventSource, event_types } from "/script.js"; +import { extensionName } from "../../utils/settings.js"; +import { amilyHelper } from '../../core/tavern-helper/main.js'; + +let ChatCompletionService = undefined; +try { + const module = await import('/scripts/custom-request.js'); + ChatCompletionService = module.ChatCompletionService; + console.log('[Amily2号-Ngms外交部] 已成功召唤"皇家信使"(ChatCompletionService)。'); +} catch (e) { + console.warn("[Amily2号-Ngms外交部] 未能召唤“皇家信使”,部分高级功能(如Claw代理)将受限。请考虑更新SillyTavern版本。", e); +} + +function normalizeApiResponse(responseData) { + let data = responseData; + if (typeof data === 'string') { + try { + data = JSON.parse(data); + } catch (e) { + console.error(`[${extensionName}] Ngms API响应JSON解析失败:`, e); + return { error: { message: 'Invalid JSON response' } }; + } + } + if (data && typeof data.data === 'object' && data.data !== null && !Array.isArray(data.data)) { + if (Object.hasOwn(data.data, 'data')) { + data = data.data; + } + } + if (data && data.choices && data.choices[0]) { + return { content: data.choices[0].message?.content?.trim() }; + } + if (data && data.content) { + return { content: data.content.trim() }; + } + if (data && data.data) { + return { data: data.data }; + } + if (data && data.error) { + return { error: data.error }; + } + return data; +} + +export function getNgmsApiSettings() { + return { + apiMode: extension_settings[extensionName]?.ngmsApiMode || 'openai_test', + apiUrl: extension_settings[extensionName]?.ngmsApiUrl?.trim() || '', + apiKey: extension_settings[extensionName]?.ngmsApiKey?.trim() || '', + model: extension_settings[extensionName]?.ngmsModel || '', + maxTokens: extension_settings[extensionName]?.ngmsMaxTokens || 4000, + temperature: extension_settings[extensionName]?.ngmsTemperature || 0.7, + tavernProfile: extension_settings[extensionName]?.ngmsTavernProfile || '' + }; +} + +export async function callNgmsAI(messages, options = {}) { + if (window.AMILY2_SYSTEM_PARALYZED === true) { + console.error("[Amily2-Ngms制裁] 系统完整性已受损,所有外交活动被无限期中止。"); + return null; + } + + const apiSettings = getNgmsApiSettings(); + + const finalOptions = { + maxTokens: apiSettings.maxTokens, + temperature: apiSettings.temperature, + model: apiSettings.model, + apiUrl: apiSettings.apiUrl, + apiKey: apiSettings.apiKey, + apiMode: apiSettings.apiMode, + tavernProfile: apiSettings.tavernProfile, + ...options + }; + + if (finalOptions.apiMode !== 'sillytavern_preset') { + if (!finalOptions.apiUrl || !finalOptions.model || !finalOptions.apiKey) { + console.warn("[Amily2-Ngms外交部] API配置不完整,无法调用AI"); + toastr.error("API配置不完整,请检查URL、Key和模型配置。", "Ngms-外交部"); + return null; + } + } + + console.groupCollapsed(`[Amily2号-Ngms统一API调用] ${new Date().toLocaleTimeString()}`); + console.log("【请求参数】:", { + mode: finalOptions.apiMode, + model: finalOptions.model, + maxTokens: finalOptions.maxTokens, + temperature: finalOptions.temperature, + messagesCount: messages.length + }); + console.log("【消息内容】:", messages); + console.groupEnd(); + + try { + let responseContent; + + switch (finalOptions.apiMode) { + case 'openai_test': + responseContent = await callNgmsOpenAITest(messages, finalOptions); + break; + case 'sillytavern_preset': + responseContent = await callNgmsSillyTavernPreset(messages, finalOptions); + break; + default: + console.error(`[Amily2-Ngms外交部] 未支持的API模式: ${finalOptions.apiMode}`); + return null; + } + + if (!responseContent) { + console.warn('[Amily2-Ngms外交部] 未能获取AI响应内容'); + return null; + } + + console.groupCollapsed("[Amily2号-Ngms AI回复]"); + console.log(responseContent); + console.groupEnd(); + + return responseContent; + + } catch (error) { + console.error(`[Amily2-Ngms外交部] API调用发生错误:`, error); + + if (error.message.includes('400')) { + toastr.error(`API请求格式错误 (400): 请检查消息格式和模型配置`, "Ngms API调用失败"); + } else if (error.message.includes('401')) { + toastr.error(`API认证失败 (401): 请检查API Key配置`, "Ngms API调用失败"); + } else if (error.message.includes('403')) { + toastr.error(`API访问被拒绝 (403): 请检查权限设置`, "Ngms API调用失败"); + } else if (error.message.includes('429')) { + toastr.error(`API调用频率超限 (429): 请稍后重试`, "Ngms API调用失败"); + } else if (error.message.includes('500')) { + toastr.error(`API服务器错误 (500): 请稍后重试`, "Ngms API调用失败"); + } else { + toastr.error(`API调用失败: ${error.message}`, "Ngms API调用失败"); + } + + return null; + } +} + +async function callNgmsOpenAITest(messages, options) { + const isGoogleApi = options.apiUrl.includes('googleapis.com'); + + const body = { + chat_completion_source: 'openai', + messages: messages, + model: options.model, + reverse_proxy: options.apiUrl, + proxy_password: options.apiKey, + stream: false, + max_tokens: options.maxTokens || 30000, + temperature: options.temperature || 1, + top_p: options.top_p || 1, + }; + + if (!isGoogleApi) { + Object.assign(body, { + custom_prompt_post_processing: 'strict', + enable_web_search: false, + frequency_penalty: 0, + group_names: [], + include_reasoning: false, + presence_penalty: 0.12, + reasoning_effort: 'medium', + request_images: false, + }); + } + + const response = await fetch('/api/backends/chat-completions/generate', { + method: 'POST', + headers: { ...getRequestHeaders(), 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Ngms全兼容API请求失败: ${response.status} - ${errorText}`); + } + + const responseData = await response.json(); + return responseData?.choices?.[0]?.message?.content; +} + +async function callNgmsSillyTavernPreset(messages, options) { + console.log('[Amily2号-NgmsST预设] 使用SillyTavern预设调用'); + + const context = getContext(); + if (!context) { + throw new Error('无法获取SillyTavern上下文'); + } + + const profileId = options.tavernProfile; + if (!profileId) { + throw new Error('未配置SillyTavern预设ID'); + } + + let originalProfile = ''; + let responsePromise; + + try { + originalProfile = await amilyHelper.triggerSlash('/profile'); + console.log(`[Amily2号-NgmsST预设] 当前配置文件: ${originalProfile}`); + + const targetProfile = context.extensionSettings?.connectionManager?.profiles?.find(p => p.id === profileId); + if (!targetProfile) { + throw new Error(`未找到配置文件ID: ${profileId}`); + } + + const targetProfileName = targetProfile.name; + console.log(`[Amily2号-NgmsST预设] 目标配置文件: ${targetProfileName}`); + + const currentProfile = await amilyHelper.triggerSlash('/profile'); + if (currentProfile !== targetProfileName) { + console.log(`[Amily2号-NgmsST预设] 切换配置文件: ${currentProfile} -> ${targetProfileName}`); + const escapedProfileName = targetProfileName.replace(/"/g, '\\"'); + await amilyHelper.triggerSlash(`/profile await=true "${escapedProfileName}"`); + } + + if (!context.ConnectionManagerRequestService) { + throw new Error('ConnectionManagerRequestService不可用'); + } + + console.log(`[Amily2号-NgmsST预设] 通过配置文件 ${targetProfileName} 发送请求`); + responsePromise = context.ConnectionManagerRequestService.sendRequest( + targetProfile.id, + messages, + options.maxTokens || 4000 + ); + + } finally { + try { + const currentProfileAfterCall = await amilyHelper.triggerSlash('/profile'); + if (originalProfile && originalProfile !== currentProfileAfterCall) { + console.log(`[Amily2号-NgmsST预设] 恢复原始配置文件: ${currentProfileAfterCall} -> ${originalProfile}`); + const escapedOriginalProfile = originalProfile.replace(/"/g, '\\"'); + await amilyHelper.triggerSlash(`/profile await=true "${escapedOriginalProfile}"`); + } + } catch (restoreError) { + console.error('[Amily2号-NgmsST预设] 恢复配置文件失败:', restoreError); + } + } + + const result = await responsePromise; + + if (!result) { + throw new Error('未收到API响应'); + } + + const normalizedResult = normalizeApiResponse(result); + if (normalizedResult.error) { + throw new Error(normalizedResult.error.message || 'SillyTavern预设API调用失败'); + } + + return normalizedResult.content; +} + +export async function fetchNgmsModels() { + console.log('[Amily2号-Ngms外交部] 开始获取模型列表'); + + const apiSettings = getNgmsApiSettings(); + + try { + if (apiSettings.apiMode === 'sillytavern_preset') { + // SillyTavern预设模式:获取当前预设的模型 + const context = getContext(); + if (!context?.extensionSettings?.connectionManager?.profiles) { + throw new Error('无法获取SillyTavern配置文件列表'); + } + + const targetProfile = context.extensionSettings.connectionManager.profiles.find(p => p.id === apiSettings.tavernProfile); + if (!targetProfile) { + throw new Error(`未找到配置文件ID: ${apiSettings.tavernProfile}`); + } + + const models = []; + if (targetProfile.openai_model) { + models.push({ id: targetProfile.openai_model, name: targetProfile.openai_model }); + } + + if (models.length === 0) { + throw new Error('当前预设未配置模型'); + } + + console.log('[Amily2号-Ngms外交部] SillyTavern预设模式获取到模型:', models); + return models; + + } else { + if (!apiSettings.apiUrl || !apiSettings.apiKey) { + throw new Error('API URL或Key未配置'); + } + + const response = await fetch('/api/backends/chat-completions/status', { + method: 'POST', + headers: { + ...getRequestHeaders(), + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + reverse_proxy: apiSettings.apiUrl, + proxy_password: apiSettings.apiKey, + chat_completion_source: 'openai' + }) + }); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const rawData = await response.json(); + const models = Array.isArray(rawData) ? rawData : (rawData.data || rawData.models || []); + + if (!Array.isArray(models)) { + const errorMessage = result.error?.message || 'API未返回有效的模型列表数组'; + throw new Error(errorMessage); + } + + const formattedModels = models + .map(m => { + // 从name字段中提取模型名称,去掉"models/"前缀 + const modelIdRaw = m.name || m.id || m.model || m; + const modelName = String(modelIdRaw).replace(/^models\//, ''); + return { + id: modelName, + name: modelName + }; + }) + .filter(m => m.id) + .sort((a, b) => String(a.name).localeCompare(String(b.name))); + + console.log('[Amily2号-Ngms外交部] 全兼容模式获取到模型:', formattedModels); + return formattedModels; + } + } catch (error) { + console.error('[Amily2号-Ngms外交部] 获取模型列表失败:', error); + toastr.error(`获取模型列表失败: ${error.message}`, 'Ngms API'); + throw error; + } +} + +export async function testNgmsApiConnection() { + console.log('[Amily2号-Ngms外交部] 开始API连接测试'); + + const apiSettings = getNgmsApiSettings(); + + if (apiSettings.apiMode === 'sillytavern_preset') { + if (!apiSettings.tavernProfile) { + toastr.error('未配置SillyTavern预设ID', 'Ngms API连接测试失败'); + return false; + } + } else { + if (!apiSettings.apiUrl || !apiSettings.apiKey || !apiSettings.model) { + toastr.error('API配置不完整,请检查URL、Key和模型', 'Ngms API连接测试失败'); + return false; + } + } + + try { + toastr.info('正在发送测试消息"你好!"...', 'Ngms API连接测试'); + + const userName = window.SillyTavern.getContext?.()?.name1 || '用户'; + const systemPrompt = `接下来用户会使用测试按钮测试api连接是否正常,请回复ta连接是正常的,称呼用户为:“${userName}大人”,并复述群号1060183271,告知ta,本扩展开发者:“诗与酒.”正静待ta的入驻。`; + + const testMessages = [ + { role: 'system', content: systemPrompt }, + { role: 'user', content: '你好!' } + ]; + + const response = await callNgmsAI(testMessages); + + if (response && response.trim()) { + console.log('[Amily2号-Ngms外交部] 测试消息响应:', response); + const formattedResponse = response.replace(/\*\*(.*?)\*\*/g, '$1'); + toastr.success(`连接测试成功!AI回复: "${formattedResponse}"`, 'Ngms API连接测试成功', { "escapeHtml": false }); + return true; + } else { + throw new Error('API未返回有效响应'); + } + + } catch (error) { + console.error('[Amily2号-Ngms外交部] 连接测试失败:', error); + toastr.error(`连接测试失败: ${error.message}`, 'Ngms API连接测试失败'); + return false; + } +} diff --git a/core/api/SybdApi.js b/core/api/SybdApi.js index 9dc86f6..542b9d3 100644 --- a/core/api/SybdApi.js +++ b/core/api/SybdApi.js @@ -1,388 +1,385 @@ -import { extension_settings, getContext } from "/scripts/extensions.js"; -import { characters, this_chid, getRequestHeaders, saveSettingsDebounced, eventSource, event_types } from "/script.js"; -import { extensionName } from "../../utils/settings.js"; - -let ChatCompletionService = undefined; -try { - const module = await import('/scripts/custom-request.js'); - ChatCompletionService = module.ChatCompletionService; - console.log('[Amily2号-Sybd外交部] 已成功召唤"皇家信使"(ChatCompletionService)。'); -} catch (e) { - console.warn("[Amily2号-Sybd外交部] 未能召唤“皇家信使”,部分高级功能(如Claw代理)将受限。请考虑更新SillyTavern版本。", e); -} - -function normalizeApiResponse(responseData) { - let data = responseData; - if (typeof data === 'string') { - try { - data = JSON.parse(data); - } catch (e) { - console.error(`[${extensionName}] Sybd API响应JSON解析失败:`, e); - return { error: { message: 'Invalid JSON response' } }; - } - } - if (data && typeof data.data === 'object' && data.data !== null && !Array.isArray(data.data)) { - if (Object.hasOwn(data.data, 'data')) { - data = data.data; - } - } - if (data && data.choices && data.choices[0]) { - return { content: data.choices[0].message?.content?.trim() }; - } - if (data && data.content) { - return { content: data.content.trim() }; - } - if (data && data.data) { - return { data: data.data }; - } - if (data && data.error) { - return { error: data.error }; - } - return data; -} - -export function getSybdApiSettings() { - return { - apiMode: extension_settings[extensionName]?.sybdApiMode || 'openai_test', - apiUrl: extension_settings[extensionName]?.sybdApiUrl?.trim() || '', - apiKey: extension_settings[extensionName]?.sybdApiKey?.trim() || '', - model: extension_settings[extensionName]?.sybdModel || '', - maxTokens: extension_settings[extensionName]?.sybdMaxTokens || 4000, - temperature: extension_settings[extensionName]?.sybdTemperature || 0.7, - tavernProfile: extension_settings[extensionName]?.sybdTavernProfile || '' - }; -} - -export async function callSybdAI(messages, options = {}) { - if (window.AMILY2_SYSTEM_PARALYZED === true) { - console.error("[Amily2-Sybd制裁] 系统完整性已受损,所有外交活动被无限期中止。"); - return null; - } - - const apiSettings = getSybdApiSettings(); - - const finalOptions = { - maxTokens: apiSettings.maxTokens, - temperature: apiSettings.temperature, - model: apiSettings.model, - apiUrl: apiSettings.apiUrl, - apiKey: apiSettings.apiKey, - apiMode: apiSettings.apiMode, - tavernProfile: apiSettings.tavernProfile, - ...options - }; - - if (finalOptions.apiMode !== 'sillytavern_preset') { - if (!finalOptions.apiUrl || !finalOptions.model || !finalOptions.apiKey) { - console.warn("[Amily2-Sybd外交部] API配置不完整,无法调用AI"); - toastr.error("API配置不完整,请检查URL、Key和模型配置。", "Sybd-外交部"); - return null; - } - } - - console.groupCollapsed(`[Amily2号-Sybd统一API调用] ${new Date().toLocaleTimeString()}`); - console.log("【请求参数】:", { - mode: finalOptions.apiMode, - model: finalOptions.model, - maxTokens: finalOptions.maxTokens, - temperature: finalOptions.temperature, - messagesCount: messages.length - }); - console.log("【消息内容】:", messages); - console.groupEnd(); - - try { - let responseContent; - - switch (finalOptions.apiMode) { - case 'openai_test': - responseContent = await callSybdOpenAITest(messages, finalOptions); - break; - case 'sillytavern_preset': - responseContent = await callSybdSillyTavernPreset(messages, finalOptions); - break; - default: - console.error(`[Amily2-Sybd外交部] 未支持的API模式: ${finalOptions.apiMode}`); - return null; - } - - if (!responseContent) { - console.warn('[Amily2-Sybd外交部] 未能获取AI响应内容'); - return null; - } - - console.groupCollapsed("[Amily2号-Sybd AI回复]"); - console.log(responseContent); - console.groupEnd(); - - return responseContent; - - } catch (error) { - console.error(`[Amily2-Sybd外交部] API调用发生错误:`, error); - - if (error.message.includes('400')) { - toastr.error(`API请求格式错误 (400): 请检查消息格式和模型配置`, "Sybd API调用失败"); - } else if (error.message.includes('401')) { - toastr.error(`API认证失败 (401): 请检查API Key配置`, "Sybd API调用失败"); - } else if (error.message.includes('403')) { - toastr.error(`API访问被拒绝 (403): 请检查权限设置`, "Sybd API调用失败"); - } else if (error.message.includes('429')) { - toastr.error(`API调用频率超限 (429): 请稍后重试`, "Sybd API调用失败"); - } else if (error.message.includes('500')) { - toastr.error(`API服务器错误 (500): 请稍后重试`, "Sybd API调用失败"); - } else { - toastr.error(`API调用失败: ${error.message}`, "Sybd API调用失败"); - } - - return null; - } -} - -async function callSybdOpenAITest(messages, options) { - const isGoogleApi = options.apiUrl.includes('googleapis.com'); - - const body = { - chat_completion_source: 'openai', - messages: messages, - model: options.model, - reverse_proxy: options.apiUrl, - proxy_password: options.apiKey, - stream: false, - max_tokens: options.maxTokens || 30000, - temperature: options.temperature || 1, - top_p: options.top_p || 1, - }; - - if (!isGoogleApi) { - Object.assign(body, { - custom_prompt_post_processing: 'strict', - enable_web_search: false, - frequency_penalty: 0, - group_names: [], - include_reasoning: false, - presence_penalty: 0.12, - reasoning_effort: 'medium', - request_images: false, - }); - } - - const response = await fetch('/api/backends/chat-completions/generate', { - method: 'POST', - headers: { ...getRequestHeaders(), 'Content-Type': 'application/json' }, - body: JSON.stringify(body) - }); - - if (!response.ok) { - const errorText = await response.text(); - throw new Error(`Sybd全兼容API请求失败: ${response.status} - ${errorText}`); - } - - const responseData = await response.json(); - return responseData?.choices?.[0]?.message?.content; -} - -async function callSybdSillyTavernPreset(messages, options) { - console.log('[Amily2号-SybdST预设] 使用SillyTavern预设调用'); - - if (!window.TavernHelper || !window.TavernHelper.triggerSlash) { - throw new Error('TavernHelper不可用,无法使用SillyTavern预设模式'); - } - - const context = getContext(); - if (!context) { - throw new Error('无法获取SillyTavern上下文'); - } - - const profileId = options.tavernProfile; - if (!profileId) { - throw new Error('未配置SillyTavern预设ID'); - } - - let originalProfile = ''; - let responsePromise; - - try { - originalProfile = await window.TavernHelper.triggerSlash('/profile'); - console.log(`[Amily2号-SybdST预设] 当前配置文件: ${originalProfile}`); - - const targetProfile = context.extensionSettings?.connectionManager?.profiles?.find(p => p.id === profileId); - if (!targetProfile) { - throw new Error(`未找到配置文件ID: ${profileId}`); - } - - const targetProfileName = targetProfile.name; - console.log(`[Amily2号-SybdST预设] 目标配置文件: ${targetProfileName}`); - - const currentProfile = await window.TavernHelper.triggerSlash('/profile'); - if (currentProfile !== targetProfileName) { - console.log(`[Amily2号-SybdST预设] 切换配置文件: ${currentProfile} -> ${targetProfileName}`); - const escapedProfileName = targetProfileName.replace(/"/g, '\\"'); - await window.TavernHelper.triggerSlash(`/profile await=true "${escapedProfileName}"`); - } - - if (!context.ConnectionManagerRequestService) { - throw new Error('ConnectionManagerRequestService不可用'); - } - - console.log(`[Amily2号-SybdST预设] 通过配置文件 ${targetProfileName} 发送请求`); - responsePromise = context.ConnectionManagerRequestService.sendRequest( - targetProfile.id, - messages, - options.maxTokens || 4000 - ); - - } finally { - try { - const currentProfileAfterCall = await window.TavernHelper.triggerSlash('/profile'); - if (originalProfile && originalProfile !== currentProfileAfterCall) { - console.log(`[Amily2号-SybdST预设] 恢复原始配置文件: ${currentProfileAfterCall} -> ${originalProfile}`); - const escapedOriginalProfile = originalProfile.replace(/"/g, '\\"'); - await window.TavernHelper.triggerSlash(`/profile await=true "${escapedOriginalProfile}"`); - } - } catch (restoreError) { - console.error('[Amily2号-SybdST预设] 恢复配置文件失败:', restoreError); - } - } - - const result = await responsePromise; - - if (!result) { - throw new Error('未收到API响应'); - } - - const normalizedResult = normalizeApiResponse(result); - if (normalizedResult.error) { - throw new Error(normalizedResult.error.message || 'SillyTavern预设API调用失败'); - } - - return normalizedResult.content; -} - -export async function fetchSybdModels() { - console.log('[Amily2号-Sybd外交部] 开始获取模型列表'); - - const apiSettings = getSybdApiSettings(); - - try { - if (apiSettings.apiMode === 'sillytavern_preset') { - // SillyTavern预设模式:获取当前预设的模型 - const context = getContext(); - if (!context?.extensionSettings?.connectionManager?.profiles) { - throw new Error('无法获取SillyTavern配置文件列表'); - } - - const targetProfile = context.extensionSettings.connectionManager.profiles.find(p => p.id === apiSettings.tavernProfile); - if (!targetProfile) { - throw new Error(`未找到配置文件ID: ${apiSettings.tavernProfile}`); - } - - const models = []; - if (targetProfile.openai_model) { - models.push({ id: targetProfile.openai_model, name: targetProfile.openai_model }); - } - - if (models.length === 0) { - throw new Error('当前预设未配置模型'); - } - - console.log('[Amily2号-Sybd外交部] SillyTavern预设模式获取到模型:', models); - return models; - - } else { - if (!apiSettings.apiUrl || !apiSettings.apiKey) { - throw new Error('API URL或Key未配置'); - } - - const response = await fetch('/api/backends/chat-completions/status', { - method: 'POST', - headers: { - ...getRequestHeaders(), - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - reverse_proxy: apiSettings.apiUrl, - proxy_password: apiSettings.apiKey, - chat_completion_source: 'openai' - }) - }); - - if (!response.ok) { - throw new Error(`HTTP ${response.status}: ${response.statusText}`); - } - - const rawData = await response.json(); - const models = Array.isArray(rawData) ? rawData : (rawData.data || rawData.models || []); - - if (!Array.isArray(models)) { - const errorMessage = result.error?.message || 'API未返回有效的模型列表数组'; - throw new Error(errorMessage); - } - - const formattedModels = models - .map(m => { - // 从name字段中提取模型名称,去掉"models/"前缀 - const modelIdRaw = m.name || m.id || m.model || m; - const modelName = String(modelIdRaw).replace(/^models\//, ''); - return { - id: modelName, - name: modelName - }; - }) - .filter(m => m.id) - .sort((a, b) => String(a.name).localeCompare(String(b.name))); - - console.log('[Amily2号-Sybd外交部] 全兼容模式获取到模型:', formattedModels); - return formattedModels; - } - } catch (error) { - console.error('[Amily2号-Sybd外交部] 获取模型列表失败:', error); - toastr.error(`获取模型列表失败: ${error.message}`, 'Sybd API'); - throw error; - } -} - -export async function testSybdApiConnection() { - console.log('[Amily2号-Sybd外交部] 开始API连接测试'); - - const apiSettings = getSybdApiSettings(); - - if (apiSettings.apiMode === 'sillytavern_preset') { - if (!apiSettings.tavernProfile) { - toastr.error('未配置SillyTavern预设ID', 'Sybd API连接测试失败'); - return false; - } - } else { - if (!apiSettings.apiUrl || !apiSettings.apiKey || !apiSettings.model) { - toastr.error('API配置不完整,请检查URL、Key和模型', 'Sybd API连接测试失败'); - return false; - } - } - - try { - toastr.info('正在发送测试消息"你好!"...', 'Sybd API连接测试'); - - const userName = window.SillyTavern.getContext?.()?.name1 || '用户'; - const systemPrompt = `接下来用户会使用测试按钮测试api连接是否正常,请回复ta连接是正常的,称呼用户为:“${userName}大人”,并复述群号1060183271,告知ta,本扩展开发者:“诗与酒.”正静待ta的入驻。`; - - const testMessages = [ - { role: 'system', content: systemPrompt }, - { role: 'user', content: '你好!' } - ]; - - const response = await callSybdAI(testMessages); - - if (response && response.trim()) { - console.log('[Amily2号-Sybd外交部] 测试消息响应:', response); - const formattedResponse = response.replace(/\*\*(.*?)\*\*/g, '$1'); - toastr.success(`连接测试成功!AI回复: "${formattedResponse}"`, 'Sybd API连接测试成功', { "escapeHtml": false }); - return true; - } else { - throw new Error('API未返回有效响应'); - } - - } catch (error) { - console.error('[Amily2号-Sybd外交部] 连接测试失败:', error); - toastr.error(`连接测试失败: ${error.message}`, 'Sybd API连接测试失败'); - return false; - } -} +import { extension_settings, getContext } from "/scripts/extensions.js"; +import { characters, this_chid, getRequestHeaders, saveSettingsDebounced, eventSource, event_types } from "/script.js"; +import { extensionName } from "../../utils/settings.js"; +import { amilyHelper } from '../../core/tavern-helper/main.js'; + +let ChatCompletionService = undefined; +try { + const module = await import('/scripts/custom-request.js'); + ChatCompletionService = module.ChatCompletionService; + console.log('[Amily2号-Sybd外交部] 已成功召唤"皇家信使"(ChatCompletionService)。'); +} catch (e) { + console.warn("[Amily2号-Sybd外交部] 未能召唤“皇家信使”,部分高级功能(如Claw代理)将受限。请考虑更新SillyTavern版本。", e); +} + +function normalizeApiResponse(responseData) { + let data = responseData; + if (typeof data === 'string') { + try { + data = JSON.parse(data); + } catch (e) { + console.error(`[${extensionName}] Sybd API响应JSON解析失败:`, e); + return { error: { message: 'Invalid JSON response' } }; + } + } + if (data && typeof data.data === 'object' && data.data !== null && !Array.isArray(data.data)) { + if (Object.hasOwn(data.data, 'data')) { + data = data.data; + } + } + if (data && data.choices && data.choices[0]) { + return { content: data.choices[0].message?.content?.trim() }; + } + if (data && data.content) { + return { content: data.content.trim() }; + } + if (data && data.data) { + return { data: data.data }; + } + if (data && data.error) { + return { error: data.error }; + } + return data; +} + +export function getSybdApiSettings() { + return { + apiMode: extension_settings[extensionName]?.sybdApiMode || 'openai_test', + apiUrl: extension_settings[extensionName]?.sybdApiUrl?.trim() || '', + apiKey: extension_settings[extensionName]?.sybdApiKey?.trim() || '', + model: extension_settings[extensionName]?.sybdModel || '', + maxTokens: extension_settings[extensionName]?.sybdMaxTokens || 4000, + temperature: extension_settings[extensionName]?.sybdTemperature || 0.7, + tavernProfile: extension_settings[extensionName]?.sybdTavernProfile || '' + }; +} + +export async function callSybdAI(messages, options = {}) { + if (window.AMILY2_SYSTEM_PARALYZED === true) { + console.error("[Amily2-Sybd制裁] 系统完整性已受损,所有外交活动被无限期中止。"); + return null; + } + + const apiSettings = getSybdApiSettings(); + + const finalOptions = { + maxTokens: apiSettings.maxTokens, + temperature: apiSettings.temperature, + model: apiSettings.model, + apiUrl: apiSettings.apiUrl, + apiKey: apiSettings.apiKey, + apiMode: apiSettings.apiMode, + tavernProfile: apiSettings.tavernProfile, + ...options + }; + + if (finalOptions.apiMode !== 'sillytavern_preset') { + if (!finalOptions.apiUrl || !finalOptions.model || !finalOptions.apiKey) { + console.warn("[Amily2-Sybd外交部] API配置不完整,无法调用AI"); + toastr.error("API配置不完整,请检查URL、Key和模型配置。", "Sybd-外交部"); + return null; + } + } + + console.groupCollapsed(`[Amily2号-Sybd统一API调用] ${new Date().toLocaleTimeString()}`); + console.log("【请求参数】:", { + mode: finalOptions.apiMode, + model: finalOptions.model, + maxTokens: finalOptions.maxTokens, + temperature: finalOptions.temperature, + messagesCount: messages.length + }); + console.log("【消息内容】:", messages); + console.groupEnd(); + + try { + let responseContent; + + switch (finalOptions.apiMode) { + case 'openai_test': + responseContent = await callSybdOpenAITest(messages, finalOptions); + break; + case 'sillytavern_preset': + responseContent = await callSybdSillyTavernPreset(messages, finalOptions); + break; + default: + console.error(`[Amily2-Sybd外交部] 未支持的API模式: ${finalOptions.apiMode}`); + return null; + } + + if (!responseContent) { + console.warn('[Amily2-Sybd外交部] 未能获取AI响应内容'); + return null; + } + + console.groupCollapsed("[Amily2号-Sybd AI回复]"); + console.log(responseContent); + console.groupEnd(); + + return responseContent; + + } catch (error) { + console.error(`[Amily2-Sybd外交部] API调用发生错误:`, error); + + if (error.message.includes('400')) { + toastr.error(`API请求格式错误 (400): 请检查消息格式和模型配置`, "Sybd API调用失败"); + } else if (error.message.includes('401')) { + toastr.error(`API认证失败 (401): 请检查API Key配置`, "Sybd API调用失败"); + } else if (error.message.includes('403')) { + toastr.error(`API访问被拒绝 (403): 请检查权限设置`, "Sybd API调用失败"); + } else if (error.message.includes('429')) { + toastr.error(`API调用频率超限 (429): 请稍后重试`, "Sybd API调用失败"); + } else if (error.message.includes('500')) { + toastr.error(`API服务器错误 (500): 请稍后重试`, "Sybd API调用失败"); + } else { + toastr.error(`API调用失败: ${error.message}`, "Sybd API调用失败"); + } + + return null; + } +} + +async function callSybdOpenAITest(messages, options) { + const isGoogleApi = options.apiUrl.includes('googleapis.com'); + + const body = { + chat_completion_source: 'openai', + messages: messages, + model: options.model, + reverse_proxy: options.apiUrl, + proxy_password: options.apiKey, + stream: false, + max_tokens: options.maxTokens || 30000, + temperature: options.temperature || 1, + top_p: options.top_p || 1, + }; + + if (!isGoogleApi) { + Object.assign(body, { + custom_prompt_post_processing: 'strict', + enable_web_search: false, + frequency_penalty: 0, + group_names: [], + include_reasoning: false, + presence_penalty: 0.12, + reasoning_effort: 'medium', + request_images: false, + }); + } + + const response = await fetch('/api/backends/chat-completions/generate', { + method: 'POST', + headers: { ...getRequestHeaders(), 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Sybd全兼容API请求失败: ${response.status} - ${errorText}`); + } + + const responseData = await response.json(); + return responseData?.choices?.[0]?.message?.content; +} + +async function callSybdSillyTavernPreset(messages, options) { + console.log('[Amily2号-SybdST预设] 使用SillyTavern预设调用'); + + const context = getContext(); + if (!context) { + throw new Error('无法获取SillyTavern上下文'); + } + + const profileId = options.tavernProfile; + if (!profileId) { + throw new Error('未配置SillyTavern预设ID'); + } + + let originalProfile = ''; + let responsePromise; + + try { + originalProfile = await amilyHelper.triggerSlash('/profile'); + console.log(`[Amily2号-SybdST预设] 当前配置文件: ${originalProfile}`); + + const targetProfile = context.extensionSettings?.connectionManager?.profiles?.find(p => p.id === profileId); + if (!targetProfile) { + throw new Error(`未找到配置文件ID: ${profileId}`); + } + + const targetProfileName = targetProfile.name; + console.log(`[Amily2号-SybdST预设] 目标配置文件: ${targetProfileName}`); + + const currentProfile = await amilyHelper.triggerSlash('/profile'); + if (currentProfile !== targetProfileName) { + console.log(`[Amily2号-SybdST预设] 切换配置文件: ${currentProfile} -> ${targetProfileName}`); + const escapedProfileName = targetProfileName.replace(/"/g, '\\"'); + await amilyHelper.triggerSlash(`/profile await=true "${escapedProfileName}"`); + } + + if (!context.ConnectionManagerRequestService) { + throw new Error('ConnectionManagerRequestService不可用'); + } + + console.log(`[Amily2号-SybdST预设] 通过配置文件 ${targetProfileName} 发送请求`); + responsePromise = context.ConnectionManagerRequestService.sendRequest( + targetProfile.id, + messages, + options.maxTokens || 4000 + ); + + } finally { + try { + const currentProfileAfterCall = await amilyHelper.triggerSlash('/profile'); + if (originalProfile && originalProfile !== currentProfileAfterCall) { + console.log(`[Amily2号-SybdST预设] 恢复原始配置文件: ${currentProfileAfterCall} -> ${originalProfile}`); + const escapedOriginalProfile = originalProfile.replace(/"/g, '\\"'); + await amilyHelper.triggerSlash(`/profile await=true "${escapedOriginalProfile}"`); + } + } catch (restoreError) { + console.error('[Amily2号-SybdST预设] 恢复配置文件失败:', restoreError); + } + } + + const result = await responsePromise; + + if (!result) { + throw new Error('未收到API响应'); + } + + const normalizedResult = normalizeApiResponse(result); + if (normalizedResult.error) { + throw new Error(normalizedResult.error.message || 'SillyTavern预设API调用失败'); + } + + return normalizedResult.content; +} + +export async function fetchSybdModels() { + console.log('[Amily2号-Sybd外交部] 开始获取模型列表'); + + const apiSettings = getSybdApiSettings(); + + try { + if (apiSettings.apiMode === 'sillytavern_preset') { + // SillyTavern预设模式:获取当前预设的模型 + const context = getContext(); + if (!context?.extensionSettings?.connectionManager?.profiles) { + throw new Error('无法获取SillyTavern配置文件列表'); + } + + const targetProfile = context.extensionSettings.connectionManager.profiles.find(p => p.id === apiSettings.tavernProfile); + if (!targetProfile) { + throw new Error(`未找到配置文件ID: ${apiSettings.tavernProfile}`); + } + + const models = []; + if (targetProfile.openai_model) { + models.push({ id: targetProfile.openai_model, name: targetProfile.openai_model }); + } + + if (models.length === 0) { + throw new Error('当前预设未配置模型'); + } + + console.log('[Amily2号-Sybd外交部] SillyTavern预设模式获取到模型:', models); + return models; + + } else { + if (!apiSettings.apiUrl || !apiSettings.apiKey) { + throw new Error('API URL或Key未配置'); + } + + const response = await fetch('/api/backends/chat-completions/status', { + method: 'POST', + headers: { + ...getRequestHeaders(), + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + reverse_proxy: apiSettings.apiUrl, + proxy_password: apiSettings.apiKey, + chat_completion_source: 'openai' + }) + }); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const rawData = await response.json(); + const models = Array.isArray(rawData) ? rawData : (rawData.data || rawData.models || []); + + if (!Array.isArray(models)) { + const errorMessage = result.error?.message || 'API未返回有效的模型列表数组'; + throw new Error(errorMessage); + } + + const formattedModels = models + .map(m => { + // 从name字段中提取模型名称,去掉"models/"前缀 + const modelIdRaw = m.name || m.id || m.model || m; + const modelName = String(modelIdRaw).replace(/^models\//, ''); + return { + id: modelName, + name: modelName + }; + }) + .filter(m => m.id) + .sort((a, b) => String(a.name).localeCompare(String(b.name))); + + console.log('[Amily2号-Sybd外交部] 全兼容模式获取到模型:', formattedModels); + return formattedModels; + } + } catch (error) { + console.error('[Amily2号-Sybd外交部] 获取模型列表失败:', error); + toastr.error(`获取模型列表失败: ${error.message}`, 'Sybd API'); + throw error; + } +} + +export async function testSybdApiConnection() { + console.log('[Amily2号-Sybd外交部] 开始API连接测试'); + + const apiSettings = getSybdApiSettings(); + + if (apiSettings.apiMode === 'sillytavern_preset') { + if (!apiSettings.tavernProfile) { + toastr.error('未配置SillyTavern预设ID', 'Sybd API连接测试失败'); + return false; + } + } else { + if (!apiSettings.apiUrl || !apiSettings.apiKey || !apiSettings.model) { + toastr.error('API配置不完整,请检查URL、Key和模型', 'Sybd API连接测试失败'); + return false; + } + } + + try { + toastr.info('正在发送测试消息"你好!"...', 'Sybd API连接测试'); + + const userName = window.SillyTavern.getContext?.()?.name1 || '用户'; + const systemPrompt = `接下来用户会使用测试按钮测试api连接是否正常,请回复ta连接是正常的,称呼用户为:“${userName}大人”,并复述群号1060183271,告知ta,本扩展开发者:“诗与酒.”正静待ta的入驻。`; + + const testMessages = [ + { role: 'system', content: systemPrompt }, + { role: 'user', content: '你好!' } + ]; + + const response = await callSybdAI(testMessages); + + if (response && response.trim()) { + console.log('[Amily2号-Sybd外交部] 测试消息响应:', response); + const formattedResponse = response.replace(/\*\*(.*?)\*\*/g, '$1'); + toastr.success(`连接测试成功!AI回复: "${formattedResponse}"`, 'Sybd API连接测试成功', { "escapeHtml": false }); + return true; + } else { + throw new Error('API未返回有效响应'); + } + + } catch (error) { + console.error('[Amily2号-Sybd外交部] 连接测试失败:', error); + toastr.error(`连接测试失败: ${error.message}`, 'Sybd API连接测试失败'); + return false; + } +} From d4ec0aa201d00b5ef7a06cea2e1a5fb0d2be1aad Mon Sep 17 00:00:00 2001 From: Wx-2025 <351320169@qq.com> Date: Wed, 29 Oct 2025 21:22:18 +0800 Subject: [PATCH 19/49] Add files via upload --- core/commands.js | 222 ++++++- core/tavern-helper/Wrapperiframe.js | 36 ++ core/tavern-helper/iframe_client.js | 31 + core/tavern-helper/main.js | 792 ++++++++++++++++++------ core/tavern-helper/renderer-bindings.js | 51 ++ core/tavern-helper/renderer.html | 21 + core/tavern-helper/renderer.js | 601 ++++++++++++++++++ 7 files changed, 1580 insertions(+), 174 deletions(-) create mode 100644 core/tavern-helper/Wrapperiframe.js create mode 100644 core/tavern-helper/iframe_client.js create mode 100644 core/tavern-helper/renderer-bindings.js create mode 100644 core/tavern-helper/renderer.html create mode 100644 core/tavern-helper/renderer.js diff --git a/core/commands.js b/core/commands.js index 73b77ef..3f01098 100644 --- a/core/commands.js +++ b/core/commands.js @@ -1 +1,221 @@ -(function(_0x18689,_0x75b104){const _0x22eef5=_0x1e93,_0x211589=_0x18689();while(!![]){try{const _0x1fc4aa=parseInt(_0x22eef5(0x87))/0x1+-parseInt(_0x22eef5(0x9a))/0x2+parseInt(_0x22eef5(0xad))/0x3+parseInt(_0x22eef5(0x92))/0x4*(parseInt(_0x22eef5(0x95))/0x5)+-parseInt(_0x22eef5(0xa8))/0x6+parseInt(_0x22eef5(0xb3))/0x7*(-parseInt(_0x22eef5(0x8c))/0x8)+parseInt(_0x22eef5(0x9f))/0x9;if(_0x1fc4aa===_0x75b104)break;else _0x211589['push'](_0x211589['shift']());}catch(_0x155ea3){_0x211589['push'](_0x211589['shift']());}}}(_0x5a0e,0xd1ebd));import{getContext,extension_settings}from'/scripts/extensions.js';import{saveChatConditional,reloadCurrentChat}from'/script.js';import{extensionName}from'../utils/settings.js';import{SlashCommand}from'/scripts/slash-commands/SlashCommand.js';function _0x5a0e(){const _0x4d9310=['error','info','apiUrl','检测到问题,建议使用修复功能','需要至少2条消息才能测试','63libeyI','正在使用API检查回复...','正在使用API测试检测功能...','修复最新的AI回复中的问题','[Amily2]\x20致命错误:SlashCommand\x20或\x20SlashCommandParser\x20模块未能加载。','未检测到问题','mes','[Amily2-命令检查器]\x20已获取上下文消息:','正在检查并修复回复...','fix-reply','请先配置API\x20URL','测试结果:API未检测到问题,请检查API配置或提示词','592774fVyiKg','optimizedContent','...','最新消息是用户消息,无需检查','previousMessages','1233928GYQwMk','检查最新的AI回复是否有问题','[Amily2-新诏]\x20/fix-reply\x20命令已成功颁布。','[Amily2-命令检查器]\x20目标为用户消息,跳过。','contextMessages','[Amily2-命令检查器]\x20没有聊天记录。','180GJJBhk','[Amily2]\x20命令注册过程中发生意外错误:','message','184430ijBqSz','fromProps','命令检查器','addCommandObject','length','3144032mSqFRc','log','warning','未检测到需要修复的问题','check-reply','19929132pvOOTj','test-reply-checker','slice','success','chat','is_user','undefined','[Amily2-新诏]\x20/test-reply-checker\x20命令已成功颁布。','[Amily2-新诏]\x20/check-reply\x20命令已成功颁布。','5894136ufrcWW','没有找到可用于测试的AI消息','max','没有可修复的消息','测试聊天回复检查器功能','1006158NMrHEv'];_0x5a0e=function(){return _0x4d9310;};return _0x5a0e();}function _0x1e93(_0x594217,_0x4e8ad1){const _0x5a0e36=_0x5a0e();return _0x1e93=function(_0x1e9306,_0x1385fb){_0x1e9306=_0x1e9306-0x84;let _0x11a0cb=_0x5a0e36[_0x1e9306];return _0x11a0cb;},_0x1e93(_0x594217,_0x4e8ad1);}import{SlashCommandParser}from'/scripts/slash-commands/SlashCommandParser.js';import{checkAndFixWithAPI}from'./api.js';async function checkLatestMessage(){const _0x5426a2=_0x1e93,_0xd34af3=getContext(),_0x134d56=_0xd34af3[_0x5426a2(0xa3)]||[];if(!_0x134d56||_0x134d56['length']===0x0)return console[_0x5426a2(0x9b)](_0x5426a2(0x91)),{'message':null,'previousMessages':[]};const _0x6f9c91=_0x134d56[_0x134d56[_0x5426a2(0x99)]-0x1];console[_0x5426a2(0x9b)]('[Amily2-命令检查器]\x20正在侦测消息:',{'isUser':_0x6f9c91['is_user'],'messagePreview':_0x6f9c91['mes']?.['substring'](0x0,0x32)+_0x5426a2(0x89)});if(_0x6f9c91[_0x5426a2(0xa4)])return console[_0x5426a2(0x9b)](_0x5426a2(0x8f)),{'message':_0x6f9c91,'previousMessages':[]};const _0x2a6209=extension_settings[extensionName],_0xe8211a=_0x2a6209[_0x5426a2(0x90)]||0x2,_0x581bd8=Math[_0x5426a2(0xaa)](0x0,_0x134d56[_0x5426a2(0x99)]-_0xe8211a-0x1),_0x45c0ea=_0x134d56[_0x5426a2(0xa1)](_0x581bd8,_0x134d56[_0x5426a2(0x99)]-0x1);return console[_0x5426a2(0x9b)](_0x5426a2(0xba),{'count':_0x45c0ea[_0x5426a2(0x99)]}),{'message':_0x6f9c91,'previousMessages':_0x45c0ea};}async function checkCommand(){const _0xb1f058=_0x1e93,_0x391057=extension_settings[extensionName];if(!_0x391057['apiUrl'])return toastr[_0xb1f058(0xae)](_0xb1f058(0x85),'命令检查器'),'';const _0x52f2a0=await checkLatestMessage();if(!_0x52f2a0[_0xb1f058(0x94)]||_0x52f2a0[_0xb1f058(0x94)][_0xb1f058(0xa4)])return toastr[_0xb1f058(0xaf)](_0xb1f058(0x8a),_0xb1f058(0x97)),'';toastr['info'](_0xb1f058(0xb4),'命令检查器');const _0x50e800=await checkAndFixWithAPI(_0x52f2a0[_0xb1f058(0x94)],_0x52f2a0[_0xb1f058(0x8b)]);return _0x50e800&&_0x50e800[_0xb1f058(0x88)]&&_0x50e800[_0xb1f058(0x88)]!==_0x52f2a0['message'][_0xb1f058(0xb9)]?toastr[_0xb1f058(0x9c)](_0xb1f058(0xb1),_0xb1f058(0x97)):toastr[_0xb1f058(0xa2)](_0xb1f058(0xb8),_0xb1f058(0x97)),'';}export async function fixCommand(){const _0x34f052=_0x1e93,_0x2e8506=extension_settings[extensionName];if(!_0x2e8506[_0x34f052(0xb0)])return toastr[_0x34f052(0xae)](_0x34f052(0x85),_0x34f052(0x97)),'';const _0x419437=getContext(),_0x21071a=_0x419437[_0x34f052(0xa3)];if(!_0x21071a||_0x21071a[_0x34f052(0x99)]===0x0)return toastr[_0x34f052(0xaf)](_0x34f052(0xab),'命令检查器'),'';const _0x31d855=_0x21071a[_0x21071a['length']-0x1];if(_0x31d855[_0x34f052(0xa4)])return toastr[_0x34f052(0xaf)]('最新消息是用户消息,无需修复','命令检查器'),'';const _0x56bf1c=_0x2e8506['contextMessages']||0x2,_0x739a3a=Math[_0x34f052(0xaa)](0x0,_0x21071a[_0x34f052(0x99)]-0x1-_0x56bf1c),_0x5b8caa=_0x21071a[_0x34f052(0xa1)](_0x739a3a,_0x21071a[_0x34f052(0x99)]-0x1);toastr[_0x34f052(0xaf)](_0x34f052(0xbb),_0x34f052(0x97));const _0x2575e8=await checkAndFixWithAPI(_0x31d855,_0x5b8caa);return _0x2575e8&&_0x2575e8[_0x34f052(0x88)]&&_0x2575e8[_0x34f052(0x88)]!==_0x31d855[_0x34f052(0xb9)]?(_0x31d855[_0x34f052(0xb9)]=_0x2575e8[_0x34f052(0x88)],await saveChatConditional(),await reloadCurrentChat(),toastr[_0x34f052(0xa2)]('回复已修复',_0x34f052(0x97))):toastr[_0x34f052(0xaf)](_0x34f052(0x9d),'命令检查器'),'';}export async function testReplyChecker(){const _0x337447=_0x1e93,_0x4e0dc1=extension_settings[extensionName];if(!_0x4e0dc1[_0x337447(0xb0)])return toastr[_0x337447(0xae)]('请先配置API\x20URL',_0x337447(0x97)),'';const _0x55f607=getContext(),_0x83fffd=_0x55f607['chat'];if(!_0x83fffd||_0x83fffd[_0x337447(0x99)]<0x2)return toastr[_0x337447(0x9c)](_0x337447(0xb2),_0x337447(0x97)),'';let _0x53f0e1=null;for(let _0x129cbc=_0x83fffd[_0x337447(0x99)]-0x2;_0x129cbc>=0x0;_0x129cbc--){if(!_0x83fffd[_0x129cbc][_0x337447(0xa4)]){_0x53f0e1=_0x83fffd[_0x129cbc][_0x337447(0xb9)];break;}}if(!_0x53f0e1)return toastr['warning'](_0x337447(0xa9),_0x337447(0x97)),'';const _0x5c5890=_0x83fffd[_0x83fffd[_0x337447(0x99)]-0x1];if(_0x5c5890[_0x337447(0xa4)])return toastr[_0x337447(0x9c)]('最后一条消息是用户消息,无法测试',_0x337447(0x97)),'';const _0x492d95=_0x5c5890[_0x337447(0xb9)];_0x5c5890[_0x337447(0xb9)]=_0x53f0e1+'\x0a\x0a'+_0x53f0e1,toastr['info'](_0x337447(0xb5),'命令检查器');const _0x355121=_0x4e0dc1[_0x337447(0x90)]||0x2,_0x47f2a8=Math['max'](0x0,_0x83fffd[_0x337447(0x99)]-_0x355121-0x1),_0x57c82b=_0x83fffd[_0x337447(0xa1)](_0x47f2a8,_0x83fffd[_0x337447(0x99)]-0x1),_0x37e83d=await checkAndFixWithAPI(_0x5c5890,_0x57c82b);return _0x5c5890[_0x337447(0xb9)]=_0x492d95,_0x37e83d&&_0x37e83d[_0x337447(0x88)]&&_0x37e83d['optimizedContent']!==_0x53f0e1+'\x0a\x0a'+_0x53f0e1?toastr[_0x337447(0xa2)]('测试成功!API检测到重复内容并提供了修复建议','命令检查器'):toastr['warning'](_0x337447(0x86),_0x337447(0x97)),'';}export async function registerSlashCommands(){const _0x52e940=_0x1e93;try{if(typeof SlashCommand==='undefined'||typeof SlashCommandParser===_0x52e940(0xa5)){console[_0x52e940(0xae)](_0x52e940(0xb7));return;}SlashCommandParser[_0x52e940(0x98)](SlashCommand[_0x52e940(0x96)]({'name':_0x52e940(0x9e),'callback':checkCommand,'helpString':_0x52e940(0x8d)})),console['log'](_0x52e940(0xa7)),SlashCommandParser[_0x52e940(0x98)](SlashCommand[_0x52e940(0x96)]({'name':_0x52e940(0x84),'callback':fixCommand,'helpString':_0x52e940(0xb6)})),console[_0x52e940(0x9b)](_0x52e940(0x8e)),SlashCommandParser[_0x52e940(0x98)](SlashCommand[_0x52e940(0x96)]({'name':_0x52e940(0xa0),'callback':testReplyChecker,'helpString':_0x52e940(0xac)})),console[_0x52e940(0x9b)](_0x52e940(0xa6));}catch(_0x212a74){console[_0x52e940(0xae)](_0x52e940(0x93),_0x212a74);}} \ No newline at end of file +import { getContext, extension_settings } from "/scripts/extensions.js"; +import { saveChatConditional, reloadCurrentChat } from "/script.js"; +import { extensionName } from "../utils/settings.js"; +import { SlashCommand } from "/scripts/slash-commands/SlashCommand.js"; +import { SlashCommandParser } from "/scripts/slash-commands/SlashCommandParser.js"; +import { checkAndFixWithAPI } from "./api.js"; + +async function checkLatestMessage() { + const context = getContext(); + const chat = context.chat || []; + + if (!chat || chat.length === 0) { + console.log("[Amily2-命令检查器] 没有聊天记录。"); + return { message: null, previousMessages: [] }; + } + + const latestMessage = chat[chat.length - 1]; + + console.log("[Amily2-命令检查器] 正在侦测消息:", { + isUser: latestMessage.is_user, + messagePreview: latestMessage.mes?.substring(0, 50) + "...", + }); + + if (latestMessage.is_user) { + console.log("[Amily2-命令检查器] 目标为用户消息,跳过。"); + return { message: latestMessage, previousMessages: [] }; + } + + const settings = extension_settings[extensionName]; + const contextCount = settings.contextMessages || 2; + const startIndex = Math.max(0, chat.length - contextCount - 1); + const previousMessages = chat.slice(startIndex, chat.length - 1); + + console.log("[Amily2-命令检查器] 已获取上下文消息:", { + count: previousMessages.length, + }); + + return { message: latestMessage, previousMessages }; +} + +async function checkCommand() { + const settings = extension_settings[extensionName]; + if (!settings.apiUrl) { + toastr.error("请先配置API URL", "命令检查器"); + return ""; + } + const checkResult = await checkLatestMessage(); + if (!checkResult.message || checkResult.message.is_user) { + toastr.info("最新消息是用户消息,无需检查", "命令检查器"); + return ""; + } + toastr.info("正在使用API检查回复...", "命令检查器"); + const result = await checkAndFixWithAPI( + checkResult.message, + checkResult.previousMessages, + ); + if ( + result && + result.optimizedContent && + result.optimizedContent !== checkResult.message.mes + ) { + toastr.warning("检测到问题,建议使用修复功能", "命令检查器"); + } else { + toastr.success("未检测到问题", "命令检查器"); + } + return ""; +} + + +export async function fixCommand() { + const settings = extension_settings[extensionName]; + if (!settings.apiUrl) { + toastr.error("请先配置API URL", "命令检查器"); + return ""; + } + const context = getContext(); + const chat = context.chat; + if (!chat || chat.length === 0) { + toastr.info("没有可修复的消息", "命令检查器"); + return ""; + } + const latestMessage = chat[chat.length - 1]; + if (latestMessage.is_user) { + toastr.info("最新消息是用户消息,无需修复", "命令检查器"); + return ""; + } + const contextCount = settings.contextMessages || 2; + const startIndex = Math.max(0, chat.length - 1 - contextCount); + const previousMessages = chat.slice(startIndex, chat.length - 1); + toastr.info("正在检查并修复回复...", "命令检查器"); + const result = await checkAndFixWithAPI(latestMessage, previousMessages); + if ( + result && + result.optimizedContent && + result.optimizedContent !== latestMessage.mes + ) { + latestMessage.mes = result.optimizedContent; + await saveChatConditional(); + await reloadCurrentChat(); + toastr.success("回复已修复", "命令检查器"); + } else { + toastr.info("未检测到需要修复的问题", "命令检查器"); + } + return ""; +} + +export async function testReplyChecker() { + const settings = extension_settings[extensionName]; + if (!settings.apiUrl) { + toastr.error("请先配置API URL", "命令检查器"); + return ""; + } + const context = getContext(); + const chat = context.chat; + if (!chat || chat.length < 2) { + toastr.warning("需要至少2条消息才能测试", "命令检查器"); + return ""; + } + let testMessage = null; + for (let i = chat.length - 2; i >= 0; i--) { + if (!chat[i].is_user) { + testMessage = chat[i].mes; + break; + } + } + if (!testMessage) { + toastr.warning("没有找到可用于测试的AI消息", "命令检查器"); + return ""; + } + const lastMessage = chat[chat.length - 1]; + if (lastMessage.is_user) { + toastr.warning("最后一条消息是用户消息,无法测试", "命令检查器"); + return ""; + } + const originalMessage = lastMessage.mes; + lastMessage.mes = testMessage + "\n\n" + testMessage; + toastr.info("正在使用API测试检测功能...", "命令检查器"); + const contextCount = settings.contextMessages || 2; + const startIndex = Math.max(0, chat.length - contextCount - 1); + const previousMessages = chat.slice(startIndex, chat.length - 1); + const result = await checkAndFixWithAPI(lastMessage, previousMessages); + lastMessage.mes = originalMessage; + if ( + result && + result.optimizedContent && + result.optimizedContent !== testMessage + "\n\n" + testMessage + ) { + toastr.success("测试成功!API检测到重复内容并提供了修复建议", "命令检查器"); + } else { + toastr.warning( + "测试结果:API未检测到问题,请检查API配置或提示词", + "命令检查器", + ); + } + return ""; +} + +async function triggerSendButton() { + // 模拟点击发送按钮 + const sendButton = document.getElementById('send_but'); + if (sendButton) { + sendButton.click(); + console.log("[Amily2-触发器] 已触发发送按钮"); + return ""; + } else { + console.warn("[Amily2-触发器] 未找到发送按钮"); + toastr.warning("未找到发送按钮", "触发器"); + return ""; + } +} + +export async function registerSlashCommands() { + try { + if ( + typeof SlashCommand === "undefined" || + typeof SlashCommandParser === "undefined" + ) { + console.error( + "[Amily2] 致命错误:SlashCommand 或 SlashCommandParser 模块未能加载。", + ); + return; + } + SlashCommandParser.addCommandObject( + SlashCommand.fromProps({ + name: "check-reply", + callback: checkCommand, + helpString: "检查最新的AI回复是否有问题", + }), + ); + console.log("[Amily2-新诏] /check-reply 命令已成功颁布。"); + + SlashCommandParser.addCommandObject( + SlashCommand.fromProps({ + name: "fix-reply", + callback: fixCommand, + helpString: "修复最新的AI回复中的问题", + }), + ); + console.log("[Amily2-新诏] /fix-reply 命令已成功颁布。"); + + SlashCommandParser.addCommandObject( + SlashCommand.fromProps({ + name: "test-reply-checker", + callback: testReplyChecker, + helpString: "测试聊天回复检查器功能", + }), + ); + console.log("[Amily2-新诏] /test-reply-checker 命令已成功颁布。"); + + SlashCommandParser.addCommandObject( + SlashCommand.fromProps({ + name: "trigger", + callback: triggerSendButton, + helpString: "触发发送按钮 (用于自动发送消息)", + }), + ); + console.log("[Amily2-新诏] /trigger 命令已成功颁布。"); + } catch (e) { + console.error("[Amily2] 命令注册过程中发生意外错误:", e); + } +} diff --git a/core/tavern-helper/Wrapperiframe.js b/core/tavern-helper/Wrapperiframe.js new file mode 100644 index 0000000..bffad56 --- /dev/null +++ b/core/tavern-helper/Wrapperiframe.js @@ -0,0 +1,36 @@ +(function(){ + if (window.frameElement) { + window.frameElement.style.height = 'auto'; + } + function getGlobal() { + if (typeof self !== 'undefined') { return self; } + if (typeof window !== 'undefined') { return window; } + if (typeof global !== 'undefined') { return global; } + throw new Error('unable to locate global object'); + } + const globalScope = getGlobal(); + if (globalScope.generate_send_button_onclick) { + globalScope.generate_send_button_onclick_old = globalScope.generate_send_button_onclick; + globalScope.generate_send_button_onclick = function(event) { + try { + const textarea = document.getElementById('send_textarea'); + if (textarea && textarea.value) { + const customEvent = new CustomEvent('xb-send-message', { + detail: { + message: textarea.value, + event: event + }, + bubbles: true, + cancelable: true + }); + if (!window.dispatchEvent(customEvent)) { + return; + } + } + } catch (e) { + console.error('Error in xb-send-message event dispatch:', e); + } + globalScope.generate_send_button_onclick_old(event); + }; + } +})(); diff --git a/core/tavern-helper/iframe_client.js b/core/tavern-helper/iframe_client.js new file mode 100644 index 0000000..0fbca7d --- /dev/null +++ b/core/tavern-helper/iframe_client.js @@ -0,0 +1,31 @@ + +function initializeAmilyClient() { + console.log('[Amily2-IframeClient] 正在初始化...'); + + document.body.addEventListener('click', function(event) { + const target = event.target.closest('[data-amily-action]'); + + if (target) { + const action = target.dataset.amilyAction; + const detail = { ...target.dataset }; + + delete detail.amilyAction; + + console.log(`[Amily2-IframeClient] 触发动作: ${action}`, detail); + + if (window.AmilySimpleAPI && typeof window.AmilySimpleAPI.post === 'function') { + window.AmilySimpleAPI.post(action, detail); + } else { + console.error('[Amily2-IframeClient] AmilySimpleAPI 不可用。'); + } + } + }); + + console.log('[Amily2-IframeClient] 客户端脚本已加载并就绪。'); +} + +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initializeAmilyClient); +} else { + initializeAmilyClient(); +} diff --git a/core/tavern-helper/main.js b/core/tavern-helper/main.js index 22f5caf..a342b26 100644 --- a/core/tavern-helper/main.js +++ b/core/tavern-helper/main.js @@ -1,173 +1,619 @@ -import { - world_names, - loadWorldInfo, - saveWorldInfo, - createNewWorldInfo, - createWorldInfoEntry, - reloadEditor -} from "/scripts/world-info.js"; -import { characters, eventSource, event_types } from "/script.js"; -import { getContext } from "/scripts/extensions.js"; -import { executeSlashCommandsWithOptions } from '/scripts/slash-commands.js'; - - -class AmilyHelper { - - async getLorebooks() { - return [...world_names]; - } - - async getCharLorebooks(options = { type: 'all' }) { - try { - const context = getContext(); - if (!context || !context.characterId) { - console.warn('[Amily助手] 无法获取当前角色上下文。'); - return { primary: null, additional: [] }; - } - const character = characters[context.characterId]; - const primary = character?.data?.extensions?.world; - return { primary: primary || null, additional: [] }; - } catch (error) { - console.error('[Amily助手] 获取角色世界书时出错:', error); - return { primary: null, additional: [] }; - } - } - - async getLorebookEntries(bookName) { - try { - const bookData = await loadWorldInfo(bookName); - if (!bookData || !bookData.entries) { - return []; - } - const positionMap = { 0: 'before_character_definition', 1: 'after_character_definition', 2: 'before_author_note', 3: 'after_author_note', 4: 'at_depth_as_system' }; - return Object.entries(bookData.entries).map(([uid, entry]) => ({ - uid: parseInt(uid), - comment: entry.comment || '无标题条目', - content: entry.content || '', - key: entry.key || [], - keys: entry.key || [], - enabled: !entry.disable, - constant: entry.constant || false, - position: positionMap[entry.position] || 'at_depth_as_system', - depth: entry.depth || 998, - })); - } catch (error) { - console.error(`[Amily助手] 获取世界书《${bookName}》条目时出错:`, error); - return []; - } - } - - async setLorebookEntries(bookName, entries) { - try { - const bookData = await loadWorldInfo(bookName); - if (!bookData) { - console.error(`[Amily助手] 更新失败:找不到世界书《${bookName}》。`); - return false; - } - for (const entryUpdate of entries) { - const existingEntry = bookData.entries[entryUpdate.uid]; - if (existingEntry) { - if (entryUpdate.content !== undefined) existingEntry.content = entryUpdate.content; - if (entryUpdate.enabled !== undefined) existingEntry.disable = !entryUpdate.enabled; - if (entryUpdate.comment !== undefined) existingEntry.comment = entryUpdate.comment; - if (entryUpdate.key !== undefined) existingEntry.key = entryUpdate.key; - if (entryUpdate.keys !== undefined) existingEntry.key = entryUpdate.keys; - if (entryUpdate.constant !== undefined) existingEntry.constant = entryUpdate.constant; - if (entryUpdate.type === 'constant') existingEntry.constant = true; - if (entryUpdate.type === 'selective') existingEntry.constant = false; - if (entryUpdate.position !== undefined) { - const positionMap = { 'before_character_definition': 0, 'after_character_definition': 1, 'before_author_note': 2, 'after_author_note': 3, 'at_depth': 4, 'at_depth_as_system': 4 }; - existingEntry.position = positionMap[entryUpdate.position] ?? 4; - } - if (entryUpdate.depth !== undefined) existingEntry.depth = entryUpdate.depth; - } - } - await saveWorldInfo(bookName, bookData, true); - reloadEditor(bookName); // 刷新编辑器 - eventSource.emit(event_types.WORLD_INFO_UPDATED, bookName); - return true; - } catch (error) { - console.error(`[Amily助手] 更新世界书《${bookName}》条目时出错:`, error); - return false; - } - } - - async createLorebookEntries(bookName, entries) { - try { - let bookData = await loadWorldInfo(bookName); - if (!bookData) { - console.warn(`[Amily助手] 世界书《${bookName}》不存在,将自动创建。`); - await this.createLorebook(bookName); - bookData = await loadWorldInfo(bookName); - if (!bookData) { - throw new Error(`创建并加载世界书《${bookName}》失败。`); - } - } - - for (const newEntryData of entries) { - const newEntry = createWorldInfoEntry(bookName, bookData); - const positionMap = { 'before_character_definition': 0, 'after_character_definition': 1, 'before_author_note': 2, 'after_author_note': 3, 'at_depth': 4, 'at_depth_as_system': 4 }; - Object.assign(newEntry, { - comment: newEntryData.comment || '新条目', - content: newEntryData.content || '', - key: newEntryData.keys || newEntryData.key || [], - constant: newEntryData.type === 'constant' ? true : (newEntryData.constant || false), - position: typeof newEntryData.position === 'string' ? (positionMap[newEntryData.position] ?? 4) : (newEntryData.position ?? 4), - depth: newEntryData.depth ?? 998, - disable: !(newEntryData.enabled ?? true), - }); - if (newEntryData.type === 'selective') newEntry.constant = false; - } - await saveWorldInfo(bookName, bookData, true); - reloadEditor(bookName); // 刷新编辑器 - return true; - } catch (error) { - console.error(`[Amily助手] 在世界书《${bookName}》中创建新条目时出错:`, error); - return false; - } - } - - async createLorebook(bookName) { - try { - if (world_names.includes(bookName)) { - console.warn(`[Amily助手] 创建失败:世界书《${bookName}》已存在。`); - return false; - } - await createNewWorldInfo(bookName); - if (!world_names.includes(bookName)) { - world_names.push(bookName); - world_names.sort(); - } - // 派发一个自定义事件,通知UI更新 - document.dispatchEvent(new CustomEvent('amily-lorebook-created', { detail: { bookName } })); - return true; - } catch (error) { - console.error(`[Amily助手] 创建世界书《${bookName}》时出错:`, error); - return false; - } - } - - async triggerSlash(command) { - try { - console.log(`[Amily助手] 正在执行斜杠命令: ${command}`); - const result = await executeSlashCommandsWithOptions(command); - if (result.isError) { - throw new Error(result.errorMessage); - } - return result.pipe; - } catch (error) { - console.error(`[Amily助手] 执行斜杠命令 '${command}' 时出错:`, error); - throw error; - } - } - - async loadWorldInfo(bookName) { - return await loadWorldInfo(bookName); - } - - async saveWorldInfo(bookName, data, isWorldInfo) { - await saveWorldInfo(bookName, data, isWorldInfo); - } -} - -export const amilyHelper = new AmilyHelper(); +import { + world_names, + loadWorldInfo, + saveWorldInfo, + createNewWorldInfo, + createWorldInfoEntry, + reloadEditor +} from "/scripts/world-info.js"; +import { + characters, + eventSource, + event_types, + chat, + reloadCurrentChat, + saveChatConditional, + name1, + name2, + addOneMessage, + messageFormatting, + substituteParamsExtended +} from "/script.js"; +import { getContext } from "/scripts/extensions.js"; +import { executeSlashCommandsWithOptions } from '/scripts/slash-commands.js'; + + +class AmilyHelper { + + // ==================== Chat Message 相关方法 ==================== + + getChatMessages(range, options = {}) { + const { role = 'all', hide_state = 'all', include_swipes = false, include_swipe = false } = options; + const includeSwipes = include_swipes || include_swipe; + + if (!chat || !Array.isArray(chat)) { + throw new Error('聊天数组不可用'); + } + + let start, end; + const rangeStr = String(range); + + if (rangeStr.match(/^(-?\d+)$/)) { + const value = Number(rangeStr); + start = end = value < 0 ? chat.length + value : value; + } else { + const match = rangeStr.match(/^(-?\d+)-(-?\d+)$/); + if (!match) { + throw new Error(`无效的消息范围: ${range}`); + } + const [, s, e] = match; + const startVal = Number(s) < 0 ? chat.length + Number(s) : Number(s); + const endVal = Number(e) < 0 ? chat.length + Number(e) : Number(e); + start = Math.min(startVal, endVal); + end = Math.max(startVal, endVal); + } + + if (start < 0 || end >= chat.length || start > end) { + throw new Error(`消息范围超出界限: ${range}`); + } + + const getRole = (msg) => { + if (msg.is_system) return 'system'; + return msg.is_user ? 'user' : 'assistant'; + }; + + const messages = []; + for (let i = start; i <= end; i++) { + const msg = chat[i]; + if (!msg) continue; + + const msgRole = getRole(msg); + + if (role !== 'all' && msgRole !== role) continue; + + if (hide_state !== 'all') { + if ((hide_state === 'hidden') !== msg.is_system) continue; + } + + const swipe_id = msg.swipe_id ?? 0; + const swipes = msg.swipes ?? [msg.mes]; + const swipes_data = msg.variables ?? [{}]; + const swipes_info = msg.swipes_info ?? [msg.extra ?? {}]; + + if (includeSwipes) { + messages.push({ + message_id: i, + name: msg.name, + role: msgRole, + is_hidden: msg.is_system, + swipe_id: swipe_id, + swipes: swipes, + swipes_data: swipes_data, + swipes_info: swipes_info + }); + } else { + messages.push({ + message_id: i, + name: msg.name, + role: msgRole, + is_hidden: msg.is_system, + message: msg.mes, + data: swipes_data[swipe_id], + extra: swipes_info[swipe_id] + }); + } + } + + return messages; + } + + async setChatMessages(chat_messages, options = {}) { + const { refresh = 'affected' } = options; + + if (!Array.isArray(chat_messages)) { + throw new Error('chat_messages 必须是数组'); + } + + for (const chatMsg of chat_messages) { + const msg = chat[chatMsg.message_id]; + if (!msg) continue; + + if (chatMsg.name !== undefined) msg.name = chatMsg.name; + if (chatMsg.role !== undefined) msg.is_user = chatMsg.role === 'user'; + if (chatMsg.is_hidden !== undefined) msg.is_system = chatMsg.is_hidden; + + if (chatMsg.message !== undefined) { + msg.mes = chatMsg.message; + if (msg.swipes) { + msg.swipes[msg.swipe_id ?? 0] = chatMsg.message; + } + } + + if (chatMsg.data !== undefined) { + if (!msg.variables) { + msg.variables = Array(msg.swipes?.length ?? 1).fill({}); + } + msg.variables[msg.swipe_id ?? 0] = chatMsg.data; + } + + if (chatMsg.extra !== undefined) { + if (!msg.swipes_info) { + msg.swipes_info = Array(msg.swipes?.length ?? 1).fill({}); + } + msg.extra = chatMsg.extra; + msg.swipes_info[msg.swipe_id ?? 0] = chatMsg.extra; + } + } + + await saveChatConditional(); + + if (refresh === 'all') { + await reloadCurrentChat(); + } else if (refresh === 'affected') { + for (const chatMsg of chat_messages) { + const $mes = $(`div.mes[mesid="${chatMsg.message_id}"]`); + if ($mes.length) { + const msg = chat[chatMsg.message_id]; + $mes.find('.mes_text').empty().append( + messageFormatting(msg.mes, msg.name, msg.is_system, msg.is_user, chatMsg.message_id) + ); + } + } + } + + console.log(`[Amily助手] 已修改消息: ${chat_messages.map(m => m.message_id).join(', ')}`); + } + + + async setChatMessage(field_values, message_id, { + swipe_id = 'current', + refresh = 'display_and_render_current' + } = {}) { + field_values = typeof field_values === 'string' ? { message: field_values } : field_values; + + if (typeof swipe_id !== 'number' && swipe_id !== 'current') { + throw new Error(`提供的 swipe_id 无效, 请提供 'current' 或序号, 你提供的是: ${swipe_id}`); + } + if (!['none', 'display_current', 'display_and_render_current', 'all'].includes(refresh)) { + throw new Error( + `提供的 refresh 无效, 请提供 'none', 'display_current', 'display_and_render_current' 或 'all', 你提供的是: ${refresh}` + ); + } + + const chat_message = chat[message_id]; + if (!chat_message) { + console.warn(`[Amily助手] 未找到第 ${message_id} 楼的消息`); + return; + } + + const add_swipes_if_required = () => { + if (swipe_id === 'current') { + return false; + } + + if (swipe_id == 0 || (chat_message.swipes && swipe_id < chat_message.swipes.length)) { + return true; + } + + if (!chat_message.swipes) { + chat_message.swipe_id = 0; + chat_message.swipes = [chat_message.mes]; + chat_message.variables = [{}]; + } + for (let i = chat_message.swipes.length; i <= swipe_id; ++i) { + chat_message.swipes.push(''); + chat_message.variables.push({}); + } + return true; + }; + + const swipe_id_previous_index = chat_message.swipe_id ?? 0; + const swipe_id_to_set_index = swipe_id == 'current' ? swipe_id_previous_index : swipe_id; + const swipe_id_to_use_index = refresh != 'none' ? swipe_id_to_set_index : swipe_id_previous_index; + const message = field_values.message ?? + (chat_message.swipes ? chat_message.swipes[swipe_id_to_set_index] : undefined) ?? + chat_message.mes; + + const update_chat_message = () => { + const message_demacroed = substituteParamsExtended(message); + + if (field_values.data) { + if (!chat_message.variables) { + chat_message.variables = []; + } + chat_message.variables[swipe_id_to_set_index] = field_values.data; + } + + if (chat_message.swipes) { + chat_message.swipes[swipe_id_to_set_index] = message_demacroed; + chat_message.swipe_id = swipe_id_to_use_index; + } + + if (swipe_id_to_use_index === swipe_id_to_set_index) { + chat_message.mes = message_demacroed; + } + }; + + const update_partial_html = async (should_update_swipe) => { + const mes_html = $(`div.mes[mesid="${message_id}"]`); + if (!mes_html.length) { + return; + } + + if (should_update_swipe) { + mes_html.find('.swipes-counter').text(`${swipe_id_to_use_index + 1}\u200b/\u200b${chat_message.swipes.length}`); + } + + if (refresh != 'none') { + mes_html + .find('.mes_text') + .empty() + .append( + messageFormatting(message, chat_message.name, chat_message.is_system, chat_message.is_user, message_id) + ); + if (refresh === 'display_and_render_current') { + await eventSource.emit( + chat_message.is_user ? event_types.USER_MESSAGE_RENDERED : event_types.CHARACTER_MESSAGE_RENDERED, + message_id + ); + } + } + }; + + const should_update_swipe = add_swipes_if_required(); + update_chat_message(); + await saveChatConditional(); + + if (refresh == 'all') { + await reloadCurrentChat(); + } else { + await update_partial_html(should_update_swipe); + } + + console.log( + `[Amily助手] 设置第 ${message_id} 楼消息, 选项: ${JSON.stringify({ + swipe_id, + refresh, + })}, 设置前使用的消息页: ${swipe_id_previous_index}, 设置的消息页: ${swipe_id_to_set_index}, 现在使用的消息页: ${swipe_id_to_use_index}` + ); + } + + + async createChatMessages(chat_messages, options = {}) { + const { insert_at = 'end', refresh = 'all' } = options; + + let insertIndex = insert_at; + if (insert_at !== 'end') { + insertIndex = insert_at < 0 ? chat.length + insert_at : insert_at; + if (insertIndex < 0 || insertIndex > chat.length) { + throw new Error(`无效的插入位置: ${insert_at}`); + } + } + + const newMessages = chat_messages.map(msg => ({ + name: msg.name ?? (msg.role === 'user' ? name1 : name2), + is_user: msg.role === 'user', + is_system: msg.is_hidden ?? false, + mes: msg.message, + variables: [msg.data ?? {}] + })); + + if (insertIndex === 'end') { + chat.push(...newMessages); + } else { + chat.splice(insertIndex, 0, ...newMessages); + } + + await saveChatConditional(); + + if (refresh === 'affected' && insertIndex === 'end') { + newMessages.forEach(msg => addOneMessage(msg)); + } else if (refresh === 'all') { + await reloadCurrentChat(); + } + + console.log(`[Amily助手] 已创建 ${chat_messages.length} 条消息`); + } + + async deleteChatMessages(message_ids, options = {}) { + const { refresh = 'all' } = options; + + const validIds = message_ids + .map(id => id < 0 ? chat.length + id : id) + .filter(id => id >= 0 && id < chat.length) + .sort((a, b) => b - a); // 从后往前删除 + + for (const id of validIds) { + chat.splice(id, 1); + } + + await saveChatConditional(); + + if (refresh === 'all') { + await reloadCurrentChat(); + } + + console.log(`[Amily助手] 已删除消息: ${validIds.join(', ')}`); + } + + async getLorebooks() { + return [...world_names]; + } + + async getCharLorebooks(options = { type: 'all' }) { + try { + const context = getContext(); + if (!context || context.characterId === undefined) { + console.warn('[Amily助手] 无法获取当前角色上下文'); + return { primary: null, additional: [] }; + } + const character = characters[context.characterId]; + const primary = character?.data?.extensions?.world; + return { primary: primary || null, additional: [] }; + } catch (error) { + console.error('[Amily助手] 获取角色世界书时出错:', error); + return { primary: null, additional: [] }; + } + } + + async getLorebookEntries(bookName) { + try { + const bookData = await loadWorldInfo(bookName); + if (!bookData || !bookData.entries) { + return []; + } + const positionMap = { + 0: 'before_character_definition', + 1: 'after_character_definition', + 2: 'before_author_note', + 3: 'after_author_note', + 4: 'at_depth_as_system' + }; + return Object.entries(bookData.entries).map(([uid, entry]) => ({ + uid: parseInt(uid), + comment: entry.comment || '无标题条目', + content: entry.content || '', + key: entry.key || [], + keys: entry.key || [], + enabled: !entry.disable, + constant: entry.constant || false, + position: positionMap[entry.position] || 'at_depth_as_system', + depth: entry.depth || 998, + })); + } catch (error) { + console.error(`[Amily助手] 获取世界书《${bookName}》条目时出错:`, error); + return []; + } + } + + async setLorebookEntries(bookName, entries) { + try { + const bookData = await loadWorldInfo(bookName); + if (!bookData) { + console.error(`[Amily助手] 更新失败:找不到世界书《${bookName}》`); + return false; + } + for (const entryUpdate of entries) { + const existingEntry = bookData.entries[entryUpdate.uid]; + if (existingEntry) { + if (entryUpdate.content !== undefined) existingEntry.content = entryUpdate.content; + if (entryUpdate.enabled !== undefined) existingEntry.disable = !entryUpdate.enabled; + if (entryUpdate.comment !== undefined) existingEntry.comment = entryUpdate.comment; + if (entryUpdate.key !== undefined) existingEntry.key = entryUpdate.key; + if (entryUpdate.keys !== undefined) existingEntry.key = entryUpdate.keys; + if (entryUpdate.constant !== undefined) existingEntry.constant = entryUpdate.constant; + if (entryUpdate.type === 'constant') existingEntry.constant = true; + if (entryUpdate.type === 'selective') existingEntry.constant = false; + if (entryUpdate.position !== undefined) { + const positionMap = { + 'before_character_definition': 0, + 'after_character_definition': 1, + 'before_author_note': 2, + 'after_author_note': 3, + 'at_depth': 4, + 'at_depth_as_system': 4 + }; + existingEntry.position = positionMap[entryUpdate.position] ?? 4; + } + if (entryUpdate.depth !== undefined) existingEntry.depth = entryUpdate.depth; + } + } + await saveWorldInfo(bookName, bookData, true); + reloadEditor(bookName); + eventSource.emit(event_types.WORLD_INFO_UPDATED, bookName); + return true; + } catch (error) { + console.error(`[Amily助手] 更新世界书《${bookName}》条目时出错:`, error); + return false; + } + } + + async createLorebookEntries(bookName, entries) { + try { + let bookData = await loadWorldInfo(bookName); + if (!bookData) { + console.warn(`[Amily助手] 世界书《${bookName}》不存在,将自动创建`); + await this.createLorebook(bookName); + bookData = await loadWorldInfo(bookName); + if (!bookData) { + throw new Error(`创建并加载世界书《${bookName}》失败`); + } + } + + for (const newEntryData of entries) { + const newEntry = createWorldInfoEntry(bookName, bookData); + const positionMap = { + 'before_character_definition': 0, + 'after_character_definition': 1, + 'before_author_note': 2, + 'after_author_note': 3, + 'at_depth': 4, + 'at_depth_as_system': 4 + }; + Object.assign(newEntry, { + comment: newEntryData.comment || '新条目', + content: newEntryData.content || '', + key: newEntryData.keys || newEntryData.key || [], + constant: newEntryData.type === 'constant' ? true : (newEntryData.constant || false), + position: typeof newEntryData.position === 'string' ? (positionMap[newEntryData.position] ?? 4) : (newEntryData.position ?? 4), + depth: newEntryData.depth ?? 998, + disable: !(newEntryData.enabled ?? true), + }); + if (newEntryData.type === 'selective') newEntry.constant = false; + } + await saveWorldInfo(bookName, bookData, true); + reloadEditor(bookName); + return true; + } catch (error) { + console.error(`[Amily助手] 在世界书《${bookName}》中创建新条目时出错:`, error); + return false; + } + } + + async createLorebook(bookName) { + try { + if (world_names.includes(bookName)) { + console.warn(`[Amily助手] 创建失败:世界书《${bookName}》已存在`); + return false; + } + await createNewWorldInfo(bookName); + if (!world_names.includes(bookName)) { + world_names.push(bookName); + world_names.sort(); + } + document.dispatchEvent(new CustomEvent('amily-lorebook-created', { detail: { bookName } })); + return true; + } catch (error) { + console.error(`[Amily助手] 创建世界书《${bookName}》时出错:`, error); + return false; + } + } + + // ==================== 斜杠命令相关 ==================== + + async triggerSlash(command) { + try { + console.log(`[Amily助手] 正在执行斜杠命令: ${command}`); + const result = await executeSlashCommandsWithOptions(command); + if (result.isError) { + throw new Error(result.errorMessage); + } + return result.pipe; + } catch (error) { + console.error(`[Amily助手] 执行斜杠命令 '${command}' 时出错:`, error); + throw error; + } + } + + // ==================== 工具方法 ==================== + + async loadWorldInfo(bookName) { + return await loadWorldInfo(bookName); + } + + async saveWorldInfo(bookName, data, isWorldInfo) { + await saveWorldInfo(bookName, data, isWorldInfo); + } + + getLastMessageId() { + return chat.length - 1; + } +} + +export const amilyHelper = new AmilyHelper(); + + +export function initializeAmilyHelper() { + if (!window.AmilyHelper) { + window.AmilyHelper = amilyHelper; + console.log('[Amily2] AmilyHelper 已成功初始化并附加到 window 对象'); + } +} + +// ==================== iframe 通信 API ==================== + + +export function makeRequest(request, data) { + return new Promise((resolve, reject) => { + const uid = Date.now() + Math.random(); + const callbackRequest = `${request}_callback`; + + function handleMessage(event) { + const msgData = event.data || {}; + if (msgData.request === callbackRequest && msgData.uid === uid) { + window.removeEventListener('message', handleMessage); + if (msgData.error) { + reject(new Error(msgData.error)); + } else { + resolve(msgData.result); + } + } + } + + window.addEventListener('message', handleMessage); + + setTimeout(() => { + window.removeEventListener('message', handleMessage); + reject(new Error(`请求 '${request}' 超时 (30秒)`)); + }, 30000); + + window.parent.postMessage({ + source: 'amily2-iframe-request', + request: request, + uid: uid, + data: data + }, '*'); + }); +} + +// ==================== 主窗口 API ==================== + +const apiHandlers = new Map(); + + +export function registerApiHandler(request, handler) { + if (apiHandlers.has(request)) { + console.warn(`[Amily2-IframeAPI] 覆盖请求处理器: ${request}`); + } + apiHandlers.set(request, handler); +} + + +export function initializeApiListener() { + window.addEventListener('message', async (event) => { + const data = event.data || {}; + if (data.source !== 'amily2-iframe-request' || !data.request || data.uid === undefined) { + return; + } + + const handler = apiHandlers.get(data.request); + const callbackRequest = `${data.request}_callback`; + + if (!handler) { + console.error(`[Amily2-IframeAPI] 收到未知请求: ${data.request}`); + event.source.postMessage({ + request: callbackRequest, + uid: data.uid, + error: `未注册请求 '${data.request}' 的处理器` + }, '*'); + return; + } + + try { + const result = await handler(data.data, event); + event.source.postMessage({ + request: callbackRequest, + uid: data.uid, + result: result + }, '*'); + } catch (error) { + console.error(`[Amily2-IframeAPI] 执行处理器 '${data.request}' 时出错:`, error); + event.source.postMessage({ + request: callbackRequest, + uid: data.uid, + error: error.message || String(error) + }, '*'); + } + }); + console.log('[Amily2-IframeAPI] 主窗口监听器已初始化'); +} diff --git a/core/tavern-helper/renderer-bindings.js b/core/tavern-helper/renderer-bindings.js new file mode 100644 index 0000000..02b1e50 --- /dev/null +++ b/core/tavern-helper/renderer-bindings.js @@ -0,0 +1,51 @@ +import { renderAllIframes, clearAllIframes, initializeRenderer } from './renderer.js'; +import { extension_settings } from "/scripts/extensions.js"; +import { extensionName } from "../../utils/settings.js"; +import { saveSettingsDebounced } from "/script.js"; + +let isRendererInitialized = false; + +export function initializeRendererBindings() { + const container = $("#amily2_drawer_content").length + ? $("#amily2_drawer_content") + : $("#amily2_chat_optimiser"); + + if (!container.length) { + console.warn("[Amily2-Renderer] Could not find the settings container."); + return; + } + container.on('change', '#render-enable-toggle', function() { + const isChecked = this.checked; + + if (!extension_settings[extensionName]) { + extension_settings[extensionName] = {}; + } + extension_settings[extensionName].render_enabled = isChecked; + saveSettingsDebounced(); + + if (isChecked && !isRendererInitialized) { + initializeRenderer(); + isRendererInitialized = true; + console.log("[Amily2-Renderer] Renderer has been initialized on-demand."); + } + + if (isChecked) { + renderAllIframes(); + } else { + clearAllIframes(); + } + }); + + container.on('change', '#render-depth', function() { + const depth = parseInt(this.value, 10); + if (!extension_settings[extensionName]) { + extension_settings[extensionName] = {}; + } + extension_settings[extensionName].render_depth = depth; + saveSettingsDebounced(); + + toastr.success(`渲染深度已保存为: ${depth}`); + }); + + console.log("[Amily2-Renderer] Renderer UI events have been successfully bound."); +} diff --git a/core/tavern-helper/renderer.html b/core/tavern-helper/renderer.html new file mode 100644 index 0000000..96540fa --- /dev/null +++ b/core/tavern-helper/renderer.html @@ -0,0 +1,21 @@ +
+ +
+
+
启用前端渲染
+
在聊天消息中渲染HTML内容。
+ +
+
+
渲染深度
+
设置要渲染的最新消息的数量。0表示无限制。
+ +
+
+

“想给温柔的人奏响一段温柔的小插曲。”

+

+ 当开启Amily前端渲染后,务必关闭酒馆助手的前端渲染,借鉴了酒馆助手的渲染和交互逻辑,实现了更加轻量级,渲染更快,降低卡顿。 +

+ 与酒馆助手的脚本、变量等功能,完全无冲突,可并存使用。 +

+
diff --git a/core/tavern-helper/renderer.js b/core/tavern-helper/renderer.js new file mode 100644 index 0000000..7b7bfa9 --- /dev/null +++ b/core/tavern-helper/renderer.js @@ -0,0 +1,601 @@ +import { eventSource, event_types } from '/script.js'; +import { extension_settings } from '/scripts/extensions.js'; +import { extensionName } from '../../utils/settings.js'; + +const settings = { + sandboxMode: false, + useBlob: false, + wrapperIframe: true, + renderEnabled: true +}; + +const winMap = new Map(); +let lastHeights = new WeakMap(); +const blobUrls = new WeakMap(); +const hashToBlobUrl = new Map(); +const blobLRU = []; +const BLOB_CACHE_LIMIT = 32; + +function generateUniqueId() { + return `amily2-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`; +} + +function shouldRenderContentByBlock(codeBlock) { + if (!codeBlock) return false; + const content = (codeBlock.textContent || '').trim(); + if (!content) return false; + return /^\s*>> 0).toString(16); +} + +function buildResourceHints(html) { + const urls = Array.from(new Set((html.match(/https?:\/\/[^"'()\s]+/gi) || []).map(u => { try { return new URL(u).origin } catch { return null } }).filter(Boolean))); + let hints = ""; + const maxHosts = 6; + for (let i = 0; i < Math.min(urls.length, maxHosts); i++) { + const origin = urls[i]; + hints += ``; + hints += ``; + } + let preload = ""; + const font = (html.match(/https?:\/\/[^"'()\s]+\.(?:woff2|woff|ttf|otf)/i) || [])[0]; + if (font) { + const type = font.endsWith(".woff2") ? "font/woff2" : font.endsWith(".woff") ? "font/woff" : font.endsWith(".ttf") ? "font/ttf" : "font/otf"; + preload += ``; + } + const css = (html.match(/https?:\/\/[^"'()\s]+\.css/i) || [])[0]; + if (css) { + preload += ``; + } + const img = (html.match(/https?:\/\/[^"'()\s]+\.(?:png|jpg|jpeg|webp|gif|svg)/i) || [])[0]; + if (img) { + preload += ``; + } + return hints + preload; +} + +function iframeClientScript() { + return ` +(function(){ + function measureVisibleHeight(){ + try{ + var doc = document; + var target = doc.querySelector('.calendar-wrapper') || doc.body; + if(!target) return 0; + var minTop = Infinity, maxBottom = 0; + var addRect = function(el){ + try{ + var r = el.getBoundingClientRect(); + if(r && r.height > 0){ + if(minTop > r.top) minTop = r.top; + if(maxBottom < r.bottom) maxBottom = r.bottom; + } + }catch(e){} + }; + addRect(target); + var children = target.children || []; + for(var i=0;i 0 ? Math.ceil(maxBottom - Math.min(minTop, 0)) : (target.scrollHeight || 0); + }catch(e){ + return (document.body && document.body.scrollHeight) || 0; + } + } function post(m){ try{ parent.postMessage(m,'*') }catch(e){} } + var rafPending=false, lastH=0; + var HYSTERESIS = 2; + function send(force){ + if(rafPending && !force) return; + rafPending = true; + requestAnimationFrame(function(){ + rafPending = false; + var h = measureVisibleHeight(); + if(force || Math.abs(h - lastH) >= HYSTERESIS){ + lastH = h; + post({height:h, force:!!force}); + } + }); + } + try{ send(true) }catch(e){} + document.addEventListener('DOMContentLoaded', function(){ send(true) }, {once:true}); + window.addEventListener('load', function(){ send(true) }, {once:true}); + try{ + if(document.fonts){ + document.fonts.ready.then(function(){ send(true) }).catch(function(){}); + if(document.fonts.addEventListener){ + document.fonts.addEventListener('loadingdone', function(){ send(true) }); + document.fonts.addEventListener('loadingerror', function(){ send(true) }); + } + } + }catch(e){} + ['transitionend','animationend'].forEach(function(evt){ + document.addEventListener(evt, function(){ send(false) }, {passive:true, capture:true}); + }); + try{ + var root = document.querySelector('.calendar-wrapper') || document.body || document.documentElement; + var ro = new ResizeObserver(function(){ send(false) }); + ro.observe(root); + }catch(e){ + try{ + var rootMO = document.querySelector('.calendar-wrapper') || document.body || document.documentElement; + new MutationObserver(function(){ send(false) }) + .observe(rootMO, {childList:true, subtree:true, attributes:true, characterData:true}); + }catch(e){} + window.addEventListener('resize', function(){ send(false) }, {passive:true}); + } + window.addEventListener('message', function(e){ + var d = e && e.data || {}; + if(d && d.type === 'probe') setTimeout(function(){ send(true) }, 10); + }); +})();`; +} + +function buildWrappedHtml(html) { + const origin = (typeof location !== 'undefined' && location.origin) ? location.origin : ''; + const baseTag = settings && settings.useBlob ? `` : ""; + const headHints = buildResourceHints(html); + const vhFix = ``; + + const apiScript = ` + + +`; + + const injectionBlock = ` +${baseTag} + +${headHints} +${vhFix} +${apiScript} +`; + + const isFullHtml = //i.test(html); + + if (isFullHtml) { + if (html.includes('')) { + return html.replace('', `${injectionBlock}`); + } else if (html.includes('${injectionBlock}${injectionBlock}${html}`; + } + + return ` + + + + + + +${injectionBlock} + +${html}`; +} + + +function getOrCreateWrapper(preEl) { + let wrapper = preEl.previousElementSibling; + if (!wrapper || !wrapper.classList.contains('amily2-iframe-wrapper')) { + wrapper = document.createElement('div'); + wrapper.className = 'amily2-iframe-wrapper'; + wrapper.style.cssText = 'margin:0;'; + preEl.parentNode.insertBefore(wrapper, preEl); + } + return wrapper; +} + +function registerIframeMapping(iframe, wrapper) { + const tryMap = () => { + try { + if (iframe && iframe.contentWindow) { + winMap.set(iframe.contentWindow, { iframe, wrapper }); + return true; + } + } catch (e) { } + return false; + }; + if (tryMap()) return; + let tries = 0; + const t = setInterval(() => { + tries++; + if (tryMap() || tries > 20) clearInterval(t); + }, 25); +} + +function handleIframeMessage(event) { + const data = event.data || {}; + let rec = winMap.get(event.source); + if (!rec || !rec.iframe) { + const iframes = document.querySelectorAll('iframe.amily2-iframe'); + for (const iframe of iframes) { + if (iframe.contentWindow === event.source) { + rec = { iframe, wrapper: iframe.parentElement }; + winMap.set(event.source, rec); + break; + } + } + } + if (rec && rec.iframe && typeof data.height === 'number') { + const next = Math.max(0, Number(data.height) || 0); + if (next < 1) return; + const prev = lastHeights.get(rec.iframe) || 0; + if (!data.force && Math.abs(next - prev) < 1) return; + lastHeights.set(rec.iframe, next); + requestAnimationFrame(() => { rec.iframe.style.height = `${next}px`; }); + } +} + +function setIframeBlobHTML(iframe, fullHTML, codeHash) { + const existing = hashToBlobUrl.get(codeHash); + if (existing) { + iframe.src = existing; + blobUrls.set(iframe, existing); + return; + } + const blob = new Blob([fullHTML], { type: 'text/html' }); + const url = URL.createObjectURL(blob); + iframe.src = url; + blobUrls.set(iframe, url); + hashToBlobUrl.set(codeHash, url); + blobLRU.push(codeHash); + while (blobLRU.length > BLOB_CACHE_LIMIT) { + const old = blobLRU.shift(); + const u = hashToBlobUrl.get(old); + hashToBlobUrl.delete(old); + try { URL.revokeObjectURL(u) } catch (e) { } + } +} + +function releaseIframeBlob(iframe) { + try { + const url = blobUrls.get(iframe); + if (url) URL.revokeObjectURL(url); + blobUrls.delete(iframe); + } catch (e) { } +} + +function renderHtmlInIframe(htmlContent, container, preElement) { + try { + const originalHash = djb2(htmlContent); + const iframe = document.createElement('iframe'); + iframe.id = generateUniqueId(); + iframe.className = 'amily2-iframe'; + iframe.style.cssText = 'width:100%;border:none;background:transparent;overflow:hidden;height:0;margin:0;padding:0;display:block;contain:layout paint style;will-change:height;min-height:50px'; + iframe.setAttribute('frameborder', '0'); + iframe.setAttribute('scrolling', 'no'); + iframe.loading = 'eager'; + if (settings.sandboxMode) { + iframe.setAttribute('sandbox', 'allow-scripts allow-modals'); + } else { + iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-modals allow-popups'); + } + const wrapper = getOrCreateWrapper(preElement); + wrapper.querySelectorAll('.amily2-iframe').forEach(old => { + try { old.src = 'about:blank'; } catch (e) { } + releaseIframeBlob(old); + old.remove(); + }); + const codeHash = djb2(htmlContent); + const full = buildWrappedHtml(htmlContent); + if (settings.useBlob) { + setIframeBlobHTML(iframe, full, codeHash); + } else { + iframe.srcdoc = full; + } + wrapper.appendChild(iframe); + preElement.classList.remove('xb-show'); + preElement.style.display = 'none'; + registerIframeMapping(iframe, wrapper); + try { iframe.contentWindow?.postMessage({ type: 'probe' }, '*'); } catch (e) { } + preElement.dataset.xbFinal = 'true'; + preElement.dataset.xbHash = originalHash; + return iframe; + } catch (err) { + return null; + } +} + +function processCodeBlocks(messageElement) { + if (extension_settings[extensionName].render_enabled === false) return; + try { + const codeBlocks = messageElement.querySelectorAll('pre > code'); + codeBlocks.forEach(codeBlock => { + const preElement = codeBlock.parentElement; + const should = shouldRenderContentByBlock(codeBlock); + const html = codeBlock.textContent || ''; + const hash = djb2(html); + const isFinal = preElement.dataset.xbFinal === 'true'; + const same = preElement.dataset.xbHash === hash; + if (isFinal && same) return; + if (should) { + renderHtmlInIframe(html, preElement.parentNode, preElement); + } else { + preElement.classList.add('xb-show'); + preElement.removeAttribute('data-xbfinal'); + preElement.removeAttribute('data-xbhash'); + preElement.style.display = ''; + } + preElement.dataset.xiaobaixBound = 'true'; + }); + } catch (err) { + console.error('[Amily2-Renderer] Error during processCodeBlocks:', err); + } +} + +function processMessageById(messageId) { + const messageElement = document.querySelector(`div.mes[mesid="${messageId}"] .mes_text`); + if (!messageElement) return; + processCodeBlocks(messageElement); +} + +export function initializeRenderer() { + const handleMessage = (data) => { + const messageId = typeof data === 'object' ? data.messageId : data; + if (messageId == null) return; + console.log('[Amily2-Renderer] 处理消息渲染:', messageId); + setTimeout(() => processMessageById(messageId), 50); + }; + + eventSource.on(event_types.MESSAGE_RECEIVED, handleMessage); + eventSource.on(event_types.MESSAGE_UPDATED, handleMessage); + eventSource.on(event_types.MESSAGE_SWIPED, handleMessage); + eventSource.on(event_types.MESSAGE_EDITED, handleMessage); + eventSource.on(event_types.USER_MESSAGE_RENDERED, handleMessage); + eventSource.on(event_types.CHARACTER_MESSAGE_RENDERED, handleMessage); + eventSource.on(event_types.IMPERSONATE_READY, handleMessage); + + eventSource.on(event_types.CHAT_CHANGED, () => { + console.log('[Amily2-Renderer] 聊天已切换,重新渲染所有 iframe'); + setTimeout(renderAllIframes, 100); + }); + + window.addEventListener('message', handleIframeMessage); + + console.log('[Amily2-Renderer] 渲染器已初始化,监听事件: MESSAGE_RECEIVED, MESSAGE_UPDATED, MESSAGE_SWIPED, MESSAGE_EDITED, USER_MESSAGE_RENDERED, CHARACTER_MESSAGE_RENDERED, IMPERSONATE_READY'); +} + +export function renderAllIframes() { + const messages = document.querySelectorAll('.mes'); + messages.forEach(message => { + const messageId = message.getAttribute('mesid'); + if (messageId) { + processMessageById(messageId); + } + }); +} + +export function clearAllIframes() { + const iframes = document.querySelectorAll('.amily2-iframe'); + iframes.forEach(iframe => { + const wrapper = iframe.parentElement; + if (wrapper && wrapper.classList.contains('amily2-iframe-wrapper')) { + const preElement = wrapper.nextElementSibling; + if (preElement && preElement.tagName === 'PRE') { + preElement.classList.add('xb-show'); + preElement.style.display = ''; + } + wrapper.remove(); + } + }); +} From e6f53b009c9227b18721c3ac31c290525d697596 Mon Sep 17 00:00:00 2001 From: Wx-2025 <351320169@qq.com> Date: Wed, 29 Oct 2025 21:24:35 +0800 Subject: [PATCH 20/49] Update bindings.js --- ui/bindings.js | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/ui/bindings.js b/ui/bindings.js index fb79a18..e413231 100644 --- a/ui/bindings.js +++ b/ui/bindings.js @@ -421,11 +421,33 @@ function bindAmily2ModalWorldBookSettings() { } export function bindModalEvents() { - initializePlotOptimizationBindings(); bindAmily2ModalWorldBookSettings(); const container = $("#amily2_drawer_content").length ? $("#amily2_drawer_content") : $("#amily2_chat_optimiser"); + + // Collapsible sections logic + container.on('click', '.collapsible-legend', function() { + const legend = $(this); + const content = legend.siblings('.collapsible-content'); + const icon = legend.find('.collapse-icon'); + + content.slideToggle(200, function() { + const isCollapsed = !content.is(':visible'); + if (isCollapsed) { + icon.removeClass('fa-chevron-up').addClass('fa-chevron-down'); + } else { + icon.removeClass('fa-chevron-down').addClass('fa-chevron-up'); + } + + const sectionId = legend.text().trim(); + if (!extension_settings[extensionName]) { + extension_settings[extensionName] = {}; + } + extension_settings[extensionName][`collapsible_${sectionId}_collapsed`] = isCollapsed; + saveSettingsDebounced(); + }); + }); displayDailyAuthCode(); function updateModelInputView() { @@ -662,7 +684,7 @@ export function bindModalEvents() { container .off("click.amily2.chamber_nav") .on("click.amily2.chamber_nav", - "#amily2_open_plot_optimization, #amily2_open_additional_features, #amily2_open_rag_palace, #amily2_open_memorisation_forms, #amily2_open_character_world_book, #amily2_open_world_editor, #amily2_open_glossary, #amily2_back_to_main_settings, #amily2_back_to_main_from_hanlinyuan, #amily2_back_to_main_from_forms, #amily2_back_to_main_from_optimization, #amily2_back_to_main_from_cwb, #amily2_back_to_main_from_world_editor, #amily2_back_to_main_from_glossary", function () { + "#amily2_open_plot_optimization, #amily2_open_additional_features, #amily2_open_rag_palace, #amily2_open_memorisation_forms, #amily2_open_character_world_book, #amily2_open_world_editor, #amily2_open_glossary, #amily2_open_renderer, #amily2_back_to_main_settings, #amily2_back_to_main_from_hanlinyuan, #amily2_back_to_main_from_forms, #amily2_back_to_main_from_optimization, #amily2_back_to_main_from_cwb, #amily2_back_to_main_from_world_editor, #amily2_back_to_main_from_glossary, #amily2_renderer_back_button", function () { if (!pluginAuthStatus.authorized) return; const mainPanel = container.find('.plugin-features'); @@ -673,6 +695,7 @@ container const characterWorldBookPanel = container.find('#amily2_character_world_book_panel'); const worldEditorPanel = container.find('#amily2_world_editor_panel'); const glossaryPanel = container.find('#amily2_glossary_panel'); + const rendererPanel = container.find('#amily2_renderer_panel'); mainPanel.hide(); additionalPanel.hide(); @@ -682,8 +705,12 @@ container characterWorldBookPanel.hide(); worldEditorPanel.hide(); glossaryPanel.hide(); + rendererPanel.hide(); switch (this.id) { + case 'amily2_open_renderer': + rendererPanel.show(); + break; case 'amily2_open_plot_optimization': plotOptimizationPanel.show(); break; @@ -712,6 +739,7 @@ container case 'amily2_back_to_main_from_cwb': case 'amily2_back_to_main_from_world_editor': case 'amily2_back_to_main_from_glossary': + case 'amily2_renderer_back_button': mainPanel.show(); break; } From bf5f88ce963d18a1578b72c6c340b65a5d99fff5 Mon Sep 17 00:00:00 2001 From: Wx-2025 <351320169@qq.com> Date: Wed, 29 Oct 2025 21:47:10 +0800 Subject: [PATCH 21/49] Update drawer.js --- ui/drawer.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ui/drawer.js b/ui/drawer.js index 0062795..163d344 100644 --- a/ui/drawer.js +++ b/ui/drawer.js @@ -19,6 +19,7 @@ import { bindHistoriographyEvents } from "./historiography-bindings.js"; import { bindHanlinyuanEvents } from "./hanlinyuan-bindings.js"; import { bindTableEvents } from './table-bindings.js'; import { showContentModal } from "./page-window.js"; +import { initializeRendererBindings } from "../core/tavern-helper/renderer-bindings.js"; const extensionFolderPath = `scripts/extensions/third-party/${extensionName}`; @@ -101,6 +102,10 @@ async function initializePanel(contentPanel, errorContainer) { const glossaryPanelHtml = ``; mainContainer.append(glossaryPanelHtml); + const rendererContent = await $.get(`${extensionFolderPath}/core/tavern-helper/renderer.html`); + const rendererPanelHtml = ``; + mainContainer.append(rendererPanelHtml); + // 在面板创建后,加载世界书编辑器脚本 const worldEditorScriptId = 'world-editor-script'; if (!document.getElementById(worldEditorScriptId)) { @@ -117,6 +122,7 @@ async function initializePanel(contentPanel, errorContainer) { await loadSettings(); bindHanlinyuanEvents(); bindTableEvents(); + initializeRendererBindings(); contentPanel.data("initialized", true); console.log("[Amily-重构] 宫殿模块已按蓝图竣工。"); applyUpdateIndicator(); From 0741c6f422f07c44bb65ba727dfbd0a7586d118d Mon Sep 17 00:00:00 2001 From: Wx-2025 <351320169@qq.com> Date: Wed, 29 Oct 2025 21:47:40 +0800 Subject: [PATCH 22/49] Update state.js --- ui/state.js | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/ui/state.js b/ui/state.js index 6c1f0ec..71471a7 100644 --- a/ui/state.js +++ b/ui/state.js @@ -148,9 +148,37 @@ export function updateUI() { if (settings.historiographySmallTriggerThreshold !== undefined) { $('#amily2_mhb_small_trigger_count').val(settings.historiographySmallTriggerThreshold); } - populateModelDropdown(); - updatePlotOptimizationUI(); + // 同步渲染器开关状态 + if (settings.render_enabled !== undefined) { + $('#render-enable-toggle').prop('checked', settings.render_enabled); } + + // 同步渲染深度设置 + if (settings.render_depth !== undefined) { + $('#render-depth').val(settings.render_depth); + } + + populateModelDropdown(); + updatePlotOptimizationUI(); + + // Restore collapsible sections state + $('.collapsible').each(function() { + const section = $(this); + const legend = section.find('.collapsible-legend'); + const content = section.find('.collapsible-content'); + const icon = legend.find('.collapse-icon'); + const sectionId = legend.text().trim(); + const isCollapsed = extension_settings[extensionName][`collapsible_${sectionId}_collapsed`] ?? true; + + if (isCollapsed) { + content.hide(); + icon.removeClass('fa-chevron-up').addClass('fa-chevron-down'); + } else { + content.show(); + icon.removeClass('fa-chevron-down').addClass('fa-chevron-up'); + } + }); +} } From b89b0a63b65fc6eae6ca72647bc406e35b2f6927 Mon Sep 17 00:00:00 2001 From: Wx-2025 <351320169@qq.com> Date: Wed, 29 Oct 2025 22:12:52 +0800 Subject: [PATCH 23/49] Update config.js --- PresetSettings/config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/PresetSettings/config.js b/PresetSettings/config.js index f98a0bd..2a3a8fb 100644 --- a/PresetSettings/config.js +++ b/PresetSettings/config.js @@ -542,3 +542,4 @@ export const sectionTitles = { cwb_summarizer_incremental: '角色世界书(CWB-增量)', novel_processor: '小说处理', }; + From 632ff9c310f77f968ff71ee20fe5664cfa6a8167 Mon Sep 17 00:00:00 2001 From: Wx-2025 <351320169@qq.com> Date: Wed, 29 Oct 2025 22:13:38 +0800 Subject: [PATCH 24/49] Update cwb_config.js --- CharacterWorldBook/src/cwb_config.js | 1 - 1 file changed, 1 deletion(-) diff --git a/CharacterWorldBook/src/cwb_config.js b/CharacterWorldBook/src/cwb_config.js index 81ce8df..322330a 100644 --- a/CharacterWorldBook/src/cwb_config.js +++ b/CharacterWorldBook/src/cwb_config.js @@ -207,7 +207,6 @@ export const cwbDefaultSettings = { cwb_tavern_profile: '', cwb_break_armor_prompt: cwbCompleteDefaultSettings.cwb_break_armor_prompt, cwb_char_card_prompt: cwbCompleteDefaultSettings.cwb_char_card_prompt, - cwb_incremental_char_card_prompt: cwbCompleteDefaultSettings.cwb_incremental_char_card_prompt, cwb_prompt_version: '1.0.2', cwb_auto_update_threshold: 20, cwb_auto_update_enabled: false, From 74a28c50592854271c791d4b8402a91f6f96b5c2 Mon Sep 17 00:00:00 2001 From: Wx-2025 <351320169@qq.com> Date: Wed, 29 Oct 2025 22:14:04 +0800 Subject: [PATCH 25/49] Update cwb_core.js --- CharacterWorldBook/src/cwb_core.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/CharacterWorldBook/src/cwb_core.js b/CharacterWorldBook/src/cwb_core.js index f63bf31..ff4e533 100644 --- a/CharacterWorldBook/src/cwb_core.js +++ b/CharacterWorldBook/src/cwb_core.js @@ -235,11 +235,6 @@ async function proceedWithCardUpdate($panel, messagesToUse) { messages.push({ role: "system", content: state.currentCharCardPrompt }); } break; - case 'cwb_incremental_char_card_prompt': - if (state.isIncrementalUpdateEnabled && state.currentIncrementalCharCardPrompt) { - messages.push({ role: "system", content: state.currentIncrementalCharCardPrompt }); - } - break; case 'oldFiles': if (state.isIncrementalUpdateEnabled) { let oldFilesContent = "【旧档案】\n"; From 36b003faef2b499940015898a12cc921dbfcfcd1 Mon Sep 17 00:00:00 2001 From: Wx-2025 <351320169@qq.com> Date: Wed, 29 Oct 2025 22:14:59 +0800 Subject: [PATCH 26/49] Update index.js --- index.js | 157 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 150 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index 4d050f6..14613b6 100644 --- a/index.js +++ b/index.js @@ -25,7 +25,8 @@ import { cwbDefaultSettings } from './CharacterWorldBook/src/cwb_config.js'; import { bindGlossaryEvents } from './glossary/GT_bindings.js'; import './core/amily2-updater.js'; import { updateOrInsertTableInChat, startContinuousRendering, stopContinuousRendering } from './ui/message-table-renderer.js'; -import { isTavernHelperAvailable } from './core/tavernhelper-compatibility.js'; +import { initializeRenderer } from './core/tavern-helper/renderer.js'; +import { initializeApiListener, registerApiHandler, amilyHelper, initializeAmilyHelper } from './core/tavern-helper/main.js'; const STYLE_SETTINGS_KEY = 'amily2_custom_styles'; const STYLE_ROOT_SELECTOR = '#amily2_memorisation_forms_panel'; @@ -226,6 +227,7 @@ function loadPluginStyles() { loadStyleFile("amily2-glossary.css"); // 【新圣谕】为术语表披上其专属华服 loadStyleFile("table.css"); // 【第四道圣谕】为内存储司披上其专属华服 loadStyleFile("optimization.css"); // 【第五道圣谕】为剧情优化披上其专属华服 + loadStyleFile("renderer.css"); // 【新圣谕】为渲染器披上其专属华服 // 【第六道圣谕】为角色世界书披上其专属华服 const cwbStyleId = 'cwb-feature-style'; @@ -254,6 +256,55 @@ function loadPluginStyles() { } +window.addEventListener('message', function (event) { + // 处理头像获取请求 + if (event.data && event.data.type === 'getAvatars') { + const userAvatar = `/characters/${getContext().userCharacter.avatar}`; + const charAvatar = `/characters/${getContext().characters[this_chid].avatar}`; + event.source.postMessage({ + source: 'amily2-host', + type: 'avatars', + urls: { user: userAvatar, char: charAvatar } + }, '*'); + return; + } + + // 处理来自 iframe 的交互事件 + if (event.data && event.data.source === 'amily2-iframe') { + const { action, detail } = event.data; + console.log(`[Amily2-主窗口] 收到来自iframe的动作: ${action}`, detail); + + switch (action) { + case 'sendMessage': + if (detail && detail.message) { + $('#send_textarea').val(detail.message).trigger('input'); + $('#send_but').trigger('click'); + console.log(`[Amily2-主窗口] 已发送消息: ${detail.message}`); + } + break; + + case 'showToast': + if (detail && detail.message && window.toastr) { + const toastType = detail.type || 'info'; + if (typeof window.toastr[toastType] === 'function') { + window.toastr[toastType](detail.message, detail.title || '通知'); + } + } + break; + + case 'buttonClick': + console.log(`[Amily2-主窗口] 按钮被点击:`, detail); + if (window.toastr) { + window.toastr.info(`按钮 "${detail.buttonId || '未知'}" 被点击`, 'iframe交互'); + } + break; + + default: + console.warn(`[Amily2-主窗口] 未知的动作类型: ${action}`); + } + } +}); + window.addEventListener("error", (event) => { const stackTrace = event.error?.stack || ""; if (stackTrace.includes("ST-Amily2-Chat-Optimisation")) { @@ -264,12 +315,106 @@ window.addEventListener("error", (event) => { jQuery(async () => { + console.log("[Amily2号-帝国枢密院] 开始执行开国大典..."); + initializeApiListener(); + + registerApiHandler('getChatMessages', async (data) => { + return amilyHelper.getChatMessages(data.range, data.options); + }); + + registerApiHandler('setChatMessages', async (data) => { + return await amilyHelper.setChatMessages(data.messages, data.options); + }); + + registerApiHandler('setChatMessage', async (data) => { + const field_values = data.field_values || data.content; + const message_id = data.message_id !== undefined ? data.message_id : data.index; + const options = data.options || {}; + + console.log('[Amily2-API] setChatMessage 收到参数:', { field_values, message_id, options, raw_data: data }); + + return await amilyHelper.setChatMessage(field_values, message_id, options); + }); + + registerApiHandler('createChatMessages', async (data) => { + return await amilyHelper.createChatMessages(data.messages, data.options); + }); + + registerApiHandler('deleteChatMessages', async (data) => { + return await amilyHelper.deleteChatMessages(data.ids, data.options); + }); + + registerApiHandler('getLorebooks', async (data) => { + return await amilyHelper.getLorebooks(); + }); + + registerApiHandler('getCharLorebooks', async (data) => { + return await amilyHelper.getCharLorebooks(data.options); + }); + + registerApiHandler('getLorebookEntries', async (data) => { + return await amilyHelper.getLorebookEntries(data.bookName); + }); + + registerApiHandler('setLorebookEntries', async (data) => { + return await amilyHelper.setLorebookEntries(data.bookName, data.entries); + }); + + registerApiHandler('createLorebookEntries', async (data) => { + return await amilyHelper.createLorebookEntries(data.bookName, data.entries); + }); + + registerApiHandler('createLorebook', async (data) => { + return await amilyHelper.createLorebook(data.bookName); + }); + + registerApiHandler('triggerSlash', async (data) => { + return await amilyHelper.triggerSlash(data.command); + }); + + registerApiHandler('getLastMessageId', async (data) => { + return amilyHelper.getLastMessageId(); + }); + + registerApiHandler('toastr', async (data) => { + if (window.toastr && typeof window.toastr[data.type] === 'function') { + window.toastr[data.type](data.message, data.title); + } + return true; + }); + + registerApiHandler('switchSwipe', async (data) => { + const { messageIndex, swipeIndex } = data; + const messages = await amilyHelper.getChatMessages(messageIndex, { include_swipes: true }); + + if (messages && messages.length > 0 && messages[0].swipes) { + const content = messages[0].swipes[swipeIndex]; + if (content !== undefined) { + await amilyHelper.setChatMessages([{ + message_id: messageIndex, + message: content + }], { refresh: 'affected' }); + + const context = getContext(); + if (context.chat[messageIndex]) { + context.chat[messageIndex].swipe_id = swipeIndex; + } + + return { success: true, message: `已切换至开场白 ${swipeIndex}` }; + } + } + + throw new Error(`无法切换到开场白 ${swipeIndex}`); + }); + + initializeAmilyHelper(); + console.log("[Amily2号-帝国枢密院] 开始执行开国大典..."); if (!extension_settings[extensionName]) { extension_settings[extensionName] = {}; } - const combinedDefaultSettings = { ...defaultSettings, ...tableSystemDefaultSettings, ...cwbDefaultSettings, render_on_every_message: false }; + const combinedDefaultSettings = { ...defaultSettings, ...tableSystemDefaultSettings, ...cwbDefaultSettings, render_on_every_message: false, render_enabled: false }; for (const key in combinedDefaultSettings) { if (extension_settings[extensionName][key] === undefined) { @@ -298,7 +443,6 @@ jQuery(async () => { console.log("[Amily2号-开国大典] 步骤三:开始召唤府邸..."); createDrawer(); - // 【V15.0 修复】为术语表面板添加轮询加载,确保在面板渲染后再绑定事件 function waitForGlossaryPanelAndBindEvents() { let attempts = 0; const maxAttempts = 50; @@ -380,10 +524,8 @@ jQuery(async () => { let isProcessingPlotOptimization = false; async function onPlotGenerationAfterCommands(type, params, dryRun) { - // 【V15.2 新增】在发送消息后,清除所有表格的“已更新”高亮状态 clearUpdatedTables(); - // 【V15.3 修正】提交删除的逻辑已移至 injector.js,此处不再需要 console.log("[Amily2-剧情优化] Generation after commands triggered", { type, params, dryRun, isProcessing: isProcessingPlotOptimization }); @@ -515,7 +657,7 @@ jQuery(async () => { setTimeout(() => { log("【监察系统】检测到“朝代更迭”(CHAT_CHANGED),开始重修史书并刷新宫殿...", 'info'); clearHighlights(); - clearUpdatedTables(); // 【V15.2 新增】切换聊天时清除“已更新”高亮 + clearUpdatedTables(); loadTables(); renderTables(); @@ -555,7 +697,6 @@ jQuery(async () => { console.log('[Amily2-核心引擎] 开始执行统一注入 (聊天长度:', args[0]?.length || 0, ')'); try { - // 【V15.3 修正】由于 injectTableData 现在是异步的,需要 await await injectTableData(...args); } catch (error) { console.error('[Amily2-内存储司] 表格注入失败:', error); @@ -593,6 +734,8 @@ jQuery(async () => { handleUpdateCheck(); handleMessageBoard(); + initializeRenderer(); + if (extension_settings[extensionName].render_on_every_message) { startContinuousRendering(); } From 7518951900d7877bdcec0897875ea78607422c80 Mon Sep 17 00:00:00 2001 From: Wx-2025 <351320169@qq.com> Date: Wed, 29 Oct 2025 22:15:47 +0800 Subject: [PATCH 27/49] Update amily2-modal.html --- assets/amily2-modal.html | 96 +++++++++++++++++++++++++++++++--------- 1 file changed, 76 insertions(+), 20 deletions(-) diff --git a/assets/amily2-modal.html b/assets/amily2-modal.html index 42f4543..ee1ffd9 100644 --- a/assets/amily2-modal.html +++ b/assets/amily2-modal.html @@ -87,6 +87,48 @@ from { text-shadow: 0 0 5px rgba(255, 107, 107, 0.5); } to { text-shadow: 0 0 10px rgba(255, 107, 107, 0.8), 0 0 15px rgba(255, 107, 107, 0.3); } } + + .collapsible-legend { + cursor: pointer; + user-select: none; + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + } + .collapsible-legend:hover { + background-color: rgba(255, 255, 255, 0.05); + } + .collapse-icon { + transition: transform 0.2s ease-in-out; + } + .collapsible-content { + padding-top: 10px; + } + .disclaimer-box { + margin-top: 15px; + padding: 12px; + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 8px; + background-color: rgba(0, 0, 0, 0.1); + } + .disclaimer-emo { + font-style: italic; + color: #adb6e6; + text-align: center; + margin-bottom: 10px; + font-size: 13px; + } + .disclaimer-text { + font-size: 12px; + color: #c0c0c0; + line-height: 1.6; + } + .disclaimer-text strong { + color: #ffc107; + display: block; + margin-bottom: 5px; + }
@@ -123,9 +165,9 @@
记忆增强
- - - + + +
@@ -136,6 +178,7 @@ +
@@ -169,11 +212,18 @@
+
+

“我也想过琴棋书画诗酒花,奈何生活只有柴米油盐酱醋茶。”

+

+ 免责声明:本插件仅供个人学习与技术交流使用,严禁用于任何商业目的或非法活动。使用者需自行承担因使用本插件而产生的一切风险与法律责任,开发者对此不承担任何责任。 +

+
+
-
- 正文优化 - +
+ 正文优化 +
@@ -227,10 +277,12 @@
无感优化:直接替换文本,速度更快但要关流式,高楼层推荐。刷新优化:重载聊天界面,更加稳定无需关流式,低楼层推荐。
+
-
- API与模型配置 +
+ API与模型配置 +
+
-
- 统一提示词编辑器 - +
+ 统一提示词编辑器 +
@@ -314,11 +367,12 @@
+
-
- 世界书档案司 - +
+ 世界书档案司 +
+
总结与律法 @@ -413,11 +468,12 @@
-
- 界面定制 -
- -
+
+ 界面定制 +
+
+ +
@@ -454,8 +510,8 @@ + 选择一张图片作为背景。推荐使用小于5MB的图片。
- 选择一张图片作为背景。推荐使用小于5MB的图片。
From cac3fcdc84da59279737fe0d947c332935a112b2 Mon Sep 17 00:00:00 2001 From: Wx-2025 <351320169@qq.com> Date: Wed, 29 Oct 2025 22:16:24 +0800 Subject: [PATCH 28/49] Create renderer.css --- assets/renderer.css | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 assets/renderer.css diff --git a/assets/renderer.css b/assets/renderer.css new file mode 100644 index 0000000..95860ca --- /dev/null +++ b/assets/renderer.css @@ -0,0 +1,26 @@ +#tavern-helper-renderer { + padding: 10px; +} + +.amily2-renderer-info-container { + margin-top: 20px; + padding: 15px; + background-color: rgba(45, 45, 55, 0.5); + border-radius: 8px; + border: 1px solid rgba(255, 255, 255, 0.1); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); +} + +.emo-statement { + font-style: italic; + color: #d1c4e9; + text-align: center; + margin-bottom: 15px; + text-shadow: 0 0 5px rgba(209, 196, 233, 0.5); +} + +.description-text { + font-size: 14px; + color: #dddddd; + line-height: 1.6; +} From 9488afc7b25fc4945e86b231ce1051cef0ed5b34 Mon Sep 17 00:00:00 2001 From: Wx-2025 <351320169@qq.com> Date: Wed, 29 Oct 2025 22:17:01 +0800 Subject: [PATCH 29/49] Update Amily2-AdditionalFeatures.html --- assets/Amily2-AdditionalFeatures.html | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/assets/Amily2-AdditionalFeatures.html b/assets/Amily2-AdditionalFeatures.html index c0fea72..96c2ccf 100644 --- a/assets/Amily2-AdditionalFeatures.html +++ b/assets/Amily2-AdditionalFeatures.html @@ -184,12 +184,12 @@
- + -
@@ -220,12 +220,12 @@
- + \x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20×\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20
\x0a\x20\x20\x20\x20\x20\x20\x20\x20','已采集\x20','novel','rerank','getVectorCount','checked','[翰林院-枢纽]\x20加载书库列表失败:','3174224fiPGcE','fa-exclamation-triangle','400393eayTjI',')\x20已被删除','getChatId','processed','hly-kb-delete-btn','hly-rerank-model','\x20条消息,开始凝识...','会话已解锁,将跟随当前角色。','previousElementSibling','message','翰林院使用教程','未检测到预览文本,按标准流程采集消息...','hly-retrieval-enabled','val','\x20楼:\x20[','local_proxy','\x22>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20第\x20','\x22\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20data-send-date=\x22','解锁会话','scrollTop','every','change','hly-layer-end','processCondensation','hlyLog','[翰林院-枢纽]\x20未能获取SillyTavern上下文,绑定失败。','。进度已保存,可稍后重试。','embeddingModel','\x20个条目进行编纂...','\x22\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20data-is-user=\x22','is_user','[data-setting-key]','delete','hly-api-key',')\x22>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20添加新规则\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20
\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20','批量编纂任务已完成。','请输入您的Google\x20API\x20Key','toggleSessionLock','hly-hist-entry-multiselect-btn','find','startHLYHistoriography','当前角色','log-error','disabled','getGlobalKnowledgeBases','\x20条忆识。','\x22\x20placeholder=\x22开始字符,\x20如\x20\x22>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20×\x0a\x20\x20\x20\x20\x20\x20\x20\x20
\x0a\x20\x20\x20\x20',_0x4963e1=_0x5ebf60['map'](_0x60a6d7)[_0x20f51a(0x2c3)](''),_0x32d4d4='\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20在这里定义需要从提取内容中排除的文本片段。例如,排除HTML注释,可以设置开始字符为\x20``。

\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'+_0x4963e1+_0x20f51a(0x392);showHtmlModal('编辑内容排除规则',_0x32d4d4,{'okText':'保存规则','onOk':_0x44c19c=>{const _0xcf3c95=_0x20f51a,_0x6ee4f1=[];_0x44c19c[_0xcf3c95(0x397)](_0xcf3c95(0x2f2))['each'](function(){const _0x14c552=_0xcf3c95,_0x10f34d=$(this)[_0x14c552(0x397)](_0x14c552(0x23e))['eq'](0x0)['val']()[_0x14c552(0x322)](),_0x3154b9=$(this)[_0x14c552(0x397)](_0x14c552(0x23e))['eq'](0x1)[_0x14c552(0x378)]()[_0x14c552(0x322)]();_0x10f34d&&_0x3154b9&&_0x6ee4f1[_0x14c552(0x325)]({'start':_0x10f34d,'end':_0x3154b9});}),updateAndSaveSetting(_0xcf3c95(0x2c9),_0x6ee4f1),toastr[_0xcf3c95(0x1ea)](_0xcf3c95(0x2bf),_0xcf3c95(0x1dc));}});const _0x1bdae1=document[_0x20f51a(0x2c1)](_0x20f51a(0x327)),_0x4780c4=_0x1bdae1[_0x20f51a(0x1a8)](_0x20f51a(0x29b));_0x1bdae1[_0x20f51a(0x1a8)](_0x20f51a(0x270))['addEventListener'](_0x20f51a(0x273),()=>{const _0x5dc824=_0x20f51a,_0x41ff0e=_0x4780c4[_0x5dc824(0x2aa)][_0x5dc824(0x2b7)],_0x1712e0=_0x60a6d7({'start':'','end':''},_0x41ff0e);_0x4780c4['insertAdjacentHTML'](_0x5dc824(0x2a3),_0x1712e0);}),_0x4780c4['addEventListener']('click',_0x387845=>{const _0x4187bb=_0x20f51a;_0x387845['target'][_0x4187bb(0x2ce)][_0x4187bb(0x2cf)](_0x4187bb(0x2e5))&&_0x387845[_0x4187bb(0x350)]['closest']('.hly-exclusion-rule-row')[_0x4187bb(0x1cb)]();});}function previewCondensation(){const _0x81c39d=_0x13c5aa,_0x1d0fd1=document[_0x81c39d(0x2c1)]('hly-condensation-results');try{const _0x118734=_0x3ea5ae[_0x81c39d(0x292)](),_0x349fb4=_0x118734[_0x81c39d(0x23c)]['exclusionRules']||[],_0x535657={'user':document[_0x81c39d(0x2c1)](_0x81c39d(0x33b))[_0x81c39d(0x367)],'ai':document[_0x81c39d(0x2c1)](_0x81c39d(0x216))[_0x81c39d(0x367)]},_0x133ede=document[_0x81c39d(0x2c1)](_0x81c39d(0x22e))[_0x81c39d(0x367)],_0x22e611=_0x133ede?document['getElementById'](_0x81c39d(0x29a))['value']['split'](',')[_0x81c39d(0x288)](_0xafe4f7=>_0xafe4f7[_0x81c39d(0x322)]())[_0x81c39d(0x2d1)](Boolean):[],_0x5c94bd=_0x3ea5ae['getMessagesForCondensation'](_0x535657);if(!_0x5c94bd||_0x5c94bd[_0x81c39d(0x2b7)]===0x0){_0x1d0fd1['textContent']=_0x81c39d(0x268),toastr['warning'](_0x81c39d(0x390),_0x81c39d(0x35a));return;}const _0x5b7d3e=getContext()[_0x81c39d(0x1da)],_0x104de7=_0x5c94bd['map']((_0x25c477,_0x2dfcf0)=>{const _0xd5f7d0=_0x81c39d;let _0x466c19;if(_0x25c477['is_user'])_0x466c19=_0x25c477[_0xd5f7d0(0x30a)];else{if(_0x133ede&&_0x22e611[_0xd5f7d0(0x2b7)]>0x0){const _0x3fe09d=extractBlocksByTags(_0x25c477[_0xd5f7d0(0x30a)],_0x22e611);_0x466c19=_0x3fe09d[_0xd5f7d0(0x2c3)]('\x0a\x0a');}else _0x466c19=_0x25c477['mes'];_0x466c19=applyExclusionRules(_0x466c19,_0x349fb4);}const _0x157fd3=_0x5b7d3e[_0xd5f7d0(0x26f)](_0x5996d3=>_0x5996d3===_0x25c477),_0x54f592=_0x157fd3!==-0x1?_0x157fd3+0x1:-0x1;return{'id':_0xd5f7d0(0x2f0)+_0x2dfcf0,'name':_0x25c477[_0xd5f7d0(0x1c4)],'content':_0x466c19[_0xd5f7d0(0x322)](),'floor':_0x54f592,'is_user':_0x25c477['is_user'],'send_date':_0x25c477[_0xd5f7d0(0x30d)]};})[_0x81c39d(0x2d1)](_0x2a8117=>_0x2a8117['content']);if(_0x104de7['length']===0x0){_0x1d0fd1[_0x81c39d(0x267)]=_0x81c39d(0x1f6),toastr[_0x81c39d(0x223)]('根据标签提取或内容排除条件,未找到任何有效内容。','翰林院启奏');return;}const _0x1d6eaf=_0x104de7['map']((_0xf4dbe5,_0x573043)=>'\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'+_0xf4dbe5['content']+'\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20
\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20×\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20
\x0a\x20\x20\x20\x20\x20\x20\x20\x20')[_0x81c39d(0x2c3)]('');showHtmlModal(_0x81c39d(0x27a),_0x81c39d(0x235)+_0x1d6eaf+'',{'okText':_0x81c39d(0x252),'onOk':_0x476935=>{const _0x126247=_0x81c39d,_0x1d84ac=[];_0x476935['find']('.hly-preview-item-v2')['each'](function(){const _0x59c4bf=_0x46ab,_0x2f6950=$(this)[_0x59c4bf(0x397)]('.hly-preview-textarea'),_0x51f124=_0x2f6950[_0x59c4bf(0x378)]();_0x51f124[_0x59c4bf(0x322)]()&&_0x1d84ac[_0x59c4bf(0x325)]({'mes':_0x51f124,'is_user':_0x2f6950[_0x59c4bf(0x304)](_0x59c4bf(0x1a4)),'send_date':_0x2f6950[_0x59c4bf(0x304)](_0x59c4bf(0x1b6)),'floor':_0x2f6950['data'](_0x59c4bf(0x21c))});}),_0x1d0fd1[_0x126247(0x1f3)][_0x126247(0x1f7)]=JSON[_0x126247(0x28d)](_0x1d84ac);const _0x5965e9=document[_0x126247(0x2c1)](_0x126247(0x2c6))[_0x126247(0x31b)],_0x34e1b4=document[_0x126247(0x2c1)](_0x126247(0x381))[_0x126247(0x31b)];_0x1d0fd1[_0x126247(0x267)]=_0x126247(0x2eb)+_0x5965e9+_0x126247(0x226)+_0x34e1b4+'\x20楼的内容(共\x20'+_0x1d84ac[_0x126247(0x2b7)]+_0x126247(0x314),toastr['success'](_0x126247(0x347),'圣旨已达');}}),$(_0x81c39d(0x2fd))['on']('click',function(_0x1fdb73){const _0x39e4eb=_0x81c39d;_0x1fdb73[_0x39e4eb(0x20f)]();const _0x59f6b3=$(this)['data'](_0x39e4eb(0x350));$('#'+_0x59f6b3)[_0x39e4eb(0x1cb)]();});}catch(_0x1ac6f9){console['error'](_0x81c39d(0x23a),_0x1ac6f9),_0x1d0fd1['textContent']=_0x81c39d(0x1c8)+_0x1ac6f9['message'],toastr['error'](_0x81c39d(0x1c8)+_0x1ac6f9[_0x81c39d(0x374)],_0x81c39d(0x295));}}function _0x46ab(_0x1d1517,_0x14bd99){const _0x26e1e1=_0x26e1();return _0x46ab=function(_0x46ab60,_0x28dc1f){_0x46ab60=_0x46ab60-0x1a1;let _0x2c353d=_0x26e1e1[_0x46ab60];return _0x2c353d;},_0x46ab(_0x1d1517,_0x14bd99);}function log(_0xb1ce57,_0x5f470a='info'){const _0x17349b=_0x13c5aa,_0x76705c=document['getElementById']('hly-log-output');if(!_0x76705c)return;const _0xdfc310=document[_0x17349b(0x1ac)]('p'),_0x534a6c=new Date()[_0x17349b(0x2bd)]();let _0x238f63=_0x17349b(0x1d0),_0x371921=_0x17349b(0x26a);switch(_0x5f470a){case'success':_0x238f63='fa-check-circle',_0x371921=_0x17349b(0x205);break;case _0x17349b(0x275):_0x238f63=_0x17349b(0x251),_0x371921=_0x17349b(0x39a);break;case'warn':_0x238f63=_0x17349b(0x36a),_0x371921=_0x17349b(0x1a3);break;}_0xdfc310[_0x17349b(0x2c0)]=_0x17349b(0x28a)+_0x371921,_0xdfc310[_0x17349b(0x2dd)]=_0x17349b(0x213)+_0x238f63+'\x22>\x20['+_0x534a6c+']\x20'+_0xb1ce57;const _0x3a997a=_0x76705c[_0x17349b(0x1a8)](_0x17349b(0x1f2));_0x3a997a&&_0x3a997a[_0x17349b(0x1cb)](),_0x76705c[_0x17349b(0x2ab)](_0xdfc310),_0x76705c[_0x17349b(0x37e)]=_0x76705c[_0x17349b(0x1bd)];}async function ingestManualText(){const _0x199cc5=_0x13c5aa,_0x2b971a=document[_0x199cc5(0x2c1)](_0x199cc5(0x340)),_0x2a4c37=_0x2b971a[_0x199cc5(0x31b)][_0x199cc5(0x322)]();if(!_0x2a4c37){toastr[_0x199cc5(0x223)](_0x199cc5(0x27b),_0x199cc5(0x35a)),log(_0x199cc5(0x1d4),_0x199cc5(0x2da));return;}log(_0x199cc5(0x1b3)+_0x2a4c37[_0x199cc5(0x2b7)],_0x199cc5(0x239)),toastr[_0x199cc5(0x239)](_0x199cc5(0x1f8),'圣旨');try{const _0x531305=await _0x3ea5ae[_0x199cc5(0x227)](_0x2a4c37,_0x199cc5(0x1ba),{'sourceName':_0x199cc5(0x1db)});if(_0x531305[_0x199cc5(0x1ea)])toastr['success'](_0x199cc5(0x1ed)+_0x531305['count']+_0x199cc5(0x39d),_0x199cc5(0x32f)),log(_0x199cc5(0x22a)+_0x531305['count']+_0x199cc5(0x39d),_0x199cc5(0x1ea)),_0x2b971a[_0x199cc5(0x31b)]='';else throw new Error(_0x531305[_0x199cc5(0x275)]||_0x199cc5(0x21b));}catch(_0x344687){console[_0x199cc5(0x275)](_0x199cc5(0x2d2),_0x344687),toastr[_0x199cc5(0x275)](_0x199cc5(0x260)+_0x344687[_0x199cc5(0x374)],_0x199cc5(0x295)),log('手动录入失败:\x20'+_0x344687['message'],_0x199cc5(0x275));}finally{await updatePanelStatus();}} +const _0x28d2da=_0x219d;function _0x219d(_0xa49005,_0x12ee67){const _0x1a1560=_0x1a15();return _0x219d=function(_0x219db0,_0x20a982){_0x219db0=_0x219db0-0x1f3;let _0x52a346=_0x1a1560[_0x219db0];return _0x52a346;},_0x219d(_0xa49005,_0x12ee67);}(function(_0xc807d3,_0x2667b8){const _0x4a8bb6=_0x219d,_0x181744=_0xc807d3();while(!![]){try{const _0x5dec87=parseInt(_0x4a8bb6(0x31a))/0x1*(-parseInt(_0x4a8bb6(0x200))/0x2)+parseInt(_0x4a8bb6(0x35a))/0x3+parseInt(_0x4a8bb6(0x3ba))/0x4*(-parseInt(_0x4a8bb6(0x318))/0x5)+parseInt(_0x4a8bb6(0x2d9))/0x6+-parseInt(_0x4a8bb6(0x291))/0x7+parseInt(_0x4a8bb6(0x23a))/0x8+parseInt(_0x4a8bb6(0x3ab))/0x9;if(_0x5dec87===_0x2667b8)break;else _0x181744['push'](_0x181744['shift']());}catch(_0x3cd810){_0x181744['push'](_0x181744['shift']());}}}(_0x1a15,0x789fc));import{getContext}from'/scripts/extensions.js';import*as _0x5a0ed1 from'../core/rag-processor.js';import*as _0x798f38 from'../core/historiographer.js';import*as _0x4966d0 from'../core/utils/context-utils.js';import*as _0x54c7cd from'../core/ingestion-manager.js';import{showContentModal,showHtmlModal}from'./page-window.js';function _0x1a15(){const _0x1e2eb6=['未找到符合条件的消息。','.hly-nav-item','options','processCondensation','{{chat_text}}','target','】吗?','string','stringify','fetchEmbeddingModels','[翰林院-枢纽]\x20未能获取SillyTavern上下文,绑定失败。','\x20个知识库\x20(范围:\x20','\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20[','selectedIndex','正在查询宝库状态...','加载失败','】移动到【','hly-tag-input','entries','hly-kb-list-local','未能获取到任何模型。','advanced','\x20块继续录入。','hly-retrieval-notify','','scrollTop','hly-api-endpoint','generateJobId','scrollHeight','hly-kb-delete-btn','loadProgress','preventDefault','用户尝试录入空文本。','1586568LlkrfG','embeddingModel','trim','hly-current-character-name','未能获取到任何Rerank模型。','\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20获取失败','知识库\x20','dataset','[翰林院-枢纽]\x20编纂过程发生严重错误:','hly-kb-list-item','hly-custom-endpoint-docket','>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20×\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20','解锁会话','任务已中止。','#hly-add-rule-btn','[自动保存]\x20设置项\x20\x27','finalMessages','messageTypes','hly-kb-move-all-to-global','hly-hist-entry-multiselect-options','未找到匹配的条目','开始对《','请先选择一个书库并至少选择一个要编纂的条目。','children','filter',')\x22>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20
\x0a\x20\x20\x20\x20','录入内容不能为空。','top_n','请至少选择一个知识库进行操作。','info','hly-kb-delete-local-btn','hly-injection-source-selector','\x22>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20第\x20','[翰林院-枢纽]\x20核心法典未能提供初始化圣旨!','manual','\x0a\x20\x20\x20\x20\x20\x20\x20\x20','all','\x20个局部知识库均已成功删除。','click','[翰林院-枢纽]\x20加载书库列表失败:','hly-overlap-size','hly-rerank-api-key','removeEventListener','批量编纂任务已完成,但有部分错误。','.hly-hist-entry-checkbox','圣谕不明','hly-unified-template-notes','display','checked','[断点续传]\x20用户选择放弃旧任务\x20','9229779fwRJyc','tags','知识库【','html','toFixed','kbId','push','content','text','processed','此书库为空','...','清空宝库失败。','\x22\x20placeholder=\x22开始字符串,\x20如\x20`。

\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20','您确定要将所有设定恢复为出厂默认值吗?','purgeHLYStorage','您确定要将知识库【','comment','未找到匹配的书库','\x20个局部知识库...','\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20','成功获取\x20','hly-session-lock-btn','findIndex','toggleSessionLock','layerEnd','hly-kb-toggle','.hly-kb-item-checkbox:checked','notify','css','fas\x20fa-lock','hly-','warning','hly-kb-list-','\x20个条目进行批量编纂...','[翰林院-枢纽]\x20获取模型列表失败:','append','AbortError','_searchHandler','切换知识库\x20','start','fas\x20fa-lock-open','预览失败:\x20','message','成功移动了\x20','ingestTextToHanlinyuan','源区域(','beforeend','add','圣旨已下','严重错误','#hly-modal-tag-extraction-enabled','[翰林院-枢纽]\x20更新忆识数量失败:','hly-layer-end','change','getCharacterName','当前角色没有任何局部知识库可供删除。','例如\x20http://127.0.0.1:8000/v1','1113628jDNDXZ','大功告成','toLocaleTimeString','key','map','getMessagesForCondensation','apiKey','遵命,将从第\x20','getAvailableWorldbooks','移动失败:\x20','会话已锁定到:\x20','请输入知识库的新名称:','所有\x20','批量\x20','option','\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20标签提取\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20启用标签提取\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x22>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20×\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20','files','请输入您的Google\x20API\x20Key','getSettings','style','预览内容已更新,可随时开始凝识。','翰林院设定已存档封印。','fetchHLYRerankModels','\x0a忆识总数:\x20','use\x20strict','insertAdjacentHTML','任务已由用户中止。进度已保存,可随时继续。','\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20','previewHLYCondensation','input','{{lorebook_text}}','\x20楼:\x20[','预览并编辑凝识内容','您确定要将选中的\x20','2898488kmGHZn','condensationHistory','','warn','send-date','.hly-kb-rename-btn','saveHLYSettings','hly-entry-search','】已删除。','hly-chunk-size','删除局部知识库\x20','fa-times-circle','exclusionRules','getLocalKnowledgeBases','hanlinyuan-ingest-progress-bar','删除失败:\x20','获取模型失败:\x20','翰林院设定已重置为初始状态。','当前角色','allWorldbooks','未知错误','\x20个知识块','》的批量编纂任务已完成。成功:\x20','every','includes','\x20楼已成功凝识,新增\x20','processedChunks','%。是否从上次中断之处继续?','.depth','stopPropagation','\x20楼到第\x20','ingestHLYManualText','chat_history','apiEndpoint','finalText','\x0a--------------------\x0aAPI端点:\x20','value','move','\x20个知识库从\x20','hly-embedding-model','.count\x22]','hanlinyuan-ingest-novel-file-name','hly-historiography-results','closest','N/A','globalToLocal','获取Rerank模型失败:\x20','hly-independent-chat-memory-enabled','find','加载书库列表失败:\x20','hly-rerank-notify','user','fa-check-circle','\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20加载失败:\x20','开始将\x20','rerank','floor','暂无规则

','遵命,将从头开始录入此书。','\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20
\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20

','input[name=\x22hly-unified-injection-position\x22]','superSortEnabled','[翰林院-枢纽]\x20查询宝库状态失败:','className','template','正在清空宝库...','>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20','\x20个局部知识库吗?此操作无法恢复!','initialize','手动录入','hly-local-kb-char-name','hly-hist-select-all-entries','classList','错误:\x20','injection_','[翰林院-枢纽]\x20渲染知识库列表失败:','\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20添加新规则\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20','正在为《','会话已解锁,将跟随当前角色。','根据标签提取或内容排除条件,未找到任何有效内容。','\x20楼的内容(共\x20','span','hly-condensation-results','操作完成,但有\x20','\x20个知识库从【','.hly-kb-name','hly-kb-bulk-actions-local','getCollectionId','getLockedSessionInfo','[翰林院-枢纽]\x20凝识过程发生错误:','.hly-log-placeholder','请先选择书库','hly-rerank-enabled','2541018uDGzfQ','','hly-query-message-count','自定义路径:','delete','hly-kb-select-all-global','.hly-preview-delete-btn-v2','boolean','removeKnowledgeBase','resetSettings','float','#hly-rules-list','\x20个知识库。','成功切换了\x20','删除知识库\x20','未找到匹配的条目','开始批量删除\x20','each','[断点续传]\x20用户选择继续任务\x20','condensation','local','end','hly-current-vector-count','toggleKnowledgeBase','flex','renameKnowledgeBase','hly-max-results','\x20为占位符。','成功加载\x20','val',';\x22>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20输入标签\x20(以逗号分隔):\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20','宝库已清空。','testApiConnection','radio','replace','.depth_role','clearJob','independentChatMemoryEnabled','.hly-exclusion-rule-row','启禀大人,发现此书上次录入已完成\x20','\x22\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20data-send-date=\x22','allEntries','圣旨已达','hly-tag-extraction-toggle','hly-tag-input-container','#hly-modal-tag-input-container','block','已采集\x20','》的条目失败:','innerHTML','chunkSize','querySelector','position','input[name=\x22','\x20条消息,开始凝识...','contains','宝库状态','none','移动知识库\x20','\x20条有效条目),请点击“开始凝识”进入自动向量化流程。','moveKnowledgeBase','queryMessageCount','hly-kb-select-all-','565YGAnPY','name','1WJOSuX','[翰林院-枢纽]\x20未找到类型为\x20\x22','hly-kb-list-global','您确定要永久删除知识库【','type','点击以锁定,让翰林院固定操作当前角色的宝库','url','signal','\x22\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20data-is-user=\x22','准备对《','\x20个知识库执行批量操作...','checkbox','文书录入失败:\x20','updateHLYMemoryCount','global','.hly-preview-item-v2','编辑检索内容排除规则','hanlinyuan-ingest-novel-start','getVectorCount','会话已解锁。','未找到任何书库','executeCompilation'];_0x1a15=function(){return _0x1e2eb6;};return _0x1a15();}import{extractBlocksByTags,applyExclusionRules}from'../core/utils/rag-tag-extractor.js';import{filterWorldbooks,filterWorldbookEntries,highlightSearchMatch,debounce}from'../core/rag-processor.js';_0x28d2da(0x230);function setupGlobalEventHandlers(){const _0x9c32ac=_0x28d2da;window[_0x9c32ac(0x240)]=()=>saveSettingsFromUI(![]),window['resetHLYSettings']=resetSettingsToUI,window[_0x9c32ac(0x28f)]=testApi,window['fetchHLYEmbeddingModels']=fetchHLYEmbeddingModels,window[_0x9c32ac(0x22e)]=fetchHLYRerankModels,window[_0x9c32ac(0x327)]=updatePanelStatus,window[_0x9c32ac(0x3e6)]=purgeStorage,window[_0x9c32ac(0x2a3)]=startCondensation,window[_0x9c32ac(0x234)]=previewCondensation,window[_0x9c32ac(0x259)]=ingestManualText,window['hlyLog']=log,window['showHLYStats']=showStats,window[_0x9c32ac(0x283)]=startHistoriography;}function updateAndSaveSetting(_0x432dde,_0x2d2b27){const _0x3f6349=_0x28d2da,_0xf00ca3=_0x5a0ed1[_0x3f6349(0x22a)]();if(!_0xf00ca3)return;const _0x4619b0=_0x432dde[_0x3f6349(0x3d5)]('.');let _0x55f57c=_0xf00ca3;for(let _0x462ee1=0x0;_0x462ee1<_0x4619b0[_0x3f6349(0x341)]-0x1;_0x462ee1++){_0x55f57c=_0x55f57c[_0x4619b0[_0x462ee1]]=_0x55f57c[_0x4619b0[_0x462ee1]]||{};}_0x55f57c[_0x4619b0[_0x4619b0[_0x3f6349(0x341)]-0x1]]=_0x2d2b27,_0x5a0ed1[_0x3f6349(0x342)](),log(_0x3f6349(0x371)+_0x432dde+'\x27\x20已更新为:\x20'+JSON[_0x3f6349(0x338)](_0x2d2b27),'success');}function bindAutoSaveEvents(){const _0x1805bc=_0x28d2da,_0x3c1f61=document[_0x1805bc(0x214)](_0x1805bc(0x299));if(!_0x3c1f61)return;_0x3c1f61[_0x1805bc(0x29a)]('change',_0x541b13=>{const _0x3277f5=_0x1805bc,_0x4a99c5=_0x541b13[_0x3277f5(0x335)],_0x88d917=_0x4a99c5[_0x3277f5(0x369)]['settingKey'];if(!_0x88d917)return;let _0x22113a;const _0x1e717a=_0x4a99c5[_0x3277f5(0x369)][_0x3277f5(0x31e)]||_0x3277f5(0x337);if(_0x4a99c5[_0x3277f5(0x31e)]===_0x3277f5(0x325))_0x22113a=_0x4a99c5[_0x3277f5(0x3a9)];else{if(_0x4a99c5['type']===_0x3277f5(0x2fa)){if(_0x4a99c5[_0x3277f5(0x3a9)]){const _0x11aa21=_0x3c1f61[_0x3277f5(0x285)](_0x3277f5(0x30e)+_0x4a99c5[_0x3277f5(0x319)]+'\x22]'),_0x23a463=Array['from'](_0x11aa21)[_0x3277f5(0x26a)](_0x161d2d=>_0x161d2d[_0x3277f5(0x3a9)]);_0x22113a=_0x23a463[_0x3277f5(0x25e)];}else return;}else _0x22113a=_0x4a99c5[_0x3277f5(0x25e)];}switch(_0x1e717a){case _0x3277f5(0x21e):_0x22113a=parseInt(_0x22113a,0xa);break;case _0x3277f5(0x2e3):_0x22113a=parseFloat(_0x22113a);break;case'boolean':typeof _0x22113a!==_0x3277f5(0x2e0)&&(_0x22113a=_0x22113a===_0x3277f5(0x3bb));break;}if(_0x4a99c5[_0x3277f5(0x31e)]===_0x3277f5(0x2fa)&&!_0x4a99c5[_0x3277f5(0x3a9)])return;updateAndSaveSetting(_0x88d917,_0x22113a),_0x88d917==='retrieval.independentChatMemoryEnabled'&&updatePanelStatus();});}export function bindHanlinyuanEvents(){const _0x5c71ac=_0x28d2da,_0x37537a=getContext();if(!_0x37537a){console[_0x5c71ac(0x282)](_0x5c71ac(0x33a));return;}setupGlobalEventHandlers(),bindPanelToggleEvents(),bindInternalUIEvents(),bindTutorialEvents(),bindAutoSaveEvents(),bindSessionLockEvent(),initializeUnifiedInjectionEditor();if(_0x5a0ed1[_0x5c71ac(0x2c0)])_0x5a0ed1[_0x5c71ac(0x2c0)]();else{console[_0x5c71ac(0x282)](_0x5c71ac(0x394));return;}loadSettingsToUI(),loadWorldbookList(),log('[翰林院-枢纽]\x20已成功连接各部,政令畅通。','info');const _0x49d861=document[_0x5c71ac(0x214)]('hanlinyuan-ingest-novel-file-input'),_0x49b507=document['getElementById'](_0x5c71ac(0x263)),_0x13fd6c=document['getElementById'](_0x5c71ac(0x32b)),_0x4a47e4=document[_0x5c71ac(0x214)](_0x5c71ac(0x27b)),_0x5a9012=document['getElementById'](_0x5c71ac(0x2a2)),_0xb4da80=document[_0x5c71ac(0x214)](_0x5c71ac(0x248)),_0x4deb6a=document[_0x5c71ac(0x214)](_0x5c71ac(0x2a4)),_0x7150d9=document[_0x5c71ac(0x214)]('hanlinyuan-ingest-novel-controls');let _0x1e674b=null,_0x46dbb9=null;_0x49d861[_0x5c71ac(0x29a)](_0x5c71ac(0x1fc),_0xd84e50=>{const _0x3e8863=_0x5c71ac;_0x1e674b=_0xd84e50['target'][_0x3e8863(0x228)][0x0],_0x1e674b?(_0x49b507['textContent']=_0x1e674b[_0x3e8863(0x319)],_0x49b507[_0x3e8863(0x27d)]=_0x1e674b[_0x3e8863(0x319)]):_0x49b507['textContent']=_0x3e8863(0x3d1);}),_0x13fd6c[_0x5c71ac(0x29a)](_0x5c71ac(0x39f),async()=>{const _0x3bc0c9=_0x5c71ac;if(!_0x1e674b){toastr[_0x3bc0c9(0x40a)](_0x3bc0c9(0x29e));return;}let _0xafc159=0x0;const _0x586bb6=_0x54c7cd[_0x3bc0c9(0x354)](_0x1e674b),_0x33c001=_0x54c7cd[_0x3bc0c9(0x357)](_0x586bb6);if(_0x33c001){const _0x2741ba=(_0x33c001[_0x3bc0c9(0x254)]/_0x33c001[_0x3bc0c9(0x219)]*0x64)[_0x3bc0c9(0x3af)](0x1),_0x143b16=confirm(_0x3bc0c9(0x300)+_0x2741ba+_0x3bc0c9(0x255));_0x143b16?(_0xafc159=_0x33c001[_0x3bc0c9(0x254)],toastr[_0x3bc0c9(0x390)](_0x3bc0c9(0x207)+(_0xafc159+0x1)+_0x3bc0c9(0x34f),'圣旨已达'),log(_0x3bc0c9(0x2eb)+_0x586bb6+_0x3bc0c9(0x3c3)+_0xafc159+_0x3bc0c9(0x360),'info')):(_0x54c7cd[_0x3bc0c9(0x2fd)](_0x586bb6),toastr[_0x3bc0c9(0x390)](_0x3bc0c9(0x28d),_0x3bc0c9(0x303)),log(_0x3bc0c9(0x3aa)+_0x586bb6+_0x3bc0c9(0x3f6),_0x3bc0c9(0x23d)));}_0x46dbb9=new AbortController();const _0x40a85d=_0x46dbb9[_0x3bc0c9(0x321)];_0x7150d9['style'][_0x3bc0c9(0x3a8)]=_0x3bc0c9(0x312),_0x5a9012[_0x3bc0c9(0x22b)]['display']='block',_0x4deb6a['textContent']='正在读取文件...',_0xb4da80[_0x3bc0c9(0x25e)]=0x0;try{const _0x4e74f6=await _0x1e674b[_0x3bc0c9(0x3b3)](),_0x48409b=_0x4a99f5=>{const _0x10aec9=_0x3bc0c9;_0x4deb6a[_0x10aec9(0x3e1)]=_0x10aec9(0x3cf)+_0x4a99f5['message']+'\x20('+_0x4a99f5[_0x10aec9(0x3b4)]+'/'+_0x4a99f5[_0x10aec9(0x387)]+')',_0xb4da80[_0x10aec9(0x25e)]=_0x4a99f5[_0x10aec9(0x3b4)]/_0x4a99f5[_0x10aec9(0x387)]*0x64;},_0x500025=()=>{const _0xfdb1d5=_0x3bc0c9;updatePanelStatus(),log(_0xfdb1d5(0x344),_0xfdb1d5(0x390));},_0x1dd627=await _0x5a0ed1[_0x3bc0c9(0x1f3)](_0x4e74f6,_0x3bc0c9(0x38b),{'sourceName':_0x1e674b[_0x3bc0c9(0x319)]},_0x48409b,_0x40a85d,log,_0x500025,_0x586bb6,_0xafc159);if(_0x1dd627[_0x3bc0c9(0x224)])toastr[_0x3bc0c9(0x224)](_0x3bc0c9(0x2ba)+_0x1dd627[_0x3bc0c9(0x343)]+_0x3bc0c9(0x24f)),_0x4deb6a['textContent']='任务完成!成功录入\x20'+_0x1dd627[_0x3bc0c9(0x343)]+_0x3bc0c9(0x385),_0xb4da80['value']=0x64,updatePanelStatus();else throw new Error(_0x1dd627[_0x3bc0c9(0x282)]||'未知错误');}catch(_0x5163bc){_0x5163bc[_0x3bc0c9(0x319)]===_0x3bc0c9(0x40f)?(toastr[_0x3bc0c9(0x390)](_0x3bc0c9(0x232)),_0x4deb6a['textContent']=_0x3bc0c9(0x36f)):(toastr[_0x3bc0c9(0x282)](_0x3bc0c9(0x216)+_0x5163bc[_0x3bc0c9(0x415)]+_0x3bc0c9(0x365)),_0x4deb6a[_0x3bc0c9(0x3e1)]=_0x3bc0c9(0x2c5)+_0x5163bc[_0x3bc0c9(0x415)]);}finally{setTimeout(()=>{const _0x5bef81=_0x3bc0c9;_0x7150d9[_0x5bef81(0x22b)]['display']=_0x5bef81(0x2f1),_0x5a9012['style'][_0x5bef81(0x3a8)]=_0x5bef81(0x312),_0x49d861[_0x5bef81(0x25e)]='',_0x1e674b=null,_0x49b507[_0x5bef81(0x3e1)]=_0x5bef81(0x3d1);},0xbb8);}}),_0x4a47e4['addEventListener'](_0x5c71ac(0x39f),()=>{_0x46dbb9&&_0x46dbb9['abort']();});}function bindSessionLockEvent(){const _0x29a9cb=_0x28d2da,_0x1c0b46=document[_0x29a9cb(0x214)](_0x29a9cb(0x400));if(!_0x1c0b46)return;_0x1c0b46[_0x29a9cb(0x29a)](_0x29a9cb(0x39f),async()=>{const _0x1d1888=_0x29a9cb,_0x47b5c1=await _0x5a0ed1[_0x1d1888(0x402)]();updateSessionLockUI(_0x47b5c1);if(_0x47b5c1){const _0x4381c5=_0x5a0ed1[_0x1d1888(0x2d4)]();_0x4381c5&&(toastr[_0x1d1888(0x224)](_0x1d1888(0x20a)+_0x4381c5['id'],_0x1d1888(0x1f7)),log(_0x1d1888(0x3e2)+_0x4381c5['id'],_0x1d1888(0x224)));}else toastr['info'](_0x1d1888(0x2ca),'诏曰'),log(_0x1d1888(0x32d),_0x1d1888(0x390));updatePanelStatus();}),updateSessionLockUI(_0x5a0ed1[_0x29a9cb(0x274)]());}function updateSessionLockUI(_0x51b879){const _0x388703=_0x28d2da,_0x224e43=document[_0x388703(0x214)](_0x388703(0x400));if(!_0x224e43)return;const _0x4fda47=_0x224e43[_0x388703(0x30c)]('i'),_0x32f01e=_0x224e43[_0x388703(0x30c)](_0x388703(0x2cd));_0x51b879?(_0x224e43[_0x388703(0x2c4)][_0x388703(0x1f6)](_0x388703(0x3c6)),_0x4fda47['className']=_0x388703(0x408),_0x32f01e[_0x388703(0x3e1)]=_0x388703(0x36e),_0x224e43[_0x388703(0x27d)]=_0x388703(0x3d7)):(_0x224e43['classList'][_0x388703(0x21a)](_0x388703(0x3c6)),_0x4fda47[_0x388703(0x2b4)]=_0x388703(0x413),_0x32f01e['textContent']='锁定会话',_0x224e43[_0x388703(0x27d)]=_0x388703(0x31f));}function bindPanelToggleEvents(){const _0x1eb852=_0x28d2da,_0xef0868=document[_0x1eb852(0x214)](_0x1eb852(0x296));if(_0xef0868){}}function bindTutorialEvents(){const _0x50ce05=_0x28d2da,_0x51bf99=document[_0x50ce05(0x214)]('amily2_open_hanlin_tutorial');_0x51bf99&&_0x51bf99[_0x50ce05(0x29a)](_0x50ce05(0x39f),()=>{const _0x32ed9c=_0x50ce05;showContentModal(_0x32ed9c(0x386),_0x32ed9c(0x363));});}function bindInternalUIEvents(){const _0x2d6442=_0x28d2da,_0x4f1103=document[_0x2d6442(0x285)](_0x2d6442(0x331));_0x4f1103[_0x2d6442(0x3dd)](_0x14d2f3=>{const _0x511a8e=_0x2d6442;_0x14d2f3[_0x511a8e(0x29a)](_0x511a8e(0x39f),()=>{const _0x33fa96=_0x511a8e,_0x156a17=_0x14d2f3[_0x33fa96(0x369)][_0x33fa96(0x295)],_0x169aba=_0x33fa96(0x409)+_0x156a17+_0x33fa96(0x21c);document[_0x33fa96(0x285)](_0x33fa96(0x3d6))['forEach'](_0x4ad631=>{const _0x265a30=_0x33fa96;_0x4ad631[_0x265a30(0x2c4)][_0x265a30(0x29b)](_0x265a30(0x3c6),_0x4ad631['id']===_0x169aba);}),_0x4f1103[_0x33fa96(0x3dd)](_0xd74caa=>_0xd74caa['classList']['toggle'](_0x33fa96(0x3c6),_0xd74caa===_0x14d2f3));});});const _0x28b6dd=document[_0x2d6442(0x214)](_0x2d6442(0x353));_0x28b6dd&&_0x28b6dd[_0x2d6442(0x29a)](_0x2d6442(0x1fc),handleApiModeChange);const _0x146ac1=document[_0x2d6442(0x214)](_0x2d6442(0x304)),_0x1f045a=document[_0x2d6442(0x214)](_0x2d6442(0x305));_0x146ac1&&_0x1f045a&&_0x146ac1['addEventListener'](_0x2d6442(0x1fc),()=>{const _0x40bb19=_0x2d6442;_0x1f045a[_0x40bb19(0x22b)][_0x40bb19(0x3a8)]=_0x146ac1[_0x40bb19(0x3a9)]?_0x40bb19(0x307):_0x40bb19(0x312);});const _0x5370f7=document[_0x2d6442(0x214)](_0x2d6442(0x3c2));_0x5370f7&&_0x5370f7[_0x2d6442(0x29a)](_0x2d6442(0x1fc),handleWorldbookSelectionChange);const _0x25e0e1=document['getElementById']('hly-exclusion-rules-btn');_0x25e0e1&&_0x25e0e1[_0x2d6442(0x29a)](_0x2d6442(0x39f),()=>showRulesModal(_0x2d6442(0x2ec)));const _0x1f112b=document[_0x2d6442(0x214)]('hly-query-preprocessing-rules-btn');_0x1f112b&&_0x1f112b['addEventListener']('click',()=>showRulesModal('queryPreprocessing'));const _0x558a54=document[_0x2d6442(0x214)](_0x2d6442(0x286)),_0x13f699=document['getElementById'](_0x2d6442(0x375));_0x558a54&&_0x13f699&&(_0x558a54[_0x2d6442(0x29a)](_0x2d6442(0x39f),_0xf31c34=>{const _0x47841c=_0x2d6442;_0xf31c34[_0x47841c(0x257)]();const _0x33b429=_0x13f699[_0x47841c(0x22b)]['display']===_0x47841c(0x307);_0x13f699[_0x47841c(0x22b)][_0x47841c(0x3a8)]=_0x33b429?_0x47841c(0x312):_0x47841c(0x307);}),_0x13f699[_0x2d6442(0x29a)](_0x2d6442(0x1fc),_0xddf3b4=>{const _0x30a99b=_0x2d6442,_0x5475bf=_0xddf3b4['target'];if(_0x5475bf[_0x30a99b(0x31e)]!==_0x30a99b(0x325))return;const _0x18ba19=_0x13f699[_0x30a99b(0x285)](_0x30a99b(0x3a5)),_0x1f3e34=document[_0x30a99b(0x214)](_0x30a99b(0x2c3));if(_0x5475bf['id']===_0x30a99b(0x2c3))_0x18ba19[_0x30a99b(0x3dd)](_0x585cd8=>_0x585cd8[_0x30a99b(0x3a9)]=_0x5475bf[_0x30a99b(0x3a9)]);else{const _0x42fd1a=Array[_0x30a99b(0x3fb)](_0x18ba19)[_0x30a99b(0x251)](_0x61ce96=>_0x61ce96[_0x30a99b(0x3a9)]);_0x1f3e34[_0x30a99b(0x3a9)]=_0x42fd1a;}const _0x4faea4=_0x13f699[_0x30a99b(0x285)](_0x30a99b(0x3ef))['length'],_0x39cf62=_0x18ba19[_0x30a99b(0x341)];_0x558a54[_0x30a99b(0x30c)](_0x30a99b(0x2cd))[_0x30a99b(0x3e1)]=_0x30a99b(0x27e)+_0x4faea4+_0x30a99b(0x278)+_0x39cf62+_0x30a99b(0x37c);}),document[_0x2d6442(0x29a)]('click',_0x5c16ed=>{const _0x3eb234=_0x2d6442;!_0x558a54[_0x3eb234(0x310)](_0x5c16ed[_0x3eb234(0x335)])&&!_0x13f699[_0x3eb234(0x310)](_0x5c16ed['target'])&&(_0x13f699[_0x3eb234(0x22b)][_0x3eb234(0x3a8)]=_0x3eb234(0x312));}));const _0x4d42b1=document[_0x2d6442(0x214)](_0x2d6442(0x391));_0x4d42b1&&_0x4d42b1[_0x2d6442(0x29a)](_0x2d6442(0x39f),deleteAllLocalKnowledgeBases);const _0x17e211=document['getElementById']('hly-kb-move-all-to-local');_0x17e211&&_0x17e211[_0x2d6442(0x29a)]('click',()=>moveAllKnowledgeBases(_0x2d6442(0x267)));const _0x2da5c3=document[_0x2d6442(0x214)](_0x2d6442(0x374));_0x2da5c3&&_0x2da5c3[_0x2d6442(0x29a)]('click',()=>moveAllKnowledgeBases(_0x2d6442(0x3c5)));const _0x1bd6ca=[_0x2d6442(0x34c),_0x2d6442(0x31c)];_0x1bd6ca[_0x2d6442(0x3dd)](_0x2e8f5a=>{const _0x13ffc5=_0x2d6442,_0x24f8c0=document[_0x13ffc5(0x214)](_0x2e8f5a);_0x24f8c0&&(_0x24f8c0[_0x13ffc5(0x29a)]('click',handleKbAction),_0x24f8c0[_0x13ffc5(0x29a)](_0x13ffc5(0x1fc),handleKbAction));}),document['getElementById'](_0x2d6442(0x2de))[_0x2d6442(0x29a)](_0x2d6442(0x1fc),_0x13268c=>handleSelectAll(_0x13268c,'global')),document[_0x2d6442(0x214)]('hly-kb-select-all-local')[_0x2d6442(0x29a)](_0x2d6442(0x1fc),_0x2ef633=>handleSelectAll(_0x2ef633,_0x2d6442(0x2ed))),document[_0x2d6442(0x214)](_0x2d6442(0x382))['addEventListener'](_0x2d6442(0x39f),_0x424e52=>handleBulkAction(_0x424e52,'global')),document[_0x2d6442(0x214)](_0x2d6442(0x2d2))[_0x2d6442(0x29a)](_0x2d6442(0x39f),_0xa0e4c8=>handleBulkAction(_0xa0e4c8,_0x2d6442(0x2ed)));}function initializeUnifiedInjectionEditor(){const _0x5aa35f=_0x28d2da,_0x565d61=document[_0x5aa35f(0x214)](_0x5aa35f(0x392)),_0x3df534=document[_0x5aa35f(0x214)](_0x5aa35f(0x3cd)),_0x221b81=document[_0x5aa35f(0x214)](_0x5aa35f(0x3a7)),_0x201587=document['querySelectorAll'](_0x5aa35f(0x2b1)),_0x5550b6=document[_0x5aa35f(0x214)]('hly-unified-injection-depth'),_0x4dfe79=document[_0x5aa35f(0x214)](_0x5aa35f(0x27f));if(!_0x565d61)return;const _0x3e3d5a={'novel':_0x5aa35f(0x3ec),'chat':_0x5aa35f(0x334),'lorebook':_0x5aa35f(0x236),'manual':_0x5aa35f(0x2aa)};function _0x1cb71a(){const _0x2dbc04=_0x5aa35f,_0x51b4c6=_0x565d61[_0x2dbc04(0x25e)],_0x2e048c=_0x5a0ed1['getSettings'](),_0x5496d8=_0x2e048c[_0x2dbc04(0x2c6)+_0x51b4c6]||{};_0x3df534[_0x2dbc04(0x25e)]=_0x5496d8[_0x2dbc04(0x2b5)]||'',_0x221b81[_0x2dbc04(0x3e1)]='以\x20'+(_0x3e3d5a[_0x51b4c6]||'{{text}}')+_0x2dbc04(0x2f4);const _0x24f078=_0x5496d8[_0x2dbc04(0x30d)]!==undefined?String(_0x5496d8[_0x2dbc04(0x30d)]):'2';_0x201587[_0x2dbc04(0x3dd)](_0x3d788e=>_0x3d788e[_0x2dbc04(0x3a9)]=_0x3d788e['value']===_0x24f078),_0x5550b6['value']=_0x5496d8['depth']||0x0,_0x4dfe79['value']=_0x5496d8[_0x2dbc04(0x3b9)]!==undefined?String(_0x5496d8[_0x2dbc04(0x3b9)]):'0';const _0x4ac179=_0x24f078==='1';_0x5550b6[_0x2dbc04(0x298)]=!_0x4ac179,_0x4dfe79[_0x2dbc04(0x298)]=!_0x4ac179;}function _0x1d8a99(){const _0x349db5=_0x5aa35f,_0x233da5=_0x565d61[_0x349db5(0x25e)];updateAndSaveSetting('injection_'+_0x233da5+'.template',_0x3df534['value']);const _0x4bacc2=document[_0x349db5(0x30c)](_0x349db5(0x3c7));_0x4bacc2&&updateAndSaveSetting(_0x349db5(0x2c6)+_0x233da5+_0x349db5(0x3cc),parseInt(_0x4bacc2[_0x349db5(0x25e)],0xa)),updateAndSaveSetting(_0x349db5(0x2c6)+_0x233da5+_0x349db5(0x256),parseInt(_0x5550b6[_0x349db5(0x25e)],0xa)),updateAndSaveSetting(_0x349db5(0x2c6)+_0x233da5+_0x349db5(0x2fc),parseInt(_0x4dfe79[_0x349db5(0x25e)],0xa));}_0x565d61['addEventListener'](_0x5aa35f(0x1fc),_0x1cb71a);const _0x2ed4e4=debounce(_0x1d8a99,0x12c);_0x3df534[_0x5aa35f(0x29a)](_0x5aa35f(0x235),_0x2ed4e4),_0x5550b6[_0x5aa35f(0x29a)](_0x5aa35f(0x1fc),_0x1d8a99),_0x4dfe79[_0x5aa35f(0x29a)](_0x5aa35f(0x1fc),_0x1d8a99),_0x201587[_0x5aa35f(0x3dd)](_0x3ed4cf=>_0x3ed4cf[_0x5aa35f(0x29a)](_0x5aa35f(0x1fc),()=>{const _0x928f97=_0x5aa35f;_0x1d8a99();const _0x249673=_0x3ed4cf[_0x928f97(0x25e)]==='1'&&_0x3ed4cf['checked'];_0x5550b6[_0x928f97(0x298)]=!_0x249673,_0x4dfe79[_0x928f97(0x298)]=!_0x249673;})),_0x1cb71a();}function handleApiModeChange(){const _0x253c74=_0x28d2da,_0x5b6e02=document[_0x253c74(0x214)](_0x253c74(0x353))[_0x253c74(0x25e)],_0x42fea4=document['getElementById'](_0x253c74(0x36c)),_0x48deaf=document[_0x253c74(0x214)](_0x253c74(0x384)),_0xab017d=document[_0x253c74(0x214)]('hly-embedding-model'),_0x32d382=_0xab017d['previousElementSibling'];if(!_0x42fea4||!_0x48deaf)return;_0x42fea4[_0x253c74(0x22b)][_0x253c74(0x3a8)]=_0x253c74(0x307),_0x48deaf[_0x253c74(0x22b)]['display']=_0x253c74(0x307);switch(_0x5b6e02){case _0x253c74(0x210):_0x42fea4[_0x253c74(0x22b)][_0x253c74(0x3a8)]=_0x253c74(0x312),_0x48deaf['querySelector']('label')['textContent']=_0x253c74(0x2a0),_0x48deaf[_0x253c74(0x30c)](_0x253c74(0x235))['placeholder']=_0x253c74(0x229);break;case _0x253c74(0x364):_0x42fea4[_0x253c74(0x30c)](_0x253c74(0x287))[_0x253c74(0x3e1)]=_0x253c74(0x3db),_0x42fea4['querySelector'](_0x253c74(0x235))['placeholder']=_0x253c74(0x1ff),_0x48deaf['style']['display']=_0x253c74(0x312);break;case _0x253c74(0x3f9):default:_0x42fea4[_0x253c74(0x30c)](_0x253c74(0x287))[_0x253c74(0x3e1)]=_0x253c74(0x2dc),_0x42fea4[_0x253c74(0x30c)](_0x253c74(0x235))['placeholder']=_0x253c74(0x2ae),_0x48deaf['querySelector'](_0x253c74(0x287))[_0x253c74(0x3e1)]=_0x253c74(0x3bc);break;}}function loadSettingsToUI(){const _0x201612=_0x28d2da,_0x6c8a37=_0x5a0ed1[_0x201612(0x22a)]();if(!_0x6c8a37)return;document[_0x201612(0x214)](_0x201612(0x3f1))['checked']=_0x6c8a37[_0x201612(0x380)][_0x201612(0x3ee)],document[_0x201612(0x214)](_0x201612(0x353))[_0x201612(0x25e)]=_0x6c8a37[_0x201612(0x380)]['apiEndpoint'],document[_0x201612(0x214)]('hly-custom-api-url')[_0x201612(0x25e)]=_0x6c8a37['retrieval']['customApiUrl'],document[_0x201612(0x214)]('hly-api-key')[_0x201612(0x25e)]=_0x6c8a37[_0x201612(0x380)]['apiKey'];const _0x3cccd9=document[_0x201612(0x214)](_0x201612(0x261));if(_0x3cccd9[_0x201612(0x332)][_0x201612(0x341)]===0x0){const _0x574d2b=_0x6c8a37[_0x201612(0x380)][_0x201612(0x35b)],_0x4990eb=new Option(_0x574d2b,_0x574d2b,!![],!![]);_0x3cccd9[_0x201612(0x1f6)](_0x4990eb);}_0x3cccd9[_0x201612(0x25e)]=_0x6c8a37[_0x201612(0x380)]['embeddingModel'],document['getElementById'](_0x201612(0x350))['checked']=_0x6c8a37[_0x201612(0x380)][_0x201612(0x406)],document[_0x201612(0x214)](_0x201612(0x243))['value']=_0x6c8a37['advanced'][_0x201612(0x30b)],document['getElementById'](_0x201612(0x3a1))[_0x201612(0x25e)]=_0x6c8a37['advanced']['overlap'],document[_0x201612(0x214)]('hly-match-threshold')[_0x201612(0x25e)]=_0x6c8a37[_0x201612(0x34e)]['matchThreshold'],document[_0x201612(0x214)](_0x201612(0x2db))['value']=_0x6c8a37[_0x201612(0x34e)][_0x201612(0x316)],document[_0x201612(0x214)](_0x201612(0x2f3))[_0x201612(0x25e)]=_0x6c8a37['advanced'][_0x201612(0x2a9)],document['getElementById'](_0x201612(0x3de))[_0x201612(0x25e)]=_0x6c8a37[_0x201612(0x380)]['batchSize'],handleApiModeChange(),document[_0x201612(0x214)](_0x201612(0x399))[_0x201612(0x3a9)]=_0x6c8a37[_0x201612(0x2ec)]['enabled'],document[_0x201612(0x214)](_0x201612(0x2ad))[_0x201612(0x25e)]=_0x6c8a37[_0x201612(0x2ec)][_0x201612(0x3fa)],document[_0x201612(0x214)](_0x201612(0x1fb))[_0x201612(0x25e)]=_0x6c8a37[_0x201612(0x2ec)][_0x201612(0x403)],document[_0x201612(0x214)](_0x201612(0x383))[_0x201612(0x3a9)]=_0x6c8a37[_0x201612(0x2ec)][_0x201612(0x373)][_0x201612(0x26d)],document[_0x201612(0x214)](_0x201612(0x221))['checked']=_0x6c8a37['condensation'][_0x201612(0x373)]['ai'];const _0x680fd=document[_0x201612(0x214)](_0x201612(0x304)),_0x1e43f7=document[_0x201612(0x214)]('hly-tag-input'),_0x2d7c51=document[_0x201612(0x214)]('hly-tag-input-container');_0x680fd[_0x201612(0x3a9)]=_0x6c8a37[_0x201612(0x2ec)]['tagExtractionEnabled'],_0x1e43f7[_0x201612(0x25e)]=_0x6c8a37['condensation'][_0x201612(0x3ac)],_0x2d7c51[_0x201612(0x22b)][_0x201612(0x3a8)]=_0x680fd[_0x201612(0x3a9)]?'block':_0x201612(0x312),document[_0x201612(0x214)](_0x201612(0x2d8))[_0x201612(0x3a9)]=_0x6c8a37[_0x201612(0x28a)]['enabled'],document['getElementById'](_0x201612(0x3d0))[_0x201612(0x25e)]=_0x6c8a37[_0x201612(0x28a)][_0x201612(0x320)],document[_0x201612(0x214)](_0x201612(0x3a2))[_0x201612(0x25e)]=_0x6c8a37[_0x201612(0x28a)][_0x201612(0x206)];const _0x21e0db=document[_0x201612(0x214)]('hly-rerank-model');if(_0x21e0db[_0x201612(0x332)][_0x201612(0x341)]===0x0){const _0x32fc22=_0x6c8a37['rerank'][_0x201612(0x397)];if(_0x32fc22){const _0x3dff52=new Option(_0x32fc22,_0x32fc22,!![],!![]);_0x21e0db[_0x201612(0x1f6)](_0x3dff52);}}_0x21e0db[_0x201612(0x25e)]=_0x6c8a37[_0x201612(0x28a)][_0x201612(0x397)],document[_0x201612(0x214)]('hly-rerank-top-n')[_0x201612(0x25e)]=_0x6c8a37[_0x201612(0x28a)][_0x201612(0x38e)],document['getElementById']('hly-rerank-hybrid-alpha')[_0x201612(0x25e)]=_0x6c8a37[_0x201612(0x28a)][_0x201612(0x211)],document[_0x201612(0x214)](_0x201612(0x26c))[_0x201612(0x3a9)]=_0x6c8a37[_0x201612(0x28a)][_0x201612(0x406)],document['getElementById']('hly-super-sort-enabled')['checked']=_0x6c8a37[_0x201612(0x28a)][_0x201612(0x2b2)];const _0x11aba6=_0x6c8a37['rerank']['priorityRetrieval'];if(_0x11aba6){document[_0x201612(0x214)](_0x201612(0x3dc))[_0x201612(0x3a9)]=_0x11aba6[_0x201612(0x3ee)];const _0x328549=[_0x201612(0x38b),_0x201612(0x25a),_0x201612(0x2a1),_0x201612(0x395)];_0x328549['forEach'](_0x37eb92=>{const _0x37ba25=_0x201612,_0x733f84=_0x11aba6[_0x37ba25(0x3e3)][_0x37eb92];if(_0x733f84){const _0x517fd8=document['querySelector'](_0x37ba25(0x226)+_0x37eb92+'.enabled\x22]'),_0x38c290=document[_0x37ba25(0x30c)](_0x37ba25(0x226)+_0x37eb92+_0x37ba25(0x262));if(_0x517fd8)_0x517fd8[_0x37ba25(0x3a9)]=_0x733f84[_0x37ba25(0x3ee)];if(_0x38c290)_0x38c290['value']=_0x733f84[_0x37ba25(0x343)];}});}_0x6c8a37['queryPreprocessing']&&(document[_0x201612(0x214)](_0x201612(0x280))[_0x201612(0x3a9)]=_0x6c8a37['queryPreprocessing'][_0x201612(0x3ee)]),_0x6c8a37[_0x201612(0x380)][_0x201612(0x2fe)]!==undefined&&(document[_0x201612(0x214)](_0x201612(0x269))[_0x201612(0x3a9)]=_0x6c8a37[_0x201612(0x380)]['independentChatMemoryEnabled']);}function saveSettingsFromUI(_0x4956c3=!![]){const _0x4582fe=_0x28d2da,_0x59e8d8=document[_0x4582fe(0x214)](_0x4582fe(0x299));if(!_0x59e8d8)return;const _0x96a758=_0x59e8d8[_0x4582fe(0x285)](_0x4582fe(0x3fd));_0x96a758['forEach'](_0xc400f2=>{const _0x585818=_0x4582fe,_0x88e277=_0xc400f2[_0x585818(0x369)][_0x585818(0x215)];if(!_0x88e277)return;let _0x74d1e7;const _0x12e126=_0xc400f2['dataset'][_0x585818(0x31e)]||_0x585818(0x337);if(_0xc400f2[_0x585818(0x31e)]===_0x585818(0x325))_0x74d1e7=_0xc400f2['checked'];else{if(_0xc400f2['type']===_0x585818(0x2fa)){if(!_0xc400f2[_0x585818(0x3a9)])return;_0x74d1e7=_0xc400f2[_0x585818(0x25e)];}else _0x74d1e7=_0xc400f2[_0x585818(0x25e)];}switch(_0x12e126){case _0x585818(0x21e):_0x74d1e7=parseInt(_0x74d1e7,0xa);break;case _0x585818(0x2e3):_0x74d1e7=parseFloat(_0x74d1e7);break;case _0x585818(0x2e0):if(typeof _0x74d1e7!=='boolean')_0x74d1e7=_0x74d1e7===_0x585818(0x3bb);break;}const _0x10f567=_0x5a0ed1[_0x585818(0x22a)](),_0x544ffa=_0x88e277[_0x585818(0x3d5)]('.');let _0x535fc0=_0x10f567;for(let _0x5445e6=0x0;_0x5445e6<_0x544ffa[_0x585818(0x341)]-0x1;_0x5445e6++){_0x535fc0=_0x535fc0[_0x544ffa[_0x5445e6]]=_0x535fc0[_0x544ffa[_0x5445e6]]||{};}_0x535fc0[_0x544ffa[_0x544ffa[_0x585818(0x341)]-0x1]]=_0x74d1e7;}),_0x5a0ed1[_0x4582fe(0x342)](),!_0x4956c3&&(log(_0x4582fe(0x27c),_0x4582fe(0x224)),toastr[_0x4582fe(0x224)](_0x4582fe(0x22d),_0x4582fe(0x303)));}function resetSettingsToUI(){const _0x221d71=_0x28d2da;confirm(_0x221d71(0x3e5))&&(_0x5a0ed1[_0x221d71(0x2e2)](),loadSettingsToUI(),toastr[_0x221d71(0x390)](_0x221d71(0x24b),'诏曰'));}async function updatePanelStatus(){const _0x35cec2=_0x28d2da,_0x1cde32=_0x5a0ed1[_0x35cec2(0x274)](),_0x4c5e55=document[_0x35cec2(0x214)](_0x35cec2(0x35d)),_0x2248e6=document['getElementById'](_0x35cec2(0x277));if(_0x1cde32){const _0x32d178=_0x5a0ed1[_0x35cec2(0x2d4)]();_0x32d178&&(_0x4c5e55[_0x35cec2(0x3e1)]='会话已锁定',_0x2248e6[_0x35cec2(0x3e1)]=_0x32d178['id'],_0x2248e6['title']=_0x35cec2(0x33d)+_0x32d178['id'],_0x4c5e55[_0x35cec2(0x2c4)][_0x35cec2(0x1f6)](_0x35cec2(0x222)),_0x2248e6[_0x35cec2(0x2c4)]['add'](_0x35cec2(0x222)));}else _0x4c5e55['textContent']=_0x4966d0[_0x35cec2(0x1fd)](),_0x2248e6[_0x35cec2(0x3e1)]=_0x4966d0[_0x35cec2(0x276)]()||'无',_0x2248e6[_0x35cec2(0x27d)]='',_0x4c5e55['classList'][_0x35cec2(0x21a)](_0x35cec2(0x222)),_0x2248e6[_0x35cec2(0x2c4)][_0x35cec2(0x21a)](_0x35cec2(0x222));const _0x4f0a20=document[_0x35cec2(0x214)](_0x35cec2(0x2ef));_0x4f0a20[_0x35cec2(0x3e1)]=_0x35cec2(0x3b6);try{const _0x8f437e=await _0x5a0ed1[_0x35cec2(0x32c)]();_0x4f0a20[_0x35cec2(0x3e1)]=_0x8f437e;}catch(_0x5cfb5f){console[_0x35cec2(0x282)](_0x35cec2(0x1fa),_0x5cfb5f),_0x4f0a20[_0x35cec2(0x3e1)]=_0x35cec2(0x266),_0x4f0a20['title']='无法获取总数:\x20'+_0x5cfb5f[_0x35cec2(0x415)];}const _0x583b0c=document[_0x35cec2(0x214)](_0x35cec2(0x2ce));if(_0x583b0c&&!_0x583b0c[_0x35cec2(0x369)][_0x35cec2(0x25c)]){const _0x2318fc=_0x5a0ed1['getSettings'](),_0x105bf7=await _0x5a0ed1[_0x35cec2(0x2d3)]();if(_0x2318fc[_0x35cec2(0x23b)]&&_0x2318fc[_0x35cec2(0x23b)][_0x105bf7]){const _0x513227=_0x2318fc['condensationHistory'][_0x105bf7];_0x583b0c['innerHTML']='上次已从第\x20'+_0x513227['start']+'\x20楼凝识至第\x20'+_0x513227[_0x35cec2(0x2ee)]+_0x35cec2(0x2b0);}else _0x583b0c['innerHTML']='可在此预览凝识结果。

';}renderKnowledgeBases();}async function moveAllKnowledgeBases(_0x4d6d57){const _0x236ff5=_0x28d2da,_0x3d00e3=_0x4d6d57==='globalToLocal',_0x37a662=_0x3d00e3?_0x236ff5(0x328):_0x236ff5(0x2ed),_0x29df8c=_0x3d00e3?'局部':'全局',_0x5ddf43=_0x3d00e3?_0x5a0ed1[_0x236ff5(0x281)]():_0x5a0ed1[_0x236ff5(0x247)](),_0x54866b=Object['keys'](_0x5ddf43);if(_0x54866b['length']===0x0){toastr[_0x236ff5(0x390)](_0x236ff5(0x1f4)+(_0x3d00e3?'全局':'局部')+')没有任何知识库可供移动。','圣谕');return;}if(!confirm(_0x236ff5(0x270)+_0x54866b[_0x236ff5(0x341)]+_0x236ff5(0x2d0)+(_0x3d00e3?'全局':'局部')+_0x236ff5(0x349)+_0x29df8c+_0x236ff5(0x336)))return;log(_0x236ff5(0x289)+_0x54866b[_0x236ff5(0x341)]+_0x236ff5(0x260)+_0x37a662+'\x20移动到\x20'+(_0x3d00e3?_0x236ff5(0x2ed):_0x236ff5(0x328))+_0x236ff5(0x3b6),'info');const _0x5b8aac=_0x54866b['map'](_0xe86d8=>_0x5a0ed1['moveKnowledgeBase'](_0xe86d8,_0x37a662));try{await Promise[_0x236ff5(0x39d)](_0x5b8aac),toastr[_0x236ff5(0x224)](_0x236ff5(0x20c)+_0x54866b['length']+_0x236ff5(0x275),'大功告成'),log('批量移动完成。',_0x236ff5(0x224));}catch(_0x226574){toastr[_0x236ff5(0x282)](_0x236ff5(0x2ac)+_0x226574[_0x236ff5(0x415)],'警报'),log('批量移动失败:\x20'+_0x226574['message'],_0x236ff5(0x282));}finally{await updatePanelStatus();}}async function deleteAllLocalKnowledgeBases(){const _0x2f97d1=_0x28d2da,_0x10ff47=_0x5a0ed1[_0x2f97d1(0x247)](),_0x270144=Object[_0x2f97d1(0x3c9)](_0x10ff47);if(_0x270144[_0x2f97d1(0x341)]===0x0){toastr['info'](_0x2f97d1(0x1fe),'圣谕');return;}if(!confirm('您确定要永久删除【当前角色】的全部\x20'+_0x270144[_0x2f97d1(0x341)]+_0x2f97d1(0x2bf)))return;toastr[_0x2f97d1(0x390)]('正在删除\x20'+_0x270144[_0x2f97d1(0x341)]+_0x2f97d1(0x3ea),'圣旨'),log(_0x2f97d1(0x2e9)+_0x270144[_0x2f97d1(0x341)]+_0x2f97d1(0x3ea),_0x2f97d1(0x23d));let _0x3b3153=0x0,_0x6878c5=0x0;for(const _0x3a522c of _0x270144){try{await _0x5a0ed1[_0x2f97d1(0x2e1)](_0x3a522c,_0x2f97d1(0x2ed)),_0x3b3153++;}catch(_0x1b98ed){_0x6878c5++,log(_0x2f97d1(0x244)+_0x3a522c+_0x2f97d1(0x2b9)+_0x1b98ed[_0x2f97d1(0x415)],_0x2f97d1(0x282));}}_0x6878c5>0x0?toastr[_0x2f97d1(0x282)](_0x2f97d1(0x2cf)+_0x6878c5+_0x2f97d1(0x271),'警报'):toastr['success'](_0x2f97d1(0x20c)+_0x3b3153+_0x2f97d1(0x39e),_0x2f97d1(0x201)),log('局部知识库批量删除完成。成功:\x20'+_0x3b3153+',\x20失败:\x20'+_0x6878c5,_0x2f97d1(0x390)),await updatePanelStatus();}async function renderKnowledgeBases(){const _0x45c1a5=_0x28d2da,_0xcdf090=document[_0x45c1a5(0x214)](_0x45c1a5(0x34c)),_0x427456=document[_0x45c1a5(0x214)](_0x45c1a5(0x31c)),_0x156ed5=document['getElementById'](_0x45c1a5(0x2c2));if(!_0xcdf090||!_0x427456||!_0x156ed5)return;_0x156ed5[_0x45c1a5(0x3e1)]=_0x4966d0[_0x45c1a5(0x1fd)]()||_0x45c1a5(0x24c);try{const _0x43e82e=_0x5a0ed1[_0x45c1a5(0x247)](),_0x1eab4d=_0x5a0ed1[_0x45c1a5(0x281)]();await _renderKbList(_0x43e82e,_0xcdf090,_0x45c1a5(0x2ed),'hly-kb-list-local-placeholder'),await _renderKbList(_0x1eab4d,_0x427456,_0x45c1a5(0x328),'hly-kb-list-global-placeholder');}catch(_0x3aaa5e){console[_0x45c1a5(0x282)](_0x45c1a5(0x2c7),_0x3aaa5e),_0xcdf090[_0x45c1a5(0x30a)]=_0x45c1a5(0x288)+_0x3aaa5e[_0x45c1a5(0x415)]+_0x45c1a5(0x3df),_0x427456[_0x45c1a5(0x30a)]=_0x45c1a5(0x288)+_0x3aaa5e['message']+_0x45c1a5(0x3df);}}async function _renderKbList(_0x1fe686,_0xd46886,_0x2719f1,_0x1547d9){const _0x88d619=_0x28d2da,_0x34ba84=document[_0x88d619(0x214)](_0x1547d9);_0xd46886[_0x88d619(0x30a)]='',_0xd46886[_0x88d619(0x366)](_0x34ba84);if(Object[_0x88d619(0x3c9)](_0x1fe686)[_0x88d619(0x341)]===0x0){_0x34ba84[_0x88d619(0x22b)][_0x88d619(0x3a8)]=_0x88d619(0x307);return;}_0x34ba84[_0x88d619(0x22b)][_0x88d619(0x3a8)]=_0x88d619(0x312);for(const [_0x1219d4,_0x348d35]of Object[_0x88d619(0x34b)](_0x1fe686)){const _0x1aff8c=document['createElement'](_0x88d619(0x272));_0x1aff8c['className']=_0x88d619(0x36b),_0x1aff8c[_0x88d619(0x369)]['kbId']=_0x1219d4,_0x1aff8c['dataset']['kbScope']=_0x2719f1;const _0x25956c=await _0x5a0ed1['getVectorCount'](_0x1219d4,_0x2719f1),_0x5f5d0e=_0x2719f1===_0x88d619(0x2ed)?_0x88d619(0x351):_0x88d619(0x3cb);_0x1aff8c[_0x88d619(0x30a)]=_0x88d619(0x33c)+_0x1219d4+'\x22>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'+_0x348d35['name']+'\x20('+_0x25956c+_0x88d619(0x2be)+_0x5f5d0e+_0x88d619(0x28e)+(_0x348d35[_0x88d619(0x3ee)]?_0x88d619(0x3a9):'')+_0x88d619(0x36d),_0xd46886[_0x88d619(0x366)](_0x1aff8c);}}async function handleKbAction(_0x2949b5){const _0x15573c=_0x28d2da,_0x135783=_0x2949b5['target'],_0x37c968=_0x135783[_0x15573c(0x265)](_0x15573c(0x2bd));if(!_0x37c968)return;const _0x46a723=_0x37c968[_0x15573c(0x369)][_0x15573c(0x3b0)],_0x31e3da=_0x37c968[_0x15573c(0x369)]['kbScope'],_0x768d10=_0x37c968['querySelector'](_0x15573c(0x2d1))[_0x15573c(0x3e1)]['split']('\x20(')[0x0];if(_0x135783[_0x15573c(0x265)](_0x15573c(0x23f))){const _0x5bdf91=_0x37c968[_0x15573c(0x30c)](_0x15573c(0x2d1))[_0x15573c(0x3e1)]['split']('\x20(')[0x0],_0x4b788f=prompt(_0x15573c(0x20b),_0x5bdf91);if(_0x4b788f&&_0x4b788f[_0x15573c(0x35c)]()&&_0x4b788f[_0x15573c(0x35c)]()!==_0x5bdf91)try{await _0x5a0ed1[_0x15573c(0x2f2)](_0x46a723,_0x4b788f,_0x31e3da),await updatePanelStatus();}catch(_0x4e04c2){log('重命名知识库\x20'+_0x5bdf91+_0x15573c(0x2b9)+_0x4e04c2[_0x15573c(0x415)],'error'),toastr['error']('重命名失败:\x20'+_0x4e04c2[_0x15573c(0x415)]);}return;}if(_0x135783[_0x15573c(0x2c4)][_0x15573c(0x310)](_0x15573c(0x356))){if(confirm(_0x15573c(0x31d)+_0x768d10+'】吗?此操作无法恢复!'))try{await _0x5a0ed1[_0x15573c(0x2e1)](_0x46a723,_0x31e3da),log('知识库\x20'+_0x768d10+_0x15573c(0x2a8)+_0x46a723+_0x15573c(0x21d),'success'),toastr[_0x15573c(0x224)](_0x15573c(0x3ad)+_0x768d10+_0x15573c(0x242)),await updatePanelStatus();}catch(_0x35f935){log(_0x15573c(0x2e7)+_0x768d10+_0x15573c(0x2b9)+_0x35f935[_0x15573c(0x415)],'error'),toastr[_0x15573c(0x282)](_0x15573c(0x249)+_0x35f935[_0x15573c(0x415)]);}}if(_0x135783[_0x15573c(0x265)](_0x15573c(0x388))){const _0x5f358e=_0x31e3da===_0x15573c(0x2ed)?'全局':'局部';if(confirm(_0x15573c(0x3e7)+_0x768d10+'】移动到【'+_0x5f358e+'】吗?'))try{await _0x5a0ed1[_0x15573c(0x315)](_0x46a723,_0x31e3da),await updatePanelStatus();}catch(_0x3be913){log(_0x15573c(0x313)+_0x768d10+_0x15573c(0x2b9)+_0x3be913['message'],_0x15573c(0x282)),toastr[_0x15573c(0x282)](_0x15573c(0x209)+_0x3be913[_0x15573c(0x415)]);}}if(_0x135783[_0x15573c(0x2c4)][_0x15573c(0x310)](_0x15573c(0x404))&&_0x2949b5[_0x15573c(0x31e)]==='change')try{await _0x5a0ed1[_0x15573c(0x2f0)](_0x46a723,_0x31e3da),log(_0x15573c(0x368)+_0x768d10+_0x15573c(0x39b),_0x15573c(0x224));}catch(_0x4ea435){log(_0x15573c(0x411)+_0x768d10+'\x20状态失败:\x20'+_0x4ea435[_0x15573c(0x415)],'error'),toastr[_0x15573c(0x282)]('切换状态失败:\x20'+_0x4ea435[_0x15573c(0x415)]),_0x135783['checked']=!_0x135783['checked'];}_0x135783[_0x15573c(0x2c4)]['contains']('hly-kb-item-checkbox')&&_0x2949b5[_0x15573c(0x31e)]===_0x15573c(0x1fc)&&updateBulkActionUI(_0x31e3da);}function handleSelectAll(_0x3d619a,_0xdc1be9){const _0x2e695f=_0x28d2da,_0x44b69a=_0x3d619a['target']['checked'],_0x57b2a8=document[_0x2e695f(0x214)](_0x2e695f(0x40b)+_0xdc1be9),_0x1a620a=_0x57b2a8[_0x2e695f(0x285)](_0x2e695f(0x362));_0x1a620a[_0x2e695f(0x3dd)](_0x2cbad1=>_0x2cbad1[_0x2e695f(0x3a9)]=_0x44b69a),updateBulkActionUI(_0xdc1be9);}function updateBulkActionUI(_0x24846e){const _0x51595c=_0x28d2da,_0x3812cc=document[_0x51595c(0x214)](_0x51595c(0x40b)+_0x24846e),_0x2ab30f=document[_0x51595c(0x214)](_0x51595c(0x389)+_0x24846e),_0x23a3df=document['getElementById'](_0x51595c(0x317)+_0x24846e),_0x1dc6f2=_0x3812cc['querySelectorAll'](_0x51595c(0x362)),_0x22870f=_0x3812cc[_0x51595c(0x285)](_0x51595c(0x405)),_0x4e8740=_0x22870f[_0x51595c(0x341)],_0x4bc76d=_0x1dc6f2[_0x51595c(0x341)];_0x4e8740>0x0?_0x2ab30f[_0x51595c(0x22b)][_0x51595c(0x3a8)]=_0x51595c(0x2f1):_0x2ab30f[_0x51595c(0x22b)][_0x51595c(0x3a8)]=_0x51595c(0x312);if(_0x4bc76d===0x0)_0x23a3df['checked']=![],_0x23a3df[_0x51595c(0x3be)]=![];else{if(_0x4e8740===_0x4bc76d)_0x23a3df[_0x51595c(0x3a9)]=!![],_0x23a3df[_0x51595c(0x3be)]=![];else _0x4e8740>0x0?(_0x23a3df[_0x51595c(0x3a9)]=![],_0x23a3df[_0x51595c(0x3be)]=!![]):(_0x23a3df[_0x51595c(0x3a9)]=![],_0x23a3df[_0x51595c(0x3be)]=![]);}}async function handleBulkAction(_0x1870d1,_0xa62ec3){const _0x4eab20=_0x28d2da,_0x2e9826=_0x1870d1[_0x4eab20(0x335)]['dataset']['action'];if(!_0x2e9826)return;const _0x19b794=document[_0x4eab20(0x214)](_0x4eab20(0x40b)+_0xa62ec3),_0x443bd2=_0x19b794[_0x4eab20(0x285)]('.hly-kb-item-checkbox:checked'),_0x413b68=Array[_0x4eab20(0x3fb)](_0x443bd2)[_0x4eab20(0x204)](_0x3002be=>_0x3002be[_0x4eab20(0x369)][_0x4eab20(0x3b0)]);if(_0x413b68[_0x4eab20(0x341)]===0x0){toastr[_0x4eab20(0x40a)](_0x4eab20(0x38f),'圣谕');return;}let _0x1fd9ac='',_0xc813,_0x2d9e1c='';switch(_0x2e9826){case _0x4eab20(0x2dd):_0x1fd9ac='您确定要永久删除选中的\x20'+_0x413b68[_0x4eab20(0x341)]+_0x4eab20(0x3bf),_0xc813=_0x3cdb61=>_0x5a0ed1[_0x4eab20(0x2e1)](_0x3cdb61,_0xa62ec3),_0x2d9e1c='成功删除了\x20'+_0x413b68[_0x4eab20(0x341)]+_0x4eab20(0x2e5);break;case _0x4eab20(0x25f):const _0x40895d=_0xa62ec3===_0x4eab20(0x2ed)?'全局':'局部';_0x1fd9ac=_0x4eab20(0x239)+_0x413b68['length']+_0x4eab20(0x3e0)+_0x40895d+_0x4eab20(0x336),_0xc813=_0x4544ff=>_0x5a0ed1[_0x4eab20(0x315)](_0x4544ff,_0xa62ec3),_0x2d9e1c=_0x4eab20(0x416)+_0x413b68[_0x4eab20(0x341)]+_0x4eab20(0x2e5);break;case _0x4eab20(0x29b):_0x1fd9ac=_0x4eab20(0x2b8)+_0x413b68[_0x4eab20(0x341)]+_0x4eab20(0x3ca),_0xc813=_0x9d6dee=>_0x5a0ed1[_0x4eab20(0x2f0)](_0x9d6dee,_0xa62ec3),_0x2d9e1c=_0x4eab20(0x2e6)+_0x413b68[_0x4eab20(0x341)]+_0x4eab20(0x3f3);break;default:return;}if(!confirm(_0x1fd9ac))return;toastr[_0x4eab20(0x390)](_0x4eab20(0x3c0)+_0x413b68[_0x4eab20(0x341)]+_0x4eab20(0x324),'圣旨'),log(_0x4eab20(0x3c4)+_0x413b68[_0x4eab20(0x341)]+_0x4eab20(0x33b)+_0xa62ec3+_0x4eab20(0x213)+_0x2e9826+'\x20操作...',_0x4eab20(0x390));try{const _0x3c4879=_0x413b68[_0x4eab20(0x204)](_0x56534b=>_0xc813(_0x56534b));await Promise[_0x4eab20(0x39d)](_0x3c4879),toastr[_0x4eab20(0x224)](_0x2d9e1c,_0x4eab20(0x201)),log('批量\x20'+_0x2e9826+_0x4eab20(0x3d3),'success');}catch(_0x4fce3c){toastr['error'](_0x4eab20(0x3f0)+_0x4fce3c[_0x4eab20(0x415)],'警报'),log(_0x4eab20(0x20d)+_0x2e9826+'\x20操作失败:\x20'+_0x4fce3c[_0x4eab20(0x415)],_0x4eab20(0x282));}finally{await updatePanelStatus();}}async function testApi(){const _0x2cf2bf=_0x28d2da;toastr['info'](_0x2cf2bf(0x220),'圣旨');try{await _0x5a0ed1[_0x2cf2bf(0x2f9)](),toastr[_0x2cf2bf(0x224)]('神力连接通畅!','圣意');}catch(_0x197973){toastr[_0x2cf2bf(0x282)](_0x2cf2bf(0x340)+_0x197973[_0x2cf2bf(0x415)],'警报');}}async function fetchHLYEmbeddingModels(){const _0x59a464=_0x28d2da,_0x57d860=document[_0x59a464(0x214)](_0x59a464(0x261)),_0x424e74=_0x57d860[_0x59a464(0x25e)];_0x57d860[_0x59a464(0x30a)]=_0x59a464(0x23c),_0x57d860[_0x59a464(0x298)]=!![];try{log('开始获取模型列表...','info');const _0xcbff8c=await _0x5a0ed1[_0x59a464(0x339)]();_0x57d860[_0x59a464(0x30a)]='';if(_0xcbff8c['length']===0x0){_0x57d860[_0x59a464(0x30a)]='',toastr[_0x59a464(0x23d)](_0x59a464(0x34d),_0x59a464(0x294)),log(_0x59a464(0x34d),_0x59a464(0x23d));return;}_0xcbff8c[_0x59a464(0x3dd)](_0x5366f2=>{const _0x3e6a0c=new Option(_0x5366f2,_0x5366f2);_0x57d860['add'](_0x3e6a0c);}),_0xcbff8c[_0x59a464(0x252)](_0x424e74)?_0x57d860[_0x59a464(0x25e)]=_0x424e74:_0x57d860[_0x59a464(0x346)]=0x0,toastr['success'](_0x59a464(0x3ff)+_0xcbff8c[_0x59a464(0x341)]+_0x59a464(0x284),'圣意'),log(_0x59a464(0x3ff)+_0xcbff8c[_0x59a464(0x341)]+'\x20个模型。',_0x59a464(0x224));}catch(_0x31afbb){console[_0x59a464(0x282)](_0x59a464(0x40d),_0x31afbb),toastr[_0x59a464(0x282)](_0x59a464(0x24a)+_0x31afbb[_0x59a464(0x415)],_0x59a464(0x1f8)),log('获取模型失败:\x20'+_0x31afbb[_0x59a464(0x415)],_0x59a464(0x282)),_0x57d860[_0x59a464(0x30a)]=_0x59a464(0x367);}finally{_0x57d860['disabled']=![];}}async function fetchHLYRerankModels(){const _0x4bc037=_0x28d2da,_0x5a77d2=document[_0x4bc037(0x214)]('hly-rerank-model'),_0x5c4c7f=_0x5a77d2['value'];_0x5a77d2[_0x4bc037(0x30a)]=_0x4bc037(0x23c),_0x5a77d2['disabled']=!![];try{log('开始获取Rerank模型列表...',_0x4bc037(0x390));const _0x7f41c9=await _0x5a0ed1['fetchRerankModels']();_0x5a77d2[_0x4bc037(0x30a)]='';if(_0x7f41c9[_0x4bc037(0x341)]===0x0){_0x5a77d2[_0x4bc037(0x30a)]=_0x4bc037(0x3da),toastr[_0x4bc037(0x23d)](_0x4bc037(0x35e),_0x4bc037(0x294)),log(_0x4bc037(0x35e),'warn');return;}_0x7f41c9[_0x4bc037(0x3dd)](_0x3ab806=>{const _0x32eaaf=new Option(_0x3ab806,_0x3ab806);_0x5a77d2['add'](_0x32eaaf);}),_0x7f41c9[_0x4bc037(0x252)](_0x5c4c7f)?_0x5a77d2['value']=_0x5c4c7f:_0x5a77d2[_0x4bc037(0x346)]=0x0,toastr[_0x4bc037(0x224)]('成功获取\x20'+_0x7f41c9[_0x4bc037(0x341)]+_0x4bc037(0x3bd),'圣意'),log('成功获取\x20'+_0x7f41c9[_0x4bc037(0x341)]+'\x20个Rerank模型。',_0x4bc037(0x224));}catch(_0x5853a1){console[_0x4bc037(0x282)](_0x4bc037(0x398),_0x5853a1),toastr[_0x4bc037(0x282)](_0x4bc037(0x268)+_0x5853a1[_0x4bc037(0x415)],_0x4bc037(0x1f8)),log(_0x4bc037(0x268)+_0x5853a1[_0x4bc037(0x415)],_0x4bc037(0x282)),_0x5a77d2[_0x4bc037(0x30a)]=_0x4bc037(0x367);}finally{_0x5a77d2['disabled']=![];}}async function purgeStorage(){const _0x146c44=_0x28d2da;if(confirm('此操作将彻底清空当前角色的所有忆识(向量),且无法恢复。您确定要继续吗?')){toastr['info'](_0x146c44(0x2b6),'圣旨');const _0x36cd20=await _0x5a0ed1['purgeStorage']();_0x36cd20?toastr[_0x146c44(0x224)](_0x146c44(0x2f8),'圣意'):toastr[_0x146c44(0x282)](_0x146c44(0x3b7),'警报'),await updatePanelStatus();}}async function startCondensation(){const _0x3032a9=_0x28d2da,_0x406056=document[_0x3032a9(0x214)](_0x3032a9(0x2ce)),_0xea97d0=_0x406056['dataset']['finalMessages'],_0x1c7782=document[_0x3032a9(0x214)](_0x3032a9(0x2ad))[_0x3032a9(0x25e)],_0xf1843=document[_0x3032a9(0x214)]('hly-layer-end')['value'],_0x456d14={'start':parseInt(_0x1c7782),'end':parseInt(_0xf1843)};try{let _0x4c6b36;_0xea97d0?(log('检测到预览后待处理的消息对象,开始精确凝识...',_0x3032a9(0x390)),toastr['info'](_0x3032a9(0x38a),'圣旨'),_0x4c6b36=JSON['parse'](_0xea97d0),delete _0x406056['dataset'][_0x3032a9(0x372)]):(log('未检测到预览文本,按标准流程采集消息...',_0x3032a9(0x390)),toastr[_0x3032a9(0x390)](_0x3032a9(0x2ab),'圣旨'),_0x4c6b36=_0x5a0ed1[_0x3032a9(0x205)]());if(!_0x4c6b36||_0x4c6b36[_0x3032a9(0x341)]===0x0){toastr[_0x3032a9(0x40a)]('未找到符合条件的消息可供凝识。',_0x3032a9(0x294)),_0x406056[_0x3032a9(0x3e1)]=_0x3032a9(0x330);return;}_0x406056['textContent']=_0x3032a9(0x308)+_0x4c6b36[_0x3032a9(0x341)]+_0x3032a9(0x30f),toastr['info']('已采集\x20'+_0x4c6b36[_0x3032a9(0x341)]+_0x3032a9(0x30f),_0x3032a9(0x294));const _0x3ca729=await _0x5a0ed1[_0x3032a9(0x333)](_0x4c6b36,log,_0x456d14);if(_0x3ca729[_0x3032a9(0x224)]){toastr['success'](_0x3032a9(0x218)+_0x3ca729['count']+_0x3032a9(0x3f5),_0x3032a9(0x201));const _0xa6e99b=_0x456d14[_0x3032a9(0x2ee)]===0x0?getContext()[_0x3032a9(0x37e)][_0x3032a9(0x341)]:_0x456d14[_0x3032a9(0x2ee)];_0x406056[_0x3032a9(0x3e1)]=_0x3032a9(0x2af)+_0x456d14[_0x3032a9(0x412)]+_0x3032a9(0x258)+_0xa6e99b+_0x3032a9(0x253)+_0x3ca729[_0x3032a9(0x343)]+'\x20条忆识。';}else throw new Error(_0x3ca729[_0x3032a9(0x282)]||'未知错误');}catch(_0x1b6417){console[_0x3032a9(0x282)](_0x3032a9(0x2d5),_0x1b6417),toastr[_0x3032a9(0x282)]('凝识失败:\x20'+_0x1b6417['message'],_0x3032a9(0x1f8)),_0x406056[_0x3032a9(0x3e1)]='凝识失败:\x20'+_0x1b6417['message'];}finally{await updatePanelStatus();}}async function loadWorldbookList(){const _0x1d9af3=_0x28d2da,_0xc40cdb=document['getElementById'](_0x1d9af3(0x3c2)),_0x43c325=document[_0x1d9af3(0x214)]('hly-worldbook-search');if(!_0xc40cdb)return;try{log(_0x1d9af3(0x361),_0x1d9af3(0x390));const _0x511552=await _0x798f38[_0x1d9af3(0x208)]();window[_0x1d9af3(0x24d)]=_0x511552,updateWorldbookOptions(_0xc40cdb,'',_0x511552);if(_0x43c325){const _0x341d5f=debounce(_0x2c9f20=>{updateWorldbookOptions(_0xc40cdb,_0x2c9f20,_0x511552);},0x12c);_0x43c325[_0x1d9af3(0x29a)](_0x1d9af3(0x235),_0x30337c=>{const _0x362325=_0x1d9af3;_0x341d5f(_0x30337c[_0x362325(0x335)]['value']);});}log(_0x1d9af3(0x2f5)+_0x511552[_0x1d9af3(0x341)]+_0x1d9af3(0x225),_0x1d9af3(0x224));}catch(_0x4e7bfd){console[_0x1d9af3(0x282)](_0x1d9af3(0x3a0),_0x4e7bfd),log(_0x1d9af3(0x26b)+_0x4e7bfd[_0x1d9af3(0x415)],_0x1d9af3(0x282)),_0xc40cdb&&(_0xc40cdb[_0x1d9af3(0x30a)]=_0x1d9af3(0x348));}}function updateWorldbookOptions(_0x1ab03d,_0x48a496,_0x380b5c){const _0x29806c=_0x28d2da,_0x12bbd9=filterWorldbooks(_0x48a496,_0x380b5c),_0x468e7c=_0x1ab03d[_0x29806c(0x25e)];_0x1ab03d[_0x29806c(0x30a)]='请选择一个书库...';if(_0x12bbd9[_0x29806c(0x341)]===0x0){_0x1ab03d[_0x29806c(0x30a)]=_0x48a496[_0x29806c(0x35c)]()?_0x29806c(0x3e9):_0x29806c(0x32e);return;}_0x12bbd9[_0x29806c(0x3dd)](_0x58323d=>{const _0x1904ec=_0x29806c,_0x2e8091=document[_0x1904ec(0x217)](_0x1904ec(0x20e));_0x2e8091[_0x1904ec(0x25e)]=_0x58323d,_0x2e8091[_0x1904ec(0x3e1)]=_0x58323d,_0x1ab03d[_0x1904ec(0x366)](_0x2e8091);}),_0x468e7c&&_0x12bbd9[_0x29806c(0x252)](_0x468e7c)&&(_0x1ab03d[_0x29806c(0x25e)]=_0x468e7c);}async function handleWorldbookSelectionChange(){const _0x317bdd=_0x28d2da,_0x55b3eb=document[_0x317bdd(0x214)](_0x317bdd(0x3c2)),_0x10e159=document['getElementById'](_0x317bdd(0x286)),_0x314b32=document[_0x317bdd(0x214)](_0x317bdd(0x375)),_0x3e8e0d=document[_0x317bdd(0x214)](_0x317bdd(0x241)),_0x18b27d=_0x55b3eb[_0x317bdd(0x25e)];_0x10e159[_0x317bdd(0x298)]=!![],_0x10e159['querySelector'](_0x317bdd(0x2cd))[_0x317bdd(0x3e1)]=_0x317bdd(0x3c8),_0x314b32[_0x317bdd(0x30a)]='',_0x314b32[_0x317bdd(0x22b)][_0x317bdd(0x3a8)]=_0x317bdd(0x312);_0x3e8e0d&&(_0x3e8e0d[_0x317bdd(0x25e)]='');if(!_0x18b27d){_0x10e159[_0x317bdd(0x30c)]('span')[_0x317bdd(0x3e1)]=_0x317bdd(0x2d7);return;}try{log(_0x317bdd(0x2c9)+_0x18b27d+_0x317bdd(0x292),_0x317bdd(0x390));const _0x3673c0=await _0x798f38['getLoresForWorldbook'](_0x18b27d);if(_0x3673c0['length']===0x0){_0x10e159[_0x317bdd(0x30c)](_0x317bdd(0x2cd))['textContent']=_0x317bdd(0x3b5);return;}window[_0x317bdd(0x302)]=_0x3673c0,updateEntryOptions('',_0x3673c0);if(_0x3e8e0d){_0x3e8e0d[_0x317bdd(0x3a3)]('input',_0x3e8e0d[_0x317bdd(0x410)]);const _0x22638e=debounce(_0x5d2f73=>{updateEntryOptions(_0x5d2f73,_0x3673c0);},0x12c);_0x3e8e0d[_0x317bdd(0x410)]=_0x331f66=>{const _0x4855ec=_0x317bdd;_0x22638e(_0x331f66[_0x4855ec(0x335)]['value']);},_0x3e8e0d[_0x317bdd(0x29a)]('input',_0x3e8e0d[_0x317bdd(0x410)]);}log(_0x317bdd(0x2f5)+_0x3673c0[_0x317bdd(0x341)]+_0x317bdd(0x279),'success');}catch(_0x1d9eb0){console[_0x317bdd(0x282)](_0x317bdd(0x3fc)+_0x18b27d+_0x317bdd(0x309),_0x1d9eb0),log('加载条目失败:\x20'+_0x1d9eb0[_0x317bdd(0x415)],_0x317bdd(0x282)),_0x10e159[_0x317bdd(0x30c)](_0x317bdd(0x2cd))[_0x317bdd(0x3e1)]=_0x317bdd(0x3f4);}finally{_0x10e159[_0x317bdd(0x298)]=![];}}function updateEntryOptions(_0x32cadc,_0x408bcd){const _0x1c56bb=_0x28d2da,_0x4245ea=document['getElementById'](_0x1c56bb(0x375)),_0x1f8be3=document[_0x1c56bb(0x214)]('hly-hist-entry-multiselect-btn'),_0x268ecd=filterWorldbookEntries(_0x32cadc,_0x408bcd);_0x4245ea[_0x1c56bb(0x30a)]='';const _0x3c688c='\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20全选/全不选\x0a\x20\x20\x20\x20\x20\x20\x20\x20';_0x4245ea[_0x1c56bb(0x231)](_0x1c56bb(0x1f5),_0x3c688c);if(_0x268ecd[_0x1c56bb(0x341)]===0x0){const _0x4f4fc3=_0x1c56bb(0x376);_0x4245ea[_0x1c56bb(0x231)](_0x1c56bb(0x1f5),_0x4f4fc3),_0x1f8be3['querySelector'](_0x1c56bb(0x2cd))[_0x1c56bb(0x3e1)]=_0x1c56bb(0x2e8);return;}_0x268ecd[_0x1c56bb(0x3dd)](_0x475e32=>{const _0x429275=_0x1c56bb,_0x4143e3=_0x32cadc?highlightSearchMatch(_0x475e32[_0x429275(0x3e8)],_0x32cadc):_0x475e32[_0x429275(0x3e8)],_0x442cc1=_0x429275(0x3eb)+_0x475e32[_0x429275(0x3e8)]+'\x20(Key:\x20'+_0x475e32[_0x429275(0x203)]+_0x429275(0x37b)+_0x475e32[_0x429275(0x203)]+'\x22>\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'+_0x4143e3+_0x429275(0x233);_0x4245ea[_0x429275(0x231)](_0x429275(0x1f5),_0x442cc1);}),_0x1f8be3['querySelector']('span')[_0x1c56bb(0x3e1)]='已选择\x200\x20/\x20'+_0x268ecd['length']+_0x1c56bb(0x37c);}async function startHistoriography(){const _0x5ca924=_0x28d2da,_0x41eb45=document['getElementById'](_0x5ca924(0x3c2))[_0x5ca924(0x25e)],_0x225cfd=document[_0x5ca924(0x214)](_0x5ca924(0x375)),_0x52164b=document[_0x5ca924(0x214)](_0x5ca924(0x264)),_0x29394a=Array[_0x5ca924(0x3fb)](_0x225cfd['querySelectorAll']('.hly-hist-entry-checkbox:checked'))['map'](_0x38a306=>_0x38a306[_0x5ca924(0x25e)]);if(!_0x41eb45||_0x29394a['length']===0x0){toastr[_0x5ca924(0x40a)](_0x5ca924(0x378),_0x5ca924(0x3a6));return;}_0x52164b['textContent']=_0x5ca924(0x323)+_0x41eb45+_0x5ca924(0x3c1)+_0x29394a[_0x5ca924(0x341)]+_0x5ca924(0x40c),toastr[_0x5ca924(0x390)]('批量编纂任务已开始...','圣旨'),log(_0x5ca924(0x377)+_0x41eb45+_0x5ca924(0x3c1)+_0x29394a[_0x5ca924(0x341)]+_0x5ca924(0x3d4),_0x5ca924(0x390));try{const _0xe741a8=await _0x798f38[_0x5ca924(0x32f)](_0x41eb45,_0x29394a);_0x52164b[_0x5ca924(0x3e1)]=_0xe741a8[_0x5ca924(0x3b2)],_0xe741a8[_0x5ca924(0x224)]?toastr[_0x5ca924(0x224)](_0x5ca924(0x29c),'大功告成'):toastr['warning'](_0x5ca924(0x3a4),'圣谕'),log('对《'+_0x41eb45+_0x5ca924(0x250)+_0xe741a8['totalSuccess']+_0x5ca924(0x33e)+_0xe741a8[_0x5ca924(0x273)],_0x5ca924(0x224));}catch(_0x295926){console[_0x5ca924(0x282)](_0x5ca924(0x36a),_0x295926),toastr[_0x5ca924(0x282)](_0x5ca924(0x290)+_0x295926[_0x5ca924(0x415)],_0x5ca924(0x1f8)),_0x52164b['textContent']=_0x5ca924(0x290)+_0x295926['message'];}finally{await updatePanelStatus();}}async function showStats(){const _0x2eac30=_0x28d2da;try{log(_0x2eac30(0x3ce),'info'),toastr[_0x2eac30(0x390)](_0x2eac30(0x347),'圣旨');const _0x442c90=await _0x5a0ed1[_0x2eac30(0x32c)](),_0x4bf186=await _0x5a0ed1[_0x2eac30(0x2d3)](),_0x495c47=_0x5a0ed1[_0x2eac30(0x22a)](),_0x5e05b5='\x0a
\x0a翰林院宝库状态\x0a--------------------\x0a集合ID:\x20'+_0x4bf186+_0x2eac30(0x22f)+_0x442c90+_0x2eac30(0x25d)+_0x495c47[_0x2eac30(0x380)][_0x2eac30(0x25b)]+'\x0a所用模型:\x20'+_0x495c47['retrieval'][_0x2eac30(0x35b)]+_0x2eac30(0x39c);toastr['info'](_0x5e05b5,_0x2eac30(0x311),{'timeOut':0x3a98,'extendedTimeOut':0x1388,'tapToDismiss':!![],'closeButton':!![]}),log('查看宝库状态成功:集合ID='+_0x4bf186+_0x2eac30(0x381)+_0x442c90,_0x2eac30(0x224));}catch(_0x4be326){console['error'](_0x2eac30(0x2b3),_0x4be326),toastr[_0x2eac30(0x282)](_0x2eac30(0x27a)+_0x4be326[_0x2eac30(0x415)],_0x2eac30(0x1f8)),log('查询宝库状态失败:\x20'+_0x4be326['message'],_0x2eac30(0x282));}}function showRulesModal(_0x13e2f7){const _0x28fbc9=_0x28d2da,_0x3b2d01=_0x5a0ed1['getSettings'](),_0x4f2d11=_0x3b2d01[_0x13e2f7];if(!_0x4f2d11){console[_0x28fbc9(0x282)](_0x28fbc9(0x31b)+_0x13e2f7+_0x28fbc9(0x21f));return;}const _0x1a3ef3=_0x13e2f7===_0x28fbc9(0x2ec)?_0x28fbc9(0x3f7):_0x28fbc9(0x32a),_0x27f247=_0x4f2d11['exclusionRules']||[],_0x142892=(_0x3b8cd4={'start':'','end':''},_0x568e6c)=>_0x28fbc9(0x37f)+_0x568e6c+_0x28fbc9(0x29f)+(_0x3b8cd4[_0x28fbc9(0x412)]||'')['replace'](/"/g,'\x22')+_0x28fbc9(0x3b8)+(_0x3b8cd4[_0x28fbc9(0x2ee)]||'')[_0x28fbc9(0x2fb)](/"/g,'\x22')+_0x28fbc9(0x227),_0x40225e=_0x27f247[_0x28fbc9(0x204)](_0x142892)['join'](''),_0x5b4381=_0x13e2f7===_0x28fbc9(0x2bb)?_0x28fbc9(0x20f)+(_0x4f2d11[_0x28fbc9(0x3d2)]?_0x28fbc9(0x3a9):'')+_0x28fbc9(0x2b7)+(_0x4f2d11['tagExtractionEnabled']?_0x28fbc9(0x307):_0x28fbc9(0x312))+_0x28fbc9(0x2f7)+(_0x4f2d11[_0x28fbc9(0x3ac)]||'')+_0x28fbc9(0x38c):'',_0x9e2b71=_0x28fbc9(0x3fe)+_0x5b4381+_0x28fbc9(0x3e4)+(_0x40225e[_0x28fbc9(0x341)]>0x0?_0x40225e:_0x28fbc9(0x28c))+_0x28fbc9(0x2c8);showHtmlModal(_0x1a3ef3,_0x9e2b71,{'okText':_0x28fbc9(0x212),'onOk':_0x44256b=>{const _0x4d1ee0=_0x28fbc9,_0x108c96=[];_0x44256b[_0x4d1ee0(0x26a)](_0x4d1ee0(0x2ff))[_0x4d1ee0(0x2ea)](function(){const _0x3242f9=_0x4d1ee0,_0x365829=$(this)['find'](_0x3242f9(0x235))['eq'](0x0)[_0x3242f9(0x2f6)]()[_0x3242f9(0x35c)](),_0x4651ee=$(this)[_0x3242f9(0x26a)](_0x3242f9(0x235))['eq'](0x1)['val']()[_0x3242f9(0x35c)]();_0x365829&&_0x108c96['push']({'start':_0x365829,'end':_0x4651ee});});const _0x2066c9={..._0x4f2d11,'exclusionRules':_0x108c96};_0x13e2f7==='queryPreprocessing'&&(_0x2066c9[_0x4d1ee0(0x3d2)]=_0x44256b['find'](_0x4d1ee0(0x1f9))['is'](':checked'),_0x2066c9['tags']=_0x44256b[_0x4d1ee0(0x26a)](_0x4d1ee0(0x223))[_0x4d1ee0(0x2f6)]()),updateAndSaveSetting(_0x13e2f7,_0x2066c9),toastr[_0x4d1ee0(0x224)](_0x4d1ee0(0x2bc),_0x4d1ee0(0x303));},'onShow':_0x38a410=>{const _0x168f88=_0x28fbc9,_0x1e02ae=_0x38a410[_0x168f88(0x26a)](_0x168f88(0x2e4));_0x38a410[_0x168f88(0x26a)](_0x168f88(0x370))['on'](_0x168f88(0x39f),()=>{const _0x18ca7e=_0x168f88,_0x29d616=_0x1e02ae[_0x18ca7e(0x379)](_0x18ca7e(0x2ff))[_0x18ca7e(0x341)],_0x52521d=_0x142892(undefined,_0x29d616);_0x1e02ae[_0x18ca7e(0x26a)]('p')[_0x18ca7e(0x341)]>0x0?_0x1e02ae['html'](_0x52521d):_0x1e02ae[_0x18ca7e(0x40e)](_0x52521d);}),_0x1e02ae['on'](_0x168f88(0x39f),'.hly-delete-rule-btn',function(){const _0x5bdc6f=_0x168f88;$(this)[_0x5bdc6f(0x265)]('.hly-exclusion-rule-row')[_0x5bdc6f(0x21a)](),_0x1e02ae[_0x5bdc6f(0x379)]()[_0x5bdc6f(0x341)]===0x0&&_0x1e02ae[_0x5bdc6f(0x3ae)](_0x5bdc6f(0x28c));});if(_0x13e2f7===_0x168f88(0x2bb)){const _0x1931b4=_0x38a410[_0x168f88(0x26a)](_0x168f88(0x1f9)),_0x3f392b=_0x38a410[_0x168f88(0x26a)](_0x168f88(0x306));_0x1931b4['on'](_0x168f88(0x1fc),()=>{const _0x1bd41a=_0x168f88;_0x3f392b[_0x1bd41a(0x407)](_0x1bd41a(0x3a8),_0x1931b4['is'](':checked')?_0x1bd41a(0x307):_0x1bd41a(0x312));});}}});}function previewCondensation(){const _0x458e46=_0x28d2da,_0x4c9029=document[_0x458e46(0x214)](_0x458e46(0x2ce));try{const _0x2ef694=_0x5a0ed1[_0x458e46(0x22a)](),_0x40ec63=_0x2ef694['condensation'][_0x458e46(0x246)]||[],_0x23701a={'user':document['getElementById'](_0x458e46(0x383))[_0x458e46(0x3a9)],'ai':document[_0x458e46(0x214)](_0x458e46(0x221))['checked']},_0x37b140=document[_0x458e46(0x214)](_0x458e46(0x304))[_0x458e46(0x3a9)],_0x9c7f3e=_0x37b140?document['getElementById'](_0x458e46(0x34a))[_0x458e46(0x25e)][_0x458e46(0x3d5)](',')[_0x458e46(0x204)](_0x585f5d=>_0x585f5d[_0x458e46(0x35c)]())[_0x458e46(0x37a)](Boolean):[],_0x202b71=_0x5a0ed1[_0x458e46(0x205)](_0x23701a);if(!_0x202b71||_0x202b71[_0x458e46(0x341)]===0x0){_0x4c9029[_0x458e46(0x3e1)]=_0x458e46(0x3f8),toastr['warning'](_0x458e46(0x330),_0x458e46(0x294));return;}const _0x2c920a=getContext()[_0x458e46(0x37e)],_0x2e206f=_0x202b71['map']((_0x509ca6,_0x313c14)=>{const _0x4d55ed=_0x458e46;let _0xcb6b01;if(_0x509ca6[_0x4d55ed(0x2a7)])_0xcb6b01=_0x509ca6['mes'];else{if(_0x37b140&&_0x9c7f3e[_0x4d55ed(0x341)]>0x0){const _0x59fb00=extractBlocksByTags(_0x509ca6[_0x4d55ed(0x3f2)],_0x9c7f3e);_0x59fb00[_0x4d55ed(0x341)]>0x0?_0xcb6b01=_0x59fb00[_0x4d55ed(0x293)]('\x0a\x0a'):_0xcb6b01=_0x509ca6[_0x4d55ed(0x3f2)];}else _0xcb6b01=_0x509ca6['mes'];_0xcb6b01=applyExclusionRules(_0xcb6b01,_0x40ec63);}const _0xb852d=_0x2c920a[_0x4d55ed(0x401)](_0x12ac4f=>_0x12ac4f===_0x509ca6),_0x1fac31=_0xb852d!==-0x1?_0xb852d+0x1:-0x1;return{'id':_0x4d55ed(0x3d8)+_0x313c14,'name':_0x509ca6['name'],'content':_0xcb6b01[_0x4d55ed(0x35c)](),'floor':_0x1fac31,'is_user':_0x509ca6['is_user'],'send_date':_0x509ca6[_0x4d55ed(0x3d9)]};})[_0x458e46(0x37a)](_0x47c90a=>_0x47c90a[_0x458e46(0x3b2)]);if(_0x2e206f[_0x458e46(0x341)]===0x0){_0x4c9029[_0x458e46(0x3e1)]=_0x458e46(0x2cb),toastr[_0x458e46(0x40a)](_0x458e46(0x2cb),'翰林院启奏');return;}const _0x39226c=_0x2e206f['map']((_0x6f4bad,_0x3add8f)=>_0x458e46(0x35f)+_0x6f4bad['id']+_0x458e46(0x393)+_0x6f4bad[_0x458e46(0x28b)]+_0x458e46(0x237)+_0x6f4bad['name']+']\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'+_0x6f4bad[_0x458e46(0x3b2)]+_0x458e46(0x26f)+_0x6f4bad['id']+'\x22\x20title=\x22删除此条\x22>×\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x0a\x20\x20\x20\x20\x20\x20\x20\x20')['join']('');showHtmlModal(_0x458e46(0x238),''+_0x39226c+_0x458e46(0x2da),{'okText':'确认并更新预览','onOk':_0x2c3069=>{const _0x841267=_0x458e46,_0x528e12=[];_0x2c3069[_0x841267(0x26a)](_0x841267(0x329))[_0x841267(0x2ea)](function(){const _0x34b133=_0x841267,_0x15cfb7=$(this)[_0x34b133(0x26a)]('.hly-preview-textarea'),_0x5df252=_0x15cfb7[_0x34b133(0x2f6)]();_0x5df252[_0x34b133(0x35c)]()&&_0x528e12[_0x34b133(0x3b1)]({'mes':_0x5df252,'is_user':_0x15cfb7[_0x34b133(0x33f)](_0x34b133(0x21b)),'send_date':_0x15cfb7[_0x34b133(0x33f)](_0x34b133(0x23e)),'floor':_0x15cfb7[_0x34b133(0x33f)](_0x34b133(0x28b))});}),_0x4c9029[_0x841267(0x369)]['finalMessages']=JSON[_0x841267(0x338)](_0x528e12);const _0x556b93=document[_0x841267(0x214)]('hly-layer-start')[_0x841267(0x25e)],_0x58fd2b=document['getElementById'](_0x841267(0x1fb))[_0x841267(0x25e)];_0x4c9029['textContent']=_0x841267(0x27e)+_0x556b93+'\x20楼到\x20'+_0x58fd2b+_0x841267(0x2cc)+_0x528e12[_0x841267(0x341)]+_0x841267(0x314),toastr[_0x841267(0x224)](_0x841267(0x22c),_0x841267(0x303));}}),$(_0x458e46(0x2df))['on'](_0x458e46(0x39f),function(_0x49f34d){const _0x38da5c=_0x458e46;_0x49f34d[_0x38da5c(0x358)]();const _0x251f37=$(this)[_0x38da5c(0x33f)]('target');$('#'+_0x251f37)[_0x38da5c(0x21a)]();});}catch(_0x2b02a5){console[_0x458e46(0x282)]('[翰林院-枢纽]\x20预览过程发生错误:',_0x2b02a5),_0x4c9029['textContent']=_0x458e46(0x414)+_0x2b02a5[_0x458e46(0x415)],toastr[_0x458e46(0x282)](_0x458e46(0x414)+_0x2b02a5[_0x458e46(0x415)],_0x458e46(0x1f8));}}function log(_0x166e51,_0xc66294='info'){const _0x49d55b=_0x28d2da,_0x542375=document[_0x49d55b(0x214)](_0x49d55b(0x2a6));if(!_0x542375)return;const _0x4e3ca6=document[_0x49d55b(0x217)]('p'),_0x5748dd=new Date()[_0x49d55b(0x202)]();let _0x1427fd='fa-circle-info',_0x5bd2c1='log-info';switch(_0xc66294){case _0x49d55b(0x224):_0x1427fd=_0x49d55b(0x26e),_0x5bd2c1='log-success';break;case _0x49d55b(0x282):_0x1427fd=_0x49d55b(0x245),_0x5bd2c1=_0x49d55b(0x297);break;case _0x49d55b(0x23d):_0x1427fd='fa-exclamation-triangle',_0x5bd2c1=_0x49d55b(0x3ed);break;}_0x4e3ca6[_0x49d55b(0x2b4)]='hly-log-entry\x20'+_0x5bd2c1,_0x4e3ca6[_0x49d55b(0x30a)]=_0x49d55b(0x396)+_0x1427fd+_0x49d55b(0x345)+_0x5748dd+']\x20'+_0x166e51;const _0x20be44=_0x542375['querySelector'](_0x49d55b(0x2d6));_0x20be44&&_0x20be44[_0x49d55b(0x21a)](),_0x542375[_0x49d55b(0x366)](_0x4e3ca6),_0x542375[_0x49d55b(0x352)]=_0x542375[_0x49d55b(0x355)];}async function ingestManualText(){const _0x89a971=_0x28d2da,_0x10390b=document[_0x89a971(0x214)]('hly-manual-text'),_0x47a55b=_0x10390b['value'][_0x89a971(0x35c)]();if(!_0x47a55b){toastr['warning'](_0x89a971(0x38d),_0x89a971(0x294)),log(_0x89a971(0x359),'warn');return;}log('收到手动录入请求,文本长度:\x20'+_0x47a55b[_0x89a971(0x341)],'info'),toastr['info']('正在处理您提交的文书...','圣旨');try{const _0xdee09d=await _0x5a0ed1[_0x89a971(0x1f3)](_0x47a55b,'manual',{'sourceName':_0x89a971(0x2c1)});if(_0xdee09d['success'])toastr[_0x89a971(0x224)](_0x89a971(0x37d)+_0xdee09d[_0x89a971(0x343)]+_0x89a971(0x3f5),_0x89a971(0x201)),log(_0x89a971(0x39a)+_0xdee09d[_0x89a971(0x343)]+'\x20条忆识。',_0x89a971(0x224)),_0x10390b[_0x89a971(0x25e)]='';else throw new Error(_0xdee09d[_0x89a971(0x282)]||_0x89a971(0x24e));}catch(_0x3af794){console['error'](_0x89a971(0x29d),_0x3af794),toastr['error'](_0x89a971(0x326)+_0x3af794[_0x89a971(0x415)],'严重错误'),log(_0x89a971(0x2a5)+_0x3af794['message'],_0x89a971(0x282));}finally{await updatePanelStatus();}}

From d5885d270183b1df6b2f47811e3d6fdf7728ce1b Mon Sep 17 00:00:00 2001
From: Wx-2025 <351320169@qq.com>
Date: Tue, 4 Nov 2025 23:40:14 +0800
Subject: [PATCH 48/49] Update hanlinyuan.html

---
 assets/hanlinyuan.html | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/assets/hanlinyuan.html b/assets/hanlinyuan.html
index e85cae4..dfa5480 100644
--- a/assets/hanlinyuan.html
+++ b/assets/hanlinyuan.html
@@ -35,6 +35,13 @@
                     
                 
             
+            
+ + +
@@ -428,6 +435,22 @@ 每次调用API时处理的文本数量。
+ +
+ 检索预处理 +
+ + +
+
+ +
+ 此功能类似于“凝识法则”,可对您最近的几条聊天记录(即用于检索的文本)进行标签提取和内容排除,以生成更纯净、更高效的检索查询。 +
+
圣言注入 (按来源)
From 9e4f242a1cc21167f076ebe0323dad5fe25f47a0 Mon Sep 17 00:00:00 2001 From: Wx-2025 <351320169@qq.com> Date: Tue, 4 Nov 2025 23:42:05 +0800 Subject: [PATCH 49/49] Update manifest.json --- manifest.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index dc4994d..967a268 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "name": "Amily2号聊天优化助手", "display_name": "Amily2号助手", - "version": "1.6.1", + "version": "1.6.2", "author": "Wx-2025", "description": "一个拥有独立UI的智能引擎,正文优化、自动总结、记忆表格、rag向量、隐藏楼层、剧情推进六大功能整合。", "minSillyTavernVersion": "1.10.0", @@ -32,5 +32,6 @@ +