Merge pull request #3889 from BismuthGlass/feature/wi_global_matches World Info chat-independent data matching

ed895b7c3ec2dada64e35f835d1a3aae1aba1092

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

Signed
6 files changed, +168 -6Ignore whitespace
public/css/world-info.css+4 -0
@@ -124,6 +124,10 @@
124 cursor: initial;124 cursor: initial;
125}125}
126126
127.world_entry .inline-drawer-header-pointer {
128 cursor: pointer;
129}
130
127.world_entry .killSwitch {131.world_entry .killSwitch {
128 cursor: pointer;132 cursor: pointer;
129}133}
public/index.html+48 -0
@@ -6292,6 +6292,54 @@
6292 </label>6292 </label>
6293 </div>6293 </div>
6294 </div>6294 </div>
6295 <div class="inline-drawer wide100p flexFlowColumn">
6296 <div class="inline-drawer-toggle inline-drawer-header inline-drawer-header-pointer userSettingsInnerExpandable">
6297 <strong data-i18n="Additional Matching Sources">Additional Matching Sources</strong>
6298 <div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
6299 </div>
6300 <div class="inline-drawer-content flex-container flexFlowRow flexGap10 paddingBottom5px">
6301 <small class="flex-container flex1 flexFlowColumn">
6302 <label class="checkbox flex-container alignItemsCenter flexNoGap">
6303 <input type="checkbox" name="matchCharacterDescription" />
6304 <span data-i18n="Character Description">
6305 Character Description
6306 </span>
6307 </label>
6308 <label class="checkbox flex-container alignItemsCenter flexNoGap">
6309 <input type="checkbox" name="matchCharacterPersonality" />
6310 <span data-i18n="Character Personality">
6311 Character Personality
6312 </span>
6313 </label>
6314 <label class="checkbox flex-container alignItemsCenter flexNoGap">
6315 <input type="checkbox" name="matchScenario" />
6316 <span data-i18n="Scenario">
6317 Scenario
6318 </span>
6319 </label>
6320 </small>
6321 <small class="flex-container flex1 flexFlowColumn">
6322 <label class="checkbox flex-container alignItemsCenter flexNoGap">
6323 <input type="checkbox" name="matchPersonaDescription" />
6324 <span data-i18n="Persona Description">
6325 Persona Description
6326 </span>
6327 </label>
6328 <label class="checkbox flex-container alignItemsCenter flexNoGap">
6329 <input type="checkbox" name="matchCharacterDepthPrompt" />
6330 <span data-i18n="Character's Note">
6331 Character's Note
6332 </span>
6333 </label>
6334 <label class="checkbox flex-container alignItemsCenter flexNoGap">
6335 <input type="checkbox" name="matchCreatorNotes" />
6336 <span data-i18n="Creator's Notes">
6337 Creator's Notes
6338 </span>
6339 </label>
6340 </small>
6341 </div>
6342 </div>
6295 </div>6343 </div>
6296 </div>6344 </div>
6297 </form>6345 </form>
public/script.js+14 -1
@@ -2753,6 +2753,7 @@ export function substituteParams(content, _name1, _name2, _original, _group, _re
2753 environment.charVersion = fields.version || '';2753 environment.charVersion = fields.version || '';
2754 environment.char_version = fields.version || '';2754 environment.char_version = fields.version || '';
2755 environment.charDepthPrompt = fields.charDepthPrompt || '';2755 environment.charDepthPrompt = fields.charDepthPrompt || '';
2756 environment.creatorNotes = fields.creatorNotes || '';
2756 }2757 }
27572758
2758 // Must be substituted last so that they're replaced inside {{description}}2759 // Must be substituted last so that they're replaced inside {{description}}
@@ -3131,6 +3132,7 @@ export function baseChatReplace(value, name1, name2) {
3131 * @property {string} jailbreak Jailbreak instructions3132 * @property {string} jailbreak Jailbreak instructions
3132 * @property {string} version Character version3133 * @property {string} version Character version
3133 * @property {string} charDepthPrompt Character depth note3134 * @property {string} charDepthPrompt Character depth note
3135 * @property {string} creatorNotes Character creator notes
3134 * @returns {CharacterCardFields} Character card fields3136 * @returns {CharacterCardFields} Character card fields
3135 */3137 */
3136export function getCharacterCardFields({ chid = null } = {}) {3138export function getCharacterCardFields({ chid = null } = {}) {
@@ -3146,6 +3148,7 @@ export function getCharacterCardFields({ chid = null } = {}) {
3146 jailbreak: '',3148 jailbreak: '',
3147 version: '',3149 version: '',
3148 charDepthPrompt: '',3150 charDepthPrompt: '',
3151 creatorNotes: '',
3149 };3152 };
3150 result.persona = baseChatReplace(power_user.persona_description?.trim(), name1, name2);3153 result.persona = baseChatReplace(power_user.persona_description?.trim(), name1, name2);
31513154
@@ -3164,6 +3167,7 @@ export function getCharacterCardFields({ chid = null } = {}) {
3164 result.jailbreak = power_user.prefer_character_jailbreak ? baseChatReplace(character.data?.post_history_instructions?.trim(), name1, name2) : '';3167 result.jailbreak = power_user.prefer_character_jailbreak ? baseChatReplace(character.data?.post_history_instructions?.trim(), name1, name2) : '';
3165 result.version = character.data?.character_version ?? '';3168 result.version = character.data?.character_version ?? '';
3166 result.charDepthPrompt = baseChatReplace(character.data?.extensions?.depth_prompt?.prompt?.trim(), name1, name2);3169 result.charDepthPrompt = baseChatReplace(character.data?.extensions?.depth_prompt?.prompt?.trim(), name1, name2);
3170 result.creatorNotes = baseChatReplace(character.data?.creator_notes?.trim(), name1, name2);
31673171
3168 if (selected_group) {3172 if (selected_group) {
3169 const groupCards = getGroupCharacterCards(selected_group, Number(currentChid));3173 const groupCards = getGroupCharacterCards(selected_group, Number(currentChid));
@@ -3991,6 +3995,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
3991 system,3995 system,
3992 jailbreak,3996 jailbreak,
3993 charDepthPrompt,3997 charDepthPrompt,
3998 creatorNotes,
3994 } = getCharacterCardFields();3999 } = getCharacterCardFields();
39954000
3996 if (main_api !== 'openai') {4001 if (main_api !== 'openai') {
@@ -4145,7 +4150,15 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
4145 // Make quiet prompt available for WIAN4150 // Make quiet prompt available for WIAN
4146 setExtensionPrompt('QUIET_PROMPT', quiet_prompt || '', extension_prompt_types.IN_PROMPT, 0, true);4151 setExtensionPrompt('QUIET_PROMPT', quiet_prompt || '', extension_prompt_types.IN_PROMPT, 0, true);
4147 const chatForWI = coreChat.map(x => world_info_include_names ? `${x.name}: ${x.mes}` : x.mes).reverse();4152 const chatForWI = coreChat.map(x => world_info_include_names ? `${x.name}: ${x.mes}` : x.mes).reverse();
4148 const { worldInfoString, worldInfoBefore, worldInfoAfter, worldInfoExamples, worldInfoDepth } = await getWorldInfoPrompt(chatForWI, this_max_context, dryRun);4153 const globalScanData = {
4154 personaDescription: persona,
4155 characterDescription: description,
4156 characterPersonality: personality,
4157 characterDepthPrompt: charDepthPrompt,
4158 scenario: scenario,
4159 creatorNotes: creatorNotes,
4160 };
4161 const { worldInfoString, worldInfoBefore, worldInfoAfter, worldInfoExamples, worldInfoDepth } = await getWorldInfoPrompt(chatForWI, this_max_context, dryRun, globalScanData);
4149 setExtensionPrompt('QUIET_PROMPT', '', extension_prompt_types.IN_PROMPT, 0, true);4162 setExtensionPrompt('QUIET_PROMPT', '', extension_prompt_types.IN_PROMPT, 0, true);
41504163
4151 // Add message example WI4164 // Add message example WI
public/scripts/char-data.js+6 -0
@@ -33,6 +33,12 @@
33 * @property {number} role - The specific function or purpose of the extension.33 * @property {number} role - The specific function or purpose of the extension.
34 * @property {boolean} vectorized - Indicates if the extension is optimized for vectorized processing.34 * @property {boolean} vectorized - Indicates if the extension is optimized for vectorized processing.
35 * @property {number} display_index - The order in which the extension should be displayed for user interfaces.35 * @property {number} display_index - The order in which the extension should be displayed for user interfaces.
36 * @property {boolean} match_persona_description - Wether to match against the persona description.
37 * @property {boolean} match_character_description - Wether to match against the persona description.
38 * @property {boolean} match_character_personality - Wether to match against the character personality.
39 * @property {boolean} match_character_depth_prompt - Wether to match against the character depth prompt.
40 * @property {boolean} match_scenario - Wether to match against the character scenario.
41 * @property {boolean} match_creator_notes - Wether to match against the character creator notes.
36 */42 */
3743
38/**44/**
public/scripts/world-info.js+90 -5
@@ -98,11 +98,28 @@ const KNOWN_DECORATORS = ['@@activate', '@@dont_activate'];
9898
99// Typedef area99// Typedef area
100/**100/**
101 * @typedef {object} WIGlobalScanData The chat-independent data to be scanned. Each of
102 * these fields can be enabled for scanning per entry.
103 * @property {string} personaDescription User persona description
104 * @property {string} characterDescription Character description
105 * @property {string} characterPersonality Character personality
106 * @property {string} characterDepthPrompt Character depth prompt (sometimes referred to as character notes)
107 * @property {string} scenario Character defined scenario
108 * @property {string} creatorNotes Character creator notes
109 */
110
111/**
101 * @typedef {object} WIScanEntry The entry that triggered the scan112 * @typedef {object} WIScanEntry The entry that triggered the scan
102 * @property {number} [scanDepth] The depth of the scan113 * @property {number} [scanDepth] The depth of the scan
103 * @property {boolean} [caseSensitive] If the scan is case sensitive114 * @property {boolean} [caseSensitive] If the scan is case sensitive
104 * @property {boolean} [matchWholeWords] If the scan should match whole words115 * @property {boolean} [matchWholeWords] If the scan should match whole words
105 * @property {boolean} [useGroupScoring] If the scan should use group scoring116 * @property {boolean} [useGroupScoring] If the scan should use group scoring
117 * @property {boolean} [matchPersonaDescription] If the scan should match against the persona description
118 * @property {boolean} [matchCharacterDescription] If the scan should match against the character description
119 * @property {boolean} [matchCharacterPersonality] If the scan should match against the character personality
120 * @property {boolean} [matchCharacterDepthPrompt] If the scan should match against the character depth prompt
121 * @property {boolean} [matchScenario] If the scan should match against the character scenario
122 * @property {boolean} [matchCreatorNotes] If the scan should match against the creator notes
106 * @property {number} [uid] The UID of the entry that triggered the scan123 * @property {number} [uid] The UID of the entry that triggered the scan
107 * @property {string} [world] The world info book of origin of the entry124 * @property {string} [world] The world info book of origin of the entry
108 * @property {string[]} [key] The primary keys to scan for125 * @property {string[]} [key] The primary keys to scan for
@@ -139,6 +156,11 @@ class WorldInfoBuffer {
139 static externalActivations = new Map();156 static externalActivations = new Map();
140157
141 /**158 /**
159 * @type {WIGlobalScanData} Chat independent data to be scanned, such as persona and character descriptions
160 */
161 #globalScanData = null;
162
163 /**
142 * @type {string[]} Array of messages sorted by ascending depth164 * @type {string[]} Array of messages sorted by ascending depth
143 */165 */
144 #depthBuffer = [];166 #depthBuffer = [];
@@ -166,9 +188,11 @@ class WorldInfoBuffer {
166 /**188 /**
167 * Initialize the buffer with the given messages.189 * Initialize the buffer with the given messages.
168 * @param {string[]} messages Array of messages to add to the buffer190 * @param {string[]} messages Array of messages to add to the buffer
191 * @param {WIGlobalScanData} globalScanData Chat independent context to be scanned
169 */192 */
170 constructor(messages) {193 constructor(messages, globalScanData) {
171 this.#initDepthBuffer(messages);194 this.#initDepthBuffer(messages);
195 this.#globalScanData = globalScanData;
172 }196 }
173197
174 /**198 /**
@@ -225,6 +249,25 @@ class WorldInfoBuffer {
225 const JOINER = '\n' + MATCHER;249 const JOINER = '\n' + MATCHER;
226 let result = MATCHER + this.#depthBuffer.slice(this.#startDepth, depth).join(JOINER);250 let result = MATCHER + this.#depthBuffer.slice(this.#startDepth, depth).join(JOINER);
227251
252 if (entry.matchPersonaDescription && this.#globalScanData.personaDescription) {
253 result += JOINER + this.#globalScanData.personaDescription;
254 }
255 if (entry.matchCharacterDescription && this.#globalScanData.characterDescription) {
256 result += JOINER + this.#globalScanData.characterDescription;
257 }
258 if (entry.matchCharacterPersonality && this.#globalScanData.characterPersonality) {
259 result += JOINER + this.#globalScanData.characterPersonality;
260 }
261 if (entry.matchCharacterDepthPrompt && this.#globalScanData.characterDepthPrompt) {
262 result += JOINER + this.#globalScanData.characterDepthPrompt;
263 }
264 if (entry.matchScenario && this.#globalScanData.scenario) {
265 result += JOINER + this.#globalScanData.scenario;
266 }
267 if (entry.matchCreatorNotes && this.#globalScanData.creatorNotes) {
268 result += JOINER + this.#globalScanData.creatorNotes;
269 }
270
228 if (this.#injectBuffer.length > 0) {271 if (this.#injectBuffer.length > 0) {
229 result += JOINER + this.#injectBuffer.join(JOINER);272 result += JOINER + this.#injectBuffer.join(JOINER);
230 }273 }
@@ -756,6 +799,7 @@ export const worldInfoCache = new StructuredCloneMap({ cloneOnGet: true, cloneOn
756 * @param {string[]} chat - The chat messages to scan, in reverse order.799 * @param {string[]} chat - The chat messages to scan, in reverse order.
757 * @param {number} maxContext - The maximum context size of the generation.800 * @param {number} maxContext - The maximum context size of the generation.
758 * @param {boolean} isDryRun - If true, the function will not emit any events.801 * @param {boolean} isDryRun - If true, the function will not emit any events.
802 * @param {WIGlobalScanData} globalScanData Chat independent context to be scanned
759 * @typedef {object} WIPromptResult803 * @typedef {object} WIPromptResult
760 * @property {string} worldInfoString - Complete world info string804 * @property {string} worldInfoString - Complete world info string
761 * @property {string} worldInfoBefore - World info that goes before the prompt805 * @property {string} worldInfoBefore - World info that goes before the prompt
@@ -766,10 +810,10 @@ export const worldInfoCache = new StructuredCloneMap({ cloneOnGet: true, cloneOn
766 * @property {Array} anAfter - Array of entries after Author's Note810 * @property {Array} anAfter - Array of entries after Author's Note
767 * @returns {Promise<WIPromptResult>} The world info string and depth.811 * @returns {Promise<WIPromptResult>} The world info string and depth.
768 */812 */
769export async function getWorldInfoPrompt(chat, maxContext, isDryRun) {813export async function getWorldInfoPrompt(chat, maxContext, isDryRun, globalScanData) {
770 let worldInfoString = '', worldInfoBefore = '', worldInfoAfter = '';814 let worldInfoString = '', worldInfoBefore = '', worldInfoAfter = '';
771815
772 const activatedWorldInfo = await checkWorldInfo(chat, maxContext, isDryRun);816 const activatedWorldInfo = await checkWorldInfo(chat, maxContext, isDryRun, globalScanData);
773 worldInfoBefore = activatedWorldInfo.worldInfoBefore;817 worldInfoBefore = activatedWorldInfo.worldInfoBefore;
774 worldInfoAfter = activatedWorldInfo.worldInfoAfter;818 worldInfoAfter = activatedWorldInfo.worldInfoAfter;
775 worldInfoString = worldInfoBefore + worldInfoAfter;819 worldInfoString = worldInfoBefore + worldInfoAfter;
@@ -2191,6 +2235,12 @@ export const originalWIDataKeyMap = {
2191 'matchWholeWords': 'extensions.match_whole_words',2235 'matchWholeWords': 'extensions.match_whole_words',
2192 'useGroupScoring': 'extensions.use_group_scoring',2236 'useGroupScoring': 'extensions.use_group_scoring',
2193 'caseSensitive': 'extensions.case_sensitive',2237 'caseSensitive': 'extensions.case_sensitive',
2238 'matchPersonaDescription': 'extensions.match_persona_description',
2239 'matchCharacterDescription': 'extensions.match_character_description',
2240 'matchCharacterPersonality': 'extensions.match_character_personality',
2241 'matchCharacterDepthPrompt': 'extensions.match_character_depth_prompt',
2242 'matchScenario': 'extensions.match_scenario',
2243 'matchCreatorNotes': 'extensions.match_creator_notes',
2194 'scanDepth': 'extensions.scan_depth',2244 'scanDepth': 'extensions.scan_depth',
2195 'automationId': 'extensions.automation_id',2245 'automationId': 'extensions.automation_id',
2196 'vectorized': 'extensions.vectorized',2246 'vectorized': 'extensions.vectorized',
@@ -3308,6 +3358,28 @@ export async function getWorldEntry(name, data, entry) {
3308 });3358 });
3309 useGroupScoringSelect.val((entry.useGroupScoring === null || entry.useGroupScoring === undefined) ? 'null' : entry.useGroupScoring ? 'true' : 'false').trigger('input');3359 useGroupScoringSelect.val((entry.useGroupScoring === null || entry.useGroupScoring === undefined) ? 'null' : entry.useGroupScoring ? 'true' : 'false').trigger('input');
33103360
3361 function handleMatchCheckbox(fieldName) {
3362 const key = originalWIDataKeyMap[fieldName];
3363 const checkBoxElem = template.find(`input[type="checkbox"][name="${fieldName}"]`);
3364 checkBoxElem.data('uid', entry.uid);
3365 checkBoxElem.on('input', async function () {
3366 const uid = $(this).data('uid');
3367 const value = $(this).prop('checked');
3368
3369 data.entries[uid][fieldName] = value;
3370 setWIOriginalDataValue(data, uid, key, data.entries[uid][fieldName]);
3371 await saveWorldInfo(name, data);
3372 });
3373 checkBoxElem.prop('checked', !!entry[fieldName]).trigger('input');
3374 }
3375
3376 handleMatchCheckbox('matchPersonaDescription');
3377 handleMatchCheckbox('matchCharacterDescription');
3378 handleMatchCheckbox('matchCharacterPersonality');
3379 handleMatchCheckbox('matchCharacterDepthPrompt');
3380 handleMatchCheckbox('matchScenario');
3381 handleMatchCheckbox('matchCreatorNotes');
3382
3311 // automation id3383 // automation id
3312 const automationIdInput = template.find('input[name="automationId"]');3384 const automationIdInput = template.find('input[name="automationId"]');
3313 automationIdInput.data('uid', entry.uid);3385 automationIdInput.data('uid', entry.uid);
@@ -3514,6 +3586,12 @@ export const newWorldInfoEntryDefinition = {
3514 disable: { default: false, type: 'boolean' },3586 disable: { default: false, type: 'boolean' },
3515 excludeRecursion: { default: false, type: 'boolean' },3587 excludeRecursion: { default: false, type: 'boolean' },
3516 preventRecursion: { default: false, type: 'boolean' },3588 preventRecursion: { default: false, type: 'boolean' },
3589 matchPersonaDescription: { default: false, type: 'boolean' },
3590 matchCharacterDescription: { default: false, type: 'boolean' },
3591 matchCharacterPersonality: { default: false, type: 'boolean' },
3592 matchCharacterDepthPrompt: { default: false, type: 'boolean' },
3593 matchScenario: { default: false, type: 'boolean' },
3594 matchCreatorNotes: { default: false, type: 'boolean' },
3517 delayUntilRecursion: { default: 0, type: 'number' },3595 delayUntilRecursion: { default: 0, type: 'number' },
3518 probability: { default: 100, type: 'number' },3596 probability: { default: 100, type: 'number' },
3519 useProbability: { default: true, type: 'boolean' },3597 useProbability: { default: true, type: 'boolean' },
@@ -3978,6 +4056,7 @@ function parseDecorators(content) {
3978 * @param {string[]} chat The chat messages to scan, in reverse order.4056 * @param {string[]} chat The chat messages to scan, in reverse order.
3979 * @param {number} maxContext The maximum context size of the generation.4057 * @param {number} maxContext The maximum context size of the generation.
3980 * @param {boolean} isDryRun Whether to perform a dry run.4058 * @param {boolean} isDryRun Whether to perform a dry run.
4059 * @param {WIGlobalScanData} globalScanData Chat independent context to be scanned
3981 * @typedef {object} WIActivated4060 * @typedef {object} WIActivated
3982 * @property {string} worldInfoBefore The world info before the chat.4061 * @property {string} worldInfoBefore The world info before the chat.
3983 * @property {string} worldInfoAfter The world info after the chat.4062 * @property {string} worldInfoAfter The world info after the chat.
@@ -3988,9 +4067,9 @@ function parseDecorators(content) {
3988 * @property {Set<any>} allActivatedEntries All entries.4067 * @property {Set<any>} allActivatedEntries All entries.
3989 * @returns {Promise<WIActivated>} The world info activated.4068 * @returns {Promise<WIActivated>} The world info activated.
3990 */4069 */
3991export async function checkWorldInfo(chat, maxContext, isDryRun) {4070export async function checkWorldInfo(chat, maxContext, isDryRun, globalScanData) {
3992 const context = getContext();4071 const context = getContext();
3993 const buffer = new WorldInfoBuffer(chat);4072 const buffer = new WorldInfoBuffer(chat, globalScanData);
39944073
3995 console.debug(`[WI] --- START WI SCAN (on ${chat.length} messages)${isDryRun ? ' (DRY RUN)' : ''} ---`);4074 console.debug(`[WI] --- START WI SCAN (on ${chat.length} messages)${isDryRun ? ' (DRY RUN)' : ''} ---`);
39964075
@@ -4848,6 +4927,12 @@ export function convertCharacterBook(characterBook) {
4848 sticky: entry.extensions?.sticky ?? null,4927 sticky: entry.extensions?.sticky ?? null,
4849 cooldown: entry.extensions?.cooldown ?? null,4928 cooldown: entry.extensions?.cooldown ?? null,
4850 delay: entry.extensions?.delay ?? null,4929 delay: entry.extensions?.delay ?? null,
4930 matchPersonaDescription: entry.extensions?.match_persona_description ?? false,
4931 matchCharacterDescription: entry.extensions?.match_character_description ?? false,
4932 matchCharacterPersonality: entry.extensions?.match_character_personality ?? false,
4933 matchCharacterDepthPrompt: entry.extensions?.match_character_depth_prompt ?? false,
4934 matchScenario: entry.extensions?.match_scenario ?? false,
4935 matchCreatorNotes: entry.extensions?.match_creator_notes ?? false,
4851 extensions: entry.extensions ?? {},4936 extensions: entry.extensions ?? {},
4852 };4937 };
4853 });4938 });
src/endpoints/characters.js+6 -0
@@ -702,6 +702,12 @@ function convertWorldInfoToCharacterBook(name, entries) {
702 sticky: entry.sticky ?? null,702 sticky: entry.sticky ?? null,
703 cooldown: entry.cooldown ?? null,703 cooldown: entry.cooldown ?? null,
704 delay: entry.delay ?? null,704 delay: entry.delay ?? null,
705 match_persona_description: entry.matchPersonaDescription ?? false,
706 match_character_description: entry.matchCharacterDescription ?? false,
707 match_character_personality: entry.matchCharacterPersonality ?? false,
708 match_character_depth_prompt: entry.matchCharacterDepthPrompt ?? false,
709 match_scenario: entry.matchScenario ?? false,
710 match_creator_notes: entry.matchCreatorNotes ?? false,
705 },711 },
706 };712 };
707713