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) {