add Slash commands regenerate and swipe (#5243) * add Slash commands regenerate and swipe * Update /swipe slash command direction handling * Fix await not working in groups * Add SLASH_COMMAND source to swipe functionality and update constants --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>

2415e6bc86157577024e17f1e4dbfc02abf019d9

Haochen <38915833+hcsum@users.noreply.github.com>

Signed
4 files changed, +124 -4Showing whitespace changes
public/script.js+1 -1
@@ -9692,7 +9692,7 @@ export async function swipe(event, direction, { source, repeated, message = chat
96929692
9693 const mesId = Number(forceMesId ?? event?.currentTarget?.closest('.mes')?.getAttribute('mesid') ?? messageIndex ?? chat.length - 1);9693 const mesId = Number(forceMesId ?? event?.currentTarget?.closest('.mes')?.getAttribute('mesid') ?? messageIndex ?? chat.length - 1);
96949694
9695 if (source === SWIPE_SOURCE.DELETE || source === SWIPE_SOURCE.BACK || source === SWIPE_SOURCE.AUTO_SWIPE) {9695 if ([SWIPE_SOURCE.DELETE, SWIPE_SOURCE.BACK, SWIPE_SOURCE.AUTO_SWIPE, SWIPE_SOURCE.SLASH_COMMAND].includes(source)) {
9696 console.info(`The ${direction} swipe source on message #${mesId} is ${source}, Most checks have been bypassed. `);9696 console.info(`The ${direction} swipe source on message #${mesId} is ${source}, Most checks have been bypassed. `);
9697 } else {9697 } else {
9698 //Only show an error if swipes are not hidden and a message is generating.9698 //Only show an error if swipes are not hidden and a message is generating.
public/scripts/constants.js+1 -0
@@ -175,6 +175,7 @@ export const SWIPE_SOURCE = {
175 KEYBOARD: 'keyboard',175 KEYBOARD: 'keyboard',
176 BACK: 'back',176 BACK: 'back',
177 AUTO_SWIPE: 'auto_swipe',177 AUTO_SWIPE: 'auto_swipe',
178 SLASH_COMMAND: 'slash_command',
178};179};
179180
180/**181/**
public/scripts/group-chats.js+1 -1
@@ -183,7 +183,7 @@ async function regenerateGroup() {
183183
184 const abortController = new AbortController();184 const abortController = new AbortController();
185 setExternalAbortController(abortController);185 setExternalAbortController(abortController);
186 generateGroupWrapper(false, 'normal', { signal: abortController.signal });186 return generateGroupWrapper(false, 'normal', { signal: abortController.signal });
187}187}
188188
189/**189/**
public/scripts/slash-commands.js+121 -2
@@ -48,6 +48,7 @@ import {
48 setCharacterName,48 setCharacterName,
49 setExtensionPrompt,49 setExtensionPrompt,
50 showMoreMessages,50 showMoreMessages,
51 swipe,
51 stopGeneration,52 stopGeneration,
52 substituteParams,53 substituteParams,
53 syncMesToSwipe,54 syncMesToSwipe,
@@ -62,7 +63,7 @@ import { getMessageTimeStamp, isMobile } from './RossAscends-mods.js';
62import { hideChatMessageRange } from './chats.js';63import { hideChatMessageRange } from './chats.js';
63import { getContext, saveMetadataDebounced } from './extensions.js';64import { getContext, saveMetadataDebounced } from './extensions.js';
64import { getRegexedString, regex_placement } from './extensions/regex/engine.js';65import { getRegexedString, regex_placement } from './extensions/regex/engine.js';
65import { findGroupMemberId, groups, is_group_generating, openGroupById, resetSelectedGroup, saveGroupChat, selected_group, getGroupMembers } from './group-chats.js';66import { findGroupMemberId, groups, is_group_generating, openGroupById, regenerateGroup, resetSelectedGroup, saveGroupChat, selected_group, getGroupMembers } from './group-chats.js';
66import { chat_completion_sources, oai_settings, promptManager, ZAI_ENDPOINT } from './openai.js';67import { chat_completion_sources, oai_settings, promptManager, ZAI_ENDPOINT } from './openai.js';
67import { user_avatar } from './personas.js';68import { user_avatar } from './personas.js';
68import { addEphemeralStoppingString, chat_styles, context_presets, flushEphemeralStoppingStrings, playMessageSound, power_user } from './power-user.js';69import { addEphemeralStoppingString, chat_styles, context_presets, flushEphemeralStoppingStrings, playMessageSound, power_user } from './power-user.js';
@@ -90,7 +91,7 @@ import { SlashCommandScope } from './slash-commands/SlashCommandScope.js';
90import { t } from './i18n.js';91import { t } from './i18n.js';
91import { kai_settings } from './kai-settings.js';92import { kai_settings } from './kai-settings.js';
92import { instruct_presets, selectContextPreset, selectInstructPreset } from './instruct-mode.js';93import { instruct_presets, selectContextPreset, selectInstructPreset } from './instruct-mode.js';
93import { debounce_timeout } from './constants.js';94import { debounce_timeout, SWIPE_DIRECTION, SWIPE_SOURCE } from './constants.js';
94export {95export {
95 executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand,96 executeSlashCommands, executeSlashCommandsWithOptions, getSlashCommandsHelp, registerSlashCommand,
96};97};
@@ -1133,6 +1134,66 @@ export function initDefaultSlashCommands() {
1133 `,1134 `,
1134 }));1135 }));
1135 SlashCommandParser.addCommandObject(SlashCommand.fromProps({1136 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1137 name: 'regenerate',
1138 callback: regenerateChatCallback,
1139 aliases: ['regen'],
1140 namedArgumentList: [
1141 new SlashCommandNamedArgument(
1142 'await',
1143 t`Whether to await for the regeneration before proceeding`,
1144 [ARGUMENT_TYPE.BOOLEAN],
1145 false,
1146 false,
1147 'false',
1148 ),
1149 ],
1150 helpString: `
1151 <div>
1152 ${t`Regenerates the latest reply in the chat.`}
1153 </div>
1154 <div>
1155 ${t`If <code>await=true</code> named argument is passed, the command will await for the regeneration before proceeding.`}
1156 </div>
1157 `,
1158 }));
1159 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1160 name: 'swipe',
1161 callback: swipeChatCallback,
1162 namedArgumentList: [
1163 new SlashCommandNamedArgument(
1164 'direction',
1165 t`Swipe direction`,
1166 [ARGUMENT_TYPE.STRING],
1167 false,
1168 false,
1169 SWIPE_DIRECTION.RIGHT,
1170 [
1171 new SlashCommandEnumValue(SWIPE_DIRECTION.RIGHT, t`Swipe to the next reply`, enumTypes.enum, enumIcons.default),
1172 new SlashCommandEnumValue(SWIPE_DIRECTION.LEFT, t`Swipe to the previous reply`, enumTypes.enum, enumIcons.default),
1173 ],
1174 [],
1175 null,
1176 true,
1177 ),
1178 new SlashCommandNamedArgument(
1179 'await',
1180 t`Whether to await for the swipe action before proceeding`,
1181 [ARGUMENT_TYPE.BOOLEAN],
1182 false,
1183 false,
1184 'false',
1185 ),
1186 ],
1187 helpString: `
1188 <div>
1189 ${t`Swipes the latest reply. Defaults to <code>direction=right</code>; use <code>direction=left</code> to go to the previous reply. If no next swipe exists, behavior depends on message context.`}
1190 </div>
1191 <div>
1192 ${t`If <code>await=true</code> named argument is passed, the command will await for the swipe action before proceeding.`}
1193 </div>
1194 `,
1195 }));
1196 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1136 name: 'go',1197 name: 'go',
1137 callback: goToCharacterCallback,1198 callback: goToCharacterCallback,
1138 returns: t`The character/group name`,1199 returns: t`The character/group name`,
@@ -4538,6 +4599,64 @@ async function continueChatCallback(args, prompt) {
4538 return '';4599 return '';
4539}4600}
45404601
4602async function regenerateChatCallback(args) {
4603 const shouldAwait = isTrueBoolean(args?.await);
4604
4605 const outerPromise = new Promise((outerResolve) => setTimeout(async () => {
4606 try {
4607 await waitUntilCondition(() => !is_send_press && !is_group_generating, 10000, 100);
4608 } catch {
4609 console.warn('Timeout waiting for generation unlock');
4610 toastr.warning(t`Cannot run /regenerate command while the reply is being generated.`);
4611 outerResolve(Promise.resolve(''));
4612 return '';
4613 }
4614
4615 if (selected_group) {
4616 outerResolve(Promise.resolve(regenerateGroup()));
4617 return '';
4618 }
4619
4620 outerResolve(new Promise(innerResolve => setTimeout(() => {
4621 innerResolve(Generate('regenerate'));
4622 }, 1)));
4623 return '';
4624 }, 1));
4625
4626 if (shouldAwait) {
4627 const innerPromise = await outerPromise;
4628 await innerPromise;
4629 }
4630
4631 return '';
4632}
4633
4634async function swipeChatCallback(args) {
4635 const shouldAwait = isTrueBoolean(args?.await);
4636 const direction = args?.direction === SWIPE_DIRECTION.LEFT ? SWIPE_DIRECTION.LEFT : SWIPE_DIRECTION.RIGHT;
4637
4638 const outerPromise = new Promise((outerResolve) => setTimeout(async () => {
4639 try {
4640 await waitUntilCondition(() => !is_send_press && !is_group_generating, 10000, 100);
4641 } catch {
4642 console.warn('Timeout waiting for generation unlock');
4643 toastr.warning(t`Cannot run /swipe command while the reply is being generated.`);
4644 outerResolve(Promise.resolve(''));
4645 return '';
4646 }
4647
4648 outerResolve(Promise.resolve(swipe(null, direction, { source: SWIPE_SOURCE.SLASH_COMMAND, repeated: false })));
4649 return '';
4650 }, 1));
4651
4652 if (shouldAwait) {
4653 const innerPromise = await outerPromise;
4654 await innerPromise;
4655 }
4656
4657 return '';
4658}
4659
4541export async function generateSystemMessage(args, prompt) {4660export async function generateSystemMessage(args, prompt) {
4542 $('#send_textarea').val('')[0].dispatchEvent(new Event('input', { bubbles: true }));4661 $('#send_textarea').val('')[0].dispatchEvent(new Event('input', { bubbles: true }));
45434662