mirror of
https://github.com/SilenceLurker/ST-Amily2-Chat-Optimisation.git
synced 2026-06-06 13:35:51 +00:00
Update cwb_lorebookManager.js
This commit is contained in:
@@ -1,319 +1,586 @@
|
||||
import { extension_settings } from '/scripts/extensions.js';
|
||||
import { extensionName } from '../../utils/settings.js';
|
||||
import { saveSettingsDebounced } from '/script.js';
|
||||
import { world_names } from '/scripts/world-info.js';
|
||||
import { state } from './cwb_state.js';
|
||||
import { logError, logDebug, showToastr, parseCustomFormat } from './cwb_utils.js';
|
||||
import { cwbCompleteDefaultSettings } from './cwb_config.js';
|
||||
import { logError, showToastr, escapeHtml, compareVersions, isCwbEnabled } from './cwb_utils.js';
|
||||
import { fetchModelsAndConnect, updateApiStatusDisplay } from './cwb_apiService.js';
|
||||
import { checkForUpdates } from './cwb_updater.js';
|
||||
import { handleManualUpdateCard, startBatchUpdate, handleFloorRangeUpdate } from './cwb_core.js';
|
||||
import { initializeCharCardViewer } from './cwb_uiManager.js';
|
||||
import { CHAR_CARD_VIEWER_BUTTON_ID } from './cwb_state.js';
|
||||
|
||||
const { SillyTavern, TavernHelper } = window;
|
||||
const { jQuery: $ } = window;
|
||||
|
||||
export async function getTargetWorldBook() {
|
||||
logDebug('[CWB-DIAGNOSTIC] getTargetWorldBook called. Current state:', {
|
||||
target: state.worldbookTarget,
|
||||
book: state.customWorldBook
|
||||
});
|
||||
if (state.worldbookTarget === 'custom' && state.customWorldBook) {
|
||||
return state.customWorldBook;
|
||||
}
|
||||
try {
|
||||
let localTavernHelper = TavernHelper;
|
||||
if (!localTavernHelper) {
|
||||
// TavernHelper 未定义的情况下触发,但是为什么?
|
||||
(localTavernHelper = window.TavernHelper);
|
||||
}
|
||||
const primaryBook = await localTavernHelper.getCurrentCharPrimaryLorebook();
|
||||
if (!primaryBook) {
|
||||
showToastr('error', '当前角色未设置主世界书。');
|
||||
return null;
|
||||
}
|
||||
return primaryBook;
|
||||
} catch (error) {
|
||||
logError('获取主世界书时出错:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
const CWB_BOOLEAN_SETTINGS_OVERRIDE_KEY = 'cwb_boolean_settings_override';
|
||||
let $panel;
|
||||
|
||||
export async function deleteLorebookEntries(uids) {
|
||||
if (!Array.isArray(uids) || uids.length === 0) return;
|
||||
const getSettings = () => extension_settings[extensionName];
|
||||
|
||||
try {
|
||||
const context = SillyTavern.getContext();
|
||||
if (!context || !context.characterId) {
|
||||
throw new Error('没有选择角色,无法删除。');
|
||||
}
|
||||
const book = await getTargetWorldBook();
|
||||
if (!book) throw new Error('未找到目标世界书。');
|
||||
function updateControlsLockState() {
|
||||
if (!$panel) return;
|
||||
const settings = getSettings();
|
||||
const isMasterEnabled = settings.cwb_master_enabled;
|
||||
|
||||
await TavernHelper.deleteLorebookEntries(book, uids.map(Number));
|
||||
} catch (error) {
|
||||
logError('删除世界书条目失败:', error);
|
||||
showToastr('error', `删除失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
const $controlsToToggle = $panel.find('input, textarea, select, button').not('#cwb_master_enabled-checkbox, #amily2_back_to_main_from_cwb');
|
||||
|
||||
export async function saveDescriptionToLorebook(characterName, newDescription, startFloor, endFloor) {
|
||||
if (!characterName?.trim()) return false;
|
||||
|
||||
try {
|
||||
const context = SillyTavern.getContext();
|
||||
if (!context || !context.characterId) {
|
||||
showToastr('error', '没有选择角色,无法保存到世界书。');
|
||||
return false;
|
||||
}
|
||||
let chatIdentifier = state.currentChatFileIdentifier || '未知聊天';
|
||||
chatIdentifier = chatIdentifier.replace(/ imported/g, '');
|
||||
|
||||
const safeCharName = characterName.replace(/[^a-zA-Z0-9\u4e00-\u9fa5·""“”_-]/g, ',');
|
||||
const floorRange = `${startFloor + 1}-${endFloor + 1}`;
|
||||
|
||||
const newComment = `${safeCharName}-${chatIdentifier}`;
|
||||
|
||||
let bookName;
|
||||
if (state.worldbookTarget === 'custom' && state.customWorldBook) {
|
||||
bookName = state.customWorldBook;
|
||||
if (isMasterEnabled) {
|
||||
$controlsToToggle.prop('disabled', false);
|
||||
$panel.find('.settings-group').not('.master-control-group').css('opacity', '1');
|
||||
} else {
|
||||
bookName = await TavernHelper.getCurrentCharPrimaryLorebook();
|
||||
$controlsToToggle.prop('disabled', true);
|
||||
$panel.find('.settings-group').not('.master-control-group').css('opacity', '0.5');
|
||||
}
|
||||
}
|
||||
|
||||
if (!bookName) {
|
||||
showToastr('error', '未能确定要写入的世界书。请检查主世界书或自定义世界书设置。');
|
||||
return false;
|
||||
function saveApiConfig() {
|
||||
const settings = getSettings();
|
||||
settings.cwb_api_mode = $panel.find('#cwb-api-mode').val();
|
||||
settings.cwb_api_url = $panel.find('#cwb-api-url').val().trim();
|
||||
settings.cwb_api_key = $panel.find('#cwb-api-key').val();
|
||||
settings.cwb_api_model = $panel.find('#cwb-api-model').val();
|
||||
settings.cwb_tavern_profile = $panel.find('#cwb-tavern-profile').val();
|
||||
|
||||
if (settings.cwb_api_mode === 'sillytavern_preset') {
|
||||
if (!settings.cwb_tavern_profile) {
|
||||
showToastr('warning', '请选择SillyTavern预设。');
|
||||
return;
|
||||
}
|
||||
showToastr('success', 'API配置已保存!');
|
||||
} else {
|
||||
if (!settings.cwb_api_url) {
|
||||
showToastr('warning', 'API URL 不能为空。');
|
||||
return;
|
||||
}
|
||||
showToastr('success', 'API配置已保存!');
|
||||
}
|
||||
|
||||
const entries = (await TavernHelper.getLorebookEntries(bookName)) || [];
|
||||
let existing = entries.find(e =>
|
||||
Array.isArray(e.keys) &&
|
||||
e.keys.includes(chatIdentifier) &&
|
||||
e.keys.includes(safeCharName) &&
|
||||
!e.keys.includes('Amily2角色总集')
|
||||
);
|
||||
saveSettingsDebounced();
|
||||
loadSettings();
|
||||
}
|
||||
|
||||
const entryData = {
|
||||
comment: newComment,
|
||||
content: newDescription,
|
||||
keys: [chatIdentifier, safeCharName, floorRange],
|
||||
enabled: true,
|
||||
type: 'selective',
|
||||
function clearApiConfig() {
|
||||
const settings = getSettings();
|
||||
settings.cwb_api_url = '';
|
||||
settings.cwb_api_key = '';
|
||||
settings.cwb_api_model = '';
|
||||
saveSettingsDebounced();
|
||||
state.customApiConfig.url = '';
|
||||
state.customApiConfig.apiKey = '';
|
||||
state.customApiConfig.model = '';
|
||||
updateUiWithSettings();
|
||||
updateApiStatusDisplay($panel);
|
||||
showToastr('info', 'API配置已清除!');
|
||||
}
|
||||
|
||||
function saveBreakArmorPrompt() {
|
||||
const newPrompt = $panel.find('#cwb-break-armor-prompt-textarea').val().trim();
|
||||
if (!newPrompt) {
|
||||
showToastr('warning', '破甲预设不能为空。');
|
||||
return;
|
||||
}
|
||||
getSettings().cwb_break_armor_prompt = newPrompt;
|
||||
state.currentBreakArmorPrompt = newPrompt;
|
||||
saveSettingsDebounced();
|
||||
showToastr('success', '破甲预设已保存!');
|
||||
}
|
||||
|
||||
function resetBreakArmorPrompt() {
|
||||
getSettings().cwb_break_armor_prompt = cwbCompleteDefaultSettings.cwb_break_armor_prompt;
|
||||
state.currentBreakArmorPrompt = cwbCompleteDefaultSettings.cwb_break_armor_prompt;
|
||||
saveSettingsDebounced();
|
||||
updateUiWithSettings();
|
||||
showToastr('info', '破甲预设已恢复为默认值!');
|
||||
}
|
||||
|
||||
function saveCharCardPrompt() {
|
||||
const newPrompt = $panel.find('#cwb-char-card-prompt-textarea').val().trim();
|
||||
if (!newPrompt) {
|
||||
showToastr('warning', '角色卡预设不能为空。');
|
||||
return;
|
||||
}
|
||||
getSettings().cwb_char_card_prompt = newPrompt;
|
||||
state.currentCharCardPrompt = newPrompt;
|
||||
saveSettingsDebounced();
|
||||
showToastr('success', '角色卡预设已保存!');
|
||||
}
|
||||
|
||||
function resetCharCardPrompt() {
|
||||
getSettings().cwb_char_card_prompt = cwbCompleteDefaultSettings.cwb_char_card_prompt;
|
||||
state.currentCharCardPrompt = cwbCompleteDefaultSettings.cwb_char_card_prompt;
|
||||
saveSettingsDebounced();
|
||||
updateUiWithSettings();
|
||||
showToastr('info', '角色卡预设已恢复为默认值!');
|
||||
}
|
||||
|
||||
function saveAutoUpdateThreshold() {
|
||||
const valStr = $panel.find('#cwb-auto-update-threshold').val();
|
||||
const newT = parseInt(valStr, 10);
|
||||
if (!isNaN(newT) && newT >= 1) {
|
||||
getSettings().cwb_auto_update_threshold = newT;
|
||||
state.autoUpdateThreshold = newT;
|
||||
saveSettingsDebounced();
|
||||
showToastr('success', '自动更新阈值已保存!');
|
||||
} else {
|
||||
showToastr('warning', `阈值 "${valStr}" 无效。`);
|
||||
$panel.find('#cwb-auto-update-threshold').val(getSettings().cwb_auto_update_threshold);
|
||||
}
|
||||
}
|
||||
|
||||
function bindWorldBookSettings() {
|
||||
const MAX_RETRIES = 10;
|
||||
const RETRY_DELAY = 200;
|
||||
let attempt = 0;
|
||||
|
||||
function tryBind() {
|
||||
if (world_names && world_names.length > 0) {
|
||||
console.log('[CWB] World books loaded, binding settings...');
|
||||
const settings = getSettings();
|
||||
|
||||
if (settings.cwb_worldbook_target === undefined) settings.cwb_worldbook_target = 'primary';
|
||||
if (settings.cwb_custom_worldbook === undefined) settings.cwb_custom_worldbook = null;
|
||||
|
||||
const customSelectWrapper = $panel.find('#cwb_worldbook_select_wrapper');
|
||||
const bookListContainer = $panel.find('#cwb_worldbook_radio_list');
|
||||
|
||||
const renderWorldBookList = () => {
|
||||
const worldBooks = world_names.map(name => ({ name: name.replace('.json', ''), file_name: name }));
|
||||
bookListContainer.empty();
|
||||
|
||||
if (worldBooks.length > 0) {
|
||||
worldBooks.forEach(book => {
|
||||
const div = $('<div class="checkbox-item"></div>').attr('title', book.name);
|
||||
const radio = $('<input type="radio" name="cwb_worldbook_selection">')
|
||||
.attr('id', `cwb-wb-radio-${book.file_name}`)
|
||||
.val(book.file_name)
|
||||
.prop('checked', settings.cwb_custom_worldbook === book.file_name);
|
||||
const label = $('<label></label>').attr('for', `cwb-wb-radio-${book.file_name}`).text(book.name);
|
||||
div.append(radio).append(label);
|
||||
bookListContainer.append(div);
|
||||
});
|
||||
} else {
|
||||
bookListContainer.html('<p class="notes">没有找到世界书。</p>');
|
||||
}
|
||||
};
|
||||
|
||||
if (existing) {
|
||||
await TavernHelper.setLorebookEntries(bookName, [{ uid: existing.uid, ...entryData }]);
|
||||
} else {
|
||||
const cwbEntries = entries.filter(e =>
|
||||
Array.isArray(e.keys) &&
|
||||
e.keys.includes(chatIdentifier) &&
|
||||
!e.keys.includes('Amily2角色总集')
|
||||
);
|
||||
let maxDepth = 7000;
|
||||
cwbEntries.forEach(entry => {
|
||||
if (entry.position === 'at_depth_as_system' && typeof entry.depth === 'number') {
|
||||
if (entry.depth >= 7001 && entry.depth > maxDepth) {
|
||||
maxDepth = entry.depth;
|
||||
const updateCustomSelectVisibility = () => {
|
||||
const isCustom = settings.cwb_worldbook_target === 'custom';
|
||||
customSelectWrapper.toggle(isCustom);
|
||||
if (isCustom) {
|
||||
renderWorldBookList();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const newDepth = maxDepth + 1;
|
||||
let maxOrder = 7000;
|
||||
if (cwbEntries.length > 0) {
|
||||
maxOrder = cwbEntries.reduce((max, entry) => {
|
||||
const order = Number(entry.order);
|
||||
return !isNaN(order) && order > max ? order : max;
|
||||
}, 7000);
|
||||
}
|
||||
|
||||
const newEntryData = {
|
||||
...entryData,
|
||||
order: 100,
|
||||
position: 'at_depth_as_system',
|
||||
depth: newDepth,
|
||||
};
|
||||
|
||||
logDebug(`创建新角色条目:${safeCharName}`, {
|
||||
position: newEntryData.position,
|
||||
depth: newEntryData.depth,
|
||||
order: newEntryData.order
|
||||
$panel.find('input[name="cwb_worldbook_target"]').each(function() {
|
||||
$(this).prop('checked', $(this).val() === settings.cwb_worldbook_target);
|
||||
});
|
||||
updateCustomSelectVisibility();
|
||||
|
||||
$panel.off('change.cwb_worldbook_target').on('change.cwb_worldbook_target', 'input[name="cwb_worldbook_target"]', function() {
|
||||
if ($(this).prop('checked')) {
|
||||
settings.cwb_worldbook_target = $(this).val();
|
||||
state.worldbookTarget = $(this).val();
|
||||
updateCustomSelectVisibility();
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
});
|
||||
|
||||
await TavernHelper.createLorebookEntries(bookName, [newEntryData]);
|
||||
}
|
||||
showToastr('success', `角色 ${safeCharName} 的描述已保存到世界书。`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
logError(`保存世界书失败 for ${characterName}:`, error);
|
||||
showToastr('error', `保存角色 ${safeCharName} 到世界书失败。`);
|
||||
return false;
|
||||
}
|
||||
bookListContainer.off('change.cwb_worldbook_selection').on('change.cwb_worldbook_selection', 'input[name="cwb_worldbook_selection"]', function() {
|
||||
const radio = $(this);
|
||||
if (radio.prop('checked')) {
|
||||
settings.cwb_custom_worldbook = radio.val();
|
||||
state.customWorldBook = radio.val();
|
||||
saveSettingsDebounced();
|
||||
showToastr('info', `已选择世界书: ${radio.next('label').text()}`);
|
||||
}
|
||||
});
|
||||
|
||||
export async function updateCharacterRosterLorebookEntry(processedCharacterNames, startFloor, endFloor) {
|
||||
if (!Array.isArray(processedCharacterNames)) return true;
|
||||
$panel.off('click.cwb_refresh_worldbooks').on('click.cwb_refresh_worldbooks', '#cwb_refresh_worldbooks', renderWorldBookList);
|
||||
|
||||
try {
|
||||
const context = SillyTavern.getContext();
|
||||
if (!context || !context.characterId) {
|
||||
logDebug('未选择角色,无法更新角色名册。');
|
||||
return false;
|
||||
}
|
||||
let chatIdentifier = state.currentChatFileIdentifier || '未知聊天';
|
||||
if (chatIdentifier === '未知聊天') return false;
|
||||
|
||||
const cleanChatId = chatIdentifier.replace(/ imported/g, '');
|
||||
const rosterEntryComment = `Amily2角色总集-${cleanChatId}-角色总览`;
|
||||
|
||||
let characterCardName = '未识别到该角色卡名称';
|
||||
try {
|
||||
const currentChar = context.characters[context.characterId];
|
||||
if (currentChar && currentChar.name) {
|
||||
characterCardName = currentChar.name.trim();
|
||||
}
|
||||
} catch (e) {
|
||||
logDebug('[CWB] 无法获取角色名称,使用默认值');
|
||||
}
|
||||
|
||||
const initialContentPrefix = `此为当前角色卡【${characterCardName}】中登场的角色,AI需要根据剧情让以下角色在合适的时机登场:\n\n`;
|
||||
|
||||
let bookName;
|
||||
if (state.worldbookTarget === 'custom' && state.customWorldBook) {
|
||||
bookName = state.customWorldBook;
|
||||
} else if (attempt < MAX_RETRIES) {
|
||||
attempt++;
|
||||
console.log(`[CWB] World books not ready, retrying... (Attempt ${attempt})`);
|
||||
setTimeout(tryBind, RETRY_DELAY);
|
||||
} else {
|
||||
bookName = await TavernHelper.getCurrentCharPrimaryLorebook();
|
||||
console.error('[CWB] Failed to load world books after multiple retries.');
|
||||
$panel.find('#cwb_worldbook_radio_list').html('<p class="notes error">加载世界书失败,请刷新页面重试。</p>');
|
||||
}
|
||||
}
|
||||
|
||||
if (!bookName) {
|
||||
showToastr('error', '未能确定要写入的世界书。请检查主世界书或自定义世界书设置。');
|
||||
return false;
|
||||
tryBind();
|
||||
}
|
||||
|
||||
let entries = (await TavernHelper.getLorebookEntries(bookName)) || [];
|
||||
let existingRosterEntry = entries.find(entry =>
|
||||
entry.comment === rosterEntryComment ||
|
||||
entry.comment === `Amily2角色总集-${chatIdentifier}-角色总览`
|
||||
);
|
||||
export function bindSettingsEvents($settingsPanel) {
|
||||
$panel = $settingsPanel;
|
||||
|
||||
let existingNames = new Set();
|
||||
let oldStartFloor = 1;
|
||||
let oldEndFloor = 0;
|
||||
bindWorldBookSettings();
|
||||
$panel.on('click', '.sinan-nav-item', function () {
|
||||
const $this = $(this);
|
||||
const tabId = $this.data('tab');
|
||||
|
||||
if (existingRosterEntry) {
|
||||
if (existingRosterEntry.content) {
|
||||
let contentToParse = existingRosterEntry.content.replace(initialContentPrefix, '');
|
||||
$panel.find('.sinan-nav-item').removeClass('active');
|
||||
$this.addClass('active');
|
||||
$panel.find('.sinan-tab-pane').removeClass('active');
|
||||
$panel.find(`#cwb-${tabId}-tab`).addClass('active');
|
||||
});
|
||||
$panel.on('change', '#cwb-api-mode', function() {
|
||||
const selectedMode = $(this).val();
|
||||
|
||||
const floorMatch = contentToParse.match(/【前(\d+)楼角色世界书已更新完成】/);
|
||||
if (floorMatch && floorMatch[1]) {
|
||||
oldEndFloor = parseInt(floorMatch[1], 10);
|
||||
// 自动保存API模式设置
|
||||
getSettings().cwb_api_mode = selectedMode;
|
||||
saveSettingsDebounced();
|
||||
|
||||
updateApiModeUI(selectedMode);
|
||||
if (selectedMode === 'sillytavern_preset') {
|
||||
loadSillyTavernPresets(true);
|
||||
}
|
||||
|
||||
contentToParse.split('\n').forEach(line => {
|
||||
if (line.trim().startsWith('[')) {
|
||||
const nameMatch = line.match(/\[(.*?):/);
|
||||
if (nameMatch && nameMatch[1]) {
|
||||
existingNames.add(nameMatch[1].trim());
|
||||
showToastr('success', `API模式已切换为: ${selectedMode === 'sillytavern_preset' ? 'SillyTavern预设' : '全兼容'}`);
|
||||
});
|
||||
$panel.on('change', '#cwb-tavern-profile', function() {
|
||||
const selectedProfile = $(this).val();
|
||||
|
||||
// 自动保存SillyTavern预设选择
|
||||
getSettings().cwb_tavern_profile = selectedProfile;
|
||||
saveSettingsDebounced();
|
||||
|
||||
if (selectedProfile) {
|
||||
console.log(`[CWB] 选择了预设: ${selectedProfile}`);
|
||||
showToastr('success', `SillyTavern预设已选择: ${selectedProfile}`);
|
||||
}
|
||||
|
||||
updateApiStatusDisplay($panel);
|
||||
});
|
||||
// 添加API字段的实时保存
|
||||
$panel.on('input', '#cwb-api-url', function() {
|
||||
const apiUrl = $(this).val().trim();
|
||||
|
||||
// 同时更新设置和状态
|
||||
getSettings().cwb_api_url = apiUrl;
|
||||
state.customApiConfig.url = apiUrl;
|
||||
|
||||
saveSettingsDebounced();
|
||||
updateApiStatusDisplay($panel);
|
||||
|
||||
console.log('[CWB] API URL已更新 - 设置:', getSettings().cwb_api_url, ', 状态:', state.customApiConfig.url);
|
||||
});
|
||||
|
||||
$panel.on('input', '#cwb-api-key', function() {
|
||||
const apiKey = $(this).val();
|
||||
|
||||
// 同时更新设置和状态
|
||||
getSettings().cwb_api_key = apiKey;
|
||||
state.customApiConfig.apiKey = apiKey;
|
||||
|
||||
saveSettingsDebounced();
|
||||
|
||||
console.log('[CWB] API Key已更新 - 设置长度:', getSettings().cwb_api_key?.length || 0, ', 状态长度:', state.customApiConfig.apiKey?.length || 0);
|
||||
});
|
||||
|
||||
$panel.on('change', '#cwb-api-model', function() {
|
||||
const model = $(this).val();
|
||||
|
||||
// 同时更新设置和状态
|
||||
getSettings().cwb_api_model = model;
|
||||
state.customApiConfig.model = model;
|
||||
|
||||
saveSettingsDebounced();
|
||||
updateApiStatusDisplay($panel);
|
||||
|
||||
console.log('[CWB] 模型已更新 - 设置:', getSettings().cwb_api_model, ', 状态:', state.customApiConfig.model);
|
||||
|
||||
if (model) {
|
||||
showToastr('success', `模型已选择: ${model}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
const floorRangeKey = existingRosterEntry.keys.find(k => /^\d+-\d+$/.test(k));
|
||||
if (floorRangeKey) {
|
||||
[oldStartFloor] = floorRangeKey.split('-').map(Number);
|
||||
}
|
||||
|
||||
$panel.on('click', '#cwb-load-models', () => fetchModelsAndConnect($panel));
|
||||
|
||||
$panel.on('click', '#cwb-save-break-armor-prompt', saveBreakArmorPrompt);
|
||||
$panel.on('click', '#cwb-reset-break-armor-prompt', resetBreakArmorPrompt);
|
||||
$panel.on('click', '#cwb-save-char-card-prompt', saveCharCardPrompt);
|
||||
$panel.on('click', '#cwb-reset-char-card-prompt', resetCharCardPrompt);
|
||||
|
||||
$panel.on('click', '#cwb-save-auto-update-threshold', saveAutoUpdateThreshold);
|
||||
$panel.on('click', '#cwb-manual-update-card', () => handleManualUpdateCard($panel));
|
||||
$panel.on('click', '#cwb-batch-update-card', () => startBatchUpdate($panel));
|
||||
$panel.on('click', '#cwb-floor-range-update', () => handleFloorRangeUpdate($panel));
|
||||
$panel.on('click', '#cwb-check-for-updates', () => checkForUpdates(true, $panel));
|
||||
|
||||
$panel.on('click', '#cwb-auto-update-enabled', function () {
|
||||
const $checkbox = $(this).find('input[type="checkbox"]');
|
||||
const isChecked = !$checkbox.prop('checked');
|
||||
$checkbox.prop('checked', isChecked);
|
||||
|
||||
console.log(`[CWB] Auto-update switch clicked. New state: ${isChecked}`);
|
||||
getSettings().cwb_auto_update_enabled = isChecked;
|
||||
|
||||
const overrides = JSON.parse(localStorage.getItem(CWB_BOOLEAN_SETTINGS_OVERRIDE_KEY) || '{}');
|
||||
overrides.cwb_auto_update_enabled = isChecked;
|
||||
localStorage.setItem(CWB_BOOLEAN_SETTINGS_OVERRIDE_KEY, JSON.stringify(overrides));
|
||||
|
||||
saveSettingsDebounced();
|
||||
state.autoUpdateEnabled = isChecked;
|
||||
showToastr('info', `角色卡自动更新已 ${isChecked ? '启用' : '禁用'}`);
|
||||
});
|
||||
|
||||
$panel.on('click', '#cwb-viewer-enabled', function () {
|
||||
const $checkbox = $(this).find('input[type="checkbox"]');
|
||||
const isChecked = !$checkbox.prop('checked');
|
||||
$checkbox.prop('checked', isChecked);
|
||||
|
||||
console.log(`[CWB] Viewer switch clicked. New state: ${isChecked}`);
|
||||
getSettings().cwb_viewer_enabled = isChecked;
|
||||
|
||||
const overrides = JSON.parse(localStorage.getItem(CWB_BOOLEAN_SETTINGS_OVERRIDE_KEY) || '{}');
|
||||
overrides.cwb_viewer_enabled = isChecked;
|
||||
localStorage.setItem(CWB_BOOLEAN_SETTINGS_OVERRIDE_KEY, JSON.stringify(overrides));
|
||||
|
||||
saveSettingsDebounced();
|
||||
|
||||
state.viewerEnabled = isChecked;
|
||||
|
||||
const $viewerButton = $(`#${CHAR_CARD_VIEWER_BUTTON_ID}`);
|
||||
if ($viewerButton.length > 0) {
|
||||
const shouldShow = isCwbEnabled() && isChecked;
|
||||
$viewerButton.toggle(shouldShow);
|
||||
}
|
||||
|
||||
processedCharacterNames.forEach(name => existingNames.add(name.trim()));
|
||||
showToastr('info', `角色卡查看器已 ${isChecked ? '启用' : '禁用'}`);
|
||||
});
|
||||
|
||||
const newStartFloor = Math.min(oldStartFloor, startFloor + 1);
|
||||
const newEndFloor = Math.max(oldEndFloor, endFloor + 1);
|
||||
$panel.on('click', '#cwb-incremental-update-enabled', function () {
|
||||
const $checkbox = $(this).find('input[type="checkbox"]');
|
||||
const isChecked = !$checkbox.prop('checked'); // Manually toggle
|
||||
$checkbox.prop('checked', isChecked);
|
||||
|
||||
const newContent =
|
||||
initialContentPrefix +
|
||||
[...existingNames]
|
||||
.sort()
|
||||
.map(name => `[${name}: (详细查看绿灯角色条目)]`)
|
||||
.join('\n') + `\n\n{{// 本条勿动,【前${newEndFloor}楼角色世界书已更新完成】否则后续更新无法完成。}}`;
|
||||
console.log(`[CWB] Incremental update switch clicked. New state: ${isChecked}`);
|
||||
getSettings().cwb_incremental_update_enabled = isChecked;
|
||||
|
||||
const newFloorRange = `${newStartFloor}-${newEndFloor}`;
|
||||
const overrides = JSON.parse(localStorage.getItem(CWB_BOOLEAN_SETTINGS_OVERRIDE_KEY) || '{}');
|
||||
overrides.cwb_incremental_update_enabled = isChecked;
|
||||
localStorage.setItem(CWB_BOOLEAN_SETTINGS_OVERRIDE_KEY, JSON.stringify(overrides));
|
||||
|
||||
const baseKeys = [`Amily2角色总集`, cleanChatId, `角色总览`];
|
||||
const newKeys = [...baseKeys, newFloorRange];
|
||||
saveSettingsDebounced();
|
||||
state.isIncrementalUpdateEnabled = isChecked;
|
||||
showToastr('info', `增量更新模式已 ${isChecked ? '启用' : '禁用'}`);
|
||||
});
|
||||
|
||||
const entryData = {
|
||||
content: newContent,
|
||||
keys: newKeys,
|
||||
type: 'constant',
|
||||
position: 'before_character_definition',
|
||||
depth: null,
|
||||
enabled: true,
|
||||
order: 9999,
|
||||
prevent_recursion: true,
|
||||
$panel.on('click', '#cwb_master_enabled', function () {
|
||||
const $checkbox = $(this).find('input[type="checkbox"]');
|
||||
const isChecked = !$checkbox.prop('checked');
|
||||
$checkbox.prop('checked', isChecked);
|
||||
|
||||
console.log(`[CWB] Master switch clicked. New state: ${isChecked}`);
|
||||
|
||||
getSettings().cwb_master_enabled = isChecked;
|
||||
|
||||
const overrides = JSON.parse(localStorage.getItem(CWB_BOOLEAN_SETTINGS_OVERRIDE_KEY) || '{}');
|
||||
overrides.cwb_master_enabled = isChecked;
|
||||
localStorage.setItem(CWB_BOOLEAN_SETTINGS_OVERRIDE_KEY, JSON.stringify(overrides));
|
||||
|
||||
state.masterEnabled = isChecked;
|
||||
|
||||
saveSettingsDebounced();
|
||||
|
||||
updateControlsLockState();
|
||||
|
||||
const $viewerButton = $(`#${CHAR_CARD_VIEWER_BUTTON_ID}`);
|
||||
if ($viewerButton.length > 0) {
|
||||
const shouldShow = isChecked && state.viewerEnabled;
|
||||
$viewerButton.toggle(shouldShow);
|
||||
}
|
||||
|
||||
showToastr('info', `CharacterWorldBook 已 ${isChecked ? '启用' : '禁用'}`);
|
||||
|
||||
$(document).trigger('cwb:master-switch-changed', { isEnabled: isChecked });
|
||||
});
|
||||
}
|
||||
|
||||
function updateApiModeUI(mode) {
|
||||
const fields = {
|
||||
openai: [
|
||||
'label[for="cwb-api-url"]',
|
||||
'#cwb-api-url',
|
||||
'label[for="cwb-api-key"]',
|
||||
'#cwb-api-key',
|
||||
'label[for="cwb-api-model"]',
|
||||
'#cwb-api-model',
|
||||
'#cwb-load-models'
|
||||
],
|
||||
sillytavern: [
|
||||
'label[for="cwb-tavern-profile"]',
|
||||
'#cwb-tavern-profile'
|
||||
]
|
||||
};
|
||||
|
||||
if (existingRosterEntry) {
|
||||
await TavernHelper.setLorebookEntries(bookName, [
|
||||
{ uid: existingRosterEntry.uid, comment: rosterEntryComment, ...entryData },
|
||||
]);
|
||||
if (mode === 'sillytavern_preset') {
|
||||
fields.openai.forEach(selector => $panel.find(selector).hide());
|
||||
fields.sillytavern.forEach(selector => $panel.find(selector).show());
|
||||
} else {
|
||||
await TavernHelper.createLorebookEntries(bookName, [
|
||||
{ comment: rosterEntryComment, ...entryData },
|
||||
]);
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
logError('更新角色名册条目时出错:', error);
|
||||
return false;
|
||||
}
|
||||
fields.sillytavern.forEach(selector => $panel.find(selector).hide());
|
||||
fields.openai.forEach(selector => $panel.find(selector).show());
|
||||
}
|
||||
|
||||
updateApiStatusDisplay($panel);
|
||||
}
|
||||
|
||||
function loadSillyTavernPresets(showNotification = false) {
|
||||
const $profileSelect = $panel.find('#cwb-tavern-profile');
|
||||
|
||||
export async function manageAutoCardUpdateLorebookEntry() {
|
||||
try {
|
||||
if (state.worldbookTarget === 'custom' && state.customWorldBook) {
|
||||
logDebug('[CWB] 使用自定义世界书模式,跳过角色总览条目的自动管理');
|
||||
const context = window.SillyTavern?.getContext?.();
|
||||
if (!context?.extensionSettings?.connectionManager?.profiles) {
|
||||
showToastr('warning', '无法获取SillyTavern配置文件列表');
|
||||
return;
|
||||
}
|
||||
|
||||
const context = SillyTavern.getContext();
|
||||
if (!context || !context.characterId) {
|
||||
logDebug('未选择角色,跳过世界书管理。');
|
||||
return;
|
||||
}
|
||||
const bookName = await getTargetWorldBook();
|
||||
if (!bookName) return;
|
||||
const profiles = context.extensionSettings.connectionManager.profiles;
|
||||
|
||||
const entries = (await TavernHelper.getLorebookEntries(bookName)) || [];
|
||||
$profileSelect.empty();
|
||||
$profileSelect.append('<option value="">选择预设</option>');
|
||||
|
||||
const currentChatId = state.currentChatFileIdentifier;
|
||||
if (!currentChatId || currentChatId.startsWith('unknown_chat')) {
|
||||
logError(`无效的聊天标识符 "${currentChatId}"。正在中止世界书管理。`);
|
||||
return;
|
||||
}
|
||||
const cleanChatId = currentChatId.replace(/ imported/g, '');
|
||||
|
||||
let currentChatRosterExists = false;
|
||||
const entriesToUpdate = [];
|
||||
|
||||
for (const entry of entries) {
|
||||
if (Array.isArray(entry.keys) && (entry.keys.includes('Amily2角色总集') || entry.keys.includes(cleanChatId) || entry.keys.includes(currentChatId))) {
|
||||
|
||||
const isForCurrentChat = entry.keys.includes(cleanChatId) || entry.keys.includes(currentChatId);
|
||||
let shouldBeEnabled = isForCurrentChat;
|
||||
|
||||
if (isForCurrentChat && entry.keys.includes('角色总览')) {
|
||||
currentChatRosterExists = true;
|
||||
profiles.forEach(profile => {
|
||||
$profileSelect.append(`<option value="${escapeHtml(profile.id)}">${escapeHtml(profile.name)}</option>`);
|
||||
});
|
||||
const currentProfile = getSettings().cwb_tavern_profile;
|
||||
if (currentProfile) {
|
||||
$profileSelect.val(currentProfile);
|
||||
}
|
||||
|
||||
if (entry.enabled !== shouldBeEnabled) {
|
||||
entriesToUpdate.push({ uid: entry.uid, enabled: shouldBeEnabled });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (entriesToUpdate.length > 0) {
|
||||
await TavernHelper.setLorebookEntries(bookName, entriesToUpdate);
|
||||
logDebug(`已为聊天: ${cleanChatId} 管理了 ${entriesToUpdate.length} 个世界书条目的状态。`);
|
||||
}
|
||||
|
||||
if (!currentChatRosterExists) {
|
||||
logDebug(`未找到聊天 "${cleanChatId}" 的名册。正在触发创建。`);
|
||||
await updateCharacterRosterLorebookEntry([]);
|
||||
if (showNotification) {
|
||||
showToastr('success', `已加载 ${profiles.length} 个SillyTavern预设`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
logError('管理世界书条目时出错:', error);
|
||||
logError('加载SillyTavern预设失败:', error);
|
||||
showToastr('error', '加载SillyTavern预设失败');
|
||||
}
|
||||
}
|
||||
|
||||
function updateUiWithSettings() {
|
||||
if (!$panel) return;
|
||||
const settings = getSettings();
|
||||
|
||||
$panel.find('#cwb-api-mode').val(settings.cwb_api_mode || 'openai_test');
|
||||
|
||||
const currentMode = settings.cwb_api_mode || 'openai_test';
|
||||
updateApiModeUI(currentMode);
|
||||
|
||||
if (currentMode === 'sillytavern_preset') {
|
||||
loadSillyTavernPresets();
|
||||
}
|
||||
|
||||
$panel.find('#cwb-api-url').val(settings.cwb_api_url);
|
||||
$panel.find('#cwb-api-key').val(settings.cwb_api_key);
|
||||
$panel.find('#cwb-tavern-profile').val(settings.cwb_tavern_profile);
|
||||
|
||||
const $modelSelect = $panel.find('#cwb-api-model');
|
||||
if (settings.cwb_api_model) {
|
||||
$modelSelect.empty().append(`<option value="${escapeHtml(settings.cwb_api_model)}">${escapeHtml(settings.cwb_api_model)} (已保存)</option>`);
|
||||
} else {
|
||||
$modelSelect.empty().append('<option value="">请先加载并选择模型</option>');
|
||||
}
|
||||
updateApiStatusDisplay($panel);
|
||||
|
||||
$panel.find('#cwb-break-armor-prompt-textarea').val(settings.cwb_break_armor_prompt);
|
||||
$panel.find('#cwb-char-card-prompt-textarea').val(settings.cwb_char_card_prompt);
|
||||
|
||||
$panel.find('#cwb-temperature').val(settings.cwb_temperature);
|
||||
$panel.find('#cwb-temperature-value').text(settings.cwb_temperature);
|
||||
$panel.find('#cwb-max-tokens').val(settings.cwb_max_tokens);
|
||||
$panel.find('#cwb-max-tokens-value').text(settings.cwb_max_tokens);
|
||||
|
||||
$panel.find('#cwb-auto-update-threshold').val(settings.cwb_auto_update_threshold);
|
||||
$panel.find('#cwb_master_enabled-checkbox').prop('checked', settings.cwb_master_enabled);
|
||||
$panel.find('#cwb-auto-update-enabled-checkbox').prop('checked', settings.cwb_auto_update_enabled);
|
||||
$panel.find('#cwb-viewer-enabled-checkbox').prop('checked', settings.cwb_viewer_enabled);
|
||||
$panel.find('#cwb-incremental-update-enabled-checkbox').prop('checked', settings.cwb_incremental_update_enabled);
|
||||
|
||||
if (!$panel.find('#cwb-start-floor').val()) {
|
||||
$panel.find('#cwb-start-floor').val(1);
|
||||
}
|
||||
if (!$panel.find('#cwb-end-floor').val()) {
|
||||
$panel.find('#cwb-end-floor').val(1);
|
||||
}
|
||||
|
||||
$panel.find('input[name="cwb_worldbook_target"]').each(function() {
|
||||
$(this).prop('checked', $(this).val() === settings.cwb_worldbook_target);
|
||||
});
|
||||
if (settings.cwb_worldbook_target === 'custom') {
|
||||
$panel.find('#cwb_worldbook_select_wrapper').show();
|
||||
} else {
|
||||
$panel.find('#cwb_worldbook_select_wrapper').hide();
|
||||
}
|
||||
}
|
||||
|
||||
export function loadSettings() {
|
||||
console.log('[CWB] Loading settings...');
|
||||
|
||||
const settings = getSettings();
|
||||
if (!settings) {
|
||||
extension_settings[extensionName] = { ...cwbCompleteDefaultSettings };
|
||||
console.log('[CWB] Initialized default settings');
|
||||
} else {
|
||||
Object.keys(cwbCompleteDefaultSettings).forEach(key => {
|
||||
if (settings[key] === undefined || settings[key] === null) {
|
||||
settings[key] = cwbCompleteDefaultSettings[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const finalSettings = getSettings();
|
||||
const overrides = JSON.parse(localStorage.getItem(CWB_BOOLEAN_SETTINGS_OVERRIDE_KEY) || '{}');
|
||||
if (overrides.cwb_master_enabled !== undefined) {
|
||||
finalSettings.cwb_master_enabled = overrides.cwb_master_enabled;
|
||||
}
|
||||
if (overrides.cwb_auto_update_enabled !== undefined) {
|
||||
finalSettings.cwb_auto_update_enabled = overrides.cwb_auto_update_enabled;
|
||||
}
|
||||
if (overrides.cwb_viewer_enabled !== undefined) {
|
||||
finalSettings.cwb_viewer_enabled = overrides.cwb_viewer_enabled;
|
||||
}
|
||||
if (overrides.cwb_incremental_update_enabled !== undefined) {
|
||||
finalSettings.cwb_incremental_update_enabled = overrides.cwb_incremental_update_enabled;
|
||||
}
|
||||
state.masterEnabled = finalSettings.cwb_master_enabled;
|
||||
state.viewerEnabled = finalSettings.cwb_viewer_enabled;
|
||||
state.autoUpdateEnabled = finalSettings.cwb_auto_update_enabled;
|
||||
state.isIncrementalUpdateEnabled = finalSettings.cwb_incremental_update_enabled;
|
||||
|
||||
state.customApiConfig.url = finalSettings.cwb_api_url || '';
|
||||
state.customApiConfig.apiKey = finalSettings.cwb_api_key || '';
|
||||
state.customApiConfig.model = finalSettings.cwb_api_model || '';
|
||||
|
||||
state.currentBreakArmorPrompt = finalSettings.cwb_break_armor_prompt;
|
||||
state.currentCharCardPrompt = finalSettings.cwb_char_card_prompt;
|
||||
state.currentIncrementalCharCardPrompt = finalSettings.cwb_incremental_char_card_prompt;
|
||||
|
||||
state.autoUpdateThreshold = finalSettings.cwb_auto_update_threshold;
|
||||
state.worldbookTarget = finalSettings.cwb_worldbook_target;
|
||||
state.customWorldBook = finalSettings.cwb_custom_worldbook;
|
||||
|
||||
console.log('[CWB] State updated:', {
|
||||
masterEnabled: state.masterEnabled,
|
||||
viewerEnabled: state.viewerEnabled,
|
||||
autoUpdateEnabled: state.autoUpdateEnabled,
|
||||
worldbookTarget: state.worldbookTarget,
|
||||
customWorldBook: state.customWorldBook
|
||||
});
|
||||
|
||||
if ($panel) {
|
||||
updateUiWithSettings();
|
||||
}
|
||||
|
||||
updateControlsLockState();
|
||||
|
||||
setTimeout(() => {
|
||||
const $viewerButton = $(`#${CHAR_CARD_VIEWER_BUTTON_ID}`);
|
||||
if ($viewerButton.length > 0) {
|
||||
const shouldShow = isCwbEnabled() && state.viewerEnabled;
|
||||
$viewerButton.toggle(shouldShow);
|
||||
console.log('[CWB] Viewer button visibility updated:', shouldShow);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user