From 3c2113a6e74b5121ba46db5846fa355c31852c73 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Mon, 19 Feb 2024 00:17:23 +0200 Subject: [PATCH 1/8] Add ability to preserve file names when loading from assets downloader --- public/script.js | 19 ++++++++++++++++--- public/scripts/extensions/assets/index.js | 2 +- src/endpoints/characters.js | 14 +++++++++++++- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/public/script.js b/public/script.js index 066a02f99..b9df03842 100644 --- a/public/script.js +++ b/public/script.js @@ -7764,7 +7764,13 @@ async function connectAPISlash(_, text) { } } -export async function processDroppedFiles(files) { +/** + * Imports supported files dropped into the app window. + * @param {File[]} files Array of files to process + * @param {boolean?} preserveFileNames Whether to preserve original file names + * @returns {Promise} + */ +export async function processDroppedFiles(files, preserveFileNames = false) { const allowedMimeTypes = [ 'application/json', 'image/png', @@ -7776,14 +7782,20 @@ export async function processDroppedFiles(files) { for (const file of files) { if (allowedMimeTypes.includes(file.type)) { - await importCharacter(file); + await importCharacter(file, preserveFileNames); } else { toastr.warning('Unsupported file type: ' + file.name); } } } -async function importCharacter(file) { +/** + * Imports a character from a file. + * @param {File} file File to import + * @param {boolean?} preserveFileName Whether to preserve original file name + * @returns {Promise} + */ +async function importCharacter(file, preserveFileName = false) { const ext = file.name.match(/\.(\w+)$/); if (!ext || !(['json', 'png', 'yaml', 'yml'].includes(ext[1].toLowerCase()))) { return; @@ -7794,6 +7806,7 @@ async function importCharacter(file) { const formData = new FormData(); formData.append('avatar', file); formData.append('file_type', format); + formData.append('preserve_file_name', String(preserveFileName)); const data = await jQuery.ajax({ type: 'POST', diff --git a/public/scripts/extensions/assets/index.js b/public/scripts/extensions/assets/index.js index b07fa7b32..d9b612af7 100644 --- a/public/scripts/extensions/assets/index.js +++ b/public/scripts/extensions/assets/index.js @@ -236,7 +236,7 @@ async function installAsset(url, assetType, filename) { console.debug(DEBUG_PREFIX, 'Importing character ', filename); const blob = await result.blob(); const file = new File([blob], filename, { type: blob.type }); - await processDroppedFiles([file]); + await processDroppedFiles([file], true); console.debug(DEBUG_PREFIX, 'Character downloaded.'); } } diff --git a/src/endpoints/characters.js b/src/endpoints/characters.js index 0cd1c4a02..0dcefbc97 100644 --- a/src/endpoints/characters.js +++ b/src/endpoints/characters.js @@ -796,6 +796,17 @@ function getPngName(file) { return file; } +/** + * Gets the preserved name for the uploaded file if the request is valid. + * @param {import("express").Request} request - Express request object + * @returns {string | undefined} - The preserved name if the request is valid, otherwise undefined + */ +function getPreservedName(request) { + return request.body.file_type === 'png' && request.body.preserve_file_name === 'true' && request.file?.originalname + ? path.parse(request.file.originalname).name + : undefined; +} + router.post('/import', urlencodedParser, async function (request, response) { if (!request.body || !request.file) return response.sendStatus(400); @@ -803,6 +814,7 @@ router.post('/import', urlencodedParser, async function (request, response) { let filedata = request.file; let uploadPath = path.join(UPLOADS_PATH, filedata.filename); let format = request.body.file_type; + const preservedFileName = getPreservedName(request); if (format == 'yaml' || format == 'yml') { try { @@ -894,7 +906,7 @@ router.post('/import', urlencodedParser, async function (request, response) { let jsonData = JSON.parse(img_data); jsonData.name = sanitize(jsonData.data?.name || jsonData.name); - png_name = getPngName(jsonData.name); + png_name = preservedFileName || getPngName(jsonData.name); if (jsonData.spec !== undefined) { console.log('Found a v2 character file.'); From 2e00a1baaf34cc1790bea058ccf0f93ec4fa727f Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Mon, 19 Feb 2024 19:37:18 +0200 Subject: [PATCH 2/8] [FEATURE_REQUEST] Can the unlocked max context size for OpenAI completion be increased from 102k to 200k for example? #1842 --- public/scripts/openai.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/openai.js b/public/scripts/openai.js index e1552090f..e655d6a18 100644 --- a/public/scripts/openai.js +++ b/public/scripts/openai.js @@ -119,7 +119,7 @@ const scale_max = 8191; const claude_max = 9000; // We have a proper tokenizer, so theoretically could be larger (up to 9k) const claude_100k_max = 99000; let ai21_max = 9200; //can easily fit 9k gpt tokens because j2's tokenizer is efficient af -const unlocked_max = 100 * 1024; +const unlocked_max = max_200k; const oai_max_temp = 2.0; const claude_max_temp = 1.0; //same as j2 const j2_max_topk = 10.0; From 550d8483cc527ff2b30534702ab631e945cbfda0 Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Mon, 19 Feb 2024 01:17:04 +0100 Subject: [PATCH 3/8] Extend impersonate/continue/regenerate with possible custom prompts - Use custom prompt provided via slash command arguments (similar to /sysgen and others) - Use written text from textbox, if the popout menu actions are clicked --- public/script.js | 17 ++++++++++++----- public/scripts/slash-commands.js | 4 ++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/public/script.js b/public/script.js index b9df03842..188e0900f 100644 --- a/public/script.js +++ b/public/script.js @@ -7864,9 +7864,9 @@ async function importFromURL(items, files) { } } -async function doImpersonate() { +async function doImpersonate(_, prompt) { $('#send_textarea').val(''); - $('#option_impersonate').trigger('click', { fromSlashCommand: true }); + $('#option_impersonate').trigger('click', { fromSlashCommand: true, additionalPrompt: prompt }); } async function doDeleteChat() { @@ -8682,6 +8682,13 @@ jQuery(async function () { const fromSlashCommand = customData?.fromSlashCommand || false; var id = $(this).attr('id'); + // Check whether a custom prompt was provided via custom data (for example through a slash command), otherwise fall back and use the text from the textbox, if there is any + const additionalPrompt = (customData?.additionalPrompt && customData.additionalPrompt.trim()) || (String($('#send_textarea').val()).trim() || undefined); + const buildOrFillAdditionalArgs = (args = {}) => ({ + ...args, + ...(additionalPrompt !== undefined && { quiet_prompt: additionalPrompt, quietToLoud: true }), + }); + if (id == 'option_select_chat') { if ((selected_group && !is_group_generating) || (this_chid !== undefined && !is_send_press) || fromSlashCommand) { await displayPastChats(); @@ -8717,7 +8724,7 @@ jQuery(async function () { } else { is_send_press = true; - Generate('regenerate'); + Generate('regenerate', buildOrFillAdditionalArgs()); } } } @@ -8725,14 +8732,14 @@ jQuery(async function () { else if (id == 'option_impersonate') { if (is_send_press == false || fromSlashCommand) { is_send_press = true; - Generate('impersonate'); + Generate('impersonate', buildOrFillAdditionalArgs()); } } else if (id == 'option_continue') { if (is_send_press == false || fromSlashCommand) { is_send_press = true; - Generate('continue'); + Generate('continue', buildOrFillAdditionalArgs()); } } diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index 9a4175a1e..820b43ea6 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -1168,7 +1168,7 @@ async function openChat(id) { await reloadCurrentChat(); } -function continueChatCallback() { +function continueChatCallback(_, prompt) { setTimeout(async () => { try { await waitUntilCondition(() => !is_send_press && !is_group_generating, 10000, 100); @@ -1179,7 +1179,7 @@ function continueChatCallback() { // Prevent infinite recursion $('#send_textarea').val('').trigger('input'); - $('#option_continue').trigger('click', { fromSlashCommand: true }); + $('#option_continue').trigger('click', { fromSlashCommand: true, additionalPrompt: prompt }); }, 1); return ''; From a5ee46cb2a4c0ba3aa1fea2ff3ddfb2c21753a28 Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Mon, 19 Feb 2024 22:36:32 +0100 Subject: [PATCH 4/8] Only respect slash command, ignore text field --- public/script.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/script.js b/public/script.js index 188e0900f..c38eab959 100644 --- a/public/script.js +++ b/public/script.js @@ -8682,8 +8682,8 @@ jQuery(async function () { const fromSlashCommand = customData?.fromSlashCommand || false; var id = $(this).attr('id'); - // Check whether a custom prompt was provided via custom data (for example through a slash command), otherwise fall back and use the text from the textbox, if there is any - const additionalPrompt = (customData?.additionalPrompt && customData.additionalPrompt.trim()) || (String($('#send_textarea').val()).trim() || undefined); + // Check whether a custom prompt was provided via custom data (for example through a slash command) + const additionalPrompt = customData?.additionalPrompt?.trim() || undefined; const buildOrFillAdditionalArgs = (args = {}) => ({ ...args, ...(additionalPrompt !== undefined && { quiet_prompt: additionalPrompt, quietToLoud: true }), From 061b7c6922c9530101566888c14c021c5b3fc5d8 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Tue, 20 Feb 2024 02:09:01 +0200 Subject: [PATCH 5/8] Don't try to execute script commands if the message doesn't start with slash --- public/script.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/public/script.js b/public/script.js index b9df03842..3087922b8 100644 --- a/public/script.js +++ b/public/script.js @@ -2270,12 +2270,21 @@ export async function generateQuietPrompt(quiet_prompt, quietToLoud, skipWIAN, q return generateFinished; } +/** + * Executes slash commands and returns the new text and whether the generation was interrupted. + * @param {string} message Text to be sent + * @returns {Promise} Whether the message sending was interrupted + */ async function processCommands(message) { + if (!message || !message.trim().startsWith('/')) { + return false; + } + const previousText = String($('#send_textarea').val()); const result = await executeSlashCommands(message); if (!result || typeof result !== 'object') { - return null; + return false; } const currentText = String($('#send_textarea').val()); @@ -2878,7 +2887,7 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu let message_already_generated = isImpersonate ? `${name1}: ` : `${name2}: `; if (!(dryRun || type == 'regenerate' || type == 'swipe' || type == 'quiet')) { - const interruptedByCommand = await processCommands($('#send_textarea').val()); + const interruptedByCommand = await processCommands(String($('#send_textarea').val())); if (interruptedByCommand) { //$("#send_textarea").val('').trigger('input'); From 8e66a14e37453bedb7564b4efd19739c332e4c82 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Tue, 20 Feb 2024 02:29:14 +0200 Subject: [PATCH 6/8] Add hints to doc strings about additional command prompts --- public/script.js | 2 +- public/scripts/slash-commands.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/script.js b/public/script.js index 064f5a46c..8ee918f39 100644 --- a/public/script.js +++ b/public/script.js @@ -8042,7 +8042,7 @@ jQuery(async function () { registerSlashCommand('dupe', DupeChar, [], '– duplicates the currently selected character', true, true); registerSlashCommand('api', connectAPISlash, [], `(${Object.keys(CONNECT_API_MAP).join(', ')}) – connect to an API`, true, true); - registerSlashCommand('impersonate', doImpersonate, ['imp'], '– calls an impersonation response', true, true); + registerSlashCommand('impersonate', doImpersonate, ['imp'], '[prompt] – calls an impersonation response, with an optional additional prompt', true, true); registerSlashCommand('delchat', doDeleteChat, [], '– deletes the current chat', true, true); registerSlashCommand('getchatname', doGetChatName, [], '– returns the name of the current chat file into the pipe', false, true); registerSlashCommand('closechat', doCloseChat, [], '– closes the current chat', true, true); diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index 820b43ea6..fdfc1acd4 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -150,7 +150,7 @@ parser.addCommand('comment', sendCommentMessage, [], '(t parser.addCommand('single', setStoryModeCallback, ['story'], ' – sets the message style to single document mode without names or avatars visible', true, true); parser.addCommand('bubble', setBubbleModeCallback, ['bubbles'], ' – sets the message style to bubble chat mode', true, true); parser.addCommand('flat', setFlatModeCallback, ['default'], ' – sets the message style to flat chat mode', true, true); -parser.addCommand('continue', continueChatCallback, ['cont'], ' – continues the last message in the chat', true, true); +parser.addCommand('continue', continueChatCallback, ['cont'], '[prompt] – continues the last message in the chat, with an optional additional prompt', true, true); parser.addCommand('go', goToCharacterCallback, ['char'], '(name) – opens up a chat with the character or group by its name', true, true); parser.addCommand('sysgen', generateSystemMessage, [], '(prompt) – generates a system message using a specified prompt', true, true); parser.addCommand('ask', askCharacter, [], '(prompt) – asks a specified character card a prompt', true, true); From 095cd873ded1812a384edec7813eb9ac93eee2c7 Mon Sep 17 00:00:00 2001 From: Sneha C <136328295+underscorex86@users.noreply.github.com> Date: Tue, 20 Feb 2024 16:48:43 +0400 Subject: [PATCH 7/8] Update slash-commands.js added the word "persona" to the /sync description to make it easier for users to find. --- public/scripts/slash-commands.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index fdfc1acd4..ddc31dfc9 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -140,7 +140,7 @@ const getSlashCommandsHelp = parser.getHelpString.bind(parser); parser.addCommand('?', helpCommandCallback, ['help'], ' – get help on macros, chat formatting and commands', true, true); parser.addCommand('name', setNameCallback, ['persona'], '(name) – sets user name and persona avatar (if set)', true, true); -parser.addCommand('sync', syncCallback, [], ' – syncs user name in user-attributed messages in the current chat', true, true); +parser.addCommand('sync', syncCallback, [], ' – syncs user name persona in user-attributed messages in the current chat', true, true); parser.addCommand('lock', bindCallback, ['bind'], ' – locks/unlocks a persona (name and avatar) to the current chat', true, true); parser.addCommand('bg', setBackgroundCallback, ['background'], '(filename) – sets a background according to filename, partial names allowed', false, true); parser.addCommand('sendas', sendMessageAs, [], ' – sends message as a specific character. Uses character avatar if it exists in the characters list. Example that will send "Hello, guys!" from "Chloe": /sendas name="Chloe" Hello, guys!', true, true); From f0141b4dd13c780f445454aa85f5118fd6dbc45a Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Tue, 20 Feb 2024 16:57:00 +0200 Subject: [PATCH 8/8] Update slash-commands.js --- public/scripts/slash-commands.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index ddc31dfc9..e6c50ebb3 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -140,7 +140,7 @@ const getSlashCommandsHelp = parser.getHelpString.bind(parser); parser.addCommand('?', helpCommandCallback, ['help'], ' – get help on macros, chat formatting and commands', true, true); parser.addCommand('name', setNameCallback, ['persona'], '(name) – sets user name and persona avatar (if set)', true, true); -parser.addCommand('sync', syncCallback, [], ' – syncs user name persona in user-attributed messages in the current chat', true, true); +parser.addCommand('sync', syncCallback, [], ' – syncs the user persona in user-attributed messages in the current chat', true, true); parser.addCommand('lock', bindCallback, ['bind'], ' – locks/unlocks a persona (name and avatar) to the current chat', true, true); parser.addCommand('bg', setBackgroundCallback, ['background'], '(filename) – sets a background according to filename, partial names allowed', false, true); parser.addCommand('sendas', sendMessageAs, [], ' – sends message as a specific character. Uses character avatar if it exists in the characters list. Example that will send "Hello, guys!" from "Chloe": /sendas name="Chloe" Hello, guys!', true, true);