diff --git a/public/script.js b/public/script.js index cda629c72..a27a0de46 100644 --- a/public/script.js +++ b/public/script.js @@ -745,8 +745,19 @@ const per_page_default = 50; var is_advanced_char_open = false; -export let menu_type = ''; //what is selected in the menu +/** + * The type of the right menu + * @typedef {'characters' | 'character_edit' | 'create' | 'group_edit' | 'group_create' | '' } MenuType + */ + +/** + * The type of the right menu that is currently open + * @type {MenuType} + */ +export let menu_type = ''; + export let selected_button = ''; //which button pressed + //create pole save let create_save = { name: '', @@ -5409,8 +5420,14 @@ export function resetChatState() { characters.length = 0; } +/** + * + * @param {'characters' | 'character_edit' | 'create' | 'group_edit' | 'group_create'} value + */ export function setMenuType(value) { menu_type = value; + // Allow custom CSS to see which menu type is active + document.getElementById('right-nav-panel').dataset.menuType = menu_type; } export function setExternalAbortController(controller) { @@ -6792,7 +6809,7 @@ export function select_selected_character(chid) { //character select //console.log('select_selected_character() -- starting with input of -- ' + chid + ' (name:' + characters[chid].name + ')'); select_rm_create(); - menu_type = 'character_edit'; + setMenuType('character_edit'); $('#delete_button').css('display', 'flex'); $('#export_button').css('display', 'flex'); var display_name = characters[chid].name; @@ -6868,7 +6885,7 @@ export function select_selected_character(chid) { } function select_rm_create() { - menu_type = 'create'; + setMenuType('create'); //console.log('select_rm_Create() -- selected button: '+selected_button); if (selected_button == 'create') { @@ -6929,7 +6946,7 @@ function select_rm_create() { function select_rm_characters() { const doFullRefresh = menu_type === 'characters'; - menu_type = 'characters'; + setMenuType('characters'); selectRightMenuWithAnimation('rm_characters_block'); printCharacters(doFullRefresh); } @@ -8954,7 +8971,6 @@ jQuery(async function () { $('#rm_button_settings').click(function () { selected_button = 'settings'; - menu_type = 'settings'; selectRightMenuWithAnimation('rm_api_block'); }); $('#rm_button_characters').click(function () { diff --git a/public/scripts/PromptManager.js b/public/scripts/PromptManager.js index 806b5f308..dbf7dd4ed 100644 --- a/public/scripts/PromptManager.js +++ b/public/scripts/PromptManager.js @@ -685,6 +685,23 @@ class PromptManager { this.log('Initialized'); } + /** + * Get the scroll position of the prompt manager + * @returns {number} - Scroll position of the prompt manager + */ + #getScrollPosition() { + return document.getElementById(this.configuration.prefix + 'prompt_manager')?.closest('.scrollableInner')?.scrollTop; + } + + /** + * Set the scroll position of the prompt manager + * @param {number} scrollPosition - The scroll position to set + */ + #setScrollPosition(scrollPosition) { + if (scrollPosition === undefined || scrollPosition === null) return; + document.getElementById(this.configuration.prefix + 'prompt_manager')?.closest('.scrollableInner')?.scrollTo(0, scrollPosition); + } + /** * Main rendering function * @@ -703,17 +720,21 @@ class PromptManager { this.tryGenerate().finally(async () => { this.profileEnd('filling context'); this.profileStart('render'); + const scrollPosition = this.#getScrollPosition(); await this.renderPromptManager(); await this.renderPromptManagerListItems(); this.makeDraggable(); + this.#setScrollPosition(scrollPosition); this.profileEnd('render'); }); } else { // Executed during live communication this.profileStart('render'); + const scrollPosition = this.#getScrollPosition(); await this.renderPromptManager(); await this.renderPromptManagerListItems(); this.makeDraggable(); + this.#setScrollPosition(scrollPosition); this.profileEnd('render'); } }).catch(() => { diff --git a/public/scripts/extensions/tts/sbvits2.js b/public/scripts/extensions/tts/sbvits2.js index ab41d4f6d..7f542ca6a 100644 --- a/public/scripts/extensions/tts/sbvits2.js +++ b/public/scripts/extensions/tts/sbvits2.js @@ -19,6 +19,8 @@ class SBVits2TtsProvider { * @returns {string} Processed text */ processText(text) { + // backup for auto_split + text = text.replace(/\n+/g, '
'); return text; } @@ -276,6 +278,8 @@ class SBVits2TtsProvider { const [model_id, speaker_id, style] = voiceId.split('-'); const params = new URLSearchParams(); + // restore for auto_split + inputText = inputText.replaceAll('
', '\n'); params.append('text', inputText); params.append('model_id', model_id); params.append('speaker_id', speaker_id); diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index 6e29ea39e..6f7729ce9 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -84,15 +84,21 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({ helpString: 'Get help on macros, chat formatting and commands.', })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ - name: 'name', + name: 'persona', callback: setNameCallback, - unnamedArgumentList: [ - new SlashCommandArgument( - 'persona', [ARGUMENT_TYPE.STRING], true, + namedArgumentList: [ + new SlashCommandNamedArgument( + 'mode', 'The mode for persona selection. ("lookup" = search for existing persona, "temp" = create a temporary name, set a temporary name, "all" = allow both in the same command)', + [ARGUMENT_TYPE.STRING], false, false, 'all', ['lookup', 'temp', 'all'], ), ], - helpString: 'Sets user name and persona avatar (if set).', - aliases: ['persona'], + unnamedArgumentList: [ + new SlashCommandArgument( + 'persona name', [ARGUMENT_TYPE.STRING], true, + ), + ], + helpString: 'Selects the given persona with its name and avatar (by name or avatar url). If no matching persona exists, applies a temporary name.', + aliases: ['name'], })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'sync', @@ -2372,26 +2378,44 @@ function setFlatModeCallback() { $('#chat_display').val(chat_styles.DEFAULT).trigger('change'); } -function setNameCallback(_, name) { +/** + * Sets a persona name and optionally an avatar. + * @param {{mode: 'lookup' | 'temp' | 'all'}} namedArgs Named arguments + * @param {string} name Name to set + * @returns {void} + */ +function setNameCallback({ mode = 'all' }, name) { if (!name) { - toastr.warning('you must specify a name to change to'); + toastr.warning('You must specify a name to change to'); + return; + } + + if (!['lookup', 'temp', 'all'].includes(mode)) { + toastr.warning('Mode must be one of "lookup", "temp" or "all"'); return; } name = name.trim(); - // If the name is a persona, auto-select it - for (let persona of Object.values(power_user.personas)) { - if (persona.toLowerCase() === name.toLowerCase()) { - autoSelectPersona(name); + // If the name matches a persona avatar, or a name, auto-select it + if (['lookup', 'all'].includes(mode)) { + let persona = Object.entries(power_user.personas).find(([avatar, _]) => avatar === name)?.[1]; + if (!persona) persona = Object.entries(power_user.personas).find(([_, personaName]) => personaName.toLowerCase() === name.toLowerCase())?.[1]; + if (persona) { + autoSelectPersona(persona); retriggerFirstMessageOnEmptyChat(); return; + } else if (mode === 'lookup') { + toastr.warning(`Persona ${name} not found`); + return; } } - // Otherwise, set just the name - setUserName(name); //this prevented quickReply usage - retriggerFirstMessageOnEmptyChat(); + if (['temp', 'all'].includes(mode)) { + // Otherwise, set just the name + setUserName(name); //this prevented quickReply usage + retriggerFirstMessageOnEmptyChat(); + } } async function setNarratorName(_, text) { diff --git a/public/scripts/utils.js b/public/scripts/utils.js index b192dd9f1..f688d5aa4 100644 --- a/public/scripts/utils.js +++ b/public/scripts/utils.js @@ -481,14 +481,16 @@ export function trimToEndSentence(input, include_newline = false) { return ''; } + const isEmoji = x => /(\p{Emoji_Presentation}|\p{Extended_Pictographic})/gu.test(x); const punctuation = new Set(['.', '!', '?', '*', '"', ')', '}', '`', ']', '$', '。', '!', '?', '”', ')', '】', '’', '」', '_']); // extend this as you see fit let last = -1; - for (let i = input.length - 1; i >= 0; i--) { - const char = input[i]; + const characters = Array.from(input); + for (let i = characters.length - 1; i >= 0; i--) { + const char = characters[i]; - if (punctuation.has(char)) { - if (i > 0 && /[\s\n]/.test(input[i - 1])) { + if (punctuation.has(char) || isEmoji(char)) { + if (i > 0 && /[\s\n]/.test(characters[i - 1])) { last = i - 1; } else { last = i; @@ -506,7 +508,7 @@ export function trimToEndSentence(input, include_newline = false) { return input.trimEnd(); } - return input.substring(0, last + 1).trimEnd(); + return characters.slice(0, last + 1).join('').trimEnd(); } export function trimToStartSentence(input) {