@@ -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 = `${glossaryContent}
`;
mainContainer.append(glossaryPanelHtml);
+ const rendererContent = await $.get(`${extensionFolderPath}/core/tavern-helper/renderer.html`);
+ const rendererPanelHtml = `${rendererContent}
`;
+ 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 @@
+
+
“我也想过琴棋书画诗酒花,奈何生活只有柴米油盐酱醋茶。”
+
+ 免责声明:本插件仅供个人学习与技术交流使用,严禁用于任何商业目的或非法活动。使用者需自行承担因使用本插件而产生的一切风险与法律责任,开发者对此不承担任何责任。
+
+
+
-