Merge pull request #2857 from SillyTavern/wi-delay-recursion-levels WI "Delay until recursion" levels to delay delayed entries until other delayed entries are fully matched
Signed| @@ -274,3 +274,7 @@ select.keyselect+span.select2-container .select2-selection--multiple { | ||
| 274 | 274 | align-self: center; |
| 275 | 275 | width: 100%; |
| 276 | 276 | } |
| 277 | + | |
| 278 | +.world_entry:has(input[name="delay_until_recursion"]:not(:checked)) .world_entry_form_control:has(input[name="delayUntilRecursionLevel"]) { | |
| 279 | + display: none; | |
| 280 | +} | |
| @@ -5473,23 +5473,23 @@ | ||
| 5473 | 5473 | <div class="world_entry_form_control flex1"> |
| 5474 | 5474 | <small class="textAlignCenter" data-i18n="Case-Sensitive">Case-Sensitive</small> |
| 5475 | 5475 | <select name="caseSensitive" class="text_pole widthNatural margin0"> |
| 5476 | 5476 | <option value="null" data-i18n="Use global setting">Use global setting</option> |
| 5477 | 5477 | <option value="true" data-i18n="Yes">Yes</option> |
| 5478 | 5478 | <option value="false" data-i18n="No">No</option> |
| 5479 | 5479 | </select> |
| 5480 | 5480 | </div> |
| 5481 | 5481 | <div class="world_entry_form_control flex1"> |
| 5482 | 5482 | <small class="textAlignCenter" data-i18n="Match Whole Words">Match Whole Words</small> |
| 5483 | 5483 | <select name="matchWholeWords" class="text_pole widthNatural margin0"> |
| 5484 | 5484 | <option value="null" data-i18n="Use global setting">Use global setting</option> |
| 5485 | 5485 | <option value="true" data-i18n="Yes">Yes</option> |
| 5486 | 5486 | <option value="false" data-i18n="No">No</option> |
| 5487 | 5487 | </select> |
| 5488 | 5488 | </div> |
| 5489 | 5489 | <div class="world_entry_form_control flex1"> |
| 5490 | 5490 | <small class="textAlignCenter" data-i18n="Use Group Scoring">Use Group Scoring</small> |
| 5491 | 5491 | <select name="useGroupScoring" class="text_pole widthNatural margin0"> |
| 5492 | 5492 | <option value="null" data-i18n="Use global setting">Use global setting</option> |
| 5493 | 5493 | <option value="true" data-i18n="Yes">Yes</option> |
| 5494 | 5494 | <option value="false" data-i18n="No">No</option> |
| 5495 | 5495 | </select> |
| @@ -5498,6 +5498,13 @@ | ||
| 5498 | 5498 | <small class="textAlignCenter" data-i18n="Automation ID">Automation ID</small> |
| 5499 | 5499 | <input class="text_pole margin0" name="automationId" type="text" placeholder="( None )" data-i18n="[placeholder]( None )"> |
| 5500 | 5500 | </div> |
| 5501 | + <div class="world_entry_form_control flex1" title="Defines delay levels for recursive scans. Initially, only the first level (smallest number) will match. Once no matches are found, the next level becomes eligible for matching. This repeats until all levels are checked. Tied to the "Delay until recursion" setting." data-i18n="[title]delay_until_recursion_level"> | |
| 5502 | + <small class="textAlignCenter"> | |
| 5503 | + <span data-i18n="Recursion Level">Recursion Level</span> | |
| 5504 | + <div class="fa-solid fa-circle-info opacity50p"></div> | |
| 5505 | + </small> | |
| 5506 | + <input class="text_pole margin0" name="delayUntilRecursionLevel" type="text" placeholder="1"> | |
| 5507 | + </div> | |
| 5501 | 5508 | </div> |
| 5502 | 5509 | <div name="contentAndCharFilterBlock" class="world_entry_thin_controls flex2"> |
| 5503 | 5510 | <div class="world_entry_form_control flex1"> |
| @@ -5514,20 +5521,20 @@ | ||
| 5514 | 5521 | <div> |
| 5515 | 5522 | <label class="checkbox flex-container alignitemscenter flexNoGap"> |
| 5516 | 5523 | <input type="checkbox" name="exclude_recursion" /> |
| 5517 | 5524 | <span data-i18n="ExcludeNon-recursable from(will recursionnot be activated by another)"> |
| 5518 | 5525 | Non-recursable (this entry will not be activated by another) |
| 5519 | 5526 | </span> |
| 5520 | 5527 | </label> |
| 5521 | 5528 | <label class="checkbox flex-container alignitemscenter flexNoGap"> |
| 5522 | 5529 | <input type="checkbox" name="prevent_recursion" /> |
| 5523 | 5530 | <span data-i18n="Prevent further recursion (this entry will not activate others)"> |
| 5524 | 5531 | Prevent further recursion (this entry will not activate others) |
| 5525 | 5532 | </span> |
| 5526 | 5533 | </label> |
| 5527 | 5534 | <label class="checkbox flex-container alignitemscenter flexNoGap"> |
| 5528 | 5535 | <input type="checkbox" name="delay_until_recursion" /> |
| 5529 | 5536 | <span data-i18n="Delay until recursion (this entry can only be activated on recursive checking)"> |
| 5530 | 5537 | Delay until recursion (this entry can only be activated on recursive checking) |
| 5531 | 5538 | </span> |
| 5532 | 5539 | </label> |
| 5533 | 5540 | </div> |
| @@ -217,8 +217,9 @@ class WorldInfoBuffer { | ||
| 217 | 217 | depth = MAX_SCAN_DEPTH; |
| 218 | 218 | } |
| 219 | 219 | |
| 220 | 220 | const JOINERMATCHER = '\n\x01'; |
| 221 | - let result = this.#depthBuffer.slice(this.#startDepth, depth).join(JOINER); | |
| 221 | + const JOINER = '\n' + MATCHER; | |
| 222 | + let result = MATCHER + this.#depthBuffer.slice(this.#startDepth, depth).join(JOINER); | |
| 222 | 223 | |
| 223 | 224 | if (this.#injectBuffer.length > 0) { |
| 224 | 225 | result += JOINER + this.#injectBuffer.join(JOINER); |
| @@ -2949,16 +2950,39 @@ async function getWorldEntry(name, data, entry) { | ||
| 2949 | 2950 | preventRecursionInput.prop('checked', entry.preventRecursion).trigger('input'); |
| 2950 | 2951 | |
| 2951 | 2952 | // delay until recursion |
| 2953 | + // delay until recursion level | |
| 2952 | 2954 | const delayUntilRecursionInput = template.find('input[name="delay_until_recursion"]'); |
| 2953 | 2955 | delayUntilRecursionInput.data('uid', entry.uid); |
| 2956 | + const delayUntilRecursionLevelInput = template.find('input[name="delayUntilRecursionLevel"]'); | |
| 2957 | + delayUntilRecursionLevelInput.data('uid', entry.uid); | |
| 2954 | 2958 | delayUntilRecursionInput.on('input', async function () { |
| 2955 | 2959 | const uid = $(this).data('uid'); |
| 2956 | 2960 | const valuetoggled = $(this).prop('checked'); |
| 2961 | + | |
| 2962 | + // If the value contains a number, we'll take that one (set by the level input), otherwise we can use true/false switch | |
| 2963 | + const value = toggled ? data.entries[uid].delayUntilRecursion || true : false; | |
| 2964 | + | |
| 2965 | + if (!toggled) delayUntilRecursionLevelInput.val(''); | |
| 2966 | + | |
| 2957 | 2967 | data.entries[uid].delayUntilRecursion = value; |
| 2958 | 2968 | setWIOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion); |
| 2959 | 2969 | await saveWorldInfo(name, data); |
| 2960 | 2970 | }); |
| 2961 | 2971 | delayUntilRecursionInput.prop('checked', entry.delayUntilRecursion).trigger('input'); |
| 2972 | + delayUntilRecursionLevelInput.on('input', async function () { | |
| 2973 | + const uid = $(this).data('uid'); | |
| 2974 | + const content = $(this).val(); | |
| 2975 | + const value = content === '' ? (typeof data.entries[uid].delayUntilRecursion === 'boolean' ? data.entries[uid].delayUntilRecursion : true) | |
| 2976 | + : content === 1 ? true | |
| 2977 | + : !isNaN(Number(content)) ? Number(content) | |
| 2978 | + : false; | |
| 2979 | + | |
| 2980 | + data.entries[uid].delayUntilRecursion = value; | |
| 2981 | + setWIOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion); | |
| 2982 | + await saveWorldInfo(name, data); | |
| 2983 | + }); | |
| 2984 | + // No need to retrigger inpout event, we'll just set the curret current value. It was edited/saved above already | |
| 2985 | + delayUntilRecursionLevelInput.val(['number', 'string'].includes(typeof entry.delayUntilRecursion) ? entry.delayUntilRecursion : '').trigger('input'); | |
| 2962 | 2986 | |
| 2963 | 2987 | // duplicate button |
| 2964 | 2988 | const duplicateButton = template.find('.duplicate_entry_button'); |
| @@ -3257,7 +3281,7 @@ export const newWorldInfoEntryDefinition = { | ||
| 3257 | 3281 | disable: { default: false, type: 'boolean' }, |
| 3258 | 3282 | excludeRecursion: { default: false, type: 'boolean' }, |
| 3259 | 3283 | preventRecursion: { default: false, type: 'boolean' }, |
| 3260 | 3284 | delayUntilRecursion: { default: false0, type: 'booleannumber' }, |
| 3261 | 3285 | probability: { default: 100, type: 'number' }, |
| 3262 | 3286 | useProbability: { default: true, type: 'boolean' }, |
| 3263 | 3287 | depth: { default: DEFAULT_DEPTH, type: 'number' }, |
| @@ -3696,6 +3720,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) { | ||
| 3696 | 3720 | } |
| 3697 | 3721 | } |
| 3698 | 3722 | |
| 3723 | + /** @type {scan_state} */ | |
| 3699 | 3724 | let scanState = scan_state.INITIAL; |
| 3700 | 3725 | let token_budget_overflowed = false; |
| 3701 | 3726 | let count = 0; |
| @@ -3720,6 +3745,17 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) { | ||
| 3720 | 3745 | return { worldInfoBefore: '', worldInfoAfter: '', WIDepthEntries: [], EMEntries: [], allActivatedEntries: new Set() }; |
| 3721 | 3746 | } |
| 3722 | 3747 | |
| 3748 | + /** @type {number[]} Represents the delay levels for entries that are delayed until recursion */ | |
| 3749 | + const availableRecursionDelayLevels = [...new Set(sortedEntries | |
| 3750 | + .filter(entry => entry.delayUntilRecursion) | |
| 3751 | + .map(entry => entry.delayUntilRecursion === true ? 1 : entry.delayUntilRecursion), | |
| 3752 | + )].sort((a, b) => a - b); | |
| 3753 | + // Already preset with the first level | |
| 3754 | + let currentRecursionDelayLevel = availableRecursionDelayLevels.shift() ?? 0; | |
| 3755 | + if (currentRecursionDelayLevel > 0 && availableRecursionDelayLevels.length) { | |
| 3756 | + console.debug('[WI] Preparing first delayed recursion level', currentRecursionDelayLevel, '. Still delayed:', availableRecursionDelayLevels); | |
| 3757 | + } | |
| 3758 | + | |
| 3723 | 3759 | console.debug(`[WI] --- SEARCHING ENTRIES (on ${sortedEntries.length} entries) ---`); |
| 3724 | 3760 | |
| 3725 | 3761 | while (scanState) { |
| @@ -3732,7 +3768,8 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) { | ||
| 3732 | 3768 | // Track how many times the loop has run. May be useful for debugging. |
| 3733 | 3769 | count++; |
| 3734 | 3770 | |
| 3735 | 3771 | console.debug(`[WI] Loop--- LOOP #${count}. SearchSTART state---`, Object.entries(scan_state).find(x => x[1] === scanState)); |
| 3772 | + console.debug('[WI] Scan state', Object.entries(scan_state).find(x => x[1] === scanState)); | |
| 3736 | 3773 | |
| 3737 | 3774 | // Until decided otherwise, we set the loop to stop scanning after this |
| 3738 | 3775 | let nextScanState = scan_state.NONE; |
| @@ -3811,6 +3848,11 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) { | ||
| 3811 | 3848 | continue; |
| 3812 | 3849 | } |
| 3813 | 3850 | |
| 3851 | + if (scanState === scan_state.RECURSION && entry.delayUntilRecursion && entry.delayUntilRecursion > currentRecursionDelayLevel && !isSticky) { | |
| 3852 | + log('suppressed by delay until recursion level', entry.delayUntilRecursion, '. Currently', currentRecursionDelayLevel); | |
| 3853 | + continue; | |
| 3854 | + } | |
| 3855 | + | |
| 3814 | 3856 | if (scanState === scan_state.RECURSION && world_info_recursive && entry.excludeRecursion && !isSticky) { |
| 3815 | 3857 | log('suppressed by exclude recursion'); |
| 3816 | 3858 | continue; |
| @@ -3949,6 +3991,8 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) { | ||
| 3949 | 3991 | filterByInclusionGroups(newEntries, allActivatedEntries, buffer, scanState, timedEffects); |
| 3950 | 3992 | |
| 3951 | 3993 | console.debug('[WI] --- PROBABILITY CHECKS ---'); |
| 3994 | + !newEntries.length && console.debug('[WI] No probability checks to do'); | |
| 3995 | + | |
| 3952 | 3996 | for (const entry of newEntries) { |
| 3953 | 3997 | function verifyProbability() { |
| 3954 | 3998 | // If we don't need to roll, it's always true |
| @@ -3984,6 +4028,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) { | ||
| 3984 | 4028 | newContent += `${entry.content}\n`; |
| 3985 | 4029 | |
| 3986 | 4030 | if ((textToScanTokens + (await getTokenCountAsync(newContent))) >= budget) { |
| 4031 | + console.debug('[WI] --- BUDGET OVERFLOW CHECK ---'); | |
| 3987 | 4032 | if (world_info_overflow_alert) { |
| 3988 | 4033 | console.warn(`[WI] budget of ${budget} reached, stopping after ${allActivatedEntries.size} entries`); |
| 3989 | 4034 | toastr.warning(`World info budget reached after ${allActivatedEntries.size} entries.`, 'World Info'); |
| @@ -4001,23 +4046,31 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) { | ||
| 4001 | 4046 | const successfulNewEntries = newEntries.filter(x => !failedProbabilityChecks.has(x)); |
| 4002 | 4047 | const successfulNewEntriesForRecursion = successfulNewEntries.filter(x => !x.preventRecursion); |
| 4003 | 4048 | |
| 4049 | + console.debug(`[WI] --- LOOP #${count} RESULT ---`); | |
| 4004 | 4050 | if (!newEntries.length) { |
| 4005 | 4051 | console.debug('[WI] No new entries activated, stopping.'); |
| 4006 | 4052 | } else if (!successfulNewEntries.length) { |
| 4007 | 4053 | console.debug('[WI] Probability checks failed for all activated entries,. stoppingNo new entries activated.'); |
| 4008 | 4054 | } else { |
| 4009 | 4055 | console.debug(`[WI] Successfully activated ${successfulNewEntries.length} new entries to prompt. ${allActivatedEntries.size} total entries activated.`, successfulNewEntries); |
| 4010 | 4056 | } |
| 4011 | 4057 | |
| 4058 | + function logNextState(...args) { | |
| 4059 | + args.length && console.debug(args.shift(), ...args); | |
| 4060 | + console.debug('[WI] Setting scan state', Object.entries(scan_state).find(x => x[1] === scanState)); | |
| 4061 | + } | |
| 4062 | + | |
| 4012 | 4063 | // After processing and rolling entries is done, see if we should continue with normal recursion |
| 4013 | 4064 | if (world_info_recursive && !token_budget_overflowed && successfulNewEntriesForRecursion.length) { |
| 4014 | 4065 | nextScanState = scan_state.RECURSION; |
| 4066 | + logNextState('[WI] Found', successfulNewEntriesForRecursion.length, 'new entries for recursion'); | |
| 4015 | 4067 | } |
| 4016 | 4068 | |
| 4017 | 4069 | // If we are inside min activations scan, and we have recursive buffer, we should do a recursive scan before increasing the buffer again |
| 4018 | 4070 | // There might be recurse-trigger-able entries that match the buffer, so we need to check that |
| 4019 | 4071 | if (world_info_recursive && !token_budget_overflowed && scanState === scan_state.MIN_ACTIVATIONS && buffer.hasRecurse()) { |
| 4020 | 4072 | nextScanState = scan_state.RECURSION; |
| 4073 | + logNextState('[WI] Min Activations run done, whill will always be followed by a recursive scan'); | |
| 4021 | 4074 | } |
| 4022 | 4075 | |
| 4023 | 4076 | // If scanning is planned to stop, but min activations is set and not satisfied, check if we should continue |
| @@ -4031,14 +4084,21 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) { | ||
| 4031 | 4084 | ) || (buffer.getDepth() > chat.length); |
| 4032 | 4085 | |
| 4033 | 4086 | if (!over_max) { |
| 4034 | - console.debug(`[WI] Min activations not reached (${allActivatedEntries.size}/${world_info_min_activations}), advancing depth to ${buffer.getDepth() + 1} and checking again`); | |
| 4035 | 4087 | nextScanState = scan_state.MIN_ACTIVATIONS; // loop |
| 4088 | + logNextState(`[WI] Min activations not reached (${allActivatedEntries.size}/${world_info_min_activations}), advancing depth to ${buffer.getDepth() + 1}, starting another scan`); | |
| 4036 | 4089 | buffer.advanceScan(); |
| 4037 | 4090 | } else { |
| 4038 | 4091 | console.debug(`[WI] Min activations not reached (${allActivatedEntries.size}/${world_info_min_activations}), but reached on of depth. Stopping`); |
| 4039 | 4092 | } |
| 4040 | 4093 | } |
| 4041 | 4094 | |
| 4095 | + // If the scan is done, but we still have open "delay until recursion" levels, we should continue with the next one | |
| 4096 | + if (nextScanState === scan_state.NONE && availableRecursionDelayLevels.length) { | |
| 4097 | + nextScanState = scan_state.RECURSION; | |
| 4098 | + currentRecursionDelayLevel = availableRecursionDelayLevels.shift(); | |
| 4099 | + logNextState('[WI] Open delayed recursion levels left. Preparing next delayed recursion level', currentRecursionDelayLevel, '. Still delayed:', availableRecursionDelayLevels); | |
| 4100 | + } | |
| 4101 | + | |
| 4042 | 4102 | // Final check if we should really continue scan, and extend the current WI recurse buffer |
| 4043 | 4103 | scanState = nextScanState; |
| 4044 | 4104 | if (scanState) { |
| @@ -4046,6 +4106,8 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) { | ||
| 4046 | 4106 | .map(x => x.content).join('\n'); |
| 4047 | 4107 | buffer.addRecurse(text); |
| 4048 | 4108 | allActivatedText = (text + '\n' + allActivatedText); |
| 4109 | + } else { | |
| 4110 | + logNextState('[WI] Scan done. No new entries to prompt. Stopping.'); | |
| 4049 | 4111 | } |
| 4050 | 4112 | } |
| 4051 | 4113 | |