Skip Diceroll when a manual instruction steers the generation Guided Generations (and similar tools) inject their custom instruction via /inject id=instruct before triggering a swipe or response. When such an inject is active — the id list is configurable, default "instruct" — or the generation was started with its own custom prompt, Diceroll neither rolls nor injects a direction, letting the manual instruction steer alone. Plain swipes afterwards dice again as usual.
| @@ -72,6 +72,9 @@ const defaultSettings = { | ||
| 72 | 72 | useStructuredOutput: true, |
| 73 | 73 | notify: true, |
| 74 | 74 | debugDisplay: false, |
| 75 | + // Comma-separated /inject ids whose presence means another tool is already steering this | |
| 76 | + // generation (Guided Generations uses id "instruct" for guided response/swipe/corrections). | |
| 77 | + skipInjectIds: 'instruct', | |
| 75 | 78 | optionsPrompt: DEFAULT_OPTIONS_PROMPT, |
| 76 | 79 | directionTemplate: DEFAULT_DIRECTION_TEMPLATE, |
| 77 | 80 | }; |
| @@ -83,6 +86,11 @@ let isRolling = false; | ||
| 83 | 86 | // Roll data waiting to be stamped onto the message the steered generation produces. |
| 84 | 87 | let pendingStamp = null; |
| 85 | 88 | |
| 89 | +// Whether the main generation being intercepted was started with its own custom prompt | |
| 90 | +// (e.g. a swipe/regenerate triggered with an additional instruction). Tracked via | |
| 91 | +// GENERATION_STARTED because the interceptor does not receive the generation params. | |
| 92 | +let mainGenHasCustomPrompt = false; | |
| 93 | + | |
| 86 | 94 | globalThis.dicerollGenerateInterceptor = (...args) => onGenerationIntercept(...args); |
| 87 | 95 | |
| 88 | 96 | function getSettings() { |
| @@ -97,6 +105,26 @@ function getSettings() { | ||
| 97 | 105 | return extension_settings[MODULE]; |
| 98 | 106 | } |
| 99 | 107 | |
| 108 | +/** | |
| 109 | + * True when another tool is already steering this generation with a manual instruction: | |
| 110 | + * either a script inject with a configured id (Guided Generations' guided swipe/response | |
| 111 | + * inject `id=instruct` before triggering the generation), or a custom prompt passed | |
| 112 | + * directly to the generation call. | |
| 113 | + * @param {object} s Settings | |
| 114 | + * @returns {boolean} | |
| 115 | + */ | |
| 116 | +function hasManualSteering(s) { | |
| 117 | + if (mainGenHasCustomPrompt) { | |
| 118 | + return true; | |
| 119 | + } | |
| 120 | + const injects = chat_metadata.script_injects ?? {}; | |
| 121 | + return String(s.skipInjectIds ?? '') | |
| 122 | + .split(',') | |
| 123 | + .map(id => id.trim()) | |
| 124 | + .filter(Boolean) | |
| 125 | + .some(id => String(injects[id]?.value ?? '').trim()); | |
| 126 | +} | |
| 127 | + | |
| 100 | 128 | function getRole(name) { |
| 101 | 129 | return name === 'user' ? extension_prompt_roles.USER : extension_prompt_roles.SYSTEM; |
| 102 | 130 | } |
| @@ -288,6 +316,13 @@ async function onGenerationIntercept(coreChat, _contextSize, _abort, type) { | ||
| 288 | 316 | return; |
| 289 | 317 | } |
| 290 | 318 | |
| 319 | + // A guided swipe/response (or any generation carrying its own instruction) takes precedence: | |
| 320 | + // no roll, no direction injection — the manual instruction alone steers this generation. | |
| 321 | + if (hasManualSteering(s)) { | |
| 322 | + console.debug('[Diceroll] Manual instruction detected, skipping the roll for this generation.'); | |
| 323 | + return; | |
| 324 | + } | |
| 325 | + | |
| 291 | 326 | // The roll belongs to the current user turn; swipes/regenerates of the same turn can reuse it. |
| 292 | 327 | const anchor = chat.findLastIndex(x => x.is_user); |
| 293 | 328 | const stored = chat_metadata[METADATA_KEY]; |
| @@ -413,6 +448,7 @@ function loadSettingsUi() { | ||
| 413 | 448 | $('#diceroll_max_options').val(s.maxOptions); |
| 414 | 449 | $('#diceroll_options_role').val(s.optionsRole); |
| 415 | 450 | $('#diceroll_direction_role').val(s.directionRole); |
| 451 | + $('#diceroll_skip_inject_ids').val(s.skipInjectIds); | |
| 416 | 452 | $('#diceroll_options_prompt').val(s.optionsPrompt); |
| 417 | 453 | $('#diceroll_direction_template').val(s.directionTemplate); |
| 418 | 454 | } |
| @@ -452,6 +488,10 @@ function setupListeners() { | ||
| 452 | 488 | getSettings().directionRole = String($(this).val()); |
| 453 | 489 | saveSettingsDebounced(); |
| 454 | 490 | }); |
| 491 | + $('#diceroll_skip_inject_ids').on('input', function () { | |
| 492 | + getSettings().skipInjectIds = String($(this).val()); | |
| 493 | + saveSettingsDebounced(); | |
| 494 | + }); | |
| 455 | 495 | $('#diceroll_options_prompt').on('input', function () { |
| 456 | 496 | getSettings().optionsPrompt = String($(this).val()); |
| 457 | 497 | saveSettingsDebounced(); |
| @@ -483,6 +523,14 @@ async function init() { | ||
| 483 | 523 | delete chat_metadata[METADATA_KEY]; |
| 484 | 524 | }); |
| 485 | 525 | |
| 526 | + // Runs before the interceptor within the same Generate() call, so the flag is always fresh. | |
| 527 | + // The nested option request (type 'quiet') must not overwrite the outer generation's flag. | |
| 528 | + eventSource.on(event_types.GENERATION_STARTED, (type, params, dryRun) => { | |
| 529 | + if (type !== 'quiet' && !dryRun) { | |
| 530 | + mainGenHasCustomPrompt = !!params?.quiet_prompt; | |
| 531 | + } | |
| 532 | + }); | |
| 533 | + | |
| 486 | 534 | eventSource.on(event_types.MESSAGE_RECEIVED, (chatId) => stampMessage(chatId)); |
| 487 | 535 | eventSource.on(event_types.CHARACTER_MESSAGE_RENDERED, (chatId) => { |
| 488 | 536 | stampMessage(chatId); |
| @@ -61,6 +61,10 @@ | ||
| 61 | 61 | </div> |
| 62 | 62 | </div> |
| 63 | 63 | |
| 64 | + <label for="diceroll_skip_inject_ids" data-i18n="ext_diceroll_skip_inject_ids">Skip when these /inject ids are active (comma-separated)</label> | |
| 65 | + <input id="diceroll_skip_inject_ids" class="text_pole" type="text" placeholder="instruct" /> | |
| 66 | + <small data-i18n="ext_diceroll_skip_inject_ids_help">When another tool steers the reply with its own instruction — e.g. Guided Generations' guided response/swipe use /inject id=instruct — Diceroll stays out of the way and neither rolls nor injects a direction for that generation. Generations triggered with a custom prompt are skipped as well.</small> | |
| 67 | + | |
| 64 | 68 | <label for="diceroll_options_prompt" data-i18n="ext_diceroll_options_prompt">Option-generation prompt (\{{min}}, \{{max}} available)</label> |
| 65 | 69 | <textarea id="diceroll_options_prompt" class="text_pole textarea_compact" rows="7"></textarea> |
| 66 | 70 | <div id="diceroll_options_prompt_restore" class="menu_button menu_button_icon"> |