diff --git a/public/script.js b/public/script.js
index 6cf82dc27..9093abf67 100644
--- a/public/script.js
+++ b/public/script.js
@@ -172,6 +172,7 @@ import {
importTags,
tag_filter_types,
compareTagsForSort,
+ initTags,
} from './scripts/tags.js';
import {
SECRET_KEYS,
@@ -413,6 +414,7 @@ export const event_types = {
CHARACTER_FIRST_MESSAGE_SELECTED: 'character_first_message_selected',
// TODO: Naming convention is inconsistent with other events
CHARACTER_DELETED: 'characterDeleted',
+ CHARACTER_DUPLICATED: 'character_duplicated',
};
export const eventSource = new EventEmitter();
@@ -865,6 +867,7 @@ async function firstLoadInit() {
getSystemMessages();
sendSystemMessage(system_message_types.WELCOME);
initLocales();
+ initTags();
await getUserAvatars(true, user_avatar);
await getCharacters();
await getBackgrounds();
@@ -1238,7 +1241,7 @@ function getCharacterBlock(item, id) {
const template = $('#character_template .character_select').clone();
template.attr({ 'chid': id, 'id': `CharID${id}` });
template.find('img').attr('src', this_avatar).attr('alt', item.name);
- template.find('.avatar').attr('title', `[Character] ${item.name}`);
+ template.find('.avatar').attr('title', `[Character] ${item.name}\nFile: ${item.avatar}`);
template.find('.ch_name').text(item.name).attr('title', `[Character] ${item.name}`);
if (power_user.show_card_avatar_urls) {
template.find('.ch_avatar_url').text(item.avatar);
@@ -1856,6 +1859,7 @@ function insertSVGIcon(mes, extra) {
function getMessageFromTemplate({
mesId,
+ swipeId,
characterName,
isUser,
avatarImg,
@@ -1873,6 +1877,7 @@ function getMessageFromTemplate({
const mes = messageTemplate.clone();
mes.attr({
'mesid': mesId,
+ 'swipeid': swipeId,
'ch_name': characterName,
'is_user': isUser,
'is_system': !!isSystem,
@@ -2019,6 +2024,7 @@ function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll = true
let params = {
mesId: forceId ?? chat.length - 1,
+ swipeId: mes.swipe_id ?? 0,
characterName: mes.name,
isUser: mes.is_user,
avatarImg: avatarImg,
@@ -2728,9 +2734,7 @@ class StreamingProcessor {
const continueMsg = this.type === 'continue' ? this.messageAlreadyGenerated : undefined;
saveLogprobsForActiveMessage(this.messageLogprobs.filter(Boolean), continueMsg);
await saveChatConditional();
- activateSendButtons();
- showSwipeButtons();
- setGenerationProgress(0);
+ unblockGeneration();
generatedPromptCache = '';
//console.log("Generated text size:", text.length, text)
@@ -2773,11 +2777,8 @@ class StreamingProcessor {
this.isStopped = true;
this.hideMessageButtons(this.messageId);
- $('#send_textarea').removeAttr('disabled');
- is_send_press = false;
- activateSendButtons();
- setGenerationProgress(0);
- showSwipeButtons();
+ generatedPromptCache = '';
+ unblockGeneration();
}
setFirstSwipe(messageId) {
@@ -3968,6 +3969,8 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu
toastr.error(exception.error.message, 'Error', { timeOut: 10000, extendedTimeOut: 20000 });
}
+ generatedPromptCache = '';
+
unblockGeneration();
console.log(exception);
streamingProcessor = null;
@@ -4179,9 +4182,10 @@ export function removeMacros(str) {
* @param {string} messageText Message text.
* @param {string} messageBias Message bias.
* @param {number} [insertAt] Optional index to insert the message at.
+ * @params {boolean} [compact] Send as a compact display message.
* @returns {Promise} A promise that resolves when the message is inserted.
*/
-export async function sendMessageAsUser(messageText, messageBias, insertAt = null) {
+export async function sendMessageAsUser(messageText, messageBias, insertAt = null, compact = false) {
messageText = getRegexedString(messageText, regex_placement.USER_INPUT);
const message = {
@@ -4190,7 +4194,9 @@ export async function sendMessageAsUser(messageText, messageBias, insertAt = nul
is_system: false,
send_date: getMessageTimeStamp(),
mes: substituteParams(messageText),
- extra: {},
+ extra: {
+ isSmallSys: compact,
+ },
};
if (power_user.message_token_count_enabled) {
@@ -4318,6 +4324,8 @@ async function DupeChar() {
});
if (response.ok) {
toastr.success('Character Duplicated');
+ const data = await response.json();
+ await eventSource.emit(event_types.CHARACTER_DUPLICATED, { oldAvatar: body.avatar_url, newAvatar: data.path });
getCharacters();
}
}
@@ -5736,6 +5744,12 @@ export function setUserAvatar(imgfile) {
$('.zoomed_avatar[forchar]').remove();
}
+export function retriggerFirstMessageOnEmptyChat() {
+ if (this_chid >= 0 && !selected_group && chat.length === 1) {
+ $('#firstmessage_textarea').trigger('input');
+ }
+}
+
async function uploadUserAvatar(e) {
const file = e.target.files[0];
@@ -8470,9 +8484,7 @@ jQuery(async function () {
setUserAvatar(imgfile);
// force firstMes {{user}} update on persona switch
- if (this_chid >= 0 && !selected_group && chat.length === 1) {
- $('#firstmessage_textarea').trigger('input');
- }
+ retriggerFirstMessageOnEmptyChat();
});
$(document).on('click', '#user_avatar_block .avatar_upload', function () {
$('#avatar_upload_overwrite').val('');
@@ -8597,11 +8609,15 @@ jQuery(async function () {
await clearChat();
chat.length = 0;
- chat_file_for_del = getCurrentChatDetails().sessionName;
- const isDelChatCheckbox = document.getElementById('del_chat_checkbox').checked;
+ chat_file_for_del = getCurrentChatDetails()?.sessionName;
+ const isDelChatCheckbox = document.getElementById('del_chat_checkbox')?.checked;
+
+ // Make it easier to find in backups
+ if (isDelChatCheckbox) {
+ await saveChatConditional();
+ }
if (selected_group) {
- //Fix it; When you're creating a new group chat (but not when initially converting from the existing regular chat), the first greeting message doesn't automatically get translated.
await createNewGroupChat(selected_group);
if (isDelChatCheckbox) await deleteGroupChat(selected_group, chat_file_for_del);
}
@@ -8664,14 +8680,13 @@ jQuery(async function () {
$('#form_create').submit(createOrEditCharacter);
$('#delete_button').on('click', function () {
- popup_type = 'del_ch';
callPopup(`
Delete the character?
THIS IS PERMANENT!
`,
+ Also delete the chat files
+ `, 'del_ch', '',
);
});
@@ -8979,7 +8994,7 @@ jQuery(async function () {
`, 'new_chat', '');
}
@@ -9576,6 +9591,7 @@ jQuery(async function () {
const userName = String($('#your_name').val()).trim();
setUserName(userName);
await updatePersonaNameIfExists(user_avatar, userName);
+ retriggerFirstMessageOnEmptyChat();
});
$('#sync_name_button').on('click', async function () {
diff --git a/public/scripts/BulkEditOverlay.js b/public/scripts/BulkEditOverlay.js
index c2d3e44e8..8da4dc86b 100644
--- a/public/scripts/BulkEditOverlay.js
+++ b/public/scripts/BulkEditOverlay.js
@@ -50,16 +50,24 @@ class CharacterContextMenu {
* Duplicate one or more characters
*
* @param characterId
- * @returns {Promise}
+ * @returns {Promise}
*/
static duplicate = async (characterId) => {
const character = CharacterContextMenu.#getCharacter(characterId);
+ const body = { avatar_url: character.avatar };
- return fetch('/api/characters/duplicate', {
+ const result = await fetch('/api/characters/duplicate', {
method: 'POST',
headers: getRequestHeaders(),
- body: JSON.stringify({ avatar_url: character.avatar }),
+ body: JSON.stringify(body),
});
+
+ if (!result.ok) {
+ throw new Error('Character not duplicated');
+ }
+
+ const data = await result.json();
+ await eventSource.emit(event_types.CHARACTER_DUPLICATED, { oldAvatar: body.avatar_url, newAvatar: data.path });
};
/**
diff --git a/public/scripts/RossAscends-mods.js b/public/scripts/RossAscends-mods.js
index 9aa3430a7..054641268 100644
--- a/public/scripts/RossAscends-mods.js
+++ b/public/scripts/RossAscends-mods.js
@@ -403,6 +403,7 @@ function saveUserInput() {
const userInput = String($('#send_textarea').val());
SaveLocal('userInput', userInput);
}
+const saveUserInputDebounced = debounce(saveUserInput);
// Make the DIV element draggable:
@@ -662,6 +663,30 @@ export async function initMovingUI() {
}
}
+/**@type {HTMLTextAreaElement} */
+const sendTextArea = document.querySelector('#send_textarea');
+const chatBlock = document.getElementById('chat');
+const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
+
+/**
+ * this makes the chat input text area resize vertically to match the text size (limited by CSS at 50% window height)
+ */
+function autoFitSendTextArea() {
+ const originalScrollBottom = chatBlock.scrollHeight - (chatBlock.scrollTop + chatBlock.offsetHeight);
+ if (sendTextArea.scrollHeight == sendTextArea.offsetHeight) {
+ // Needs to be pulled dynamically because it is affected by font size changes
+ const sendTextAreaMinHeight = window.getComputedStyle(sendTextArea).getPropertyValue('min-height');
+ sendTextArea.style.height = sendTextAreaMinHeight;
+ }
+ sendTextArea.style.height = sendTextArea.scrollHeight + 0.3 + 'px';
+
+ if (!isFirefox) {
+ const newScrollTop = Math.round(chatBlock.scrollHeight - (chatBlock.offsetHeight + originalScrollBottom));
+ chatBlock.scrollTop = newScrollTop;
+ }
+}
+export const autoFitSendTextAreaDebounced = debounce(autoFitSendTextArea);
+
// ---------------------------------------------------
export function initRossMods() {
@@ -824,19 +849,13 @@ export function initRossMods() {
saveSettingsDebounced();
});
- //this makes the chat input text area resize vertically to match the text size (limited by CSS at 50% window height)
- $('#send_textarea').on('input', function () {
- const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
- const chatBlock = $('#chat');
- const originalScrollBottom = chatBlock[0].scrollHeight - (chatBlock.scrollTop() + chatBlock.outerHeight());
- this.style.height = window.getComputedStyle(this).getPropertyValue('min-height');
- this.style.height = this.scrollHeight + 0.3 + 'px';
-
- if (!isFirefox) {
- const newScrollTop = Math.round(chatBlock[0].scrollHeight - (chatBlock.outerHeight() + originalScrollBottom));
- chatBlock.scrollTop(newScrollTop);
+ $(sendTextArea).on('input', () => {
+ if (sendTextArea.scrollHeight > sendTextArea.offsetHeight || sendTextArea.value === '') {
+ autoFitSendTextArea();
+ } else {
+ autoFitSendTextAreaDebounced();
}
- saveUserInput();
+ saveUserInputDebounced();
});
restoreUserInput();
@@ -891,23 +910,30 @@ export function initRossMods() {
processHotkeys(event.originalEvent);
});
+ const hotkeyTargets = {
+ 'send_textarea': sendTextArea,
+ 'dialogue_popup_input': document.querySelector('#dialogue_popup_input'),
+ };
+
//Additional hotkeys CTRL+ENTER and CTRL+UPARROW
/**
* @param {KeyboardEvent} event
*/
function processHotkeys(event) {
//Enter to send when send_textarea in focus
- if ($(':focus').attr('id') === 'send_textarea') {
+ if (document.activeElement == hotkeyTargets['send_textarea']) {
const sendOnEnter = shouldSendOnEnter();
if (!event.shiftKey && !event.ctrlKey && !event.altKey && event.key == 'Enter' && sendOnEnter) {
event.preventDefault();
sendTextareaMessage();
+ return;
}
}
- if ($(':focus').attr('id') === 'dialogue_popup_input' && !isMobile()) {
+ if (document.activeElement == hotkeyTargets['dialogue_popup_input'] && !isMobile()) {
if (!event.shiftKey && !event.ctrlKey && event.key == 'Enter') {
event.preventDefault();
$('#dialogue_popup_ok').trigger('click');
+ return;
}
}
//ctrl+shift+up to scroll to context line
@@ -919,6 +945,7 @@ export function initRossMods() {
scrollTop: contextLine.offset().top - $('#chat').offset().top + $('#chat').scrollTop(),
}, 300);
} else { toastr.warning('Context line not found, send a message first!'); }
+ return;
}
//ctrl+shift+down to scroll to bottom of chat
if (event.shiftKey && event.ctrlKey && event.key == 'ArrowDown') {
@@ -926,6 +953,7 @@ export function initRossMods() {
$('#chat').animate({
scrollTop: $('#chat').prop('scrollHeight'),
}, 300);
+ return;
}
// Alt+Enter or AltGr+Enter to Continue
@@ -933,6 +961,7 @@ export function initRossMods() {
if (is_send_press == false) {
console.debug('Continuing with Alt+Enter');
$('#option_continue').trigger('click');
+ return;
}
}
@@ -942,6 +971,7 @@ export function initRossMods() {
if (editMesDone.length > 0) {
console.debug('Accepting edits with Ctrl+Enter');
editMesDone.trigger('click');
+ return;
} else if (is_send_press == false) {
const skipConfirmKey = 'RegenerateWithCtrlEnter';
const skipConfirm = LoadLocalBool(skipConfirmKey);
@@ -968,6 +998,7 @@ export function initRossMods() {
doRegenerate();
});
}
+ return;
} else {
console.debug('Ctrl+Enter ignored');
}
@@ -976,7 +1007,7 @@ export function initRossMods() {
// Helper function to check if nanogallery2's lightbox is active
function isNanogallery2LightboxActive() {
// Check if the body has the 'nGY2On' class, adjust this based on actual behavior
- return $('body').hasClass('nGY2_body_scrollbar');
+ return document.body.classList.contains('nGY2_body_scrollbar');
}
if (event.key == 'ArrowLeft') { //swipes left
@@ -989,6 +1020,7 @@ export function initRossMods() {
!isInputElementInFocus()
) {
$('.swipe_left:last').click();
+ return;
}
}
if (event.key == 'ArrowRight') { //swipes right
@@ -1001,13 +1033,14 @@ export function initRossMods() {
!isInputElementInFocus()
) {
$('.swipe_right:last').click();
+ return;
}
}
if (event.ctrlKey && event.key == 'ArrowUp') { //edits last USER message if chatbar is empty and focused
if (
- $('#send_textarea').val() === '' &&
+ hotkeyTargets['send_textarea'].value === '' &&
chatbarInFocus === true &&
($('.swipe_right:last').css('display') === 'flex' || $('.last_mes').attr('is_system') === 'true') &&
$('#character_popup').css('display') === 'none' &&
@@ -1018,6 +1051,7 @@ export function initRossMods() {
const editMes = lastIsUserMes.querySelector('.mes_block .mes_edit');
if (editMes !== null) {
$(editMes).trigger('click');
+ return;
}
}
}
@@ -1025,7 +1059,7 @@ export function initRossMods() {
if (event.key == 'ArrowUp') { //edits last message if chatbar is empty and focused
console.log('got uparrow input');
if (
- $('#send_textarea').val() === '' &&
+ hotkeyTargets['send_textarea'].value === '' &&
chatbarInFocus === true &&
//$('.swipe_right:last').css('display') === 'flex' &&
$('.last_mes .mes_buttons').is(':visible') &&
@@ -1036,6 +1070,7 @@ export function initRossMods() {
const editMes = lastMes.querySelector('.mes_block .mes_edit');
if (editMes !== null) {
$(editMes).click();
+ return;
}
}
}
diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js
index 57da890ee..27708edc5 100644
--- a/public/scripts/group-chats.js
+++ b/public/scripts/group-chats.js
@@ -189,6 +189,8 @@ export async function getGroupChat(groupId) {
await printMessages();
} else {
sendSystemMessage(system_message_types.GROUP, '', { isSmallSys: true });
+ await eventSource.emit(event_types.MESSAGE_RECEIVED, (chat.length - 1));
+ await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, (chat.length - 1));
if (group && Array.isArray(group.members)) {
for (let member of group.members) {
const character = characters.find(x => x.avatar === member || x.name === member);
@@ -199,7 +201,9 @@ export async function getGroupChat(groupId) {
const mes = await getFirstCharacterMessage(character);
chat.push(mes);
+ await eventSource.emit(event_types.MESSAGE_RECEIVED, (chat.length - 1));
addOneMessage(mes);
+ await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, (chat.length - 1));
}
}
await saveGroupChat(groupId, false);
diff --git a/public/scripts/openai.js b/public/scripts/openai.js
index 5acecfe7d..3a722dc97 100644
--- a/public/scripts/openai.js
+++ b/public/scripts/openai.js
@@ -3507,11 +3507,11 @@ async function onModelChange() {
if (oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) {
if (oai_settings.max_context_unlocked) {
$('#openai_max_context').attr('max', unlocked_max);
- } else if (value === 'gemini-1.5-pro') {
+ } else if (value === 'gemini-1.5-pro-latest') {
$('#openai_max_context').attr('max', max_1mil);
- } else if (value === 'gemini-pro') {
+ } else if (value === 'gemini-ultra' || value === 'gemini-1.0-pro-latest' || value === 'gemini-pro' || value === 'gemini-1.0-ultra-latest') {
$('#openai_max_context').attr('max', max_32k);
- } else if (value === 'gemini-pro-vision') {
+ } else if (value === 'gemini-1.0-pro-vision-latest' || value === 'gemini-pro-vision') {
$('#openai_max_context').attr('max', max_16k);
} else {
$('#openai_max_context').attr('max', max_8k);
@@ -3939,21 +3939,26 @@ export function isImageInliningSupported() {
return false;
}
- const gpt4v = 'gpt-4-vision';
- const geminiProV = 'gemini-pro-vision';
- const claude = 'claude-3';
-
if (!oai_settings.image_inlining) {
return false;
}
+ // gultra just isn't being offered as multimodal, thanks google.
+ const visionSupportedModels = [
+ 'gpt-4-vision',
+ 'gemini-1.0-pro-vision-latest',
+ 'gemini-1.5-pro-latest',
+ 'gemini-pro-vision',
+ 'claude-3'
+ ];
+
switch (oai_settings.chat_completion_source) {
case chat_completion_sources.OPENAI:
- return oai_settings.openai_model.includes(gpt4v);
+ return visionSupportedModels.some(model => oai_settings.openai_model.includes(model));
case chat_completion_sources.MAKERSUITE:
- return oai_settings.google_model.includes(geminiProV);
+ return visionSupportedModels.some(model => oai_settings.google_model.includes(model));
case chat_completion_sources.CLAUDE:
- return oai_settings.claude_model.includes(claude);
+ return visionSupportedModels.some(model => oai_settings.claude_model.includes(model));
case chat_completion_sources.OPENROUTER:
return !oai_settings.openrouter_force_instruct;
case chat_completion_sources.CUSTOM:
diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js
index 76d4b11db..20f3e8666 100644
--- a/public/scripts/slash-commands.js
+++ b/public/scripts/slash-commands.js
@@ -22,6 +22,7 @@ import {
name1,
reloadCurrentChat,
removeMacros,
+ retriggerFirstMessageOnEmptyChat,
saveChatConditional,
sendMessageAsUser,
sendSystemMessage,
@@ -203,10 +204,10 @@ parser.addCommand('name', setNameCallback, ['persona'], '(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);
-parser.addCommand('sys', sendNarratorMessage, ['nar'], '(text) – sends message as a system narrator', false, true);
+parser.addCommand('sendas', sendMessageAs, [], '[name=CharName compact=true/false (text)] – 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!. If "compact" is set to true, the message is sent using a compact layout.', true, true);
+parser.addCommand('sys', sendNarratorMessage, ['nar'], '[compact=true/false (text)] – sends message as a system narrator. If "compact" is set to true, the message is sent using a compact layout.', false, true);
parser.addCommand('sysname', setNarratorName, [], '(name) – sets a name for future system narrator messages in this chat (display only). Default: System. Leave empty to reset.', true, true);
-parser.addCommand('comment', sendCommentMessage, [], '(text) – adds a note/comment message not part of the chat', false, true);
+parser.addCommand('comment', sendCommentMessage, [], '[compact=true/false (text)] – adds a note/comment message not part of the chat. If "compact" is set to true, the message is sent using a compact layout.', false, true);
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);
@@ -215,7 +216,7 @@ parser.addCommand('go', goToCharacterCallback, ['char'], '(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);
parser.addCommand('delname', deleteMessagesByNameCallback, ['cancel'], '(name) – deletes all messages attributed to a specified name', true, true);
-parser.addCommand('send', sendUserMessageCallback, [], '(text) – adds a user message to the chat log without triggering a generation', true, true);
+parser.addCommand('send', sendUserMessageCallback, [], '[compact=true/false (text)] – adds a user message to the chat log without triggering a generation. If "compact" is set to true, the message is sent using a compact layout.', true, true);
parser.addCommand('trigger', triggerGenerationCallback, [], ' await=true/false – triggers a message generation. If in group, can trigger a message for the specified group member index or name. If await=true named argument passed, the command will await for the triggered generation before continuing.', true, true);
parser.addCommand('hide', hideMessageCallback, [], '(message index or range) – hides a chat message from the prompt', true, true);
parser.addCommand('unhide', unhideMessageCallback, [], '(message index or range) – unhides a message from the prompt', true, true);
@@ -1175,9 +1176,10 @@ async function sendUserMessageCallback(args, text) {
}
text = text.trim();
+ const compact = isTrueBoolean(args?.compact);
const bias = extractMessageBias(text);
const insertAt = Number(resolveVariable(args?.at));
- await sendMessageAsUser(text, bias, insertAt);
+ await sendMessageAsUser(text, bias, insertAt, compact);
return '';
}
@@ -1340,12 +1342,14 @@ function setNameCallback(_, name) {
for (let persona of Object.values(power_user.personas)) {
if (persona.toLowerCase() === name.toLowerCase()) {
autoSelectPersona(name);
+ retriggerFirstMessageOnEmptyChat();
return;
}
}
// Otherwise, set just the name
setUserName(name); //this prevented quickReply usage
+ retriggerFirstMessageOnEmptyChat();
}
async function setNarratorName(_, text) {
@@ -1388,6 +1392,7 @@ export async function sendMessageAs(args, text) {
// Messages that do nothing but set bias will be hidden from the context
const bias = extractMessageBias(mesText);
const isSystem = bias && !removeMacros(mesText).length;
+ const compact = isTrueBoolean(args?.compact);
const character = characters.find(x => x.name === name);
let force_avatar, original_avatar;
@@ -1412,6 +1417,7 @@ export async function sendMessageAs(args, text) {
extra: {
bias: bias.trim().length ? bias : null,
gen_id: Date.now(),
+ isSmallSys: compact,
},
};
@@ -1441,6 +1447,7 @@ export async function sendNarratorMessage(args, text) {
// Messages that do nothing but set bias will be hidden from the context
const bias = extractMessageBias(text);
const isSystem = bias && !removeMacros(text).length;
+ const compact = isTrueBoolean(args?.compact);
const message = {
name: name,
@@ -1453,6 +1460,7 @@ export async function sendNarratorMessage(args, text) {
type: system_message_types.NARRATOR,
bias: bias.trim().length ? bias : null,
gen_id: Date.now(),
+ isSmallSys: compact,
},
};
@@ -1517,6 +1525,7 @@ async function sendCommentMessage(args, text) {
return;
}
+ const compact = isTrueBoolean(args?.compact);
const message = {
name: COMMENT_NAME_DEFAULT,
is_user: false,
@@ -1527,6 +1536,7 @@ async function sendCommentMessage(args, text) {
extra: {
type: system_message_types.COMMENT,
gen_id: Date.now(),
+ isSmallSys: compact,
},
};
diff --git a/public/scripts/tags.js b/public/scripts/tags.js
index c4bf4626c..8895545e8 100644
--- a/public/scripts/tags.js
+++ b/public/scripts/tags.js
@@ -8,6 +8,8 @@ import {
entitiesFilter,
printCharacters,
buildAvatarList,
+ eventSource,
+ event_types,
} from '../script.js';
// eslint-disable-next-line no-unused-vars
import { FILTER_TYPES, FILTER_STATES, isFilterState, FilterHelper } from './filters.js';
@@ -133,7 +135,7 @@ function filterByTagState(entities, { globalDisplayFilters = false, subForEntity
}
if (subForEntity !== undefined && subForEntity.type === 'tag') {
- entities = filterTagSubEntities(subForEntity.item, entities, { filterHidden : filterHidden });
+ entities = filterTagSubEntities(subForEntity.item, entities, { filterHidden: filterHidden });
}
return entities;
@@ -1177,7 +1179,7 @@ function onClearAllFiltersClick() {
// We have to manually go through the elements and unfilter by clicking...
// Thankfully nearly all filter controls are three-state-toggles
const filterTags = $('.rm_tag_controls .rm_tag_filter').find('.tag');
- for(const tag of filterTags) {
+ for (const tag of filterTags) {
const toggleState = $(tag).attr('data-toggle-state');
if (toggleState !== undefined && !isFilterState(toggleState ?? FILTER_STATES.UNDEFINED, FILTER_STATES.UNDEFINED)) {
toggleTagThreeState($(tag), { stateOverride: FILTER_STATES.UNDEFINED, simulateClick: true });
@@ -1188,7 +1190,17 @@ function onClearAllFiltersClick() {
$('#character_search_bar').val('').trigger('input');
}
-jQuery(() => {
+/**
+ * Copy tags from one character to another.
+ * @param {{oldAvatar: string, newAvatar: string}} data Event data
+ */
+function copyTags(data) {
+ const prevTagMap = tag_map[data.oldAvatar] || [];
+ const newTagMap = tag_map[data.newAvatar] || [];
+ tag_map[data.newAvatar] = Array.from(new Set([...prevTagMap, ...newTagMap]));
+}
+
+export function initTags() {
createTagInput('#tagInput', '#tagList', { tagOptions: { removable: true } });
createTagInput('#groupTagInput', '#groupTagList', { tagOptions: { removable: true } });
@@ -1205,5 +1217,5 @@ jQuery(() => {
$(document).on('click', '.tag_view_create', onTagCreateClick);
$(document).on('click', '.tag_view_backup', onTagsBackupClick);
$(document).on('click', '.tag_view_restore', onBackupRestoreClick);
-});
-
+ eventSource.on(event_types.CHARACTER_DUPLICATED, copyTags);
+}
diff --git a/public/scripts/templates/macros.html b/public/scripts/templates/macros.html
index 34b768ac1..a2e1ff009 100644
--- a/public/scripts/templates/macros.html
+++ b/public/scripts/templates/macros.html
@@ -33,7 +33,7 @@
{{time_UTC±#}} – the current time in the specified UTC time zone offset, e.g. UTC-4 or UTC+2
{{idle_duration}} – the time since the last user message was sent
{{bias "text here"}} – sets a behavioral bias for the AI until the next user input. Quotes around the text are important.
-
{{roll:(formula)}} – rolls a dice. (ex: >{{roll:1d6}&rcub will roll a 6-sided dice and return a number between 1 and 6)
+
{{roll:(formula)}} – rolls a dice. (ex: >{{roll:1d6}} will roll a 6-sided dice and return a number between 1 and 6)
{{random:(args)}} – returns a random item from the list. (ex: {{random:1,2,3,4}} will return 1 of the 4 numbers at random. Works with text lists too.
{{random::(arg1)::(arg2)}} – alternative syntax for random that allows to use commas in the list items.
{{banned "text here"}} – dynamically add text in the quotes to banned words sequences, if Text Generation WebUI backend used. Do nothing for others backends. Can be used anywhere (Character description, WI, AN, etc.) Quotes around the text are important.