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 | * @param {boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded | 202 | * @param {boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded |
| 203 | * @param {boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected | 203 | * @param {boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected |
| 204 | * @param {boolean} [props.executeOnNewChat] whether to execute the quick reply when a new chat is created | 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 | * @param {string} [props.automationId] when not empty, the quick reply will be executed when the WI with the given automation ID is activated | 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 | * @returns {QuickReply} the new quick reply | 207 | * @returns {QuickReply} the new quick reply |
| 207 | */ | 208 | */ |
| @@ -217,6 +218,7 @@ export class QuickReplyApi { | |||
| 217 | executeOnChatChange, | 218 | executeOnChatChange, |
| 218 | executeOnGroupMemberDraft, | 219 | executeOnGroupMemberDraft, |
| 219 | executeOnNewChat, | 220 | executeOnNewChat, |
| 221 | executeBeforeGeneration, | ||
| 220 | automationId, | 222 | automationId, |
| 221 | } = {}) { | 223 | } = {}) { |
| 222 | const set = this.getSetByName(setName); | 224 | const set = this.getSetByName(setName); |
| @@ -236,6 +238,7 @@ export class QuickReplyApi { | |||
| 236 | qr.executeOnChatChange = executeOnChatChange ?? false; | 238 | qr.executeOnChatChange = executeOnChatChange ?? false; |
| 237 | qr.executeOnGroupMemberDraft = executeOnGroupMemberDraft ?? false; | 239 | qr.executeOnGroupMemberDraft = executeOnGroupMemberDraft ?? false; |
| 238 | qr.executeOnNewChat = executeOnNewChat ?? false; | 240 | qr.executeOnNewChat = executeOnNewChat ?? false; |
| 241 | qr.executeBeforeGeneration = executeBeforeGeneration ?? false; | ||
| 239 | qr.automationId = automationId ?? ''; | 242 | qr.automationId = automationId ?? ''; |
| 240 | qr.onUpdate(); | 243 | qr.onUpdate(); |
| 241 | return qr; | 244 | return qr; |
| @@ -259,6 +262,7 @@ export class QuickReplyApi { | |||
| 259 | * @param {boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded | 262 | * @param {boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded |
| 260 | * @param {boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected | 263 | * @param {boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected |
| 261 | * @param {boolean} [props.executeOnNewChat] whether to execute the quick reply when a new chat is created | 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 | * @param {string} [props.automationId] when not empty, the quick reply will be executed when the WI with the given automation ID is activated | 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 | * @returns {QuickReply} the altered quick reply | 267 | * @returns {QuickReply} the altered quick reply |
| 264 | */ | 268 | */ |
| @@ -275,6 +279,7 @@ export class QuickReplyApi { | |||
| 275 | executeOnChatChange, | 279 | executeOnChatChange, |
| 276 | executeOnGroupMemberDraft, | 280 | executeOnGroupMemberDraft, |
| 277 | executeOnNewChat, | 281 | executeOnNewChat, |
| 282 | executeBeforeGeneration, | ||
| 278 | automationId, | 283 | automationId, |
| 279 | } = {}) { | 284 | } = {}) { |
| 280 | const qr = this.getQrByLabel(setName, label); | 285 | const qr = this.getQrByLabel(setName, label); |
| @@ -293,6 +298,7 @@ export class QuickReplyApi { | |||
| 293 | qr.executeOnChatChange = executeOnChatChange ?? qr.executeOnChatChange; | 298 | qr.executeOnChatChange = executeOnChatChange ?? qr.executeOnChatChange; |
| 294 | qr.executeOnGroupMemberDraft = executeOnGroupMemberDraft ?? qr.executeOnGroupMemberDraft; | 299 | qr.executeOnGroupMemberDraft = executeOnGroupMemberDraft ?? qr.executeOnGroupMemberDraft; |
| 295 | qr.executeOnNewChat = executeOnNewChat ?? qr.executeOnNewChat; | 300 | qr.executeOnNewChat = executeOnNewChat ?? qr.executeOnNewChat; |
| 301 | qr.executeBeforeGeneration = executeBeforeGeneration ?? qr.executeBeforeGeneration; | ||
| 296 | qr.automationId = automationId ?? qr.automationId; | 302 | qr.automationId = automationId ?? qr.automationId; |
| 297 | qr.onUpdate(); | 303 | qr.onUpdate(); |
| 298 | return qr; | 304 | return qr; |
| @@ -116,6 +116,10 @@ | |||
| 116 | <input type="checkbox" id="qr--executeOnGroupMemberDraft"> | 116 | <input type="checkbox" id="qr--executeOnGroupMemberDraft"> |
| 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> | 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 | </label> | 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 | <div class="flex-container alignItemsBaseline flexFlowColumn flexNoGap" title="Activate this quick reply when a World Info entry with the same Automation ID is triggered."> | 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 | <small data-i18n="Automation ID:">Automation ID</small> | 124 | <small data-i18n="Automation ID:">Automation ID</small> |
| 121 | <input type="text" id="qr--automationId" class="text_pole flex1" placeholder="( None )"> | 125 | <input type="text" id="qr--automationId" class="text_pole flex1" placeholder="( None )"> |
| @@ -85,6 +85,7 @@ const loadSets = async () => { | |||
| 85 | qr.executeOnChatChange = slot.autoExecute_chatLoad ?? false; | 85 | qr.executeOnChatChange = slot.autoExecute_chatLoad ?? false; |
| 86 | qr.executeOnGroupMemberDraft = slot.autoExecute_groupMemberDraft ?? false; | 86 | qr.executeOnGroupMemberDraft = slot.autoExecute_groupMemberDraft ?? false; |
| 87 | qr.executeOnNewChat = slot.autoExecute_newChat ?? false; | 87 | qr.executeOnNewChat = slot.autoExecute_newChat ?? false; |
| 88 | qr.executeBeforeGeneration = slot.autoExecute_beforeGeneration ?? false; | ||
| 88 | qr.automationId = slot.automationId ?? ''; | 89 | qr.automationId = slot.automationId ?? ''; |
| 89 | qr.contextList = (slot.contextMenu ?? []).map(it=>({ | 90 | qr.contextList = (slot.contextMenu ?? []).map(it=>({ |
| 90 | set: it.preset, | 91 | set: it.preset, |
| @@ -307,3 +308,16 @@ const onNewChat = async () => { | |||
| 307 | await autoExec.handleNewChat(); | 308 | await autoExec.handleNewChat(); |
| 308 | }; | 309 | }; |
| 309 | eventSource.on(event_types.CHAT_CREATED, (...args) => executeIfReadyElseQueue(onNewChat, args)); | 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 | await this.performAutoExecute(this.getCommands('executeOnNewChat')); | 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 | * @param {any[]} entries Set of activated entries | 87 | * @param {any[]} entries Set of activated entries |
| 83 | */ | 88 | */ |
| @@ -46,6 +46,7 @@ export class QuickReply { | |||
| 46 | /**@type {boolean}*/ executeOnChatChange = false; | 46 | /**@type {boolean}*/ executeOnChatChange = false; |
| 47 | /**@type {boolean}*/ executeOnGroupMemberDraft = false; | 47 | /**@type {boolean}*/ executeOnGroupMemberDraft = false; |
| 48 | /**@type {boolean}*/ executeOnNewChat = false; | 48 | /**@type {boolean}*/ executeOnNewChat = false; |
| 49 | /**@type {boolean}*/ executeBeforeGeneration = false; | ||
| 49 | /**@type {string}*/ automationId = ''; | 50 | /**@type {string}*/ automationId = ''; |
| 50 | 51 | ||
| 51 | /**@type {function}*/ onExecute; | 52 | /**@type {function}*/ onExecute; |
| @@ -1065,6 +1066,13 @@ export class QuickReply { | |||
| 1065 | this.updateContext(); | 1066 | this.updateContext(); |
| 1066 | }); | 1067 | }); |
| 1067 | /**@type {HTMLInputElement}*/ | 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 | const executeOnNewChat = dom.querySelector('#qr--executeOnNewChat'); | 1076 | const executeOnNewChat = dom.querySelector('#qr--executeOnNewChat'); |
| 1069 | executeOnNewChat.checked = this.executeOnNewChat; | 1077 | executeOnNewChat.checked = this.executeOnNewChat; |
| 1070 | executeOnNewChat.addEventListener('click', ()=>{ | 1078 | executeOnNewChat.addEventListener('click', ()=>{ |
| @@ -1919,6 +1927,7 @@ export class QuickReply { | |||
| 1919 | executeOnChatChange: this.executeOnChatChange, | 1927 | executeOnChatChange: this.executeOnChatChange, |
| 1920 | executeOnGroupMemberDraft: this.executeOnGroupMemberDraft, | 1928 | executeOnGroupMemberDraft: this.executeOnGroupMemberDraft, |
| 1921 | executeOnNewChat: this.executeOnNewChat, | 1929 | executeOnNewChat: this.executeOnNewChat, |
| 1930 | executeBeforeGeneration: this.executeBeforeGeneration, | ||
| 1922 | automationId: this.automationId, | 1931 | automationId: this.automationId, |
| 1923 | }; | 1932 | }; |
| 1924 | } | 1933 | } |
| @@ -36,6 +36,7 @@ export class SlashCommandHandler { | |||
| 36 | if (qr.executeOnChatChange) icons += '💬'; | 36 | if (qr.executeOnChatChange) icons += '💬'; |
| 37 | if (qr.executeOnNewChat) icons += '🆕'; | 37 | if (qr.executeOnNewChat) icons += '🆕'; |
| 38 | if (qr.executeOnGroupMemberDraft) icons += enumIcons.group; | 38 | if (qr.executeOnGroupMemberDraft) icons += enumIcons.group; |
| 39 | if (qr.executeBeforeGeneration) icons += '✈️'; | ||
| 39 | return icons; | 40 | return icons; |
| 40 | } | 41 | } |
| 41 | 42 | ||
| @@ -267,6 +268,7 @@ export class SlashCommandHandler { | |||
| 267 | new SlashCommandNamedArgument('load', 'auto execute on chat load, e.g., load=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'), | 268 | new SlashCommandNamedArgument('load', 'auto execute on chat load, e.g., load=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'), |
| 268 | new SlashCommandNamedArgument('new', 'auto execute on new chat, e.g., new=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'), | 269 | new SlashCommandNamedArgument('new', 'auto execute on new chat, e.g., new=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'), |
| 269 | new SlashCommandNamedArgument('group', 'auto execute on group member selection, e.g., group=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'), | 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 | new SlashCommandNamedArgument('title', 'title / tooltip to be shown on button, e.g., title="My Fancy Button"', [ARGUMENT_TYPE.STRING], false), | 272 | new SlashCommandNamedArgument('title', 'title / tooltip to be shown on button, e.g., title="My Fancy Button"', [ARGUMENT_TYPE.STRING], false), |
| 271 | ]; | 273 | ]; |
| 272 | const qrUpdateArgs = [ | 274 | const qrUpdateArgs = [ |
| @@ -874,6 +876,7 @@ export class SlashCommandHandler { | |||
| 874 | executeOnChatChange: isTrueBoolean(args.load), | 876 | executeOnChatChange: isTrueBoolean(args.load), |
| 875 | executeOnNewChat: isTrueBoolean(args.new), | 877 | executeOnNewChat: isTrueBoolean(args.new), |
| 876 | executeOnGroupMemberDraft: isTrueBoolean(args.group), | 878 | executeOnGroupMemberDraft: isTrueBoolean(args.group), |
| 879 | executeBeforeGeneration: isTrueBoolean(args.generation), | ||
| 877 | automationId: args.automationId ?? '', | 880 | automationId: args.automationId ?? '', |
| 878 | }, | 881 | }, |
| 879 | ); | 882 | ); |
| @@ -910,6 +913,7 @@ export class SlashCommandHandler { | |||
| 910 | executeOnChatChange: args.load === undefined ? undefined : isTrueBoolean(args.load), | 913 | executeOnChatChange: args.load === undefined ? undefined : isTrueBoolean(args.load), |
| 911 | executeOnGroupMemberDraft: args.group === undefined ? undefined : isTrueBoolean(args.group), | 914 | executeOnGroupMemberDraft: args.group === undefined ? undefined : isTrueBoolean(args.group), |
| 912 | executeOnNewChat: args.new === undefined ? undefined : isTrueBoolean(args.new), | 915 | executeOnNewChat: args.new === undefined ? undefined : isTrueBoolean(args.new), |
| 916 | executeBeforeGeneration: args.generation === undefined ? undefined : isTrueBoolean(args.generation), | ||
| 913 | automationId: args.automationId ?? '', | 917 | automationId: args.automationId ?? '', |
| 914 | }, | 918 | }, |
| 915 | ); | 919 | ); |