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 | useStructuredOutput: true, | 72 | useStructuredOutput: true, |
| 73 | notify: true, | 73 | notify: true, |
| 74 | debugDisplay: false, | 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 | optionsPrompt: DEFAULT_OPTIONS_PROMPT, | 78 | optionsPrompt: DEFAULT_OPTIONS_PROMPT, |
| 76 | directionTemplate: DEFAULT_DIRECTION_TEMPLATE, | 79 | directionTemplate: DEFAULT_DIRECTION_TEMPLATE, |
| 77 | }; | 80 | }; |
| @@ -83,6 +86,11 @@ let isRolling = false; | |||
| 83 | // Roll data waiting to be stamped onto the message the steered generation produces. | 86 | // Roll data waiting to be stamped onto the message the steered generation produces. |
| 84 | let pendingStamp = null; | 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 | globalThis.dicerollGenerateInterceptor = (...args) => onGenerationIntercept(...args); | 94 | globalThis.dicerollGenerateInterceptor = (...args) => onGenerationIntercept(...args); |
| 87 | 95 | ||
| 88 | function getSettings() { | 96 | function getSettings() { |
| @@ -97,6 +105,26 @@ function getSettings() { | |||
| 97 | return extension_settings[MODULE]; | 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 | function getRole(name) { | 128 | function getRole(name) { |
| 101 | return name === 'user' ? extension_prompt_roles.USER : extension_prompt_roles.SYSTEM; | 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 | return; | 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 | // The roll belongs to the current user turn; swipes/regenerates of the same turn can reuse it. | 326 | // The roll belongs to the current user turn; swipes/regenerates of the same turn can reuse it. |
| 292 | const anchor = chat.findLastIndex(x => x.is_user); | 327 | const anchor = chat.findLastIndex(x => x.is_user); |
| 293 | const stored = chat_metadata[METADATA_KEY]; | 328 | const stored = chat_metadata[METADATA_KEY]; |
| @@ -413,6 +448,7 @@ function loadSettingsUi() { | |||
| 413 | $('#diceroll_max_options').val(s.maxOptions); | 448 | $('#diceroll_max_options').val(s.maxOptions); |
| 414 | $('#diceroll_options_role').val(s.optionsRole); | 449 | $('#diceroll_options_role').val(s.optionsRole); |
| 415 | $('#diceroll_direction_role').val(s.directionRole); | 450 | $('#diceroll_direction_role').val(s.directionRole); |
| 451 | $('#diceroll_skip_inject_ids').val(s.skipInjectIds); | ||
| 416 | $('#diceroll_options_prompt').val(s.optionsPrompt); | 452 | $('#diceroll_options_prompt').val(s.optionsPrompt); |
| 417 | $('#diceroll_direction_template').val(s.directionTemplate); | 453 | $('#diceroll_direction_template').val(s.directionTemplate); |
| 418 | } | 454 | } |
| @@ -452,6 +488,10 @@ function setupListeners() { | |||
| 452 | getSettings().directionRole = String($(this).val()); | 488 | getSettings().directionRole = String($(this).val()); |
| 453 | saveSettingsDebounced(); | 489 | saveSettingsDebounced(); |
| 454 | }); | 490 | }); |
| 491 | $('#diceroll_skip_inject_ids').on('input', function () { | ||
| 492 | getSettings().skipInjectIds = String($(this).val()); | ||
| 493 | saveSettingsDebounced(); | ||
| 494 | }); | ||
| 455 | $('#diceroll_options_prompt').on('input', function () { | 495 | $('#diceroll_options_prompt').on('input', function () { |
| 456 | getSettings().optionsPrompt = String($(this).val()); | 496 | getSettings().optionsPrompt = String($(this).val()); |
| 457 | saveSettingsDebounced(); | 497 | saveSettingsDebounced(); |
| @@ -483,6 +523,14 @@ async function init() { | |||
| 483 | delete chat_metadata[METADATA_KEY]; | 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 | eventSource.on(event_types.MESSAGE_RECEIVED, (chatId) => stampMessage(chatId)); | 534 | eventSource.on(event_types.MESSAGE_RECEIVED, (chatId) => stampMessage(chatId)); |
| 487 | eventSource.on(event_types.CHARACTER_MESSAGE_RENDERED, (chatId) => { | 535 | eventSource.on(event_types.CHARACTER_MESSAGE_RENDERED, (chatId) => { |
| 488 | stampMessage(chatId); | 536 | stampMessage(chatId); |
| @@ -61,6 +61,10 @@ | |||
| 61 | </div> | 61 | </div> |
| 62 | </div> | 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 | <label for="diceroll_options_prompt" data-i18n="ext_diceroll_options_prompt">Option-generation prompt (\{{min}}, \{{max}} available)</label> | 68 | <label for="diceroll_options_prompt" data-i18n="ext_diceroll_options_prompt">Option-generation prompt (\{{min}}, \{{max}} available)</label> |
| 65 | <textarea id="diceroll_options_prompt" class="text_pole textarea_compact" rows="7"></textarea> | 69 | <textarea id="diceroll_options_prompt" class="text_pole textarea_compact" rows="7"></textarea> |
| 66 | <div id="diceroll_options_prompt_restore" class="menu_button menu_button_icon"> | 70 | <div id="diceroll_options_prompt_restore" class="menu_button menu_button_icon"> |