diff --git a/README.md b/README.md index b03d301..d042c0b 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,18 @@ The extension fully supports swipes: You can click the "Refresh RPG Info" button in the settings to refresh the RPG data at any time in separate generation mode. +### Compatibility with Guided Generations + +This extension detects when a "guided generation" prompt is submitted (for example, via the GuidedGenerations extension which injects an ephemeral `instruct` prompt), and will avoid adding its tracker injection instructions (requests for stats, info box, and context prompts) to the generation context. This prevents conflicting instructions and ensures guided generations behave as the user expects. + +If you want tracker prompts to apply during a guided generation, run the update via separate generation or temporarily disable guided generation in the other extension. + +There is a new setting "Skip Tracker & HTML Injections during Guided Generations" in the RPG Companion settings (Advanced section). It now supports three modes: +- none: never skip (always inject the tracker prompts as usual, default) +- impersonation: only skip when an impersonation-style guided generation is detected +- guided: skip whenever a guided `instruct` or `quiet_prompt` generation is detected + + ## 🎨 Themes Choose from 6 beautiful themes: diff --git a/index.js b/index.js index cdb4713..46e2b1a 100644 --- a/index.js +++ b/index.js @@ -313,6 +313,11 @@ async function initUI() { saveSettings(); }); + $('#rpg-skip-guided-mode').on('change', function() { + extensionSettings.skipInjectionsForGuided = String($(this).val()); + saveSettings(); + }); + $('#rpg-toggle-plot-buttons').on('change', function() { extensionSettings.enablePlotButtons = $(this).prop('checked'); // console.log('[RPG Companion] Toggle enablePlotButtons changed to:', extensionSettings.enablePlotButtons); @@ -420,6 +425,7 @@ async function initUI() { $('#rpg-custom-text').val(extensionSettings.customColors.text); $('#rpg-custom-highlight').val(extensionSettings.customColors.highlight); $('#rpg-generation-mode').val(extensionSettings.generationMode); + $('#rpg-skip-guided-mode').val(extensionSettings.skipInjectionsForGuided); updatePanelVisibility(); updateSectionVisibility(); diff --git a/src/core/config.js b/src/core/config.js index 184b11d..4901cec 100644 --- a/src/core/config.js +++ b/src/core/config.js @@ -34,6 +34,13 @@ export const defaultSettings = { showThoughtsInChat: true, // Show thoughts overlay in chat alwaysShowThoughtBubble: false, // Auto-expand thought bubble without clicking icon enableHtmlPrompt: false, // Enable immersive HTML prompt injection + // Controls when the extension skips injecting tracker instructions/examples/HTML + // into generations that appear to be user-injected instructions. Valid values: + // - 'none' -> never skip (legacy behavior: always inject) + // - 'guided' -> skip for any guided / instruct or quiet_prompt generation + // - 'impersonation' -> skip only for impersonation-style guided generations + // This setting helps compatibility with other extensions like GuidedGenerations. + skipInjectionsForGuided: 'none', enablePlotButtons: true, // Show plot progression buttons above chat input panelPosition: 'right', // 'left', 'right', or 'top' theme: 'default', // Theme: default, sci-fi, fantasy, cyberpunk, custom diff --git a/src/core/state.js b/src/core/state.js index 1a4c190..9c79f73 100644 --- a/src/core/state.js +++ b/src/core/state.js @@ -21,6 +21,7 @@ export let extensionSettings = { showInventory: true, // Show inventory section (v2 system) showThoughtsInChat: true, // Show thoughts overlay in chat enableHtmlPrompt: false, // Enable immersive HTML prompt injection + skipInjectionsForGuided: 'none', // skip injections for instruct injections and quiet prompts (GuidedGenerations compatibility) enablePlotButtons: true, // Show plot progression buttons above chat input panelPosition: 'right', // 'left', 'right', or 'top' theme: 'default', // Theme: default, sci-fi, fantasy, cyberpunk, custom diff --git a/src/systems/generation/injector.js b/src/systems/generation/injector.js index e4d8955..e6556b6 100644 --- a/src/systems/generation/injector.js +++ b/src/systems/generation/injector.js @@ -13,6 +13,7 @@ import { lastActionWasSwipe, setLastActionWasSwipe } from '../../core/state.js'; +import { evaluateSuppression } from './suppression.js'; import { parseUserStats } from './parser.js'; import { generateTrackerExample, @@ -44,7 +45,28 @@ export function onGenerationStarted(type, data) { return; } - const chat = getContext().chat; + const context = getContext(); + const chat = context.chat; + // Detect if a guided generation is active (GuidedGenerations and similar extensions + // inject an ephemeral 'instruct' injection into chatMetadata.script_injects). + // If present, we should avoid injecting RPG tracker instructions that ask + // the model to include stats/etc. This prevents conflicts when guided prompts + // are used (e.g., GuidedGenerations Extension). + // Evaluate suppression using the shared helper + const suppression = evaluateSuppression(extensionSettings, context, data); + const { shouldSuppress, skipMode, isGuidedGeneration, isImpersonationGeneration, hasQuietPrompt, instructContent, quietPromptRaw, matchedPattern } = suppression; + + if (shouldSuppress) { + // Debugging: indicate active suppression and which source triggered it + console.debug(`[RPG Companion] Suppression active (mode=${skipMode}). isGuided=${isGuidedGeneration}, isImpersonation=${isImpersonationGeneration}, hasQuietPrompt=${hasQuietPrompt} - skipping RPG tracker injections for this generation.`); + + // Also clear any existing RPG Companion prompts so they do not leak into this generation + // (e.g., previously set extension prompts should not be used alongside a guided prompt) + setExtensionPrompt('rpg-companion-inject', '', extension_prompt_types.IN_CHAT, 0, false); + setExtensionPrompt('rpg-companion-example', '', extension_prompt_types.IN_CHAT, 0, false); + setExtensionPrompt('rpg-companion-html', '', extension_prompt_types.IN_CHAT, 0, false); + setExtensionPrompt('rpg-companion-context', '', extension_prompt_types.IN_CHAT, 1, false); + } const lastMessage = chat && chat.length > 0 ? chat[chat.length - 1] : null; // For SEPARATE mode only: Check if we need to commit extension data @@ -145,7 +167,7 @@ export function onGenerationStarted(type, data) { } // If we have previous tracker data and found an assistant message, inject it as an assistant message - if (example && lastAssistantDepth > 0) { + if (!shouldSuppress && example && lastAssistantDepth > 0) { setExtensionPrompt('rpg-companion-example', example, extension_prompt_types.IN_CHAT, lastAssistantDepth, false, extension_prompt_roles.ASSISTANT); // console.log('[RPG Companion] Injected tracker example as assistant message at depth:', lastAssistantDepth); } else { @@ -153,11 +175,15 @@ export function onGenerationStarted(type, data) { } // Inject the instructions as a user message at depth 0 (right before generation) - setExtensionPrompt('rpg-companion-inject', instructions, extension_prompt_types.IN_CHAT, 0, false, extension_prompt_roles.USER); + // If this is a guided generation (user explicitly injected 'instruct'), skip adding + // our tracker instructions to avoid clobbering the guided prompt. + if (!shouldSuppress) { + setExtensionPrompt('rpg-companion-inject', instructions, extension_prompt_types.IN_CHAT, 0, false, extension_prompt_roles.USER); + } // console.log('[RPG Companion] Injected RPG tracking instructions at depth 0 (right before generation)'); // Inject HTML prompt separately at depth 0 if enabled (prevents duplication on swipes) - if (extensionSettings.enableHtmlPrompt) { + if (extensionSettings.enableHtmlPrompt && !shouldSuppress) { const htmlPrompt = `\nIf appropriate, include inline HTML, CSS, and JS elements for creative, visual storytelling throughout your response: - Use them liberally to depict any in-world content that can be visualized (screens, posters, books, signs, letters, logos, crests, seals, medallions, labels, etc.), with creative license for animations, 3D effects, pop-ups, dropdowns, websites, and so on. - Style them thematically to match the theme (e.g., sleek for sci-fi, rustic for fantasy), ensuring text is visible. @@ -186,7 +212,10 @@ Ensure these details naturally reflect and influence the narrative. Character be `; // Inject context at depth 1 (before last user message) as SYSTEM - setExtensionPrompt('rpg-companion-context', wrappedContext, extension_prompt_types.IN_CHAT, 1, false); + // Skip when a guided generation injection is present to avoid conflicting instructions + if (!shouldSuppress) { + setExtensionPrompt('rpg-companion-context', wrappedContext, extension_prompt_types.IN_CHAT, 1, false); + } // console.log('[RPG Companion] Injected contextual summary for separate mode:', contextSummary); } else { // Clear if no data yet @@ -194,7 +223,7 @@ Ensure these details naturally reflect and influence the narrative. Character be } // Inject HTML prompt separately at depth 0 if enabled (same as together mode pattern) - if (extensionSettings.enableHtmlPrompt) { + if (extensionSettings.enableHtmlPrompt && !shouldSuppress) { const htmlPrompt = `\nIf appropriate, include inline HTML, CSS, and JS elements for creative, visual storytelling throughout your response: - Use them liberally to depict any in-world content that can be visualized (screens, posters, books, signs, letters, logos, crests, seals, medallions, labels, etc.), with creative license for animations, 3D effects, pop-ups, dropdowns, websites, and so on. - Style them thematically to match the theme (e.g., sleek for sci-fi, rustic for fantasy), ensuring text is visible. diff --git a/src/systems/generation/suppression.js b/src/systems/generation/suppression.js new file mode 100644 index 0000000..aa0313b --- /dev/null +++ b/src/systems/generation/suppression.js @@ -0,0 +1,82 @@ +/** + * Suppression helper for guided generation injection behavior. + * + * This module exports a pure function `evaluateSuppression` that computes + * whether RPG Companion should suppress tracker and HTML injections for a + * given generation request, based on runtime settings, extended context, and + * generation data (quiet prompt flags, etc.). + */ + +/** + * Determine if suppression should be applied for this generation. + * + * @param {any} extensionSettings - extension settings object (may contain skipInjectionsForGuided) + * @param {any} context - SillyTavern context object (used to find chatMetadata.script_injects.instruct) + * @param {any} data - Generation data (contains quiet_prompt/quietPrompt flags) + * @returns {Object} - An object describing the suppression decision. + */ +export function evaluateSuppression(extensionSettings, context, data) { + // Detect presence of any injected `instruct` script + const instructObj = context?.chatMetadata?.script_injects?.instruct; + const isGuidedGeneration = !!instructObj; + const quietPromptRaw = data?.quiet_prompt || data?.quietPrompt || ''; + const hasQuietPrompt = !!quietPromptRaw; + + // Normalize the injected instruction body (it may be an object with a 'value' field or a raw string) + let instructContent = ''; + if (instructObj) { + if (typeof instructObj === 'object') { + instructContent = String(instructObj.value || instructObj || ''); + } else { + instructContent = String(instructObj); + } + } + + const IMPERSONATION_PATTERNS = [ + { id: 'first-perspective', re: /write in first person perspective from/i }, + { id: 'second-perspective', re: /write in second person perspective from/i }, + { id: 'third-perspective', re: /write in third person perspective from/i }, + { id: 'you-yours', re: /using you\/yours for/i }, + { id: 'third-person-pronouns', re: /third-person pronouns for/i }, + { id: 'impersonate-word', re: /\bimpersonat(e|ion)?\b/i }, + { id: 'assume-role', re: /assume the role of/i }, + { id: 'play-role', re: /play the role of/i }, + { id: 'impersonate-command', re: /\/impersonate await=true/i }, + { id: 'generic-first', re: /\bfirst person\b/i }, + { id: 'generic-second', re: /\bsecond person\b/i }, + { id: 'generic-third', re: /\bthird person\b/i } + ]; + + // Include quietPrompt raw text in detection; guided impersonation flows may pass it directly here + const combinedTextForDetection = [instructContent, quietPromptRaw].filter(Boolean).join('\n'); + + let matchedPattern = ''; + let isImpersonationGeneration = false; + if (combinedTextForDetection.length) { + for (const pat of IMPERSONATION_PATTERNS) { + if (pat.re.test(combinedTextForDetection)) { + matchedPattern = pat.id; + isImpersonationGeneration = true; + break; + } + } + } + + const skipMode = (extensionSettings && extensionSettings.skipInjectionsForGuided) || 'none'; + + // Compute suppression according to mode + const shouldSuppress = skipMode === 'guided' + ? (isGuidedGeneration || hasQuietPrompt) + : (skipMode === 'impersonation' ? isImpersonationGeneration : false); + + return { + shouldSuppress, + skipMode, + isGuidedGeneration, + isImpersonationGeneration, + hasQuietPrompt, + instructContent, + quietPromptRaw, + matchedPattern + }; +} diff --git a/template.html b/template.html index 2f67991..c19a5ec 100644 --- a/template.html +++ b/template.html @@ -255,6 +255,18 @@ 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). +
+ + +
+ + When set, the extension will not inject tracker prompts, examples, or HTML instructions according to the selected mode when a guided generation (via `instruct` or `quiet_prompt`) is detected. Useful when using GuidedGenerations or similar extensions. + +