feat: Send Quick Reply Before Message Generation (#4407) * feat: Send Quick Reply Before Message Generation * fix: Use correct event and check for dry runs on before-generation * Replace icon * Skip for events in groups outside of group wrapper * Compatibility with API and slash commands --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
Signed| @@ -202,6 +202,7 @@ export class QuickReplyApi { | ||
| 202 | 202 | * @param {boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded |
| 203 | 203 | * @param {boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected |
| 204 | 204 | * @param {boolean} [props.executeOnNewChat] whether to execute the quick reply when a new chat is created |
| 205 | + * @param {boolean} [props.executeBeforeGeneration] whether to execute the quick reply before message generation | |
| 205 | 206 | * @param {string} [props.automationId] when not empty, the quick reply will be executed when the WI with the given automation ID is activated |
| 206 | 207 | * @returns {QuickReply} the new quick reply |
| 207 | 208 | */ |
| @@ -217,6 +218,7 @@ export class QuickReplyApi { | ||
| 217 | 218 | executeOnChatChange, |
| 218 | 219 | executeOnGroupMemberDraft, |
| 219 | 220 | executeOnNewChat, |
| 221 | + executeBeforeGeneration, | |
| 220 | 222 | automationId, |
| 221 | 223 | } = {}) { |
| 222 | 224 | const set = this.getSetByName(setName); |
| @@ -236,6 +238,7 @@ export class QuickReplyApi { | ||
| 236 | 238 | qr.executeOnChatChange = executeOnChatChange ?? false; |
| 237 | 239 | qr.executeOnGroupMemberDraft = executeOnGroupMemberDraft ?? false; |
| 238 | 240 | qr.executeOnNewChat = executeOnNewChat ?? false; |
| 241 | + qr.executeBeforeGeneration = executeBeforeGeneration ?? false; | |
| 239 | 242 | qr.automationId = automationId ?? ''; |
| 240 | 243 | qr.onUpdate(); |
| 241 | 244 | return qr; |
| @@ -259,6 +262,7 @@ export class QuickReplyApi { | ||
| 259 | 262 | * @param {boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded |
| 260 | 263 | * @param {boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected |
| 261 | 264 | * @param {boolean} [props.executeOnNewChat] whether to execute the quick reply when a new chat is created |
| 265 | + * @param {boolean} [props.executeBeforeGeneration] whether to execute the quick reply before message generation | |
| 262 | 266 | * @param {string} [props.automationId] when not empty, the quick reply will be executed when the WI with the given automation ID is activated |
| 263 | 267 | * @returns {QuickReply} the altered quick reply |
| 264 | 268 | */ |
| @@ -275,6 +279,7 @@ export class QuickReplyApi { | ||
| 275 | 279 | executeOnChatChange, |
| 276 | 280 | executeOnGroupMemberDraft, |
| 277 | 281 | executeOnNewChat, |
| 282 | + executeBeforeGeneration, | |
| 278 | 283 | automationId, |
| 279 | 284 | } = {}) { |
| 280 | 285 | const qr = this.getQrByLabel(setName, label); |
| @@ -293,6 +298,7 @@ export class QuickReplyApi { | ||
| 293 | 298 | qr.executeOnChatChange = executeOnChatChange ?? qr.executeOnChatChange; |
| 294 | 299 | qr.executeOnGroupMemberDraft = executeOnGroupMemberDraft ?? qr.executeOnGroupMemberDraft; |
| 295 | 300 | qr.executeOnNewChat = executeOnNewChat ?? qr.executeOnNewChat; |
| 301 | + qr.executeBeforeGeneration = executeBeforeGeneration ?? qr.executeBeforeGeneration; | |
| 296 | 302 | qr.automationId = automationId ?? qr.automationId; |
| 297 | 303 | qr.onUpdate(); |
| 298 | 304 | return qr; |
| @@ -116,6 +116,10 @@ | ||
| 116 | 116 | <input type="checkbox" id="qr--executeOnGroupMemberDraft"> |
| 117 | 117 | <span><i class="fa-solid fa-fw fa-people-group"></i><span data-i18n="Execute on group member draft">Execute on group member draft</span></span> |
| 118 | 118 | </label> |
| 119 | + <label class="checkbox_label"> | |
| 120 | + <input type="checkbox" id="qr--executeBeforeGeneration" > | |
| 121 | + <span><i class="fa-solid fa-fw fa-paper-plane"></i><span data-i18n="Execute before message generation">Execute before message generation</span></span> | |
| 122 | + </label> | |
| 119 | 123 | <div class="flex-container alignItemsBaseline flexFlowColumn flexNoGap" title="Activate this quick reply when a World Info entry with the same Automation ID is triggered."> |
| 120 | 124 | <small data-i18n="Automation ID:">Automation ID</small> |
| 121 | 125 | <input type="text" id="qr--automationId" class="text_pole flex1" placeholder="( None )"> |
| @@ -85,6 +85,7 @@ const loadSets = async () => { | ||
| 85 | 85 | qr.executeOnChatChange = slot.autoExecute_chatLoad ?? false; |
| 86 | 86 | qr.executeOnGroupMemberDraft = slot.autoExecute_groupMemberDraft ?? false; |
| 87 | 87 | qr.executeOnNewChat = slot.autoExecute_newChat ?? false; |
| 88 | + qr.executeBeforeGeneration = slot.autoExecute_beforeGeneration ?? false; | |
| 88 | 89 | qr.automationId = slot.automationId ?? ''; |
| 89 | 90 | qr.contextList = (slot.contextMenu ?? []).map(it=>({ |
| 90 | 91 | set: it.preset, |
| @@ -307,3 +308,16 @@ const onNewChat = async () => { | ||
| 307 | 308 | await autoExec.handleNewChat(); |
| 308 | 309 | }; |
| 309 | 310 | eventSource.on(event_types.CHAT_CREATED, (...args) => executeIfReadyElseQueue(onNewChat, args)); |
| 311 | + | |
| 312 | +const onBeforeGeneration = async (_generationType, _options = {}, isDryRun = false) => { | |
| 313 | + if (isDryRun) { | |
| 314 | + log('Before-generation hook skipped due to dryRun.'); | |
| 315 | + return; | |
| 316 | + } | |
| 317 | + if (selected_group && this_chid === undefined) { | |
| 318 | + log('Before-generation hook skipped for event before group wrapper.'); | |
| 319 | + return; | |
| 320 | + } | |
| 321 | + await autoExec.handleBeforeGeneration(); | |
| 322 | +}; | |
| 323 | +eventSource.on(event_types.GENERATION_AFTER_COMMANDS, (...args) => executeIfReadyElseQueue(onBeforeGeneration, args)); | |
| @@ -78,6 +78,11 @@ export class AutoExecuteHandler { | ||
| 78 | 78 | await this.performAutoExecute(this.getCommands('executeOnNewChat')); |
| 79 | 79 | } |
| 80 | 80 | |
| 81 | + async handleBeforeGeneration() { | |
| 82 | + if (!this.checkExecute()) return; | |
| 83 | + await this.performAutoExecute(this.getCommands('executeBeforeGeneration')); | |
| 84 | + } | |
| 85 | + | |
| 81 | 86 | /** |
| 82 | 87 | * @param {any[]} entries Set of activated entries |
| 83 | 88 | */ |
| @@ -46,6 +46,7 @@ export class QuickReply { | ||
| 46 | 46 | /**@type {boolean}*/ executeOnChatChange = false; |
| 47 | 47 | /**@type {boolean}*/ executeOnGroupMemberDraft = false; |
| 48 | 48 | /**@type {boolean}*/ executeOnNewChat = false; |
| 49 | + /**@type {boolean}*/ executeBeforeGeneration = false; | |
| 49 | 50 | /**@type {string}*/ automationId = ''; |
| 50 | 51 | |
| 51 | 52 | /**@type {function}*/ onExecute; |
| @@ -1065,6 +1066,13 @@ export class QuickReply { | ||
| 1065 | 1066 | this.updateContext(); |
| 1066 | 1067 | }); |
| 1067 | 1068 | /**@type {HTMLInputElement}*/ |
| 1069 | + const executeBeforeGeneration = dom.querySelector('#qr--executeBeforeGeneration'); | |
| 1070 | + executeBeforeGeneration.checked = this.executeBeforeGeneration; | |
| 1071 | + executeBeforeGeneration.addEventListener('click', ()=>{ | |
| 1072 | + this.executeBeforeGeneration = executeBeforeGeneration.checked; | |
| 1073 | + this.updateContext(); | |
| 1074 | + }); | |
| 1075 | + /**@type {HTMLInputElement}*/ | |
| 1068 | 1076 | const executeOnNewChat = dom.querySelector('#qr--executeOnNewChat'); |
| 1069 | 1077 | executeOnNewChat.checked = this.executeOnNewChat; |
| 1070 | 1078 | executeOnNewChat.addEventListener('click', ()=>{ |
| @@ -1919,6 +1927,7 @@ export class QuickReply { | ||
| 1919 | 1927 | executeOnChatChange: this.executeOnChatChange, |
| 1920 | 1928 | executeOnGroupMemberDraft: this.executeOnGroupMemberDraft, |
| 1921 | 1929 | executeOnNewChat: this.executeOnNewChat, |
| 1930 | + executeBeforeGeneration: this.executeBeforeGeneration, | |
| 1922 | 1931 | automationId: this.automationId, |
| 1923 | 1932 | }; |
| 1924 | 1933 | } |
| @@ -36,6 +36,7 @@ export class SlashCommandHandler { | ||
| 36 | 36 | if (qr.executeOnChatChange) icons += '💬'; |
| 37 | 37 | if (qr.executeOnNewChat) icons += '🆕'; |
| 38 | 38 | if (qr.executeOnGroupMemberDraft) icons += enumIcons.group; |
| 39 | + if (qr.executeBeforeGeneration) icons += '✈️'; | |
| 39 | 40 | return icons; |
| 40 | 41 | } |
| 41 | 42 | |
| @@ -267,6 +268,7 @@ export class SlashCommandHandler { | ||
| 267 | 268 | new SlashCommandNamedArgument('load', 'auto execute on chat load, e.g., load=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'), |
| 268 | 269 | new SlashCommandNamedArgument('new', 'auto execute on new chat, e.g., new=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'), |
| 269 | 270 | new SlashCommandNamedArgument('group', 'auto execute on group member selection, e.g., group=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'), |
| 271 | + new SlashCommandNamedArgument('generation', 'auto execute before message generation, e.g., generation=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'), | |
| 270 | 272 | new SlashCommandNamedArgument('title', 'title / tooltip to be shown on button, e.g., title="My Fancy Button"', [ARGUMENT_TYPE.STRING], false), |
| 271 | 273 | ]; |
| 272 | 274 | const qrUpdateArgs = [ |
| @@ -874,6 +876,7 @@ export class SlashCommandHandler { | ||
| 874 | 876 | executeOnChatChange: isTrueBoolean(args.load), |
| 875 | 877 | executeOnNewChat: isTrueBoolean(args.new), |
| 876 | 878 | executeOnGroupMemberDraft: isTrueBoolean(args.group), |
| 879 | + executeBeforeGeneration: isTrueBoolean(args.generation), | |
| 877 | 880 | automationId: args.automationId ?? '', |
| 878 | 881 | }, |
| 879 | 882 | ); |
| @@ -910,6 +913,7 @@ export class SlashCommandHandler { | ||
| 910 | 913 | executeOnChatChange: args.load === undefined ? undefined : isTrueBoolean(args.load), |
| 911 | 914 | executeOnGroupMemberDraft: args.group === undefined ? undefined : isTrueBoolean(args.group), |
| 912 | 915 | executeOnNewChat: args.new === undefined ? undefined : isTrueBoolean(args.new), |
| 916 | + executeBeforeGeneration: args.generation === undefined ? undefined : isTrueBoolean(args.generation), | |
| 913 | 917 | automationId: args.automationId ?? '', |
| 914 | 918 | }, |
| 915 | 919 | ); |