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>

63dcda8609ed82a156e12aa13aa9d5d7732ee964

MAX-TAB <60381043+MAX-TAB@users.noreply.github.com>

Signed
6 files changed, +42 -0Showing whitespace changes
public/scripts/extensions/quick-reply/api/QuickReplyApi.js+6 -0
@@ -202,6 +202,7 @@ export class QuickReplyApi {
202202 * @param {boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded
203203 * @param {boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected
204204 * @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
205206 * @param {string} [props.automationId] when not empty, the quick reply will be executed when the WI with the given automation ID is activated
206207 * @returns {QuickReply} the new quick reply
207208 */
@@ -217,6 +218,7 @@ export class QuickReplyApi {
217218 executeOnChatChange,
218219 executeOnGroupMemberDraft,
219220 executeOnNewChat,
221+ executeBeforeGeneration,
220222 automationId,
221223 } = {}) {
222224 const set = this.getSetByName(setName);
@@ -236,6 +238,7 @@ export class QuickReplyApi {
236238 qr.executeOnChatChange = executeOnChatChange ?? false;
237239 qr.executeOnGroupMemberDraft = executeOnGroupMemberDraft ?? false;
238240 qr.executeOnNewChat = executeOnNewChat ?? false;
241+ qr.executeBeforeGeneration = executeBeforeGeneration ?? false;
239242 qr.automationId = automationId ?? '';
240243 qr.onUpdate();
241244 return qr;
@@ -259,6 +262,7 @@ export class QuickReplyApi {
259262 * @param {boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded
260263 * @param {boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected
261264 * @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
262266 * @param {string} [props.automationId] when not empty, the quick reply will be executed when the WI with the given automation ID is activated
263267 * @returns {QuickReply} the altered quick reply
264268 */
@@ -275,6 +279,7 @@ export class QuickReplyApi {
275279 executeOnChatChange,
276280 executeOnGroupMemberDraft,
277281 executeOnNewChat,
282+ executeBeforeGeneration,
278283 automationId,
279284 } = {}) {
280285 const qr = this.getQrByLabel(setName, label);
@@ -293,6 +298,7 @@ export class QuickReplyApi {
293298 qr.executeOnChatChange = executeOnChatChange ?? qr.executeOnChatChange;
294299 qr.executeOnGroupMemberDraft = executeOnGroupMemberDraft ?? qr.executeOnGroupMemberDraft;
295300 qr.executeOnNewChat = executeOnNewChat ?? qr.executeOnNewChat;
301+ qr.executeBeforeGeneration = executeBeforeGeneration ?? qr.executeBeforeGeneration;
296302 qr.automationId = automationId ?? qr.automationId;
297303 qr.onUpdate();
298304 return qr;
public/scripts/extensions/quick-reply/html/qrEditor.html+4 -0
@@ -116,6 +116,10 @@
116116 <input type="checkbox" id="qr--executeOnGroupMemberDraft">
117117 <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>
118118 </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>
119123 <div class="flex-container alignItemsBaseline flexFlowColumn flexNoGap" title="Activate this quick reply when a World Info entry with the same Automation ID is triggered.">
120124 <small data-i18n="Automation ID:">Automation ID</small>
121125 <input type="text" id="qr--automationId" class="text_pole flex1" placeholder="( None )">
public/scripts/extensions/quick-reply/index.js+14 -0
@@ -85,6 +85,7 @@ const loadSets = async () => {
8585 qr.executeOnChatChange = slot.autoExecute_chatLoad ?? false;
8686 qr.executeOnGroupMemberDraft = slot.autoExecute_groupMemberDraft ?? false;
8787 qr.executeOnNewChat = slot.autoExecute_newChat ?? false;
88+ qr.executeBeforeGeneration = slot.autoExecute_beforeGeneration ?? false;
8889 qr.automationId = slot.automationId ?? '';
8990 qr.contextList = (slot.contextMenu ?? []).map(it=>({
9091 set: it.preset,
@@ -307,3 +308,16 @@ const onNewChat = async () => {
307308 await autoExec.handleNewChat();
308309};
309310eventSource.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));
public/scripts/extensions/quick-reply/src/AutoExecuteHandler.js+5 -0
@@ -78,6 +78,11 @@ export class AutoExecuteHandler {
7878 await this.performAutoExecute(this.getCommands('executeOnNewChat'));
7979 }
8080
81+ async handleBeforeGeneration() {
82+ if (!this.checkExecute()) return;
83+ await this.performAutoExecute(this.getCommands('executeBeforeGeneration'));
84+ }
85+
8186 /**
8287 * @param {any[]} entries Set of activated entries
8388 */
public/scripts/extensions/quick-reply/src/QuickReply.js+9 -0
@@ -46,6 +46,7 @@ export class QuickReply {
4646 /**@type {boolean}*/ executeOnChatChange = false;
4747 /**@type {boolean}*/ executeOnGroupMemberDraft = false;
4848 /**@type {boolean}*/ executeOnNewChat = false;
49+ /**@type {boolean}*/ executeBeforeGeneration = false;
4950 /**@type {string}*/ automationId = '';
5051
5152 /**@type {function}*/ onExecute;
@@ -1065,6 +1066,13 @@ export class QuickReply {
10651066 this.updateContext();
10661067 });
10671068 /**@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}*/
10681076 const executeOnNewChat = dom.querySelector('#qr--executeOnNewChat');
10691077 executeOnNewChat.checked = this.executeOnNewChat;
10701078 executeOnNewChat.addEventListener('click', ()=>{
@@ -1919,6 +1927,7 @@ export class QuickReply {
19191927 executeOnChatChange: this.executeOnChatChange,
19201928 executeOnGroupMemberDraft: this.executeOnGroupMemberDraft,
19211929 executeOnNewChat: this.executeOnNewChat,
1930+ executeBeforeGeneration: this.executeBeforeGeneration,
19221931 automationId: this.automationId,
19231932 };
19241933 }
public/scripts/extensions/quick-reply/src/SlashCommandHandler.js+4 -0
@@ -36,6 +36,7 @@ export class SlashCommandHandler {
3636 if (qr.executeOnChatChange) icons += '💬';
3737 if (qr.executeOnNewChat) icons += '🆕';
3838 if (qr.executeOnGroupMemberDraft) icons += enumIcons.group;
39+ if (qr.executeBeforeGeneration) icons += '✈️';
3940 return icons;
4041 }
4142
@@ -267,6 +268,7 @@ export class SlashCommandHandler {
267268 new SlashCommandNamedArgument('load', 'auto execute on chat load, e.g., load=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
268269 new SlashCommandNamedArgument('new', 'auto execute on new chat, e.g., new=true', [ARGUMENT_TYPE.BOOLEAN], false, false, 'false'),
269270 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'),
270272 new SlashCommandNamedArgument('title', 'title / tooltip to be shown on button, e.g., title="My Fancy Button"', [ARGUMENT_TYPE.STRING], false),
271273 ];
272274 const qrUpdateArgs = [
@@ -874,6 +876,7 @@ export class SlashCommandHandler {
874876 executeOnChatChange: isTrueBoolean(args.load),
875877 executeOnNewChat: isTrueBoolean(args.new),
876878 executeOnGroupMemberDraft: isTrueBoolean(args.group),
879+ executeBeforeGeneration: isTrueBoolean(args.generation),
877880 automationId: args.automationId ?? '',
878881 },
879882 );
@@ -910,6 +913,7 @@ export class SlashCommandHandler {
910913 executeOnChatChange: args.load === undefined ? undefined : isTrueBoolean(args.load),
911914 executeOnGroupMemberDraft: args.group === undefined ? undefined : isTrueBoolean(args.group),
912915 executeOnNewChat: args.new === undefined ? undefined : isTrueBoolean(args.new),
916+ executeBeforeGeneration: args.generation === undefined ? undefined : isTrueBoolean(args.generation),
913917 automationId: args.automationId ?? '',
914918 },
915919 );