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 -18Ignore whitespace
public/css/world-info.css+4 -0
@@ -274,3 +274,7 @@ select.keyselect+span.select2-container .select2-selection--multiple {
274 align-self: center;274 align-self: center;
275 width: 100%;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}
public/index.html+17 -10
@@ -5473,23 +5473,23 @@
5473 <div class="world_entry_form_control flex1">5473 <div class="world_entry_form_control flex1">
5474 <small class="textAlignCenter" data-i18n="Case-Sensitive">Case-Sensitive</small>5474 <small class="textAlignCenter" data-i18n="Case-Sensitive">Case-Sensitive</small>
5475 <select name="caseSensitive" class="text_pole widthNatural margin0">5475 <select name="caseSensitive" class="text_pole widthNatural margin0">
5476 <option value="null" data-i18n="Use global setting">Use global setting</option>5476 <option value="null" data-i18n="Use global">Use global</option>
5477 <option value="true" data-i18n="Yes">Yes</option>5477 <option value="true" data-i18n="Yes">Yes</option>
5478 <option value="false" data-i18n="No">No</option>5478 <option value="false" data-i18n="No">No</option>
5479 </select>5479 </select>
5480 </div>5480 </div>
5481 <div class="world_entry_form_control flex1">5481 <div class="world_entry_form_control flex1">
5482 <small class="textAlignCenter" data-i18n="Match Whole Words">Match Whole Words</small>5482 <small class="textAlignCenter" data-i18n="Whole Words">Whole Words</small>
5483 <select name="matchWholeWords" class="text_pole widthNatural margin0">5483 <select name="matchWholeWords" class="text_pole widthNatural margin0">
5484 <option value="null" data-i18n="Use global setting">Use global setting</option>5484 <option value="null" data-i18n="Use global">Use global</option>
5485 <option value="true" data-i18n="Yes">Yes</option>5485 <option value="true" data-i18n="Yes">Yes</option>
5486 <option value="false" data-i18n="No">No</option>5486 <option value="false" data-i18n="No">No</option>
5487 </select>5487 </select>
5488 </div>5488 </div>
5489 <div class="world_entry_form_control flex1">5489 <div class="world_entry_form_control flex1">
5490 <small class="textAlignCenter" data-i18n="Use Group Scoring">Use Group Scoring</small>5490 <small class="textAlignCenter" data-i18n="Group Scoring">Group Scoring</small>
5491 <select name="useGroupScoring" class="text_pole widthNatural margin0">5491 <select name="useGroupScoring" class="text_pole widthNatural margin0">
5492 <option value="null" data-i18n="Use global setting">Use global setting</option>5492 <option value="null" data-i18n="Use global">Use global</option>
5493 <option value="true" data-i18n="Yes">Yes</option>5493 <option value="true" data-i18n="Yes">Yes</option>
5494 <option value="false" data-i18n="No">No</option>5494 <option value="false" data-i18n="No">No</option>
5495 </select>5495 </select>
@@ -5498,6 +5498,13 @@
5498 <small class="textAlignCenter" data-i18n="Automation ID">Automation ID</small>5498 <small class="textAlignCenter" data-i18n="Automation ID">Automation ID</small>
5499 <input class="text_pole margin0" name="automationId" type="text" placeholder="( None )" data-i18n="[placeholder]( None )">5499 <input class="text_pole margin0" name="automationId" type="text" placeholder="( None )" data-i18n="[placeholder]( None )">
5500 </div>5500 </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>
5501 </div>5508 </div>
5502 <div name="contentAndCharFilterBlock" class="world_entry_thin_controls flex2">5509 <div name="contentAndCharFilterBlock" class="world_entry_thin_controls flex2">
5503 <div class="world_entry_form_control flex1">5510 <div class="world_entry_form_control flex1">
@@ -5514,20 +5521,20 @@
5514 <div>5521 <div>
5515 <label class="checkbox flex-container alignitemscenter flexNoGap">5522 <label class="checkbox flex-container alignitemscenter flexNoGap">
5516 <input type="checkbox" name="exclude_recursion" />5523 <input type="checkbox" name="exclude_recursion" />
5517 <span data-i18n="Exclude from recursion">5524 <span data-i18n="Non-recursable (will not be activated by another)">
5518 Non-recursable (this entry will not be activated by another)5525 Non-recursable (will not be activated by another)
5519 </span>5526 </span>
5520 </label>5527 </label>
5521 <label class="checkbox flex-container alignitemscenter flexNoGap">5528 <label class="checkbox flex-container alignitemscenter flexNoGap">
5522 <input type="checkbox" name="prevent_recursion" />5529 <input type="checkbox" name="prevent_recursion" />
5523 <span data-i18n="Prevent further recursion (this entry will not activate others)">5530 <span data-i18n="Prevent further recursion (this entry will not activate others)">
5524 Prevent further recursion (this entry will not activate others)5531 Prevent further recursion (will not activate others)
5525 </span>5532 </span>
5526 </label>5533 </label>
5527 <label class="checkbox flex-container alignitemscenter flexNoGap">5534 <label class="checkbox flex-container alignitemscenter flexNoGap">
5528 <input type="checkbox" name="delay_until_recursion" />5535 <input type="checkbox" name="delay_until_recursion" />
5529 <span data-i18n="Delay until recursion (this entry can only be activated on recursive checking)">5536 <span data-i18n="Delay until recursion (can only be activated on recursive checking)">
5530 Delay until recursion (this entry can only be activated on recursive checking)5537 Delay until recursion (can only be activated on recursive checking)
5531 </span>5538 </span>
5532 </label>5539 </label>
5533 </div>5540 </div>
public/scripts/world-info.js+70 -8
@@ -217,8 +217,9 @@ class WorldInfoBuffer {
217 depth = MAX_SCAN_DEPTH;217 depth = MAX_SCAN_DEPTH;
218 }218 }
219219
220 const JOINER = '\n\x01';220 const MATCHER = '\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
223 if (this.#injectBuffer.length > 0) {224 if (this.#injectBuffer.length > 0) {
224 result += JOINER + this.#injectBuffer.join(JOINER);225 result += JOINER + this.#injectBuffer.join(JOINER);
@@ -2949,16 +2950,39 @@ async function getWorldEntry(name, data, entry) {
2949 preventRecursionInput.prop('checked', entry.preventRecursion).trigger('input');2950 preventRecursionInput.prop('checked', entry.preventRecursion).trigger('input');
29502951
2951 // delay until recursion2952 // delay until recursion
2953 // delay until recursion level
2952 const delayUntilRecursionInput = template.find('input[name="delay_until_recursion"]');2954 const delayUntilRecursionInput = template.find('input[name="delay_until_recursion"]');
2953 delayUntilRecursionInput.data('uid', entry.uid);2955 delayUntilRecursionInput.data('uid', entry.uid);
2956 const delayUntilRecursionLevelInput = template.find('input[name="delayUntilRecursionLevel"]');
2957 delayUntilRecursionLevelInput.data('uid', entry.uid);
2954 delayUntilRecursionInput.on('input', async function () {2958 delayUntilRecursionInput.on('input', async function () {
2955 const uid = $(this).data('uid');2959 const uid = $(this).data('uid');
2956 const value = $(this).prop('checked');2960 const toggled = $(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 data.entries[uid].delayUntilRecursion = value;2967 data.entries[uid].delayUntilRecursion = value;
2958 setWIOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion);2968 setWIOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion);
2959 await saveWorldInfo(name, data);2969 await saveWorldInfo(name, data);
2960 });2970 });
2961 delayUntilRecursionInput.prop('checked', entry.delayUntilRecursion).trigger('input');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');
29622986
2963 // duplicate button2987 // duplicate button
2964 const duplicateButton = template.find('.duplicate_entry_button');2988 const duplicateButton = template.find('.duplicate_entry_button');
@@ -3257,7 +3281,7 @@ export const newWorldInfoEntryDefinition = {
3257 disable: { default: false, type: 'boolean' },3281 disable: { default: false, type: 'boolean' },
3258 excludeRecursion: { default: false, type: 'boolean' },3282 excludeRecursion: { default: false, type: 'boolean' },
3259 preventRecursion: { default: false, type: 'boolean' },3283 preventRecursion: { default: false, type: 'boolean' },
3260 delayUntilRecursion: { default: false, type: 'boolean' },3284 delayUntilRecursion: { default: 0, type: 'number' },
3261 probability: { default: 100, type: 'number' },3285 probability: { default: 100, type: 'number' },
3262 useProbability: { default: true, type: 'boolean' },3286 useProbability: { default: true, type: 'boolean' },
3263 depth: { default: DEFAULT_DEPTH, type: 'number' },3287 depth: { default: DEFAULT_DEPTH, type: 'number' },
@@ -3696,6 +3720,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
3696 }3720 }
3697 }3721 }
36983722
3723 /** @type {scan_state} */
3699 let scanState = scan_state.INITIAL;3724 let scanState = scan_state.INITIAL;
3700 let token_budget_overflowed = false;3725 let token_budget_overflowed = false;
3701 let count = 0;3726 let count = 0;
@@ -3720,6 +3745,17 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
3720 return { worldInfoBefore: '', worldInfoAfter: '', WIDepthEntries: [], EMEntries: [], allActivatedEntries: new Set() };3745 return { worldInfoBefore: '', worldInfoAfter: '', WIDepthEntries: [], EMEntries: [], allActivatedEntries: new Set() };
3721 }3746 }
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
3723 console.debug(`[WI] --- SEARCHING ENTRIES (on ${sortedEntries.length} entries) ---`);3759 console.debug(`[WI] --- SEARCHING ENTRIES (on ${sortedEntries.length} entries) ---`);
37243760
3725 while (scanState) {3761 while (scanState) {
@@ -3732,7 +3768,8 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
3732 // Track how many times the loop has run. May be useful for debugging.3768 // Track how many times the loop has run. May be useful for debugging.
3733 count++;3769 count++;
37343770
3735 console.debug(`[WI] Loop #${count}. Search state`, Object.entries(scan_state).find(x => x[1] === scanState));3771 console.debug(`[WI] --- LOOP #${count} START ---`);
3772 console.debug('[WI] Scan state', Object.entries(scan_state).find(x => x[1] === scanState));
37363773
3737 // Until decided otherwise, we set the loop to stop scanning after this3774 // Until decided otherwise, we set the loop to stop scanning after this
3738 let nextScanState = scan_state.NONE;3775 let nextScanState = scan_state.NONE;
@@ -3811,6 +3848,11 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
3811 continue;3848 continue;
3812 }3849 }
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
3814 if (scanState === scan_state.RECURSION && world_info_recursive && entry.excludeRecursion && !isSticky) {3856 if (scanState === scan_state.RECURSION && world_info_recursive && entry.excludeRecursion && !isSticky) {
3815 log('suppressed by exclude recursion');3857 log('suppressed by exclude recursion');
3816 continue;3858 continue;
@@ -3949,6 +3991,8 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
3949 filterByInclusionGroups(newEntries, allActivatedEntries, buffer, scanState, timedEffects);3991 filterByInclusionGroups(newEntries, allActivatedEntries, buffer, scanState, timedEffects);
39503992
3951 console.debug('[WI] --- PROBABILITY CHECKS ---');3993 console.debug('[WI] --- PROBABILITY CHECKS ---');
3994 !newEntries.length && console.debug('[WI] No probability checks to do');
3995
3952 for (const entry of newEntries) {3996 for (const entry of newEntries) {
3953 function verifyProbability() {3997 function verifyProbability() {
3954 // If we don't need to roll, it's always true3998 // If we don't need to roll, it's always true
@@ -3984,6 +4028,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
3984 newContent += `${entry.content}\n`;4028 newContent += `${entry.content}\n`;
39854029
3986 if ((textToScanTokens + (await getTokenCountAsync(newContent))) >= budget) {4030 if ((textToScanTokens + (await getTokenCountAsync(newContent))) >= budget) {
4031 console.debug('[WI] --- BUDGET OVERFLOW CHECK ---');
3987 if (world_info_overflow_alert) {4032 if (world_info_overflow_alert) {
3988 console.warn(`[WI] budget of ${budget} reached, stopping after ${allActivatedEntries.size} entries`);4033 console.warn(`[WI] budget of ${budget} reached, stopping after ${allActivatedEntries.size} entries`);
3989 toastr.warning(`World info budget reached after ${allActivatedEntries.size} entries.`, 'World Info');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 const successfulNewEntries = newEntries.filter(x => !failedProbabilityChecks.has(x));4046 const successfulNewEntries = newEntries.filter(x => !failedProbabilityChecks.has(x));
4002 const successfulNewEntriesForRecursion = successfulNewEntries.filter(x => !x.preventRecursion);4047 const successfulNewEntriesForRecursion = successfulNewEntries.filter(x => !x.preventRecursion);
40034048
4049 console.debug(`[WI] --- LOOP #${count} RESULT ---`);
4004 if (!newEntries.length) {4050 if (!newEntries.length) {
4005 console.debug('[WI] No new entries activated, stopping');4051 console.debug('[WI] No new entries activated.');
4006 } else if (!successfulNewEntries.length) {4052 } else if (!successfulNewEntries.length) {
4007 console.debug('[WI] Probability checks failed for all activated entries, stopping');4053 console.debug('[WI] Probability checks failed for all activated entries. No new entries activated.');
4008 } else {4054 } else {
4009 console.debug(`[WI] Successfully activated ${successfulNewEntries.length} new entries to prompt. ${allActivatedEntries.size} total entries activated.`, successfulNewEntries);4055 console.debug(`[WI] Successfully activated ${successfulNewEntries.length} new entries to prompt. ${allActivatedEntries.size} total entries activated.`, successfulNewEntries);
4010 }4056 }
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
4012 // After processing and rolling entries is done, see if we should continue with normal recursion4063 // After processing and rolling entries is done, see if we should continue with normal recursion
4013 if (world_info_recursive && !token_budget_overflowed && successfulNewEntriesForRecursion.length) {4064 if (world_info_recursive && !token_budget_overflowed && successfulNewEntriesForRecursion.length) {
4014 nextScanState = scan_state.RECURSION;4065 nextScanState = scan_state.RECURSION;
4066 logNextState('[WI] Found', successfulNewEntriesForRecursion.length, 'new entries for recursion');
4015 }4067 }
40164068
4017 // If we are inside min activations scan, and we have recursive buffer, we should do a recursive scan before increasing the buffer again4069 // If we are inside min activations scan, and we have recursive buffer, we should do a recursive scan before increasing the buffer again
4018 // There might be recurse-trigger-able entries that match the buffer, so we need to check that4070 // There might be recurse-trigger-able entries that match the buffer, so we need to check that
4019 if (world_info_recursive && !token_budget_overflowed && scanState === scan_state.MIN_ACTIVATIONS && buffer.hasRecurse()) {4071 if (world_info_recursive && !token_budget_overflowed && scanState === scan_state.MIN_ACTIVATIONS && buffer.hasRecurse()) {
4020 nextScanState = scan_state.RECURSION;4072 nextScanState = scan_state.RECURSION;
4073 logNextState('[WI] Min Activations run done, whill will always be followed by a recursive scan');
4021 }4074 }
40224075
4023 // If scanning is planned to stop, but min activations is set and not satisfied, check if we should continue4076 // 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 ) || (buffer.getDepth() > chat.length);4084 ) || (buffer.getDepth() > chat.length);
40324085
4033 if (!over_max) {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 nextScanState = scan_state.MIN_ACTIVATIONS; // loop4087 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 buffer.advanceScan();4089 buffer.advanceScan();
4037 } else {4090 } else {
4038 console.debug(`[WI] Min activations not reached (${allActivatedEntries.size}/${world_info_min_activations}), but reached on of depth. Stopping`);4091 console.debug(`[WI] Min activations not reached (${allActivatedEntries.size}/${world_info_min_activations}), but reached on of depth. Stopping`);
4039 }4092 }
4040 }4093 }
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
4042 // Final check if we should really continue scan, and extend the current WI recurse buffer4102 // Final check if we should really continue scan, and extend the current WI recurse buffer
4043 scanState = nextScanState;4103 scanState = nextScanState;
4044 if (scanState) {4104 if (scanState) {
@@ -4046,6 +4106,8 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
4046 .map(x => x.content).join('\n');4106 .map(x => x.content).join('\n');
4047 buffer.addRecurse(text);4107 buffer.addRecurse(text);
4048 allActivatedText = (text + '\n' + allActivatedText);4108 allActivatedText = (text + '\n' + allActivatedText);
4109 } else {
4110 logNextState('[WI] Scan done. No new entries to prompt. Stopping.');
4049 }4111 }
4050 }4112 }
40514113