} Branch file name
*/
export async function branchChat(mesId) {
if (this_chid === undefined && !selected_group) {
- toastr.info('No character selected.', 'Branch creation aborted');
- return;
+ toastr.info('No character selected.', 'Create Branch');
+ return null;
}
const fileName = await createBranch(mesId);
@@ -377,7 +392,243 @@ export async function branchChat(mesId) {
return fileName;
}
-jQuery(function () {
+function registerBookmarksSlashCommands() {
+ /**
+ * Validates a message ID. (Is a number, exists as a message)
+ *
+ * @param {number} mesId - The message ID to validate.
+ * @param {string} context - The context of the slash command. Will be used as the title of any toasts.
+ * @returns {boolean} - Returns true if the message ID is valid, otherwise false.
+ */
+ function validateMessageId(mesId, context) {
+ if (isNaN(mesId)) {
+ toastr.warning('Invalid message ID was provided', context);
+ return false;
+ }
+ if (!chat[mesId]) {
+ toastr.warning(`Message for id ${mesId} not found`, context);
+ return false;
+ }
+ return true;
+ }
+
+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
+ name: 'branch-create',
+ returns: 'Name of the new branch',
+ callback: async (args, text) => {
+ const mesId = Number(args.mesId ?? text ?? getLastMessageId());
+ if (!validateMessageId(mesId, 'Create Branch')) return '';
+
+ const branchName = await branchChat(mesId);
+ return branchName ?? '';
+ },
+ namedArgumentList: [
+ SlashCommandNamedArgument.fromProps({
+ name: 'mes',
+ description: 'Message ID',
+ typeList: [ARGUMENT_TYPE.NUMBER],
+ enumProvider: commonEnumProviders.messages(),
+ }),
+ ],
+ unnamedArgumentList: [
+ SlashCommandArgument.fromProps({
+ description: 'Message ID',
+ typeList: [ARGUMENT_TYPE.NUMBER],
+ enumProvider: commonEnumProviders.messages(),
+ }),
+ ],
+ helpString: `
+
+ Create a new branch from the selected message. If no message id is provided, will use the last message.
+
+
+ Creating a branch will automatically choose a name for the branch.
+ After creating the branch, the branch chat will be automatically opened.
+
+
+ Use Checkpoints and /checkpoint-create
instead if you do not want to jump to the new chat.
+
`,
+ }));
+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
+ name: 'checkpoint-create',
+ returns: 'Name of the new checkpoint',
+ callback: async (args, text) => {
+ const mesId = Number(args.mesId ?? getLastMessageId());
+ if (!validateMessageId(mesId, 'Create Checkpoint')) return '';
+
+ if (!text || typeof text !== 'string') {
+ toastr.warning('Checkpoint name must be provided', 'Create Checkpoint');
+ return '';
+ }
+
+ const checkPointName = await createNewBookmark(mesId, { forceName: text });
+ return checkPointName ?? '';
+ },
+ namedArgumentList: [
+ SlashCommandNamedArgument.fromProps({
+ name: 'mes',
+ description: 'Message ID',
+ typeList: [ARGUMENT_TYPE.NUMBER],
+ enumProvider: commonEnumProviders.messages(),
+ }),
+ ],
+ unnamedArgumentList: [
+ SlashCommandArgument.fromProps({
+ description: 'Checkpoint name',
+ typeList: [ARGUMENT_TYPE.STRING],
+ isRequired: true,
+ }),
+ ],
+ helpString: `
+
+ Create a new checkpoint for the selected message with the provided name. If no message id is provided, will use the last message.
+
+
+ A created checkpoint will be permanently linked with the message.
+ If a checkpoint already exists, the link to it will be overwritten.
+ After creating the checkpoint, the checkpoint chat can be opened with the checkpoint flag,
+ using the /go
command with the checkpoint name or the /checkpoint-go
command on the message.
+
+
+ Use Branches and /branch-create
instead if you do want to jump to the new chat.
+
+ `,
+ }));
+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
+ name: 'checkpoint-go',
+ returns: 'Name of the checkpoint',
+ callback: async (args, text) => {
+ const mesId = Number(args.mesId ?? text ?? getLastMessageId());
+ if (!validateMessageId(mesId, 'Open Checkpoint')) return '';
+
+ const checkPointName = chat[mesId].extra?.bookmark_link;
+ if (!checkPointName) {
+ toastr.warning('No checkpoint is linked to the selected message', 'Open Checkpoint');
+ return '';
+ }
+
+ if (selected_group) {
+ await openGroupChat(selected_group, checkPointName);
+ } else {
+ await openCharacterChat(checkPointName);
+ }
+
+ return checkPointName;
+ },
+ namedArgumentList: [
+ SlashCommandNamedArgument.fromProps({
+ name: 'mes',
+ description: 'Message ID',
+ typeList: [ARGUMENT_TYPE.NUMBER],
+ enumProvider: commonEnumProviders.messages(),
+ }),
+ ],
+ unnamedArgumentList: [
+ SlashCommandArgument.fromProps({
+ description: 'Message ID',
+ typeList: [ARGUMENT_TYPE.NUMBER],
+ enumProvider: commonEnumProviders.messages(),
+ }),
+ ],
+ helpString: `
+
+ Open the checkpoint linked to the selected message. If no message id is provided, will use the last message.
+
+
+ Use /checkpoint-get
if you want to make sure that the selected message has a checkpoint.
+
`,
+ }));
+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
+ name: 'checkpoint-exit',
+ returns: 'The name of the chat exited to. Returns null if not in a checkpoint chat.',
+ callback: async () => {
+ const mainChat = await backToMainChat();
+ return mainChat ?? '';
+ },
+ helpString: 'Exit the checkpoint chat.
If not in a checkpoint chat, returns null.',
+ }));
+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
+ name: 'checkpoint-parent',
+ returns: 'Name of the parent chat for this checkpoint',
+ callback: async () => {
+ const mainChatName = getMainChatName();
+ return mainChatName ?? '';
+ },
+ helpString: 'Get the name of the parent chat for this checkpoint. If not in a checkpoint chat, returns null.',
+ }))
+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
+ name: 'checkpoint-get',
+ returns: 'Name of the chat',
+ callback: async (args, text) => {
+ const mesId = Number(args.mesId ?? text ?? getLastMessageId());
+ if (!validateMessageId(mesId, 'Get Checkpoint')) return '';
+
+ const checkPointName = chat[mesId].extra?.bookmark_link;
+ return checkPointName ?? '';
+ },
+ namedArgumentList: [
+ SlashCommandNamedArgument.fromProps({
+ name: 'mes',
+ description: 'Message ID',
+ typeList: [ARGUMENT_TYPE.NUMBER],
+ enumProvider: commonEnumProviders.messages(),
+ }),
+ ],
+ unnamedArgumentList: [
+ SlashCommandArgument.fromProps({
+ description: 'Message ID',
+ typeList: [ARGUMENT_TYPE.NUMBER],
+ enumProvider: commonEnumProviders.messages(),
+ }),
+ ],
+ helpString: `
+
+ Get the name of the checkpoint linked to the selected message. If no message id is provided, will use the last message.
+ If no checkpoint is linked, the result will be empty.
+
`,
+ }));
+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
+ name: 'checkpoint-list',
+ returns: 'JSON array of all existing checkpoints in this chat, as an array',
+ /** @param {{links?: string}} args @returns {Promise} */
+ callback: async (args, _) => {
+ const result = [];
+ for (const mesId in chat) {
+ if (chat[mesId].extra?.bookmark_link) {
+ result.push(args.links ? chat[mesId].extra.bookmark_link : Number(mesId));
+ }
+ }
+ return JSON.stringify(result);
+ },
+ namedArgumentList: [
+ SlashCommandNamedArgument.fromProps({
+ name: 'links',
+ description: 'Get a list of all links / chat names of the checkpoints, instead of the message ids',
+ typeList: [ARGUMENT_TYPE.BOOLEAN],
+ enumList: commonEnumProviders.boolean('trueFalse')(),
+ defaultValue: 'false',
+ }),
+ ],
+ helpString: `
+
+ List all existing checkpoints in this chat.
+
+
+ Returns a list of all message ids that have a checkpoint, or all checkpoint links if links
is set to true
.
+ The value will be a JSON array.
+
`,
+ }));
+}
+
+export function initBookmarks() {
$('#option_new_bookmark').on('click', saveBookmarkMenu);
$('#option_back_to_main').on('click', backToMainChat);
$('#option_convert_to_group').on('click', convertSoloToGroupChat);
@@ -386,7 +637,7 @@ jQuery(function () {
// If shift is held down, we are not following the bookmark, but creating a new one
if (e.shiftKey) {
var selectedMesId = $(this).closest('.mes').attr('mesid');
- await createNewBookmark(selectedMesId);
+ await createNewBookmark(Number(selectedMesId));
return;
}
@@ -416,7 +667,7 @@ jQuery(function () {
$(document).on('click', '.mes_create_bookmark', async function () {
var selected_mes_id = $(this).closest('.mes').attr('mesid');
if (selected_mes_id !== undefined) {
- await createNewBookmark(selected_mes_id);
+ await createNewBookmark(Number(selected_mes_id));
}
});
@@ -426,4 +677,6 @@ jQuery(function () {
await branchChat(Number(selected_mes_id));
}
});
-});
+
+ registerBookmarksSlashCommands();
+}