Merge branch 'staging' into or-prompt-post-processing

9b76b6dd3c6f07b367ee50fb6a543105a414e4c4

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

6 files changed, +97 -34Ignore whitespace
.github/workflows/pr-auto-manager.yml+10 -2
@@ -13,9 +13,11 @@ permissions:
13jobs:13jobs:
14 label-by-size:14 label-by-size:
15 name: 🏷️ Label PR by Size15 name: 🏷️ Label PR by Size
16 # This job should run after all others, to prevent possible concurrency issues
17 needs: [label-by-branches, label-by-files, remove-stale-label, check-merge-blocking-labels, write-auto-comments]
16 runs-on: ubuntu-latest18 runs-on: ubuntu-latest
17 # Only needs to run when code is changed19 # Only needs to run when code is changed
18 if: github.event.action == 'opened' || github.event.action == 'synchronize'20 if: always() && (github.event.action == 'opened' || github.event.action == 'synchronize')
1921
20 # Override permissions, the labeler needs issues write access22 # Override permissions, the labeler needs issues write access
21 permissions:23 permissions:
@@ -159,7 +161,7 @@ jobs:
159161
160 write-auto-comments:162 write-auto-comments:
161 name: 💬 Post PR Comments Based on Labels163 name: 💬 Post PR Comments Based on Labels
162 needs: [label-by-size, label-by-branches, label-by-files]164 needs: [label-by-branches, label-by-files]
163 runs-on: ubuntu-latest165 runs-on: ubuntu-latest
164 # Run, even if the previous jobs were skipped/failed166 # Run, even if the previous jobs were skipped/failed
165 if: always()167 if: always()
@@ -184,6 +186,12 @@ jobs:
184 runs-on: ubuntu-latest186 runs-on: ubuntu-latest
185 if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'staging'187 if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'staging'
186188
189 # Override permissions, We need to be able to write to issues
190 permissions:
191 contents: read
192 issues: write
193 pull-requests: write
194
187 steps:195 steps:
188 - name: Extract Linked Issues From PR Description196 - name: Extract Linked Issues From PR Description
189 id: extract_issues197 id: extract_issues
public/script.js+10 -3
@@ -495,6 +495,8 @@ export const event_types = {
495 GENERATE_AFTER_COMBINE_PROMPTS: 'generate_after_combine_prompts',495 GENERATE_AFTER_COMBINE_PROMPTS: 'generate_after_combine_prompts',
496 GENERATE_AFTER_DATA: 'generate_after_data',496 GENERATE_AFTER_DATA: 'generate_after_data',
497 GROUP_MEMBER_DRAFTED: 'group_member_drafted',497 GROUP_MEMBER_DRAFTED: 'group_member_drafted',
498 GROUP_WRAPPER_STARTED: 'group_wrapper_started',
499 GROUP_WRAPPER_FINISHED: 'group_wrapper_finished',
498 WORLD_INFO_ACTIVATED: 'world_info_activated',500 WORLD_INFO_ACTIVATED: 'world_info_activated',
499 TEXT_COMPLETION_SETTINGS_READY: 'text_completion_settings_ready',501 TEXT_COMPLETION_SETTINGS_READY: 'text_completion_settings_ready',
500 CHAT_COMPLETION_SETTINGS_READY: 'chat_completion_settings_ready',502 CHAT_COMPLETION_SETTINGS_READY: 'chat_completion_settings_ready',
@@ -3098,6 +3100,9 @@ export function baseChatReplace(value, name1, name2) {
30983100
3099/**3101/**
3100 * Returns the character card fields for the current character.3102 * Returns the character card fields for the current character.
3103 * @param {object} [options]
3104 * @param {number} [options.chid] Optional character index
3105 *
3101 * @typedef {object} CharacterCardFields3106 * @typedef {object} CharacterCardFields
3102 * @property {string} system System prompt3107 * @property {string} system System prompt
3103 * @property {string} mesExamples Message examples3108 * @property {string} mesExamples Message examples
@@ -3110,7 +3115,9 @@ export function baseChatReplace(value, name1, name2) {
3110 * @property {string} charDepthPrompt Character depth note3115 * @property {string} charDepthPrompt Character depth note
3111 * @returns {CharacterCardFields} Character card fields3116 * @returns {CharacterCardFields} Character card fields
3112 */3117 */
3113export function getCharacterCardFields() {3118export function getCharacterCardFields({ chid = null } = {}) {
3119 const currentChid = chid ?? this_chid;
3120
3114 const result = {3121 const result = {
3115 system: '',3122 system: '',
3116 mesExamples: '',3123 mesExamples: '',
@@ -3124,7 +3131,7 @@ export function getCharacterCardFields() {
3124 };3131 };
3125 result.persona = baseChatReplace(power_user.persona_description?.trim(), name1, name2);3132 result.persona = baseChatReplace(power_user.persona_description?.trim(), name1, name2);
31263133
3127 const character = characters[this_chid];3134 const character = characters[currentChid];
31283135
3129 if (!character) {3136 if (!character) {
3130 return result;3137 return result;
@@ -3141,7 +3148,7 @@ export function getCharacterCardFields() {
3141 result.charDepthPrompt = baseChatReplace(character.data?.extensions?.depth_prompt?.prompt?.trim(), name1, name2);3148 result.charDepthPrompt = baseChatReplace(character.data?.extensions?.depth_prompt?.prompt?.trim(), name1, name2);
31423149
3143 if (selected_group) {3150 if (selected_group) {
3144 const groupCards = getGroupCharacterCards(selected_group, Number(this_chid));3151 const groupCards = getGroupCharacterCards(selected_group, Number(currentChid));
31453152
3146 if (groupCards) {3153 if (groupCards) {
3147 result.description = groupCards.description;3154 result.description = groupCards.description;
public/scripts/extensions/expressions/index.js+58 -16
@@ -4,7 +4,7 @@ import { characters, eventSource, event_types, generateRaw, getRequestHeaders, m
4import { dragElement, isMobile } from '../../RossAscends-mods.js';4import { dragElement, isMobile } from '../../RossAscends-mods.js';
5import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js';5import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js';
6import { loadMovingUIState, performFuzzySearch, power_user } from '../../power-user.js';6import { loadMovingUIState, performFuzzySearch, power_user } from '../../power-user.js';
7import { onlyUnique, debounce, getCharaFilename, trimToEndSentence, trimToStartSentence, waitUntilCondition, findChar } from '../../utils.js';7import { onlyUnique, debounce, getCharaFilename, trimToEndSentence, trimToStartSentence, waitUntilCondition, findChar, isFalseBoolean } from '../../utils.js';
8import { hideMutedSprites, selected_group } from '../../group-chats.js';8import { hideMutedSprites, selected_group } from '../../group-chats.js';
9import { isJsonSchemaSupported } from '../../textgen-settings.js';9import { isJsonSchemaSupported } from '../../textgen-settings.js';
10import { debounce_timeout } from '../../constants.js';10import { debounce_timeout } from '../../constants.js';
@@ -17,6 +17,7 @@ import { slashCommandReturnHelper } from '../../slash-commands/SlashCommandRetur
17import { generateWebLlmChatPrompt, isWebLlmSupported } from '../shared.js';17import { generateWebLlmChatPrompt, isWebLlmSupported } from '../shared.js';
18import { Popup, POPUP_RESULT } from '../../popup.js';18import { Popup, POPUP_RESULT } from '../../popup.js';
19import { t } from '../../i18n.js';19import { t } from '../../i18n.js';
20import { removeReasoningFromString } from '../../reasoning.js';
20export { MODULE_NAME };21export { MODULE_NAME };
2122
22/**23/**
@@ -678,7 +679,7 @@ async function setSpriteFolderCommand(_, folder) {
678 return '';679 return '';
679}680}
680681
681async function classifyCallback(/** @type {{api: string?, prompt: string?}} */ { api = null, prompt = null }, text) {682async function classifyCallback(/** @type {{api: string?, filter: string?, prompt: string?}} */ { api = null, filter = null, prompt = null }, text) {
682 if (!text) {683 if (!text) {
683 toastr.error('No text provided');684 toastr.error('No text provided');
684 return '';685 return '';
@@ -689,13 +690,14 @@ async function classifyCallback(/** @type {{api: string?, prompt: string?}} */ {
689 }690 }
690691
691 const expressionApi = EXPRESSION_API[api] || extension_settings.expressions.api;692 const expressionApi = EXPRESSION_API[api] || extension_settings.expressions.api;
693 const filterAvailable = !isFalseBoolean(filter);
692694
693 if (!modules.includes('classify') && expressionApi == EXPRESSION_API.extras) {695 if (!modules.includes('classify') && expressionApi == EXPRESSION_API.extras) {
694 toastr.warning('Text classification is disabled or not available');696 toastr.warning('Text classification is disabled or not available');
695 return '';697 return '';
696 }698 }
697699
698 const label = await getExpressionLabel(text, expressionApi, { customPrompt: prompt });700 const label = await getExpressionLabel(text, expressionApi, { filterAvailable: filterAvailable, customPrompt: prompt });
699 console.debug(`Classification result for "${text}": ${label}`);701 console.debug(`Classification result for "${text}": ${label}`);
700 return label;702 return label;
701}703}
@@ -928,6 +930,9 @@ function parseLlmResponse(emotionResponse, labels) {
928930
929 return response;931 return response;
930 } catch {932 } catch {
933 // Clean possible reasoning from response
934 emotionResponse = removeReasoningFromString(emotionResponse);
935
931 const fuse = new Fuse(labels, { includeScore: true });936 const fuse = new Fuse(labels, { includeScore: true });
932 console.debug('Using fuzzy search in labels:', labels);937 console.debug('Using fuzzy search in labels:', labels);
933 const result = fuse.search(emotionResponse);938 const result = fuse.search(emotionResponse);
@@ -988,10 +993,11 @@ function onTextGenSettingsReady(args) {
988 * @param {string} text - The text to classify and retrieve the expression label for.993 * @param {string} text - The text to classify and retrieve the expression label for.
989 * @param {EXPRESSION_API} [expressionsApi=extension_settings.expressions.api] - The expressions API to use for classification.994 * @param {EXPRESSION_API} [expressionsApi=extension_settings.expressions.api] - The expressions API to use for classification.
990 * @param {object} [options={}] - Optional arguments.995 * @param {object} [options={}] - Optional arguments.
996 * @param {boolean?} [options.filterAvailable=null] - Whether to filter available expressions. If not specified, uses the extension setting.
991 * @param {string?} [options.customPrompt=null] - The custom prompt to use for classification.997 * @param {string?} [options.customPrompt=null] - The custom prompt to use for classification.
992 * @returns {Promise<string?>} - The label of the expression.998 * @returns {Promise<string?>} - The label of the expression.
993 */999 */
994export async function getExpressionLabel(text, expressionsApi = extension_settings.expressions.api, { customPrompt = null } = {}) {1000export async function getExpressionLabel(text, expressionsApi = extension_settings.expressions.api, { filterAvailable = null, customPrompt = null } = {}) {
995 // Return if text is undefined, saving a costly fetch request1001 // Return if text is undefined, saving a costly fetch request
996 if ((!modules.includes('classify') && expressionsApi == EXPRESSION_API.extras) || !text) {1002 if ((!modules.includes('classify') && expressionsApi == EXPRESSION_API.extras) || !text) {
997 return extension_settings.expressions.fallback_expression;1003 return extension_settings.expressions.fallback_expression;
@@ -1003,6 +1009,11 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin
10031009
1004 text = sampleClassifyText(text);1010 text = sampleClassifyText(text);
10051011
1012 filterAvailable ??= extension_settings.expressions.filterAvailable;
1013 if (filterAvailable && ![EXPRESSION_API.llm, EXPRESSION_API.webllm].includes(expressionsApi)) {
1014 console.debug('Filter available is only supported for LLM and WebLLM expressions');
1015 }
1016
1006 try {1017 try {
1007 switch (expressionsApi) {1018 switch (expressionsApi) {
1008 // Local BERT pipeline1019 // Local BERT pipeline
@@ -1027,7 +1038,7 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin
1027 return extension_settings.expressions.fallback_expression;1038 return extension_settings.expressions.fallback_expression;
1028 }1039 }
10291040
1030 const expressionsList = await getExpressionsList();1041 const expressionsList = await getExpressionsList({ filterAvailable: filterAvailable });
1031 const prompt = substituteParamsExtended(customPrompt, { labels: expressionsList }) || await getLlmPrompt(expressionsList);1042 const prompt = substituteParamsExtended(customPrompt, { labels: expressionsList }) || await getLlmPrompt(expressionsList);
1032 eventSource.once(event_types.TEXT_COMPLETION_SETTINGS_READY, onTextGenSettingsReady);1043 eventSource.once(event_types.TEXT_COMPLETION_SETTINGS_READY, onTextGenSettingsReady);
1033 const emotionResponse = await generateRaw(text, main_api, false, false, prompt);1044 const emotionResponse = await generateRaw(text, main_api, false, false, prompt);
@@ -1040,7 +1051,7 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin
1040 return extension_settings.expressions.fallback_expression;1051 return extension_settings.expressions.fallback_expression;
1041 }1052 }
10421053
1043 const expressionsList = await getExpressionsList();1054 const expressionsList = await getExpressionsList({ filterAvailable: filterAvailable });
1044 const prompt = substituteParamsExtended(customPrompt, { labels: expressionsList }) || await getLlmPrompt(expressionsList);1055 const prompt = substituteParamsExtended(customPrompt, { labels: expressionsList }) || await getLlmPrompt(expressionsList);
1045 const messages = [1056 const messages = [
1046 { role: 'user', content: text + '\n\n' + prompt },1057 { role: 'user', content: text + '\n\n' + prompt },
@@ -1320,12 +1331,28 @@ function getCachedExpressions() {
1320 return [...expressionsList, ...extension_settings.expressions.custom].filter(onlyUnique);1331 return [...expressionsList, ...extension_settings.expressions.custom].filter(onlyUnique);
1321}1332}
13221333
1323export async function getExpressionsList() {1334export async function getExpressionsList({ filterAvailable = false } = {}) {
1324 // Return cached list if available1335 // If there is no cached list, load and cache it
1325 if (Array.isArray(expressionsList)) {1336 if (!Array.isArray(expressionsList)) {
1326 return getCachedExpressions();1337 expressionsList = await resolveExpressionsList();
1338 }
1339
1340 const expressions = getCachedExpressions();
1341
1342 // Filtering is only available for llm and webllm APIs
1343 if (!filterAvailable || ![EXPRESSION_API.llm, EXPRESSION_API.webllm].includes(extension_settings.expressions.api)) {
1344 return expressions;
1327 }1345 }
13281346
1347 // Get expressions with available sprites
1348 const currentLastMessage = selected_group ? getLastCharacterMessage() : null;
1349 const spriteFolderName = getSpriteFolderName(currentLastMessage, currentLastMessage?.name);
1350
1351 return expressions.filter(label => {
1352 const expression = spriteCache[spriteFolderName]?.find(x => x.label === label);
1353 return (expression?.files.length ?? 0) > 0;
1354 });
1355
1329 /**1356 /**
1330 * Returns the list of expressions from the API or fallback in offline mode.1357 * Returns the list of expressions from the API or fallback in offline mode.
1331 * @returns {Promise<string[]>}1358 * @returns {Promise<string[]>}
@@ -1372,9 +1399,6 @@ export async function getExpressionsList() {
1372 expressionsList = DEFAULT_EXPRESSIONS.slice();1399 expressionsList = DEFAULT_EXPRESSIONS.slice();
1373 return expressionsList;1400 return expressionsList;
1374 }1401 }
1375
1376 const result = await resolveExpressionsList();
1377 return [...result, ...extension_settings.expressions.custom].filter(onlyUnique);
1378}1402}
13791403
1380/**1404/**
@@ -1810,7 +1834,7 @@ async function onClickExpressionUpload(event) {
1810 }1834 }
1811 }1835 }
1812 } else {1836 } else {
1813 spriteName = withoutExtension(clickedFileName);1837 spriteName = withoutExtension(expression);
1814 }1838 }
18151839
1816 if (!spriteName) {1840 if (!spriteName) {
@@ -2102,6 +2126,10 @@ function migrateSettings() {
2102 extension_settings.expressions.rerollIfSame = !!$(this).prop('checked');2126 extension_settings.expressions.rerollIfSame = !!$(this).prop('checked');
2103 saveSettingsDebounced();2127 saveSettingsDebounced();
2104 });2128 });
2129 $('#expressions_filter_available').prop('checked', extension_settings.expressions.filterAvailable).on('input', function () {
2130 extension_settings.expressions.filterAvailable = !!$(this).prop('checked');
2131 saveSettingsDebounced();
2132 });
2105 $('#expression_override_cleanup_button').on('click', onClickExpressionOverrideRemoveAllButton);2133 $('#expression_override_cleanup_button').on('click', onClickExpressionOverrideRemoveAllButton);
2106 $(document).on('dragstart', '.expression', (e) => {2134 $(document).on('dragstart', '.expression', (e) => {
2107 e.preventDefault();2135 e.preventDefault();
@@ -2279,13 +2307,13 @@ function migrateSettings() {
2279 SlashCommandParser.addCommandObject(SlashCommand.fromProps({2307 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
2280 name: 'expression-list',2308 name: 'expression-list',
2281 aliases: ['expressions'],2309 aliases: ['expressions'],
2282 /** @type {(args: {return: string}) => Promise<string>} */2310 /** @type {(args: {return: string, filter: string}) => Promise<string>} */
2283 callback: async (args) => {2311 callback: async (args) => {
2284 let returnType =2312 let returnType =
2285 /** @type {import('../../slash-commands/SlashCommandReturnHelper.js').SlashCommandReturnType} */2313 /** @type {import('../../slash-commands/SlashCommandReturnHelper.js').SlashCommandReturnType} */
2286 (args.return);2314 (args.return);
22872315
2288 const list = await getExpressionsList();2316 const list = await getExpressionsList({ filterAvailable: !isFalseBoolean(args.filter) });
22892317
2290 return await slashCommandReturnHelper.doReturn(returnType ?? 'pipe', list, { objectToStringFunc: list => list.join(', ') });2318 return await slashCommandReturnHelper.doReturn(returnType ?? 'pipe', list, { objectToStringFunc: list => list.join(', ') });
2291 },2319 },
@@ -2298,6 +2326,13 @@ function migrateSettings() {
2298 enumList: slashCommandReturnHelper.enumList({ allowObject: true }),2326 enumList: slashCommandReturnHelper.enumList({ allowObject: true }),
2299 forceEnum: true,2327 forceEnum: true,
2300 }),2328 }),
2329 SlashCommandNamedArgument.fromProps({
2330 name: 'filter',
2331 description: 'Filter the list to only include expressions that have available sprites for the current character.',
2332 typeList: [ARGUMENT_TYPE.BOOLEAN],
2333 enumList: commonEnumProviders.boolean('trueFalse')(),
2334 defaultValue: 'true',
2335 }),
2301 ],2336 ],
2302 returns: 'The comma-separated list of available expressions, including custom expressions.',2337 returns: 'The comma-separated list of available expressions, including custom expressions.',
2303 helpString: 'Returns a list of available expressions, including custom expressions.',2338 helpString: 'Returns a list of available expressions, including custom expressions.',
@@ -2314,6 +2349,13 @@ function migrateSettings() {
2314 enumList: Object.keys(EXPRESSION_API).map(api => new SlashCommandEnumValue(api, null, enumTypes.enum)),2349 enumList: Object.keys(EXPRESSION_API).map(api => new SlashCommandEnumValue(api, null, enumTypes.enum)),
2315 }),2350 }),
2316 SlashCommandNamedArgument.fromProps({2351 SlashCommandNamedArgument.fromProps({
2352 name: 'filter',
2353 description: 'Filter the list to only include expressions that have available sprites for the current character.',
2354 typeList: [ARGUMENT_TYPE.BOOLEAN],
2355 enumList: commonEnumProviders.boolean('trueFalse')(),
2356 defaultValue: 'true',
2357 }),
2358 SlashCommandNamedArgument.fromProps({
2317 name: 'prompt',2359 name: 'prompt',
2318 description: 'Custom prompt for classification. Only relevant if Classifier API is set to LLM.',2360 description: 'Custom prompt for classification. Only relevant if Classifier API is set to LLM.',
2319 typeList: [ARGUMENT_TYPE.STRING],2361 typeList: [ARGUMENT_TYPE.STRING],
public/scripts/extensions/expressions/settings.html+5 -1
@@ -29,7 +29,11 @@
29 </select>29 </select>
30 </div>30 </div>
31 <div class="expression_llm_prompt_block m-b-1 m-t-1">31 <div class="expression_llm_prompt_block m-b-1 m-t-1">
32 <label for="expression_llm_prompt" class="title_restorable">32 <label class="checkbox_label" for="expressions_filter_available" title="When using LLM or WebLLM classifier, only show and use expressions that have sprites assigned to them." data-i18n="[title]When using LLM or WebLLM classifier, only show and use expressions that have sprites assigned to them.">
33 <input id="expressions_filter_available" type="checkbox">
34 <span data-i18n="Filter expressions for available sprites">Filter expressions for available sprites</span>
35 </label>
36 <label for="expression_llm_prompt" class="title_restorable m-t-1">
33 <span data-i18n="LLM Prompt">LLM Prompt</span>37 <span data-i18n="LLM Prompt">LLM Prompt</span>
34 <div id="expression_llm_prompt_restore" title="Restore default value" class="right_menu_button">38 <div id="expression_llm_prompt_restore" title="Restore default value" class="right_menu_button">
35 <i class="fa-solid fa-clock-rotate-left fa-sm"></i>39 <i class="fa-solid fa-clock-rotate-left fa-sm"></i>
public/scripts/group-chats.js+10 -7
@@ -435,16 +435,18 @@ export function getGroupCharacterCards(groupId, characterId) {
435 * @param {string} value Value to replace435 * @param {string} value Value to replace
436 * @param {string} fieldName Name of the field436 * @param {string} fieldName Name of the field
437 * @param {string} characterName Name of the character437 * @param {string} characterName Name of the character
438 * @param {boolean} trim Whether to trim the value
438 * @returns {string} Replaced text439 * @returns {string} Replaced text
439 * */440 * */
440 function customBaseChatReplace(value, fieldName, characterName) {441 function customBaseChatReplace(value, fieldName, characterName, trim) {
441 if (!value) {442 if (!value) {
442 return '';443 return '';
443 }444 }
444445
445 // We should do the custom field name replacement first, and then run it through the normal macro engine with provided names446 // We should do the custom field name replacement first, and then run it through the normal macro engine with provided names
446 value = value.replace(/<FIELDNAME>/gi, fieldName);447 value = value.replace(/<FIELDNAME>/gi, fieldName);
447 return baseChatReplace(value.trim(), name1, characterName);448 value = trim ? value.trim() : value;
449 return baseChatReplace(value, name1, characterName);
448 }450 }
449451
450 /**452 /**
@@ -467,13 +469,12 @@ export function getGroupCharacterCards(groupId, characterId) {
467 }469 }
468470
469 // Prepare and replace prefixes471 // Prepare and replace prefixes
470 const prefix = customBaseChatReplace(group.generation_mode_join_prefix, fieldName, characterName);472 const prefix = customBaseChatReplace(group.generation_mode_join_prefix, fieldName, characterName, false);
471 const suffix = customBaseChatReplace(group.generation_mode_join_suffix, fieldName, characterName);473 const suffix = customBaseChatReplace(group.generation_mode_join_suffix, fieldName, characterName, false);
472 const separator = power_user.instruct.wrap ? '\n' : '';
473 // Also run the macro replacement on the actual content474 // Also run the macro replacement on the actual content
474 value = customBaseChatReplace(value, fieldName, characterName);475 value = customBaseChatReplace(value, fieldName, characterName, true);
475476
476 return `${prefix ? prefix + separator : ''}${value}${suffix ? separator + suffix : ''}`;477 return `${prefix}${value}${suffix}`;
477 }478 }
478479
479 const scenarioOverride = chat_metadata['scenario'];480 const scenarioOverride = chat_metadata['scenario'];
@@ -904,6 +905,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
904 groupChatQueueOrder.set(characters[activatedMembers[i]].avatar, i + 1);905 groupChatQueueOrder.set(characters[activatedMembers[i]].avatar, i + 1);
905 }906 }
906 }907 }
908 await eventSource.emit(event_types.GROUP_WRAPPER_STARTED, { selected_group, type });
907 // now the real generation begins: cycle through every activated character909 // now the real generation begins: cycle through every activated character
908 for (const chId of activatedMembers) {910 for (const chId of activatedMembers) {
909 throwIfAborted();911 throwIfAborted();
@@ -942,6 +944,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
942 setCharacterName('');944 setCharacterName('');
943 activateSendButtons();945 activateSendButtons();
944 showSwipeButtons();946 showSwipeButtons();
947 await eventSource.emit(event_types.GROUP_WRAPPER_FINISHED, { selected_group, type });
945 }948 }
946949
947 return Promise.resolve(textResult);950 return Promise.resolve(textResult);
src/endpoints/chats.js+4 -5
@@ -783,12 +783,11 @@ router.post('/search', validateAvatarUrlMiddleware, function (request, response)
783 continue;783 continue;
784 }784 }
785785
786 // Search through messages786 // Search through title and messages of the chat
787 const fragments = query.trim().toLowerCase().split(/\s+/).filter(x => x);787 const fragments = query.trim().toLowerCase().split(/\s+/).filter(x => x);
788 const hasMatch = messages.some(message => {788 const text = [chatFile.path.split(/[\\/]/).pop().replace(/.jsonl$/, ''),
789 const text = message?.mes?.toLowerCase();789 ...messages.map(message => message?.mes)].join('\n').toLowerCase();
790 return text && fragments.every(fragment => text.includes(fragment));790 const hasMatch = fragments.every(fragment => text.includes(fragment));
791 });
792791
793 if (hasMatch) {792 if (hasMatch) {
794 results.push({793 results.push({