From 407a45a25c9ff332112e0424fd939f453e97303c Mon Sep 17 00:00:00 2001 From: Andy Mauragis Date: Thu, 13 Nov 2025 13:45:27 -0500 Subject: [PATCH 1/3] feat: Add core suppression logic and integrate into prompt injector --- src/systems/generation/injector.js | 41 ++++++++++++-- src/systems/generation/suppression.js | 82 +++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 6 deletions(-) create mode 100644 src/systems/generation/suppression.js 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 + }; +} From 0ac85ad9fd0043b032e147f4a913bfa84c2764e4 Mon Sep 17 00:00:00 2001 From: Andy Mauragis Date: Thu, 13 Nov 2025 13:45:27 -0500 Subject: [PATCH 2/3] feat: Add 'Skip Injections during Guided Generations' setting and UI --- index.js | 6 ++++++ src/core/config.js | 7 +++++++ src/core/state.js | 1 + template.html | 12 ++++++++++++ 4 files changed, 26 insertions(+) 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/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. + +