Merge remote-tracking branch 'upstream/staging' into improve-bulk-edit-and-fixes

This commit is contained in:
Wolfsblvt
2024-03-29 02:42:27 +01:00
16 changed files with 255 additions and 166 deletions

View File

@ -1,3 +1,4 @@
@echo off
pushd %~dp0 pushd %~dp0
set NODE_ENV=production set NODE_ENV=production
call npm install --no-audit --no-fund --quiet --omit=dev call npm install --no-audit --no-fund --quiet --omit=dev

58
package-lock.json generated
View File

@ -21,7 +21,7 @@
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.6",
"cors": "^2.8.5", "cors": "^2.8.5",
"csrf-csrf": "^2.2.3", "csrf-csrf": "^2.2.3",
"express": "^4.18.2", "express": "^4.19.2",
"form-data": "^4.0.0", "form-data": "^4.0.0",
"google-translate-api-browser": "^3.0.1", "google-translate-api-browser": "^3.0.1",
"gpt3-tokenizer": "^1.1.5", "gpt3-tokenizer": "^1.1.5",
@ -1752,15 +1752,16 @@
"version": "0.1.12" "version": "0.1.12"
}, },
"node_modules/express": { "node_modules/express": {
"version": "4.18.2", "version": "4.19.2",
"license": "MIT", "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"dependencies": { "dependencies": {
"accepts": "~1.3.8", "accepts": "~1.3.8",
"array-flatten": "1.1.1", "array-flatten": "1.1.1",
"body-parser": "1.20.1", "body-parser": "1.20.2",
"content-disposition": "0.5.4", "content-disposition": "0.5.4",
"content-type": "~1.0.4", "content-type": "~1.0.4",
"cookie": "0.5.0", "cookie": "0.6.0",
"cookie-signature": "1.0.6", "cookie-signature": "1.0.6",
"debug": "2.6.9", "debug": "2.6.9",
"depd": "2.0.0", "depd": "2.0.0",
@ -1791,55 +1792,14 @@
"node": ">= 0.10.0" "node": ">= 0.10.0"
} }
}, },
"node_modules/express/node_modules/body-parser": {
"version": "1.20.1",
"license": "MIT",
"dependencies": {
"bytes": "3.1.2",
"content-type": "~1.0.4",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.11.0",
"raw-body": "2.5.1",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/express/node_modules/bytes": {
"version": "3.1.2",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/express/node_modules/cookie": { "node_modules/express/node_modules/cookie": {
"version": "0.5.0", "version": "0.6.0",
"license": "MIT", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"engines": { "engines": {
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/express/node_modules/raw-body": {
"version": "2.5.1",
"license": "MIT",
"dependencies": {
"bytes": "3.1.2",
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/express/node_modules/safe-buffer": { "node_modules/express/node_modules/safe-buffer": {
"version": "5.2.1", "version": "5.2.1",
"funding": [ "funding": [

View File

@ -11,7 +11,7 @@
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.6",
"cors": "^2.8.5", "cors": "^2.8.5",
"csrf-csrf": "^2.2.3", "csrf-csrf": "^2.2.3",
"express": "^4.18.2", "express": "^4.19.2",
"form-data": "^4.0.0", "form-data": "^4.0.0",
"google-translate-api-browser": "^3.0.1", "google-translate-api-browser": "^3.0.1",
"gpt3-tokenizer": "^1.1.5", "gpt3-tokenizer": "^1.1.5",

View File

@ -2604,11 +2604,20 @@
<div> <div>
<h4 data-i18n="Google Model">Google Model</h4> <h4 data-i18n="Google Model">Google Model</h4>
<select id="model_google_select"> <select id="model_google_select">
<option value="gemini-1.5-pro">Gemini 1.5 Pro</option> <optgroup label="Latest">
<option value="gemini-pro">Gemini Pro</option> <!-- Points to 1.0, no default 1.5 endpoint -->
<option value="gemini-pro-vision">Gemini Pro Vision</option> <option value="gemini-pro">Gemini Pro</option>
<option value="text-bison-001">Bison Text</option> <option value="gemini-pro-vision">Gemini Pro Vision</option>
<option value="chat-bison-001">Bison Chat</option> <option value="gemini-ultra">Gemini Ultra</option>
<option value="text-bison-001">Bison Text</option>
<option value="chat-bison-001">Bison Chat</option>
</optgroup>
<optgroup label="Sub-versions">
<option value="gemini-1.5-pro-latest">Gemini 1.5 Pro</option>
<option value="gemini-1.0-pro-latest">Gemini 1.0 Pro</option>
<option value="gemini-1.0-pro-vision-latest">Gemini 1.0 Pro Vision</option>
<option value="gemini-1.0-ultra-latest">Gemini 1.0 Ultra</option>
</optgroup>
</select> </select>
</div> </div>
</form> </form>
@ -4101,7 +4110,7 @@
</div> </div>
<div id="tags_div"> <div id="tags_div">
<div class="tag_controls"> <div class="tag_controls">
<input id="tagInput" class="text_pole tag_input wide100p margin0" data-i18n="[placeholder]Search / Create Tags" placeholder="Search / Create tags" maxlength="50" /> <input id="tagInput" class="text_pole textarea_compact tag_input wide100p margin0" data-i18n="[placeholder]Search / Create Tags" placeholder="Search / Create tags" maxlength="50" />
<div class="tags_view menu_button fa-solid fa-tags" title="View all tags" data-i18n="[title]View all tags"></div> <div class="tags_view menu_button fa-solid fa-tags" title="View all tags" data-i18n="[title]View all tags"></div>
</div> </div>
<div id="tagList" class="tags"></div> <div id="tagList" class="tags"></div>
@ -4121,7 +4130,7 @@
</div> </div>
<div id="descriptionWrapper" class="flex-container flexFlowColumn flex1"> <div id="descriptionWrapper" class="flex-container flexFlowColumn flex1">
<hr> <hr>
<div id="description_div" class="marginBot5 flex-container alignitemscenter"> <div id="description_div" class="flex-container alignitemscenter">
<span data-i18n="Character Description">Description</span> <span data-i18n="Character Description">Description</span>
<i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="description_textarea" title="Expand the editor"></i> <i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="description_textarea" title="Expand the editor"></i>
<a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#character-description" class="notes-link" target="_blank"> <a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#character-description" class="notes-link" target="_blank">
@ -4134,7 +4143,7 @@
</div> </div>
</div> </div>
<div id="firstMessageWrapper" class="flex-container flexFlowColumn flex1"> <div id="firstMessageWrapper" class="flex-container flexFlowColumn flex1">
<div id="first_message_div" class="marginBot5 title_restorable"> <div id="first_message_div" class="title_restorable">
<div class="flex-container alignitemscenter flex1"> <div class="flex-container alignitemscenter flex1">
<span data-i18n="First message">First message</span> <span data-i18n="First message">First message</span>
<i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="firstmessage_textarea" title="Expand the editor"></i> <i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="firstmessage_textarea" title="Expand the editor"></i>
@ -4184,7 +4193,7 @@
</div> </div>
<div id="group_tags_div" class="wide100p"> <div id="group_tags_div" class="wide100p">
<div class="tag_controls"> <div class="tag_controls">
<input id="groupTagInput" class="text_pole tag_input flex1 margin0" data-i18n="[placeholder]Search / Create Tags" placeholder="Search / Create tags" maxlength="50" /> <input id="groupTagInput" class="text_pole textarea_compact tag_input flex1 margin0" data-i18n="[placeholder]Search / Create Tags" placeholder="Search / Create tags" maxlength="50" />
<div class="tags_view menu_button fa-solid fa-tags margin0" title="View all tags" data-i18n="[title]View all tags"></div> <div class="tags_view menu_button fa-solid fa-tags margin0" title="View all tags" data-i18n="[title]View all tags"></div>
</div> </div>
<div id="groupTagList" class="tags paddingTopBot5"></div> <div id="groupTagList" class="tags paddingTopBot5"></div>

View File

@ -172,6 +172,7 @@ import {
importTags, importTags,
tag_filter_types, tag_filter_types,
compareTagsForSort, compareTagsForSort,
initTags,
} from './scripts/tags.js'; } from './scripts/tags.js';
import { import {
SECRET_KEYS, SECRET_KEYS,
@ -413,6 +414,7 @@ export const event_types = {
CHARACTER_FIRST_MESSAGE_SELECTED: 'character_first_message_selected', CHARACTER_FIRST_MESSAGE_SELECTED: 'character_first_message_selected',
// TODO: Naming convention is inconsistent with other events // TODO: Naming convention is inconsistent with other events
CHARACTER_DELETED: 'characterDeleted', CHARACTER_DELETED: 'characterDeleted',
CHARACTER_DUPLICATED: 'character_duplicated',
}; };
export const eventSource = new EventEmitter(); export const eventSource = new EventEmitter();
@ -865,6 +867,7 @@ async function firstLoadInit() {
getSystemMessages(); getSystemMessages();
sendSystemMessage(system_message_types.WELCOME); sendSystemMessage(system_message_types.WELCOME);
initLocales(); initLocales();
initTags();
await getUserAvatars(true, user_avatar); await getUserAvatars(true, user_avatar);
await getCharacters(); await getCharacters();
await getBackgrounds(); await getBackgrounds();
@ -1238,7 +1241,7 @@ function getCharacterBlock(item, id) {
const template = $('#character_template .character_select').clone(); const template = $('#character_template .character_select').clone();
template.attr({ 'chid': id, 'id': `CharID${id}` }); template.attr({ 'chid': id, 'id': `CharID${id}` });
template.find('img').attr('src', this_avatar).attr('alt', item.name); 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}`); template.find('.ch_name').text(item.name).attr('title', `[Character] ${item.name}`);
if (power_user.show_card_avatar_urls) { if (power_user.show_card_avatar_urls) {
template.find('.ch_avatar_url').text(item.avatar); template.find('.ch_avatar_url').text(item.avatar);
@ -1856,6 +1859,7 @@ function insertSVGIcon(mes, extra) {
function getMessageFromTemplate({ function getMessageFromTemplate({
mesId, mesId,
swipeId,
characterName, characterName,
isUser, isUser,
avatarImg, avatarImg,
@ -1873,6 +1877,7 @@ function getMessageFromTemplate({
const mes = messageTemplate.clone(); const mes = messageTemplate.clone();
mes.attr({ mes.attr({
'mesid': mesId, 'mesid': mesId,
'swipeid': swipeId,
'ch_name': characterName, 'ch_name': characterName,
'is_user': isUser, 'is_user': isUser,
'is_system': !!isSystem, 'is_system': !!isSystem,
@ -2019,6 +2024,7 @@ function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll = true
let params = { let params = {
mesId: forceId ?? chat.length - 1, mesId: forceId ?? chat.length - 1,
swipeId: mes.swipe_id ?? 0,
characterName: mes.name, characterName: mes.name,
isUser: mes.is_user, isUser: mes.is_user,
avatarImg: avatarImg, avatarImg: avatarImg,
@ -2728,9 +2734,7 @@ class StreamingProcessor {
const continueMsg = this.type === 'continue' ? this.messageAlreadyGenerated : undefined; const continueMsg = this.type === 'continue' ? this.messageAlreadyGenerated : undefined;
saveLogprobsForActiveMessage(this.messageLogprobs.filter(Boolean), continueMsg); saveLogprobsForActiveMessage(this.messageLogprobs.filter(Boolean), continueMsg);
await saveChatConditional(); await saveChatConditional();
activateSendButtons(); unblockGeneration();
showSwipeButtons();
setGenerationProgress(0);
generatedPromptCache = ''; generatedPromptCache = '';
//console.log("Generated text size:", text.length, text) //console.log("Generated text size:", text.length, text)
@ -2773,11 +2777,8 @@ class StreamingProcessor {
this.isStopped = true; this.isStopped = true;
this.hideMessageButtons(this.messageId); this.hideMessageButtons(this.messageId);
$('#send_textarea').removeAttr('disabled'); generatedPromptCache = '';
is_send_press = false; unblockGeneration();
activateSendButtons();
setGenerationProgress(0);
showSwipeButtons();
} }
setFirstSwipe(messageId) { 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 }); toastr.error(exception.error.message, 'Error', { timeOut: 10000, extendedTimeOut: 20000 });
} }
generatedPromptCache = '';
unblockGeneration(); unblockGeneration();
console.log(exception); console.log(exception);
streamingProcessor = null; streamingProcessor = null;
@ -4179,9 +4182,10 @@ export function removeMacros(str) {
* @param {string} messageText Message text. * @param {string} messageText Message text.
* @param {string} messageBias Message bias. * @param {string} messageBias Message bias.
* @param {number} [insertAt] Optional index to insert the message at. * @param {number} [insertAt] Optional index to insert the message at.
* @params {boolean} [compact] Send as a compact display message.
* @returns {Promise<void>} A promise that resolves when the message is inserted. * @returns {Promise<void>} 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); messageText = getRegexedString(messageText, regex_placement.USER_INPUT);
const message = { const message = {
@ -4190,7 +4194,9 @@ export async function sendMessageAsUser(messageText, messageBias, insertAt = nul
is_system: false, is_system: false,
send_date: getMessageTimeStamp(), send_date: getMessageTimeStamp(),
mes: substituteParams(messageText), mes: substituteParams(messageText),
extra: {}, extra: {
isSmallSys: compact,
},
}; };
if (power_user.message_token_count_enabled) { if (power_user.message_token_count_enabled) {
@ -4318,6 +4324,8 @@ async function DupeChar() {
}); });
if (response.ok) { if (response.ok) {
toastr.success('Character Duplicated'); toastr.success('Character Duplicated');
const data = await response.json();
await eventSource.emit(event_types.CHARACTER_DUPLICATED, { oldAvatar: body.avatar_url, newAvatar: data.path });
getCharacters(); getCharacters();
} }
} }
@ -5736,6 +5744,12 @@ export function setUserAvatar(imgfile) {
$('.zoomed_avatar[forchar]').remove(); $('.zoomed_avatar[forchar]').remove();
} }
export function retriggerFirstMessageOnEmptyChat() {
if (this_chid >= 0 && !selected_group && chat.length === 1) {
$('#firstmessage_textarea').trigger('input');
}
}
async function uploadUserAvatar(e) { async function uploadUserAvatar(e) {
const file = e.target.files[0]; const file = e.target.files[0];
@ -8470,9 +8484,7 @@ jQuery(async function () {
setUserAvatar(imgfile); setUserAvatar(imgfile);
// force firstMes {{user}} update on persona switch // force firstMes {{user}} update on persona switch
if (this_chid >= 0 && !selected_group && chat.length === 1) { retriggerFirstMessageOnEmptyChat();
$('#firstmessage_textarea').trigger('input');
}
}); });
$(document).on('click', '#user_avatar_block .avatar_upload', function () { $(document).on('click', '#user_avatar_block .avatar_upload', function () {
$('#avatar_upload_overwrite').val(''); $('#avatar_upload_overwrite').val('');
@ -8597,11 +8609,15 @@ jQuery(async function () {
await clearChat(); await clearChat();
chat.length = 0; chat.length = 0;
chat_file_for_del = getCurrentChatDetails().sessionName; chat_file_for_del = getCurrentChatDetails()?.sessionName;
const isDelChatCheckbox = document.getElementById('del_chat_checkbox').checked; const isDelChatCheckbox = document.getElementById('del_chat_checkbox')?.checked;
// Make it easier to find in backups
if (isDelChatCheckbox) {
await saveChatConditional();
}
if (selected_group) { 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); await createNewGroupChat(selected_group);
if (isDelChatCheckbox) await deleteGroupChat(selected_group, chat_file_for_del); if (isDelChatCheckbox) await deleteGroupChat(selected_group, chat_file_for_del);
} }
@ -8664,14 +8680,13 @@ jQuery(async function () {
$('#form_create').submit(createOrEditCharacter); $('#form_create').submit(createOrEditCharacter);
$('#delete_button').on('click', function () { $('#delete_button').on('click', function () {
popup_type = 'del_ch';
callPopup(` callPopup(`
<h3>Delete the character?</h3> <h3>Delete the character?</h3>
<b>THIS IS PERMANENT!<br><br> <b>THIS IS PERMANENT!<br><br>
<label for="del_char_checkbox" class="checkbox_label justifyCenter"> <label for="del_char_checkbox" class="checkbox_label justifyCenter">
<input type="checkbox" id="del_char_checkbox" /> <input type="checkbox" id="del_char_checkbox" />
<span>Also delete the chat files</span> <small>Also delete the chat files</small>
</label><br></b>`, </label><br></b>`, 'del_ch', '',
); );
}); });
@ -8979,7 +8994,7 @@ jQuery(async function () {
<label for="del_chat_checkbox" class="checkbox_label justifyCenter" <label for="del_chat_checkbox" class="checkbox_label justifyCenter"
title="If necessary, you can later restore this chat file from the /backups folder"> title="If necessary, you can later restore this chat file from the /backups folder">
<input type="checkbox" id="del_chat_checkbox" /> <input type="checkbox" id="del_chat_checkbox" />
<span>Also delete the current chat file</span> <small>Also delete the current chat file</small>
</label><br> </label><br>
`, 'new_chat', ''); `, 'new_chat', '');
} }
@ -9576,6 +9591,7 @@ jQuery(async function () {
const userName = String($('#your_name').val()).trim(); const userName = String($('#your_name').val()).trim();
setUserName(userName); setUserName(userName);
await updatePersonaNameIfExists(user_avatar, userName); await updatePersonaNameIfExists(user_avatar, userName);
retriggerFirstMessageOnEmptyChat();
}); });
$('#sync_name_button').on('click', async function () { $('#sync_name_button').on('click', async function () {

View File

@ -50,16 +50,24 @@ class CharacterContextMenu {
* Duplicate one or more characters * Duplicate one or more characters
* *
* @param characterId * @param characterId
* @returns {Promise<Response>} * @returns {Promise<any>}
*/ */
static duplicate = async (characterId) => { static duplicate = async (characterId) => {
const character = CharacterContextMenu.#getCharacter(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', method: 'POST',
headers: getRequestHeaders(), 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 });
}; };
/** /**

View File

@ -403,6 +403,7 @@ function saveUserInput() {
const userInput = String($('#send_textarea').val()); const userInput = String($('#send_textarea').val());
SaveLocal('userInput', userInput); SaveLocal('userInput', userInput);
} }
const saveUserInputDebounced = debounce(saveUserInput);
// Make the DIV element draggable: // 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() { export function initRossMods() {
@ -824,19 +849,13 @@ export function initRossMods() {
saveSettingsDebounced(); saveSettingsDebounced();
}); });
//this makes the chat input text area resize vertically to match the text size (limited by CSS at 50% window height) $(sendTextArea).on('input', () => {
$('#send_textarea').on('input', function () { if (sendTextArea.scrollHeight > sendTextArea.offsetHeight || sendTextArea.value === '') {
const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1; autoFitSendTextArea();
const chatBlock = $('#chat'); } else {
const originalScrollBottom = chatBlock[0].scrollHeight - (chatBlock.scrollTop() + chatBlock.outerHeight()); autoFitSendTextAreaDebounced();
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);
} }
saveUserInput(); saveUserInputDebounced();
}); });
restoreUserInput(); restoreUserInput();
@ -891,23 +910,30 @@ export function initRossMods() {
processHotkeys(event.originalEvent); processHotkeys(event.originalEvent);
}); });
const hotkeyTargets = {
'send_textarea': sendTextArea,
'dialogue_popup_input': document.querySelector('#dialogue_popup_input'),
};
//Additional hotkeys CTRL+ENTER and CTRL+UPARROW //Additional hotkeys CTRL+ENTER and CTRL+UPARROW
/** /**
* @param {KeyboardEvent} event * @param {KeyboardEvent} event
*/ */
function processHotkeys(event) { function processHotkeys(event) {
//Enter to send when send_textarea in focus //Enter to send when send_textarea in focus
if ($(':focus').attr('id') === 'send_textarea') { if (document.activeElement == hotkeyTargets['send_textarea']) {
const sendOnEnter = shouldSendOnEnter(); const sendOnEnter = shouldSendOnEnter();
if (!event.shiftKey && !event.ctrlKey && !event.altKey && event.key == 'Enter' && sendOnEnter) { if (!event.shiftKey && !event.ctrlKey && !event.altKey && event.key == 'Enter' && sendOnEnter) {
event.preventDefault(); event.preventDefault();
sendTextareaMessage(); 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') { if (!event.shiftKey && !event.ctrlKey && event.key == 'Enter') {
event.preventDefault(); event.preventDefault();
$('#dialogue_popup_ok').trigger('click'); $('#dialogue_popup_ok').trigger('click');
return;
} }
} }
//ctrl+shift+up to scroll to context line //ctrl+shift+up to scroll to context line
@ -919,6 +945,7 @@ export function initRossMods() {
scrollTop: contextLine.offset().top - $('#chat').offset().top + $('#chat').scrollTop(), scrollTop: contextLine.offset().top - $('#chat').offset().top + $('#chat').scrollTop(),
}, 300); }, 300);
} else { toastr.warning('Context line not found, send a message first!'); } } else { toastr.warning('Context line not found, send a message first!'); }
return;
} }
//ctrl+shift+down to scroll to bottom of chat //ctrl+shift+down to scroll to bottom of chat
if (event.shiftKey && event.ctrlKey && event.key == 'ArrowDown') { if (event.shiftKey && event.ctrlKey && event.key == 'ArrowDown') {
@ -926,6 +953,7 @@ export function initRossMods() {
$('#chat').animate({ $('#chat').animate({
scrollTop: $('#chat').prop('scrollHeight'), scrollTop: $('#chat').prop('scrollHeight'),
}, 300); }, 300);
return;
} }
// Alt+Enter or AltGr+Enter to Continue // Alt+Enter or AltGr+Enter to Continue
@ -933,6 +961,7 @@ export function initRossMods() {
if (is_send_press == false) { if (is_send_press == false) {
console.debug('Continuing with Alt+Enter'); console.debug('Continuing with Alt+Enter');
$('#option_continue').trigger('click'); $('#option_continue').trigger('click');
return;
} }
} }
@ -942,6 +971,7 @@ export function initRossMods() {
if (editMesDone.length > 0) { if (editMesDone.length > 0) {
console.debug('Accepting edits with Ctrl+Enter'); console.debug('Accepting edits with Ctrl+Enter');
editMesDone.trigger('click'); editMesDone.trigger('click');
return;
} else if (is_send_press == false) { } else if (is_send_press == false) {
const skipConfirmKey = 'RegenerateWithCtrlEnter'; const skipConfirmKey = 'RegenerateWithCtrlEnter';
const skipConfirm = LoadLocalBool(skipConfirmKey); const skipConfirm = LoadLocalBool(skipConfirmKey);
@ -968,6 +998,7 @@ export function initRossMods() {
doRegenerate(); doRegenerate();
}); });
} }
return;
} else { } else {
console.debug('Ctrl+Enter ignored'); console.debug('Ctrl+Enter ignored');
} }
@ -976,7 +1007,7 @@ export function initRossMods() {
// Helper function to check if nanogallery2's lightbox is active // Helper function to check if nanogallery2's lightbox is active
function isNanogallery2LightboxActive() { function isNanogallery2LightboxActive() {
// Check if the body has the 'nGY2On' class, adjust this based on actual behavior // 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 if (event.key == 'ArrowLeft') { //swipes left
@ -989,6 +1020,7 @@ export function initRossMods() {
!isInputElementInFocus() !isInputElementInFocus()
) { ) {
$('.swipe_left:last').click(); $('.swipe_left:last').click();
return;
} }
} }
if (event.key == 'ArrowRight') { //swipes right if (event.key == 'ArrowRight') { //swipes right
@ -1001,13 +1033,14 @@ export function initRossMods() {
!isInputElementInFocus() !isInputElementInFocus()
) { ) {
$('.swipe_right:last').click(); $('.swipe_right:last').click();
return;
} }
} }
if (event.ctrlKey && event.key == 'ArrowUp') { //edits last USER message if chatbar is empty and focused if (event.ctrlKey && event.key == 'ArrowUp') { //edits last USER message if chatbar is empty and focused
if ( if (
$('#send_textarea').val() === '' && hotkeyTargets['send_textarea'].value === '' &&
chatbarInFocus === true && chatbarInFocus === true &&
($('.swipe_right:last').css('display') === 'flex' || $('.last_mes').attr('is_system') === 'true') && ($('.swipe_right:last').css('display') === 'flex' || $('.last_mes').attr('is_system') === 'true') &&
$('#character_popup').css('display') === 'none' && $('#character_popup').css('display') === 'none' &&
@ -1018,6 +1051,7 @@ export function initRossMods() {
const editMes = lastIsUserMes.querySelector('.mes_block .mes_edit'); const editMes = lastIsUserMes.querySelector('.mes_block .mes_edit');
if (editMes !== null) { if (editMes !== null) {
$(editMes).trigger('click'); $(editMes).trigger('click');
return;
} }
} }
} }
@ -1025,7 +1059,7 @@ export function initRossMods() {
if (event.key == 'ArrowUp') { //edits last message if chatbar is empty and focused if (event.key == 'ArrowUp') { //edits last message if chatbar is empty and focused
console.log('got uparrow input'); console.log('got uparrow input');
if ( if (
$('#send_textarea').val() === '' && hotkeyTargets['send_textarea'].value === '' &&
chatbarInFocus === true && chatbarInFocus === true &&
//$('.swipe_right:last').css('display') === 'flex' && //$('.swipe_right:last').css('display') === 'flex' &&
$('.last_mes .mes_buttons').is(':visible') && $('.last_mes .mes_buttons').is(':visible') &&
@ -1036,6 +1070,7 @@ export function initRossMods() {
const editMes = lastMes.querySelector('.mes_block .mes_edit'); const editMes = lastMes.querySelector('.mes_block .mes_edit');
if (editMes !== null) { if (editMes !== null) {
$(editMes).click(); $(editMes).click();
return;
} }
} }
} }

View File

@ -189,6 +189,8 @@ export async function getGroupChat(groupId) {
await printMessages(); await printMessages();
} else { } else {
sendSystemMessage(system_message_types.GROUP, '', { isSmallSys: true }); 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)) { if (group && Array.isArray(group.members)) {
for (let member of group.members) { for (let member of group.members) {
const character = characters.find(x => x.avatar === member || x.name === member); 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); const mes = await getFirstCharacterMessage(character);
chat.push(mes); chat.push(mes);
await eventSource.emit(event_types.MESSAGE_RECEIVED, (chat.length - 1));
addOneMessage(mes); addOneMessage(mes);
await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, (chat.length - 1));
} }
} }
await saveGroupChat(groupId, false); await saveGroupChat(groupId, false);

View File

@ -3507,11 +3507,11 @@ async function onModelChange() {
if (oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) { if (oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) {
if (oai_settings.max_context_unlocked) { if (oai_settings.max_context_unlocked) {
$('#openai_max_context').attr('max', unlocked_max); $('#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); $('#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); $('#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); $('#openai_max_context').attr('max', max_16k);
} else { } else {
$('#openai_max_context').attr('max', max_8k); $('#openai_max_context').attr('max', max_8k);
@ -3939,21 +3939,26 @@ export function isImageInliningSupported() {
return false; return false;
} }
const gpt4v = 'gpt-4-vision';
const geminiProV = 'gemini-pro-vision';
const claude = 'claude-3';
if (!oai_settings.image_inlining) { if (!oai_settings.image_inlining) {
return false; 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) { switch (oai_settings.chat_completion_source) {
case chat_completion_sources.OPENAI: 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: 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: 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: case chat_completion_sources.OPENROUTER:
return !oai_settings.openrouter_force_instruct; return !oai_settings.openrouter_force_instruct;
case chat_completion_sources.CUSTOM: case chat_completion_sources.CUSTOM:

View File

@ -22,6 +22,7 @@ import {
name1, name1,
reloadCurrentChat, reloadCurrentChat,
removeMacros, removeMacros,
retriggerFirstMessageOnEmptyChat,
saveChatConditional, saveChatConditional,
sendMessageAsUser, sendMessageAsUser,
sendSystemMessage, sendSystemMessage,
@ -203,10 +204,10 @@ parser.addCommand('name', setNameCallback, ['persona'], '<span class="monospace"
parser.addCommand('sync', syncCallback, [], ' syncs the user 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('lock', bindCallback, ['bind'], ' locks/unlocks a persona (name and avatar) to the current chat', true, true);
parser.addCommand('bg', setBackgroundCallback, ['background'], '<span class="monospace">(filename)</span> sets a background according to filename, partial names allowed', false, true); parser.addCommand('bg', setBackgroundCallback, ['background'], '<span class="monospace">(filename)</span> 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": <tt>/sendas name="Chloe" Hello, guys!</tt>', true, true); parser.addCommand('sendas', sendMessageAs, [], '<span class="monospace">[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": <tt>/sendas name="Chloe" Hello, guys!</tt>. If "compact" is set to true, the message is sent using a compact layout.', true, true);
parser.addCommand('sys', sendNarratorMessage, ['nar'], '<span class="monospace">(text)</span> sends message as a system narrator', false, true); parser.addCommand('sys', sendNarratorMessage, ['nar'], '<span class="monospace">[compact=true/false (text)]</span> 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, [], '<span class="monospace">(name)</span> sets a name for future system narrator messages in this chat (display only). Default: System. Leave empty to reset.', true, true); parser.addCommand('sysname', setNarratorName, [], '<span class="monospace">(name)</span> 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, [], '<span class="monospace">(text)</span> adds a note/comment message not part of the chat', false, true); parser.addCommand('comment', sendCommentMessage, [], '<span class="monospace">[compact=true/false (text)]</span> 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('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('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('flat', setFlatModeCallback, ['default'], ' sets the message style to flat chat mode', true, true);
@ -215,7 +216,7 @@ parser.addCommand('go', goToCharacterCallback, ['char'], '<span class="monospace
parser.addCommand('sysgen', generateSystemMessage, [], '<span class="monospace">(prompt)</span> generates a system message using a specified prompt', true, true); parser.addCommand('sysgen', generateSystemMessage, [], '<span class="monospace">(prompt)</span> generates a system message using a specified prompt', true, true);
parser.addCommand('ask', askCharacter, [], '<span class="monospace">(prompt)</span> asks a specified character card a prompt', true, true); parser.addCommand('ask', askCharacter, [], '<span class="monospace">(prompt)</span> asks a specified character card a prompt', true, true);
parser.addCommand('delname', deleteMessagesByNameCallback, ['cancel'], '<span class="monospace">(name)</span> deletes all messages attributed to a specified name', true, true); parser.addCommand('delname', deleteMessagesByNameCallback, ['cancel'], '<span class="monospace">(name)</span> deletes all messages attributed to a specified name', true, true);
parser.addCommand('send', sendUserMessageCallback, [], '<span class="monospace">(text)</span> adds a user message to the chat log without triggering a generation', true, true); parser.addCommand('send', sendUserMessageCallback, [], '<span class="monospace">[compact=true/false (text)]</span> 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, [], ' <span class="monospace">await=true/false</span> triggers a message generation. If in group, can trigger a message for the specified group member index or name. If <code>await=true</code> named argument passed, the command will await for the triggered generation before continuing.', true, true); parser.addCommand('trigger', triggerGenerationCallback, [], ' <span class="monospace">await=true/false</span> triggers a message generation. If in group, can trigger a message for the specified group member index or name. If <code>await=true</code> named argument passed, the command will await for the triggered generation before continuing.', true, true);
parser.addCommand('hide', hideMessageCallback, [], '<span class="monospace">(message index or range)</span> hides a chat message from the prompt', true, true); parser.addCommand('hide', hideMessageCallback, [], '<span class="monospace">(message index or range)</span> hides a chat message from the prompt', true, true);
parser.addCommand('unhide', unhideMessageCallback, [], '<span class="monospace">(message index or range)</span> unhides a message from the prompt', true, true); parser.addCommand('unhide', unhideMessageCallback, [], '<span class="monospace">(message index or range)</span> unhides a message from the prompt', true, true);
@ -1175,9 +1176,10 @@ async function sendUserMessageCallback(args, text) {
} }
text = text.trim(); text = text.trim();
const compact = isTrueBoolean(args?.compact);
const bias = extractMessageBias(text); const bias = extractMessageBias(text);
const insertAt = Number(resolveVariable(args?.at)); const insertAt = Number(resolveVariable(args?.at));
await sendMessageAsUser(text, bias, insertAt); await sendMessageAsUser(text, bias, insertAt, compact);
return ''; return '';
} }
@ -1340,12 +1342,14 @@ function setNameCallback(_, name) {
for (let persona of Object.values(power_user.personas)) { for (let persona of Object.values(power_user.personas)) {
if (persona.toLowerCase() === name.toLowerCase()) { if (persona.toLowerCase() === name.toLowerCase()) {
autoSelectPersona(name); autoSelectPersona(name);
retriggerFirstMessageOnEmptyChat();
return; return;
} }
} }
// Otherwise, set just the name // Otherwise, set just the name
setUserName(name); //this prevented quickReply usage setUserName(name); //this prevented quickReply usage
retriggerFirstMessageOnEmptyChat();
} }
async function setNarratorName(_, text) { 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 // Messages that do nothing but set bias will be hidden from the context
const bias = extractMessageBias(mesText); const bias = extractMessageBias(mesText);
const isSystem = bias && !removeMacros(mesText).length; const isSystem = bias && !removeMacros(mesText).length;
const compact = isTrueBoolean(args?.compact);
const character = characters.find(x => x.name === name); const character = characters.find(x => x.name === name);
let force_avatar, original_avatar; let force_avatar, original_avatar;
@ -1412,6 +1417,7 @@ export async function sendMessageAs(args, text) {
extra: { extra: {
bias: bias.trim().length ? bias : null, bias: bias.trim().length ? bias : null,
gen_id: Date.now(), 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 // Messages that do nothing but set bias will be hidden from the context
const bias = extractMessageBias(text); const bias = extractMessageBias(text);
const isSystem = bias && !removeMacros(text).length; const isSystem = bias && !removeMacros(text).length;
const compact = isTrueBoolean(args?.compact);
const message = { const message = {
name: name, name: name,
@ -1453,6 +1460,7 @@ export async function sendNarratorMessage(args, text) {
type: system_message_types.NARRATOR, type: system_message_types.NARRATOR,
bias: bias.trim().length ? bias : null, bias: bias.trim().length ? bias : null,
gen_id: Date.now(), gen_id: Date.now(),
isSmallSys: compact,
}, },
}; };
@ -1517,6 +1525,7 @@ async function sendCommentMessage(args, text) {
return; return;
} }
const compact = isTrueBoolean(args?.compact);
const message = { const message = {
name: COMMENT_NAME_DEFAULT, name: COMMENT_NAME_DEFAULT,
is_user: false, is_user: false,
@ -1527,6 +1536,7 @@ async function sendCommentMessage(args, text) {
extra: { extra: {
type: system_message_types.COMMENT, type: system_message_types.COMMENT,
gen_id: Date.now(), gen_id: Date.now(),
isSmallSys: compact,
}, },
}; };

View File

@ -8,6 +8,8 @@ import {
entitiesFilter, entitiesFilter,
printCharacters, printCharacters,
buildAvatarList, buildAvatarList,
eventSource,
event_types,
} from '../script.js'; } from '../script.js';
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
import { FILTER_TYPES, FILTER_STATES, isFilterState, FilterHelper } from './filters.js'; 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') { if (subForEntity !== undefined && subForEntity.type === 'tag') {
entities = filterTagSubEntities(subForEntity.item, entities, { filterHidden : filterHidden }); entities = filterTagSubEntities(subForEntity.item, entities, { filterHidden: filterHidden });
} }
return entities; return entities;
@ -1177,7 +1179,7 @@ function onClearAllFiltersClick() {
// We have to manually go through the elements and unfilter by clicking... // We have to manually go through the elements and unfilter by clicking...
// Thankfully nearly all filter controls are three-state-toggles // Thankfully nearly all filter controls are three-state-toggles
const filterTags = $('.rm_tag_controls .rm_tag_filter').find('.tag'); 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'); const toggleState = $(tag).attr('data-toggle-state');
if (toggleState !== undefined && !isFilterState(toggleState ?? FILTER_STATES.UNDEFINED, FILTER_STATES.UNDEFINED)) { if (toggleState !== undefined && !isFilterState(toggleState ?? FILTER_STATES.UNDEFINED, FILTER_STATES.UNDEFINED)) {
toggleTagThreeState($(tag), { stateOverride: FILTER_STATES.UNDEFINED, simulateClick: true }); toggleTagThreeState($(tag), { stateOverride: FILTER_STATES.UNDEFINED, simulateClick: true });
@ -1188,7 +1190,17 @@ function onClearAllFiltersClick() {
$('#character_search_bar').val('').trigger('input'); $('#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('#tagInput', '#tagList', { tagOptions: { removable: true } });
createTagInput('#groupTagInput', '#groupTagList', { 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_create', onTagCreateClick);
$(document).on('click', '.tag_view_backup', onTagsBackupClick); $(document).on('click', '.tag_view_backup', onTagsBackupClick);
$(document).on('click', '.tag_view_restore', onBackupRestoreClick); $(document).on('click', '.tag_view_restore', onBackupRestoreClick);
}); eventSource.on(event_types.CHARACTER_DUPLICATED, copyTags);
}

View File

@ -33,7 +33,7 @@
<li><tt>&lcub;&lcub;time_UTC±#&rcub;&rcub;</tt> the current time in the specified UTC time zone offset, e.g. UTC-4 or UTC+2</li> <li><tt>&lcub;&lcub;time_UTC±#&rcub;&rcub;</tt> the current time in the specified UTC time zone offset, e.g. UTC-4 or UTC+2</li>
<li><tt>&lcub;&lcub;idle_duration&rcub;&rcub;</tt> the time since the last user message was sent</li> <li><tt>&lcub;&lcub;idle_duration&rcub;&rcub;</tt> the time since the last user message was sent</li>
<li><tt>&lcub;&lcub;bias "text here"&rcub;&rcub;</tt> sets a behavioral bias for the AI until the next user input. Quotes around the text are important.</li> <li><tt>&lcub;&lcub;bias "text here"&rcub;&rcub;</tt> sets a behavioral bias for the AI until the next user input. Quotes around the text are important.</li>
<li><tt>&lcub;&lcub;roll:(formula)&rcub;&rcub;</tt> rolls a dice. (ex: <tt>>&lcub;&lcub;roll:1d6&rcub;&rcub</tt> will roll a 6-sided dice and return a number between 1 and 6)</li> <li><tt>&lcub;&lcub;roll:(formula)&rcub;&rcub;</tt> rolls a dice. (ex: <tt>>&lcub;&lcub;roll:1d6&rcub;&rcub;</tt> will roll a 6-sided dice and return a number between 1 and 6)</li>
<li><tt>&lcub;&lcub;random:(args)&rcub;&rcub;</tt> returns a random item from the list. (ex: <tt>&lcub;&lcub;random:1,2,3,4&rcub;&rcub;</tt> will return 1 of the 4 numbers at random. Works with text lists too.</li> <li><tt>&lcub;&lcub;random:(args)&rcub;&rcub;</tt> returns a random item from the list. (ex: <tt>&lcub;&lcub;random:1,2,3,4&rcub;&rcub;</tt> will return 1 of the 4 numbers at random. Works with text lists too.</li>
<li><tt>&lcub;&lcub;random::(arg1)::(arg2)&rcub;&rcub;</tt> alternative syntax for random that allows to use commas in the list items.</li> <li><tt>&lcub;&lcub;random::(arg1)::(arg2)&rcub;&rcub;</tt> alternative syntax for random that allows to use commas in the list items.</li>
<li><tt>&lcub;&lcub;banned "text here"&rcub;&rcub;</tt> 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.</li> <li><tt>&lcub;&lcub;banned "text here"&rcub;&rcub;</tt> 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.</li>

View File

@ -972,7 +972,6 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
'typical_p': settings.typical_p, 'typical_p': settings.typical_p,
'typical': settings.typical_p, 'typical': settings.typical_p,
'sampler_seed': settings.seed, 'sampler_seed': settings.seed,
'seed': settings.seed,
'min_p': settings.min_p, 'min_p': settings.min_p,
'repetition_penalty': settings.rep_pen, 'repetition_penalty': settings.rep_pen,
'frequency_penalty': settings.freq_pen, 'frequency_penalty': settings.freq_pen,
@ -1024,6 +1023,7 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
'penalty_alpha': settings.type === OOBA ? settings.penalty_alpha : undefined, 'penalty_alpha': settings.type === OOBA ? settings.penalty_alpha : undefined,
'temperature_last': (settings.type === OOBA || settings.type === APHRODITE || settings.type == TABBY) ? settings.temperature_last : undefined, 'temperature_last': (settings.type === OOBA || settings.type === APHRODITE || settings.type == TABBY) ? settings.temperature_last : undefined,
'do_sample': settings.type === OOBA ? settings.do_sample : undefined, 'do_sample': settings.type === OOBA ? settings.do_sample : undefined,
'seed': settings.seed,
'guidance_scale': cfgValues?.guidanceScale?.value ?? settings.guidance_scale ?? 1, 'guidance_scale': cfgValues?.guidanceScale?.value ?? settings.guidance_scale ?? 1,
'negative_prompt': cfgValues?.negativePrompt ?? substituteParams(settings.negative_prompt) ?? '', 'negative_prompt': cfgValues?.negativePrompt ?? substituteParams(settings.negative_prompt) ?? '',
'grammar_string': settings.grammar_string, 'grammar_string': settings.grammar_string,

View File

@ -383,6 +383,15 @@ small {
text-align: center; text-align: center;
} }
.mes.smallSysMes .mes_text p:last-child {
margin: 0;
}
.mes.smallSysMes .swipe_right,
.mes.smallSysMes .swipe_left {
display: none !important;
}
.mes.smallSysMes .mes_text { .mes.smallSysMes .mes_text {
padding: 0 !important; padding: 0 !important;
text-align: center; text-align: center;
@ -1408,7 +1417,7 @@ input[type="file"] {
} }
.extension_token_counter { .extension_token_counter {
font-size: calc(var(--mainFontSize) * 0.9); font-size: calc(var(--mainFontSize) * 0.875);
width: 100%; width: 100%;
text-align: right; text-align: right;
margin-bottom: 5px; margin-bottom: 5px;

View File

@ -1008,7 +1008,7 @@ router.post('/duplicate', jsonParser, async function (request, response) {
fs.copyFileSync(filename, newFilename); fs.copyFileSync(filename, newFilename);
console.log(`${filename} was copied to ${newFilename}`); console.log(`${filename} was copied to ${newFilename}`);
response.sendStatus(200); response.send({ path: path.parse(newFilename).base });
} }
catch (error) { catch (error) {
console.error(error); console.error(error);

View File

@ -192,53 +192,73 @@ function convertClaudeMessages(messages, prefillString, useSysPrompt, humanMsgFi
function convertGooglePrompt(messages, model) { function convertGooglePrompt(messages, model) {
// This is a 1x1 transparent PNG // This is a 1x1 transparent PNG
const PNG_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='; const PNG_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
const visionSupportedModels = [
'gemini-1.0-pro-vision-latest',
'gemini-1.5-pro-latest',
'gemini-pro-vision',
];
const isMultimodal = visionSupportedModels.includes(model);
let hasImage = false;
const contents = []; const contents = [];
let lastRole = ''; messages.forEach((message, index) => {
let currentText = ''; // fix the roles
if (message.role === 'system') {
message.role = 'user';
} else if (message.role === 'assistant') {
message.role = 'model';
}
const isMultimodal = model === 'gemini-pro-vision'; // similar story as claude
if (message.name) {
if (isMultimodal) { if (Array.isArray(message.content)) {
const combinedText = messages.map((message) => { message.content[0].text = `${message.name}: ${message.content[0].text}`;
const role = message.role === 'assistant' ? 'MODEL: ' : 'USER: ';
return role + message.content;
}).join('\n\n').trim();
const imageEntry = messages.find((message) => message.content?.[1]?.image_url);
const imageData = imageEntry?.content?.[1]?.image_url?.data ?? PNG_PIXEL;
contents.push({
parts: [
{ text: combinedText },
{
inlineData: {
mimeType: 'image/png',
data: imageData,
},
},
],
role: 'user',
});
} else {
messages.forEach((message, index) => {
const role = message.role === 'assistant' ? 'model' : 'user';
if (lastRole === role) {
currentText += '\n\n' + message.content;
} else { } else {
if (currentText !== '') { message.content = `${message.name}: ${message.content}`;
contents.push({ }
parts: [{ text: currentText.trim() }], delete message.name;
role: lastRole, }
//create the prompt parts
const parts = [];
if (typeof message.content === 'string') {
parts.push({ text: message.content });
} else if (Array.isArray(message.content)) {
message.content.forEach((part) => {
if (part.type === 'text') {
parts.push({ text: part.text });
} else if (part.type === 'image_url' && isMultimodal) {
parts.push({
inlineData: {
mimeType: 'image/png',
data: part.image_url.url,
},
}); });
hasImage = true;
} }
currentText = message.content; });
lastRole = role; }
}
if (index === messages.length - 1) { // merge consecutive messages with the same role
contents.push({ if (index > 0 && message.role === contents[contents.length - 1].role) {
parts: [{ text: currentText.trim() }], contents[contents.length - 1].parts[0].text += '\n\n' + parts[0].text;
role: lastRole, } else {
}); contents.push({
} role: message.role,
parts: parts,
});
}
});
// pro 1.5 doesn't require a dummy image to be attached, other vision models do
if (isMultimodal && model !== 'gemini-1.5-pro-latest' && !hasImage) {
contents[0].parts.push({
inlineData: {
mimeType: 'image/png',
data: PNG_PIXEL,
},
}); });
} }