mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-03-02 02:47:52 +01:00
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
This commit is contained in:
commit
0b0bd27321
@ -274,3 +274,7 @@ select.keyselect+span.select2-container .select2-selection--multiple {
|
||||
align-self: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.world_entry:has(input[name="delay_until_recursion"]:not(:checked)) .world_entry_form_control:has(input[name="delayUntilRecursionLevel"]) {
|
||||
display: none;
|
||||
}
|
||||
|
@ -5473,23 +5473,23 @@
|
||||
<div class="world_entry_form_control flex1">
|
||||
<small class="textAlignCenter" data-i18n="Case-Sensitive">Case-Sensitive</small>
|
||||
<select name="caseSensitive" class="text_pole widthNatural margin0">
|
||||
<option value="null" data-i18n="Use global setting">Use global setting</option>
|
||||
<option value="null" data-i18n="Use global">Use global</option>
|
||||
<option value="true" data-i18n="Yes">Yes</option>
|
||||
<option value="false" data-i18n="No">No</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="world_entry_form_control flex1">
|
||||
<small class="textAlignCenter" data-i18n="Match Whole Words">Match Whole Words</small>
|
||||
<small class="textAlignCenter" data-i18n="Whole Words">Whole Words</small>
|
||||
<select name="matchWholeWords" class="text_pole widthNatural margin0">
|
||||
<option value="null" data-i18n="Use global setting">Use global setting</option>
|
||||
<option value="null" data-i18n="Use global">Use global</option>
|
||||
<option value="true" data-i18n="Yes">Yes</option>
|
||||
<option value="false" data-i18n="No">No</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="world_entry_form_control flex1">
|
||||
<small class="textAlignCenter" data-i18n="Use Group Scoring">Use Group Scoring</small>
|
||||
<small class="textAlignCenter" data-i18n="Group Scoring">Group Scoring</small>
|
||||
<select name="useGroupScoring" class="text_pole widthNatural margin0">
|
||||
<option value="null" data-i18n="Use global setting">Use global setting</option>
|
||||
<option value="null" data-i18n="Use global">Use global</option>
|
||||
<option value="true" data-i18n="Yes">Yes</option>
|
||||
<option value="false" data-i18n="No">No</option>
|
||||
</select>
|
||||
@ -5498,6 +5498,13 @@
|
||||
<small class="textAlignCenter" data-i18n="Automation ID">Automation ID</small>
|
||||
<input class="text_pole margin0" name="automationId" type="text" placeholder="( None )" data-i18n="[placeholder]( None )">
|
||||
</div>
|
||||
<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">
|
||||
<small class="textAlignCenter">
|
||||
<span data-i18n="Recursion Level">Recursion Level</span>
|
||||
<div class="fa-solid fa-circle-info opacity50p"></div>
|
||||
</small>
|
||||
<input class="text_pole margin0" name="delayUntilRecursionLevel" type="text" placeholder="1">
|
||||
</div>
|
||||
</div>
|
||||
<div name="contentAndCharFilterBlock" class="world_entry_thin_controls flex2">
|
||||
<div class="world_entry_form_control flex1">
|
||||
@ -5514,20 +5521,20 @@
|
||||
<div>
|
||||
<label class="checkbox flex-container alignitemscenter flexNoGap">
|
||||
<input type="checkbox" name="exclude_recursion" />
|
||||
<span data-i18n="Exclude from recursion">
|
||||
Non-recursable (this entry will not be activated by another)
|
||||
<span data-i18n="Non-recursable (will not be activated by another)">
|
||||
Non-recursable (will not be activated by another)
|
||||
</span>
|
||||
</label>
|
||||
<label class="checkbox flex-container alignitemscenter flexNoGap">
|
||||
<input type="checkbox" name="prevent_recursion" />
|
||||
<span data-i18n="Prevent further recursion (this entry will not activate others)">
|
||||
Prevent further recursion (this entry will not activate others)
|
||||
Prevent further recursion (will not activate others)
|
||||
</span>
|
||||
</label>
|
||||
<label class="checkbox flex-container alignitemscenter flexNoGap">
|
||||
<input type="checkbox" name="delay_until_recursion" />
|
||||
<span data-i18n="Delay until recursion (this entry can only be activated on recursive checking)">
|
||||
Delay until recursion (this entry can only be activated on recursive checking)
|
||||
<span data-i18n="Delay until recursion (can only be activated on recursive checking)">
|
||||
Delay until recursion (can only be activated on recursive checking)
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
@ -217,8 +217,9 @@ class WorldInfoBuffer {
|
||||
depth = MAX_SCAN_DEPTH;
|
||||
}
|
||||
|
||||
const JOINER = '\n\x01';
|
||||
let result = this.#depthBuffer.slice(this.#startDepth, depth).join(JOINER);
|
||||
const MATCHER = '\x01';
|
||||
const JOINER = '\n' + MATCHER;
|
||||
let result = MATCHER + this.#depthBuffer.slice(this.#startDepth, depth).join(JOINER);
|
||||
|
||||
if (this.#injectBuffer.length > 0) {
|
||||
result += JOINER + this.#injectBuffer.join(JOINER);
|
||||
@ -2949,16 +2950,39 @@ async function getWorldEntry(name, data, entry) {
|
||||
preventRecursionInput.prop('checked', entry.preventRecursion).trigger('input');
|
||||
|
||||
// delay until recursion
|
||||
// delay until recursion level
|
||||
const delayUntilRecursionInput = template.find('input[name="delay_until_recursion"]');
|
||||
delayUntilRecursionInput.data('uid', entry.uid);
|
||||
const delayUntilRecursionLevelInput = template.find('input[name="delayUntilRecursionLevel"]');
|
||||
delayUntilRecursionLevelInput.data('uid', entry.uid);
|
||||
delayUntilRecursionInput.on('input', async function () {
|
||||
const uid = $(this).data('uid');
|
||||
const value = $(this).prop('checked');
|
||||
const toggled = $(this).prop('checked');
|
||||
|
||||
// If the value contains a number, we'll take that one (set by the level input), otherwise we can use true/false switch
|
||||
const value = toggled ? data.entries[uid].delayUntilRecursion || true : false;
|
||||
|
||||
if (!toggled) delayUntilRecursionLevelInput.val('');
|
||||
|
||||
data.entries[uid].delayUntilRecursion = value;
|
||||
setWIOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion);
|
||||
await saveWorldInfo(name, data);
|
||||
});
|
||||
delayUntilRecursionInput.prop('checked', entry.delayUntilRecursion).trigger('input');
|
||||
delayUntilRecursionLevelInput.on('input', async function () {
|
||||
const uid = $(this).data('uid');
|
||||
const content = $(this).val();
|
||||
const value = content === '' ? (typeof data.entries[uid].delayUntilRecursion === 'boolean' ? data.entries[uid].delayUntilRecursion : true)
|
||||
: content === 1 ? true
|
||||
: !isNaN(Number(content)) ? Number(content)
|
||||
: false;
|
||||
|
||||
data.entries[uid].delayUntilRecursion = value;
|
||||
setWIOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion);
|
||||
await saveWorldInfo(name, data);
|
||||
});
|
||||
// No need to retrigger inpout event, we'll just set the curret current value. It was edited/saved above already
|
||||
delayUntilRecursionLevelInput.val(['number', 'string'].includes(typeof entry.delayUntilRecursion) ? entry.delayUntilRecursion : '').trigger('input');
|
||||
|
||||
// duplicate button
|
||||
const duplicateButton = template.find('.duplicate_entry_button');
|
||||
@ -3257,7 +3281,7 @@ export const newWorldInfoEntryDefinition = {
|
||||
disable: { default: false, type: 'boolean' },
|
||||
excludeRecursion: { default: false, type: 'boolean' },
|
||||
preventRecursion: { default: false, type: 'boolean' },
|
||||
delayUntilRecursion: { default: false, type: 'boolean' },
|
||||
delayUntilRecursion: { default: 0, type: 'number' },
|
||||
probability: { default: 100, type: 'number' },
|
||||
useProbability: { default: true, type: 'boolean' },
|
||||
depth: { default: DEFAULT_DEPTH, type: 'number' },
|
||||
@ -3696,6 +3720,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {scan_state} */
|
||||
let scanState = scan_state.INITIAL;
|
||||
let token_budget_overflowed = false;
|
||||
let count = 0;
|
||||
@ -3720,6 +3745,17 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
|
||||
return { worldInfoBefore: '', worldInfoAfter: '', WIDepthEntries: [], EMEntries: [], allActivatedEntries: new Set() };
|
||||
}
|
||||
|
||||
/** @type {number[]} Represents the delay levels for entries that are delayed until recursion */
|
||||
const availableRecursionDelayLevels = [...new Set(sortedEntries
|
||||
.filter(entry => entry.delayUntilRecursion)
|
||||
.map(entry => entry.delayUntilRecursion === true ? 1 : entry.delayUntilRecursion),
|
||||
)].sort((a, b) => a - b);
|
||||
// Already preset with the first level
|
||||
let currentRecursionDelayLevel = availableRecursionDelayLevels.shift() ?? 0;
|
||||
if (currentRecursionDelayLevel > 0 && availableRecursionDelayLevels.length) {
|
||||
console.debug('[WI] Preparing first delayed recursion level', currentRecursionDelayLevel, '. Still delayed:', availableRecursionDelayLevels);
|
||||
}
|
||||
|
||||
console.debug(`[WI] --- SEARCHING ENTRIES (on ${sortedEntries.length} entries) ---`);
|
||||
|
||||
while (scanState) {
|
||||
@ -3732,7 +3768,8 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
|
||||
// Track how many times the loop has run. May be useful for debugging.
|
||||
count++;
|
||||
|
||||
console.debug(`[WI] Loop #${count}. Search state`, Object.entries(scan_state).find(x => x[1] === scanState));
|
||||
console.debug(`[WI] --- LOOP #${count} START ---`);
|
||||
console.debug('[WI] Scan state', Object.entries(scan_state).find(x => x[1] === scanState));
|
||||
|
||||
// Until decided otherwise, we set the loop to stop scanning after this
|
||||
let nextScanState = scan_state.NONE;
|
||||
@ -3811,6 +3848,11 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (scanState === scan_state.RECURSION && entry.delayUntilRecursion && entry.delayUntilRecursion > currentRecursionDelayLevel && !isSticky) {
|
||||
log('suppressed by delay until recursion level', entry.delayUntilRecursion, '. Currently', currentRecursionDelayLevel);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (scanState === scan_state.RECURSION && world_info_recursive && entry.excludeRecursion && !isSticky) {
|
||||
log('suppressed by exclude recursion');
|
||||
continue;
|
||||
@ -3949,6 +3991,8 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
|
||||
filterByInclusionGroups(newEntries, allActivatedEntries, buffer, scanState, timedEffects);
|
||||
|
||||
console.debug('[WI] --- PROBABILITY CHECKS ---');
|
||||
!newEntries.length && console.debug('[WI] No probability checks to do');
|
||||
|
||||
for (const entry of newEntries) {
|
||||
function verifyProbability() {
|
||||
// If we don't need to roll, it's always true
|
||||
@ -3984,6 +4028,7 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
|
||||
newContent += `${entry.content}\n`;
|
||||
|
||||
if ((textToScanTokens + (await getTokenCountAsync(newContent))) >= budget) {
|
||||
console.debug('[WI] --- BUDGET OVERFLOW CHECK ---');
|
||||
if (world_info_overflow_alert) {
|
||||
console.warn(`[WI] budget of ${budget} reached, stopping after ${allActivatedEntries.size} entries`);
|
||||
toastr.warning(`World info budget reached after ${allActivatedEntries.size} entries.`, 'World Info');
|
||||
@ -4001,23 +4046,31 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
|
||||
const successfulNewEntries = newEntries.filter(x => !failedProbabilityChecks.has(x));
|
||||
const successfulNewEntriesForRecursion = successfulNewEntries.filter(x => !x.preventRecursion);
|
||||
|
||||
console.debug(`[WI] --- LOOP #${count} RESULT ---`);
|
||||
if (!newEntries.length) {
|
||||
console.debug('[WI] No new entries activated, stopping');
|
||||
console.debug('[WI] No new entries activated.');
|
||||
} else if (!successfulNewEntries.length) {
|
||||
console.debug('[WI] Probability checks failed for all activated entries, stopping');
|
||||
console.debug('[WI] Probability checks failed for all activated entries. No new entries activated.');
|
||||
} else {
|
||||
console.debug(`[WI] Successfully activated ${successfulNewEntries.length} new entries to prompt. ${allActivatedEntries.size} total entries activated.`, successfulNewEntries);
|
||||
}
|
||||
|
||||
function logNextState(...args) {
|
||||
args.length && console.debug(args.shift(), ...args);
|
||||
console.debug('[WI] Setting scan state', Object.entries(scan_state).find(x => x[1] === scanState));
|
||||
}
|
||||
|
||||
// After processing and rolling entries is done, see if we should continue with normal recursion
|
||||
if (world_info_recursive && !token_budget_overflowed && successfulNewEntriesForRecursion.length) {
|
||||
nextScanState = scan_state.RECURSION;
|
||||
logNextState('[WI] Found', successfulNewEntriesForRecursion.length, 'new entries for recursion');
|
||||
}
|
||||
|
||||
// If we are inside min activations scan, and we have recursive buffer, we should do a recursive scan before increasing the buffer again
|
||||
// There might be recurse-trigger-able entries that match the buffer, so we need to check that
|
||||
if (world_info_recursive && !token_budget_overflowed && scanState === scan_state.MIN_ACTIVATIONS && buffer.hasRecurse()) {
|
||||
nextScanState = scan_state.RECURSION;
|
||||
logNextState('[WI] Min Activations run done, whill will always be followed by a recursive scan');
|
||||
}
|
||||
|
||||
// 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) {
|
||||
) || (buffer.getDepth() > chat.length);
|
||||
|
||||
if (!over_max) {
|
||||
console.debug(`[WI] Min activations not reached (${allActivatedEntries.size}/${world_info_min_activations}), advancing depth to ${buffer.getDepth() + 1} and checking again`);
|
||||
nextScanState = scan_state.MIN_ACTIVATIONS; // loop
|
||||
logNextState(`[WI] Min activations not reached (${allActivatedEntries.size}/${world_info_min_activations}), advancing depth to ${buffer.getDepth() + 1}, starting another scan`);
|
||||
buffer.advanceScan();
|
||||
} else {
|
||||
console.debug(`[WI] Min activations not reached (${allActivatedEntries.size}/${world_info_min_activations}), but reached on of depth. Stopping`);
|
||||
}
|
||||
}
|
||||
|
||||
// If the scan is done, but we still have open "delay until recursion" levels, we should continue with the next one
|
||||
if (nextScanState === scan_state.NONE && availableRecursionDelayLevels.length) {
|
||||
nextScanState = scan_state.RECURSION;
|
||||
currentRecursionDelayLevel = availableRecursionDelayLevels.shift();
|
||||
logNextState('[WI] Open delayed recursion levels left. Preparing next delayed recursion level', currentRecursionDelayLevel, '. Still delayed:', availableRecursionDelayLevels);
|
||||
}
|
||||
|
||||
// Final check if we should really continue scan, and extend the current WI recurse buffer
|
||||
scanState = nextScanState;
|
||||
if (scanState) {
|
||||
@ -4046,6 +4106,8 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
|
||||
.map(x => x.content).join('\n');
|
||||
buffer.addRecurse(text);
|
||||
allActivatedText = (text + '\n' + allActivatedText);
|
||||
} else {
|
||||
logNextState('[WI] Scan done. No new entries to prompt. Stopping.');
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user