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

0b0bd2732141a961c1740cc5e82b410aacb63244

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
3 files changed, +91 -18Showing whitespace changes
public/css/world-info.css+4 -0
@@ -274,3 +274,7 @@ select.keyselect+span.select2-container .select2-selection--multiple {
274274 align-self: center;
275275 width: 100%;
276276}
277+
278+.world_entry:has(input[name="delay_until_recursion"]:not(:checked)) .world_entry_form_control:has(input[name="delayUntilRecursionLevel"]) {
279+ display: none;
280+}
public/index.html+17 -10
@@ -5473,23 +5473,23 @@
54735473 <div class="world_entry_form_control flex1">
54745474 <small class="textAlignCenter" data-i18n="Case-Sensitive">Case-Sensitive</small>
54755475 <select name="caseSensitive" class="text_pole widthNatural margin0">
54765476 <option value="null" data-i18n="Use global setting">Use global setting</option>
54775477 <option value="true" data-i18n="Yes">Yes</option>
54785478 <option value="false" data-i18n="No">No</option>
54795479 </select>
54805480 </div>
54815481 <div class="world_entry_form_control flex1">
54825482 <small class="textAlignCenter" data-i18n="Match Whole Words">Match Whole Words</small>
54835483 <select name="matchWholeWords" class="text_pole widthNatural margin0">
54845484 <option value="null" data-i18n="Use global setting">Use global setting</option>
54855485 <option value="true" data-i18n="Yes">Yes</option>
54865486 <option value="false" data-i18n="No">No</option>
54875487 </select>
54885488 </div>
54895489 <div class="world_entry_form_control flex1">
54905490 <small class="textAlignCenter" data-i18n="Use Group Scoring">Use Group Scoring</small>
54915491 <select name="useGroupScoring" class="text_pole widthNatural margin0">
54925492 <option value="null" data-i18n="Use global setting">Use global setting</option>
54935493 <option value="true" data-i18n="Yes">Yes</option>
54945494 <option value="false" data-i18n="No">No</option>
54955495 </select>
@@ -5498,6 +5498,13 @@
54985498 <small class="textAlignCenter" data-i18n="Automation ID">Automation ID</small>
54995499 <input class="text_pole margin0" name="automationId" type="text" placeholder="( None )" data-i18n="[placeholder]( None )">
55005500 </div>
5501+ <div class="world_entry_form_control flex1" title="Defines delay levels for recursive scans.&#13;&#13;Initially, only the first level (smallest number) will match.&#13;Once no matches are found, the next level becomes eligible for matching.&#13;This repeats until all levels are checked.&#13;&#13;Tied to the &quot;Delay until recursion&quot; 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>
55015508 </div>
55025509 <div name="contentAndCharFilterBlock" class="world_entry_thin_controls flex2">
55035510 <div class="world_entry_form_control flex1">
@@ -5514,20 +5521,20 @@
55145521 <div>
55155522 <label class="checkbox flex-container alignitemscenter flexNoGap">
55165523 <input type="checkbox" name="exclude_recursion" />
55175524 <span data-i18n="ExcludeNon-recursable from(will recursionnot be activated by another)">
55185525 Non-recursable (this entry will not be activated by another)
55195526 </span>
55205527 </label>
55215528 <label class="checkbox flex-container alignitemscenter flexNoGap">
55225529 <input type="checkbox" name="prevent_recursion" />
55235530 <span data-i18n="Prevent further recursion (this entry will not activate others)">
55245531 Prevent further recursion (this entry will not activate others)
55255532 </span>
55265533 </label>
55275534 <label class="checkbox flex-container alignitemscenter flexNoGap">
55285535 <input type="checkbox" name="delay_until_recursion" />
55295536 <span data-i18n="Delay until recursion (this entry can only be activated on recursive checking)">
55305537 Delay until recursion (this entry can only be activated on recursive checking)
55315538 </span>
55325539 </label>
55335540 </div>
public/scripts/world-info.js+70 -8
@@ -217,8 +217,9 @@ class WorldInfoBuffer {
217217 depth = MAX_SCAN_DEPTH;
218218 }
219219
220220 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);
222223
223224 if (this.#injectBuffer.length > 0) {
224225 result += JOINER + this.#injectBuffer.join(JOINER);
@@ -2949,16 +2950,39 @@ async function getWorldEntry(name, data, entry) {
29492950 preventRecursionInput.prop('checked', entry.preventRecursion).trigger('input');
29502951
29512952 // delay until recursion
2953+ // delay until recursion level
29522954 const delayUntilRecursionInput = template.find('input[name="delay_until_recursion"]');
29532955 delayUntilRecursionInput.data('uid', entry.uid);
2956+ const delayUntilRecursionLevelInput = template.find('input[name="delayUntilRecursionLevel"]');
2957+ delayUntilRecursionLevelInput.data('uid', entry.uid);
29542958 delayUntilRecursionInput.on('input', async function () {
29552959 const uid = $(this).data('uid');
29562960 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+
29572967 data.entries[uid].delayUntilRecursion = value;
29582968 setWIOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion);
29592969 await saveWorldInfo(name, data);
29602970 });
29612971 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');
29622986
29632987 // duplicate button
29642988 const duplicateButton = template.find('.duplicate_entry_button');
@@ -3257,7 +3281,7 @@ export const newWorldInfoEntryDefinition = {
32573281 disable: { default: false, type: 'boolean' },
32583282 excludeRecursion: { default: false, type: 'boolean' },
32593283 preventRecursion: { default: false, type: 'boolean' },
32603284 delayUntilRecursion: { default: false0, type: 'booleannumber' },
32613285 probability: { default: 100, type: 'number' },
32623286 useProbability: { default: true, type: 'boolean' },
32633287 depth: { default: DEFAULT_DEPTH, type: 'number' },
@@ -3696,6 +3720,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
36963720 }
36973721 }
36983722
3723+ /** @type {scan_state} */
36993724 let scanState = scan_state.INITIAL;
37003725 let token_budget_overflowed = false;
37013726 let count = 0;
@@ -3720,6 +3745,17 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
37203745 return { worldInfoBefore: '', worldInfoAfter: '', WIDepthEntries: [], EMEntries: [], allActivatedEntries: new Set() };
37213746 }
37223747
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+
37233759 console.debug(`[WI] --- SEARCHING ENTRIES (on ${sortedEntries.length} entries) ---`);
37243760
37253761 while (scanState) {
@@ -3732,7 +3768,8 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
37323768 // Track how many times the loop has run. May be useful for debugging.
37333769 count++;
37343770
37353771 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));
37363773
37373774 // Until decided otherwise, we set the loop to stop scanning after this
37383775 let nextScanState = scan_state.NONE;
@@ -3811,6 +3848,11 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
38113848 continue;
38123849 }
38133850
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+
38143856 if (scanState === scan_state.RECURSION && world_info_recursive && entry.excludeRecursion && !isSticky) {
38153857 log('suppressed by exclude recursion');
38163858 continue;
@@ -3949,6 +3991,8 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
39493991 filterByInclusionGroups(newEntries, allActivatedEntries, buffer, scanState, timedEffects);
39503992
39513993 console.debug('[WI] --- PROBABILITY CHECKS ---');
3994+ !newEntries.length && console.debug('[WI] No probability checks to do');
3995+
39523996 for (const entry of newEntries) {
39533997 function verifyProbability() {
39543998 // If we don't need to roll, it's always true
@@ -3984,6 +4028,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
39844028 newContent += `${entry.content}\n`;
39854029
39864030 if ((textToScanTokens + (await getTokenCountAsync(newContent))) >= budget) {
4031+ console.debug('[WI] --- BUDGET OVERFLOW CHECK ---');
39874032 if (world_info_overflow_alert) {
39884033 console.warn(`[WI] budget of ${budget} reached, stopping after ${allActivatedEntries.size} entries`);
39894034 toastr.warning(`World info budget reached after ${allActivatedEntries.size} entries.`, 'World Info');
@@ -4001,23 +4046,31 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
40014046 const successfulNewEntries = newEntries.filter(x => !failedProbabilityChecks.has(x));
40024047 const successfulNewEntriesForRecursion = successfulNewEntries.filter(x => !x.preventRecursion);
40034048
4049+ console.debug(`[WI] --- LOOP #${count} RESULT ---`);
40044050 if (!newEntries.length) {
40054051 console.debug('[WI] No new entries activated, stopping.');
40064052 } else if (!successfulNewEntries.length) {
40074053 console.debug('[WI] Probability checks failed for all activated entries,. stoppingNo new entries activated.');
40084054 } else {
40094055 console.debug(`[WI] Successfully activated ${successfulNewEntries.length} new entries to prompt. ${allActivatedEntries.size} total entries activated.`, successfulNewEntries);
40104056 }
40114057
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+
40124063 // After processing and rolling entries is done, see if we should continue with normal recursion
40134064 if (world_info_recursive && !token_budget_overflowed && successfulNewEntriesForRecursion.length) {
40144065 nextScanState = scan_state.RECURSION;
4066+ logNextState('[WI] Found', successfulNewEntriesForRecursion.length, 'new entries for recursion');
40154067 }
40164068
40174069 // If we are inside min activations scan, and we have recursive buffer, we should do a recursive scan before increasing the buffer again
40184070 // There might be recurse-trigger-able entries that match the buffer, so we need to check that
40194071 if (world_info_recursive && !token_budget_overflowed && scanState === scan_state.MIN_ACTIVATIONS && buffer.hasRecurse()) {
40204072 nextScanState = scan_state.RECURSION;
4073+ logNextState('[WI] Min Activations run done, whill will always be followed by a recursive scan');
40214074 }
40224075
40234076 // 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) {
40314084 ) || (buffer.getDepth() > chat.length);
40324085
40334086 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`);
40354087 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`);
40364089 buffer.advanceScan();
40374090 } else {
40384091 console.debug(`[WI] Min activations not reached (${allActivatedEntries.size}/${world_info_min_activations}), but reached on of depth. Stopping`);
40394092 }
40404093 }
40414094
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+
40424102 // Final check if we should really continue scan, and extend the current WI recurse buffer
40434103 scanState = nextScanState;
40444104 if (scanState) {
@@ -4046,6 +4106,8 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
40464106 .map(x => x.content).join('\n');
40474107 buffer.addRecurse(text);
40484108 allActivatedText = (text + '\n' + allActivatedText);
4109+ } else {
4110+ logNextState('[WI] Scan done. No new entries to prompt. Stopping.');
40494111 }
40504112 }
40514113