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 -0Ignore whitespace
public/scripts/extensions/quick-reply/api/QuickReplyApi.js+6 -0
@@ -202,6 +202,7 @@ export class QuickReplyApi {
202 * @param {boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded202 * @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 selected203 * @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 created204 * @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 activated206 * @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 reply207 * @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 loaded262 * @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 selected263 * @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 created264 * @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 activated266 * @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 reply267 * @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;
public/scripts/extensions/quick-reply/html/qrEditor.html+4 -0
@@ -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 )">
public/scripts/extensions/quick-reply/index.js+14 -0
@@ -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};
309eventSource.on(event_types.CHAT_CREATED, (...args) => executeIfReadyElseQueue(onNewChat, args));310eventSource.on(event_types.CHAT_CREATED, (...args) => executeIfReadyElseQueue(onNewChat, args));
311
312const 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};
323eventSource.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 {
78 await this.performAutoExecute(this.getCommands('executeOnNewChat'));78 await this.performAutoExecute(this.getCommands('executeOnNewChat'));
79 }79 }
8080
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 entries87 * @param {any[]} entries Set of activated entries
83 */88 */
public/scripts/extensions/quick-reply/src/QuickReply.js+9 -0
@@ -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 = '';
5051
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 }
public/scripts/extensions/quick-reply/src/SlashCommandHandler.js+4 -0
@@ -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 }
4142
@@ -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 );