Validate story string about missing fields (#2462) * Validate story string about missing fields * Update validation to only warn once * Improve story string validation log once --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
Signed| @@ -40,7 +40,7 @@ import { tokenizers } from './tokenizers.js'; | |||
| 40 | import { BIAS_CACHE } from './logit-bias.js'; | 40 | import { BIAS_CACHE } from './logit-bias.js'; |
| 41 | import { renderTemplateAsync } from './templates.js'; | 41 | import { renderTemplateAsync } from './templates.js'; |
| 42 | 42 | ||
| 43 | import { countOccurrences, debounce, delay, download, getFileText, isOdd, isTrueBoolean, onlyUnique, resetScrollHeight, shuffle, sortMoments, stringToRange, timestampToMoment } from './utils.js'; | 43 | import { countOccurrences, debounce, delay, download, getFileText, getStringHash, isOdd, isTrueBoolean, onlyUnique, resetScrollHeight, shuffle, sortMoments, stringToRange, timestampToMoment } from './utils.js'; |
| 44 | import { FILTER_TYPES } from './filters.js'; | 44 | import { FILTER_TYPES } from './filters.js'; |
| 45 | import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js'; | 45 | import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js'; |
| 46 | import { SlashCommand } from './slash-commands/SlashCommand.js'; | 46 | import { SlashCommand } from './slash-commands/SlashCommand.js'; |
| @@ -335,6 +335,8 @@ const storage_keys = { | |||
| 335 | compact_input_area: 'compact_input_area', | 335 | compact_input_area: 'compact_input_area', |
| 336 | auto_connect_legacy: 'AutoConnectEnabled', | 336 | auto_connect_legacy: 'AutoConnectEnabled', |
| 337 | auto_load_chat_legacy: 'AutoLoadChatEnabled', | 337 | auto_load_chat_legacy: 'AutoLoadChatEnabled', |
| 338 | |||
| 339 | storyStringValidationCache: 'StoryStringValidationCache', | ||
| 338 | }; | 340 | }; |
| 339 | 341 | ||
| 340 | const contextControls = [ | 342 | const contextControls = [ |
| @@ -2105,6 +2107,9 @@ export function fuzzySearchGroups(searchValue) { | |||
| 2105 | */ | 2107 | */ |
| 2106 | export function renderStoryString(params) { | 2108 | export function renderStoryString(params) { |
| 2107 | try { | 2109 | try { |
| 2110 | // Validate and log possible warnings/errors | ||
| 2111 | validateStoryString(power_user.context.story_string, params); | ||
| 2112 | |||
| 2108 | // compile the story string template into a function, with no HTML escaping | 2113 | // compile the story string template into a function, with no HTML escaping |
| 2109 | const compiledTemplate = Handlebars.compile(power_user.context.story_string, { noEscape: true }); | 2114 | const compiledTemplate = Handlebars.compile(power_user.context.story_string, { noEscape: true }); |
| 2110 | 2115 | ||
| @@ -2132,6 +2137,55 @@ export function renderStoryString(params) { | |||
| 2132 | } | 2137 | } |
| 2133 | } | 2138 | } |
| 2134 | 2139 | ||
| 2140 | /** | ||
| 2141 | * Validate the story string for possible warnings or issues | ||
| 2142 | * | ||
| 2143 | * @param {string} storyString - The story string | ||
| 2144 | * @param {Object} params - The story string parameters | ||
| 2145 | */ | ||
| 2146 | function validateStoryString(storyString, params) { | ||
| 2147 | /** @type {{hashCache: {[hash: string]: {fieldsWarned: {[key: string]: boolean}}}}} */ | ||
| 2148 | const cache = JSON.parse(localStorage.getItem(storage_keys.storyStringValidationCache)) ?? { hashCache: {} }; | ||
| 2149 | |||
| 2150 | const hash = getStringHash(storyString); | ||
| 2151 | |||
| 2152 | // Initialize the cache for the current hash if it doesn't exist | ||
| 2153 | if (!cache.hashCache[hash]) { | ||
| 2154 | cache.hashCache[hash] = { fieldsWarned: {} }; | ||
| 2155 | } | ||
| 2156 | |||
| 2157 | const currentCache = cache.hashCache[hash]; | ||
| 2158 | const fieldsToWarn = []; | ||
| 2159 | |||
| 2160 | function validateMissingField(field, fallbackLegacyField = null) { | ||
| 2161 | const contains = storyString.includes(`{{${field}}}`) || (!!fallbackLegacyField && storyString.includes(`{{${fallbackLegacyField}}}`)); | ||
| 2162 | if (!contains && params[field]) { | ||
| 2163 | const wasLogged = currentCache.fieldsWarned[field]; | ||
| 2164 | if (!wasLogged) { | ||
| 2165 | fieldsToWarn.push(field); | ||
| 2166 | currentCache.fieldsWarned[field] = true; | ||
| 2167 | } | ||
| 2168 | console.warn(`The story string does not contain {{${field}}}, but it would contain content:\n`, params[field]); | ||
| 2169 | } | ||
| 2170 | } | ||
| 2171 | |||
| 2172 | validateMissingField('description'); | ||
| 2173 | validateMissingField('personality'); | ||
| 2174 | validateMissingField('persona'); | ||
| 2175 | validateMissingField('scenario'); | ||
| 2176 | validateMissingField('system'); | ||
| 2177 | validateMissingField('wiBefore', 'loreBefore'); | ||
| 2178 | validateMissingField('wiAfter', 'loreAfter'); | ||
| 2179 | |||
| 2180 | if (fieldsToWarn.length > 0) { | ||
| 2181 | const fieldsList = fieldsToWarn.map(field => `{{${field}}}`).join(', '); | ||
| 2182 | toastr.warning(`The story string does not contain the following fields, but they would contain content: ${fieldsList}`, 'Story String Validation'); | ||
| 2183 | } | ||
| 2184 | |||
| 2185 | localStorage.setItem(storage_keys.storyStringValidationCache, JSON.stringify(cache)); | ||
| 2186 | } | ||
| 2187 | |||
| 2188 | |||
| 2135 | const sortFunc = (a, b) => power_user.sort_order == 'asc' ? compareFunc(a, b) : compareFunc(b, a); | 2189 | const sortFunc = (a, b) => power_user.sort_order == 'asc' ? compareFunc(a, b) : compareFunc(b, a); |
| 2136 | const compareFunc = (first, second) => { | 2190 | const compareFunc = (first, second) => { |
| 2137 | const a = first[power_user.sort_field]; | 2191 | const a = first[power_user.sort_field]; |