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 -6Showing whitespace changes
public/css/world-info.css+4 -0
@@ -124,6 +124,10 @@
124124 cursor: initial;
125125}
126126
127+.world_entry .inline-drawer-header-pointer {
128+ cursor: pointer;
129+}
130+
127131.world_entry .killSwitch {
128132 cursor: pointer;
129133}
public/index.html+48 -0
@@ -6292,6 +6292,54 @@
62926292 </label>
62936293 </div>
62946294 </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>
62956343 </div>
62966344 </div>
62976345 </form>
public/script.js+14 -1
@@ -2753,6 +2753,7 @@ export function substituteParams(content, _name1, _name2, _original, _group, _re
27532753 environment.charVersion = fields.version || '';
27542754 environment.char_version = fields.version || '';
27552755 environment.charDepthPrompt = fields.charDepthPrompt || '';
2756+ environment.creatorNotes = fields.creatorNotes || '';
27562757 }
27572758
27582759 // Must be substituted last so that they're replaced inside {{description}}
@@ -3131,6 +3132,7 @@ export function baseChatReplace(value, name1, name2) {
31313132 * @property {string} jailbreak Jailbreak instructions
31323133 * @property {string} version Character version
31333134 * @property {string} charDepthPrompt Character depth note
3135+ * @property {string} creatorNotes Character creator notes
31343136 * @returns {CharacterCardFields} Character card fields
31353137 */
31363138export function getCharacterCardFields({ chid = null } = {}) {
@@ -3146,6 +3148,7 @@ export function getCharacterCardFields({ chid = null } = {}) {
31463148 jailbreak: '',
31473149 version: '',
31483150 charDepthPrompt: '',
3151+ creatorNotes: '',
31493152 };
31503153 result.persona = baseChatReplace(power_user.persona_description?.trim(), name1, name2);
31513154
@@ -3164,6 +3167,7 @@ export function getCharacterCardFields({ chid = null } = {}) {
31643167 result.jailbreak = power_user.prefer_character_jailbreak ? baseChatReplace(character.data?.post_history_instructions?.trim(), name1, name2) : '';
31653168 result.version = character.data?.character_version ?? '';
31663169 result.charDepthPrompt = baseChatReplace(character.data?.extensions?.depth_prompt?.prompt?.trim(), name1, name2);
3170+ result.creatorNotes = baseChatReplace(character.data?.creator_notes?.trim(), name1, name2);
31673171
31683172 if (selected_group) {
31693173 const groupCards = getGroupCharacterCards(selected_group, Number(currentChid));
@@ -3991,6 +3995,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
39913995 system,
39923996 jailbreak,
39933997 charDepthPrompt,
3998+ creatorNotes,
39943999 } = getCharacterCardFields();
39954000
39964001 if (main_api !== 'openai') {
@@ -4145,7 +4150,15 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
41454150 // Make quiet prompt available for WIAN
41464151 setExtensionPrompt('QUIET_PROMPT', quiet_prompt || '', extension_prompt_types.IN_PROMPT, 0, true);
41474152 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);
41494162 setExtensionPrompt('QUIET_PROMPT', '', extension_prompt_types.IN_PROMPT, 0, true);
41504163
41514164 // Add message example WI
public/scripts/char-data.js+6 -0
@@ -33,6 +33,12 @@
3333 * @property {number} role - The specific function or purpose of the extension.
3434 * @property {boolean} vectorized - Indicates if the extension is optimized for vectorized processing.
3535 * @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.
3642 */
3743
3844/**
public/scripts/world-info.js+90 -5
@@ -98,11 +98,28 @@ const KNOWN_DECORATORS = ['@@activate', '@@dont_activate'];
9898
9999// Typedef area
100100/**
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+/**
101112 * @typedef {object} WIScanEntry The entry that triggered the scan
102113 * @property {number} [scanDepth] The depth of the scan
103114 * @property {boolean} [caseSensitive] If the scan is case sensitive
104115 * @property {boolean} [matchWholeWords] If the scan should match whole words
105116 * @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
106123 * @property {number} [uid] The UID of the entry that triggered the scan
107124 * @property {string} [world] The world info book of origin of the entry
108125 * @property {string[]} [key] The primary keys to scan for
@@ -139,6 +156,11 @@ class WorldInfoBuffer {
139156 static externalActivations = new Map();
140157
141158 /**
159+ * @type {WIGlobalScanData} Chat independent data to be scanned, such as persona and character descriptions
160+ */
161+ #globalScanData = null;
162+
163+ /**
142164 * @type {string[]} Array of messages sorted by ascending depth
143165 */
144166 #depthBuffer = [];
@@ -166,9 +188,11 @@ class WorldInfoBuffer {
166188 /**
167189 * Initialize the buffer with the given messages.
168190 * @param {string[]} messages Array of messages to add to the buffer
191+ * @param {WIGlobalScanData} globalScanData Chat independent context to be scanned
169192 */
170193 constructor(messages, globalScanData) {
171194 this.#initDepthBuffer(messages);
195+ this.#globalScanData = globalScanData;
172196 }
173197
174198 /**
@@ -225,6 +249,25 @@ class WorldInfoBuffer {
225249 const JOINER = '\n' + MATCHER;
226250 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+
228271 if (this.#injectBuffer.length > 0) {
229272 result += JOINER + this.#injectBuffer.join(JOINER);
230273 }
@@ -756,6 +799,7 @@ export const worldInfoCache = new StructuredCloneMap({ cloneOnGet: true, cloneOn
756799 * @param {string[]} chat - The chat messages to scan, in reverse order.
757800 * @param {number} maxContext - The maximum context size of the generation.
758801 * @param {boolean} isDryRun - If true, the function will not emit any events.
802+ * @param {WIGlobalScanData} globalScanData Chat independent context to be scanned
759803 * @typedef {object} WIPromptResult
760804 * @property {string} worldInfoString - Complete world info string
761805 * @property {string} worldInfoBefore - World info that goes before the prompt
@@ -766,10 +810,10 @@ export const worldInfoCache = new StructuredCloneMap({ cloneOnGet: true, cloneOn
766810 * @property {Array} anAfter - Array of entries after Author's Note
767811 * @returns {Promise<WIPromptResult>} The world info string and depth.
768812 */
769813export async function getWorldInfoPrompt(chat, maxContext, isDryRun, globalScanData) {
770814 let worldInfoString = '', worldInfoBefore = '', worldInfoAfter = '';
771815
772816 const activatedWorldInfo = await checkWorldInfo(chat, maxContext, isDryRun, globalScanData);
773817 worldInfoBefore = activatedWorldInfo.worldInfoBefore;
774818 worldInfoAfter = activatedWorldInfo.worldInfoAfter;
775819 worldInfoString = worldInfoBefore + worldInfoAfter;
@@ -2191,6 +2235,12 @@ export const originalWIDataKeyMap = {
21912235 'matchWholeWords': 'extensions.match_whole_words',
21922236 'useGroupScoring': 'extensions.use_group_scoring',
21932237 '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',
21942244 'scanDepth': 'extensions.scan_depth',
21952245 'automationId': 'extensions.automation_id',
21962246 'vectorized': 'extensions.vectorized',
@@ -3308,6 +3358,28 @@ export async function getWorldEntry(name, data, entry) {
33083358 });
33093359 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+
33113383 // automation id
33123384 const automationIdInput = template.find('input[name="automationId"]');
33133385 automationIdInput.data('uid', entry.uid);
@@ -3514,6 +3586,12 @@ export const newWorldInfoEntryDefinition = {
35143586 disable: { default: false, type: 'boolean' },
35153587 excludeRecursion: { default: false, type: 'boolean' },
35163588 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' },
35173595 delayUntilRecursion: { default: 0, type: 'number' },
35183596 probability: { default: 100, type: 'number' },
35193597 useProbability: { default: true, type: 'boolean' },
@@ -3978,6 +4056,7 @@ function parseDecorators(content) {
39784056 * @param {string[]} chat The chat messages to scan, in reverse order.
39794057 * @param {number} maxContext The maximum context size of the generation.
39804058 * @param {boolean} isDryRun Whether to perform a dry run.
4059+ * @param {WIGlobalScanData} globalScanData Chat independent context to be scanned
39814060 * @typedef {object} WIActivated
39824061 * @property {string} worldInfoBefore The world info before the chat.
39834062 * @property {string} worldInfoAfter The world info after the chat.
@@ -3988,9 +4067,9 @@ function parseDecorators(content) {
39884067 * @property {Set<any>} allActivatedEntries All entries.
39894068 * @returns {Promise<WIActivated>} The world info activated.
39904069 */
39914070export async function checkWorldInfo(chat, maxContext, isDryRun, globalScanData) {
39924071 const context = getContext();
39934072 const buffer = new WorldInfoBuffer(chat, globalScanData);
39944073
39954074 console.debug(`[WI] --- START WI SCAN (on ${chat.length} messages)${isDryRun ? ' (DRY RUN)' : ''} ---`);
39964075
@@ -4848,6 +4927,12 @@ export function convertCharacterBook(characterBook) {
48484927 sticky: entry.extensions?.sticky ?? null,
48494928 cooldown: entry.extensions?.cooldown ?? null,
48504929 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,
48514936 extensions: entry.extensions ?? {},
48524937 };
48534938 });
src/endpoints/characters.js+6 -0
@@ -702,6 +702,12 @@ function convertWorldInfoToCharacterBook(name, entries) {
702702 sticky: entry.sticky ?? null,
703703 cooldown: entry.cooldown ?? null,
704704 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,
705711 },
706712 };
707713