Merge pull request #47 from amauragis/guided-generation-compat

Add a prompt injection suppresson feature for Guided Generations
This commit is contained in:
Spicy Marinara
2025-11-13 20:59:13 +01:00
committed by GitHub
7 changed files with 155 additions and 6 deletions
+12
View File
@@ -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:
+6
View File
@@ -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();
+7
View File
@@ -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
+1
View File
@@ -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
+35 -6
View File
@@ -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.
+82
View File
@@ -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
};
}
+12
View File
@@ -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).
</small>
<div class="rpg-setting-row">
<label for="rpg-skip-guided-mode">Skip Injections during Guided Generations:</label>
<select id="rpg-skip-guided-mode" class="rpg-select">
<option value="none">Never skip</option>
<option value="impersonation">Only on impersonation requests</option>
<option value="guided">Always for guided or quiet prompts</option>
</select>
</div>
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;">
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.
</small>
<!-- Clear Cache Button -->
<div style="margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--rpg-border);">
<button id="rpg-clear-cache" class="rpg-btn-clear-cache">