From f5418841cb055d09fcb659b2d4dd01cf348ada2d Mon Sep 17 00:00:00 2001 From: Spicy_Marinara Date: Sun, 19 Oct 2025 20:05:17 +0200 Subject: [PATCH] Add preset switching feature and clean up console logs - Add 'Use separate preset for tracker generation' setting - Implement automatic preset switching using /preset slash command - Import getCurrentPresetName() from SillyTavern's regex engine - Automatically import 'RPG Companion Trackers' preset on first load - Comment out non-essential console.log statements - Keep initialization, error, and migration logs for debugging --- RPG Companion Trackers.json | 264 ++++++++++++++++++++ index.js | 66 ++++- src/core/config.js | 1 + src/core/persistence.js | 2 +- src/core/state.js | 1 + src/systems/generation/apiClient.js | 64 +++++ src/systems/generation/promptBuilder.js | 6 +- src/systems/interaction/inventoryActions.js | 2 +- src/utils/avatars.js | 4 +- template.html | 8 + 10 files changed, 410 insertions(+), 8 deletions(-) create mode 100644 RPG Companion Trackers.json diff --git a/RPG Companion Trackers.json b/RPG Companion Trackers.json new file mode 100644 index 0000000..62d3ee1 --- /dev/null +++ b/RPG Companion Trackers.json @@ -0,0 +1,264 @@ +{ + "chat_completion_source": "custom", + "openai_model": "gpt-4o", + "claude_model": "claude-3-sonnet-20240229", + "openrouter_model": "OR_Website", + "openrouter_use_fallback": false, + "openrouter_group_models": false, + "openrouter_sort_models": "alphabetically", + "openrouter_providers": [], + "openrouter_allow_fallbacks": true, + "openrouter_middleout": "on", + "ai21_model": "jamba-large", + "mistralai_model": "mistral-large-latest", + "cohere_model": "command-r-plus", + "perplexity_model": "llama-3-70b-instruct", + "groq_model": "llama3-70b-8192", + "xai_model": "grok-4-0709", + "pollinations_model": "openai", + "aimlapi_model": "gpt-4o-mini-2024-07-18", + "electronhub_model": "gpt-4o-mini", + "electronhub_sort_models": "alphabetically", + "electronhub_group_models": false, + "moonshot_model": "kimi-latest", + "fireworks_model": "accounts/fireworks/models/kimi-k2-instruct", + "cometapi_model": "gpt-4o", + "custom_model": "", + "custom_prompt_post_processing": "semi", + "google_model": "gemini-pro", + "vertexai_model": "gemini-2.5-pro-exp-03-25", + "azure_api_version": "2024-02-15-preview", + "azure_openai_model": "", + "temperature": 1, + "frequency_penalty": 0, + "presence_penalty": 0, + "top_p": 1, + "top_k": 0, + "top_a": 1, + "min_p": 0, + "repetition_penalty": 1, + "openai_max_context": 16384, + "openai_max_tokens": 8192, + "wrap_in_quotes": false, + "names_behavior": -1, + "send_if_empty": "", + "impersonation_prompt": "", + "new_chat_prompt": "", + "new_group_chat_prompt": "", + "new_example_chat_prompt": "", + "continue_nudge_prompt": "", + "bias_preset_selected": "Default (none)", + "max_context_unlocked": false, + "wi_format": "", + "scenario_format": "", + "personality_format": "", + "group_nudge_prompt": "", + "stream_openai": false, + "prompts": [ + { + "name": "Main Prompt", + "system_prompt": true, + "role": "system", + "content": "", + "identifier": "main", + "injection_position": 0, + "injection_depth": 4, + "forbid_overrides": false + }, + { + "name": "NSFW Prompt", + "system_prompt": true, + "role": "system", + "content": "", + "identifier": "nsfw" + }, + { + "identifier": "dialogueExamples", + "name": "Chat Examples", + "system_prompt": true, + "marker": true + }, + { + "name": "Jailbreak Prompt", + "system_prompt": true, + "role": "system", + "content": "", + "identifier": "jailbreak" + }, + { + "identifier": "chatHistory", + "name": "Chat History", + "system_prompt": true, + "marker": true + }, + { + "identifier": "worldInfoAfter", + "name": "World Info (after)", + "system_prompt": true, + "marker": true + }, + { + "identifier": "worldInfoBefore", + "name": "World Info (before)", + "system_prompt": true, + "marker": true + }, + { + "identifier": "enhanceDefinitions", + "role": "system", + "name": "Enhance Definitions", + "content": "If you have more knowledge of {{char}}, add to the character's lore and personality to enhance them but keep the Character Sheet's definitions absolute.", + "system_prompt": true, + "marker": false + }, + { + "identifier": "charDescription", + "name": "Char Description", + "system_prompt": true, + "marker": true + }, + { + "identifier": "charPersonality", + "name": "Char Personality", + "system_prompt": true, + "marker": true + }, + { + "identifier": "scenario", + "name": "Scenario", + "system_prompt": true, + "marker": true + }, + { + "identifier": "personaDescription", + "name": "Persona Description", + "system_prompt": true, + "marker": true + } + ], + "prompt_order": [ + { + "character_id": 100000, + "order": [ + { + "identifier": "main", + "enabled": true + }, + { + "identifier": "worldInfoBefore", + "enabled": true + }, + { + "identifier": "charDescription", + "enabled": true + }, + { + "identifier": "charPersonality", + "enabled": true + }, + { + "identifier": "scenario", + "enabled": true + }, + { + "identifier": "enhanceDefinitions", + "enabled": false + }, + { + "identifier": "nsfw", + "enabled": true + }, + { + "identifier": "worldInfoAfter", + "enabled": true + }, + { + "identifier": "dialogueExamples", + "enabled": true + }, + { + "identifier": "chatHistory", + "enabled": true + }, + { + "identifier": "jailbreak", + "enabled": true + } + ] + }, + { + "character_id": 100001, + "order": [ + { + "identifier": "main", + "enabled": false + }, + { + "identifier": "worldInfoBefore", + "enabled": false + }, + { + "identifier": "personaDescription", + "enabled": false + }, + { + "identifier": "charDescription", + "enabled": false + }, + { + "identifier": "charPersonality", + "enabled": false + }, + { + "identifier": "scenario", + "enabled": false + }, + { + "identifier": "enhanceDefinitions", + "enabled": false + }, + { + "identifier": "nsfw", + "enabled": false + }, + { + "identifier": "worldInfoAfter", + "enabled": false + }, + { + "identifier": "dialogueExamples", + "enabled": false + }, + { + "identifier": "chatHistory", + "enabled": false + }, + { + "identifier": "jailbreak", + "enabled": false + } + ] + } + ], + "show_external_models": false, + "assistant_prefill": "", + "assistant_impersonation": "", + "claude_use_sysprompt": true, + "use_makersuite_sysprompt": true, + "vertexai_auth_mode": "full", + "squash_system_messages": true, + "image_inlining": false, + "inline_image_quality": "auto", + "video_inlining": false, + "bypass_status_check": false, + "continue_prefill": false, + "continue_postfix": "", + "function_calling": false, + "show_thoughts": false, + "reasoning_effort": "auto", + "enable_web_search": false, + "request_images": false, + "seed": -1, + "n": 1, + "extensions": {} +} \ No newline at end of file diff --git a/index.js b/index.js index ebb968a..558f0c5 100644 --- a/index.js +++ b/index.js @@ -242,6 +242,11 @@ async function initUI() { updateGenerationModeUI(); }); + $('#rpg-use-separate-preset').on('change', function() { + extensionSettings.useSeparatePreset = $(this).prop('checked'); + saveSettings(); + }); + $('#rpg-toggle-user-stats').on('change', function() { extensionSettings.showUserStats = $(this).prop('checked'); saveSettings(); @@ -367,7 +372,7 @@ async function initUI() { $('#rpg-toggle-auto-update').prop('checked', extensionSettings.autoUpdate); $('#rpg-position-select').val(extensionSettings.panelPosition); $('#rpg-update-depth').val(extensionSettings.updateDepth); - $('#rpg-use-main-model').prop('checked', extensionSettings.useMainModel); + $('#rpg-use-separate-preset').prop('checked', extensionSettings.useSeparatePreset); $('#rpg-toggle-user-stats').prop('checked', extensionSettings.showUserStats); $('#rpg-toggle-info-box').prop('checked', extensionSettings.showInfoBox); $('#rpg-toggle-thoughts').prop('checked', extensionSettings.showCharacterThoughts); @@ -432,11 +437,62 @@ async function initUI() { // (commitTrackerData, onMessageSent, onMessageReceived, onCharacterChanged, // onMessageSwiped, updatePersonaAvatar, clearExtensionPrompts) +/** + * Ensures the "RPG Companion Trackers" preset exists in the user's OpenAI Settings. + * Imports the preset file from the extension folder if it doesn't exist. + */ +async function ensureTrackerPresetExists() { + try { + const presetName = 'RPG Companion Trackers'; + const presetPath = `data/default-user/OpenAI Settings/${presetName}.json`; + // Check if preset already exists + const checkResponse = await fetch(`/${presetPath}`, { method: 'HEAD' }); + if (checkResponse.ok) { + console.log(`[RPG Companion] Preset "${presetName}" already exists`); + return; + } + // Preset doesn't exist - import it from extension folder + console.log(`[RPG Companion] Importing preset "${presetName}"...`); + // Load preset from extension folder + const extensionPresetPath = `${extensionFolderPath}/${presetName}.json`; + const presetResponse = await fetch(`/${extensionPresetPath}`); + if (!presetResponse.ok) { + console.warn(`[RPG Companion] Could not load preset template from ${extensionPresetPath}`); + return; + } + + const presetData = await presetResponse.json(); + + // Save preset to user's OpenAI Settings folder + const saveResponse = await fetch('/api/presets/save-openai', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + name: presetName, + preset: presetData + }) + }); + + if (saveResponse.ok) { + console.log(`[RPG Companion] ✅ Successfully imported preset "${presetName}"`); + toastr.success( + `The "RPG Companion Trackers" preset has been imported to your OpenAI Settings.`, + 'RPG Companion', + { timeOut: 5000 } + ); + } else { + console.warn(`[RPG Companion] Failed to save preset: ${saveResponse.statusText}`); + } + } catch (error) { + console.error('[RPG Companion] Error importing tracker preset:', error); + // Non-critical - users can manually import if needed + } +} /** * Main initialization function. @@ -483,6 +539,14 @@ jQuery(async () => { // Non-critical - continue without it } + // Import the RPG Companion Trackers preset if needed + try { + await ensureTrackerPresetExists(); + } catch (error) { + console.error('[RPG Companion] Preset import failed:', error); + // Non-critical - users can manually import if needed + } + // Detect conflicting regex scripts from old manual formatters try { const conflicts = detectConflictingRegexScripts(st_extension_settings); diff --git a/src/core/config.js b/src/core/config.js index 2ddd8b0..93d2495 100644 --- a/src/core/config.js +++ b/src/core/config.js @@ -26,6 +26,7 @@ export const defaultSettings = { autoUpdate: true, updateDepth: 4, // How many messages to include in the context generationMode: 'together', // 'separate' or 'together' - whether to generate with main response or separately + useSeparatePreset: false, // Use 'RPG Companion Trackers' preset for tracker generation instead of main API model showUserStats: true, showInfoBox: true, showCharacterThoughts: true, diff --git a/src/core/persistence.js b/src/core/persistence.js index e54527f..c4ba4cf 100644 --- a/src/core/persistence.js +++ b/src/core/persistence.js @@ -64,7 +64,7 @@ export function loadSettings() { if (!power_user.extensions) { power_user.extensions = {}; - console.log('[RPG Companion] Created power_user.extensions object'); + // console.log('[RPG Companion] Created power_user.extensions object'); } if (power_user.extensions[extensionName]) { diff --git a/src/core/state.js b/src/core/state.js index 295f611..354609c 100644 --- a/src/core/state.js +++ b/src/core/state.js @@ -14,6 +14,7 @@ export let extensionSettings = { autoUpdate: true, updateDepth: 4, // How many messages to include in the context generationMode: 'together', // 'separate' or 'together' - whether to generate with main response or separately + useSeparatePreset: false, // Use 'RPG Companion Trackers' preset for tracker generation instead of main API model showUserStats: true, showInfoBox: true, showCharacterThoughts: true, diff --git a/src/systems/generation/apiClient.js b/src/systems/generation/apiClient.js index a613be2..fef85d5 100644 --- a/src/systems/generation/apiClient.js +++ b/src/systems/generation/apiClient.js @@ -4,6 +4,7 @@ */ import { generateRaw, chat } from '../../../../../../../script.js'; +import { executeSlashCommandsOnChatInput } from '../../../../../../../scripts/slash-commands.js'; import { extensionSettings, lastGeneratedData, @@ -16,6 +17,49 @@ import { import { saveChatData } from '../../core/persistence.js'; import { generateSeparateUpdatePrompt } from './promptBuilder.js'; import { parseResponse, parseUserStats } from './parser.js'; +import { getCurrentPresetName } from '../../../../../regex/engine.js'; + +/** + * Switches to a specific preset by name using the /preset slash command + * @param {string} presetName - Name of the preset to switch to + * @returns {Promise} The previous preset name, or null if switching failed + */ +async function switchToPreset(presetName) { + try { + // Get the current preset before switching using SillyTavern's built-in API + const previousPreset = getCurrentPresetName(); + + // Use the /preset slash command to switch presets + // This is the proper way to change presets in SillyTavern + await executeSlashCommandsOnChatInput(`/preset ${presetName}`, { quiet: true }); + + // console.log(`[RPG Companion] Switched from preset "${previousPreset || 'none'}" to "${presetName}"`); + return previousPreset; + } catch (error) { + console.error('[RPG Companion] Error switching preset:', error); + return null; + } +} + +/** + * Restores a previously saved preset using the /preset slash command + * @param {string} presetName - Name of the preset to restore + */ +async function restorePreset(presetName) { + try { + if (!presetName) { + console.warn('[RPG Companion] No preset name to restore'); + return; + } + + // Use the /preset slash command to restore the preset + await executeSlashCommandsOnChatInput(`/preset ${presetName}`, { quiet: true }); + + // console.log(`[RPG Companion] Restored preset to "${presetName}"`); + } catch (error) { + console.error('[RPG Companion] Error restoring preset:', error); + } +} /** * Updates RPG tracker data using separate API call (separate mode only). @@ -42,6 +86,8 @@ export async function updateRPGData(renderUserStats, renderInfoBox, renderThough return; } + let previousPreset = null; + try { setIsGenerating(true); @@ -50,6 +96,14 @@ export async function updateRPGData(renderUserStats, renderInfoBox, renderThough const originalHtml = $updateBtn.html(); $updateBtn.html(' Updating...').prop('disabled', true); + // Switch to separate preset if enabled + if (extensionSettings.useSeparatePreset) { + previousPreset = await switchToPreset('RPG Companion Trackers'); + if (!previousPreset) { + console.warn('[RPG Companion] Failed to switch to RPG Companion Trackers preset. Using current preset.'); + } + } + const prompt = generateSeparateUpdatePrompt(); // Generate using raw prompt (uses current preset, no chat history) @@ -142,9 +196,19 @@ export async function updateRPGData(renderUserStats, renderInfoBox, renderThough } catch (error) { console.error('[RPG Companion] Error updating RPG data:', error); + + // Restore preset on error if we switched it + if (extensionSettings.useSeparatePreset && previousPreset) { + await restorePreset(previousPreset); + } } finally { setIsGenerating(false); + // Restore preset after successful generation + if (extensionSettings.useSeparatePreset && previousPreset) { + await restorePreset(previousPreset); + } + // Restore button to original state const $updateBtn = $('#rpg-manual-update'); $updateBtn.html(' Refresh RPG Info').prop('disabled', false); diff --git a/src/systems/generation/promptBuilder.js b/src/systems/generation/promptBuilder.js index a24135b..970a5ce 100644 --- a/src/systems/generation/promptBuilder.js +++ b/src/systems/generation/promptBuilder.js @@ -100,7 +100,7 @@ export function generateTrackerInstructions(includeHtmlPrompt = true, includeCon // Only add tracker instructions if at least one tracker is enabled if (hasAnyTrackers) { // Universal instruction header - instructions += `\nYou must start your response with an appropriate update to the trackers in EXACTLY the same format as below, enclosed in separate Markdown code fences. Replace X with proper numbers and placeholders in [brackets] (while removing the brackets themselves) with in-world details ${userName} perceives about the current scene and the present characters. Consider the last trackers in the conversation (if they exist). Manage them accordingly and realistically; raise, lower, change, or keep the values unchanged based on the user's actions, the passage of time, and logical consequences:\n`; + instructions += `\nAt the start of every reply, you must attach update to the trackers in EXACTLY the same format as below, enclosed in separate Markdown code fences. Replace X with proper numbers and [placeholders] with in-world details ${userName} perceives about the current scene and the present characters. Consider the last trackers in the conversation (if they exist). Manage them accordingly and realistically; raise, lower, change, or keep the values unchanged based on the user's actions, the passage of time, and logical consequences:\n`; // Add format specifications for each enabled tracker if (extensionSettings.showUserStats) { @@ -150,7 +150,7 @@ export function generateTrackerInstructions(includeHtmlPrompt = true, includeCon // Only add continuation instruction if includeContinuation is true if (includeContinuation) { - instructions += `After updating the trackers, continue directly from where the last message in the chat history left off. Ensure the trackers you provide naturally reflect and influence the narrative. Character behavior, dialogue, and story events should acknowledge these conditions when relevant, such as fatigue affecting performance, low hygiene influencing social interactions, environmental factors shaping the scene, a character's emotional state coloring their responses, and so on.\n\n`; + instructions += `After updating the trackers, continue directly from where the last message in the chat history left off. Ensure the trackers you provide naturally reflect and influence the narrative. Character behavior, dialogue, and story events should acknowledge these conditions when relevant, such as fatigue affecting the protagonist's performance, low hygiene influencing their social interactions, environmental factors shaping the scene, a character's emotional state coloring their responses, and so on. Do not render brackets.\n\n`; } // Include attributes and dice roll only if there was a dice roll @@ -373,7 +373,7 @@ export function generateSeparateUpdatePrompt() { // Build the instruction message let instructionMessage = `\n\n`; instructionMessage += generateRPGPromptText().replace('start your response with', 'respond with'); - instructionMessage += `Provide ONLY the requested data in the exact formats specified above. Do not include any roleplay response, other text, or commentary.`; + instructionMessage += `Provide ONLY the requested data in the exact formats specified above. Do not include any roleplay response, other text, or commentary. Do not render brackets.`; messages.push({ role: 'user', diff --git a/src/systems/interaction/inventoryActions.js b/src/systems/interaction/inventoryActions.js index 80934c0..bd1255d 100644 --- a/src/systems/interaction/inventoryActions.js +++ b/src/systems/interaction/inventoryActions.js @@ -469,7 +469,7 @@ export function initInventoryEventListeners() { switchViewMode(field, view); }); - console.log('[RPG Companion] Inventory event listeners initialized'); + // console.log('[RPG Companion] Inventory event listeners initialized'); } /** diff --git a/src/utils/avatars.js b/src/utils/avatars.js index 382870c..ed64a57 100644 --- a/src/utils/avatars.js +++ b/src/utils/avatars.js @@ -16,7 +16,7 @@ import { getThumbnailUrl } from '../../../../../../script.js'; export function getSafeThumbnailUrl(type, filename) { // Return null if no filename provided if (!filename || filename === 'none') { - console.log(`[RPG Companion] No valid filename provided for ${type} thumbnail`); + // console.log(`[RPG Companion] No valid filename provided for ${type} thumbnail`); return null; } @@ -30,7 +30,7 @@ export function getSafeThumbnailUrl(type, filename) { return null; } - console.log(`[RPG Companion] Successfully generated ${type} thumbnail URL for: ${filename}`); + // console.log(`[RPG Companion] Successfully generated ${type} thumbnail URL for: ${filename}`); return url; } catch (error) { // Log detailed error information for debugging diff --git a/template.html b/template.html index a720db6..8d24685 100644 --- a/template.html +++ b/template.html @@ -215,6 +215,14 @@ Number of recent messages to include (Separate mode only) + + + Separate mode only. When enabled, tracker generation will use the model from the "RPG Companion Trackers" preset instead of your main API model. The preset will be switched automatically during generation and restored afterward. Select the desired model in that preset and make sure the "Bind presets to API connections" toggle is on (next to the import/export preset buttons). + +