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>
Signed| @@ -9692,7 +9692,7 @@ export async function swipe(event, direction, { source, repeated, message = chat | |||
| 9692 | 9692 | ||
| 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); |
| 9694 | 9694 | ||
| 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. |
| @@ -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 | }; |
| 179 | 180 | ||
| 180 | /** | 181 | /** |
| @@ -183,7 +183,7 @@ async function regenerateGroup() { | |||
| 183 | 183 | ||
| 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 | } |
| 188 | 188 | ||
| 189 | /** | 189 | /** |
| @@ -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'; | |||
| 62 | import { hideChatMessageRange } from './chats.js'; | 63 | import { hideChatMessageRange } from './chats.js'; |
| 63 | import { getContext, saveMetadataDebounced } from './extensions.js'; | 64 | import { getContext, saveMetadataDebounced } from './extensions.js'; |
| 64 | import { getRegexedString, regex_placement } from './extensions/regex/engine.js'; | 65 | import { getRegexedString, regex_placement } from './extensions/regex/engine.js'; |
| 65 | import { findGroupMemberId, groups, is_group_generating, openGroupById, resetSelectedGroup, saveGroupChat, selected_group, getGroupMembers } from './group-chats.js'; | 66 | import { findGroupMemberId, groups, is_group_generating, openGroupById, regenerateGroup, resetSelectedGroup, saveGroupChat, selected_group, getGroupMembers } from './group-chats.js'; |
| 66 | import { chat_completion_sources, oai_settings, promptManager, ZAI_ENDPOINT } from './openai.js'; | 67 | import { chat_completion_sources, oai_settings, promptManager, ZAI_ENDPOINT } from './openai.js'; |
| 67 | import { user_avatar } from './personas.js'; | 68 | import { user_avatar } from './personas.js'; |
| 68 | import { addEphemeralStoppingString, chat_styles, context_presets, flushEphemeralStoppingStrings, playMessageSound, power_user } from './power-user.js'; | 69 | import { addEphemeralStoppingString, chat_styles, context_presets, flushEphemeralStoppingStrings, playMessageSound, power_user } from './power-user.js'; |
| @@ -90,7 +91,7 @@ import { SlashCommandScope } from './slash-commands/SlashCommandScope.js'; | |||
| 90 | import { t } from './i18n.js'; | 91 | import { t } from './i18n.js'; |
| 91 | import { kai_settings } from './kai-settings.js'; | 92 | import { kai_settings } from './kai-settings.js'; |
| 92 | import { instruct_presets, selectContextPreset, selectInstructPreset } from './instruct-mode.js'; | 93 | import { instruct_presets, selectContextPreset, selectInstructPreset } from './instruct-mode.js'; |
| 93 | import { debounce_timeout } from './constants.js'; | 94 | import { debounce_timeout, SWIPE_DIRECTION, SWIPE_SOURCE } from './constants.js'; |
| 94 | export { | 95 | export { |
| 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 | } |
| 4540 | 4601 | ||
| 4602 | async 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 | |||
| 4634 | async 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 | |||
| 4541 | export async function generateSystemMessage(args, prompt) { | 4660 | export 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 })); |
| 4543 | 4662 | ||