- Before you get started, you must select a user name.
+
+ Before you get started, you must select a user name.
+
This can be changed at any time via the icon.
From 4527880c59aa91bc1e303669dce772cc3a53d58c Mon Sep 17 00:00:00 2001
From: Wolfsblvt
Date: Mon, 25 Mar 2024 20:55:48 +0100
Subject: [PATCH 063/255] Add additional update script for forks/branches
(#1963)
---
Update-Instructions.txt | 3 ++
UpdateForkAndStart.bat | 103 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 106 insertions(+)
create mode 100644 UpdateForkAndStart.bat
diff --git a/Update-Instructions.txt b/Update-Instructions.txt
index b862f8907..f153660b2 100644
--- a/Update-Instructions.txt
+++ b/Update-Instructions.txt
@@ -22,6 +22,9 @@ You can also try running the 'UpdateAndStart.bat' file, which will almost do the
Alternatively, if the command prompt gives you problems (and you have GitHub Desktop installed), you can use the 'Repository' menu and select 'Pull'.
The updates are applied automatically and safely.
+If you are a developer and use a fork of ST or switch branches regularly, you can use the 'UpdateForkAndStart.bat', which works similarly to 'UpdateAndStart.bat',
+but automatically pulls changes into your fork and handles switched branches gracefully by asking if you want to switch back.
+
Method 2 - ZIP
If you insist on installing via a zip, here is the tedious process for doing the update:
diff --git a/UpdateForkAndStart.bat b/UpdateForkAndStart.bat
new file mode 100644
index 000000000..5052b9aa0
--- /dev/null
+++ b/UpdateForkAndStart.bat
@@ -0,0 +1,103 @@
+@echo off
+@setlocal enabledelayedexpansion
+pushd %~dp0
+
+echo Checking Git installation
+git --version > nul 2>&1
+if %errorlevel% neq 0 (
+ echo Git is not installed on this system. Skipping update.
+ echo If you installed with a zip file, you will need to download the new zip and install it manually.
+ goto end
+)
+
+REM Checking current branch
+FOR /F "tokens=*" %%i IN ('git rev-parse --abbrev-ref HEAD') DO SET CURRENT_BRANCH=%%i
+echo Current branch: %CURRENT_BRANCH%
+
+REM Checking for automatic branch switching configuration
+set AUTO_SWITCH=
+FOR /F "tokens=*" %%j IN ('git config --local script.autoSwitch') DO SET AUTO_SWITCH=%%j
+
+SET TARGET_BRANCH=%CURRENT_BRANCH%
+
+if NOT "!AUTO_SWITCH!"=="" (
+ if "!AUTO_SWITCH!"=="s" (
+ goto autoswitch-staging
+ )
+ if "!AUTO_SWITCH!"=="r" (
+ goto autoswitch-release
+ )
+
+ if "!AUTO_SWITCH!"=="staging" (
+ :autoswitch-staging
+ echo Auto-switching to staging branch
+ git checkout staging
+ SET TARGET_BRANCH=staging
+ goto update
+ )
+ if "!AUTO_SWITCH!"=="release" (
+ :autoswitch-release
+ echo Auto-switching to release branch
+ git checkout release
+ SET TARGET_BRANCH=release
+ goto update
+ )
+
+ echo Auto-switching defined to stay on current branch
+ goto update
+)
+
+if "!CURRENT_BRANCH!"=="staging" (
+ echo Staying on the current branch
+ goto update
+)
+if "!CURRENT_BRANCH!"=="release" (
+ echo Staying on the current branch
+ goto update
+)
+
+echo You are not on 'staging' or 'release'. You are on '!CURRENT_BRANCH!'.
+set /p "CHOICE=Do you want to switch to 'staging' (s), 'release' (r), or stay (any other key)? "
+if /i "!CHOICE!"=="s" (
+ echo Switching to staging branch
+ git checkout staging
+ SET TARGET_BRANCH=staging
+ goto update
+)
+if /i "!CHOICE!"=="r" (
+ echo Switching to release branch
+ git checkout release
+ SET TARGET_BRANCH=release
+ goto update
+)
+
+echo Staying on the current branch
+
+:update
+REM Checking for 'upstream' remote
+git remote | findstr "upstream" > nul
+if %errorlevel% equ 0 (
+ echo Updating and rebasing against 'upstream'
+ git fetch upstream
+ git rebase upstream/%TARGET_BRANCH% --autostash
+ goto install
+)
+
+echo Updating and rebasing against 'origin'
+git pull --rebase --autostash origin %TARGET_BRANCH%
+
+
+:install
+if %errorlevel% neq 0 (
+ echo There were errors while updating. Please check manually.
+ goto end
+)
+
+echo Installing npm packages and starting server
+set NODE_ENV=production
+call npm install --no-audit --no-fund --quiet --omit=dev
+node server.js %*
+
+:end
+pause
+popd
From 7c0cf50d806ad4f09dc1829b1d505c9e43e2e984 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Tue, 26 Mar 2024 00:07:26 +0200
Subject: [PATCH 064/255] #1966 Fix selector
---
public/script.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/public/script.js b/public/script.js
index 418149c72..38ed270cd 100644
--- a/public/script.js
+++ b/public/script.js
@@ -5670,7 +5670,7 @@ export async function getUserAvatars(doRender = true, openPageAt = '') {
function highlightSelectedAvatar() {
$('#user_avatar_block .avatar-container').removeClass('selected');
- $(`#user_avatar_block .avatar-container[imgfile='${user_avatar}']`).addClass('selected');
+ $(`#user_avatar_block .avatar-container[imgfile="${user_avatar}"]`).addClass('selected');
}
/**
From 92ec45af4b72bbb5f1fbe8890dfb708d0005214a Mon Sep 17 00:00:00 2001
From: LenAnderson
Date: Mon, 25 Mar 2024 18:16:18 -0400
Subject: [PATCH 065/255] call processHotkeys debounced
---
public/scripts/RossAscends-mods.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/public/scripts/RossAscends-mods.js b/public/scripts/RossAscends-mods.js
index da93d1ed7..fcbaf35b6 100644
--- a/public/scripts/RossAscends-mods.js
+++ b/public/scripts/RossAscends-mods.js
@@ -884,8 +884,9 @@ export function initRossMods() {
}
$(document).on('keydown', function (event) {
- processHotkeys(event.originalEvent);
+ processHotkeysDebounced(event.originalEvent);
});
+ const processHotkeysDebounced = debounce(processHotkeys);
//Additional hotkeys CTRL+ENTER and CTRL+UPARROW
/**
From 6fa6f0c81552d2c43e38086072e826b14e431933 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Tue, 26 Mar 2024 16:10:55 +0200
Subject: [PATCH 066/255] Fix panel buttons alignment
---
public/index.html | 24 ++++++++++++------------
public/style.css | 1 +
2 files changed, 13 insertions(+), 12 deletions(-)
diff --git a/public/index.html b/public/index.html
index b793f87f6..751cce6b3 100644
--- a/public/index.html
+++ b/public/index.html
@@ -5236,12 +5236,12 @@
-
-
-
-
+
+
+
+
-
+
@@ -5380,12 +5380,12 @@
-
-
-
-
+
+
+
+
-
+
@@ -5693,8 +5693,8 @@
-
-
+
+
diff --git a/public/style.css b/public/style.css
index d4c4a1855..91932cb47 100644
--- a/public/style.css
+++ b/public/style.css
@@ -528,6 +528,7 @@ body.reduced-motion #bg_custom {
top: 5px;
margin-right: 5px;
z-index: 2000;
+ min-width: 55px;
}
.panelControlBar .drag-grabber {
From e567aa2c314e81aba0e6df0f85df90f7f95bc8e2 Mon Sep 17 00:00:00 2001
From: LenAnderson
Date: Tue, 26 Mar 2024 12:09:26 -0400
Subject: [PATCH 067/255] replace debounce with other performance improvements
- remove debounce from processHotkey
- replace dom-queries in conditions with vars
- replace some jQuery in conditions with vanilla JS
---
public/scripts/RossAscends-mods.js | 29 ++++++++++++++++++++++-------
1 file changed, 22 insertions(+), 7 deletions(-)
diff --git a/public/scripts/RossAscends-mods.js b/public/scripts/RossAscends-mods.js
index fcbaf35b6..ee6402ae4 100644
--- a/public/scripts/RossAscends-mods.js
+++ b/public/scripts/RossAscends-mods.js
@@ -884,9 +884,13 @@ export function initRossMods() {
}
$(document).on('keydown', function (event) {
- processHotkeysDebounced(event.originalEvent);
+ processHotkeys(event.originalEvent);
});
- const processHotkeysDebounced = debounce(processHotkeys);
+
+ const hotkeyTargets = {
+ 'send_textarea': sendTextArea,
+ 'dialogue_popup_input': document.querySelector('#dialogue_popup_input'),
+ };
//Additional hotkeys CTRL+ENTER and CTRL+UPARROW
/**
@@ -894,17 +898,19 @@ export function initRossMods() {
*/
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
@@ -916,6 +922,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') {
@@ -923,6 +930,7 @@ export function initRossMods() {
$('#chat').animate({
scrollTop: $('#chat').prop('scrollHeight'),
}, 300);
+ return;
}
// Alt+Enter or AltGr+Enter to Continue
@@ -930,6 +938,7 @@ export function initRossMods() {
if (is_send_press == false) {
console.debug('Continuing with Alt+Enter');
$('#option_continue').trigger('click');
+ return;
}
}
@@ -939,6 +948,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);
@@ -965,6 +975,7 @@ export function initRossMods() {
doRegenerate();
});
}
+ return;
} else {
console.debug('Ctrl+Enter ignored');
}
@@ -973,7 +984,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
@@ -986,6 +997,7 @@ export function initRossMods() {
!isInputElementInFocus()
) {
$('.swipe_left:last').click();
+ return;
}
}
if (event.key == 'ArrowRight') { //swipes right
@@ -998,13 +1010,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' &&
@@ -1015,6 +1028,7 @@ export function initRossMods() {
const editMes = lastIsUserMes.querySelector('.mes_block .mes_edit');
if (editMes !== null) {
$(editMes).trigger('click');
+ return;
}
}
}
@@ -1022,7 +1036,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') &&
@@ -1033,6 +1047,7 @@ export function initRossMods() {
const editMes = lastMes.querySelector('.mes_block .mes_edit');
if (editMes !== null) {
$(editMes).click();
+ return;
}
}
}
From 69d195ef31563965f0357838cab4a8c7279562e4 Mon Sep 17 00:00:00 2001
From: LenAnderson
Date: Tue, 26 Mar 2024 12:11:00 -0400
Subject: [PATCH 068/255] improve send_textarea autofit performance
- only expand immediately
- shrink debounced
---
public/scripts/RossAscends-mods.js | 38 +++++++++++++++++++++---------
1 file changed, 27 insertions(+), 11 deletions(-)
diff --git a/public/scripts/RossAscends-mods.js b/public/scripts/RossAscends-mods.js
index ee6402ae4..f7d2e5139 100644
--- a/public/scripts/RossAscends-mods.js
+++ b/public/scripts/RossAscends-mods.js
@@ -658,6 +658,28 @@ export async function initMovingUI() {
}
}
+/**@type {HTMLTextAreaElement} */
+const sendTextArea = document.querySelector('#send_textarea');
+/**
+ * this makes the chat input text area resize vertically to match the text size (limited by CSS at 50% window height)
+ */
+function autoFitSendTextArea() {
+ const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
+ /**@type {HTMLDivElement} */
+ const chatBlock = document.querySelector('#chat');
+ const originalScrollBottom = chatBlock.scrollHeight - (chatBlock.scrollTop + chatBlock.offsetHeight);
+ if (sendTextArea.scrollHeight == sendTextArea.offsetHeight) {
+ sendTextArea.style.height = window.getComputedStyle(sendTextArea).getPropertyValue('min-height');
+ }
+ 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() {
@@ -820,17 +842,11 @@ 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.addEventListener('input', ()=>{
+ if (sendTextArea.scrollHeight > sendTextArea.offsetHeight) {
+ autoFitSendTextArea();
+ } else {
+ autoFitSendTextAreaDebounced();
}
saveUserInput();
});
From d9022db7d9b0ab76d11e4bd07d14621f3e12f835 Mon Sep 17 00:00:00 2001
From: LenAnderson
Date: Tue, 26 Mar 2024 12:11:15 -0400
Subject: [PATCH 069/255] debounce saving of user input in send_textarea
---
public/scripts/RossAscends-mods.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/public/scripts/RossAscends-mods.js b/public/scripts/RossAscends-mods.js
index f7d2e5139..9f73494c9 100644
--- a/public/scripts/RossAscends-mods.js
+++ b/public/scripts/RossAscends-mods.js
@@ -399,6 +399,7 @@ function saveUserInput() {
const userInput = String($('#send_textarea').val());
SaveLocal('userInput', userInput);
}
+const saveUserInputDebounced = debounce(saveUserInput);
// Make the DIV element draggable:
@@ -848,7 +849,7 @@ export function initRossMods() {
} else {
autoFitSendTextAreaDebounced();
}
- saveUserInput();
+ saveUserInputDebounced();
});
restoreUserInput();
From 78ba88f94ffdb840a9b8a367bebcdbf6fb1eb1cf Mon Sep 17 00:00:00 2001
From: LenAnderson
Date: Tue, 26 Mar 2024 12:21:22 -0400
Subject: [PATCH 070/255] set active character / active group in /go command
---
public/scripts/slash-commands.js | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js
index a954404c0..76d4b11db 100644
--- a/public/scripts/slash-commands.js
+++ b/public/scripts/slash-commands.js
@@ -25,6 +25,8 @@ import {
saveChatConditional,
sendMessageAsUser,
sendSystemMessage,
+ setActiveCharacter,
+ setActiveGroup,
setCharacterId,
setCharacterName,
setExtensionPrompt,
@@ -1248,11 +1250,15 @@ async function goToCharacterCallback(_, name) {
if (characterIndex !== -1) {
await openChat(new String(characterIndex));
+ setActiveCharacter(characters[characterIndex]?.avatar);
+ setActiveGroup(null);
return characters[characterIndex]?.name;
} else {
const group = groups.find(it => it.name.toLowerCase() == name.toLowerCase());
if (group) {
await openGroupById(group.id);
+ setActiveCharacter(null);
+ setActiveGroup(group.id);
return group.name;
} else {
console.warn(`No matches found for name "${name}"`);
From 4e7cd6d63b096f3242e2303c8d3902717c056954 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Tue, 26 Mar 2024 18:30:12 +0200
Subject: [PATCH 071/255] Set active character for /random
---
public/scripts/power-user.js | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js
index 5628793bc..d0cc3fc06 100644
--- a/public/scripts/power-user.js
+++ b/public/scripts/power-user.js
@@ -21,6 +21,8 @@ import {
saveChatConditional,
setAnimationDuration,
ANIMATION_DURATION_DEFAULT,
+ setActiveGroup,
+ setActiveCharacter,
} from '../script.js';
import { isMobile, initMovingUI, favsToHotswap } from './RossAscends-mods.js';
import {
@@ -2302,6 +2304,8 @@ async function doRandomChat() {
resetSelectedGroup();
const characterId = Math.floor(Math.random() * characters.length).toString();
setCharacterId(characterId);
+ setActiveCharacter(characters[characterId]?.avatar);
+ setActiveGroup(null);
await delay(1);
await reloadCurrentChat();
return characters[characterId]?.name;
From 3debc063727429d5fef3518d0cf99230c1341347 Mon Sep 17 00:00:00 2001
From: LenAnderson
Date: Tue, 26 Mar 2024 12:32:23 -0400
Subject: [PATCH 072/255] fix for jQuery input event not triggering real input
event
---
public/scripts/RossAscends-mods.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/public/scripts/RossAscends-mods.js b/public/scripts/RossAscends-mods.js
index 9f73494c9..aaf9d5233 100644
--- a/public/scripts/RossAscends-mods.js
+++ b/public/scripts/RossAscends-mods.js
@@ -843,7 +843,7 @@ export function initRossMods() {
saveSettingsDebounced();
});
- sendTextArea.addEventListener('input', ()=>{
+ $(sendTextArea).on('input', ()=>{
if (sendTextArea.scrollHeight > sendTextArea.offsetHeight) {
autoFitSendTextArea();
} else {
From 6a51855f193955925e295cf05574d46532f364b5 Mon Sep 17 00:00:00 2001
From: based
Date: Wed, 27 Mar 2024 13:52:51 +1000
Subject: [PATCH 073/255] Update Makersuite models
---
public/index.html | 19 ++++++++++++++-----
public/scripts/openai.js | 25 +++++++++++++++----------
src/prompt-converters.js | 8 +++++++-
3 files changed, 36 insertions(+), 16 deletions(-)
diff --git a/public/index.html b/public/index.html
index 751cce6b3..baf5c2d3d 100644
--- a/public/index.html
+++ b/public/index.html
@@ -2604,11 +2604,20 @@
Google Model
-
-
-
-
-
+
+
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/src/prompt-converters.js b/src/prompt-converters.js
index 52161b661..3648fa1f8 100644
--- a/src/prompt-converters.js
+++ b/src/prompt-converters.js
@@ -196,7 +196,13 @@ function convertGooglePrompt(messages, model) {
let lastRole = '';
let currentText = '';
- const isMultimodal = model === 'gemini-pro-vision';
+ const visionSupportedModels = [
+ 'gemini-1.0-pro-vision-latest',
+ 'gemini-1.5-pro-latest',
+ 'gemini-pro-vision',
+ ];
+
+ const isMultimodal = visionSupportedModels.includes(model);
if (isMultimodal) {
const combinedText = messages.map((message) => {
From 9bd3a526aacf894e113584b27109b7d8e58077a3 Mon Sep 17 00:00:00 2001
From: Alexander Abushady <44341163+AAbushady@users.noreply.github.com>
Date: Tue, 26 Mar 2024 23:57:24 -0400
Subject: [PATCH 074/255] Fix for unique swipes
Fix for unique swipes in Aphrodite
---
public/scripts/textgen-settings.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/public/scripts/textgen-settings.js b/public/scripts/textgen-settings.js
index b16b67173..515355e59 100644
--- a/public/scripts/textgen-settings.js
+++ b/public/scripts/textgen-settings.js
@@ -972,7 +972,6 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
'typical_p': settings.typical_p,
'typical': settings.typical_p,
'sampler_seed': settings.seed,
- 'seed': settings.seed,
'min_p': settings.min_p,
'repetition_penalty': settings.rep_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,
'temperature_last': (settings.type === OOBA || settings.type === APHRODITE || settings.type == TABBY) ? settings.temperature_last : undefined,
'do_sample': settings.type === OOBA ? settings.do_sample : undefined,
+ 'seed': settings.seed,
'guidance_scale': cfgValues?.guidanceScale?.value ?? settings.guidance_scale ?? 1,
'negative_prompt': cfgValues?.negativePrompt ?? substituteParams(settings.negative_prompt) ?? '',
'grammar_string': settings.grammar_string,
From f3b9920f227780f091d2cd57526e78c4544b885d Mon Sep 17 00:00:00 2001
From: based
Date: Wed, 27 Mar 2024 15:48:26 +1000
Subject: [PATCH 075/255] actually convert the prompts properly
---
src/prompt-converters.js | 94 +++++++++++++++++++++++-----------------
1 file changed, 54 insertions(+), 40 deletions(-)
diff --git a/src/prompt-converters.js b/src/prompt-converters.js
index 3648fa1f8..0b5af8f80 100644
--- a/src/prompt-converters.js
+++ b/src/prompt-converters.js
@@ -192,9 +192,6 @@ function convertClaudeMessages(messages, prefillString, useSysPrompt, humanMsgFi
function convertGooglePrompt(messages, model) {
// This is a 1x1 transparent PNG
const PNG_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
- const contents = [];
- let lastRole = '';
- let currentText = '';
const visionSupportedModels = [
'gemini-1.0-pro-vision-latest',
@@ -203,48 +200,65 @@ function convertGooglePrompt(messages, model) {
];
const isMultimodal = visionSupportedModels.includes(model);
+ let hasImage = false;
- if (isMultimodal) {
- const combinedText = messages.map((message) => {
- const role = message.role === 'assistant' ? 'MODEL: ' : 'USER: ';
- return role + message.content;
- }).join('\n\n').trim();
+ const contents = [];
+ messages.forEach((message, index) => {
+ // fix the roles
+ if (message.role === 'system') {
+ message.role = 'user';
+ } else if (message.role === 'assistant') {
+ message.role = 'model';
+ }
- 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;
+ // similar story as claude
+ if (message.name) {
+ if (Array.isArray(message.content)) {
+ message.content[0].text = `${message.name}: ${message.content[0].text}`;
} else {
- if (currentText !== '') {
- contents.push({
- parts: [{ text: currentText.trim() }],
- role: lastRole,
+ message.content = `${message.name}: ${message.content}`;
+ }
+ delete message.name;
+ }
+
+ //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) {
- contents.push({
- parts: [{ text: currentText.trim() }],
- role: lastRole,
- });
- }
+ });
+ }
+
+ // merge consecutive messages with the same role
+ if (index > 0 && message.role === contents[contents.length - 1].role) {
+ contents[contents.length - 1].parts[0].text += '\n\n' + parts[0].text;
+ } 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,
+ },
});
}
From 40daf1ca1d04902997aa9eed67f3beffcb44ecdd Mon Sep 17 00:00:00 2001
From: Wolfsblvt
Date: Wed, 27 Mar 2024 03:36:09 +0100
Subject: [PATCH 076/255] Bulk edit tag improvements
- Show mutual tags on bulk edit
- Update tag list on tag added/removed in bulk edit
- Add "remove mutual" button to bulk edit tags
---
public/scripts/BulkEditOverlay.js | 85 +++++++++++++++++++++++++++----
public/scripts/tags.js | 84 ++++++++++++++++++++++--------
public/style.css | 8 +++
3 files changed, 144 insertions(+), 33 deletions(-)
diff --git a/public/scripts/BulkEditOverlay.js b/public/scripts/BulkEditOverlay.js
index eb69f279b..d11b56aa9 100644
--- a/public/scripts/BulkEditOverlay.js
+++ b/public/scripts/BulkEditOverlay.js
@@ -15,7 +15,7 @@ import {
import { favsToHotswap } from './RossAscends-mods.js';
import { hideLoader, showLoader } from './loader.js';
import { convertCharacterToPersona } from './personas.js';
-import { createTagInput, getTagKeyForEntity, tag_map } from './tags.js';
+import { createTagInput, getTagKeyForEntity, getTagsList, printTagList, tag_map, compareTagsForSort, removeTagFromMap } from './tags.js';
// Utility object for popup messages.
const popupMessage = {
@@ -193,8 +193,9 @@ class BulkTagPopupHandler {
return `
-
Add tags to ${characterIds.length} characters
-
+
Modify tags of ${characterIds.length} characters
+ This popup allows you to modify the mutual tags of all selected characters.
+
@@ -203,8 +204,15 @@ class BulkTagPopupHandler {
+
+
+ All
+
+
+
+ Mutual
+
Close
-
Remove all
@@ -215,13 +223,47 @@ class BulkTagPopupHandler {
/**
* Append and show the tag control
*
- * @param characters - The characters assigned to this control
+ * @param characterIds - The characters assigned to this control
*/
- static show(characters) {
- document.body.insertAdjacentHTML('beforeend', this.#getHtml(characters));
- createTagInput('#bulkTagInput', '#bulkTagList');
+ static show(characterIds) {
+ if (characterIds.length == 0) {
+ console.log('No characters selected for bulk edit tags.');
+ return;
+ }
+
+ document.body.insertAdjacentHTML('beforeend', this.#getHtml(characterIds));
+
+ this.mutualTags = this.getMutualTags(characterIds);
+
+ // Print the tag list with all mutuable tags, marking them as removable. That is the initial fill
+ printTagList($('#bulkTagList'), { tags: () => this.getMutualTags(characterIds), tagOptions: { removable: true } });
+
+ // Tag input with empty tags so new tag gets added and it doesn't get emptied on redraw
+ createTagInput('#bulkTagInput', '#bulkTagList', { tags: () => this.getMutualTags(characterIds), tagOptions: { removable: true }});
+
+ document.querySelector('#bulk_tag_popup_reset').addEventListener('click', this.resetTags.bind(this, characterIds));
+ document.querySelector('#bulk_tag_popup_remove_mutual').addEventListener('click', this.removeMutual.bind(this, characterIds));
document.querySelector('#bulk_tag_popup_cancel').addEventListener('click', this.hide.bind(this));
- document.querySelector('#bulk_tag_popup_reset').addEventListener('click', this.resetTags.bind(this, characters));
+ }
+
+ static getMutualTags(characterIds) {
+ if (characterIds.length == 0) {
+ return [];
+ }
+
+ if (characterIds.length === 1) {
+ // Just use tags of the single character
+ return getTagsList(getTagKeyForEntity(characterIds[0]));
+ }
+
+ // Find mutual tags for multiple characters
+ const allTags = characterIds.map(c => getTagsList(getTagKeyForEntity(c)));
+ const mutualTags = allTags.reduce((mutual, characterTags) =>
+ mutual.filter(tag => characterTags.some(cTag => cTag.id === tag.id))
+ );
+
+ this.mutualTags = mutualTags.sort(compareTagsForSort);
+ return this.mutualTags;
}
/**
@@ -242,10 +284,31 @@ class BulkTagPopupHandler {
* @param characterIds
*/
static resetTags(characterIds) {
- characterIds.forEach((characterId) => {
+ for (const characterId of characterIds) {
const key = getTagKeyForEntity(characterId);
if (key) tag_map[key] = [];
- });
+ }
+
+ $('#bulkTagList').empty();
+
+ printCharacters(true);
+ }
+
+ /**
+ * Empty the tag map for the given characters
+ *
+ * @param characterIds
+ */
+ static removeMutual(characterIds) {
+ const mutualTags = this.getMutualTags(characterIds);
+
+ for (const characterId of characterIds) {
+ for(const tag of mutualTags) {
+ removeTagFromMap(tag.id, characterId);
+ }
+ }
+
+ $('#bulkTagList').empty();
printCharacters(true);
}
diff --git a/public/scripts/tags.js b/public/scripts/tags.js
index 2615d87b3..8c92cd265 100644
--- a/public/scripts/tags.js
+++ b/public/scripts/tags.js
@@ -36,6 +36,7 @@ export {
importTags,
sortTags,
compareTagsForSort,
+ removeTagFromMap,
};
const CHARACTER_FILTER_SELECTOR = '#rm_characters_block .rm_tag_filter';
@@ -57,12 +58,12 @@ export const tag_filter_types = {
};
const ACTIONABLE_TAGS = {
- FAV: { id: 1, name: 'Show only favorites', color: 'rgba(255, 255, 0, 0.5)', action: filterByFav, icon: 'fa-solid fa-star', class: 'filterByFavorites' },
- GROUP: { id: 0, name: 'Show only groups', color: 'rgba(100, 100, 100, 0.5)', action: filterByGroups, icon: 'fa-solid fa-users', class: 'filterByGroups' },
- FOLDER: { id: 4, name: 'Always show folders', color: 'rgba(120, 120, 120, 0.5)', action: filterByFolder, icon: 'fa-solid fa-folder-plus', class: 'filterByFolder' },
- VIEW: { id: 2, name: 'Manage tags', color: 'rgba(150, 100, 100, 0.5)', action: onViewTagsListClick, icon: 'fa-solid fa-gear', class: 'manageTags' },
- HINT: { id: 3, name: 'Show Tag List', color: 'rgba(150, 100, 100, 0.5)', action: onTagListHintClick, icon: 'fa-solid fa-tags', class: 'showTagList' },
- UNFILTER: { id: 5, name: 'Clear all filters', action: onClearAllFiltersClick, icon: 'fa-solid fa-filter-circle-xmark', class: 'clearAllFilters' },
+ FAV: { id: 1, sort_order: 1, name: 'Show only favorites', color: 'rgba(255, 255, 0, 0.5)', action: filterByFav, icon: 'fa-solid fa-star', class: 'filterByFavorites' },
+ GROUP: { id: 0, sort_order: 2, name: 'Show only groups', color: 'rgba(100, 100, 100, 0.5)', action: filterByGroups, icon: 'fa-solid fa-users', class: 'filterByGroups' },
+ FOLDER: { id: 4, sort_order: 3, name: 'Always show folders', color: 'rgba(120, 120, 120, 0.5)', action: filterByFolder, icon: 'fa-solid fa-folder-plus', class: 'filterByFolder' },
+ VIEW: { id: 2, sort_order: 4, name: 'Manage tags', color: 'rgba(150, 100, 100, 0.5)', action: onViewTagsListClick, icon: 'fa-solid fa-gear', class: 'manageTags' },
+ HINT: { id: 3, sort_order: 5, name: 'Show Tag List', color: 'rgba(150, 100, 100, 0.5)', action: onTagListHintClick, icon: 'fa-solid fa-tags', class: 'showTagList' },
+ UNFILTER: { id: 5, sort_order: 6, name: 'Clear all filters', action: onClearAllFiltersClick, icon: 'fa-solid fa-filter-circle-xmark', class: 'clearAllFilters' },
};
const InListActionable = {
@@ -390,7 +391,15 @@ function findTag(request, resolve, listSelector) {
resolve(result);
}
-function selectTag(event, ui, listSelector) {
+/**
+ * Select a tag and add it to the list. This function is mostly used as an event handler for the tag selector control.
+ * @param {*} event -
+ * @param {*} ui -
+ * @param {*} listSelector - The selector of the list to print/add to
+ * @param {PrintTagListOptions} [tagListOptions] - Optional parameters for printing the tag list. Can be set to be consistent with the expected behavior of tags in the list that was defined before.
+ * @returns {boolean} false, to keep the input clear
+ */
+function selectTag(event, ui, listSelector, tagListOptions = {}) {
let tagName = ui.item.value;
let tag = tags.find(t => t.name === tagName);
@@ -414,9 +423,28 @@ function selectTag(event, ui, listSelector) {
saveSettingsDebounced();
+ // If we have a manual list of tags to print, we should add this tag here to that manual list, otherwise it may not get printed
+ if (tagListOptions.tags !== undefined) {
+ const tagExists = (tags, tag) => tags.some(x => x.id === tag.id);
+
+ if (typeof tagListOptions.tags === 'function') {
+ // If 'tags' is a function, wrap it to include new tag upon invocation
+ const originalTagsFunction = tagListOptions.tags;
+ tagListOptions.tags = () => {
+ const currentTags = originalTagsFunction();
+ return tagExists(currentTags, tag) ? currentTags : [...currentTags, tag];
+ };
+ } else {
+ tagListOptions.tags = tagExists(tagListOptions.tags, tag) ? tags : [...tagListOptions.tags, tag];
+ }
+ }
+
// add tag to the UI and internal map - we reprint so sorting and new markup is done correctly
- printTagList(listSelector, { tagOptions: { removable: true } });
- printTagList($(getInlineListSelector()));
+ printTagList(listSelector, tagListOptions);
+ const inlineSelector = getInlineListSelector();
+ if (inlineSelector) {
+ printTagList($(inlineSelector), tagListOptions);
+ }
printTagFilters(tag_filter_types.character);
printTagFilters(tag_filter_types.group_member);
@@ -492,7 +520,7 @@ function createNewTag(tagName) {
}
/**
- * @typedef {object} TagOptions
+ * @typedef {object} TagOptions - Options for tag behavior. (Same object will be passed into "appendTagToList")
* @property {boolean} [removable=false] - Whether tags can be removed.
* @property {boolean} [selectable=false] - Whether tags can be selected.
* @property {function} [action=undefined] - Action to perform on tag interaction.
@@ -500,20 +528,24 @@ function createNewTag(tagName) {
* @property {boolean} [skipExistsCheck=false] - If true, the tag gets added even if a tag with the same id already exists.
*/
+/**
+ * @typedef {object} PrintTagListOptions - Optional parameters for printing the tag list.
+ * @property {Array
diff --git a/public/style.css b/public/style.css
index 3f54d5cec..6e000651d 100644
--- a/public/style.css
+++ b/public/style.css
@@ -1416,7 +1416,7 @@ input[type="file"] {
}
.extension_token_counter {
- font-size: calc(var(--mainFontSize) * 0.9);
+ font-size: calc(var(--mainFontSize) * 0.875);
width: 100%;
text-align: right;
margin-bottom: 5px;
From 4b6a3054b1a63e89ab6b2e0f4b1b095ecea0d196 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Thu, 28 Mar 2024 02:27:37 +0200
Subject: [PATCH 101/255] Implement user alignment message
---
public/script.js | 45 ++++++++++++++++++++++++++++++++++++++-------
1 file changed, 38 insertions(+), 7 deletions(-)
diff --git a/public/script.js b/public/script.js
index b07ca95be..634c3b5f4 100644
--- a/public/script.js
+++ b/public/script.js
@@ -3264,6 +3264,8 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu
let chat2 = [];
let continue_mag = '';
+ const userMessageIndices = [];
+
for (let i = coreChat.length - 1, j = 0; i >= 0; i--, j++) {
if (main_api == 'openai') {
chat2[i] = coreChat[j].mes;
@@ -3291,6 +3293,22 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu
chat2[i] = chat2[i].slice(0, chat2[i].lastIndexOf(coreChat[j].mes) + coreChat[j].mes.length);
continue_mag = coreChat[j].mes;
}
+
+ if (coreChat[j].is_user) {
+ userMessageIndices.push(i);
+ }
+ }
+
+ let addUserAlignment = isInstruct && power_user.instruct.user_alignment_message;
+ let userAlignmentMessage = '';
+
+ if (addUserAlignment) {
+ const alignmentMessage = {
+ name: name1,
+ mes: power_user.instruct.user_alignment_message,
+ is_user: true,
+ };
+ userAlignmentMessage = formatMessageHistoryItem(alignmentMessage, isInstruct, false);
}
// Add persona description to prompt
@@ -3349,6 +3367,7 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu
allAnchors,
quiet_prompt,
cyclePrompt,
+ userAlignmentMessage,
].join('').replace(/\r/gm, '');
return getTokenCount(encodeString, power_user.token_padding);
}
@@ -3367,16 +3386,19 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu
// Collect enough messages to fill the context
let arrMes = [];
let tokenCount = getMessagesTokenCount();
- for (let item of chat2) {
+ let lastAddedIndex = -1;
+ for (let i = 0; i < chat2.length; i++) {
// not needed for OAI prompting
if (main_api == 'openai') {
break;
}
+ const item = chat2[i];
tokenCount += getTokenCount(item.replace(/\r/gm, ''));
chatString = item + chatString;
if (tokenCount < this_max_context) {
arrMes[arrMes.length] = item;
+ lastAddedIndex = i;
} else {
break;
}
@@ -3385,8 +3407,21 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu
await delay(1);
}
+ const stoppedAtUser = userMessageIndices.includes(lastAddedIndex);
+ if (addUserAlignment && !stoppedAtUser) {
+ tokenCount += getTokenCount(userAlignmentMessage.replace(/\r/gm, ''));
+ chatString = userAlignmentMessage + chatString;
+ arrMes[arrMes.length] = userAlignmentMessage;
+ // Injected indices shift by 1 for user alignment message at the beginning
+ injectedIndices.forEach((value, index) => (injectedIndices[index] = value + 1));
+ injectedIndices.push(0);
+ }
+
+ // Filter injections which don't fit in the context
+ injectedIndices = injectedIndices.filter(value => value < arrMes.length);
+
if (main_api !== 'openai') {
- setInContextMessages(arrMes.length, type);
+ setInContextMessages(arrMes.length - injectedIndices.length, type);
}
// Estimate how many unpinned example messages fit in the context
@@ -3664,7 +3699,7 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu
};
finalMesSend.forEach((item, i) => {
- item.injected = Array.isArray(injectedIndices) && injectedIndices.includes(i);
+ item.injected = injectedIndices.includes(finalMesSend.length - i - 1);
});
let data = {
@@ -4030,10 +4065,6 @@ function doChatInject(messages, isContinue) {
}
}
- for (let i = 0; i < injectedIndices.length; i++) {
- injectedIndices[i] = messages.length - injectedIndices[i] - 1;
- }
-
messages.reverse();
return injectedIndices;
}
From 6e411b06b9b823af79a1874439a4221836846359 Mon Sep 17 00:00:00 2001
From: P3il4 <42489293+P3il4@users.noreply.github.com>
Date: Thu, 28 Mar 2024 03:32:57 +0300
Subject: [PATCH 102/255] Fix prompt cache after API error
---
public/script.js | 2 ++
1 file changed, 2 insertions(+)
diff --git a/public/script.js b/public/script.js
index 36539cc9a..97e30e42f 100644
--- a/public/script.js
+++ b/public/script.js
@@ -3973,6 +3973,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;
From 689af3151a9a50b7b0770e55958ccab2be884bae Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Thu, 28 Mar 2024 02:59:52 +0200
Subject: [PATCH 103/255] Pre-populate chat history with injections
---
public/script.js | 64 ++++++++++++++++++++++++++++++++++++------------
1 file changed, 49 insertions(+), 15 deletions(-)
diff --git a/public/script.js b/public/script.js
index 634c3b5f4..cfbfc7700 100644
--- a/public/script.js
+++ b/public/script.js
@@ -3384,21 +3384,19 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu
}
// Collect enough messages to fill the context
- let arrMes = [];
+ let arrMes = new Array(chat2.length);
let tokenCount = getMessagesTokenCount();
let lastAddedIndex = -1;
- for (let i = 0; i < chat2.length; i++) {
- // not needed for OAI prompting
- if (main_api == 'openai') {
- break;
- }
- const item = chat2[i];
+ // Pre-allocate all injections first.
+ // If it doesn't fit - user shot himself in the foot
+ for (const index of injectedIndices) {
+ const item = chat2[index];
tokenCount += getTokenCount(item.replace(/\r/gm, ''));
chatString = item + chatString;
if (tokenCount < this_max_context) {
- arrMes[arrMes.length] = item;
- lastAddedIndex = i;
+ arrMes[index] = item;
+ lastAddedIndex = Math.max(lastAddedIndex, index);
} else {
break;
}
@@ -3407,18 +3405,54 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu
await delay(1);
}
+ for (let i = 0; i < chat2.length; i++) {
+ // not needed for OAI prompting
+ if (main_api == 'openai') {
+ break;
+ }
+
+ // Skip already injected messages
+ if (arrMes[i] !== undefined) {
+ continue;
+ }
+
+ const item = chat2[i];
+ tokenCount += getTokenCount(item.replace(/\r/gm, ''));
+ chatString = item + chatString;
+ if (tokenCount < this_max_context) {
+ arrMes[i] = item;
+ lastAddedIndex = Math.max(lastAddedIndex, i);
+ } else {
+ break;
+ }
+
+ // Prevent UI thread lock on tokenization
+ await delay(1);
+ }
+
+ // Add user alignment message if last message is not a user message
const stoppedAtUser = userMessageIndices.includes(lastAddedIndex);
if (addUserAlignment && !stoppedAtUser) {
tokenCount += getTokenCount(userAlignmentMessage.replace(/\r/gm, ''));
chatString = userAlignmentMessage + chatString;
- arrMes[arrMes.length] = userAlignmentMessage;
- // Injected indices shift by 1 for user alignment message at the beginning
- injectedIndices.forEach((value, index) => (injectedIndices[index] = value + 1));
- injectedIndices.push(0);
+ arrMes.push(userAlignmentMessage);
+ injectedIndices.push(arrMes.length - 1);
}
- // Filter injections which don't fit in the context
- injectedIndices = injectedIndices.filter(value => value < arrMes.length);
+ // Unsparse the array. Adjust injected indices
+ const newArrMes = [];
+ const newInjectedIndices = [];
+ for (let i = 0; i < arrMes.length; i++) {
+ if (arrMes[i] !== undefined) {
+ newArrMes.push(arrMes[i]);
+ if (injectedIndices.includes(i)) {
+ newInjectedIndices.push(newArrMes.length - 1);
+ }
+ }
+ }
+
+ arrMes = newArrMes;
+ injectedIndices = newInjectedIndices;
if (main_api !== 'openai') {
setInContextMessages(arrMes.length - injectedIndices.length, type);
From 3e49c9d02c6e9a0537a941c6f6c21ef554e10b89 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Thu, 28 Mar 2024 14:47:24 +0200
Subject: [PATCH 104/255] &rcub => }
---
public/scripts/templates/macros.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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.
From a951f68c8df1b4b26374d2e90b726d8e8f4d6260 Mon Sep 17 00:00:00 2001
From: Wolfsblvt
Date: Fri, 29 Mar 2024 02:20:16 +0100
Subject: [PATCH 115/255] cli server args precedency fix + port/listen arg
- Fixes precedence: cli > (env) > yaml > default
- Add cli arguments for port and listen
---
server.js | 34 ++++++++++++++++++++++++----------
1 file changed, 24 insertions(+), 10 deletions(-)
diff --git a/server.js b/server.js
index 77ab7073f..ce7007f9b 100644
--- a/server.js
+++ b/server.js
@@ -55,15 +55,29 @@ if (process.versions && process.versions.node && process.versions.node.match(/20
// Set default DNS resolution order to IPv4 first
dns.setDefaultResultOrder('ipv4first');
+const DEFAULT_PORT = 8000;
+const DEFAULT_AUTORUN = false;
+const DEFAULT_LISTEN = false;
+const DEFAULT_CORS_PROXY = false;
+
const cliArguments = yargs(hideBin(process.argv))
- .option('autorun', {
+ .usage('Usage: [options]')
+ .option('port', {
+ type: 'number',
+ default: null,
+ describe: `Sets the port under which SillyTavern will run.\nIf not provided falls back to yaml config 'port'.\n[config default: ${DEFAULT_PORT}]`,
+ }).option('autorun', {
type: 'boolean',
- default: false,
- describe: 'Automatically launch SillyTavern in the browser.',
+ default: null,
+ describe: `Automatically launch SillyTavern in the browser.\nAutorun is automatically disabled if --ssl is set to true.\nIf not provided falls back to yaml config 'autorun'.\n[config default: ${DEFAULT_AUTORUN}]`,
+ }).option('listen', {
+ type: 'boolean',
+ default: null,
+ describe: `SillyTavern is listening on all network interfaces (Wi-Fi, LAN, localhost). If false, will limit it only to internal localhost (127.0.0.1).\nIf not provided falls back to yaml config 'listen'.\n[config default: ${DEFAULT_LISTEN}]`,
}).option('corsProxy', {
type: 'boolean',
- default: false,
- describe: 'Enables CORS proxy',
+ default: null,
+ describe: `Enables CORS proxy\nIf not provided falls back to yaml config 'enableCorsProxy'.\n[config default: ${DEFAULT_CORS_PROXY}]`,
}).option('disableCsrf', {
type: 'boolean',
default: false,
@@ -91,10 +105,10 @@ const app = express();
app.use(compression());
app.use(responseTime());
-const server_port = process.env.SILLY_TAVERN_PORT || getConfigValue('port', 8000);
-
-const autorun = (getConfigValue('autorun', false) || cliArguments.autorun) && !cliArguments.ssl;
-const listen = getConfigValue('listen', false);
+const server_port = cliArguments.port ?? process.env.SILLY_TAVERN_PORT ?? getConfigValue('port', DEFAULT_PORT);
+const autorun = (cliArguments.autorun ?? getConfigValue('autorun', DEFAULT_AUTORUN)) && !cliArguments.ssl;
+const listen = cliArguments.listen ?? getConfigValue('listen', DEFAULT_LISTEN);
+const enableCorsProxy = cliArguments.corsProxy ?? getConfigValue('enableCorsProxy', DEFAULT_CORS_PROXY)
const { DIRECTORIES, UPLOADS_PATH } = require('./src/constants');
@@ -144,7 +158,7 @@ if (!cliArguments.disableCsrf) {
});
}
-if (getConfigValue('enableCorsProxy', false) || cliArguments.corsProxy) {
+if (enableCorsProxy) {
const bodyParser = require('body-parser');
app.use(bodyParser.json({
limit: '200mb',
From 167673fcf56f164c6c6adb10b6917ce1199c02fc Mon Sep 17 00:00:00 2001
From: Wolfsblvt
Date: Fri, 29 Mar 2024 04:41:16 +0100
Subject: [PATCH 116/255] Updated code documentation
- Updated code documentation for all methods added/changed with this PR
- Expanded tooltip to "bulk edit" to explain how it works
---
public/index.html | 2 +-
public/scripts/BulkEditOverlay.js | 74 ++++++++++++++++++++++---------
public/scripts/tags.js | 14 +++---
3 files changed, 63 insertions(+), 27 deletions(-)
diff --git a/public/index.html b/public/index.html
index 8b2ee23a5..098ceb30d 100644
--- a/public/index.html
+++ b/public/index.html
@@ -4327,7 +4327,7 @@
-
+
diff --git a/public/scripts/BulkEditOverlay.js b/public/scripts/BulkEditOverlay.js
index 8da4dc86b..48d95e68b 100644
--- a/public/scripts/BulkEditOverlay.js
+++ b/public/scripts/BulkEditOverlay.js
@@ -40,7 +40,7 @@ class CharacterContextMenu {
* Tag one or more characters,
* opens a popup.
*
- * @param selectedCharacters
+ * @param {Array} selectedCharacters
*/
static tag = (selectedCharacters) => {
BulkTagPopupHandler.show(selectedCharacters);
@@ -49,7 +49,7 @@ class CharacterContextMenu {
/**
* Duplicate one or more characters
*
- * @param characterId
+ * @param {number} characterId
* @returns {Promise}
*/
static duplicate = async (characterId) => {
@@ -74,7 +74,7 @@ class CharacterContextMenu {
* Favorite a character
* and highlight it.
*
- * @param characterId
+ * @param {number} characterId
* @returns {Promise}
*/
static favorite = async (characterId) => {
@@ -110,7 +110,7 @@ class CharacterContextMenu {
* Convert one or more characters to persona,
* may open a popup for one or more characters.
*
- * @param characterId
+ * @param {number} characterId
* @returns {Promise}
*/
static persona = async (characterId) => await convertCharacterToPersona(characterId);
@@ -119,8 +119,8 @@ class CharacterContextMenu {
* Delete one or more characters,
* opens a popup.
*
- * @param characterId
- * @param deleteChats
+ * @param {number} characterId
+ * @param {boolean} [deleteChats]
* @returns {Promise}
*/
static delete = async (characterId, deleteChats = false) => {
@@ -234,7 +234,7 @@ class BulkTagPopupHandler {
/**
* Append and show the tag control
*
- * @param characterIds - The characters assigned to this control
+ * @param {Array} characterIds - The characters assigned to this control
*/
static show(characterIds) {
if (characterIds.length == 0) {
@@ -250,7 +250,7 @@ class BulkTagPopupHandler {
// Print the tag list with all mutuable tags, marking them as removable. That is the initial fill
printTagList($('#bulkTagList'), { tags: () => this.getMutualTags(characterIds), tagOptions: { removable: true } });
- // Tag input with empty tags so new tag gets added and it doesn't get emptied on redraw
+ // Tag input with resolvable list for the mutual tags to get redrawn, so that newly added tags get sorted correctly
createTagInput('#bulkTagInput', '#bulkTagList', { tags: () => this.getMutualTags(characterIds), tagOptions: { removable: true }});
document.querySelector('#bulk_tag_popup_reset').addEventListener('click', this.resetTags.bind(this, characterIds));
@@ -258,6 +258,12 @@ class BulkTagPopupHandler {
document.querySelector('#bulk_tag_popup_cancel').addEventListener('click', this.hide.bind(this));
}
+ /**
+ * Builds a list of all tags that the provided characters have in common.
+ *
+ * @param {Array} characterIds - The characters to find mutual tags for
+ * @returns {Array} A list of mutual tags
+ */
static getMutualTags(characterIds) {
if (characterIds.length == 0) {
return [];
@@ -293,7 +299,7 @@ class BulkTagPopupHandler {
/**
* Empty the tag map for the given characters
*
- * @param characterIds
+ * @param {Array} characterIds
*/
static resetTags(characterIds) {
for (const characterId of characterIds) {
@@ -307,9 +313,9 @@ class BulkTagPopupHandler {
}
/**
- * Empty the tag map for the given characters
+ * Remove the mutual tags for all given characters
*
- * @param characterIds
+ * @param {Array} characterIds
*/
static removeMutual(characterIds) {
const mutualTags = this.getMutualTags(characterIds);
@@ -627,6 +633,15 @@ class BulkEditOverlay {
this.#cancelNextToggle = false;
};
+ /**
+ * When shift click was held down, this function handles the multi select of characters in a single click.
+ *
+ * If the last clicked character was deselected, and the current one was deselected too, it will deselect all currently selected characters between those two.
+ * If the last clicked character was selected, and the current one was selected too, it will select all currently not selected characters between those two.
+ * If the states do not match, nothing will happen.
+ *
+ * @param {HTMLElement} currentCharacter - The html element of the currently toggled character
+ */
handleShiftClick = (currentCharacter) => {
const characterId = currentCharacter.getAttribute('chid');
const select = !this.selectedCharacters.includes(characterId);
@@ -634,11 +649,18 @@ class BulkEditOverlay {
if (this.lastSelected.characterId && this.lastSelected.select !== undefined) {
// Only if select state and the last select state match we execute the range select
if (select === this.lastSelected.select) {
- this.selectCharactersInRange(currentCharacter, select);
+ this.toggleCharactersInRange(currentCharacter, select);
}
}
};
+ /**
+ * Toggles the selection of a given characters
+ *
+ * @param {HTMLElement} character - The html element of a character
+ * @param {object} param1 - Optional params
+ * @param {boolean} [param1.markState] - Whether the toggle of this character should be remembered as the last done toggle
+ */
toggleSingleCharacter = (character, { markState = true } = {}) => {
const characterId = character.getAttribute('chid');
@@ -648,11 +670,11 @@ class BulkEditOverlay {
if (select) {
character.classList.add(BulkEditOverlay.selectedClass);
if (legacyBulkEditCheckbox) legacyBulkEditCheckbox.checked = true;
- this.selectCharacter(characterId);
+ this.#selectedCharacters.push(String(characterId));
} else {
character.classList.remove(BulkEditOverlay.selectedClass);
if (legacyBulkEditCheckbox) legacyBulkEditCheckbox.checked = false;
- this.dismissCharacter(characterId);
+ this.#selectedCharacters = this.#selectedCharacters.filter(item => String(characterId) !== item)
}
this.updateSelectedCount();
@@ -663,12 +685,24 @@ class BulkEditOverlay {
}
};
+ /**
+ * Updates the selected count element with the current count
+ *
+ * @param {number} [countOverride] - optional override for a manual number to set
+ */
updateSelectedCount = (countOverride = undefined) => {
const count = countOverride ?? this.selectedCharacters.length;
$(`#${BulkEditOverlay.bulkSelectedCountId}`).text(count).attr('title', `${count} characters selected`);
};
- selectCharactersInRange = (currentCharacter, select) => {
+ /**
+ * Toggles the selection of characters in a given range.
+ * The range is provided by the given character and the last selected one remembered in the selection state.
+ *
+ * @param {HTMLElement} currentCharacter - The html element of the currently toggled character
+ * @param {boolean} select - true if the characters in the range are to be selected, false if deselected
+ */
+ toggleCharactersInRange = (currentCharacter, select) => {
const currentCharacterId = currentCharacter.getAttribute('chid');
const characters = Array.from(document.querySelectorAll('#' + BulkEditOverlay.containerId + ' .' + BulkEditOverlay.characterClass));
@@ -680,8 +714,10 @@ class BulkEditOverlay {
const characterId = character.getAttribute('chid');
const isCharacterSelected = this.selectedCharacters.includes(characterId);
- if (select && !isCharacterSelected || !select && isCharacterSelected) {
- this.toggleSingleCharacter(character, { markState: currentCharacterId == i });
+ // Only toggle the character if it wasn't on the state we have are toggling towards.
+ // Also doing a weird type check, because typescript checker doesn't like the return of 'querySelectorAll'.
+ if ((select && !isCharacterSelected || !select && isCharacterSelected) && character instanceof HTMLElement) {
+ this.toggleSingleCharacter(character, { markState: currentCharacterId == characterId });
}
}
};
@@ -771,10 +807,6 @@ class BulkEditOverlay {
addStateChangeCallback = callback => this.stateChangeCallbacks.push(callback);
- selectCharacter = characterId => this.selectedCharacters.push(String(characterId));
-
- dismissCharacter = characterId => this.#selectedCharacters = this.selectedCharacters.filter(item => String(characterId) !== item);
-
/**
* Clears internal character storage and
* removes visual highlight.
diff --git a/public/scripts/tags.js b/public/scripts/tags.js
index 8895545e8..816087d73 100644
--- a/public/scripts/tags.js
+++ b/public/scripts/tags.js
@@ -320,7 +320,8 @@ function getTagKey() {
/**
* Gets the tag key for any provided entity/id/key. If a valid tag key is provided, it just returns this.
- * Robust method to find a valid tag key for any entity
+ * Robust method to find a valid tag key for any entity.
+ *
* @param {object|number|string} entityOrKey An entity with id property (character, group, tag), or directly an id or tag key.
* @returns {string} The tag key that can be found.
*/
@@ -394,9 +395,10 @@ function findTag(request, resolve, listSelector) {
}
/**
- * Select a tag and add it to the list. This function is mostly used as an event handler for the tag selector control.
- * @param {*} event -
- * @param {*} ui -
+ * Select a tag and add it to the list. This function is (mostly) used as an event handler for the tag selector control.
+ *
+ * @param {*} event - The event that fired on autocomplete select
+ * @param {*} ui - An Object with label and value properties for the selected option
* @param {*} listSelector - The selector of the list to print/add to
* @param {PrintTagListOptions} [tagListOptions] - Optional parameters for printing the tag list. Can be set to be consistent with the expected behavior of tags in the list that was defined before.
* @returns {boolean} false, to keep the input clear
@@ -529,7 +531,8 @@ function createNewTag(tagName) {
*/
/**
- * Prints the list of tags.
+ * Prints the list of tags
+ *
* @param {JQuery} element - The container element where the tags are to be printed.
* @param {PrintTagListOptions} [options] - Optional parameters for printing the tag list.
*/
@@ -798,6 +801,7 @@ function applyTagsOnGroupSelect() {
}
/**
+ * Create a tag input by enabling the autocomplete feature of a given input element. Tags will be added to the given list.
*
* @param {string} inputSelector - the selector for the tag input control
* @param {string} listSelector - the selector for the list of the tags modified by the input control
From bf8b6b80d70b0bd779625351c033e8202daee9f2 Mon Sep 17 00:00:00 2001
From: Wolfsblvt
Date: Fri, 29 Mar 2024 05:53:26 +0100
Subject: [PATCH 117/255] Refactor and improve bulk delete popup
- Improve bulk edit popup with display of avatars and better format
- Refactor both calls of bulk delete to use the same method
- Add display of filename on avatar hover for inline avatars (@Cohee you forgot this one (: )
---
public/script.js | 2 +-
public/scripts/BulkEditOverlay.js | 59 +++++++++++++++++++++----------
public/scripts/bulk-edit.js | 28 ++-------------
public/style.css | 2 +-
4 files changed, 45 insertions(+), 46 deletions(-)
diff --git a/public/script.js b/public/script.js
index 9093abf67..e9bd89da0 100644
--- a/public/script.js
+++ b/public/script.js
@@ -5357,7 +5357,7 @@ function buildAvatarList(block, entities, { templateId = 'inline_avatar_template
avatarTemplate.attr('data-type', entity.type);
avatarTemplate.attr({ 'chid': id, 'id': `CharID${id}` });
avatarTemplate.find('img').attr('src', this_avatar).attr('alt', entity.item.name);
- avatarTemplate.attr('title', `[Character] ${entity.item.name}`);
+ avatarTemplate.attr('title', `[Character] ${entity.item.name}\nFile: ${entity.item.avatar}`);
if (highlightFavs) {
avatarTemplate.toggleClass('is_fav', entity.item.fav || entity.item.fav == 'true');
avatarTemplate.find('.ch_fav').val(entity.item.fav);
diff --git a/public/scripts/BulkEditOverlay.js b/public/scripts/BulkEditOverlay.js
index 48d95e68b..19955e7ac 100644
--- a/public/scripts/BulkEditOverlay.js
+++ b/public/scripts/BulkEditOverlay.js
@@ -19,18 +19,6 @@ import { hideLoader, showLoader } from './loader.js';
import { convertCharacterToPersona } from './personas.js';
import { createTagInput, getTagKeyForEntity, getTagsList, printTagList, tag_map, compareTagsForSort, removeTagFromMap } from './tags.js';
-// Utility object for popup messages.
-const popupMessage = {
- deleteChat(characterCount) {
- return `
Delete ${characterCount} characters?
- THIS IS PERMANENT!
-
-
- Also delete the chat files
- `;
- },
-};
-
/**
* Static object representing the actions of the
* character context menu override.
@@ -198,6 +186,12 @@ class CharacterContextMenu {
* Represents a tag control not bound to a single character
*/
class BulkTagPopupHandler {
+ /**
+ * Gets the HTML as a string that is going to be the popup for the bulk tag edit
+ *
+ * @param {Array} characterIds - The characters that are shown inside the popup
+ * @returns String containing the html for the popup
+ */
static #getHtml = (characterIds) => {
const characterData = JSON.stringify({ characterIds: characterIds });
return `
@@ -227,8 +221,7 @@ class BulkTagPopupHandler {
-
- `;
+
`;
};
/**
@@ -430,7 +423,7 @@ class BulkEditOverlay {
/**
*
- * @returns {*[]}
+ * @returns {number[]}
*/
get selectedCharacters() {
return this.#selectedCharacters;
@@ -775,6 +768,29 @@ class BulkEditOverlay {
this.browseState();
};
+ /**
+ * Gets the HTML as a string that is displayed inside the popup for the bulk delete
+ *
+ * @param {Array} characterIds - The characters that are shown inside the popup
+ * @returns String containing the html for the popup content
+ */
+ static #getDeletePopupContentHtml = (characterIds) => {
+ return `
+
Delete ${characterIds.length} characters?
+
+
+ THIS IS PERMANENT!
+
+
+
+
+
+
+ Also delete the chat files
+
+
`;
+ }
+
/**
* Request user input before concurrently handle deletion
* requests.
@@ -782,8 +798,9 @@ class BulkEditOverlay {
* @returns {Promise}
*/
handleContextMenuDelete = () => {
- callPopup(
- popupMessage.deleteChat(this.selectedCharacters.length), null)
+ const characterIds = this.selectedCharacters;
+ const popupContent = BulkEditOverlay.#getDeletePopupContentHtml(characterIds);
+ const promise = callPopup(popupContent, null)
.then((accept) => {
if (true !== accept) return;
@@ -791,11 +808,17 @@ class BulkEditOverlay {
showLoader();
toastr.info('We\'re deleting your characters, please wait...', 'Working on it');
- Promise.allSettled(this.selectedCharacters.map(async characterId => CharacterContextMenu.delete(characterId, deleteChats)))
+ return Promise.allSettled(characterIds.map(async characterId => CharacterContextMenu.delete(characterId, deleteChats)))
.then(() => getCharacters())
.then(() => this.browseState())
.finally(() => hideLoader());
});
+
+ // At this moment the popup is already changed in the dom, but not yet closed/resolved. We build the avatar list here
+ const entities = characterIds.map(id => characterToEntity(characters[id], id)).filter(entity => entity.item !== undefined);
+ buildAvatarList($('#bulk_delete_avatars_block'), entities);
+
+ return promise;
};
/**
diff --git a/public/scripts/bulk-edit.js b/public/scripts/bulk-edit.js
index 95d0d79bb..c266e148b 100644
--- a/public/scripts/bulk-edit.js
+++ b/public/scripts/bulk-edit.js
@@ -84,32 +84,8 @@ async function deleteCharacter(this_chid) {
async function onDeleteButtonClick() {
console.log('Delete button clicked');
- // Create a mapping of chid to avatar
- let toDelete = [];
- $('.bulk_select_checkbox:checked').each((i, el) => {
- const chid = $(el).parent().attr('chid');
- const avatar = characters[chid].avatar;
- // Add the avatar to the list of avatars to delete
- toDelete.push(avatar);
- });
-
- const confirm = await callPopup('
Are you sure you want to delete these characters?
You would need to delete the chat files manually. ', 'confirm');
-
- if (!confirm) {
- console.log('User cancelled delete');
- return;
- }
-
- // Delete the characters
- for (const avatar of toDelete) {
- console.log(`Deleting character with avatar ${avatar}`);
- await getCharacters();
-
- //chid should be the key of the character with the given avatar
- const chid = Object.keys(characters).find((key) => characters[key].avatar === avatar);
- console.log(`Deleting character with chid ${chid}`);
- await deleteCharacter(chid);
- }
+ // We just let the button trigger the context menu delete option
+ await characterGroupOverlay.handleContextMenuDelete();
}
/**
diff --git a/public/style.css b/public/style.css
index f8aecba0c..e6218e820 100644
--- a/public/style.css
+++ b/public/style.css
@@ -3135,7 +3135,7 @@ body.big-avatars .missing-avatar {
}
}
-span.warning {
+.warning {
color: var(--warning);
font-weight: bolder;
}
From c39b0ed3d96b88b597787ef683b753a401ce5f18 Mon Sep 17 00:00:00 2001
From: deffcolony <61471128+deffcolony@users.noreply.github.com>
Date: Fri, 29 Mar 2024 13:32:47 +0100
Subject: [PATCH 118/255] issue labeler
+added automatic label system for a more easy filter
---
.github/ISSUE_TEMPLATE/bug-report.yml | 10 +++++-----
.github/labeler.yml | 6 ++++++
.github/workflows/labeler.yml | 19 +++++++++++++++++++
3 files changed, 30 insertions(+), 5 deletions(-)
create mode 100644 .github/labeler.yml
create mode 100644 .github/workflows/labeler.yml
diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml
index 82cc9c9cf..58f8ae2eb 100644
--- a/.github/ISSUE_TEMPLATE/bug-report.yml
+++ b/.github/ISSUE_TEMPLATE/bug-report.yml
@@ -9,11 +9,11 @@ body:
label: Environment
description: Where are you running SillyTavern?
options:
- - Self-Hosted (Bare Metal)
- - Self-Hosted (Docker)
- - Android (Termux)
- - Cloud Service (Static)
- - Other (Specify below)
+ - 🪟 Windows
+ - 🐧 Linux
+ - 📱 Termux
+ - 🐋 Docker
+ - 🍎 Mac
validations:
required: true
diff --git a/.github/labeler.yml b/.github/labeler.yml
new file mode 100644
index 000000000..fafd4435d
--- /dev/null
+++ b/.github/labeler.yml
@@ -0,0 +1,6 @@
+# Add/remove 'critical' label if issue contains the words 'urgent' or 'critical'
+#critical:
+# - '(critical|urgent)'
+
+Environment:
+ - '(🪟 Windows|🍎 Mac|🐋 Docker|📱 Termux|🐧 Linux)'
\ No newline at end of file
diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml
new file mode 100644
index 000000000..554c588b8
--- /dev/null
+++ b/.github/workflows/labeler.yml
@@ -0,0 +1,19 @@
+name: "Issue Labeler"
+on:
+ issues:
+ types: [opened, edited]
+
+permissions:
+ issues: write
+ contents: read
+
+jobs:
+ triage:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: github/issue-labeler@v3.4
+ with:
+ configuration-path: .github/labeler.yml
+# not-before: 2020-01-15T02:54:32Z # optional and will result in any issues prior to this timestamp to be ignored.
+ enable-versioned-regex: 0
+ repo-token: ${{ github.token }}
\ No newline at end of file
From da035d4984555da0b19b028d4a0e635056142c73 Mon Sep 17 00:00:00 2001
From: deffcolony <61471128+deffcolony@users.noreply.github.com>
Date: Fri, 29 Mar 2024 13:48:20 +0100
Subject: [PATCH 119/255] Update labeler.yml
---
.github/labeler.yml | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)
diff --git a/.github/labeler.yml b/.github/labeler.yml
index fafd4435d..850096c9e 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -2,5 +2,17 @@
#critical:
# - '(critical|urgent)'
-Environment:
- - '(🪟 Windows|🍎 Mac|🐋 Docker|📱 Termux|🐧 Linux)'
\ No newline at end of file
+🪟 Windows:
+ - '(🪟 Windows)'
+
+🍎 Mac:
+ - '(🍎 Mac)'
+
+🐋 Docker:
+ - '(🐋 Docker)'
+
+📱 Termux:
+ - '(📱 Termux)'
+
+🐧 Linux:
+ - '(🐧 Linux)'
\ No newline at end of file
From 2d5b871f2a8a397ffbce0f02835759c0aa2f6552 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Fri, 29 Mar 2024 15:01:08 +0200
Subject: [PATCH 120/255] Fix array access
---
public/script.js | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/public/script.js b/public/script.js
index c2096a108..04fb0238d 100644
--- a/public/script.js
+++ b/public/script.js
@@ -3389,6 +3389,11 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu
// If it doesn't fit - user shot himself in the foot
for (const index of injectedIndices) {
const item = chat2[index];
+
+ if (typeof item !== 'string') {
+ continue;
+ }
+
tokenCount += getTokenCount(item.replace(/\r/gm, ''));
chatString = item + chatString;
if (tokenCount < this_max_context) {
@@ -3414,6 +3419,11 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu
}
const item = chat2[i];
+
+ if (typeof item !== 'string') {
+ continue;
+ }
+
tokenCount += getTokenCount(item.replace(/\r/gm, ''));
chatString = item + chatString;
if (tokenCount < this_max_context) {
From a3ec0938c55c96aa9f243a9efd959d22851b8ad0 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Fri, 29 Mar 2024 17:28:28 +0200
Subject: [PATCH 121/255] KoboldCpp grammar fix
---
public/scripts/textgen-settings.js | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/public/scripts/textgen-settings.js b/public/scripts/textgen-settings.js
index 515355e59..fdd2380f7 100644
--- a/public/scripts/textgen-settings.js
+++ b/public/scripts/textgen-settings.js
@@ -38,7 +38,7 @@ export const textgen_types = {
OPENROUTER: 'openrouter',
};
-const { MANCER, APHRODITE, TABBY, TOGETHERAI, OOBA, OLLAMA, LLAMACPP, INFERMATICAI, DREAMGEN, OPENROUTER } = textgen_types;
+const { MANCER, APHRODITE, TABBY, TOGETHERAI, OOBA, OLLAMA, LLAMACPP, INFERMATICAI, DREAMGEN, OPENROUTER, KOBOLDCPP } = textgen_types;
const LLAMACPP_DEFAULT_ORDER = [
'top_k',
@@ -1047,6 +1047,10 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
//'prompt_logprobs': settings.prompt_log_probs_aphrodite,
};
+ if (settings.type === KOBOLDCPP) {
+ params.grammar = settings.grammar_string;
+ }
+
if (settings.type === MANCER) {
params.n = canMultiSwipe ? settings.n : 1;
params.epsilon_cutoff /= 1000;
From 6a688cc3835404907034ec935b3fce4524ec2f4c Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Fri, 29 Mar 2024 18:07:45 +0200
Subject: [PATCH 122/255] Add fallback if tag_map is uninitialized
---
public/scripts/tags.js | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/public/scripts/tags.js b/public/scripts/tags.js
index 816087d73..a20f0ff40 100644
--- a/public/scripts/tags.js
+++ b/public/scripts/tags.js
@@ -339,6 +339,12 @@ export function getTagKeyForEntity(entityOrKey) {
x = character.avatar;
}
+ // Uninitialized character tag map
+ if (character && !(x in tag_map)) {
+ tag_map[x] = [];
+ return x;
+ }
+
// We should hopefully have a key now. Let's check
if (x in tag_map) {
return x;
@@ -349,7 +355,7 @@ export function getTagKeyForEntity(entityOrKey) {
}
function addTagToMap(tagId, characterId = null) {
- const key = getTagKey() ?? getTagKeyForEntity(characterId);
+ const key = characterId !== null && characterId !== undefined ? getTagKeyForEntity(characterId) : getTagKey();
if (!key) {
return;
@@ -365,7 +371,7 @@ function addTagToMap(tagId, characterId = null) {
}
function removeTagFromMap(tagId, characterId = null) {
- const key = getTagKey() ?? getTagKeyForEntity(characterId);
+ const key = characterId !== null && characterId !== undefined ? getTagKeyForEntity(characterId) : getTagKey();
if (!key) {
return;
From 06d1369f582e3643519a62a485d314461332d856 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Fri, 29 Mar 2024 19:39:59 +0200
Subject: [PATCH 123/255] Make default instruct/context restorable
---
public/index.html | 33 ++++++++++++++++++++------------
src/endpoints/content-manager.js | 2 +-
2 files changed, 22 insertions(+), 13 deletions(-)
diff --git a/public/index.html b/public/index.html
index e96da2b01..399003f39 100644
--- a/public/index.html
+++ b/public/index.html
@@ -2715,8 +2715,14 @@
From bcfa097c224df1ea7c8b3235d7e1bfbb12ffbfd5 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Fri, 29 Mar 2024 19:52:19 +0200
Subject: [PATCH 126/255] readme to .gitkeep
---
public/group chats/{README.md => .gitkeep} | 0
public/groups/{README.md => .gitkeep} | 0
2 files changed, 0 insertions(+), 0 deletions(-)
rename public/group chats/{README.md => .gitkeep} (100%)
rename public/groups/{README.md => .gitkeep} (100%)
diff --git a/public/group chats/README.md b/public/group chats/.gitkeep
similarity index 100%
rename from public/group chats/README.md
rename to public/group chats/.gitkeep
diff --git a/public/groups/README.md b/public/groups/.gitkeep
similarity index 100%
rename from public/groups/README.md
rename to public/groups/.gitkeep
From 19fd0f18d877130638595d1adab84c7a9619fc64 Mon Sep 17 00:00:00 2001
From: Hirose <86906598+HiroseKoichi@users.noreply.github.com>
Date: Fri, 29 Mar 2024 20:33:46 -0500
Subject: [PATCH 127/255] Update Alpaca.json
---
default/content/presets/instruct/Alpaca.json | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/default/content/presets/instruct/Alpaca.json b/default/content/presets/instruct/Alpaca.json
index f3b3b4cc8..0edd429e4 100644
--- a/default/content/presets/instruct/Alpaca.json
+++ b/default/content/presets/instruct/Alpaca.json
@@ -11,13 +11,13 @@
"names_force_groups": true,
"activation_regex": "",
"system_sequence_prefix": "",
- "system_sequence_suffix": "",
+ "system_sequence_suffix": "### Input:",
"first_output_sequence": "",
"skip_examples": false,
- "output_suffix": "",
- "input_suffix": "",
- "system_suffix": "",
+ "output_suffix": "\n\n",
+ "input_suffix": "\n\n",
+ "system_suffix": "\n\n",
"user_alignment_message": "",
"system_same_as_user": true,
"name": "Alpaca"
-}
\ No newline at end of file
+}
From d9730ba6ec11eeb837ee866a13a6e0ab4b5a6c54 Mon Sep 17 00:00:00 2001
From: Hirose <86906598+HiroseKoichi@users.noreply.github.com>
Date: Fri, 29 Mar 2024 20:35:07 -0500
Subject: [PATCH 128/255] Create Alpaca.json
---
default/content/presets/context/Alpaca.json | 12 ++++++++++++
1 file changed, 12 insertions(+)
create mode 100644 default/content/presets/context/Alpaca.json
diff --git a/default/content/presets/context/Alpaca.json b/default/content/presets/context/Alpaca.json
new file mode 100644
index 000000000..6e9418549
--- /dev/null
+++ b/default/content/presets/context/Alpaca.json
@@ -0,0 +1,12 @@
+{
+ "story_string": "{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}\n\n",
+ "example_separator": "",
+ "chat_start": "",
+ "use_stop_strings": false,
+ "allow_jailbreak": false,
+ "always_force_name2": true,
+ "trim_sentences": false,
+ "include_newline": false,
+ "single_line": false,
+ "name": "Alpaca"
+}
From 385b5324283879f1dfdaa5ea300be26359a8c885 Mon Sep 17 00:00:00 2001
From: Hirose <86906598+HiroseKoichi@users.noreply.github.com>
Date: Fri, 29 Mar 2024 20:36:58 -0500
Subject: [PATCH 129/255] Update Alpaca-Roleplay.json
---
.../presets/instruct/Alpaca-Roleplay.json | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/default/content/presets/instruct/Alpaca-Roleplay.json b/default/content/presets/instruct/Alpaca-Roleplay.json
index 37bf6cae7..5a5054340 100644
--- a/default/content/presets/instruct/Alpaca-Roleplay.json
+++ b/default/content/presets/instruct/Alpaca-Roleplay.json
@@ -1,9 +1,9 @@
{
"system_prompt": "Develop the plot slowly, always stay in character. Describe all actions in full, elaborate, explicit, graphic, and vivid detail. Mention all relevant sensory perceptions.",
- "input_sequence": "\n### Instruction:",
- "output_sequence": "\n### Response:",
- "last_output_sequence": "\n### Response (2 paragraphs, engaging, natural, authentic, descriptive, creative):",
- "system_sequence": "",
+ "input_sequence": "### Instruction:",
+ "output_sequence": "### Response:",
+ "last_output_sequence": "### Response (2 paragraphs, engaging, natural, authentic, descriptive, creative):",
+ "system_sequence": "### Input:",
"stop_sequence": "",
"wrap": true,
"macro": true,
@@ -14,10 +14,10 @@
"system_sequence_suffix": "",
"first_output_sequence": "",
"skip_examples": false,
- "output_suffix": "",
- "input_suffix": "",
- "system_suffix": "",
+ "output_suffix": "\n\n",
+ "input_suffix": "\n\n",
+ "system_suffix": "\n\n",
"user_alignment_message": "",
- "system_same_as_user": true,
+ "system_same_as_user": false,
"name": "Alpaca-Roleplay"
-}
\ No newline at end of file
+}
From 86cb0a3551e4087e5c76b81b46219a0b82edc060 Mon Sep 17 00:00:00 2001
From: Hirose <86906598+HiroseKoichi@users.noreply.github.com>
Date: Fri, 29 Mar 2024 20:38:15 -0500
Subject: [PATCH 130/255] Update Alpaca-Roleplay.json
---
default/content/presets/context/Alpaca-Roleplay.json | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/default/content/presets/context/Alpaca-Roleplay.json b/default/content/presets/context/Alpaca-Roleplay.json
index d564a1dd7..e7a45c2d8 100644
--- a/default/content/presets/context/Alpaca-Roleplay.json
+++ b/default/content/presets/context/Alpaca-Roleplay.json
@@ -1,5 +1,5 @@
{
- "story_string": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}.\n\n{{#if system}}{{system}}\n\n{{/if}}### Input:\n{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}",
+ "story_string": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}.\n\n{{#if system}}{{system}}\n\n{{/if}}### Input:\n{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}\n\n",
"example_separator": "### New Roleplay:",
"chat_start": "### New Roleplay:",
"use_stop_strings": false,
@@ -9,4 +9,4 @@
"include_newline": false,
"single_line": false,
"name": "Alpaca-Roleplay"
-}
\ No newline at end of file
+}
From 3174ea1d539c6fc1602443bbb51d7d75f348649d Mon Sep 17 00:00:00 2001
From: Hirose <86906598+HiroseKoichi@users.noreply.github.com>
Date: Fri, 29 Mar 2024 20:41:03 -0500
Subject: [PATCH 131/255] Update Synthia.json
---
default/content/presets/instruct/Synthia.json | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/default/content/presets/instruct/Synthia.json b/default/content/presets/instruct/Synthia.json
index 3315c9046..d8c0534e0 100644
--- a/default/content/presets/instruct/Synthia.json
+++ b/default/content/presets/instruct/Synthia.json
@@ -1,9 +1,9 @@
{
"system_prompt": "Elaborate on the topic using a Tree of Thoughts and backtrack when necessary to construct a clear, cohesive Chain of Thought reasoning. Always answer without hesitation.",
- "input_sequence": "\nUSER: ",
- "output_sequence": "\nASSISTANT: ",
+ "input_sequence": "USER: ",
+ "output_sequence": "ASSISTANT: ",
"last_output_sequence": "",
- "system_sequence": "",
+ "system_sequence": "SYSTEM: ",
"stop_sequence": "",
"wrap": false,
"macro": true,
@@ -14,10 +14,10 @@
"system_sequence_suffix": "",
"first_output_sequence": "",
"skip_examples": false,
- "output_suffix": "",
- "input_suffix": "",
- "system_suffix": "",
+ "output_suffix": "\n",
+ "input_suffix": "\n",
+ "system_suffix": "\n",
"user_alignment_message": "Let's get started. Please respond based on the information and instructions provided above.",
"system_same_as_user": true,
"name": "Synthia"
-}
\ No newline at end of file
+}
From 8892e320e5a91cf62ae85ac696c473945301bca2 Mon Sep 17 00:00:00 2001
From: Hirose <86906598+HiroseKoichi@users.noreply.github.com>
Date: Fri, 29 Mar 2024 20:42:36 -0500
Subject: [PATCH 132/255] Create Synthia.json
---
default/content/presets/context/Synthia.json | 12 ++++++++++++
1 file changed, 12 insertions(+)
create mode 100644 default/content/presets/context/Synthia.json
diff --git a/default/content/presets/context/Synthia.json b/default/content/presets/context/Synthia.json
new file mode 100644
index 000000000..8bffe47d3
--- /dev/null
+++ b/default/content/presets/context/Synthia.json
@@ -0,0 +1,12 @@
+{
+ "story_string": "{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}",
+ "example_separator": "",
+ "chat_start": "",
+ "use_stop_strings": false,
+ "allow_jailbreak": false,
+ "always_force_name2": true,
+ "trim_sentences": false,
+ "include_newline": false,
+ "single_line": false,
+ "name": "Synthia"
+}
From b49a2e6df2038d59d29490f8019d4b27258f2c7a Mon Sep 17 00:00:00 2001
From: Hirose <86906598+HiroseKoichi@users.noreply.github.com>
Date: Fri, 29 Mar 2024 20:46:59 -0500
Subject: [PATCH 133/255] Update Mistral.json
---
default/content/presets/instruct/Mistral.json | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/default/content/presets/instruct/Mistral.json b/default/content/presets/instruct/Mistral.json
index 2dbf47fdc..4f35139fa 100644
--- a/default/content/presets/instruct/Mistral.json
+++ b/default/content/presets/instruct/Mistral.json
@@ -1,7 +1,7 @@
{
"system_prompt": "Write {{char}}'s next reply in this fictional roleplay with {{user}}.",
"input_sequence": "[INST] ",
- "output_sequence": "\n",
+ "output_sequence": "",
"last_output_sequence": "",
"system_sequence": "",
"stop_sequence": "",
@@ -15,9 +15,9 @@
"first_output_sequence": "",
"skip_examples": false,
"output_suffix": "\n",
- "input_suffix": " [/INST]",
+ "input_suffix": " [/INST]\n",
"system_suffix": "",
"user_alignment_message": "Let's get started. Please respond based on the information and instructions provided above.",
"system_same_as_user": true,
"name": "Mistral"
-}
\ No newline at end of file
+}
From 7a3a2a7874bbfca32cceb6e50ef6241820533d88 Mon Sep 17 00:00:00 2001
From: Hirose <86906598+HiroseKoichi@users.noreply.github.com>
Date: Fri, 29 Mar 2024 20:48:07 -0500
Subject: [PATCH 134/255] Update Mistral.json
---
default/content/presets/context/Mistral.json | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/default/content/presets/context/Mistral.json b/default/content/presets/context/Mistral.json
index d8c437e0e..ebc691a5e 100644
--- a/default/content/presets/context/Mistral.json
+++ b/default/content/presets/context/Mistral.json
@@ -1,5 +1,5 @@
{
- "story_string": "[INST] {{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}[/INST]",
+ "story_string": "[INST] {{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}} [/INST]",
"example_separator": "Examples:",
"chat_start": "",
"use_stop_strings": false,
@@ -9,4 +9,4 @@
"include_newline": false,
"single_line": false,
"name": "Mistral"
-}
\ No newline at end of file
+}
From 4b466a9871873c63211acafe551cdcfeabe0fd15 Mon Sep 17 00:00:00 2001
From: Hirose <86906598+HiroseKoichi@users.noreply.github.com>
Date: Fri, 29 Mar 2024 20:50:24 -0500
Subject: [PATCH 135/255] Update Llama 2 Chat.json
---
default/content/presets/instruct/Llama 2 Chat.json | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/default/content/presets/instruct/Llama 2 Chat.json b/default/content/presets/instruct/Llama 2 Chat.json
index c129e71d1..aeb4e13fd 100644
--- a/default/content/presets/instruct/Llama 2 Chat.json
+++ b/default/content/presets/instruct/Llama 2 Chat.json
@@ -1,7 +1,7 @@
{
"system_prompt": "Write {{char}}'s next reply in this fictional roleplay with {{user}}.",
"input_sequence": "[INST] ",
- "output_sequence": " ",
+ "output_sequence": "",
"last_output_sequence": "",
"system_sequence": "",
"stop_sequence": "",
@@ -10,14 +10,14 @@
"names": false,
"names_force_groups": true,
"activation_regex": "",
- "system_sequence_prefix": "[INST] <>\n",
- "system_sequence_suffix": "\n<>\n",
+ "system_sequence_prefix": "",
+ "system_sequence_suffix": "",
"first_output_sequence": "",
"skip_examples": false,
- "output_suffix": " ",
- "input_suffix": " [/INST]",
+ "output_suffix": "\n",
+ "input_suffix": " [/INST]\n",
"system_suffix": "",
"user_alignment_message": "Let's get started. Please respond based on the information and instructions provided above.",
"system_same_as_user": true,
"name": "Llama 2 Chat"
-}
\ No newline at end of file
+}
From b9fa614093b800aa8b555eaa6f2273ca1a3aeb4a Mon Sep 17 00:00:00 2001
From: Hirose <86906598+HiroseKoichi@users.noreply.github.com>
Date: Fri, 29 Mar 2024 20:51:16 -0500
Subject: [PATCH 136/255] Create Llama 2 Chat.json
---
default/content/presets/context/Llama 2 Chat.json | 12 ++++++++++++
1 file changed, 12 insertions(+)
create mode 100644 default/content/presets/context/Llama 2 Chat.json
diff --git a/default/content/presets/context/Llama 2 Chat.json b/default/content/presets/context/Llama 2 Chat.json
new file mode 100644
index 000000000..a5e948925
--- /dev/null
+++ b/default/content/presets/context/Llama 2 Chat.json
@@ -0,0 +1,12 @@
+{
+ "story_string": "[INST] <>\n{{#if system}}{{system}}\n<>\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}} [/INST]",
+ "example_separator": "",
+ "chat_start": "",
+ "use_stop_strings": false,
+ "allow_jailbreak": false,
+ "always_force_name2": true,
+ "trim_sentences": false,
+ "include_newline": false,
+ "single_line": false,
+ "name": "Llama 2 Chat"
+}
From 79548d93a98b59dfc9050a02dd7fae6b03716756 Mon Sep 17 00:00:00 2001
From: Hirose <86906598+HiroseKoichi@users.noreply.github.com>
Date: Fri, 29 Mar 2024 21:00:36 -0500
Subject: [PATCH 137/255] Update Alpaca.json
---
default/content/presets/instruct/Alpaca.json | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/default/content/presets/instruct/Alpaca.json b/default/content/presets/instruct/Alpaca.json
index 0edd429e4..4ccdb7175 100644
--- a/default/content/presets/instruct/Alpaca.json
+++ b/default/content/presets/instruct/Alpaca.json
@@ -3,7 +3,7 @@
"input_sequence": "### Instruction:",
"output_sequence": "### Response:",
"last_output_sequence": "",
- "system_sequence": "",
+ "system_sequence": "### Input:",
"stop_sequence": "",
"wrap": true,
"macro": true,
@@ -11,7 +11,7 @@
"names_force_groups": true,
"activation_regex": "",
"system_sequence_prefix": "",
- "system_sequence_suffix": "### Input:",
+ "system_sequence_suffix": "",
"first_output_sequence": "",
"skip_examples": false,
"output_suffix": "\n\n",
From 3c627996e0c47690054b09ada751351088c63165 Mon Sep 17 00:00:00 2001
From: Hirose <86906598+HiroseKoichi@users.noreply.github.com>
Date: Fri, 29 Mar 2024 21:03:23 -0500
Subject: [PATCH 138/255] Update Alpaca.json
---
default/content/presets/instruct/Alpaca.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/default/content/presets/instruct/Alpaca.json b/default/content/presets/instruct/Alpaca.json
index 4ccdb7175..96fd2cc83 100644
--- a/default/content/presets/instruct/Alpaca.json
+++ b/default/content/presets/instruct/Alpaca.json
@@ -18,6 +18,6 @@
"input_suffix": "\n\n",
"system_suffix": "\n\n",
"user_alignment_message": "",
- "system_same_as_user": true,
+ "system_same_as_user": false,
"name": "Alpaca"
}
From 80f4bd4d9efefd65f39454ea6598ee662b1b0113 Mon Sep 17 00:00:00 2001
From: Wolfsblvt
Date: Sat, 30 Mar 2024 03:06:40 +0100
Subject: [PATCH 139/255] Global refactor of printCharacter and filter print
- (!) Refactor character list and filter redrawing to one global debounce
- Refactor all places where character list and filters where redrawn to the correct usage (hope I didn't miss any)
- Automatically redraw character list on each tag bulk edit
- Fix tags not being sorted in bulk edit mutual tags list
- Refactor bulk tag edit class to actually be an instance object
- Remember scroll position on character list redraw - unless it's a full refresh
---
public/script.js | 41 +++++++++----
public/scripts/BulkEditOverlay.js | 99 ++++++++++++++++++++-----------
public/scripts/bulk-edit.js | 6 +-
public/scripts/power-user.js | 12 ++--
public/scripts/tags.js | 42 ++++++-------
5 files changed, 122 insertions(+), 78 deletions(-)
diff --git a/public/script.js b/public/script.js
index e9bd89da0..9df870852 100644
--- a/public/script.js
+++ b/public/script.js
@@ -282,6 +282,7 @@ export {
mesForShowdownParse,
characterGroupOverlay,
printCharacters,
+ printCharactersDebounced,
isOdd,
countOccurrences,
};
@@ -498,6 +499,14 @@ const durationSaveEdit = 1000;
const saveSettingsDebounced = debounce(() => saveSettings(), durationSaveEdit);
export const saveCharacterDebounced = debounce(() => $('#create_button').trigger('click'), durationSaveEdit);
+/**
+ * Prints the character list in a debounced fashion without blocking, with a delay of 100 milliseconds.
+ * Use this function instead of a direct `printCharacters()` whenever the reprinting of the character list is not the primary focus.
+ *
+ * The printing will also always reprint all filter options of the global list, to keep them up to date.
+ */
+const printCharactersDebounced = debounce(() => { printCharacters(false); }, 100);
+
/**
* @enum {string} System message types
*/
@@ -836,7 +845,7 @@ export let active_character = '';
/** The tag of the active group. (Coincidentally also the id) */
export let active_group = '';
-export const entitiesFilter = new FilterHelper(debounce(printCharacters, 100));
+export const entitiesFilter = new FilterHelper(printCharactersDebounced);
export const personasFilter = new FilterHelper(debounce(getUserAvatars, 100));
export function getRequestHeaders() {
@@ -1275,19 +1284,31 @@ function getCharacterBlock(item, id) {
return template;
}
+/**
+ * Prints the global character list, optionally doing a full refresh of the list
+ * Use this function whenever the reprinting of the character list is the primary focus, otherwise using `printCharactersDebounced` is preferred for a cleaner, non-blocking experience.
+ *
+ * The printing will also always reprint all filter options of the global list, to keep them up to date.
+ *
+ * @param {boolean} fullRefresh - If true, the list is fully refreshed and the navigation is being reset
+ */
async function printCharacters(fullRefresh = false) {
- if (fullRefresh) {
- saveCharactersPage = 0;
- printTagFilters(tag_filter_types.character);
- printTagFilters(tag_filter_types.group_member);
-
- await delay(1);
- }
-
const storageKey = 'Characters_PerPage';
const listId = '#rm_print_characters_block';
const entities = getEntitiesList({ doFilter: true });
+ let currentScrollTop = $(listId).scrollTop();
+
+ if (fullRefresh) {
+ saveCharactersPage = 0;
+ currentScrollTop = 0;
+ await delay(1);
+ }
+
+ // We are actually always reprinting filters, as it "doesn't hurt", and this way they are always up to date
+ printTagFilters(tag_filter_types.character);
+ printTagFilters(tag_filter_types.group_member);
+
$('#rm_print_characters_pagination').pagination({
dataSource: entities,
pageSize: Number(localStorage.getItem(storageKey)) || per_page_default,
@@ -1340,7 +1361,7 @@ async function printCharacters(fullRefresh = false) {
saveCharactersPage = e;
},
afterRender: function () {
- $(listId).scrollTop(0);
+ $(listId).scrollTop(currentScrollTop);
},
});
diff --git a/public/scripts/BulkEditOverlay.js b/public/scripts/BulkEditOverlay.js
index 19955e7ac..f3df8156d 100644
--- a/public/scripts/BulkEditOverlay.js
+++ b/public/scripts/BulkEditOverlay.js
@@ -1,6 +1,7 @@
'use strict';
import {
+ characterGroupOverlay,
callPopup,
characters,
deleteCharacter,
@@ -9,9 +10,9 @@ import {
getCharacters,
getPastCharacterChats,
getRequestHeaders,
- printCharacters,
buildAvatarList,
characterToEntity,
+ printCharactersDebounced,
} from '../script.js';
import { favsToHotswap } from './RossAscends-mods.js';
@@ -31,7 +32,7 @@ class CharacterContextMenu {
* @param {Array} selectedCharacters
*/
static tag = (selectedCharacters) => {
- BulkTagPopupHandler.show(selectedCharacters);
+ characterGroupOverlay.bulkTagPopupHandler.show(selectedCharacters);
};
/**
@@ -186,18 +187,36 @@ class CharacterContextMenu {
* Represents a tag control not bound to a single character
*/
class BulkTagPopupHandler {
+ /**
+ * The characters for this popup
+ * @type {number[]}
+ */
+ characterIds;
+
+ /**
+ * A storage of the current mutual tags, as calculated by getMutualTags()
+ * @type {object[]}
+ */
+ currentMutualTags;
+
+ /**
+ * Sets up the bulk popup menu handler for the given overlay.
+ *
+ * Characters can be passed in with the show() call.
+ */
+ constructor() { }
+
/**
* Gets the HTML as a string that is going to be the popup for the bulk tag edit
*
- * @param {Array} characterIds - The characters that are shown inside the popup
* @returns String containing the html for the popup
*/
- static #getHtml = (characterIds) => {
- const characterData = JSON.stringify({ characterIds: characterIds });
+ #getHtml = () => {
+ const characterData = JSON.stringify({ characterIds: this.characterIds });
return `
-
Modify tags of ${characterIds.length} characters
+
Modify tags of ${this.characterIds.length} characters
Add or remove the mutual tags of all selected characters.
@@ -227,93 +246,91 @@ class BulkTagPopupHandler {
/**
* Append and show the tag control
*
- * @param {Array} characterIds - The characters assigned to this control
+ * @param {number[]} characterIds - The characters that are shown inside the popup
*/
- static show(characterIds) {
- if (characterIds.length == 0) {
+ show(characterIds) {
+ // shallow copy character ids persistently into this tooltip
+ this.characterIds = characterIds.slice();
+
+ if (this.characterIds.length == 0) {
console.log('No characters selected for bulk edit tags.');
return;
}
- document.body.insertAdjacentHTML('beforeend', this.#getHtml(characterIds));
+ document.body.insertAdjacentHTML('beforeend', this.#getHtml());
- const entities = characterIds.map(id => characterToEntity(characters[id], id)).filter(entity => entity.item !== undefined);
+ const entities = this.characterIds.map(id => characterToEntity(characters[id], id)).filter(entity => entity.item !== undefined);
buildAvatarList($('#bulk_tags_avatars_block'), entities);
// Print the tag list with all mutuable tags, marking them as removable. That is the initial fill
- printTagList($('#bulkTagList'), { tags: () => this.getMutualTags(characterIds), tagOptions: { removable: true } });
+ printTagList($('#bulkTagList'), { tags: () => this.getMutualTags(), tagOptions: { removable: true } });
// Tag input with resolvable list for the mutual tags to get redrawn, so that newly added tags get sorted correctly
- createTagInput('#bulkTagInput', '#bulkTagList', { tags: () => this.getMutualTags(characterIds), tagOptions: { removable: true }});
+ createTagInput('#bulkTagInput', '#bulkTagList', { tags: () => this.getMutualTags(), tagOptions: { removable: true }});
- document.querySelector('#bulk_tag_popup_reset').addEventListener('click', this.resetTags.bind(this, characterIds));
- document.querySelector('#bulk_tag_popup_remove_mutual').addEventListener('click', this.removeMutual.bind(this, characterIds));
+ document.querySelector('#bulk_tag_popup_reset').addEventListener('click', this.resetTags.bind(this));
+ document.querySelector('#bulk_tag_popup_remove_mutual').addEventListener('click', this.removeMutual.bind(this));
document.querySelector('#bulk_tag_popup_cancel').addEventListener('click', this.hide.bind(this));
}
/**
* Builds a list of all tags that the provided characters have in common.
*
- * @param {Array} characterIds - The characters to find mutual tags for
* @returns {Array} A list of mutual tags
*/
- static getMutualTags(characterIds) {
- if (characterIds.length == 0) {
+ getMutualTags() {
+ if (this.characterIds.length == 0) {
return [];
}
- if (characterIds.length === 1) {
+ if (this.characterIds.length === 1) {
// Just use tags of the single character
- return getTagsList(getTagKeyForEntity(characterIds[0]));
+ return getTagsList(getTagKeyForEntity(this.characterIds[0]));
}
// Find mutual tags for multiple characters
- const allTags = characterIds.map(cid => getTagsList(getTagKeyForEntity(cid)));
+ const allTags = this.characterIds.map(cid => getTagsList(getTagKeyForEntity(cid)));
const mutualTags = allTags.reduce((mutual, characterTags) =>
mutual.filter(tag => characterTags.some(cTag => cTag.id === tag.id))
);
- this.mutualTags = mutualTags.sort(compareTagsForSort);
- return this.mutualTags;
+ this.currentMutualTags = mutualTags.sort(compareTagsForSort);
+ return this.currentMutualTags;
}
/**
* Hide and remove the tag control
*/
- static hide() {
+ hide() {
let popupElement = document.querySelector('#bulk_tag_shadow_popup');
if (popupElement) {
document.body.removeChild(popupElement);
}
- printCharacters(true);
+ // No need to redraw here, all tags actions were redrawn when they happened
}
/**
* Empty the tag map for the given characters
- *
- * @param {Array} characterIds
*/
- static resetTags(characterIds) {
- for (const characterId of characterIds) {
+ resetTags() {
+ for (const characterId of this.characterIds) {
const key = getTagKeyForEntity(characterId);
if (key) tag_map[key] = [];
}
$('#bulkTagList').empty();
- printCharacters(true);
+ printCharactersDebounced();
}
/**
* Remove the mutual tags for all given characters
- *
- * @param {Array} characterIds
*/
- static removeMutual(characterIds) {
- const mutualTags = this.getMutualTags(characterIds);
+ removeMutual() {
+ const mutualTags = this.getMutualTags();
- for (const characterId of characterIds) {
+ for (const characterId of this.characterIds) {
for(const tag of mutualTags) {
removeTagFromMap(tag.id, characterId);
}
@@ -321,7 +338,7 @@ class BulkTagPopupHandler {
$('#bulkTagList').empty();
- printCharacters(true);
+ printCharactersDebounced();
}
}
@@ -364,6 +381,7 @@ class BulkEditOverlay {
#longPress = false;
#stateChangeCallbacks = [];
#selectedCharacters = [];
+ #bulkTagPopupHandler = new BulkTagPopupHandler();
/**
* @typedef {object} LastSelected - An object noting the last selected character and its state.
@@ -429,6 +447,15 @@ class BulkEditOverlay {
return this.#selectedCharacters;
}
+ /**
+ * The instance of the bulk tag popup handler that handles tagging of all selected characters
+ *
+ * @returns {BulkTagPopupHandler}
+ */
+ get bulkTagPopupHandler() {
+ return this.#bulkTagPopupHandler;
+ }
+
constructor() {
if (bulkEditOverlayInstance instanceof BulkEditOverlay)
return bulkEditOverlayInstance;
diff --git a/public/scripts/bulk-edit.js b/public/scripts/bulk-edit.js
index c266e148b..f09e290df 100644
--- a/public/scripts/bulk-edit.js
+++ b/public/scripts/bulk-edit.js
@@ -30,7 +30,7 @@ const toggleBulkEditMode = (isBulkEdit) => {
}
};
-(new BulkEditOverlay()).addStateChangeCallback((state) => {
+characterGroupOverlay.addStateChangeCallback((state) => {
if (state === BulkEditOverlayState.select) enableBulkEdit();
if (state === BulkEditOverlayState.browse) disableBulkEdit();
});
@@ -52,7 +52,7 @@ function onSelectAllButtonClick() {
let atLeastOneSelected = false;
for (const character of characters) {
const checked = $(character).find('.bulk_select_checkbox:checked').length > 0;
- if (!checked) {
+ if (!checked && character instanceof HTMLElement) {
characterGroupOverlay.toggleSingleCharacter(character);
atLeastOneSelected = true;
}
@@ -62,7 +62,7 @@ function onSelectAllButtonClick() {
// If none was selected, trigger click on all to deselect all of them
for(const character of characters) {
const checked = $(character).find('.bulk_select_checkbox:checked') ?? false;
- if (checked) {
+ if (checked && character instanceof HTMLElement) {
characterGroupOverlay.toggleSingleCharacter(character);
}
}
diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js
index d0cc3fc06..c7a9d3502 100644
--- a/public/scripts/power-user.js
+++ b/public/scripts/power-user.js
@@ -10,7 +10,7 @@ import {
eventSource,
event_types,
getCurrentChatId,
- printCharacters,
+ printCharactersDebounced,
setCharacterId,
setEditedMessageId,
renderTemplate,
@@ -1288,7 +1288,7 @@ async function applyTheme(name) {
key: 'bogus_folders',
action: async () => {
$('#bogus_folders').prop('checked', power_user.bogus_folders);
- await printCharacters(true);
+ printCharactersDebounced();
},
},
{
@@ -3045,7 +3045,7 @@ $(document).ready(() => {
$('#show_card_avatar_urls').on('input', function () {
power_user.show_card_avatar_urls = !!$(this).prop('checked');
- printCharacters();
+ printCharactersDebounced();
saveSettingsDebounced();
});
@@ -3068,7 +3068,7 @@ $(document).ready(() => {
power_user.sort_field = $(this).find(':selected').data('field');
power_user.sort_order = $(this).find(':selected').data('order');
power_user.sort_rule = $(this).find(':selected').data('rule');
- printCharacters();
+ printCharactersDebounced();
saveSettingsDebounced();
});
@@ -3365,15 +3365,15 @@ $(document).ready(() => {
$('#bogus_folders').on('input', function () {
const value = !!$(this).prop('checked');
power_user.bogus_folders = value;
+ printCharactersDebounced();
saveSettingsDebounced();
- printCharacters(true);
});
$('#aux_field').on('change', function () {
const value = $(this).find(':selected').val();
power_user.aux_field = String(value);
+ printCharactersDebounced();
saveSettingsDebounced();
- printCharacters(false);
});
$('#restore_user_input').on('input', function () {
diff --git a/public/scripts/tags.js b/public/scripts/tags.js
index a20f0ff40..8c38ec864 100644
--- a/public/scripts/tags.js
+++ b/public/scripts/tags.js
@@ -7,6 +7,7 @@ import {
getCharacters,
entitiesFilter,
printCharacters,
+ printCharactersDebounced,
buildAvatarList,
eventSource,
event_types,
@@ -48,12 +49,6 @@ function getFilterHelper(listSelector) {
return $(listSelector).is(GROUP_FILTER_SELECTOR) ? groupCandidatesFilter : entitiesFilter;
}
-const redrawCharsAndFiltersDebounced = debounce(() => {
- printCharacters(false);
- printTagFilters(tag_filter_types.character);
- printTagFilters(tag_filter_types.group_member);
-}, 100);
-
export const tag_filter_types = {
character: 0,
group_member: 1,
@@ -406,10 +401,11 @@ function findTag(request, resolve, listSelector) {
* @param {*} event - The event that fired on autocomplete select
* @param {*} ui - An Object with label and value properties for the selected option
* @param {*} listSelector - The selector of the list to print/add to
- * @param {PrintTagListOptions} [tagListOptions] - Optional parameters for printing the tag list. Can be set to be consistent with the expected behavior of tags in the list that was defined before.
+ * @param {object} param1 - Optional parameters for this method call
+ * @param {PrintTagListOptions} [param1.tagListOptions] - Optional parameters for printing the tag list. Can be set to be consistent with the expected behavior of tags in the list that was defined before.
* @returns {boolean} false, to keep the input clear
*/
-function selectTag(event, ui, listSelector, tagListOptions = {}) {
+function selectTag(event, ui, listSelector, { tagListOptions = {} } = {}) {
let tagName = ui.item.value;
let tag = tags.find(t => t.name === tagName);
@@ -431,6 +427,7 @@ function selectTag(event, ui, listSelector, tagListOptions = {}) {
addTagToMap(tag.id);
}
+ printCharactersDebounced();
saveSettingsDebounced();
// We should manually add the selected tag to the print tag function, so we cover places where the tag list did not automatically include it
@@ -443,9 +440,6 @@ function selectTag(event, ui, listSelector, tagListOptions = {}) {
printTagList($(inlineSelector), tagListOptions);
}
- printTagFilters(tag_filter_types.character);
- printTagFilters(tag_filter_types.group_member);
-
// need to return false to keep the input clear
return false;
}
@@ -493,10 +487,11 @@ async function importTags(imported_char) {
console.debug('added tag to map', tag, imported_char.name);
}
}
+
saveSettingsDebounced();
+
+ // Await the character list, which will automatically reprint it and all tag filters
await getCharacters();
- printTagFilters(tag_filter_types.character);
- printTagFilters(tag_filter_types.group_member);
// need to return false to keep the input clear
return false;
@@ -767,8 +762,7 @@ function onTagRemoveClick(event) {
$(`${getInlineListSelector()} .tag[id="${tagId}"]`).remove();
- printTagFilters(tag_filter_types.character);
- printTagFilters(tag_filter_types.group_member);
+ printCharactersDebounced();
saveSettingsDebounced();
@@ -818,7 +812,7 @@ export function createTagInput(inputSelector, listSelector, tagListOptions = {})
// @ts-ignore
.autocomplete({
source: (i, o) => findTag(i, o, listSelector),
- select: (e, u) => selectTag(e, u, listSelector, tagListOptions),
+ select: (e, u) => selectTag(e, u, listSelector, { tagListOptions: tagListOptions }),
minLength: 0,
})
.focus(onTagInputFocus); // <== show tag list on click
@@ -900,10 +894,9 @@ function makeTagListDraggable(tagContainer) {
}
});
- saveSettingsDebounced();
-
// If the order of tags in display has changed, we need to redraw some UI elements. Do it debounced so it doesn't block and you can drag multiple tags.
- redrawCharsAndFiltersDebounced();
+ printCharactersDebounced();
+ saveSettingsDebounced();
};
// @ts-ignore
@@ -1003,8 +996,9 @@ async function onTagRestoreFileSelect(e) {
}
$('#tag_view_restore_input').val('');
+ printCharactersDebounced();
saveSettingsDebounced();
- printCharacters(true);
+
onViewTagsListClick();
}
@@ -1029,7 +1023,8 @@ function onTagsBackupClick() {
function onTagCreateClick() {
const tag = createNewTag('New Tag');
appendViewTagToList($('#tag_view_list .tag_view_list_tags'), tag, []);
- printCharacters(false);
+
+ printCharactersDebounced();
saveSettingsDebounced();
}
@@ -1098,7 +1093,7 @@ function onTagAsFolderClick() {
updateDrawTagFolder(element, tag);
// If folder display has changed, we have to redraw the character list, otherwise this folders state would not change
- printCharacters(true);
+ printCharactersDebounced();
saveSettingsDebounced();
}
@@ -1133,7 +1128,8 @@ function onTagDeleteClick() {
tags.splice(index, 1);
$(`.tag[id="${id}"]`).remove();
$(`.tag_view_item[id="${id}"]`).remove();
- printCharacters(false);
+
+ printCharactersDebounced();
saveSettingsDebounced();
}
From f4eed15e4a8ddf0d42527ebb88e58d1c6258e2b9 Mon Sep 17 00:00:00 2001
From: Hirose <86906598+HiroseKoichi@users.noreply.github.com>
Date: Fri, 29 Mar 2024 21:13:33 -0500
Subject: [PATCH 140/255] Update ChatML.json
---
default/content/presets/instruct/ChatML.json | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/default/content/presets/instruct/ChatML.json b/default/content/presets/instruct/ChatML.json
index f6bf0aa44..348ae2458 100644
--- a/default/content/presets/instruct/ChatML.json
+++ b/default/content/presets/instruct/ChatML.json
@@ -1,11 +1,11 @@
{
"system_prompt": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}.",
- "input_sequence": "\n<|im_start|>user\n",
- "output_sequence": "\n<|im_start|>assistant\n",
+ "input_sequence": "<|im_start|>user",
+ "output_sequence": "<|im_start|>assistant",
"last_output_sequence": "",
- "system_sequence": "\n<|im_start|>system\n",
+ "system_sequence": "<|im_start|>system",
"stop_sequence": "<|im_end|>",
- "wrap": false,
+ "wrap": true,
"macro": true,
"names": true,
"names_force_groups": true,
@@ -14,9 +14,9 @@
"system_sequence_suffix": "",
"first_output_sequence": "",
"skip_examples": false,
- "output_suffix": "<|im_end|>",
- "input_suffix": "<|im_end|>",
- "system_suffix": "<|im_end|>",
+ "output_suffix": "<|im_end|>\n",
+ "input_suffix": "<|im_end|>\n",
+ "system_suffix": "<|im_end|>\n",
"user_alignment_message": "",
"system_same_as_user": false,
"name": "ChatML"
From e0bff492b8d7766305fc70d628c45f09e581a24c Mon Sep 17 00:00:00 2001
From: Hirose <86906598+HiroseKoichi@users.noreply.github.com>
Date: Fri, 29 Mar 2024 21:30:48 -0500
Subject: [PATCH 141/255] Update Synthia.json
---
default/content/presets/instruct/Synthia.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/default/content/presets/instruct/Synthia.json b/default/content/presets/instruct/Synthia.json
index d8c0534e0..24ec4849e 100644
--- a/default/content/presets/instruct/Synthia.json
+++ b/default/content/presets/instruct/Synthia.json
@@ -18,6 +18,6 @@
"input_suffix": "\n",
"system_suffix": "\n",
"user_alignment_message": "Let's get started. Please respond based on the information and instructions provided above.",
- "system_same_as_user": true,
+ "system_same_as_user": false,
"name": "Synthia"
}
From dddcac9af818870bf5b621d2876f733ebc7e45b7 Mon Sep 17 00:00:00 2001
From: kir-gadjello <111190790+kir-gadjello@users.noreply.github.com>
Date: Sat, 30 Mar 2024 01:12:29 -0300
Subject: [PATCH 142/255] implement drawthings local app api support for sd
extension
---
.../extensions/stable-diffusion/index.js | 144 ++++++++++++++++++
.../extensions/stable-diffusion/settings.html | 16 ++
src/endpoints/stable-diffusion.js | 73 +++++++++
3 files changed, 233 insertions(+)
diff --git a/public/scripts/extensions/stable-diffusion/index.js b/public/scripts/extensions/stable-diffusion/index.js
index bb12cb416..7fd51ddc5 100644
--- a/public/scripts/extensions/stable-diffusion/index.js
+++ b/public/scripts/extensions/stable-diffusion/index.js
@@ -47,6 +47,7 @@ const sources = {
openai: 'openai',
comfy: 'comfy',
togetherai: 'togetherai',
+ drawthings: 'drawthings',
};
const generationMode = {
@@ -217,6 +218,9 @@ const defaultSettings = {
vlad_url: 'http://localhost:7860',
vlad_auth: '',
+ drawthings_url: 'http://localhost:7860',
+ drawthings_auth: '',
+
hr_upscaler: 'Latent',
hr_scale: 2.0,
hr_scale_min: 1.0,
@@ -312,6 +316,8 @@ function getSdRequestBody() {
return { url: extension_settings.sd.vlad_url, auth: extension_settings.sd.vlad_auth };
case sources.auto:
return { url: extension_settings.sd.auto_url, auth: extension_settings.sd.auto_auth };
+ case sources.drawthings:
+ return { url: extension_settings.sd.drawthings_url };
default:
throw new Error('Invalid SD source.');
}
@@ -385,6 +391,8 @@ async function loadSettings() {
$('#sd_auto_auth').val(extension_settings.sd.auto_auth);
$('#sd_vlad_url').val(extension_settings.sd.vlad_url);
$('#sd_vlad_auth').val(extension_settings.sd.vlad_auth);
+ $('#sd_drawthings_url').val(extension_settings.sd.drawthings_url);
+ $('#sd_drawthings_auth').val(extension_settings.sd.drawthings_auth);
$('#sd_interactive_mode').prop('checked', extension_settings.sd.interactive_mode);
$('#sd_openai_style').val(extension_settings.sd.openai_style);
$('#sd_openai_quality').val(extension_settings.sd.openai_quality);
@@ -844,6 +852,16 @@ function onVladAuthInput() {
saveSettingsDebounced();
}
+function onDrawthingsUrlInput() {
+ extension_settings.sd.drawthings_url = $('#sd_drawthings_url').val();
+ saveSettingsDebounced();
+}
+
+function onDrawthingsAuthInput() {
+ extension_settings.sd.drawthings_auth = $('#sd_drawthings_auth').val();
+ saveSettingsDebounced();
+}
+
function onHrUpscalerChange() {
extension_settings.sd.hr_upscaler = $('#sd_hr_upscaler').find(':selected').val();
saveSettingsDebounced();
@@ -910,6 +928,29 @@ async function validateAutoUrl() {
}
}
+async function validateDrawthingsUrl() {
+ try {
+ if (!extension_settings.sd.drawthings_url) {
+ throw new Error('URL is not set.');
+ }
+
+ const result = await fetch('/api/sd/drawthings/ping', {
+ method: 'POST',
+ headers: getRequestHeaders(),
+ body: JSON.stringify(getSdRequestBody()),
+ });
+
+ if (!result.ok) {
+ throw new Error('SD Drawthings returned an error.');
+ }
+
+ await loadSettingOptions();
+ toastr.success('SD Drawthings API connected.');
+ } catch (error) {
+ toastr.error(`Could not validate SD Drawthings API: ${error.message}`);
+ }
+}
+
async function validateVladUrl() {
try {
if (!extension_settings.sd.vlad_url) {
@@ -997,6 +1038,27 @@ async function getAutoRemoteModel() {
}
}
+async function getDrawthingsRemoteModel() {
+ try {
+ const result = await fetch('/api/sd/drawthings/get-model', {
+ method: 'POST',
+ headers: getRequestHeaders(),
+ body: JSON.stringify(getSdRequestBody()),
+ });
+
+ if (!result.ok) {
+ throw new Error('SD DrawThings API returned an error.');
+ }
+
+ const data = await result.text();
+
+ return data;
+ } catch (error) {
+ console.error(error);
+ return null;
+ }
+}
+
async function onVaeChange() {
extension_settings.sd.vae = $('#sd_vae').find(':selected').val();
}
@@ -1087,6 +1149,9 @@ async function loadSamplers() {
case sources.auto:
samplers = await loadAutoSamplers();
break;
+ case sources.drawthings:
+ samplers = await loadDrawthingsSamplers();
+ break;
case sources.novel:
samplers = await loadNovelSamplers();
break;
@@ -1172,6 +1237,11 @@ async function loadAutoSamplers() {
}
}
+async function loadDrawthingsSamplers() {
+ // The app developer doesn't provide an API to get these yet
+ return ["UniPC", "DPM++ 2M Karras", "Euler a", "DPM++ SDE Karras", "PLMS", "DDIM", "LCM", "Euler A Substep", "DPM++ SDE Substep", "TCD"];
+}
+
async function loadVladSamplers() {
if (!extension_settings.sd.vlad_url) {
return [];
@@ -1248,6 +1318,9 @@ async function loadModels() {
case sources.auto:
models = await loadAutoModels();
break;
+ case sources.drawthings:
+ models = await loadDrawthingsModels();
+ break;
case sources.novel:
models = await loadNovelModels();
break;
@@ -1384,6 +1457,27 @@ async function loadAutoModels() {
}
}
+async function loadDrawthingsModels() {
+ if (!extension_settings.sd.drawthings_url) {
+ return [];
+ }
+
+ try {
+ const currentModel = await getDrawthingsRemoteModel();
+
+ if (currentModel) {
+ extension_settings.sd.model = currentModel;
+ }
+
+ const data = [{value: currentModel, text: currentModel}];
+
+ return data;
+ } catch (error) {
+ console.log("Error loading DrawThings API models:", error);
+ return [];
+ }
+}
+
async function loadOpenAiModels() {
return [
{ value: 'dall-e-3', text: 'DALL-E 3' },
@@ -1506,6 +1600,9 @@ async function loadSchedulers() {
case sources.vlad:
schedulers = ['N/A'];
break;
+ case sources.drawthings:
+ schedulers = ['N/A'];
+ break;
case sources.openai:
schedulers = ['N/A'];
break;
@@ -1568,6 +1665,9 @@ async function loadVaes() {
case sources.vlad:
vaes = ['N/A'];
break;
+ case sources.drawthings:
+ vaes = ['N/A'];
+ break;
case sources.openai:
vaes = ['N/A'];
break;
@@ -1975,6 +2075,9 @@ async function sendGenerationRequest(generationType, prompt, characterName = nul
case sources.vlad:
result = await generateAutoImage(prefixedPrompt, negativePrompt);
break;
+ case sources.drawthings:
+ result = await generateDrawthingsImage(prefixedPrompt, negativePrompt);
+ break;
case sources.auto:
result = await generateAutoImage(prefixedPrompt, negativePrompt);
break;
@@ -2157,6 +2260,42 @@ async function generateAutoImage(prompt, negativePrompt) {
}
}
+/**
+ * Generates an image in Drawthings API using the provided prompt and configuration settings.
+ *
+ * @param {string} prompt - The main instruction used to guide the image generation.
+ * @param {string} negativePrompt - The instruction used to restrict the image generation.
+ * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
+ */
+async function generateDrawthingsImage(prompt, negativePrompt) {
+ const result = await fetch('/api/sd/drawthings/generate', {
+ method: 'POST',
+ headers: getRequestHeaders(),
+ body: JSON.stringify({
+ ...getSdRequestBody(),
+ prompt: prompt,
+ negative_prompt: negativePrompt,
+ sampler_name: extension_settings.sd.sampler,
+ steps: extension_settings.sd.steps,
+ cfg_scale: extension_settings.sd.scale,
+ width: extension_settings.sd.width,
+ height: extension_settings.sd.height,
+ restore_faces: !!extension_settings.sd.restore_faces,
+ enable_hr: !!extension_settings.sd.enable_hr,
+ denoising_strength: extension_settings.sd.denoising_strength,
+ // TODO: advanced API parameters: hr, upscaler
+ }),
+ });
+
+ if (result.ok) {
+ const data = await result.json();
+ return { format: 'png', data: data.images[0] };
+ } else {
+ const text = await result.text();
+ throw new Error(text);
+ }
+}
+
/**
* Generates an image in NovelAI API using the provided prompt and configuration settings.
*
@@ -2573,6 +2712,8 @@ function isValidState() {
return true;
case sources.auto:
return !!extension_settings.sd.auto_url;
+ case sources.drawthings:
+ return !!extension_settings.sd.drawthings_url;
case sources.vlad:
return !!extension_settings.sd.vlad_url;
case sources.novel:
@@ -2715,6 +2856,9 @@ jQuery(async () => {
$('#sd_auto_validate').on('click', validateAutoUrl);
$('#sd_auto_url').on('input', onAutoUrlInput);
$('#sd_auto_auth').on('input', onAutoAuthInput);
+ $('#sd_drawthings_validate').on('click', validateDrawthingsUrl);
+ $('#sd_drawthings_url').on('input', onDrawthingsUrlInput);
+ $('#sd_drawthings_auth').on('input', onDrawthingsAuthInput);
$('#sd_vlad_validate').on('click', validateVladUrl);
$('#sd_vlad_url').on('input', onVladUrlInput);
$('#sd_vlad_auth').on('input', onVladAuthInput);
diff --git a/public/scripts/extensions/stable-diffusion/settings.html b/public/scripts/extensions/stable-diffusion/settings.html
index 9fcefe3bc..3cb34d3aa 100644
--- a/public/scripts/extensions/stable-diffusion/settings.html
+++ b/public/scripts/extensions/stable-diffusion/settings.html
@@ -36,6 +36,7 @@
+
@@ -56,6 +57,21 @@
Important: run SD Web UI with the --api flag! The server must be accessible from the SillyTavern host machine.
+
+ DrawThings API URL
+
+
+
+
+
+ Connect
+
+
+
+ Authentication (optional)
+
+ Important: run DrawThings app with HTTP API switch enabled in the UI! The server must be accessible from the SillyTavern host machine.
+
SD.Next API URL
diff --git a/src/endpoints/stable-diffusion.js b/src/endpoints/stable-diffusion.js
index 1054d2d6b..e2168cd80 100644
--- a/src/endpoints/stable-diffusion.js
+++ b/src/endpoints/stable-diffusion.js
@@ -638,7 +638,80 @@ together.post('/generate', jsonParser, async (request, response) => {
}
});
+const drawthings = express.Router();
+
+drawthings.post('/ping', jsonParser, async (request, response) => {
+ try {
+ const url = new URL(request.body.url);
+ url.pathname = '/';
+
+ const result = await fetch(url, {
+ method: 'HEAD',
+ });
+
+ if (!result.ok) {
+ throw new Error('SD DrawThings API returned an error.');
+ }
+
+ return response.sendStatus(200);
+ } catch (error) {
+ console.log(error);
+ return response.sendStatus(500);
+ }
+});
+
+drawthings.post('/get-model', jsonParser, async (request, response) => {
+ try {
+ const url = new URL(request.body.url);
+ url.pathname = '/';
+
+ const result = await fetch(url, {
+ method: 'GET',
+ });
+ const data = await result.json();
+
+ return response.send(data['model']);
+ } catch (error) {
+ console.log(error);
+ return response.sendStatus(500);
+ }
+});
+
+drawthings.post('/generate', jsonParser, async (request, response) => {
+ try {
+ console.log('SD DrawThings API request:', request.body);
+
+ const url = new URL(request.body.url);
+ url.pathname = '/sdapi/v1/txt2img';
+
+ const body = {...request.body};
+ delete body.url;
+
+ const result = await fetch(url, {
+ method: 'POST',
+ body: JSON.stringify(body),
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': getBasicAuthHeader(request.body.auth),
+ },
+ timeout: 0,
+ });
+
+ if (!result.ok) {
+ const text = await result.text();
+ throw new Error('SD DrawThings API returned an error.', { cause: text });
+ }
+
+ const data = await result.json();
+ return response.send(data);
+ } catch (error) {
+ console.log(error);
+ return response.sendStatus(500);
+ }
+});
+
router.use('/comfy', comfy);
router.use('/together', together);
+router.use('/drawthings', drawthings);
module.exports = { router };
From ea4ba57408869eb1dbecd7189ec5ad060cddc915 Mon Sep 17 00:00:00 2001
From: Wolfsblvt
Date: Sat, 30 Mar 2024 05:41:54 +0100
Subject: [PATCH 143/255] Fix horizontal scrollbar appearing in popups
- Fix that annoying horizontal scrollbar appearing in popups, e.g. the tag popup when you drag tags around
- Still provide possibility to make popups actually utilize scrollbars
---
public/script.js | 7 ++++---
public/style.css | 9 +++++++++
2 files changed, 13 insertions(+), 3 deletions(-)
diff --git a/public/script.js b/public/script.js
index 9df870852..9057cfec3 100644
--- a/public/script.js
+++ b/public/script.js
@@ -6810,18 +6810,19 @@ function onScenarioOverrideRemoveClick() {
* @param {string} type
* @param {string} inputValue - Value to set the input to.
* @param {PopupOptions} options - Options for the popup.
- * @typedef {{okButton?: string, rows?: number, wide?: boolean, large?: boolean }} PopupOptions - Options for the popup.
+ * @typedef {{okButton?: string, rows?: number, wide?: boolean, large?: boolean, allowHorizontalScrolling?: boolean, allowVerticalScrolling?: boolean }} PopupOptions - Options for the popup.
* @returns
*/
-function callPopup(text, type, inputValue = '', { okButton, rows, wide, large } = {}) {
+function callPopup(text, type, inputValue = '', { okButton, rows, wide, large, allowHorizontalScrolling, allowVerticalScrolling } = {}) {
dialogueCloseStop = true;
if (type) {
popup_type = type;
}
$('#dialogue_popup').toggleClass('wide_dialogue_popup', !!wide);
-
$('#dialogue_popup').toggleClass('large_dialogue_popup', !!large);
+ $('#dialogue_popup').toggleClass('horizontal_scrolling_dialogue_popup', !!allowHorizontalScrolling);
+ $('#dialogue_popup').toggleClass('vertical_scrolling_dialogue_popup', !!allowVerticalScrolling);
$('#dialogue_popup_cancel').css('display', 'inline-block');
switch (popup_type) {
diff --git a/public/style.css b/public/style.css
index e6218e820..90c04de9f 100644
--- a/public/style.css
+++ b/public/style.css
@@ -2081,6 +2081,7 @@ grammarly-extension {
display: flex;
flex-direction: column;
overflow-y: hidden;
+ overflow-x: hidden;
}
.rm_stat_block {
@@ -2101,6 +2102,14 @@ grammarly-extension {
min-width: var(--sheldWidth);
}
+.horizontal_scrolling_dialogue_popup {
+ overflow-x: unset !important;
+}
+
+.vertical_scrolling_dialogue_popup {
+ overflow-y: unset !important;
+}
+
#bulk_tag_popup_holder,
#dialogue_popup_holder {
display: flex;
From 12a36341f8c7ff1f38610ef09028a1d30256ecae Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sat, 30 Mar 2024 11:50:20 +0200
Subject: [PATCH 144/255] Fix newline trimming for the last message
---
public/script.js | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/public/script.js b/public/script.js
index 04fb0238d..7d1f85bdb 100644
--- a/public/script.js
+++ b/public/script.js
@@ -3505,15 +3505,18 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu
console.debug('generating prompt');
chatString = '';
arrMes = arrMes.reverse();
- arrMes.forEach(function (item, i, arr) {// For added anchors and others
+ arrMes.forEach(function (item, i, arr) {
// OAI doesn't need all of this
if (main_api === 'openai') {
return;
}
- // Cohee: I'm not even sure what this is for anymore
+ // Cohee: This removes a newline from the end of the last message in the context
+ // Last prompt line will add a newline if it's not a continuation
if (i === arrMes.length - 1 && type !== 'continue') {
- item = item.replace(/\n?$/, '');
+ if (!isInstruct || power_user.instruct.wrap) {
+ item = item.replace(/\n?$/, '');
+ }
}
mesSend[mesSend.length] = { message: item, extensionPrompts: [] };
From 06e15e6d5fc81b7a09844b909ca5c8a6e3434a04 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sat, 30 Mar 2024 12:00:10 +0200
Subject: [PATCH 145/255] L + skill issue + bad copypaste
---
public/scripts/instruct-mode.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/public/scripts/instruct-mode.js b/public/scripts/instruct-mode.js
index e646cdd79..48fec1129 100644
--- a/public/scripts/instruct-mode.js
+++ b/public/scripts/instruct-mode.js
@@ -373,7 +373,7 @@ export function formatInstructModeExamples(mesExamplesArray, name1, name2) {
let inputPrefix = power_user.instruct.input_sequence || '';
let outputPrefix = power_user.instruct.output_sequence || '';
- let inputSuffix = power_user.instruct.output_suffix || '';
+ let inputSuffix = power_user.instruct.input_suffix || '';
let outputSuffix = power_user.instruct.output_suffix || '';
if (power_user.instruct.macro) {
From d997f8dc530c0f027ca6b493f9f67dd75c67d8aa Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sat, 30 Mar 2024 12:26:21 +0200
Subject: [PATCH 146/255] Add {{trim}} macro
---
default/content/presets/context/ChatML.json | 2 +-
default/content/presets/context/Llama 2 Chat.json | 2 +-
default/content/presets/context/Mistral.json | 2 +-
public/scripts/macros.js | 4 ++++
4 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/default/content/presets/context/ChatML.json b/default/content/presets/context/ChatML.json
index 80046d170..2184e91d3 100644
--- a/default/content/presets/context/ChatML.json
+++ b/default/content/presets/context/ChatML.json
@@ -1,5 +1,5 @@
{
- "story_string": "<|im_start|>system\n{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}<|im_end|>",
+ "story_string": "<|im_start|>system\n{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}{{trim}}<|im_end|>",
"example_separator": "",
"chat_start": "",
"use_stop_strings": false,
diff --git a/default/content/presets/context/Llama 2 Chat.json b/default/content/presets/context/Llama 2 Chat.json
index a5e948925..be18ad69d 100644
--- a/default/content/presets/context/Llama 2 Chat.json
+++ b/default/content/presets/context/Llama 2 Chat.json
@@ -1,5 +1,5 @@
{
- "story_string": "[INST] <>\n{{#if system}}{{system}}\n<>\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}} [/INST]",
+ "story_string": "[INST] <>\n{{#if system}}{{system}}\n<>\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}{{trim}} [/INST]",
"example_separator": "",
"chat_start": "",
"use_stop_strings": false,
diff --git a/default/content/presets/context/Mistral.json b/default/content/presets/context/Mistral.json
index ebc691a5e..d9551afe8 100644
--- a/default/content/presets/context/Mistral.json
+++ b/default/content/presets/context/Mistral.json
@@ -1,5 +1,5 @@
{
- "story_string": "[INST] {{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}} [/INST]",
+ "story_string": "[INST] {{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}{{trim}} [/INST]",
"example_separator": "Examples:",
"chat_start": "",
"use_stop_strings": false,
diff --git a/public/scripts/macros.js b/public/scripts/macros.js
index 63da09b0c..f3527d451 100644
--- a/public/scripts/macros.js
+++ b/public/scripts/macros.js
@@ -4,6 +4,9 @@ import { textgenerationwebui_banned_in_macros } from './textgen-settings.js';
import { replaceInstructMacros } from './instruct-mode.js';
import { replaceVariableMacros } from './variables.js';
+// Register any macro that you want to leave in the compiled story string
+Handlebars.registerHelper('trim', () => '{{trim}}');
+
/**
* Returns the ID of the last message in the chat.
* @returns {string} The ID of the last message in the chat.
@@ -257,6 +260,7 @@ export function evaluateMacros(content, env) {
content = replaceInstructMacros(content);
content = replaceVariableMacros(content);
content = content.replace(/{{newline}}/gi, '\n');
+ content = content.replace(/\n*{{trim}}\n*/gi, '');
content = content.replace(/{{input}}/gi, () => String($('#send_textarea').val()));
// Substitute passed-in variables
From b747bdf89ba4e3b10ce299bcdfc27e36e9aa2796 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sat, 30 Mar 2024 13:46:46 +0200
Subject: [PATCH 147/255] Fix nav styles for narrower screens
---
public/style.css | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/public/style.css b/public/style.css
index 90c04de9f..711d4fcca 100644
--- a/public/style.css
+++ b/public/style.css
@@ -3887,6 +3887,7 @@ body:not(.movingUI) .drawer-content.maximized {
.paginationjs-size-changer select {
width: unset;
margin: 0;
+ font-size: calc(var(--mainFontSize) * 0.85);
}
.paginationjs-pages ul li a {
@@ -3916,10 +3917,10 @@ body:not(.movingUI) .drawer-content.maximized {
}
.paginationjs-nav {
- padding: 5px;
+ padding: 2px;
font-size: calc(var(--mainFontSize) * .8);
font-weight: bold;
- width: max-content;
+ width: auto;
}
.onboarding {
From 652987ae0195a644aed055c5cd5a2df2d4e95ddd Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sat, 30 Mar 2024 14:23:50 +0200
Subject: [PATCH 148/255] Add missing auth header
---
public/scripts/extensions/stable-diffusion/index.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/public/scripts/extensions/stable-diffusion/index.js b/public/scripts/extensions/stable-diffusion/index.js
index 7fd51ddc5..5f1a561c9 100644
--- a/public/scripts/extensions/stable-diffusion/index.js
+++ b/public/scripts/extensions/stable-diffusion/index.js
@@ -317,7 +317,7 @@ function getSdRequestBody() {
case sources.auto:
return { url: extension_settings.sd.auto_url, auth: extension_settings.sd.auto_auth };
case sources.drawthings:
- return { url: extension_settings.sd.drawthings_url };
+ return { url: extension_settings.sd.drawthings_url, auth: extension_settings.sd.drawthings_auth };
default:
throw new Error('Invalid SD source.');
}
From 9987ec33d95f7f8d40df38ed1b291476ea7c8344 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sat, 30 Mar 2024 15:48:24 +0200
Subject: [PATCH 149/255] Add new contexts to content index
---
default/content/index.json | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/default/content/index.json b/default/content/index.json
index fd66ea8d9..8a914b959 100644
--- a/default/content/index.json
+++ b/default/content/index.json
@@ -368,6 +368,10 @@
"filename": "presets/context/Alpaca-Single-Turn.json",
"type": "context"
},
+ {
+ "filename": "presets/context/Alpaca.json",
+ "type": "context"
+ },
{
"filename": "presets/context/ChatML.json",
"type": "context"
@@ -388,6 +392,10 @@
"filename": "presets/context/Lightning 1.1.json",
"type": "context"
},
+ {
+ "filename": "presets/context/Llama 2 Chat.json",
+ "type": "context"
+ },
{
"filename": "presets/context/Minimalist.json",
"type": "context"
@@ -412,6 +420,10 @@
"filename": "presets/context/Story.json",
"type": "context"
},
+ {
+ "filename": "presets/context/Synthia.json",
+ "type": "context"
+ },
{
"filename": "presets/context/simple-proxy-for-tavern.json",
"type": "context"
From e99d37d549520d47e72f5dcf5a98b37e98419d69 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sat, 30 Mar 2024 15:54:42 +0200
Subject: [PATCH 150/255] Remove extraneous newlines when formatting quiet
prompts
---
public/script.js | 2 +-
public/scripts/instruct-mode.js | 5 +++++
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/public/script.js b/public/script.js
index 7d1f85bdb..83a587d2d 100644
--- a/public/script.js
+++ b/public/script.js
@@ -3555,7 +3555,7 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu
//TODO: respect output_sequence vs last_output_sequence settings
//TODO: decide how to prompt this to clarify who is talking 'Narrator', 'System', etc.
if (isInstruct) {
- lastMesString += '\n' + quietAppend; // + power_user.instruct.output_sequence + '\n';
+ lastMesString += quietAppend; // + power_user.instruct.output_sequence + '\n';
} else {
lastMesString += quietAppend;
}
diff --git a/public/scripts/instruct-mode.js b/public/scripts/instruct-mode.js
index 48fec1129..de8b93c5b 100644
--- a/public/scripts/instruct-mode.js
+++ b/public/scripts/instruct-mode.js
@@ -458,6 +458,11 @@ export function formatInstructModePrompt(name, isImpersonate, promptBias, name1,
const separator = power_user.instruct.wrap ? '\n' : '';
let text = includeNames ? (separator + sequence + separator + `${name}:`) : (separator + sequence);
+ // Quiet prompt already has a newline at the end
+ if (isQuiet && separator) {
+ text = text.slice(separator.length);
+ }
+
if (!isImpersonate && promptBias) {
text += (includeNames ? promptBias : (separator + promptBias.trimStart()));
}
From 34c79049ad054135538583fb9bd0b7d2c81642fe Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sat, 30 Mar 2024 16:04:10 +0200
Subject: [PATCH 151/255] Fix edge case with newlines in quiet prompts with
wrap enabled
---
public/script.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/public/script.js b/public/script.js
index 83a587d2d..4c7e06f88 100644
--- a/public/script.js
+++ b/public/script.js
@@ -3514,7 +3514,7 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu
// Cohee: This removes a newline from the end of the last message in the context
// Last prompt line will add a newline if it's not a continuation
if (i === arrMes.length - 1 && type !== 'continue') {
- if (!isInstruct || power_user.instruct.wrap) {
+ if (!isInstruct || (power_user.instruct.wrap && type !== 'quiet')) {
item = item.replace(/\n?$/, '');
}
}
From 26690353e1db9fc5b82c025f5fcf6c895a712ad5 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sat, 30 Mar 2024 16:05:43 +0200
Subject: [PATCH 152/255] + edge case comment
---
public/script.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/public/script.js b/public/script.js
index 4c7e06f88..5a2be853e 100644
--- a/public/script.js
+++ b/public/script.js
@@ -3513,6 +3513,7 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu
// Cohee: This removes a newline from the end of the last message in the context
// Last prompt line will add a newline if it's not a continuation
+ // In instruct mode it only removes it if wrap is enabled and it's not a quiet generation
if (i === arrMes.length - 1 && type !== 'continue') {
if (!isInstruct || (power_user.instruct.wrap && type !== 'quiet')) {
item = item.replace(/\n?$/, '');
From bd62c2fb70ff0505616175a2c1bc0e9ad6e36be9 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sat, 30 Mar 2024 18:13:55 +0200
Subject: [PATCH 153/255] Add 'as' argument for /gen command
---
public/scripts/slash-commands.js | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js
index 7d4c63dbe..fb745364d 100644
--- a/public/scripts/slash-commands.js
+++ b/public/scripts/slash-commands.js
@@ -230,7 +230,7 @@ parser.addCommand('peek', peekCallback, [], '(message in
parser.addCommand('delswipe', deleteSwipeCallback, ['swipedel'], '(optional 1-based id) – deletes a swipe from the last chat message. If swipe id not provided - deletes the current swipe.', true, true);
parser.addCommand('echo', echoCallback, [], '(title=string severity=info/warning/error/success [text]) – echoes the text to toast message. Useful for pipes debugging.', true, true);
//parser.addCommand('#', (_, value) => '', [], ' – a comment, does nothing, e.g. /# the next three commands switch variables a and b', true, true);
-parser.addCommand('gen', generateCallback, [], '(lock=on/off name="System" [prompt]) – generates text using the provided prompt and passes it to the next command through the pipe, optionally locking user input while generating and allowing to configure the in-prompt name for instruct mode (default = "System").', true, true);
+parser.addCommand('gen', generateCallback, [], '(lock=on/off name="System" [prompt]) – generates text using the provided prompt and passes it to the next command through the pipe, optionally locking user input while generating and allowing to configure the in-prompt name for instruct mode (default = "System"). "as" argument controls the role of the output prompt: system (default) or char.', true, true);
parser.addCommand('genraw', generateRawCallback, [], '(lock=on/off instruct=on/off stop=[] as=system/char [prompt]) – generates text using the provided prompt and passes it to the next command through the pipe, optionally locking user input while generating. Does not include chat history or character card. Use instruct=off to skip instruct formatting, e.g. /genraw instruct=off Why is the sky blue?. Use stop=... with a JSON-serialized array to add one-time custom stop strings, e.g. /genraw stop=["\\n"] Say hi. "as" argument controls the role of the output prompt: system (default) or char.', true, true);
parser.addCommand('addswipe', addSwipeCallback, ['swipeadd'], '(text) – adds a swipe to the last chat message.', true, true);
parser.addCommand('abort', abortCallback, [], ' – aborts the slash command batch execution', true, true);
@@ -687,6 +687,8 @@ async function generateCallback(args, value) {
// Prevent generate recursion
$('#send_textarea').val('').trigger('input');
const lock = isTrueBoolean(args?.lock);
+ const as = args?.as || 'system';
+ const quietToLoud = as === 'char';
try {
if (lock) {
@@ -695,7 +697,7 @@ async function generateCallback(args, value) {
setEphemeralStopStrings(resolveVariable(args?.stop));
const name = args?.name;
- const result = await generateQuietPrompt(value, false, false, '', name);
+ const result = await generateQuietPrompt(value, quietToLoud, false, '', name);
return result;
} finally {
if (lock) {
From a8388259ab5929ddfad531c570152b2c4a73af82 Mon Sep 17 00:00:00 2001
From: Lumi
Date: Sat, 30 Mar 2024 19:57:23 +0100
Subject: [PATCH 154/255] Update server.js
Print warning if basicAuth username or password fails to parse.
In a normal case the user has no way to be informed if the username or password fails to parse. While this might end up being a skill issue on the users side it could help them to troubleshoot the issue.
---
server.js | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/server.js b/server.js
index 538679dbc..8aa2397a6 100644
--- a/server.js
+++ b/server.js
@@ -655,6 +655,18 @@ const setupTasks = async function () {
if (listen) {
console.log('\n0.0.0.0 means SillyTavern is listening on all network interfaces (Wi-Fi, LAN, localhost). If you want to limit it only to internal localhost (127.0.0.1), change the setting in config.yaml to "listen: false". Check "access.log" file in the SillyTavern directory if you want to inspect incoming connections.\n');
}
+
+ if (getConfigValue("basicAuthMode", false)) {
+ const basicAuthUser = getConfigValue("basicAuthUser", null);
+ if (!basicAuthUser.username || !basicAuthUser.password) {
+ console.warn(
+ color.yellow(
+ "Basic Authentication is set, but username or password is not set or empty!"
+ )
+ );
+ }
+ }
+
};
/**
From 6fe7c1fdaf873f85f8a064cdbb35b1f7e6aa60c9 Mon Sep 17 00:00:00 2001
From: Wolfsblvt
Date: Sat, 30 Mar 2024 20:33:08 +0100
Subject: [PATCH 155/255] Fix reprint loop on tag filters
- Fix endless loop if a tag was selected
- Tag selection is now saved, both 'selected' and 'excluded' (old state is lost though)
- Streamlined reprinting even more by refactoring bogus drilldown
---
public/scripts/filters.js | 3 +-
public/scripts/tags.js | 61 +++++++++++++++++----------------------
2 files changed, 29 insertions(+), 35 deletions(-)
diff --git a/public/scripts/filters.js b/public/scripts/filters.js
index d880cbf37..92ed6992c 100644
--- a/public/scripts/filters.js
+++ b/public/scripts/filters.js
@@ -24,6 +24,7 @@ export const FILTER_STATES = {
EXCLUDED: { key: 'EXCLUDED', class: 'excluded' },
UNDEFINED: { key: 'UNDEFINED', class: 'undefined' },
};
+export const DEFAULT_FILTER_STATE = FILTER_STATES.UNDEFINED.key;
/**
* Robust check if one state equals the other. It does not care whether it's the state key or the state value object.
@@ -203,7 +204,7 @@ export class FilterHelper {
return this.filterDataByState(data, state, isFolder);
}
- filterDataByState(data, state, filterFunc, { includeFolders } = {}) {
+ filterDataByState(data, state, filterFunc, { includeFolders = false } = {}) {
if (isFilterState(state, FILTER_STATES.SELECTED)) {
return data.filter(entity => filterFunc(entity) || (includeFolders && entity.type == 'tag'));
}
diff --git a/public/scripts/tags.js b/public/scripts/tags.js
index 8c38ec864..dddc3c337 100644
--- a/public/scripts/tags.js
+++ b/public/scripts/tags.js
@@ -13,7 +13,7 @@ import {
event_types,
} from '../script.js';
// eslint-disable-next-line no-unused-vars
-import { FILTER_TYPES, FILTER_STATES, isFilterState, FilterHelper } from './filters.js';
+import { FILTER_TYPES, FILTER_STATES, DEFAULT_FILTER_STATE, isFilterState, FilterHelper } from './filters.js';
import { groupCandidatesFilter, groups, selected_group } from './group-chats.js';
import { download, onlyUnique, parseJsonFile, uuidv4, getSortableDelay, debounce } from './utils.js';
@@ -180,6 +180,7 @@ function isBogusFolderOpen() {
/**
* Function to be called when a specific tag/folder is chosen to "drill down".
+ *
* @param {*} source The jQuery element clicked when choosing the folder
* @param {string} tagId The tag id that is behind the chosen folder
* @param {boolean} remove Whether the given tag should be removed (otherwise it is added/chosen)
@@ -197,12 +198,9 @@ function chooseBogusFolder(source, tagId, remove = false) {
// Instead of manually updating the filter conditions, we just "click" on the filter tag
// We search inside which filter block we are located in and use that one
const FILTER_SELECTOR = ($(source).closest('#rm_characters_block') ?? $(source).closest('#rm_group_chats_block')).find('.rm_tag_filter');
- if (remove) {
- // Click twice to skip over the 'excluded' state
- $(FILTER_SELECTOR).find(`.tag[id=${tagId}]`).trigger('click').trigger('click');
- } else {
- $(FILTER_SELECTOR).find(`.tag[id=${tagId}]`).trigger('click');
- }
+ const tagElement = $(FILTER_SELECTOR).find(`.tag[id=${tagId}]`);
+
+ toggleTagThreeState(tagElement, { stateOverride: DEFAULT_FILTER_STATE, simulateClick: true });
}
/**
@@ -603,8 +601,8 @@ function appendTagToList(listElement, tag, { removable = false, selectable = fal
tagElement.find('.tag_name').text('').attr('title', tag.name).addClass(tag.icon);
}
- if (tag.excluded && isGeneralList) {
- toggleTagThreeState(tagElement, { stateOverride: FILTER_STATES.EXCLUDED });
+ if (selectable && isGeneralList) {
+ toggleTagThreeState(tagElement, { stateOverride: tag.filterState ?? DEFAULT_FILTER_STATE });
}
if (selectable) {
@@ -629,34 +627,28 @@ function onTagFilterClick(listElement) {
let state = toggleTagThreeState($(this));
- // Manual undefined check required for three-state boolean
if (existingTag) {
- existingTag.excluded = isFilterState(state, FILTER_STATES.EXCLUDED);
-
+ existingTag.filterState = state;
saveSettingsDebounced();
}
- // Update bogus folder if applicable
- if (isBogusFolder(existingTag)) {
- // Update bogus drilldown
- if ($(this).hasClass('selected')) {
- appendTagToList($('.rm_tag_controls .rm_tag_bogus_drilldown'), existingTag, { removable: true });
- } else {
- $(listElement).closest('.rm_tag_controls').find(`.rm_tag_bogus_drilldown .tag[id=${tagId}]`).remove();
- }
- }
-
+ // We don't print anything manually, updating the filter will automatically trigger a redraw of all relevant stuff
runTagFilters(listElement);
- updateTagFilterIndicator();
}
function toggleTagThreeState(element, { stateOverride = undefined, simulateClick = false } = {}) {
const states = Object.keys(FILTER_STATES);
+ // Make it clear we're getting indexes and handling the 'not found' case in one place
+ function getStateIndex(key, fallback) {
+ const index = states.indexOf(key);
+ return index !== -1 ? index : states.indexOf(fallback);
+ }
+
const overrideKey = states.includes(stateOverride) ? stateOverride : Object.keys(FILTER_STATES).find(key => FILTER_STATES[key] === stateOverride);
- const currentStateIndex = states.indexOf(element.attr('data-toggle-state')) ?? states.length - 1;
- const targetStateIndex = overrideKey !== undefined ? states.indexOf(overrideKey) : (currentStateIndex + 1) % states.length;
+ const currentStateIndex = getStateIndex(element.attr('data-toggle-state'), DEFAULT_FILTER_STATE);
+ const targetStateIndex = overrideKey !== undefined ? getStateIndex(overrideKey, DEFAULT_FILTER_STATE) : (currentStateIndex + 1) % states.length;
if (simulateClick) {
// Calculate how many clicks are needed to go from the current state to the target state
@@ -695,10 +687,8 @@ function runTagFilters(listElement) {
}
function printTagFilters(type = tag_filter_types.character) {
- const filterData = structuredClone(entitiesFilter.getFilterData(FILTER_TYPES.TAG));
const FILTER_SELECTOR = type === tag_filter_types.character ? CHARACTER_FILTER_SELECTOR : GROUP_FILTER_SELECTOR;
$(FILTER_SELECTOR).empty();
- $(FILTER_SELECTOR).siblings('.rm_tag_bogus_drilldown').empty();
// Print all action tags. (Exclude folder if that setting isn't chosen)
const actionTags = Object.values(ACTIONABLE_TAGS).filter(tag => power_user.bogus_folders || tag.id != ACTIONABLE_TAGS.FOLDER.id);
@@ -708,18 +698,21 @@ function printTagFilters(type = tag_filter_types.character) {
printTagList($(FILTER_SELECTOR), { empty: false, tags: inListActionTags, tagActionSelector: tag => tag.action, tagOptions: { isGeneralList: true } });
const characterTagIds = Object.values(tag_map).flat();
- const tagsToDisplay = tags
- .filter(x => characterTagIds.includes(x.id))
- .sort(compareTagsForSort);
+ const tagsToDisplay = tags.filter(x => characterTagIds.includes(x.id)).sort(compareTagsForSort);
printTagList($(FILTER_SELECTOR), { empty: false, tags: tagsToDisplay, tagOptions: { selectable: true, isGeneralList: true } });
- runTagFilters(FILTER_SELECTOR);
+ // Print bogus folder navigation
+ const bogusDrilldown = $(FILTER_SELECTOR).siblings('.rm_tag_bogus_drilldown');
+ bogusDrilldown.empty();
+ if (power_user.bogus_folders && bogusDrilldown.length > 0) {
+ const filterData = structuredClone(entitiesFilter.getFilterData(FILTER_TYPES.TAG));
+ const navigatedTags = filterData.selected.map(x => tags.find(t => t.id == x)).filter(x => isBogusFolder(x));
- // Simulate clicks on all "selected" tags when we reprint, otherwise their filter gets lost. "excluded" is persisted.
- for (const tagId of filterData.selected) {
- toggleTagThreeState($(`${FILTER_SELECTOR} .tag[id="${tagId}"]`), { stateOverride: FILTER_STATES.SELECTED, simulateClick: true });
+ printTagList(bogusDrilldown, { tags: navigatedTags, tagOptions: { removable: true } });
}
+ runTagFilters(FILTER_SELECTOR);
+
if (power_user.show_tag_filters) {
$('.rm_tag_controls .showTagList').addClass('selected');
$('.rm_tag_controls').find('.tag:not(.actionable)').show();
From 4d98310848a26ede8d03fe7650c90be24b89ef5d Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sat, 30 Mar 2024 22:38:09 +0200
Subject: [PATCH 156/255] Limit console log depth again (a little bit)
---
server.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/server.js b/server.js
index 61aedc313..edecb3d7c 100644
--- a/server.js
+++ b/server.js
@@ -30,7 +30,7 @@ const fetch = require('node-fetch').default;
// Unrestrict console logs display limit
util.inspect.defaultOptions.maxArrayLength = null;
util.inspect.defaultOptions.maxStringLength = null;
-util.inspect.defaultOptions.depth = null;
+util.inspect.defaultOptions.depth = 4;
// local library imports
const basicAuthMiddleware = require('./src/middleware/basicAuth');
From c94460714d56fd6db3ed4214f417ccb0b6801fd3 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sat, 30 Mar 2024 22:42:51 +0200
Subject: [PATCH 157/255] Whitelist to check listen mode via console
---
server.js | 2 +-
src/middleware/whitelist.js | 52 +++++++++++++++++++++----------------
2 files changed, 30 insertions(+), 24 deletions(-)
diff --git a/server.js b/server.js
index edecb3d7c..553997b5f 100644
--- a/server.js
+++ b/server.js
@@ -123,7 +123,7 @@ app.use(CORS);
if (listen && getConfigValue('basicAuthMode', false)) app.use(basicAuthMiddleware);
-app.use(whitelistMiddleware);
+app.use(whitelistMiddleware(listen));
// CSRF Protection //
if (!cliArguments.disableCsrf) {
diff --git a/src/middleware/whitelist.js b/src/middleware/whitelist.js
index 5d9798680..87d5ac5a5 100644
--- a/src/middleware/whitelist.js
+++ b/src/middleware/whitelist.js
@@ -8,7 +8,6 @@ const { color, getConfigValue } = require('../util');
const whitelistPath = path.join(process.cwd(), './whitelist.txt');
let whitelist = getConfigValue('whitelist', []);
let knownIPs = new Set();
-const listen = getConfigValue('listen', false);
const whitelistMode = getConfigValue('whitelistMode', true);
if (fs.existsSync(whitelistPath)) {
@@ -34,30 +33,37 @@ function getIpFromRequest(req) {
return clientIp;
}
-const whitelistMiddleware = function (req, res, next) {
- const clientIp = getIpFromRequest(req);
+/**
+ * Returns a middleware function that checks if the client IP is in the whitelist.
+ * @param {boolean} listen If listen mode is enabled via config or command line
+ * @returns {import('express').RequestHandler} The middleware function
+ */
+function whitelistMiddleware(listen) {
+ return function (req, res, next) {
+ const clientIp = getIpFromRequest(req);
- if (listen && !knownIPs.has(clientIp)) {
- const userAgent = req.headers['user-agent'];
- console.log(color.yellow(`New connection from ${clientIp}; User Agent: ${userAgent}\n`));
- knownIPs.add(clientIp);
+ if (listen && !knownIPs.has(clientIp)) {
+ const userAgent = req.headers['user-agent'];
+ console.log(color.yellow(`New connection from ${clientIp}; User Agent: ${userAgent}\n`));
+ knownIPs.add(clientIp);
- // Write access log
- const timestamp = new Date().toISOString();
- const log = `${timestamp} ${clientIp} ${userAgent}\n`;
- fs.appendFile('access.log', log, (err) => {
- if (err) {
- console.error('Failed to write access log:', err);
- }
- });
- }
+ // Write access log
+ const timestamp = new Date().toISOString();
+ const log = `${timestamp} ${clientIp} ${userAgent}\n`;
+ fs.appendFile('access.log', log, (err) => {
+ if (err) {
+ console.error('Failed to write access log:', err);
+ }
+ });
+ }
- //clientIp = req.connection.remoteAddress.split(':').pop();
- if (whitelistMode === true && !whitelist.some(x => ipMatching.matches(clientIp, ipMatching.getMatch(x)))) {
- console.log(color.red('Forbidden: Connection attempt from ' + clientIp + '. If you are attempting to connect, please add your IP address in whitelist or disable whitelist mode in config.yaml in root of SillyTavern folder.\n'));
- return res.status(403).send('Forbidden: Connection attempt from ' + clientIp + '. If you are attempting to connect, please add your IP address in whitelist or disable whitelist mode in config.yaml in root of SillyTavern folder.');
- }
- next();
-};
+ //clientIp = req.connection.remoteAddress.split(':').pop();
+ if (whitelistMode === true && !whitelist.some(x => ipMatching.matches(clientIp, ipMatching.getMatch(x)))) {
+ console.log(color.red('Forbidden: Connection attempt from ' + clientIp + '. If you are attempting to connect, please add your IP address in whitelist or disable whitelist mode in config.yaml in root of SillyTavern folder.\n'));
+ return res.status(403).send('Forbidden: Connection attempt from ' + clientIp + '. If you are attempting to connect, please add your IP address in whitelist or disable whitelist mode in config.yaml in root of SillyTavern folder.');
+ }
+ next();
+ };
+}
module.exports = whitelistMiddleware;
From af6deda64d1e687e711a0b181f231c21e24736a2 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sat, 30 Mar 2024 22:46:18 +0200
Subject: [PATCH 158/255] Null safety + reuse variable
---
server.js | 19 ++++++++-----------
1 file changed, 8 insertions(+), 11 deletions(-)
diff --git a/server.js b/server.js
index a57b082cc..af8e692b7 100644
--- a/server.js
+++ b/server.js
@@ -109,7 +109,8 @@ app.use(responseTime());
const server_port = cliArguments.port ?? process.env.SILLY_TAVERN_PORT ?? getConfigValue('port', DEFAULT_PORT);
const autorun = (cliArguments.autorun ?? getConfigValue('autorun', DEFAULT_AUTORUN)) && !cliArguments.ssl;
const listen = cliArguments.listen ?? getConfigValue('listen', DEFAULT_LISTEN);
-const enableCorsProxy = cliArguments.corsProxy ?? getConfigValue('enableCorsProxy', DEFAULT_CORS_PROXY)
+const enableCorsProxy = cliArguments.corsProxy ?? getConfigValue('enableCorsProxy', DEFAULT_CORS_PROXY);
+const basicAuthMode = getConfigValue('basicAuthMode', false);
const { DIRECTORIES, UPLOADS_PATH } = require('./src/constants');
@@ -121,7 +122,7 @@ const CORS = cors({
app.use(CORS);
-if (listen && getConfigValue('basicAuthMode', false)) app.use(basicAuthMiddleware);
+if (listen && basicAuthMode) app.use(basicAuthMiddleware);
app.use(whitelistMiddleware(listen));
@@ -516,14 +517,10 @@ const setupTasks = async function () {
console.log('\n0.0.0.0 means SillyTavern is listening on all network interfaces (Wi-Fi, LAN, localhost). If you want to limit it only to internal localhost (127.0.0.1), change the setting in config.yaml to "listen: false". Check "access.log" file in the SillyTavern directory if you want to inspect incoming connections.\n');
}
- if (getConfigValue("basicAuthMode", false)) {
- const basicAuthUser = getConfigValue("basicAuthUser", null);
- if (!basicAuthUser.username || !basicAuthUser.password) {
- console.warn(
- color.yellow(
- "Basic Authentication is set, but username or password is not set or empty!"
- )
- );
+ if (basicAuthMode) {
+ const basicAuthUser = getConfigValue('basicAuthUser', {});
+ if (!basicAuthUser?.username || !basicAuthUser?.password) {
+ console.warn(color.yellow('Basic Authentication is enabled, but username or password is not set or empty!'));
}
}
@@ -541,7 +538,7 @@ async function loadPlugins() {
return cleanupPlugins;
} catch {
console.log('Plugin loading failed.');
- return () => {};
+ return () => { };
}
}
From 50670c1e6a1f555dfcee5040cff242e336ab0877 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sat, 30 Mar 2024 22:52:57 +0200
Subject: [PATCH 159/255] + more reused config variable
---
server.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/server.js b/server.js
index af8e692b7..d57f73f72 100644
--- a/server.js
+++ b/server.js
@@ -542,7 +542,7 @@ async function loadPlugins() {
}
}
-if (listen && !getConfigValue('whitelistMode', true) && !getConfigValue('basicAuthMode', false)) {
+if (listen && !getConfigValue('whitelistMode', true) && !basicAuthMode) {
if (getConfigValue('securityOverride', false)) {
console.warn(color.red('Security has been overridden. If it\'s not a trusted network, change the settings.'));
}
From 71a630ad8500f2d83532c7d6a943f05c2224ec06 Mon Sep 17 00:00:00 2001
From: Wolfsblvt
Date: Sat, 30 Mar 2024 22:06:50 +0100
Subject: [PATCH 160/255] Code documentation for tags & bogus state
- Add lots of code documentation for tag functions (I'm sorry, I live in object oriented languages...)
- Fix bogus folder setting not being respected for some controls
---
public/script.js | 43 +++++++++++++-
public/scripts/tags.js | 125 +++++++++++++++++++++++++++++++++--------
2 files changed, 145 insertions(+), 23 deletions(-)
diff --git a/public/script.js b/public/script.js
index 9b5438791..8ed0323e2 100644
--- a/public/script.js
+++ b/public/script.js
@@ -1324,7 +1324,7 @@ async function printCharacters(fullRefresh = false) {
showNavigator: true,
callback: function (data) {
$(listId).empty();
- if (isBogusFolderOpen()) {
+ if (power_user.bogus_folders && isBogusFolderOpen()) {
$(listId).append(getBackBlock());
}
if (!data.length) {
@@ -1368,18 +1368,59 @@ async function printCharacters(fullRefresh = false) {
favsToHotswap();
}
+/** @typedef {object} Character - A character */
+/** @typedef {object} Group - A group */
+
+/**
+ * @typedef {object} Entity - Object representing a display entity
+ * @property {Character|Group|import('./scripts/tags.js').Tag|*} item - The item
+ * @property {string|number} id - The id
+ * @property {string} type - The type of this entity (character, group, tag)
+ * @property {Entity[]} [entities] - An optional list of entities relevant for this item
+ * @property {number} [hidden] - An optional number representing how many hidden entities this entity contains
+ */
+
+/**
+ * Converts the given character to its entity representation
+ *
+ * @param {Character} character - The character
+ * @param {string|number} id - The id of this character
+ * @returns {Entity} The entity for this character
+ */
export function characterToEntity(character, id) {
return { item: character, id, type: 'character' };
}
+/**
+ * Converts the given group to its entity representation
+ *
+ * @param {Group} group - The group
+ * @returns {Entity} The entity for this group
+ */
export function groupToEntity(group) {
return { item: group, id: group.id, type: 'group' };
}
+/**
+ * Converts the given tag to its entity representation
+ *
+ * @param {import('./scripts/tags.js').Tag} tag - The tag
+ * @returns {Entity} The entity for this tag
+ */
export function tagToEntity(tag) {
return { item: structuredClone(tag), id: tag.id, type: 'tag', entities: [] };
}
+/**
+ * Builds the full list of all entities available
+ *
+ * They will be correctly marked and filtered.
+ *
+ * @param {object} param0 - Optional parameters
+ * @param {boolean} [param0.doFilter] - Whether this entity list should already be filtered based on the global filters
+ * @param {boolean} [param0.doSort] - Whether the entity list should be sorted when returned
+ * @returns {Entity[]} All entities
+ */
export function getEntitiesList({ doFilter = false, doSort = true } = {}) {
let entities = [
...characters.map((item, index) => characterToEntity(item, index)),
diff --git a/public/scripts/tags.js b/public/scripts/tags.js
index dddc3c337..a33b338f0 100644
--- a/public/scripts/tags.js
+++ b/public/scripts/tags.js
@@ -6,7 +6,6 @@ import {
menu_type,
getCharacters,
entitiesFilter,
- printCharacters,
printCharactersDebounced,
buildAvatarList,
eventSource,
@@ -55,12 +54,12 @@ export const tag_filter_types = {
};
const ACTIONABLE_TAGS = {
- FAV: { id: 1, sort_order: 1, name: 'Show only favorites', color: 'rgba(255, 255, 0, 0.5)', action: filterByFav, icon: 'fa-solid fa-star', class: 'filterByFavorites' },
- GROUP: { id: 0, sort_order: 2, name: 'Show only groups', color: 'rgba(100, 100, 100, 0.5)', action: filterByGroups, icon: 'fa-solid fa-users', class: 'filterByGroups' },
- FOLDER: { id: 4, sort_order: 3, name: 'Always show folders', color: 'rgba(120, 120, 120, 0.5)', action: filterByFolder, icon: 'fa-solid fa-folder-plus', class: 'filterByFolder' },
- VIEW: { id: 2, sort_order: 4, name: 'Manage tags', color: 'rgba(150, 100, 100, 0.5)', action: onViewTagsListClick, icon: 'fa-solid fa-gear', class: 'manageTags' },
- HINT: { id: 3, sort_order: 5, name: 'Show Tag List', color: 'rgba(150, 100, 100, 0.5)', action: onTagListHintClick, icon: 'fa-solid fa-tags', class: 'showTagList' },
- UNFILTER: { id: 5, sort_order: 6, name: 'Clear all filters', action: onClearAllFiltersClick, icon: 'fa-solid fa-filter-circle-xmark', class: 'clearAllFilters' },
+ FAV: { id: "1", sort_order: 1, name: 'Show only favorites', color: 'rgba(255, 255, 0, 0.5)', action: filterByFav, icon: 'fa-solid fa-star', class: 'filterByFavorites' },
+ GROUP: { id: "0", sort_order: 2, name: 'Show only groups', color: 'rgba(100, 100, 100, 0.5)', action: filterByGroups, icon: 'fa-solid fa-users', class: 'filterByGroups' },
+ FOLDER: { id: "4", sort_order: 3, name: 'Always show folders', color: 'rgba(120, 120, 120, 0.5)', action: filterByFolder, icon: 'fa-solid fa-folder-plus', class: 'filterByFolder' },
+ VIEW: { id: "2", sort_order: 4, name: 'Manage tags', color: 'rgba(150, 100, 100, 0.5)', action: onViewTagsListClick, icon: 'fa-solid fa-gear', class: 'manageTags' },
+ HINT: { id: "3", sort_order: 5, name: 'Show Tag List', color: 'rgba(150, 100, 100, 0.5)', action: onTagListHintClick, icon: 'fa-solid fa-tags', class: 'showTagList' },
+ UNFILTER: { id: "5", sort_order: 6, name: 'Clear all filters', action: onClearAllFiltersClick, icon: 'fa-solid fa-filter-circle-xmark', class: 'clearAllFilters' },
};
const InListActionable = {
@@ -82,8 +81,31 @@ const TAG_FOLDER_TYPES = {
};
const TAG_FOLDER_DEFAULT_TYPE = 'NONE';
+/**
+ * @typedef {object} Tag - Object representing a tag
+ * @property {string} id - The id of the tag (As a kind of has string. This is used whenever the tag is referenced or linked, as the name might change)
+ * @property {string} name - The name of the tag
+ * @property {string} [folder_type] - The bogus folder type of this tag (based on `TAG_FOLDER_TYPES`)
+ * @property {string} [filter_state] - The saved state of the filter chosen of this tag (based on `FILTER_STATES`)
+ * @property {number} [sort_order] - A custom integer representing the sort order if tags are sorted
+ * @property {string} [color] - The background color of the tag
+ * @property {string} [color2] - The foreground color of the tag
+ * @property {number} [create_date] - A number representing the date when this tag was created
+ *
+ * @property {string} [class] - An optional css class added to the control representing this tag when printed. Used for custom tags in the filters.
+ * @property {string} [icon] - An optional css class of an icon representing this tag when printed. This will replace the tag name with the icon. Used for custom tags in the filters.
+ */
+/**
+ * An list of all tags that are available
+ * @type {Tag[]}
+ */
let tags = [];
+
+/**
+ * A map representing the key of an entity (character avatar, group id, etc) with a corresponding array of tags this entity has assigned. The array might not exist if no tags were assigned yet.
+ * @type {Object.}
+ */
let tag_map = {};
/**
@@ -136,6 +158,15 @@ function filterByTagState(entities, { globalDisplayFilters = false, subForEntity
return entities;
}
+/**
+ * Filter a a list of entities based on a given tag, returning all entities that represent "sub entities"
+ *
+ * @param {Tag} tag - The to filter the entities for
+ * @param {object[]} entities - The list of possible entities (tag, group, folder) that should get filtered
+ * @param {object} param2 - optional parameteres
+ * @param {boolean} [param2.filterHidden] - Whether hidden entities should be filtered out too
+ * @returns {object[]} The filtered list of entities that apply to the given tag
+ */
function filterTagSubEntities(tag, entities, { filterHidden = true } = {}) {
const filterData = structuredClone(entitiesFilter.getFilterData(FILTER_TYPES.TAG));
@@ -160,7 +191,9 @@ function filterTagSubEntities(tag, entities, { filterHidden = true } = {}) {
/**
* Indicates whether a given tag is defined as a folder. Meaning it's neither undefined nor 'NONE'.
- * @returns {boolean} If it's a tag folder
+ *
+ * @param {Tag} tag - The tag to check
+ * @returns {boolean} Whether it's a tag folder
*/
function isBogusFolder(tag) {
return tag?.folder_type !== undefined && tag.folder_type !== TAG_FOLDER_DEFAULT_TYPE;
@@ -168,6 +201,7 @@ function isBogusFolder(tag) {
/**
* Indicates whether a user is currently in a bogus folder.
+ *
* @returns {boolean} If currently viewing a folder
*/
function isBogusFolderOpen() {
@@ -205,21 +239,22 @@ function chooseBogusFolder(source, tagId, remove = false) {
/**
* Builds the tag block for the specified item.
- * @param {Object} item The tag item
+ *
+ * @param {Tag} tag The tag item
* @param {*} entities The list ob sub items for this tag
* @param {*} hidden A count of how many sub items are hidden
* @returns The html for the tag block
*/
-function getTagBlock(item, entities, hidden = 0) {
+function getTagBlock(tag, entities, hidden = 0) {
let count = entities.length;
- const tagFolder = TAG_FOLDER_TYPES[item.folder_type];
+ const tagFolder = TAG_FOLDER_TYPES[tag.folder_type];
const template = $('#bogus_folder_template .bogus_folder_select').clone();
template.addClass(tagFolder.class);
- template.attr({ 'tagid': item.id, 'id': `BogusFolder${item.id}` });
- template.find('.avatar').css({ 'background-color': item.color, 'color': item.color2 }).attr('title', `[Folder] ${item.name}`);
- template.find('.ch_name').text(item.name).attr('title', `[Folder] ${item.name}`);
+ template.attr({ 'tagid': tag.id, 'id': `BogusFolder${tag.id}` });
+ template.find('.avatar').css({ 'background-color': tag.color, 'color': tag.color2 }).attr('title', `[Folder] ${tag.name}`);
+ template.find('.ch_name').text(tag.name).attr('title', `[Folder] ${tag.name}`);
template.find('.bogus_folder_hidden_counter').text(hidden > 0 ? `${hidden} hidden` : '');
template.find('.bogus_folder_counter').text(`${count} ${count != 1 ? 'characters' : 'character'}`);
template.find('.bogus_folder_icon').addClass(tagFolder.fa_icon);
@@ -275,6 +310,13 @@ function createTagMapFromList(listElement, key) {
saveSettingsDebounced();
}
+/**
+ * Gets a list of all tags for a given entity key.
+ * If you have an entity, you can get it's key easily via `getTagKeyForEntity(entity)`.
+ *
+ * @param {string} key - The key for which to get tags via the tag map
+ * @returns {Tag[]} A list of tags
+ */
function getTagsList(key) {
if (!Array.isArray(tag_map[key])) {
tag_map[key] = [];
@@ -299,6 +341,9 @@ function getInlineListSelector() {
return null;
}
+/**
+ * Gets the current tag key based on the currently selected character or group
+ */
function getTagKey() {
if (selected_group && menu_type === 'group_edit') {
return selected_group;
@@ -442,6 +487,12 @@ function selectTag(event, ui, listSelector, { tagListOptions = {} } = {}) {
return false;
}
+/**
+ * Get a list of existing tags matching a list of provided new tag names
+ *
+ * @param {string[]} new_tags - A list of strings representing tag names
+ * @returns List of existing tags
+ */
function getExistingTags(new_tags) {
let existing_tags = [];
for (let tag of new_tags) {
@@ -495,11 +546,18 @@ async function importTags(imported_char) {
return false;
}
+/**
+ * Creates a new tag with default properties and a randomly generated id
+ *
+ * @param {string} tagName - name of the tag
+ * @returns {Tag}
+ */
function createNewTag(tagName) {
const tag = {
id: uuidv4(),
name: tagName,
folder_type: TAG_FOLDER_DEFAULT_TYPE,
+ filter_state: DEFAULT_FILTER_STATE,
sort_order: tags.length,
color: '',
color2: '',
@@ -520,8 +578,8 @@ function createNewTag(tagName) {
/**
* @typedef {object} PrintTagListOptions - Optional parameters for printing the tag list.
- * @property {Array|function(): Array} [tags=undefined] - Optional override of tags that should be printed. Those will not be sorted. If no supplied, tags for the relevant character are printed. Can also be a function that returns the tags.
- * @property {object} [addTag=undefined] - Optionally provide a tag that should be manually added to this print. Either to the overriden tag list or the found tags based on the entity/key. Will respect the tag exists check.
+ * @property {Tag[]|function(): Tag[]} [tags=undefined] - Optional override of tags that should be printed. Those will not be sorted. If no supplied, tags for the relevant character are printed. Can also be a function that returns the tags.
+ * @property {Tag} [addTag=undefined] - Optionally provide a tag that should be manually added to this print. Either to the overriden tag list or the found tags based on the entity/key. Will respect the tag exists check.
* @property {object|number|string} [forEntityOrKey=undefined] - Optional override for the chosen entity, otherwise the currently selected is chosen. Can be an entity with id property (character, group, tag), or directly an id or tag key.
* @property {boolean|string} [empty=true] - Whether the list should be initially empty. If a string string is provided, 'always' will always empty the list, otherwise it'll evaluate to a boolean.
* @property {function(object): function} [tagActionSelector=undefined] - An optional override for the action property that can be assigned to each tag via tagOptions.
@@ -568,10 +626,11 @@ function printTagList(element, { tags = undefined, addTag = undefined, forEntity
}
/**
- * Appends a tag to the list element.
- * @param {JQuery} listElement List element.
- * @param {object} tag Tag object to append.
- * @param {TagOptions} [options={}] - Options for tag behavior.
+ * Appends a tag to the list element
+ *
+ * @param {JQuery} listElement - List element
+ * @param {Tag} tag - Tag object to append
+ * @param {TagOptions} [options={}] - Options for tag behavior
* @returns {void}
*/
function appendTagToList(listElement, tag, { removable = false, selectable = false, action = undefined, isGeneralList = false, skipExistsCheck = false } = {}) {
@@ -602,7 +661,7 @@ function appendTagToList(listElement, tag, { removable = false, selectable = fal
}
if (selectable && isGeneralList) {
- toggleTagThreeState(tagElement, { stateOverride: tag.filterState ?? DEFAULT_FILTER_STATE });
+ toggleTagThreeState(tagElement, { stateOverride: tag.filter_state ?? DEFAULT_FILTER_STATE });
}
if (selectable) {
@@ -628,7 +687,7 @@ function onTagFilterClick(listElement) {
let state = toggleTagThreeState($(this));
if (existingTag) {
- existingTag.filterState = state;
+ existingTag.filter_state = state;
saveSettingsDebounced();
}
@@ -636,6 +695,15 @@ function onTagFilterClick(listElement) {
runTagFilters(listElement);
}
+/**
+ * Toggle the filter state of a given tag element
+ *
+ * @param {JQuery} element - The jquery element representing the tag for which the state should be toggled
+ * @param {object} param1 - Optional parameters
+ * @param {string} [param1.stateOverride] - Optional state override to which the state should be toggled to. If not set, the state will move to the next one in the chain.
+ * @param {boolean} [param1.simulateClick] - Optionally specify that the state should not just be set on the html element, but actually achieved via triggering the "click" on it, which follows up with the general click handlers and reprinting
+ * @returns {string} The string representing the new state
+ */
function toggleTagThreeState(element, { stateOverride = undefined, simulateClick = false } = {}) {
const states = Object.keys(FILTER_STATES);
@@ -900,10 +968,23 @@ function makeTagListDraggable(tagContainer) {
});
}
+/**
+ * Sorts the given tags, returning a shallow copy of it
+ *
+ * @param {Tag[]} tags - The tags
+ * @returns {Tag[]} The sorted tags
+ */
function sortTags(tags) {
return tags.slice().sort(compareTagsForSort);
}
+/**
+ * Compares two given tags and returns the compare result
+ *
+ * @param {Tag} a - First tag
+ * @param {Tag} b - Second tag
+ * @returns The compare result
+ */
function compareTagsForSort(a, b) {
if (a.sort_order !== undefined && b.sort_order !== undefined) {
return a.sort_order - b.sort_order;
From 153f75cf1afd9727383a5f478f16968ae607b9d5 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sat, 30 Mar 2024 23:12:01 +0200
Subject: [PATCH 161/255] Add role selection to Character's Note depth prompt
---
public/index.html | 14 ++++++++++++--
public/script.js | 36 +++++++++++++++++++++++++++++++++--
public/scripts/group-chats.js | 6 ++++--
src/endpoints/characters.js | 6 +++++-
4 files changed, 55 insertions(+), 7 deletions(-)
diff --git a/public/index.html b/public/index.html
index d91958044..d28775b27 100644
--- a/public/index.html
+++ b/public/index.html
@@ -4505,7 +4505,7 @@
Character's Note
-
+
@@ -4513,7 +4513,17 @@
@ Depth
-
+
+
+
+ Role
+
+
+
+
+
+
+
Tokens: counting...
diff --git a/public/script.js b/public/script.js
index 5a2be853e..acb63bcf3 100644
--- a/public/script.js
+++ b/public/script.js
@@ -752,6 +752,7 @@ function getCurrentChatId() {
const talkativeness_default = 0.5;
export const depth_prompt_depth_default = 4;
+export const depth_prompt_role_default = 'system';
const per_page_default = 50;
var is_advanced_char_open = false;
@@ -778,6 +779,7 @@ let create_save = {
alternate_greetings: [],
depth_prompt_prompt: '',
depth_prompt_depth: depth_prompt_depth_default,
+ depth_prompt_role: depth_prompt_role_default,
extensions: {},
};
@@ -3114,12 +3116,14 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu
if (selected_group && Array.isArray(groupDepthPrompts) && groupDepthPrompts.length > 0) {
groupDepthPrompts.forEach((value, index) => {
- setExtensionPrompt('DEPTH_PROMPT_' + index, value.text, extension_prompt_types.IN_CHAT, value.depth, extension_settings.note.allowWIScan);
+ const role = getExtensionPromptRoleByName(value.role);
+ setExtensionPrompt('DEPTH_PROMPT_' + index, value.text, extension_prompt_types.IN_CHAT, value.depth, extension_settings.note.allowWIScan, role);
});
} else {
const depthPromptText = baseChatReplace(characters[this_chid].data?.extensions?.depth_prompt?.prompt?.trim(), name1, name2) || '';
const depthPromptDepth = characters[this_chid].data?.extensions?.depth_prompt?.depth ?? depth_prompt_depth_default;
- setExtensionPrompt('DEPTH_PROMPT', depthPromptText, extension_prompt_types.IN_CHAT, depthPromptDepth, extension_settings.note.allowWIScan);
+ const depthPromptRole = getExtensionPromptRoleByName(characters[this_chid].data?.extensions?.depth_prompt?.role ?? depth_prompt_role_default);
+ setExtensionPrompt('DEPTH_PROMPT', depthPromptText, extension_prompt_types.IN_CHAT, depthPromptDepth, extension_settings.note.allowWIScan, depthPromptRole);
}
// Parse example messages
@@ -6697,6 +6701,7 @@ export function select_selected_character(chid) {
$('#scenario_pole').val(characters[chid].scenario);
$('#depth_prompt_prompt').val(characters[chid].data?.extensions?.depth_prompt?.prompt ?? '');
$('#depth_prompt_depth').val(characters[chid].data?.extensions?.depth_prompt?.depth ?? depth_prompt_depth_default);
+ $('#depth_prompt_role').val(characters[chid].data?.extensions?.depth_prompt?.role ?? depth_prompt_role_default);
$('#talkativeness_slider').val(characters[chid].talkativeness || talkativeness_default);
$('#mes_example_textarea').val(characters[chid].mes_example);
$('#selected_chat_pole').val(characters[chid].chat);
@@ -6767,6 +6772,7 @@ function select_rm_create() {
$('#scenario_pole').val(create_save.scenario);
$('#depth_prompt_prompt').val(create_save.depth_prompt_prompt);
$('#depth_prompt_depth').val(create_save.depth_prompt_depth);
+ $('#depth_prompt_role').val(create_save.depth_prompt_role);
$('#mes_example_textarea').val(create_save.mes_example);
$('#character_json_data').val('');
$('#avatar_div').css('display', 'flex');
@@ -6810,6 +6816,30 @@ export function setExtensionPrompt(key, value, position, depth, scan = false, ro
};
}
+/**
+ * Gets a enum value of the extension prompt role by its name.
+ * @param {string} roleName The name of the extension prompt role.
+ * @returns {number} The role id of the extension prompt.
+ */
+export function getExtensionPromptRoleByName(roleName) {
+ // If the role is already a valid number, return it
+ if (typeof roleName === 'number' && Object.values(extension_prompt_roles).includes(roleName)) {
+ return roleName;
+ }
+
+ switch (roleName) {
+ case 'system':
+ return extension_prompt_roles.SYSTEM;
+ case 'user':
+ return extension_prompt_roles.USER;
+ case 'assistant':
+ return extension_prompt_roles.ASSISTANT;
+ }
+
+ // Skill issue?
+ return extension_prompt_roles.SYSTEM;
+}
+
/**
* Removes all char A/N prompt injections from the chat.
* To clean up when switching from groups to solo and vice versa.
@@ -7425,6 +7455,7 @@ async function createOrEditCharacter(e) {
{ id: '#scenario_pole', callback: value => create_save.scenario = value },
{ id: '#depth_prompt_prompt', callback: value => create_save.depth_prompt_prompt = value },
{ id: '#depth_prompt_depth', callback: value => create_save.depth_prompt_depth = value, defaultValue: depth_prompt_depth_default },
+ { id: '#depth_prompt_role', callback: value => create_save.depth_prompt_role = value, defaultValue: depth_prompt_role_default },
{ id: '#mes_example_textarea', callback: value => create_save.mes_example = value },
{ id: '#character_json_data', callback: () => { } },
{ id: '#alternate_greetings_template', callback: value => create_save.alternate_greetings = value, defaultValue: [] },
@@ -8794,6 +8825,7 @@ jQuery(async function () {
'#talkativeness_slider': function () { create_save.talkativeness = Number($('#talkativeness_slider').val()); },
'#depth_prompt_prompt': function () { create_save.depth_prompt_prompt = String($('#depth_prompt_prompt').val()); },
'#depth_prompt_depth': function () { create_save.depth_prompt_depth = Number($('#depth_prompt_depth').val()); },
+ '#depth_prompt_role': function () { create_save.depth_prompt_role = String($('#depth_prompt_role').val()); },
};
Object.keys(elementsToUpdate).forEach(function (id) {
diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js
index 27708edc5..3b2f1c121 100644
--- a/public/scripts/group-chats.js
+++ b/public/scripts/group-chats.js
@@ -68,6 +68,7 @@ import {
depth_prompt_depth_default,
loadItemizedPrompts,
animation_duration,
+ depth_prompt_role_default,
} from '../script.js';
import { printTagList, createTagMapFromList, applyTagsOnCharacterSelect, tag_map } from './tags.js';
import { FILTER_TYPES, FilterHelper } from './filters.js';
@@ -284,7 +285,7 @@ export function findGroupMemberId(arg) {
* Gets depth prompts for group members.
* @param {string} groupId Group ID
* @param {number} characterId Current Character ID
- * @returns {{depth: number, text: string}[]} Array of depth prompts
+ * @returns {{depth: number, text: string, role: string}[]} Array of depth prompts
*/
export function getGroupDepthPrompts(groupId, characterId) {
if (!groupId) {
@@ -320,9 +321,10 @@ export function getGroupDepthPrompts(groupId, characterId) {
const depthPromptText = baseChatReplace(character.data?.extensions?.depth_prompt?.prompt?.trim(), name1, character.name) || '';
const depthPromptDepth = character.data?.extensions?.depth_prompt?.depth ?? depth_prompt_depth_default;
+ const depthPromptRole = character.data?.extensions?.depth_prompt?.role ?? depth_prompt_role_default;
if (depthPromptText) {
- depthPrompts.push({ text: depthPromptText, depth: depthPromptDepth });
+ depthPrompts.push({ text: depthPromptText, depth: depthPromptDepth, role: depthPromptRole });
}
}
diff --git a/src/endpoints/characters.js b/src/endpoints/characters.js
index 613f88c79..45d2896ac 100644
--- a/src/endpoints/characters.js
+++ b/src/endpoints/characters.js
@@ -210,7 +210,8 @@ function convertToV2(char) {
creator: char.creator,
tags: char.tags,
depth_prompt_prompt: char.depth_prompt_prompt,
- depth_prompt_response: char.depth_prompt_response,
+ depth_prompt_depth: char.depth_prompt_depth,
+ depth_prompt_role: char.depth_prompt_role,
});
result.chat = char.chat ?? humanizedISO8601DateTime();
@@ -331,9 +332,12 @@ function charaFormatData(data) {
// Spec extension: depth prompt
const depth_default = 4;
+ const role_default = 'system';
const depth_value = !isNaN(Number(data.depth_prompt_depth)) ? Number(data.depth_prompt_depth) : depth_default;
+ const role_value = data.depth_prompt_role ?? role_default;
_.set(char, 'data.extensions.depth_prompt.prompt', data.depth_prompt_prompt ?? '');
_.set(char, 'data.extensions.depth_prompt.depth', depth_value);
+ _.set(char, 'data.extensions.depth_prompt.role', role_value);
//_.set(char, 'data.extensions.create_date', humanizedISO8601DateTime());
//_.set(char, 'data.extensions.avatar', 'none');
//_.set(char, 'data.extensions.chat', data.ch_name + ' - ' + humanizedISO8601DateTime());
From 32cde5f13f65a690bd9ecf65dd1ee075983e697b Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sat, 30 Mar 2024 23:20:46 +0200
Subject: [PATCH 162/255] Fix tag map cleanup on tag deletion, run lint
---
public/scripts/tags.js | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/public/scripts/tags.js b/public/scripts/tags.js
index a33b338f0..06a8fbc66 100644
--- a/public/scripts/tags.js
+++ b/public/scripts/tags.js
@@ -15,7 +15,7 @@ import {
import { FILTER_TYPES, FILTER_STATES, DEFAULT_FILTER_STATE, isFilterState, FilterHelper } from './filters.js';
import { groupCandidatesFilter, groups, selected_group } from './group-chats.js';
-import { download, onlyUnique, parseJsonFile, uuidv4, getSortableDelay, debounce } from './utils.js';
+import { download, onlyUnique, parseJsonFile, uuidv4, getSortableDelay } from './utils.js';
import { power_user } from './power-user.js';
export {
@@ -54,12 +54,12 @@ export const tag_filter_types = {
};
const ACTIONABLE_TAGS = {
- FAV: { id: "1", sort_order: 1, name: 'Show only favorites', color: 'rgba(255, 255, 0, 0.5)', action: filterByFav, icon: 'fa-solid fa-star', class: 'filterByFavorites' },
- GROUP: { id: "0", sort_order: 2, name: 'Show only groups', color: 'rgba(100, 100, 100, 0.5)', action: filterByGroups, icon: 'fa-solid fa-users', class: 'filterByGroups' },
- FOLDER: { id: "4", sort_order: 3, name: 'Always show folders', color: 'rgba(120, 120, 120, 0.5)', action: filterByFolder, icon: 'fa-solid fa-folder-plus', class: 'filterByFolder' },
- VIEW: { id: "2", sort_order: 4, name: 'Manage tags', color: 'rgba(150, 100, 100, 0.5)', action: onViewTagsListClick, icon: 'fa-solid fa-gear', class: 'manageTags' },
- HINT: { id: "3", sort_order: 5, name: 'Show Tag List', color: 'rgba(150, 100, 100, 0.5)', action: onTagListHintClick, icon: 'fa-solid fa-tags', class: 'showTagList' },
- UNFILTER: { id: "5", sort_order: 6, name: 'Clear all filters', action: onClearAllFiltersClick, icon: 'fa-solid fa-filter-circle-xmark', class: 'clearAllFilters' },
+ FAV: { id: '1', sort_order: 1, name: 'Show only favorites', color: 'rgba(255, 255, 0, 0.5)', action: filterByFav, icon: 'fa-solid fa-star', class: 'filterByFavorites' },
+ GROUP: { id: '0', sort_order: 2, name: 'Show only groups', color: 'rgba(100, 100, 100, 0.5)', action: filterByGroups, icon: 'fa-solid fa-users', class: 'filterByGroups' },
+ FOLDER: { id: '4', sort_order: 3, name: 'Always show folders', color: 'rgba(120, 120, 120, 0.5)', action: filterByFolder, icon: 'fa-solid fa-folder-plus', class: 'filterByFolder' },
+ VIEW: { id: '2', sort_order: 4, name: 'Manage tags', color: 'rgba(150, 100, 100, 0.5)', action: onViewTagsListClick, icon: 'fa-solid fa-gear', class: 'manageTags' },
+ HINT: { id: '3', sort_order: 5, name: 'Show Tag List', color: 'rgba(150, 100, 100, 0.5)', action: onTagListHintClick, icon: 'fa-solid fa-tags', class: 'showTagList' },
+ UNFILTER: { id: '5', sort_order: 6, name: 'Clear all filters', action: onClearAllFiltersClick, icon: 'fa-solid fa-filter-circle-xmark', class: 'clearAllFilters' },
};
const InListActionable = {
@@ -983,7 +983,7 @@ function sortTags(tags) {
*
* @param {Tag} a - First tag
* @param {Tag} b - Second tag
- * @returns The compare result
+ * @returns {number} The compare result
*/
function compareTagsForSort(a, b) {
if (a.sort_order !== undefined && b.sort_order !== undefined) {
@@ -1196,7 +1196,7 @@ function onTagDeleteClick() {
const id = $(this).closest('.tag_view_item').attr('id');
for (const key of Object.keys(tag_map)) {
- tag_map[key] = tag_map[key].filter(x => x.id !== id);
+ tag_map[key] = tag_map[key].filter(x => x !== id);
}
const index = tags.findIndex(x => x.id === id);
tags.splice(index, 1);
From 8c5a81baff8407ce9b5e0274ce1d42c56c9b9511 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sat, 30 Mar 2024 23:23:14 +0200
Subject: [PATCH 163/255] Only transition actionable tag filters
---
public/css/tags.css | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/public/css/tags.css b/public/css/tags.css
index b919b8300..93cc8b284 100644
--- a/public/css/tags.css
+++ b/public/css/tags.css
@@ -139,11 +139,13 @@
cursor: pointer;
opacity: 0.6;
filter: brightness(0.8);
+}
+
+.rm_tag_filter .tag.actionable {
transition: opacity 200ms;
}
.rm_tag_filter .tag:hover {
-
opacity: 1;
filter: brightness(1);
}
From 4d0cef75165178fb28d5b14c91af907321a9a7d0 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sat, 30 Mar 2024 23:57:49 +0200
Subject: [PATCH 164/255] Add gitkeep to themes
---
public/themes/.gitkeep | 0
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 public/themes/.gitkeep
diff --git a/public/themes/.gitkeep b/public/themes/.gitkeep
new file mode 100644
index 000000000..e69de29bb
From a96bb4050595ab1e1c9d98cede6fedab09d522eb Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sun, 31 Mar 2024 00:27:12 +0200
Subject: [PATCH 165/255] #1991 Add API key for llama.cpp
---
public/index.html | 9 +++++++
public/script.js | 1 +
public/scripts/secrets.js | 2 ++
src/additional-headers.js | 55 ++++++++++++++++-----------------------
src/endpoints/secrets.js | 1 +
5 files changed, 35 insertions(+), 33 deletions(-)
diff --git a/public/index.html b/public/index.html
index d28775b27..afd279d6e 100644
--- a/public/index.html
+++ b/public/index.html
@@ -2151,6 +2151,15 @@
ggerganov/llama.cpp (inference server)
+
API key (optional)
+
+
+
+
+
+
+ For privacy reasons, your API key will be hidden after you reload the page.
+
API URL
Example: http://127.0.0.1:8080
diff --git a/public/script.js b/public/script.js
index acb63bcf3..61669e028 100644
--- a/public/script.js
+++ b/public/script.js
@@ -8991,6 +8991,7 @@ jQuery(async function () {
{ id: 'api_key_dreamgen', secret: SECRET_KEYS.DREAMGEN },
{ id: 'api_key_openrouter-tg', secret: SECRET_KEYS.OPENROUTER },
{ id: 'api_key_koboldcpp', secret: SECRET_KEYS.KOBOLDCPP },
+ { id: 'api_key_llamacpp', secret: SECRET_KEYS.LLAMACPP },
];
for (const key of keys) {
diff --git a/public/scripts/secrets.js b/public/scripts/secrets.js
index 9ba9dcedd..a6d82e5e7 100644
--- a/public/scripts/secrets.js
+++ b/public/scripts/secrets.js
@@ -22,6 +22,7 @@ export const SECRET_KEYS = {
OOBA: 'api_key_ooba',
NOMICAI: 'api_key_nomicai',
KOBOLDCPP: 'api_key_koboldcpp',
+ LLAMACPP: 'api_key_llamacpp',
};
const INPUT_MAP = {
@@ -45,6 +46,7 @@ const INPUT_MAP = {
[SECRET_KEYS.DREAMGEN]: '#api_key_dreamgen',
[SECRET_KEYS.NOMICAI]: '#api_key_nomicai',
[SECRET_KEYS.KOBOLDCPP]: '#api_key_koboldcpp',
+ [SECRET_KEYS.LLAMACPP]: '#api_key_llamacpp',
};
async function clearSecret() {
diff --git a/src/additional-headers.js b/src/additional-headers.js
index e69872bf3..4ac30d25c 100644
--- a/src/additional-headers.js
+++ b/src/additional-headers.js
@@ -60,6 +60,14 @@ function getTabbyHeaders() {
}) : {};
}
+function getLlamaCppHeaders() {
+ const apiKey = readSecret(SECRET_KEYS.LLAMACPP);
+
+ return apiKey ? ({
+ 'Authorization': `Bearer ${apiKey}`,
+ }) : {};
+}
+
function getOobaHeaders() {
const apiKey = readSecret(SECRET_KEYS.OOBA);
@@ -93,40 +101,21 @@ function getOverrideHeaders(urlHost) {
* @param {string|null} server API server for new request
*/
function setAdditionalHeaders(request, args, server) {
- let headers;
+ const headerGetters = {
+ [TEXTGEN_TYPES.MANCER]: getMancerHeaders,
+ [TEXTGEN_TYPES.APHRODITE]: getAphroditeHeaders,
+ [TEXTGEN_TYPES.TABBY]: getTabbyHeaders,
+ [TEXTGEN_TYPES.TOGETHERAI]: getTogetherAIHeaders,
+ [TEXTGEN_TYPES.OOBA]: getOobaHeaders,
+ [TEXTGEN_TYPES.INFERMATICAI]: getInfermaticAIHeaders,
+ [TEXTGEN_TYPES.DREAMGEN]: getDreamGenHeaders,
+ [TEXTGEN_TYPES.OPENROUTER]: getOpenRouterHeaders,
+ [TEXTGEN_TYPES.KOBOLDCPP]: getKoboldCppHeaders,
+ [TEXTGEN_TYPES.LLAMACPP]: getLlamaCppHeaders,
+ };
- switch (request.body.api_type) {
- case TEXTGEN_TYPES.MANCER:
- headers = getMancerHeaders();
- break;
- case TEXTGEN_TYPES.APHRODITE:
- headers = getAphroditeHeaders();
- break;
- case TEXTGEN_TYPES.TABBY:
- headers = getTabbyHeaders();
- break;
- case TEXTGEN_TYPES.TOGETHERAI:
- headers = getTogetherAIHeaders();
- break;
- case TEXTGEN_TYPES.OOBA:
- headers = getOobaHeaders();
- break;
- case TEXTGEN_TYPES.INFERMATICAI:
- headers = getInfermaticAIHeaders();
- break;
- case TEXTGEN_TYPES.DREAMGEN:
- headers = getDreamGenHeaders();
- break;
- case TEXTGEN_TYPES.OPENROUTER:
- headers = getOpenRouterHeaders();
- break;
- case TEXTGEN_TYPES.KOBOLDCPP:
- headers = getKoboldCppHeaders();
- break;
- default:
- headers = {};
- break;
- }
+ const getHeaders = headerGetters[request.body.api_type];
+ const headers = getHeaders ? getHeaders() : {};
if (typeof server === 'string' && server.length > 0) {
try {
diff --git a/src/endpoints/secrets.js b/src/endpoints/secrets.js
index 10ba9e556..55c5df008 100644
--- a/src/endpoints/secrets.js
+++ b/src/endpoints/secrets.js
@@ -34,6 +34,7 @@ const SECRET_KEYS = {
DREAMGEN: 'api_key_dreamgen',
NOMICAI: 'api_key_nomicai',
KOBOLDCPP: 'api_key_koboldcpp',
+ LLAMACPP: 'api_key_llamacpp',
};
// These are the keys that are safe to expose, even if allowKeysExposure is false
From c58fcfd4da6902541fccab5457c824677d6c9056 Mon Sep 17 00:00:00 2001
From: Wolfsblvt
Date: Sun, 31 Mar 2024 00:21:33 +0100
Subject: [PATCH 166/255] Fix actionable filters and bogus selection again
- Fix actionable filters and their toggle state
- Make bogus folders clickable again
- Even more code documentation
---
public/scripts/filters.js | 24 ++++++++++++++++--------
public/scripts/tags.js | 33 +++++++++++++++++++++++++++++----
2 files changed, 45 insertions(+), 12 deletions(-)
diff --git a/public/scripts/filters.js b/public/scripts/filters.js
index 92ed6992c..2743ccdba 100644
--- a/public/scripts/filters.js
+++ b/public/scripts/filters.js
@@ -2,8 +2,8 @@ import { fuzzySearchCharacters, fuzzySearchGroups, fuzzySearchPersonas, fuzzySea
import { tag_map } from './tags.js';
/**
- * The filter types.
- * @type {Object.}
+ * The filter types
+ * @type {{ SEARCH: string, TAG: string, FOLDER: string, FAV: string, GROUP: string, WORLD_INFO_SEARCH: string, PERSONA_SEARCH: string, [key: string]: string }}
*/
export const FILTER_TYPES = {
SEARCH: 'search',
@@ -16,26 +16,34 @@ export const FILTER_TYPES = {
};
/**
- * The filter states.
- * @type {Object.}
+ * @typedef FilterState One of the filter states
+ * @property {string} key - The key of the state
+ * @property {string} class - The css class for this state
+ */
+
+/**
+ * The filter states
+ * @type {{ SELECTED: FilterState, EXCLUDED: FilterState, UNDEFINED: FilterState, [key: string]: FilterState }}
*/
export const FILTER_STATES = {
SELECTED: { key: 'SELECTED', class: 'selected' },
EXCLUDED: { key: 'EXCLUDED', class: 'excluded' },
UNDEFINED: { key: 'UNDEFINED', class: 'undefined' },
};
+/** @type {string} the default filter state of `FILTER_STATES` */
export const DEFAULT_FILTER_STATE = FILTER_STATES.UNDEFINED.key;
/**
* Robust check if one state equals the other. It does not care whether it's the state key or the state value object.
- * @param {Object} a First state
- * @param {Object} b Second state
+ * @param {FilterState|string} a First state
+ * @param {FilterState|string} b Second state
+ * @returns {boolean}
*/
export function isFilterState(a, b) {
const states = Object.keys(FILTER_STATES);
- const aKey = states.includes(a) ? a : states.find(key => FILTER_STATES[key] === a);
- const bKey = states.includes(b) ? b : states.find(key => FILTER_STATES[key] === b);
+ const aKey = typeof a == 'string' && states.includes(a) ? a : states.find(key => FILTER_STATES[key] === a);
+ const bKey = typeof b == 'string' && states.includes(b) ? b : states.find(key => FILTER_STATES[key] === b);
return aKey === bKey;
}
diff --git a/public/scripts/tags.js b/public/scripts/tags.js
index 06a8fbc66..ac2e722da 100644
--- a/public/scripts/tags.js
+++ b/public/scripts/tags.js
@@ -53,6 +53,10 @@ export const tag_filter_types = {
group_member: 1,
};
+/**
+ * @type {{ FAV: Tag, GROUP: Tag, FOLDER: Tag, VIEW: Tag, HINT: Tag, UNFILTER: Tag }}
+ * A collection of global actional tags for the filter panel
+ * */
const ACTIONABLE_TAGS = {
FAV: { id: '1', sort_order: 1, name: 'Show only favorites', color: 'rgba(255, 255, 0, 0.5)', action: filterByFav, icon: 'fa-solid fa-star', class: 'filterByFavorites' },
GROUP: { id: '0', sort_order: 2, name: 'Show only groups', color: 'rgba(100, 100, 100, 0.5)', action: filterByGroups, icon: 'fa-solid fa-users', class: 'filterByGroups' },
@@ -62,9 +66,11 @@ const ACTIONABLE_TAGS = {
UNFILTER: { id: '5', sort_order: 6, name: 'Clear all filters', action: onClearAllFiltersClick, icon: 'fa-solid fa-filter-circle-xmark', class: 'clearAllFilters' },
};
+/** @type {{[key: string]: Tag}} An optional list of actionables that can be utilized by extensions */
const InListActionable = {
};
+/** @type {Tag[]} A list of default tags */
const DEFAULT_TAGS = [
{ id: uuidv4(), name: 'Plain Text', create_date: Date.now() },
{ id: uuidv4(), name: 'OpenAI', create_date: Date.now() },
@@ -74,6 +80,20 @@ const DEFAULT_TAGS = [
{ id: uuidv4(), name: 'AliChat', create_date: Date.now() },
];
+/**
+ * @typedef FolderType Bogus folder type
+ * @property {string} icon - The icon as a string representation / character
+ * @property {string} class - The class to apply to the folder type element
+ * @property {string} [fa_icon] - Optional font-awesome icon class representing the folder type element
+ * @property {string} [tooltip] - Optional tooltip for the folder type element
+ * @property {string} [color] - Optional color for the folder type element
+ * @property {string} [size] - A string representation of the size that the folder type element should be
+ */
+
+/**
+ * @type {{ OPEN: FolderType, CLOSED: FolderType, NONE: FolderType, [key: string]: FolderType }}
+ * The list of all possible tag folder types
+ */
const TAG_FOLDER_TYPES = {
OPEN: { icon: '✔', class: 'folder_open', fa_icon: 'fa-folder-open', tooltip: 'Open Folder (Show all characters even if not selected)', color: 'green', size: '1' },
CLOSED: { icon: '👁', class: 'folder_closed', fa_icon: 'fa-eye-slash', tooltip: 'Closed Folder (Hide all characters unless selected)', color: 'lightgoldenrodyellow', size: '0.7' },
@@ -92,6 +112,7 @@ const TAG_FOLDER_DEFAULT_TYPE = 'NONE';
* @property {string} [color2] - The foreground color of the tag
* @property {number} [create_date] - A number representing the date when this tag was created
*
+ * @property {function} [action] - An optional function that gets executed when this tag is an actionable tag and is clicked on.
* @property {string} [class] - An optional css class added to the control representing this tag when printed. Used for custom tags in the filters.
* @property {string} [icon] - An optional css class of an icon representing this tag when printed. This will replace the tag name with the icon. Used for custom tags in the filters.
*/
@@ -234,7 +255,7 @@ function chooseBogusFolder(source, tagId, remove = false) {
const FILTER_SELECTOR = ($(source).closest('#rm_characters_block') ?? $(source).closest('#rm_group_chats_block')).find('.rm_tag_filter');
const tagElement = $(FILTER_SELECTOR).find(`.tag[id=${tagId}]`);
- toggleTagThreeState(tagElement, { stateOverride: DEFAULT_FILTER_STATE, simulateClick: true });
+ toggleTagThreeState(tagElement, { stateOverride: !remove ? FILTER_STATES.SELECTED : DEFAULT_FILTER_STATE, simulateClick: true });
}
/**
@@ -271,6 +292,7 @@ function getTagBlock(tag, entities, hidden = 0) {
*/
function filterByFav(filterHelper) {
const state = toggleTagThreeState($(this));
+ ACTIONABLE_TAGS.FAV.filter_state = state;
filterHelper.setFilterData(FILTER_TYPES.FAV, state);
}
@@ -280,6 +302,7 @@ function filterByFav(filterHelper) {
*/
function filterByGroups(filterHelper) {
const state = toggleTagThreeState($(this));
+ ACTIONABLE_TAGS.GROUP.filter_state = state;
filterHelper.setFilterData(FILTER_TYPES.GROUP, state);
}
@@ -289,6 +312,7 @@ function filterByGroups(filterHelper) {
*/
function filterByFolder(filterHelper) {
const state = toggleTagThreeState($(this));
+ ACTIONABLE_TAGS.FOLDER.filter_state = state;
filterHelper.setFilterData(FILTER_TYPES.FOLDER, state);
}
@@ -660,7 +684,8 @@ function appendTagToList(listElement, tag, { removable = false, selectable = fal
tagElement.find('.tag_name').text('').attr('title', tag.name).addClass(tag.icon);
}
- if (selectable && isGeneralList) {
+ // If this is a tag for a general list and its either selectable or actionable, lets mark its current state
+ if ((selectable || action) && isGeneralList) {
toggleTagThreeState(tagElement, { stateOverride: tag.filter_state ?? DEFAULT_FILTER_STATE });
}
@@ -700,7 +725,7 @@ function onTagFilterClick(listElement) {
*
* @param {JQuery} element - The jquery element representing the tag for which the state should be toggled
* @param {object} param1 - Optional parameters
- * @param {string} [param1.stateOverride] - Optional state override to which the state should be toggled to. If not set, the state will move to the next one in the chain.
+ * @param {import('./filters.js').FilterState|string} [param1.stateOverride] - Optional state override to which the state should be toggled to. If not set, the state will move to the next one in the chain.
* @param {boolean} [param1.simulateClick] - Optionally specify that the state should not just be set on the html element, but actually achieved via triggering the "click" on it, which follows up with the general click handlers and reprinting
* @returns {string} The string representing the new state
*/
@@ -713,7 +738,7 @@ function toggleTagThreeState(element, { stateOverride = undefined, simulateClick
return index !== -1 ? index : states.indexOf(fallback);
}
- const overrideKey = states.includes(stateOverride) ? stateOverride : Object.keys(FILTER_STATES).find(key => FILTER_STATES[key] === stateOverride);
+ const overrideKey = typeof stateOverride == 'string' && states.includes(stateOverride) ? stateOverride : Object.keys(FILTER_STATES).find(key => FILTER_STATES[key] === stateOverride);
const currentStateIndex = getStateIndex(element.attr('data-toggle-state'), DEFAULT_FILTER_STATE);
const targetStateIndex = overrideKey !== undefined ? getStateIndex(overrideKey, DEFAULT_FILTER_STATE) : (currentStateIndex + 1) % states.length;
From 0a71d09fe16e50bc3b70537095beb7d49a31fabd Mon Sep 17 00:00:00 2001
From: Hirose <86906598+HiroseKoichi@users.noreply.github.com>
Date: Sat, 30 Mar 2024 17:37:06 -0500
Subject: [PATCH 167/255] Fix default instruct format in settings.json
Updated the default Alpaca instruct in settings.json to match the latest PR.
---
default/settings.json | 16 +++++++++++-----
1 file changed, 11 insertions(+), 5 deletions(-)
diff --git a/default/settings.json b/default/settings.json
index 3c8faadbf..dbd731c45 100644
--- a/default/settings.json
+++ b/default/settings.json
@@ -155,17 +155,23 @@
"system_prompt": "Below is an instruction that describes a task. Write a response that appropriately completes the request.\n\nWrite {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n",
"input_sequence": "### Instruction:",
"output_sequence": "### Response:",
- "first_output_sequence": "",
"last_output_sequence": "",
- "system_sequence_prefix": "",
- "system_sequence_suffix": "",
+ "system_sequence": "### Input:",
"stop_sequence": "",
- "separator_sequence": "",
"wrap": true,
"macro": true,
"names": false,
"names_force_groups": true,
- "activation_regex": ""
+ "activation_regex": "",
+ "system_sequence_prefix": "",
+ "system_sequence_suffix": "",
+ "first_output_sequence": "",
+ "skip_examples": false,
+ "output_suffix": "\n\n",
+ "input_suffix": "\n\n",
+ "system_suffix": "\n\n",
+ "user_alignment_message": "",
+ "system_same_as_user": false
},
"default_context": "Default",
"context": {
From b0fb50aef6d6be42bc9b66e0db0e913bc140992d Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sun, 31 Mar 2024 10:30:06 +0300
Subject: [PATCH 168/255] Fix example dialogue separator being skipped in
instruct mode
---
public/scripts/instruct-mode.js | 40 +++++++++++++++++++--------------
1 file changed, 23 insertions(+), 17 deletions(-)
diff --git a/public/scripts/instruct-mode.js b/public/scripts/instruct-mode.js
index de8b93c5b..9c75977e1 100644
--- a/public/scripts/instruct-mode.js
+++ b/public/scripts/instruct-mode.js
@@ -365,8 +365,10 @@ export function formatInstructModeSystemPrompt(systemPrompt) {
* @returns {string[]} Formatted example messages string.
*/
export function formatInstructModeExamples(mesExamplesArray, name1, name2) {
+ const blockHeading = power_user.context.example_separator ? power_user.context.example_separator + '\n' : '';
+
if (power_user.instruct.skip_examples) {
- return mesExamplesArray.map(x => x.replace(/\n/i, ''));
+ return mesExamplesArray.map(x => x.replace(/\n/i, blockHeading));
}
const includeNames = power_user.instruct.names || (!!selected_group && power_user.instruct.names_force_groups);
@@ -387,28 +389,32 @@ export function formatInstructModeExamples(mesExamplesArray, name1, name2) {
}
const separator = power_user.instruct.wrap ? '\n' : '';
- const parsedExamples = [];
+ const formattedExamples = [];
for (const item of mesExamplesArray) {
const cleanedItem = item.replace(//i, '{Example Dialogue:}').replace(/\r/gm, '');
const blockExamples = parseExampleIntoIndividual(cleanedItem);
- parsedExamples.push(...blockExamples);
+
+ if (blockExamples.length === 0) {
+ continue;
+ }
+
+ if (blockHeading) {
+ formattedExamples.push(power_user.blockHeading);
+ }
+
+ for (const example of blockExamples) {
+ const prefix = example.name == 'example_user' ? inputPrefix : outputPrefix;
+ const suffix = example.name == 'example_user' ? inputSuffix : outputSuffix;
+ const name = example.name == 'example_user' ? name1 : name2;
+ const messageContent = includeNames ? `${name}: ${example.content}` : example.content;
+ const formattedMessage = [prefix, messageContent + suffix].filter(x => x).join(separator);
+ formattedExamples.push(formattedMessage);
+ }
}
- // Not something we can parse, return as is
- if (!Array.isArray(parsedExamples) || parsedExamples.length === 0) {
- return mesExamplesArray;
- }
-
- const formattedExamples = [];
-
- for (const example of parsedExamples) {
- const prefix = example.name == 'example_user' ? inputPrefix : outputPrefix;
- const suffix = example.name == 'example_user' ? inputSuffix : outputSuffix;
- const name = example.name == 'example_user' ? name1 : name2;
- const messageContent = includeNames ? `${name}: ${example.content}` : example.content;
- const formattedMessage = [prefix, messageContent + suffix].filter(x => x).join(separator);
- formattedExamples.push(formattedMessage);
+ if (formattedExamples.length === 0) {
+ return mesExamplesArray.map(x => x.replace(/\n/i, blockHeading));
}
return formattedExamples;
From e99baac9c041098b79b632a7d2aa7cc0792ad89c Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sun, 31 Mar 2024 10:48:23 +0300
Subject: [PATCH 169/255] Adjust drilldown arrow style This thing was huge
---
public/css/tags.css | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/public/css/tags.css b/public/css/tags.css
index 93cc8b284..9a3e02064 100644
--- a/public/css/tags.css
+++ b/public/css/tags.css
@@ -232,18 +232,16 @@
.rm_tag_bogus_drilldown .tag:not(:first-child) {
position: relative;
- margin-left: calc(var(--mainFontSize) * 2);
+ margin-left: 1em;
}
.rm_tag_bogus_drilldown .tag:not(:first-child)::before {
+ font-family: 'Font Awesome 6 Free';
+ content: "\f054";
position: absolute;
- left: calc(var(--mainFontSize) * -2);
- top: -1px;
- content: "\21E8";
- font-size: calc(var(--mainFontSize) * 2);
+ left: -1em;
+ top: auto;
color: var(--SmartThemeBodyColor);
- line-height: calc(var(--mainFontSize) * 1.3);
- text-align: center;
text-shadow: 1px 1px 0px black,
-1px -1px 0px black,
-1px 1px 0px black,
From b2f42f1b9f17a1a28533e719116893a5d3f15cbb Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sun, 31 Mar 2024 10:54:23 +0300
Subject: [PATCH 170/255] Close context menu immediately when clicked on mass
tag
---
public/scripts/BulkEditOverlay.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/public/scripts/BulkEditOverlay.js b/public/scripts/BulkEditOverlay.js
index f3df8156d..fccf12da3 100644
--- a/public/scripts/BulkEditOverlay.js
+++ b/public/scripts/BulkEditOverlay.js
@@ -853,6 +853,7 @@ class BulkEditOverlay {
*/
handleContextMenuTag = () => {
CharacterContextMenu.tag(this.selectedCharacters);
+ this.browseState();
};
addStateChangeCallback = callback => this.stateChangeCallbacks.push(callback);
From 3b8188877fe0aeb22d1fc0c2c18f17f3f3b7b6c2 Mon Sep 17 00:00:00 2001
From: deffcolony <61471128+deffcolony@users.noreply.github.com>
Date: Sun, 31 Mar 2024 12:47:51 +0200
Subject: [PATCH 171/255] update ISSUE_TEMPLATE
---
.github/ISSUE_TEMPLATE/bug-report.yml | 10 +++++-----
.github/ISSUE_TEMPLATE/feature-request.yml | 6 +++---
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml
index 58f8ae2eb..53be9963f 100644
--- a/.github/ISSUE_TEMPLATE/bug-report.yml
+++ b/.github/ISSUE_TEMPLATE/bug-report.yml
@@ -69,16 +69,16 @@ body:
required: false
- type: checkboxes
- id: idiot-check
+ id: user-check
attributes:
label: Please tick the boxes
- description: Before submitting, please ensure that
+ description: Before submitting, please ensure that you have completed the following checklist
options:
- - label: You have explained the issue clearly, and included all relevant info
+ - label: I have explained the issue clearly, and I included all relevant info
required: true
- - label: You've checked that this [issue hasn't already been raised](https://github.com/SillyTavern/SillyTavern/issues?q=is%3Aissue)
+ - label: I have checked that this [issue hasn't already been raised](https://github.com/SillyTavern/SillyTavern/issues?q=is%3Aissue)
required: true
- - label: You've checked the [docs](https://docs.sillytavern.app/) 
+ - label: I have checked the [docs](https://docs.sillytavern.app/) 
required: true
- type: markdown
diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml
index 4180f93da..761dec374 100644
--- a/.github/ISSUE_TEMPLATE/feature-request.yml
+++ b/.github/ISSUE_TEMPLATE/feature-request.yml
@@ -15,7 +15,7 @@ body:
- 'No'
- 'Yes'
validations:
- required: false
+ required: true
# Field 2 - Is it bug-related
- type: textarea
@@ -71,12 +71,12 @@ body:
- type: dropdown
id: canImplement
attributes:
- label: Is this something you would be keen to implement?
+ label: Are you willing to test this on staging/unstable branch if this is implemented?
description: Are you raising this ticket in order to get an issue number for your PR?
options:
- 'No'
- 'Maybe'
- - 'Yes!'
+ - 'Yes'
validations:
required: false
From 39f9ba0ef5818edf96bb91d6bc624e042b094e14 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sun, 31 Mar 2024 13:56:56 +0300
Subject: [PATCH 172/255] Update feature-request.yml
---
.github/ISSUE_TEMPLATE/feature-request.yml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml
index 761dec374..bbb97465e 100644
--- a/.github/ISSUE_TEMPLATE/feature-request.yml
+++ b/.github/ISSUE_TEMPLATE/feature-request.yml
@@ -67,12 +67,12 @@ body:
validations:
required: true
- # Field 7 - Can the user implement
+ # Field 7 - Can the user user test in staging
- type: dropdown
- id: canImplement
+ id: canTestStaging
attributes:
label: Are you willing to test this on staging/unstable branch if this is implemented?
- description: Are you raising this ticket in order to get an issue number for your PR?
+ description: Otherwise you'll need to wait until the next stable release after the feature is developed.
options:
- 'No'
- 'Maybe'
From 2e28f242519030e3175dc6f266116499ec5db22a Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sun, 31 Mar 2024 14:27:43 +0300
Subject: [PATCH 173/255] Fix summary controls disappearing when switching Chat
Completion sources
---
public/scripts/extensions/memory/index.js | 92 +------------------
.../scripts/extensions/memory/settings.html | 85 +++++++++++++++++
2 files changed, 89 insertions(+), 88 deletions(-)
create mode 100644 public/scripts/extensions/memory/settings.html
diff --git a/public/scripts/extensions/memory/index.js b/public/scripts/extensions/memory/index.js
index 19003052e..da0ae00e7 100644
--- a/public/scripts/extensions/memory/index.js
+++ b/public/scripts/extensions/memory/index.js
@@ -1,5 +1,5 @@
import { getStringHash, debounce, waitUntilCondition, extractAllWords } from '../../utils.js';
-import { getContext, getApiUrl, extension_settings, doExtrasFetch, modules } from '../../extensions.js';
+import { getContext, getApiUrl, extension_settings, doExtrasFetch, modules, renderExtensionTemplate } from '../../extensions.js';
import { animation_duration, eventSource, event_types, extension_prompt_roles, extension_prompt_types, generateQuietPrompt, is_send_press, saveSettingsDebounced, substituteParams } from '../../../script.js';
import { is_group_generating, selected_group } from '../../group-chats.js';
import { registerSlashCommand } from '../../slash-commands.js';
@@ -98,8 +98,8 @@ function onSummarySourceChange(event) {
}
function switchSourceControls(value) {
- $('#memory_settings [data-source]').each((_, element) => {
- const source = $(element).data('source');
+ $('#memory_settings [data-summary-source]').each((_, element) => {
+ const source = $(element).data('summary-source');
$(element).toggle(source === value);
});
}
@@ -581,91 +581,7 @@ function setupListeners() {
jQuery(function () {
function addExtensionControls() {
- const settingsHtml = `
-
-
-
-
Summarize
-
-
-
-
- Summarize with:
-
-
-
-
-
-
- Current summary:
-
Restore Previous
-
-
-
-
-
-
- Summarize now
-
- Pause
- No WI/AN
-
-
-
-
- Summary Settings
-
-
-
-
- Insertion Template
-
-
- Injection Position
-
-
-
- Before Main Prompt / Story String
-
-
-
- After Main Prompt / Story String
-
-
-
- In-chat @ Depth
- as
-
-
-
-
-
-
-
-
-
-
-
- Summary Prompt
-
-
-
- Summary length ( words)
-
- Update every messages
- 0 = disable
-
- Update every words
- 0 = disable
-
- If both sliders are non-zero, then both will trigger summary updates a their respective intervals.
-
+
+
+ Before Main Prompt / Story String
+
+
+
+ After Main Prompt / Story String
+
+
+
+ In-chat @ Depth
+ as
+
+
+
+
+
+
+
+
+
+
+
+ Summary Prompt
+
+
+ Summary length ( words)
+
+ Update every messages
+ 0 = disable
+
+ Update every words
+ 0 = disable
+
+ If both sliders are non-zero, then both will trigger summary updates a their respective intervals.
+
+
+
+
+
+
From ad4269f4764a856879d2206bf3e499f33bbbdc58 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sun, 31 Mar 2024 19:42:12 +0300
Subject: [PATCH 174/255] Add system prompt to /genraw command
---
public/script.js | 17 ++++++++++++++---
public/scripts/slash-commands.js | 5 +++--
2 files changed, 17 insertions(+), 5 deletions(-)
diff --git a/public/script.js b/public/script.js
index f32538823..99fae36f4 100644
--- a/public/script.js
+++ b/public/script.js
@@ -2911,17 +2911,24 @@ class StreamingProcessor {
* @param {string} api API to use. Main API is used if not specified.
* @param {boolean} instructOverride true to override instruct mode, false to use the default value
* @param {boolean} quietToLoud true to generate a message in system mode, false to generate a message in character mode
+ * @param {string} [systemPrompt] System prompt to use. Only Instruct mode or OpenAI.
* @returns {Promise} Generated message
*/
-export async function generateRaw(prompt, api, instructOverride, quietToLoud) {
+export async function generateRaw(prompt, api, instructOverride, quietToLoud, systemPrompt) {
if (!api) {
api = main_api;
}
const abortController = new AbortController();
- const isInstruct = power_user.instruct.enabled && main_api !== 'openai' && main_api !== 'novel' && !instructOverride;
+ const isInstruct = power_user.instruct.enabled && api !== 'openai' && api !== 'novel' && !instructOverride;
const isQuiet = true;
+ if (systemPrompt) {
+ systemPrompt = substituteParams(systemPrompt);
+ systemPrompt = isInstruct ? formatInstructModeSystemPrompt(systemPrompt) : systemPrompt;
+ prompt = api === 'openai' ? prompt : `${systemPrompt}\n${prompt}`;
+ }
+
prompt = substituteParams(prompt);
prompt = api == 'novel' ? adjustNovelInstructionPrompt(prompt) : prompt;
prompt = isInstruct ? formatInstructModeChat(name1, prompt, false, true, '', name1, name2, false) : prompt;
@@ -2948,8 +2955,12 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud) {
case 'textgenerationwebui':
generateData = getTextGenGenerationData(prompt, amount_gen, false, false, null, 'quiet');
break;
- case 'openai':
+ case 'openai': {
generateData = [{ role: 'user', content: prompt.trim() }];
+ if (systemPrompt) {
+ generateData.unshift({ role: 'system', content: systemPrompt.trim() });
+ }
+ } break;
}
let data = {};
diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js
index fb745364d..f07f66d24 100644
--- a/public/scripts/slash-commands.js
+++ b/public/scripts/slash-commands.js
@@ -231,7 +231,7 @@ parser.addCommand('delswipe', deleteSwipeCallback, ['swipedel'], '(title=string severity=info/warning/error/success [text]) – echoes the text to toast message. Useful for pipes debugging.', true, true);
//parser.addCommand('#', (_, value) => '', [], ' – a comment, does nothing, e.g. /# the next three commands switch variables a and b', true, true);
parser.addCommand('gen', generateCallback, [], '(lock=on/off name="System" [prompt]) – generates text using the provided prompt and passes it to the next command through the pipe, optionally locking user input while generating and allowing to configure the in-prompt name for instruct mode (default = "System"). "as" argument controls the role of the output prompt: system (default) or char.', true, true);
-parser.addCommand('genraw', generateRawCallback, [], '(lock=on/off instruct=on/off stop=[] as=system/char [prompt]) – generates text using the provided prompt and passes it to the next command through the pipe, optionally locking user input while generating. Does not include chat history or character card. Use instruct=off to skip instruct formatting, e.g. /genraw instruct=off Why is the sky blue?. Use stop=... with a JSON-serialized array to add one-time custom stop strings, e.g. /genraw stop=["\\n"] Say hi. "as" argument controls the role of the output prompt: system (default) or char.', true, true);
+parser.addCommand('genraw', generateRawCallback, [], '(lock=on/off instruct=on/off stop=[] as=system/char system="system prompt" [prompt]) – generates text using the provided prompt and passes it to the next command through the pipe, optionally locking user input while generating. Does not include chat history or character card. Use instruct=off to skip instruct formatting, e.g. /genraw instruct=off Why is the sky blue?. Use stop=... with a JSON-serialized array to add one-time custom stop strings, e.g. /genraw stop=["\\n"] Say hi. "as" argument controls the role of the output prompt: system (default) or char. "system" argument adds an (optional) system prompt at the start.', true, true);
parser.addCommand('addswipe', addSwipeCallback, ['swipeadd'], '(text) – adds a swipe to the last chat message.', true, true);
parser.addCommand('abort', abortCallback, [], ' – aborts the slash command batch execution', true, true);
parser.addCommand('fuzzy', fuzzyCallback, [], 'list=["a","b","c"] threshold=0.4 (text to search) – performs a fuzzy match of each items of list within the text to search. If any item matches then its name is returned. If no item list matches the text to search then no value is returned. The optional threshold (default is 0.4) allows some control over the matching. A low value (min 0.0) means the match is very strict. At 1.0 (max) the match is very loose and probably matches anything. The returned value passes to the next command through the pipe.', true, true); parser.addCommand('pass', (_, arg) => arg, ['return'], '(text) – passes the text to the next command through the pipe.', true, true);
@@ -661,6 +661,7 @@ async function generateRawCallback(args, value) {
const lock = isTrueBoolean(args?.lock);
const as = args?.as || 'system';
const quietToLoud = as === 'char';
+ const systemPrompt = resolveVariable(args?.system) || '';
try {
if (lock) {
@@ -668,7 +669,7 @@ async function generateRawCallback(args, value) {
}
setEphemeralStopStrings(resolveVariable(args?.stop));
- const result = await generateRaw(value, '', isFalseBoolean(args?.instruct), quietToLoud);
+ const result = await generateRaw(value, '', isFalseBoolean(args?.instruct), quietToLoud, systemPrompt);
return result;
} finally {
if (lock) {
From 3331cb64917d7892cf0648f16258f37890222d0c Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sun, 31 Mar 2024 21:02:38 +0300
Subject: [PATCH 175/255] Add ability to temporarily override response length
for /gen and /genraw
---
public/script.js | 203 ++++++++++++++++++++-----------
public/scripts/slash-commands.js | 10 +-
2 files changed, 138 insertions(+), 75 deletions(-)
diff --git a/public/script.js b/public/script.js
index 99fae36f4..8252c0b79 100644
--- a/public/script.js
+++ b/public/script.js
@@ -2372,21 +2372,31 @@ function getStoppingStrings(isImpersonate, isContinue) {
* @param {boolean} skipWIAN whether to skip addition of World Info and Author's Note into the prompt
* @param {string} quietImage Image to use for the quiet prompt
* @param {string} quietName Name to use for the quiet prompt (defaults to "System:")
+ * @param {number} [responseLength] Maximum response length. If unset, the global default value is used.
* @returns
*/
-export async function generateQuietPrompt(quiet_prompt, quietToLoud, skipWIAN, quietImage = null, quietName = null) {
+export async function generateQuietPrompt(quiet_prompt, quietToLoud, skipWIAN, quietImage = null, quietName = null, responseLength = null) {
console.log('got into genQuietPrompt');
- /** @type {GenerateOptions} */
- const options = {
- quiet_prompt,
- quietToLoud,
- skipWIAN: skipWIAN,
- force_name2: true,
- quietImage: quietImage,
- quietName: quietName,
- };
- const generateFinished = await Generate('quiet', options);
- return generateFinished;
+ const responseLengthCustomized = typeof responseLength === 'number' && responseLength > 0;
+ let originalResponseLength = -1;
+ try {
+ /** @type {GenerateOptions} */
+ const options = {
+ quiet_prompt,
+ quietToLoud,
+ skipWIAN: skipWIAN,
+ force_name2: true,
+ quietImage: quietImage,
+ quietName: quietName,
+ };
+ originalResponseLength = responseLengthCustomized ? saveResponseLength(main_api, responseLength) : -1;
+ const generateFinished = await Generate('quiet', options);
+ return generateFinished;
+ } finally {
+ if (responseLengthCustomized) {
+ restoreResponseLength(main_api, originalResponseLength);
+ }
+ }
}
/**
@@ -2912,14 +2922,17 @@ class StreamingProcessor {
* @param {boolean} instructOverride true to override instruct mode, false to use the default value
* @param {boolean} quietToLoud true to generate a message in system mode, false to generate a message in character mode
* @param {string} [systemPrompt] System prompt to use. Only Instruct mode or OpenAI.
+ * @param {number} [responseLength] Maximum response length. If unset, the global default value is used.
* @returns {Promise} Generated message
*/
-export async function generateRaw(prompt, api, instructOverride, quietToLoud, systemPrompt) {
+export async function generateRaw(prompt, api, instructOverride, quietToLoud, systemPrompt, responseLength) {
if (!api) {
api = main_api;
}
const abortController = new AbortController();
+ const responseLengthCustomized = typeof responseLength === 'number' && responseLength > 0;
+ let originalResponseLength = -1;
const isInstruct = power_user.instruct.enabled && api !== 'openai' && api !== 'novel' && !instructOverride;
const isQuiet = true;
@@ -2934,70 +2947,109 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy
prompt = isInstruct ? formatInstructModeChat(name1, prompt, false, true, '', name1, name2, false) : prompt;
prompt = isInstruct ? (prompt + formatInstructModePrompt(name2, false, '', name1, name2, isQuiet, quietToLoud)) : (prompt + '\n');
- let generateData = {};
+ try {
+ originalResponseLength = responseLengthCustomized ? saveResponseLength(api, responseLength) : -1;
+ let generateData = {};
- switch (api) {
- case 'kobold':
- case 'koboldhorde':
- if (preset_settings === 'gui') {
- generateData = { prompt: prompt, gui_settings: true, max_length: amount_gen, max_context_length: max_context, api_server };
- } else {
- const isHorde = api === 'koboldhorde';
- const koboldSettings = koboldai_settings[koboldai_setting_names[preset_settings]];
- generateData = getKoboldGenerationData(prompt, koboldSettings, amount_gen, max_context, isHorde, 'quiet');
+ switch (api) {
+ case 'kobold':
+ case 'koboldhorde':
+ if (preset_settings === 'gui') {
+ generateData = { prompt: prompt, gui_settings: true, max_length: amount_gen, max_context_length: max_context, api_server };
+ } else {
+ const isHorde = api === 'koboldhorde';
+ const koboldSettings = koboldai_settings[koboldai_setting_names[preset_settings]];
+ generateData = getKoboldGenerationData(prompt, koboldSettings, amount_gen, max_context, isHorde, 'quiet');
+ }
+ break;
+ case 'novel': {
+ const novelSettings = novelai_settings[novelai_setting_names[nai_settings.preset_settings_novel]];
+ generateData = getNovelGenerationData(prompt, novelSettings, amount_gen, false, false, null, 'quiet');
+ break;
}
- break;
- case 'novel': {
- const novelSettings = novelai_settings[novelai_setting_names[nai_settings.preset_settings_novel]];
- generateData = getNovelGenerationData(prompt, novelSettings, amount_gen, false, false, null, 'quiet');
- break;
+ case 'textgenerationwebui':
+ generateData = getTextGenGenerationData(prompt, amount_gen, false, false, null, 'quiet');
+ break;
+ case 'openai': {
+ generateData = [{ role: 'user', content: prompt.trim() }];
+ if (systemPrompt) {
+ generateData.unshift({ role: 'system', content: systemPrompt.trim() });
+ }
+ } break;
}
- case 'textgenerationwebui':
- generateData = getTextGenGenerationData(prompt, amount_gen, false, false, null, 'quiet');
- break;
- case 'openai': {
- generateData = [{ role: 'user', content: prompt.trim() }];
- if (systemPrompt) {
- generateData.unshift({ role: 'system', content: systemPrompt.trim() });
+
+ let data = {};
+
+ if (api == 'koboldhorde') {
+ data = await generateHorde(prompt, generateData, abortController.signal, false);
+ } else if (api == 'openai') {
+ data = await sendOpenAIRequest('quiet', generateData, abortController.signal);
+ } else {
+ const generateUrl = getGenerateUrl(api);
+ const response = await fetch(generateUrl, {
+ method: 'POST',
+ headers: getRequestHeaders(),
+ cache: 'no-cache',
+ body: JSON.stringify(generateData),
+ signal: abortController.signal,
+ });
+
+ if (!response.ok) {
+ const error = await response.json();
+ throw error;
}
- } break;
+
+ data = await response.json();
+ }
+
+ if (data.error) {
+ throw new Error(data.error);
+ }
+
+ const message = cleanUpMessage(extractMessageFromData(data), false, false, true);
+
+ if (!message) {
+ throw new Error('No message generated');
+ }
+
+ return message;
+ } finally {
+ if (responseLengthCustomized) {
+ restoreResponseLength(api, originalResponseLength);
+ }
}
+}
- let data = {};
-
- if (api == 'koboldhorde') {
- data = await generateHorde(prompt, generateData, abortController.signal, false);
- } else if (api == 'openai') {
- data = await sendOpenAIRequest('quiet', generateData, abortController.signal);
+/**
+ * Temporarily change the response length for the specified API.
+ * @param {string} api API to use.
+ * @param {number} responseLength Target response length.
+ * @returns {number} The original response length.
+ */
+function saveResponseLength(api, responseLength) {
+ let oldValue = -1;
+ if (api === 'openai') {
+ oldValue = oai_settings.openai_max_tokens;
+ oai_settings.openai_max_tokens = responseLength;
} else {
- const generateUrl = getGenerateUrl(api);
- const response = await fetch(generateUrl, {
- method: 'POST',
- headers: getRequestHeaders(),
- cache: 'no-cache',
- body: JSON.stringify(generateData),
- signal: abortController.signal,
- });
-
- if (!response.ok) {
- const error = await response.json();
- throw error;
- }
-
- data = await response.json();
+ oldValue = max_context;
+ max_context = responseLength;
}
+ return oldValue;
+}
- if (data.error) {
- throw new Error(data.error);
+/**
+ * Restore the original response length for the specified API.
+ * @param {string} api API to use.
+ * @param {number} responseLength Target response length.
+ * @returns {void}
+ */
+function restoreResponseLength(api, responseLength) {
+ if (api === 'openai') {
+ oai_settings.openai_max_tokens = responseLength;
+ } else {
+ max_context = responseLength;
}
-
- const message = cleanUpMessage(extractMessageFromData(data), false, false, true);
-
- if (!message) {
- throw new Error('No message generated');
- }
-
- return message;
}
/**
@@ -4390,10 +4442,19 @@ export async function sendMessageAsUser(messageText, messageBias, insertAt = nul
}
}
-export function getMaxContextSize() {
+/**
+ * Gets the maximum usable context size for the current API.
+ * @param {number|null} overrideResponseLength Optional override for the response length.
+ * @returns {number} Maximum usable context size.
+ */
+export function getMaxContextSize(overrideResponseLength = null) {
+ if (typeof overrideResponseLength !== 'number' || overrideResponseLength <= 0 || isNaN(overrideResponseLength)) {
+ overrideResponseLength = null;
+ }
+
let this_max_context = 1487;
if (main_api == 'kobold' || main_api == 'koboldhorde' || main_api == 'textgenerationwebui') {
- this_max_context = (max_context - amount_gen);
+ this_max_context = (max_context - (overrideResponseLength || amount_gen));
}
if (main_api == 'novel') {
this_max_context = Number(max_context);
@@ -4410,10 +4471,10 @@ export function getMaxContextSize() {
}
}
- this_max_context = this_max_context - amount_gen;
+ this_max_context = this_max_context - (overrideResponseLength || amount_gen);
}
if (main_api == 'openai') {
- this_max_context = oai_settings.openai_max_context - oai_settings.openai_max_tokens;
+ this_max_context = oai_settings.openai_max_context - (overrideResponseLength || oai_settings.openai_max_tokens);
}
return this_max_context;
}
diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js
index f07f66d24..aef1de058 100644
--- a/public/scripts/slash-commands.js
+++ b/public/scripts/slash-commands.js
@@ -230,8 +230,8 @@ parser.addCommand('peek', peekCallback, [], '(message in
parser.addCommand('delswipe', deleteSwipeCallback, ['swipedel'], '(optional 1-based id) – deletes a swipe from the last chat message. If swipe id not provided - deletes the current swipe.', true, true);
parser.addCommand('echo', echoCallback, [], '(title=string severity=info/warning/error/success [text]) – echoes the text to toast message. Useful for pipes debugging.', true, true);
//parser.addCommand('#', (_, value) => '', [], ' – a comment, does nothing, e.g. /# the next three commands switch variables a and b', true, true);
-parser.addCommand('gen', generateCallback, [], '(lock=on/off name="System" [prompt]) – generates text using the provided prompt and passes it to the next command through the pipe, optionally locking user input while generating and allowing to configure the in-prompt name for instruct mode (default = "System"). "as" argument controls the role of the output prompt: system (default) or char.', true, true);
-parser.addCommand('genraw', generateRawCallback, [], '(lock=on/off instruct=on/off stop=[] as=system/char system="system prompt" [prompt]) – generates text using the provided prompt and passes it to the next command through the pipe, optionally locking user input while generating. Does not include chat history or character card. Use instruct=off to skip instruct formatting, e.g. /genraw instruct=off Why is the sky blue?. Use stop=... with a JSON-serialized array to add one-time custom stop strings, e.g. /genraw stop=["\\n"] Say hi. "as" argument controls the role of the output prompt: system (default) or char. "system" argument adds an (optional) system prompt at the start.', true, true);
+parser.addCommand('gen', generateCallback, [], '(lock=on/off name="System" length=123 [prompt]) – generates text using the provided prompt and passes it to the next command through the pipe, optionally locking user input while generating and allowing to configure the in-prompt name for instruct mode (default = "System"). "as" argument controls the role of the output prompt: system (default) or char. If "length" argument is provided as a number in tokens, allows to temporarily override an API response length.', true, true);
+parser.addCommand('genraw', generateRawCallback, [], '(lock=on/off instruct=on/off stop=[] as=system/char system="system prompt" length=123 [prompt]) – generates text using the provided prompt and passes it to the next command through the pipe, optionally locking user input while generating. Does not include chat history or character card. Use instruct=off to skip instruct formatting, e.g. /genraw instruct=off Why is the sky blue?. Use stop=... with a JSON-serialized array to add one-time custom stop strings, e.g. /genraw stop=["\\n"] Say hi. "as" argument controls the role of the output prompt: system (default) or char. "system" argument adds an (optional) system prompt at the start. If "length" argument is provided as a number in tokens, allows to temporarily override an API response length.', true, true);
parser.addCommand('addswipe', addSwipeCallback, ['swipeadd'], '(text) – adds a swipe to the last chat message.', true, true);
parser.addCommand('abort', abortCallback, [], ' – aborts the slash command batch execution', true, true);
parser.addCommand('fuzzy', fuzzyCallback, [], 'list=["a","b","c"] threshold=0.4 (text to search) – performs a fuzzy match of each items of list within the text to search. If any item matches then its name is returned. If no item list matches the text to search then no value is returned. The optional threshold (default is 0.4) allows some control over the matching. A low value (min 0.0) means the match is very strict. At 1.0 (max) the match is very loose and probably matches anything. The returned value passes to the next command through the pipe.', true, true); parser.addCommand('pass', (_, arg) => arg, ['return'], '(text) – passes the text to the next command through the pipe.', true, true);
@@ -662,6 +662,7 @@ async function generateRawCallback(args, value) {
const as = args?.as || 'system';
const quietToLoud = as === 'char';
const systemPrompt = resolveVariable(args?.system) || '';
+ const length = Number(resolveVariable(args?.length) ?? 0) || 0;
try {
if (lock) {
@@ -669,7 +670,7 @@ async function generateRawCallback(args, value) {
}
setEphemeralStopStrings(resolveVariable(args?.stop));
- const result = await generateRaw(value, '', isFalseBoolean(args?.instruct), quietToLoud, systemPrompt);
+ const result = await generateRaw(value, '', isFalseBoolean(args?.instruct), quietToLoud, systemPrompt, length);
return result;
} finally {
if (lock) {
@@ -690,6 +691,7 @@ async function generateCallback(args, value) {
const lock = isTrueBoolean(args?.lock);
const as = args?.as || 'system';
const quietToLoud = as === 'char';
+ const length = Number(resolveVariable(args?.length) ?? 0) || 0;
try {
if (lock) {
@@ -698,7 +700,7 @@ async function generateCallback(args, value) {
setEphemeralStopStrings(resolveVariable(args?.stop));
const name = args?.name;
- const result = await generateQuietPrompt(value, quietToLoud, false, '', name);
+ const result = await generateQuietPrompt(value, quietToLoud, false, '', name, length);
return result;
} finally {
if (lock) {
From b990eb523bd0eb7c3be0243c63e7d2c1ec60d06a Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sun, 31 Mar 2024 21:22:27 +0300
Subject: [PATCH 176/255] #1980 Add raw summary prompt builder mode
---
public/scripts/extensions/memory/index.js | 249 +++++++++++++++++-
.../scripts/extensions/memory/settings.html | 79 ++++--
public/scripts/extensions/memory/style.css | 12 +-
3 files changed, 310 insertions(+), 30 deletions(-)
diff --git a/public/scripts/extensions/memory/index.js b/public/scripts/extensions/memory/index.js
index da0ae00e7..00c6de443 100644
--- a/public/scripts/extensions/memory/index.js
+++ b/public/scripts/extensions/memory/index.js
@@ -1,11 +1,25 @@
-import { getStringHash, debounce, waitUntilCondition, extractAllWords } from '../../utils.js';
+import { getStringHash, debounce, waitUntilCondition, extractAllWords, delay } from '../../utils.js';
import { getContext, getApiUrl, extension_settings, doExtrasFetch, modules, renderExtensionTemplate } from '../../extensions.js';
-import { animation_duration, eventSource, event_types, extension_prompt_roles, extension_prompt_types, generateQuietPrompt, is_send_press, saveSettingsDebounced, substituteParams } from '../../../script.js';
+import {
+ activateSendButtons,
+ deactivateSendButtons,
+ animation_duration,
+ eventSource,
+ event_types,
+ extension_prompt_roles,
+ extension_prompt_types,
+ generateQuietPrompt,
+ is_send_press,
+ saveSettingsDebounced,
+ substituteParams,
+ generateRaw,
+ getMaxContextSize,
+} from '../../../script.js';
import { is_group_generating, selected_group } from '../../group-chats.js';
import { registerSlashCommand } from '../../slash-commands.js';
import { loadMovingUIState } from '../../power-user.js';
import { dragElement } from '../../RossAscends-mods.js';
-import { getTextTokens, tokenizers } from '../../tokenizers.js';
+import { getTextTokens, getTokenCount, tokenizers } from '../../tokenizers.js';
export { MODULE_NAME };
const MODULE_NAME = '1_memory';
@@ -39,7 +53,13 @@ const summary_sources = {
'main': 'main',
};
-const defaultPrompt = '[Pause your roleplay. Summarize the most important facts and events that have happened in the chat so far. If a summary already exists in your memory, use that as a base and expand with new facts. Limit the summary to {{words}} words or less. Your response should include nothing but the summary.]';
+const prompt_builders = {
+ DEFAULT: 0,
+ RAW_BLOCKING: 1,
+ RAW_NON_BLOCKING: 2,
+};
+
+const defaultPrompt = '[Pause your roleplay. Summarize the most important facts and events in the story so far. If a summary already exists in your memory, use that as a base and expand with new facts. Limit the summary to {{words}} words or less. Your response should include nothing but the summary.]';
const defaultTemplate = '[Summary: {{summary}}]';
const defaultSettings = {
@@ -57,12 +77,21 @@ const defaultSettings = {
promptWordsStep: 25,
promptInterval: 10,
promptMinInterval: 0,
- promptMaxInterval: 100,
+ promptMaxInterval: 250,
promptIntervalStep: 1,
promptForceWords: 0,
promptForceWordsStep: 100,
promptMinForceWords: 0,
promptMaxForceWords: 10000,
+ overrideResponseLength: 0,
+ overrideResponseLengthMin: 0,
+ overrideResponseLengthMax: 4096,
+ overrideResponseLengthStep: 16,
+ maxMessagesPerRequest: 0,
+ maxMessagesPerRequestMin: 0,
+ maxMessagesPerRequestMax: 250,
+ maxMessagesPerRequestStep: 1,
+ prompt_builder: prompt_builders.RAW_BLOCKING,
};
function loadSettings() {
@@ -87,9 +116,50 @@ function loadSettings() {
$('#memory_role').val(extension_settings.memory.role).trigger('input');
$(`input[name="memory_position"][value="${extension_settings.memory.position}"]`).prop('checked', true).trigger('input');
$('#memory_prompt_words_force').val(extension_settings.memory.promptForceWords).trigger('input');
+ $(`input[name="memory_prompt_builder"][value="${extension_settings.memory.prompt_builder}"]`).prop('checked', true).trigger('input');
+ $('#memory_override_response_length').val(extension_settings.memory.overrideResponseLength).trigger('input');
+ $('#memory_max_messages_per_request').val(extension_settings.memory.maxMessagesPerRequest).trigger('input');
switchSourceControls(extension_settings.memory.source);
}
+async function onPromptIntervalAutoClick() {
+ const context = getContext();
+ const maxPromptLength = getMaxContextSize(extension_settings.memory.overrideResponseLength);
+ const chat = context.chat;
+ const allMessages = chat.filter(m => !m.is_system && m.mes).map(m => m.mes);
+ const messagesWordCount = allMessages.map(m => extractAllWords(m)).flat().length;
+ const messagesTokenCount = getTokenCount(allMessages.join('\n'));
+ const tokensPerWord = messagesTokenCount / messagesWordCount;
+ const averageMessageTokenCount = messagesTokenCount / allMessages.length;
+ const targetSummaryTokens = Math.round(extension_settings.memory.promptWords * tokensPerWord);
+ const promptTokens = getTokenCount(extension_settings.memory.prompt);
+ const promptAllowance = maxPromptLength - promptTokens - targetSummaryTokens;
+ const maxMessagesPerSummary = extension_settings.memory.maxMessagesPerRequest || 0;
+ const averageMessagesPerPrompt = Math.floor(promptAllowance / averageMessageTokenCount);
+ const unfitMessages = maxMessagesPerSummary > 0 ? averageMessagesPerPrompt - maxMessagesPerSummary : 0;
+ const adjustedAverageMessagesPerPrompt = Math.max(1, averageMessagesPerPrompt - (unfitMessages > 0 ? Math.ceil(unfitMessages / 2) : 0));
+
+ console.table({
+ maxPromptLength,
+ promptAllowance,
+ targetSummaryTokens,
+ promptTokens,
+ messagesWordCount,
+ messagesTokenCount,
+ tokensPerWord,
+ averageMessageTokenCount,
+ averageMessagesPerPrompt,
+ adjustedAverageMessagesPerPrompt,
+ maxMessagesPerSummary,
+ unfitMessages,
+ });
+
+ const ROUNDING = 5;
+ extension_settings.memory.promptInterval = Math.max(1, Math.floor(adjustedAverageMessagesPerPrompt / ROUNDING) * ROUNDING);
+
+ $('#memory_prompt_interval').val(extension_settings.memory.promptInterval).trigger('input');
+}
+
function onSummarySourceChange(event) {
const value = event.target.value;
extension_settings.memory.source = value;
@@ -130,6 +200,10 @@ function onMemoryPromptIntervalInput() {
saveSettingsDebounced();
}
+function onMemoryPromptRestoreClick() {
+ $('#memory_prompt').val(defaultPrompt).trigger('input');
+}
+
function onMemoryPromptInput() {
const value = $(this).val();
extension_settings.memory.prompt = value;
@@ -171,6 +245,20 @@ function onMemoryPromptWordsForceInput() {
saveSettingsDebounced();
}
+function onOverrideResponseLengthInput() {
+ const value = $(this).val();
+ extension_settings.memory.overrideResponseLength = Number(value);
+ $('#memory_override_response_length_value').text(extension_settings.memory.overrideResponseLength);
+ saveSettingsDebounced();
+}
+
+function onMaxMessagesPerRequestInput() {
+ const value = $(this).val();
+ extension_settings.memory.maxMessagesPerRequest = Number(value);
+ $('#memory_max_messages_per_request_value').text(extension_settings.memory.maxMessagesPerRequest);
+ saveSettingsDebounced();
+}
+
function saveLastValues() {
const context = getContext();
lastGroupId = context.groupId;
@@ -196,6 +284,22 @@ function getLatestMemoryFromChat(chat) {
return '';
}
+function getIndexOfLatestChatSummary(chat) {
+ if (!Array.isArray(chat) || !chat.length) {
+ return -1;
+ }
+
+ const reversedChat = chat.slice().reverse();
+ reversedChat.shift();
+ for (let mes of reversedChat) {
+ if (mes.extra && mes.extra.memory) {
+ return chat.indexOf(mes);
+ }
+ }
+
+ return -1;
+}
+
async function onChatEvent() {
// Module not enabled
if (extension_settings.memory.source === summary_sources.extras) {
@@ -359,8 +463,41 @@ async function summarizeChatMain(context, force, skipWIAN) {
console.debug('Summarization prompt is empty. Skipping summarization.');
return;
}
+
console.log('sending summary prompt');
- const summary = await generateQuietPrompt(prompt, false, skipWIAN);
+ let summary = '';
+ let index = null;
+
+ if (prompt_builders.DEFAULT === extension_settings.memory.prompt_builder) {
+ summary = await generateQuietPrompt(prompt, false, skipWIAN, '', '', extension_settings.memory.overrideResponseLength);
+ }
+
+ if ([prompt_builders.RAW_BLOCKING, prompt_builders.RAW_NON_BLOCKING].includes(extension_settings.memory.prompt_builder)) {
+ const lock = extension_settings.memory.prompt_builder === prompt_builders.RAW_BLOCKING;
+ try {
+ if (lock) {
+ deactivateSendButtons();
+ }
+
+ const { rawPrompt, lastUsedIndex } = await getRawSummaryPrompt(context, prompt);
+
+ if (lastUsedIndex === null || lastUsedIndex === -1) {
+ if (force) {
+ toastr.info('To try again, remove the latest summary.', 'No messages found to summarize');
+ }
+
+ return null;
+ }
+
+ summary = await generateRaw(rawPrompt, '', false, false, prompt, extension_settings.memory.overrideResponseLength);
+ index = lastUsedIndex;
+ } finally {
+ if (lock) {
+ activateSendButtons();
+ }
+ }
+ }
+
const newContext = getContext();
// something changed during summarization request
@@ -371,10 +508,83 @@ async function summarizeChatMain(context, force, skipWIAN) {
return;
}
- setMemoryContext(summary, true);
+ setMemoryContext(summary, true, index);
return summary;
}
+/**
+ * Get the raw summarization prompt from the chat context.
+ * @param {object} context ST context
+ * @param {string} prompt Summarization system prompt
+ * @returns {Promise<{rawPrompt: string, lastUsedIndex: number}>} Raw summarization prompt
+ */
+async function getRawSummaryPrompt(context, prompt) {
+ /**
+ * Get the memory string from the chat buffer.
+ * @param {boolean} includeSystem Include prompt into the memory string
+ * @returns {string} Memory string
+ */
+ function getMemoryString(includeSystem) {
+ const delimiter = '\n\n';
+ const stringBuilder = [];
+ const bufferString = chatBuffer.slice().join(delimiter);
+
+ if (includeSystem) {
+ stringBuilder.push(prompt);
+ }
+
+ if (latestSummary) {
+ stringBuilder.push(latestSummary);
+ }
+
+ stringBuilder.push(bufferString);
+
+ return stringBuilder.join(delimiter).trim();
+ }
+
+ const chat = context.chat.slice();
+ const latestSummary = getLatestMemoryFromChat(chat);
+ const latestSummaryIndex = getIndexOfLatestChatSummary(chat);
+ chat.pop(); // We always exclude the last message from the buffer
+ const chatBuffer = [];
+ const PADDING = 64;
+ const PROMPT_SIZE = getMaxContextSize(extension_settings.memory.overrideResponseLength);
+ let latestUsedMessage = null;
+
+ for (let index = latestSummaryIndex + 1; index < chat.length; index++) {
+ const message = chat[index];
+
+ if (!message) {
+ break;
+ }
+
+ if (message.is_system || !message.mes) {
+ continue;
+ }
+
+ const entry = `${message.name}:\n${message.mes}`;
+ chatBuffer.push(entry);
+
+ const tokens = getTokenCount(getMemoryString(true), PADDING);
+ await delay(1);
+
+ if (tokens > PROMPT_SIZE) {
+ chatBuffer.pop();
+ break;
+ }
+
+ latestUsedMessage = message;
+
+ if (extension_settings.memory.maxMessagesPerRequest > 0 && chatBuffer.length >= extension_settings.memory.maxMessagesPerRequest) {
+ break;
+ }
+ }
+
+ const lastUsedIndex = context.chat.indexOf(latestUsedMessage);
+ const rawPrompt = getMemoryString(false);
+ return { rawPrompt, lastUsedIndex };
+}
+
async function summarizeChatExtras(context) {
function getMemoryString() {
return (longMemory + '\n\n' + memoryBuffer.slice().reverse().join('\n\n')).trim();
@@ -482,12 +692,24 @@ function onMemoryContentInput() {
setMemoryContext(value, true);
}
+function onMemoryPromptBuilderInput(e) {
+ const value = Number(e.target.value);
+ extension_settings.memory.prompt_builder = value;
+ saveSettingsDebounced();
+}
+
function reinsertMemory() {
- const existingValue = $('#memory_contents').val();
+ const existingValue = String($('#memory_contents').val());
setMemoryContext(existingValue, false);
}
-function setMemoryContext(value, saveToMessage) {
+/**
+ * Set the summary value to the context and save it to the chat message extra.
+ * @param {string} value Value of a summary
+ * @param {boolean} saveToMessage Should the summary be saved to the chat message extra
+ * @param {number|null} index Index of the chat message to save the summary to. If null, the pre-last message is used.
+ */
+function setMemoryContext(value, saveToMessage, index = null) {
const context = getContext();
context.setExtensionPrompt(MODULE_NAME, formatMemoryValue(value), extension_settings.memory.position, extension_settings.memory.depth, false, extension_settings.memory.role);
$('#memory_contents').val(value);
@@ -497,7 +719,7 @@ function setMemoryContext(value, saveToMessage) {
console.debug('Role: ' + extension_settings.memory.role);
if (saveToMessage && context.chat.length) {
- const idx = context.chat.length - 2;
+ const idx = index ?? context.chat.length - 2;
const mes = context.chat[idx < 0 ? 0 : idx];
if (!mes.extra) {
@@ -573,6 +795,13 @@ function setupListeners() {
$('#memory_role').off('click').on('input', onMemoryRoleInput);
$('input[name="memory_position"]').off('click').on('change', onMemoryPositionChange);
$('#memory_prompt_words_force').off('click').on('input', onMemoryPromptWordsForceInput);
+ $('#memory_prompt_builder_default').off('click').on('input', onMemoryPromptBuilderInput);
+ $('#memory_prompt_builder_raw_blocking').off('click').on('input', onMemoryPromptBuilderInput);
+ $('#memory_prompt_builder_raw_non_blocking').off('click').on('input', onMemoryPromptBuilderInput);
+ $('#memory_prompt_restore').off('click').on('click', onMemoryPromptRestoreClick);
+ $('#memory_prompt_interval_auto').off('click').on('click', onPromptIntervalAutoClick);
+ $('#memory_override_response_length').off('click').on('input', onOverrideResponseLengthInput);
+ $('#memory_max_messages_per_request').off('click').on('input', onMaxMessagesPerRequestInput);
$('#summarySettingsBlockToggle').off('click').on('click', function () {
console.log('saw settings button click');
$('#summarySettingsBlock').slideToggle(200, 'swing'); //toggleClass("hidden");
diff --git a/public/scripts/extensions/memory/settings.html b/public/scripts/extensions/memory/settings.html
index b5b54142c..7bf3626e7 100644
--- a/public/scripts/extensions/memory/settings.html
+++ b/public/scripts/extensions/memory/settings.html
@@ -36,9 +36,67 @@
+
+
+
+ Update every words
+ 0 = disable
+
+
+ If both sliders are non-zero, then both will trigger summary updates at their respective intervals.
+
+
- Insertion Template
-
+ Injection Template
+
Injection Position
@@ -61,23 +119,6 @@
-
-
-
-
- Summary Prompt
-
-
- Summary length ( words)
-
- Update every messages
- 0 = disable
-
- Update every words
- 0 = disable
-
- If both sliders are non-zero, then both will trigger summary updates a their respective intervals.
-
diff --git a/public/scripts/extensions/memory/style.css b/public/scripts/extensions/memory/style.css
index 20dfb5e3c..2f3ddbb25 100644
--- a/public/scripts/extensions/memory/style.css
+++ b/public/scripts/extensions/memory/style.css
@@ -24,4 +24,14 @@ label[for="memory_frozen"] input {
flex-direction: row;
align-items: center;
justify-content: space-between;
-}
\ No newline at end of file
+}
+
+.memory_disabled_hint {
+ margin-left: 2px;
+}
+
+#summarySettingsBlock {
+ display: flex;
+ flex-direction: column;
+ row-gap: 5px;
+}
From 6aa97c73e92c48a4c6ff8e2ee74b7203486aa993 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sun, 31 Mar 2024 21:27:13 +0300
Subject: [PATCH 177/255] Fix example dialogue formatting with block header
---
public/scripts/instruct-mode.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/public/scripts/instruct-mode.js b/public/scripts/instruct-mode.js
index 9c75977e1..c7aa812ce 100644
--- a/public/scripts/instruct-mode.js
+++ b/public/scripts/instruct-mode.js
@@ -400,7 +400,7 @@ export function formatInstructModeExamples(mesExamplesArray, name1, name2) {
}
if (blockHeading) {
- formattedExamples.push(power_user.blockHeading);
+ formattedExamples.push(blockHeading);
}
for (const example of blockExamples) {
From 159404c3e21adeb25c4d0e64fa1b5c71af13bc6d Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Mon, 1 Apr 2024 01:18:29 +0300
Subject: [PATCH 178/255] Hide "no WI/AN" in Extras summary mode
---
public/scripts/extensions/memory/settings.html | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/public/scripts/extensions/memory/settings.html b/public/scripts/extensions/memory/settings.html
index 7bf3626e7..c6f05fd2d 100644
--- a/public/scripts/extensions/memory/settings.html
+++ b/public/scripts/extensions/memory/settings.html
@@ -17,7 +17,9 @@
-
- Update every words
- 0 = disable
+
+
+ Update every words
+ 0 = disable
+
+
+
+
If both sliders are non-zero, then both will trigger summary updates at their respective intervals.
From 70adee3c631a0290ce182f8612e33e99ab62ad74 Mon Sep 17 00:00:00 2001
From: Wolfsblvt
Date: Mon, 1 Apr 2024 01:47:56 +0200
Subject: [PATCH 180/255] Add {{pick}} macro replacement
- Pick macro that works like random, but is consistent for the chat and context
- Change help text for random to actually utilize the new, preferred syntax
---
public/scripts/macros.js | 30 ++++++++++++++++++++++++++--
public/scripts/templates/macros.html | 3 ++-
2 files changed, 30 insertions(+), 3 deletions(-)
diff --git a/public/scripts/macros.js b/public/scripts/macros.js
index f3527d451..a8d178604 100644
--- a/public/scripts/macros.js
+++ b/public/scripts/macros.js
@@ -1,5 +1,5 @@
-import { chat, main_api, getMaxContextSize } from '../script.js';
-import { timestampToMoment, isDigitsOnly } from './utils.js';
+import { chat, main_api, getMaxContextSize, getCurrentChatId } from '../script.js';
+import { timestampToMoment, isDigitsOnly, getStringHash } from './utils.js';
import { textgenerationwebui_banned_in_macros } from './textgen-settings.js';
import { replaceInstructMacros } from './instruct-mode.js';
import { replaceVariableMacros } from './variables.js';
@@ -211,6 +211,29 @@ function randomReplace(input, emptyListPlaceholder = '') {
return input;
}
+function pickReplace(input, rawContent, emptyListPlaceholder = '') {
+ const pickPattern = /{{pick\s?::\s?([^}]+)}}/gi;
+ const chatIdHash = getStringHash(getCurrentChatId());
+ const rawContentHash = getStringHash(rawContent);
+
+ return input.replace(pickPattern, (match, listString, offset) => {
+ const list = listString.includes('::')
+ ? listString.split('::').filter(item => item.length > 0)
+ : listString.split(',').map(item => item.trim()).filter(item => item.length > 0);
+
+ if (list.length === 0) {
+ return emptyListPlaceholder;
+ }
+
+ const combinedSeedString = `${chatIdHash}-${rawContentHash}-${offset}`;
+ const finalSeed = getStringHash(combinedSeedString);
+ const rng = new Math.seedrandom(finalSeed);
+
+ const randomIndex = Math.floor(rng() * list.length);
+ return list[randomIndex].trim();
+ });
+}
+
function diceRollReplace(input, invalidRollPlaceholder = '') {
const rollPattern = /{{roll[ : ]([^}]+)}}/gi;
@@ -245,6 +268,8 @@ export function evaluateMacros(content, env) {
return '';
}
+ const rawContent = content;
+
// Legacy non-macro substitutions
content = content.replace(//gi, typeof env.user === 'function' ? env.user() : env.user);
content = content.replace(//gi, typeof env.char === 'function' ? env.char() : env.char);
@@ -300,5 +325,6 @@ export function evaluateMacros(content, env) {
});
content = bannedWordsReplace(content);
content = randomReplace(content);
+ content = pickReplace(content, rawContent);
return content;
}
diff --git a/public/scripts/templates/macros.html b/public/scripts/templates/macros.html
index 1b137122a..1c27718cc 100644
--- a/public/scripts/templates/macros.html
+++ b/public/scripts/templates/macros.html
@@ -34,8 +34,9 @@
{{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}} 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::(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.
+
{{pick::(args)}} – picks a random item from the list. Works the same as {{random}}, with the same possible syntax options, but the pick will stay consistent for this chat once picked and won't be re-rolled on consecutive messages and prompt processing.
{{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.
diff --git a/public/scripts/extensions/quick-reply/index.js b/public/scripts/extensions/quick-reply/index.js
index a032a12d6..7b58f4aaa 100644
--- a/public/scripts/extensions/quick-reply/index.js
+++ b/public/scripts/extensions/quick-reply/index.js
@@ -104,7 +104,7 @@ const loadSets = async () => {
qr.executeOnAi = slot.autoExecute_botMessage ?? false;
qr.executeOnChatChange = slot.autoExecute_chatLoad ?? false;
qr.executeOnGroupMemberDraft = slot.autoExecute_groupMemberDraft ?? false;
- qr.automationId = slot.automationId ?? false;
+ qr.automationId = slot.automationId ?? '';
qr.contextList = (slot.contextMenu ?? []).map(it=>({
set: it.preset,
isChained: it.chain,
From 9e4b765db133f979e7e673aef09a9ed907bf660e Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Fri, 5 Apr 2024 17:59:12 +0300
Subject: [PATCH 234/255] #2012 Replace all comfy seeds
---
public/scripts/extensions/stable-diffusion/index.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/public/scripts/extensions/stable-diffusion/index.js b/public/scripts/extensions/stable-diffusion/index.js
index f4e47ca84..6b656e549 100644
--- a/public/scripts/extensions/stable-diffusion/index.js
+++ b/public/scripts/extensions/stable-diffusion/index.js
@@ -2524,7 +2524,7 @@ async function generateComfyImage(prompt, negativePrompt) {
}
let workflow = (await workflowResponse.json()).replace('"%prompt%"', JSON.stringify(prompt));
workflow = workflow.replace('"%negative_prompt%"', JSON.stringify(negativePrompt));
- workflow = workflow.replace('"%seed%"', JSON.stringify(Math.round(Math.random() * Number.MAX_SAFE_INTEGER)));
+ workflow = workflow.replaceAll('"%seed%"', JSON.stringify(Math.round(Math.random() * Number.MAX_SAFE_INTEGER)));
placeholders.forEach(ph => {
workflow = workflow.replace(`"%${ph}%"`, JSON.stringify(extension_settings.sd[ph]));
});
From a1a8d7fe4ca9d35d1a46f94fe333b86d3b297ee6 Mon Sep 17 00:00:00 2001
From: caesarw
Date: Fri, 5 Apr 2024 15:39:06 +0000
Subject: [PATCH 235/255] Added workflows for nightly staging build
* the nightly staging build starts at 00:00 UTC everyday
* multi-arch build support is added (amd64, arm64)
---
.github/workflows/docker-publish.yml | 73 ++++++++++++++++++++--------
1 file changed, 53 insertions(+), 20 deletions(-)
diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml
index 06e8eac9e..588fcd80c 100644
--- a/.github/workflows/docker-publish.yml
+++ b/.github/workflows/docker-publish.yml
@@ -1,45 +1,78 @@
# This workflow will publish a docker image for every full release to the GitHub package repository
-name: Create Docker Image on Release
+name: Create Docker Image (Release and Staging)
on:
release:
# Allow pre-releases
types: [published]
+ schedule:
+ # Build the staging image everyday at 00:00 UTC
+ - cron: "0 0 * * *"
env:
# This should allow creation of docker images even in forked repositories
- # Image name may not contain uppercase characters, so we can not use the repository name
- # Creates a string like: ghcr.io/SillyTavern/sillytavern
- image_name: ghcr.io/sillytavern/sillytavern
+ IMAGE_NAME: ${{ github.repository }}
+ REGISTRY: ghcr.io
jobs:
-
build:
-
runs-on: ubuntu-latest
steps:
- - name: Checkout
+ # Using the following workaround because currently GitHub Actions
+ # does not support logical AND/OR operations on triggers
+ # It's currently not possible to have `branches` under the `schedule` trigger
+ - name: Checkout the release branch
+ if: ${{ github.event_name == 'release' }}
uses: actions/checkout@v3
+ with:
+ ref: "release"
- # Build docker image using dockerfile and tag it with branch name
- # Assumes branch name is the version number
- - name: Build the Docker image
- run: |
- docker build . --file Dockerfile --tag $image_name:${{ github.ref_name }}
+ - name: Checkout the staging branch
+ if: ${{ github.event_name == 'schedule' }}
+ uses: actions/checkout@v3
+ with:
+ ref: "staging"
+
+ # Setting up QEMU for multi-arch image build
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v3
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Extract metadata (tags, labels) for the image
+ uses: docker/metadata-action@v5.5.1
+ id: metadata
+ with:
+ images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
+ tags: ${{ github.ref_name }}
# Login into package repository as the person who created the release
- - name: Login to GitHub Container Registry
- uses: docker/login-action@v1
+ - name: Log in to the Container registry
+ uses: docker/login-action@v3
with:
- registry: ghcr.io
+ registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- # Assumes release is the latest and marks image as such
- - name: Docker Tag and Push
+ # Build docker image using dockerfile for amd64 and arm64
+ # Tag it with branch name
+ # Assumes branch name is the version number
+ - name: Build and push
+ uses: docker/build-push-action@v5.3.0
+ with:
+ context: .
+ platforms: linux/amd64,linux/arm64
+ file: Dockerfile
+ push: true
+ tags: ${{ steps.metadata.outputs.tags }}
+ labels: ${{ steps.metadata.outputs.labels }}
+
+ # If the workflow is triggered by a release, marks and push the image as such
+ - name: Docker tag latest and push
+ if: ${{ github.event_name == 'release' }}
run: |
- docker tag $image_name:${{ github.ref_name }} $image_name:latest
- docker push $image_name:${{ github.ref_name }}
- docker push $image_name:latest
+ docker tag $IMAGE_NAME:${{ github.ref_name }} $IMAGE_NAME:latest
+ docker push $IMAGE_NAME:latest
From c0213c086c0680ee62da354c08d16e80785d7d19 Mon Sep 17 00:00:00 2001
From: KegaPlayer <128023803+KegaPlayer@users.noreply.github.com>
Date: Fri, 5 Apr 2024 12:12:32 -0500
Subject: [PATCH 236/255] Update es-es.json
Proofreading work done by a Spanish language native on the es-es.json file. Mostly centered around making some terminology consistent and a few minor grammar fixes.
---
public/locales/es-es.json | 398 +++++++++++++++++++-------------------
1 file changed, 199 insertions(+), 199 deletions(-)
diff --git a/public/locales/es-es.json b/public/locales/es-es.json
index c16ba2902..1f919c909 100644
--- a/public/locales/es-es.json
+++ b/public/locales/es-es.json
@@ -5,7 +5,7 @@
"novelaipreserts": "Preajustes de NovelAI",
"default": "Predeterminado",
"openaipresets": "Preajustes de OpenAI",
- "text gen webio(ooba) presets": "Preajustes de generación de texto WebUI(ooba)",
+ "text gen webio(ooba) presets": "Preajustes de Text Gen WebUI(ooba)",
"response legth(tokens)": "Longitud de respuesta (tokens)",
"select": "Seleccionar",
"context size(tokens)": "Tamaño de contexto (tokens)",
@@ -13,17 +13,17 @@
"Only select models support context sizes greater than 4096 tokens. Increase only if you know what you're doing.": "Solo algunos modelos admiten tamaños de contexto mayores de 4096 tokens. Aumenta solo si sabes lo que estás haciendo.",
"rep.pen": "Penalización de repetición",
"WI Entry Status:🔵 Constant🟢 Normal❌ Disabled": "Estado de entrada de WI:🔵 Constante🟢 Normal❌ Desactivado",
- "rep.pen range": "Rango de penalización de repetición",
+ "rep.pen range": "rango de penalización de repetición",
"Temperature controls the randomness in token selection": "La temperatura controla la aleatoriedad en la selección de tokens",
"temperature": "Temperatura",
"Top K sets a maximum amount of top tokens that can be chosen from": "Top K establece una cantidad máxima de tokens principales que se pueden elegir",
"Top P (a.k.a. nucleus sampling)": "Top P (también conocido como muestreo de núcleo)",
- "Typical P Sampling prioritizes tokens based on their deviation from the average entropy of the set": "El muestreo P típico prioriza tokens según su desviación de la entropía promedio del conjunto",
+ "Typical P Sampling prioritizes tokens based on their deviation from the average entropy of the set": "El Muestreo P Típico prioriza tokens según su desviación de la entropía promedio del conjunto",
"Min P sets a base minimum probability": "Min P establece una probabilidad mínima base",
"Top A sets a threshold for token selection based on the square of the highest token probability": "Top A establece un umbral para la selección de tokens basado en el cuadrado de la probabilidad de token más alta",
"Tail-Free Sampling (TFS)": "Muestreo sin cola (TFS)",
- "Epsilon cutoff sets a probability floor below which tokens are excluded from being sampled": "El corte epsilon establece un límite de probabilidad por debajo del cual se excluyen los tokens de ser muestreados",
- "Scale Temperature dynamically per token, based on the variation of probabilities": "Escalas de temperatura dinámicamente por token, basado en la variación de probabilidades",
+ "Epsilon cutoff sets a probability floor below which tokens are excluded from being sampled": "El corte Epsilon establece un límite de probabilidad por debajo del cual se excluyen los tokens de ser muestreados",
+ "Scale Temperature dynamically per token, based on the variation of probabilities": "Escala la Temperatura dinámicamente por token, basado en la variación de probabilidades",
"Minimum Temp": "Temperatura mínima",
"Maximum Temp": "Temperatura máxima",
"Exponent": "Exponente",
@@ -33,11 +33,11 @@
"Variability parameter for Mirostat outputs": "Parámetro de variabilidad para las salidas de Mirostat",
"Learning rate of Mirostat": "Tasa de aprendizaje de Mirostat",
"Strength of the Contrastive Search regularization term. Set to 0 to disable CS": "Fuerza del término de regularización de la Búsqueda Contrastiva. Establece en 0 para deshabilitar CS.",
- "Temperature Last": "Última temperatura",
+ "Temperature Last": "Temperatura de Último",
"Use the temperature sampler last": "Usar el muestreador de temperatura al final",
"LLaMA / Mistral / Yi models only": "Solo modelos LLaMA / Mistral / Yi",
"Example: some text [42, 69, 1337]": "Ejemplo: algún texto [42, 69, 1337]",
- "Classifier Free Guidance. More helpful tip coming soon": "Guía libre de clasificadores. Pronto llegará un consejo más útil",
+ "Classifier Free Guidance. More helpful tip coming soon": "Guía Libre de Clasificadores. Pronto llegará un consejo más útil",
"Scale": "Escala",
"GBNF Grammar": "Gramática GBNF",
"Usage Stats": "Estadísticas de uso",
@@ -74,7 +74,7 @@
"Add BOS Token": "Agregar token BOS",
"Add the bos_token to the beginning of prompts. Disabling this can make the replies more creative": "Agrega el token BOS al principio de las indicaciones. Desactivar esto puede hacer que las respuestas sean más creativas",
"Ban EOS Token": "Prohibir token EOS",
- "Ban the eos_token. This forces the model to never end the generation prematurely": "Prohibir el token eos. Esto obliga al modelo a nunca terminar la generación prematuramente",
+ "Ban the eos_token. This forces the model to never end the generation prematurely": "Prohibir el token EOS. Esto obliga al modelo a nunca terminar la generación prematuramente",
"Skip Special Tokens": "Omitir tokens especiales",
"Beam search": "Búsqueda de haz",
"Number of Beams": "Número de haces",
@@ -83,9 +83,9 @@
"Contrastive search": "Búsqueda contrastiva",
"Penalty Alpha": "Alfa de penalización",
"Seed": "Semilla",
- "Epsilon Cutoff": "Corte epsilon",
- "Eta Cutoff": "Corte eta",
- "Negative Prompt": "Indicación negativa",
+ "Epsilon Cutoff": "Corte Epsilon",
+ "Eta Cutoff": "Corte Eta",
+ "Negative Prompt": "Indicaciónes negativas",
"Mirostat (mode=1 is only for llama.cpp)": "Mirostat (modo=1 es solo para llama.cpp)",
"Mirostat is a thermostat for output perplexity": "Mirostat es un termostato para la perplejidad de salida",
"Add text here that would make the AI generate things you don't want in your outputs.": "Agrega aquí texto que haría que la IA genere cosas que no quieres en tus salidas.",
@@ -102,32 +102,32 @@
"NSFW Encouraged": "NSFW Alentado",
"Tell the AI that NSFW is allowed.": "Indica a la IA que se permite contenido NSFW.",
"NSFW Prioritized": "NSFW Priorizado",
- "NSFW prompt text goes first in the prompt to emphasize its effect.": "El texto de la indicación NSFW va primero en la indicación para enfatizar su efecto.",
- "Streaming": "Transmisión",
+ "NSFW prompt text goes first in the prompt to emphasize its effect.": "El texto de las indicaciones NSFW va primero en la indicación para enfatizar su efecto.",
+ "Streaming": "Transmisión (Streaming)",
"Dynamic Temperature": "Temperatura dinámica",
- "Restore current preset": "Restaurar la configuración actual",
- "Neutralize Samplers": "Neutralizar los muestreadores",
- "Text Completion presets": "Preajustes de completado de texto",
+ "Restore current preset": "Restaurar el preajuste actual",
+ "Neutralize Samplers": "Neutralizar muestreadores",
+ "Text Completion presets": "Preajustes de Completado de Texto",
"Documentation on sampling parameters": "Documentación sobre parámetros de muestreo",
"Set all samplers to their neutral/disabled state.": "Establecer todos los muestreadores en su estado neutral/desactivado.",
"Only enable this if your model supports context sizes greater than 4096 tokens": "Habilita esto solo si tu modelo admite tamaños de contexto mayores de 4096 tokens",
"Display the response bit by bit as it is generated": "Mostrar la respuesta poco a poco según se genera",
"Generate only one line per request (KoboldAI only, ignored by KoboldCpp).": "Generar solo una línea por solicitud (solo KoboldAI, ignorado por KoboldCpp).",
- "Ban the End-of-Sequence (EOS) token (with KoboldCpp, and possibly also other tokens with KoboldAI).": "Prohibir el token Fin-de-secuencia (EOS) (con KoboldCpp, y posiblemente también otros tokens con KoboldAI).",
+ "Ban the End-of-Sequence (EOS) token (with KoboldCpp, and possibly also other tokens with KoboldAI).": "Prohibir el token Fin-de-Secuencia (EOS) (con KoboldCpp, y posiblemente también otros tokens con KoboldAI).",
"Good for story writing, but should not be used for chat and instruct mode.": "Bueno para escribir historias, pero no debería usarse para el modo de chat e instrucción.",
"Enhance Definitions": "Mejorar Definiciones",
"Use OAI knowledge base to enhance definitions for public figures and known fictional characters": "Utilizar la base de conocimientos de OAI para mejorar las definiciones de figuras públicas y personajes ficticios conocidos",
"Wrap in Quotes": "Envolver entre comillas",
"Wrap entire user message in quotes before sending.": "Envolver todo el mensaje del usuario entre comillas antes de enviarlo.",
- "Leave off if you use quotes manually for speech.": "Omite esto si usas comillas manualmente para el discurso.",
- "Main prompt": "Indicación principal",
- "The main prompt used to set the model behavior": "La indicación principal utilizada para establecer el comportamiento del modelo",
- "NSFW prompt": "Indicación NSFW",
- "Prompt that is used when the NSFW toggle is on": "Indicación que se utiliza cuando el interruptor NSFW está activado",
- "Jailbreak prompt": "Indicación de jailbreak",
- "Prompt that is used when the Jailbreak toggle is on": "Indicación que se utiliza cuando el interruptor Jailbreak está activado",
- "Impersonation prompt": "Indicación de suplantación de identidad",
- "Prompt that is used for Impersonation function": "Indicación que se utiliza para la función de suplantación de identidad",
+ "Leave off if you use quotes manually for speech.": "Omite esto si usas comillas manualmente para diálogo.",
+ "Main prompt": "Indicaciónes principales",
+ "The main prompt used to set the model behavior": "Las indicaciónes principales utilizadas para establecer el comportamiento del modelo",
+ "NSFW prompt": "Indicaciónes NSFW",
+ "Prompt that is used when the NSFW toggle is on": "Indicaciónes que se utilizan cuando el interruptor NSFW está activado",
+ "Jailbreak prompt": "Indicaciónes de jailbreak",
+ "Prompt that is used when the Jailbreak toggle is on": "Indicaciónes que se utilizan cuando el interruptor Jailbreak está activado",
+ "Impersonation prompt": "Indicaciónes de Suplantación",
+ "Prompt that is used for Impersonation function": "Indicación que se utiliza para la función de Suplantación",
"Logit Bias": "Sesgo de logit",
"Helps to ban or reenforce the usage of certain words": "Ayuda a prohibir o reforzar el uso de ciertas palabras",
"View / Edit bias preset": "Ver / Editar preajuste de sesgo",
@@ -136,17 +136,17 @@
"Message to send when auto-jailbreak is on.": "Mensaje para enviar cuando el auto-jailbreak está activado.",
"Jailbreak confirmation reply": "Respuesta de confirmación de jailbreak",
"Bot must send this back to confirm jailbreak": "El bot debe enviar esto de vuelta para confirmar el jailbreak",
- "Character Note": "Nota del personaje",
+ "Character Note": "Nota de personaje",
"Influences bot behavior in its responses": "Influye en el comportamiento del bot en sus respuestas",
"Connect": "Conectar",
"Test Message": "Mensaje de prueba",
"API": "API",
"KoboldAI": "KoboldAI",
- "Use Horde": "Usar Horda",
+ "Use Horde": "Usar Horde",
"API url": "URL de la API",
"PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (Modo envolvente para API de OpenAI)",
- "Register a Horde account for faster queue times": "Registra una cuenta de la Horda para tiempos de espera más rápidos",
- "Learn how to contribute your idle GPU cycles to the Hord": "Aprende cómo contribuir con tus ciclos de GPU inactivos a la Horda",
+ "Register a Horde account for faster queue times": "Registra una cuenta de Horde para tiempos de espera más rápidos",
+ "Learn how to contribute your idle GPU cycles to the Hord": "Aprende cómo contribuir con tus ciclos de GPU inactivos a Horde",
"Adjust context size to worker capabilities": "Ajusta el tamaño del contexto a las capacidades del trabajador",
"Adjust response length to worker capabilities": "Ajusta la longitud de la respuesta a las capacidades del trabajador",
"API key": "Clave API",
@@ -168,7 +168,7 @@
"For privacy reasons": "Por razones de privacidad, la clave API se oculta después de actualizar la página",
"Models": "Modelos",
"Hold Control / Command key to select multiple models.": "Mantén presionada la tecla Control / Comando para seleccionar varios modelos.",
- "Horde models not loaded": "Modelos de la Horda no cargados",
+ "Horde models not loaded": "Modelos de Horde no cargados",
"Not connected...": "No conectado...",
"Novel API key": "Clave API de Novel",
"Follow": "Seguir",
@@ -199,7 +199,7 @@
"OpenAI Model": "Modelo de OpenAI",
"Claude API Key": "Clave API de Claude",
"Get your key from": "Obtén tu clave desde",
- "Anthropic's developer console": "consola de desarrolladores de Anthropic",
+ "Anthropic's developer console": "la consola de desarrolladores de Anthropic",
"Slack and Poe cookies will not work here, do not bother trying.": "Las cookies de Slack y Poe no funcionarán aquí, no te molestes en intentarlo.",
"Claude Model": "Modelo de Claude",
"Scale API Key": "Clave API de Scale",
@@ -214,72 +214,72 @@
"OpenRouter API Key": "Clave API de OpenRouter",
"Connect to the API": "Conectar a la API",
"OpenRouter Model": "Modelo de OpenRouter",
- "View Remaining Credits": "Ver créditos restantes",
+ "View Remaining Credits": "Ver Créditos Restantes",
"Click Authorize below or get the key from": "Haz clic en Autorizar a continuación o obtén la clave desde",
"Auto-connect to Last Server": "Conexión automática al último servidor",
"View hidden API keys": "Ver claves API ocultas",
"Advanced Formatting": "Formato avanzado",
- "Context Template": "Plantilla de contexto",
+ "Context Template": "Plantilla de Contexto",
"AutoFormat Overrides": "Anulaciones de AutoFormato",
"Disable description formatting": "Desactivar formato de descripción",
"Disable personality formatting": "Desactivar formato de personalidad",
"Disable scenario formatting": "Desactivar formato de escenario",
"Disable example chats formatting": "Desactivar formato de chats de ejemplo",
"Disable chat start formatting": "Desactivar formato de inicio de chat",
- "Custom Chat Separator": "Separador de chat personalizado",
- "Replace Macro in Custom Stopping Strings": "Reemplazar macro en cadenas de detención personalizadas",
- "Strip Example Messages from Prompt": "Eliminar mensajes de ejemplo de la solicitud",
+ "Custom Chat Separator": "Separador de Chat Personalizado",
+ "Replace Macro in Custom Stopping Strings": "Reemplazar macro en Cadenas de Detención Personalizadas",
+ "Strip Example Messages from Prompt": "Eliminar Mensajes de Ejemplo de las Indicaciones",
"Story String": "Cadena de historia",
"Example Separator": "Separador de ejemplo",
"Chat Start": "Inicio de chat",
"Activation Regex": "Regex de activación",
- "Instruct Mode": "Modo de instrucción",
- "Wrap Sequences with Newline": "Envolver secuencias con nueva línea",
- "Include Names": "Incluir nombres",
- "Force for Groups and Personas": "Forzar para grupos y personas",
- "System Prompt": "Solicitud del sistema",
- "Instruct Mode Sequences": "Secuencias en modo de instrucción",
- "Input Sequence": "Secuencia de entrada",
- "Output Sequence": "Secuencia de salida",
- "First Output Sequence": "Primera secuencia de salida",
- "Last Output Sequence": "Última secuencia de salida",
- "System Sequence Prefix": "Prefijo de secuencia del sistema",
- "System Sequence Suffix": "Sufijo de secuencia del sistema",
- "Stop Sequence": "Secuencia de parada",
- "Context Formatting": "Formato de contexto",
- "(Saved to Context Template)": "(Guardado en plantilla de contexto)",
+ "Instruct Mode": "Modo Instrucción",
+ "Wrap Sequences with Newline": "Envolver Secuencias con Nueva línea",
+ "Include Names": "Incluir Nombres",
+ "Force for Groups and Personas": "Forzar para Grupos y Personas",
+ "System Prompt": "Indicaciones del Sistema",
+ "Instruct Mode Sequences": "Secuencias en Modo Instrucción",
+ "Input Sequence": "Secuencia de Entrada",
+ "Output Sequence": "Secuencia de Salida",
+ "First Output Sequence": "Primera Secuencia de Salida",
+ "Last Output Sequence": "Última Secuencia de Salida",
+ "System Sequence Prefix": "Prefijo de Secuencia del Sistema",
+ "System Sequence Suffix": "Sufijo de Secuencia del Sistema",
+ "Stop Sequence": "Secuencia de Parada",
+ "Context Formatting": "Formato de Contexto",
+ "(Saved to Context Template)": "(Guardado en Plantilla de Contexto)",
"Tokenizer": "Tokenizador",
"None / Estimated": "Ninguno / Estimado",
"Sentencepiece (LLaMA)": "Sentencepiece (LLaMA)",
"Token Padding": "Relleno de token",
"Save preset as": "Guardar preajuste como",
- "Always add character's name to prompt": "Siempre agregar el nombre del personaje a la solicitud",
- "Use as Stop Strings": "Usar como cadenas de parada",
- "Bind to Context": "Vincular al contexto",
+ "Always add character's name to prompt": "Siempre agregar el nombre del personaje a las indicaciones",
+ "Use as Stop Strings": "Usar como Cadenas de Parada",
+ "Bind to Context": "Vincular al Contexto",
"Generate only one line per request": "Generar solo una línea por solicitud",
- "Misc. Settings": "Configuraciones misceláneas",
+ "Misc. Settings": "Configuraciones Misceláneas",
"Auto-Continue": "Autocontinuar",
- "Collapse Consecutive Newlines": "Colapsar nuevas líneas consecutivas",
- "Allow for Chat Completion APIs": "Permitir APIs de finalización de chat",
+ "Collapse Consecutive Newlines": "Colapsar Nuevas líneas Consecutivas",
+ "Allow for Chat Completion APIs": "Permitir para APIs de Completado de Chat",
"Target length (tokens)": "Longitud objetivo (tokens)",
- "Keep Example Messages in Prompt": "Mantener mensajes de ejemplo en la solicitud",
- "Remove Empty New Lines from Output": "Eliminar nuevas líneas vacías de la salida",
+ "Keep Example Messages in Prompt": "Mantener Mensajes de Ejemplo en las indicaciones",
+ "Remove Empty New Lines from Output": "Eliminar Nuevas líneas vacías de la salida",
"Disabled for all models": "Desactivado para todos los modelos",
"Automatic (based on model name)": "Automático (basado en el nombre del modelo)",
"Enabled for all models": "Activado para todos los modelos",
- "Anchors Order": "Orden de anclajes",
+ "Anchors Order": "Orden de Anclajes",
"Character then Style": "Personaje luego estilo",
"Style then Character": "Estilo luego personaje",
"Character Anchor": "Anclaje de personaje",
"Style Anchor": "Anclaje de estilo",
- "World Info": "Información del mundo",
+ "World Info": "Información de Mundo (WI)",
"Scan Depth": "Profundidad de escaneo",
"Case-Sensitive": "Sensible a mayúsculas y minúsculas",
"Match Whole Words": "Coincidir con palabras completas",
"Use global setting": "Usar configuración global",
"Yes": "Sí",
"No": "No",
- "Context %": "Contexto %",
+ "Context %": "% de Contexto",
"Budget Cap": "Límite de presupuesto",
"(0 = disabled)": "(0 = desactivado)",
"depth": "profundidad",
@@ -289,29 +289,29 @@
"None": "Ninguno",
"User Settings": "Configuraciones de usuario",
"UI Mode": "Modo de IU",
- "UI Language": "Idioma",
+ "UI Language": "Idioma de la UI",
"MovingUI Preset": "Preajuste de MovingUI",
"UI Customization": "Personalización de la IU",
- "Avatar Style": "Estilo de avatar",
+ "Avatar Style": "Estilo de Avatar",
"Circle": "Círculo",
"Rectangle": "Rectángulo",
"Square": "Cuadrado",
- "Chat Style": "Estilo de chat",
+ "Chat Style": "Estilo de Chat",
"Default": "Predeterminado",
"Bubbles": "Burbujas",
"No Blur Effect": "Sin efecto de desenfoque",
"No Text Shadows": "Sin sombras de texto",
"Waifu Mode": "Modo Waifu",
"Message Timer": "Temporizador de mensajes",
- "Model Icon": "Ícono del modelo",
+ "Model Icon": "Ícono del Modelo",
"# of messages (0 = disabled)": "# de mensajes (0 = desactivado)",
- "Advanced Character Search": "Búsqueda avanzada de personajes",
+ "Advanced Character Search": "Búsqueda Avanzada de Personajes",
"Allow {{char}}: in bot messages": "Permitir {{char}}: en mensajes de bot",
"Allow {{user}}: in bot messages": "Permitir {{user}}: en mensajes de bot",
"Show tags in responses": "Mostrar etiquetas en respuestas",
"Aux List Field": "Campo de lista auxiliar",
- "Lorebook Import Dialog": "Diálogo de importación de libro de historia",
- "MUI Preset": "Preset MUI",
+ "Lorebook Import Dialog": "Diálogo de Importación de Libro de Historia",
+ "MUI Preset": "Preajuste MUI",
"If set in the advanced character definitions, this field will be displayed in the characters list.": "Si se establece en las definiciones avanzadas de personajes, este campo se mostrará en la lista de personajes.",
"Relaxed API URLS": "URLS de API relajadas",
"Custom CSS": "CSS personalizado",
@@ -322,7 +322,7 @@
"Relax message trim in Groups": "Relajar recorte de mensajes en Grupos",
"Characters Hotswap": "Cambio rápido de personajes",
"Request token probabilities": "Solicitar probabilidades de tokens",
- "Movable UI Panels": "Paneles de UI móviles",
+ "Movable UI Panels": "Paneles de UI Móviles",
"Reset Panels": "Restablecer paneles",
"UI Colors": "Colores de UI",
"Main Text": "Texto principal",
@@ -331,44 +331,44 @@
"Shadow Color": "Color de sombra",
"FastUI BG": "Fondo de FastUI",
"Blur Tint": "Tinte de desenfoque",
- "Font Scale": "Escala de fuente",
+ "Font Scale": "Tamaño de fuente",
"Blur Strength": "Fuerza de desenfoque",
"Text Shadow Width": "Ancho de sombra de texto",
- "UI Theme Preset": "Preset de tema de UI",
+ "UI Theme Preset": "Preajuste de tema de UI",
"Power User Options": "Opciones avanzadas de usuario",
"Swipes": "Deslizamientos",
"Miscellaneous": "Varios",
"Theme Toggles": "Conmutadores de tema",
- "Background Sound Only": "Solo sonido de fondo",
+ "Background Sound Only": "Solo Sonido de Fondo",
"Auto-load Last Chat": "Cargar automáticamente el último chat",
- "Auto-save Message Edits": "Guardar automáticamente las ediciones de mensajes",
+ "Auto-save Message Edits": "Guardar automáticamente las Ediciones de Mensajes",
"Auto-fix Markdown": "Auto-corregir Markdown",
"Allow : in bot messages": "Permitir : en mensajes de bot",
- "Auto-scroll Chat": "Chat de desplazamiento automático",
+ "Auto-scroll Chat": "Desplazamiento de Chat Automático",
"Render Formulas": "Renderizar fórmulas",
"Send on Enter": "Enviar al presionar Enter",
"Always disabled": "Siempre desactivado",
"Automatic (desktop)": "Automático (escritorio)",
"Always enabled": "Siempre activado",
"Debug Menu": "Menú de depuración",
- "Restore User Input": "Restaurar entrada de usuario",
+ "Restore User Input": "Restaurar Entrada de Usuario",
"Character Handling": "Manipulación de personajes",
"Example Messages Behavior": "Comportamiento de mensajes de ejemplo",
- "Gradual push-out": "Empuje gradual",
- "Chat/Message Handling": "Manipulación de chat/mensaje",
+ "Gradual push-out": "Expulsión gradual",
+ "Chat/Message Handling": "Manipulación de Chat/Mensaje",
"Always include examples": "Siempre incluir ejemplos",
"Never include examples": "Nunca incluir ejemplos",
- "Forbid External Media": "Prohibir medios externos",
- "System Backgrounds": "Fondos del sistema",
+ "Forbid External Media": "Prohibir Medios Externos",
+ "System Backgrounds": "Fondos del Sistema",
"Name": "Nombre",
"Your Avatar": "Tu avatar",
- "Extensions API:": "API de extensiones:",
+ "Extensions API:": "API de Extensiones:",
"SillyTavern-extras": "Extras de SillyTavern",
"Auto-connect": "Conexión automática",
- "Active extensions": "Extensiones activas",
- "Extension settings": "Configuraciones de extensión",
+ "Active extensions": "Extensiones Activas",
+ "Extension settings": "Configuraciones de Extensión",
"Description": "Descripción",
- "First message": "Primer mensaje",
+ "First message": "Primer Mensaje",
"Group Controls": "Controles de grupo",
"Group reply strategy": "Estrategia de respuesta de grupo",
"Natural order": "Orden natural",
@@ -387,19 +387,19 @@
"Circumstances and context of the dialogue": "Circunstancias y contexto del diálogo",
"Talkativeness": "Habladuría",
"How often the chracter speaks in": "Con qué frecuencia habla el personaje en",
- "group chats!": "chats de grupo!",
+ "group chats!": "chats grupales!",
"Shy": "Tímido",
"Normal": "Normal",
- "Chatty": "Charlatán",
+ "Chatty": "Parlanchín",
"Examples of dialogue": "Ejemplos de diálogo",
"Forms a personality more clearly": "Forma una personalidad más clara",
"Save": "Guardar",
- "World Info Editor": "Editor de información del mundo",
+ "World Info Editor": "Editor de Información de Mundo (WI)",
"New summary": "Nuevo resumen",
"Export": "Exportar",
"Delete World": "Eliminar mundo",
"Chat History": "Historial de chat",
- "Group Chat Scenario Override": "Anulación de escenario de chat grupal",
+ "Group Chat Scenario Override": "Anulación de escenario de Chat Grupal",
"All group members will use the following scenario text instead of what is specified in their character cards.": "Todos los miembros del grupo usarán el siguiente texto de escenario en lugar de lo que se especifica en sus tarjetas de personaje.",
"Keywords": "Palabras clave",
"Separate with commas": "Separar con comas",
@@ -424,60 +424,60 @@
"Start new chat": "Iniciar nuevo chat",
"View past chats": "Ver chats anteriores",
"Delete messages": "Eliminar mensajes",
- "Impersonate": "Hacerse pasar por",
+ "Impersonate": "Suplantar",
"Regenerate": "Regenerar",
"PNG": "PNG",
"JSON": "JSON",
- "presets": "ajustes preestablecidos",
+ "presets": "preajustes",
"Message Sound": "Sonido de mensaje",
"Author's Note": "Nota del autor",
"Send Jailbreak": "Enviar Jailbreak",
"Replace empty message": "Reemplazar mensaje vacío",
"Send this text instead of nothing when the text box is empty.": "Enviar este texto en lugar de nada cuando el cuadro de texto está vacío.",
- "NSFW avoidance prompt": "Indicación de evitación de NSFW",
- "Prompt that is used when the NSFW toggle is off": "Indicación que se usa cuando el interruptor de NSFW está apagado",
- "Advanced prompt bits": "Bits de indicación avanzada",
- "World Info format": "Formato de información del mundo",
- "Wraps activated World Info entries before inserting into the prompt. Use {0} to mark a place where the content is inserted.": "Envuelve las entradas de información del mundo activadas antes de insertarlas en la indicación. Use {0} para marcar un lugar donde se inserta el contenido.",
+ "NSFW avoidance prompt": "Indicaciones de evitación de NSFW",
+ "Prompt that is used when the NSFW toggle is off": "Indicaciones que se usa cuando el interruptor de NSFW está apagado",
+ "Advanced prompt bits": "Bits de Indicaciones Avanzadas",
+ "World Info format": "Formato de Información de Mundo (WI)",
+ "Wraps activated World Info entries before inserting into the prompt. Use {0} to mark a place where the content is inserted.": "Envuelve las entradas de Información de Mundo (WI) activadas antes de insertarlas en las indicaciones. Use {0} para marcar un lugar donde se inserta el contenido.",
"Unrestricted maximum value for the context slider": "Valor máximo sin restricciones para el control deslizante de contexto",
- "Chat Completion Source": "Fuente de completado de chat",
- "Avoid sending sensitive information to the Horde.": "Evite enviar información sensible a la Horda.",
+ "Chat Completion Source": "Fuente de Completado de Chat",
+ "Avoid sending sensitive information to the Horde.": "Evite enviar información sensible a Horde.",
"Review the Privacy statement": "Revise la declaración de privacidad",
- "Learn how to contribute your idel GPU cycles to the Horde": "Aprende cómo contribuir con tus ciclos de GPU inactivos a la Horda",
+ "Learn how to contribute your idel GPU cycles to the Horde": "Aprende cómo contribuir con tus ciclos de GPU inactivos a Horde",
"Trusted workers only": "Solo trabajadores de confianza",
"For privacy reasons, your API key will be hidden after you reload the page.": "Por razones de privacidad, su clave de API se ocultará después de que vuelva a cargar la página.",
- "-- Horde models not loaded --": "-- Modelos de la Horda no cargados --",
+ "-- Horde models not loaded --": "-- Modelos de Horde no cargados --",
"Example: http://127.0.0.1:5000/api ": "Ejemplo: http://127.0.0.1:5000/api",
"No connection...": "Sin conexión...",
- "Get your NovelAI API Key": "Obtenga su clave de API de NovelAI",
- "KoboldAI Horde": "Horda de KoboldAI",
+ "Get your NovelAI API Key": "Obtenga su Clave de API de NovelAI",
+ "KoboldAI Horde": "Horde de KoboldAI",
"Text Gen WebUI (ooba)": "Text Gen WebUI (ooba)",
"NovelAI": "NovelAI",
- "Chat Completion (OpenAI, Claude, Window/OpenRouter, Scale)": "Completado de chat (OpenAI, Claude, Window/OpenRouter, Scale)",
+ "Chat Completion (OpenAI, Claude, Window/OpenRouter, Scale)": "Completado de Chat (OpenAI, Claude, Window/OpenRouter, Scale)",
"OpenAI API key": "Clave de API de OpenAI",
"Trim spaces": "Recortar espacios",
- "Trim Incomplete Sentences": "Recortar oraciones incompletas",
- "Include Newline": "Incluir nueva línea",
+ "Trim Incomplete Sentences": "Recortar Oraciones Incompletas",
+ "Include Newline": "Incluir Nueva línea",
"Non-markdown strings": "Cadenas no Markdown",
- "Replace Macro in Sequences": "Reemplazar macro en secuencias",
- "Presets": "Ajustes preestablecidos",
+ "Replace Macro in Sequences": "Reemplazar Macro en Secuencias",
+ "Presets": "Preajustes",
"Separator": "Separador",
- "Start Reply With": "Iniciar respuesta con",
+ "Start Reply With": "Iniciar Respuesta con",
"Show reply prefix in chat": "Mostrar prefijo de respuesta en el chat",
- "Worlds/Lorebooks": "Mundos/Libros de historia",
- "Active World(s)": "Mundo(s) activo(s)",
+ "Worlds/Lorebooks": "Mundos/Libros de Historia",
+ "Active World(s)": "Mundo(s) Activo(s)",
"Activation Settings": "Configuraciones de activación",
- "Character Lore Insertion Strategy": "Estrategia de inserción de lore de personajes",
+ "Character Lore Insertion Strategy": "Estrategia de Inserción de Historia de Dersonajes",
"Sorted Evenly": "Ordenado uniformemente",
- "Active World(s) for all chats": "Mundo(s) activo(s) para todos los chats",
- "-- World Info not found --": "-- Información del mundo no encontrada --",
+ "Active World(s) for all chats": "Mundo(s) Activo(s) para todos los chats",
+ "-- World Info not found --": "-- Información de Mundo (WI) no encontrada --",
"--- Pick to Edit ---": "--- Seleccionar para editar ---",
"or": "o",
"New": "Nuevo",
"Priority": "Prioridad",
"Custom": "Personalizado",
- "Title A-Z": "Título de la A a la Z",
- "Title Z-A": "Título de la Z a la A",
+ "Title A-Z": "Título de A a Z",
+ "Title Z-A": "Título de Z a A",
"Tokens ↗": "Tokens ↗",
"Tokens ↘": "Tokens ↘",
"Depth ↗": "Profundidad ↗",
@@ -486,26 +486,26 @@
"Order ↘": "Orden ↘",
"UID ↗": "UID ↗",
"UID ↘": "UID ↘",
- "Trigger% ↗": "Desencadenar% ↗",
- "Trigger% ↘": "Desencadenar% ↘",
+ "Trigger% ↗": "Activador% ↗",
+ "Trigger% ↘": "Activador% ↘",
"Order:": "Orden:",
"Depth:": "Profundidad:",
- "Character Lore First": "Lore del personaje primero",
- "Global Lore First": "Lore global primero",
- "Recursive Scan": "Exploración recursiva",
+ "Character Lore First": "Historia de Personaje Primero",
+ "Global Lore First": "Historia Global Primero",
+ "Recursive Scan": "Escaneo Recursiva",
"Case Sensitive": "Sensible a mayúsculas y minúsculas",
"Match whole words": "Coincidir palabras completas",
- "Alert On Overflow": "Alerta en desbordamiento",
- "World/Lore Editor": "Editor de mundo/Lore",
+ "Alert On Overflow": "Alerta en Desbordamiento",
+ "World/Lore Editor": "Editor de Mundo/Historia",
"--- None ---": "--- Ninguno ---",
"Comma separated (ignored if empty)": "Separado por comas (ignorado si está vacío)",
"Use Probability": "Usar Probabilidad",
"Exclude from recursion": "Excluir de la recursión",
"Entry Title/Memo": "Título/Memo",
"Position:": "Posición:",
- "T_Position": "↑Char: antes de definiciones de caracteres\n↓Char: después de definiciones de caracteres\n↑AN: antes de notas del autor\n↓AN: después de notas del autor\n@D: en profundidad",
- "Before Char Defs": "Antes de Definiciones de Caracteres",
- "After Char Defs": "Después de Definiciones de Caracteres",
+ "T_Position": "↑Char: antes de definiciones de personajes\n↓Char: después de definiciones de personajes\n↑AN: antes de notas del autor\n↓AN: después de notas del autor\n@D: en profundidad",
+ "Before Char Defs": "Antes de Def. de Personaje",
+ "After Char Defs": "Después de Def. de Personaje",
"Before AN": "Antes de AN",
"After AN": "Después de AN",
"at Depth": "en Profundidad",
@@ -521,7 +521,7 @@
"Chat Background": "Fondo de Chat",
"UI Background": "Fondo de IU",
"Mad Lab Mode": "Modo Laboratorio Loco",
- "Show Message Token Count": "Mostrar Conteo de Tokens de Mensaje",
+ "Show Message Token Count": "Mostrar Conteo de Tokens en Mensaje",
"Compact Input Area (Mobile)": "Área de Entrada Compacta (Móvil)",
"Zen Sliders": "Deslizadores Zen",
"UI Border": "Borde de IU",
@@ -534,9 +534,9 @@
"Streaming FPS": "FPS de Transmisión",
"Gestures": "Gestos",
"Message IDs": "IDs de Mensaje",
- "Prefer Character Card Prompt": "Preferir Tarjeta de Personaje con Indicación",
- "Prefer Character Card Jailbreak": "Preferir Jailbreak de Tarjeta de Personaje",
- "Press Send to continue": "Presione Enviar para continuar",
+ "Prefer Character Card Prompt": "Preferir Indicaciones en Tarjeta de Personaje",
+ "Prefer Character Card Jailbreak": "Preferir Jailbreak en Tarjeta de Personaje",
+ "Press Send to continue": "Presionar Enviar para continuar",
"Quick 'Continue' button": "Botón 'Continuar' Rápido",
"Log prompts to console": "Registrar indicaciones en la consola",
"Never resize avatars": "Nunca redimensionar avatares",
@@ -569,11 +569,11 @@
"Show the number of tokens in each message in the chat log": "Mostrar el número de tokens en cada mensaje en el registro de chat",
"Single-row message input area. Mobile only, no effect on PC": "Área de entrada de mensaje de una sola fila. Solo móvil, sin efecto en PC",
"In the Character Management panel, show quick selection buttons for favorited characters": "En el panel de Gestión de Personajes, mostrar botones de selección rápida para personajes favoritos",
- "Show tagged character folders in the character list": "Mostrar carpetas de personajes etiquetadas en la lista de personajes",
+ "Show tagged character folders in the character list": "Mostrar carpetas de personajes etiquetados en la lista de personajes",
"Play a sound when a message generation finishes": "Reproducir un sonido cuando finaliza la generación de un mensaje",
"Only play a sound when ST's browser tab is unfocused": "Solo reproducir un sonido cuando la pestaña del navegador de ST no está enfocada",
"Reduce the formatting requirements on API URLs": "Reducir los requisitos de formato en las URL de API",
- "Ask to import the World Info/Lorebook for every new character with embedded lorebook. If unchecked, a brief message will be shown instead": "Pedir importar la Información Mundial/Libro de Leyendas para cada nuevo personaje con un lorebook incrustado. Si no está marcado, se mostrará un mensaje breve en su lugar",
+ "Ask to import the World Info/Lorebook for every new character with embedded lorebook. If unchecked, a brief message will be shown instead": "Pedir importar Información de Mundo (WI)/Libro de Historia para cada nuevo personaje con un Libro de Historia incrustado. Si no está marcado, se mostrará un mensaje breve en su lugar",
"Restore unsaved user input on page refresh": "Restaurar la entrada de usuario no guardada al actualizar la página",
"Allow repositioning certain UI elements by dragging them. PC only, no effect on mobile": "Permitir reposicionar ciertos elementos de IU arrastrándolos. Solo PC, sin efecto en móviles",
"MovingUI preset. Predefined/saved draggable positions": "Preconfiguración MovingUI. Posiciones arrastrables predefinidas/guardadas",
@@ -581,7 +581,7 @@
"Apply a custom CSS style to all of the ST GUI": "Aplicar un estilo CSS personalizado a toda la GUI de ST",
"Use fuzzy matching, and search characters in the list by all data fields, not just by a name substring": "Usar coincidencia difusa y buscar personajes en la lista por todos los campos de datos, no solo por una subcadena de nombre",
"If checked and the character card contains a prompt override (System Prompt), use that instead": "Si está marcado y la tarjeta de personaje contiene una anulación de indicación (Indicación del sistema), usar eso en su lugar",
- "If checked and the character card contains a jailbreak override (Post History Instruction), use that instead": "Si está marcado y la tarjeta de personaje contiene una anulación de jailbreak (Instrucción de Historial de Publicaciones), usar eso en su lugar",
+ "If checked and the character card contains a jailbreak override (Post History Instruction), use that instead": "Si está marcado y la tarjeta de personaje contiene una anulación de jailbreak (Instrucciones Post Historial), usar eso en su lugar",
"Avoid cropping and resizing imported character images. When off, crop/resize to 400x600": "Evitar recortar y redimensionar imágenes de personajes importadas. Cuando esté desactivado, recortar/redimensionar a 400x600",
"Show actual file names on the disk, in the characters list display only": "Mostrar nombres de archivo reales en el disco, solo en la visualización de la lista de personajes",
"Prompt to import embedded card tags on character import. Otherwise embedded tags are ignored": "Solicitar importar etiquetas de tarjeta incrustadas al importar un personaje. De lo contrario, las etiquetas incrustadas se ignoran",
@@ -590,7 +590,7 @@
"Show arrow buttons on the last in-chat message to generate alternative AI responses. Both PC and mobile": "Mostrar botones de flecha en el último mensaje del chat para generar respuestas alternativas de la IA. Tanto PC como móvil",
"Allow using swiping gestures on the last in-chat message to trigger swipe generation. Mobile only, no effect on PC": "Permitir el uso de gestos de deslizamiento en el último mensaje del chat para activar la generación de deslizamiento. Solo móvil, sin efecto en PC",
"Save edits to messages without confirmation as you type": "Guardar ediciones en mensajes sin confirmación mientras escribe",
- "Render LaTeX and AsciiMath equation notation in chat messages. Powered by KaTeX": "Renderizar notación de ecuaciones LaTeX y AsciiMath en mensajes de chat. Alimentado por KaTeX",
+ "Render LaTeX and AsciiMath equation notation in chat messages. Powered by KaTeX": "Renderizar notación de ecuaciones LaTeX y AsciiMath en mensajes de chat. Impulsado por KaTeX",
"Disalow embedded media from other domains in chat messages": "No permitir medios incrustados de otros dominios en mensajes de chat",
"Skip encoding and characters in message text, allowing a subset of HTML markup as well as Markdown": "Omitir la codificación de los caracteres en el texto del mensaje, permitiendo un subconjunto de marcado HTML, así como Markdown",
"Allow AI messages in groups to contain lines spoken by other group members": "Permitir que los mensajes de IA en grupos contengan líneas habladas por otros miembros del grupo",
@@ -599,7 +599,7 @@
"Enable the auto-swipe function. Settings in this section only have an effect when auto-swipe is enabled": "Habilitar la función de deslizamiento automático. La configuración en esta sección solo tiene efecto cuando el deslizamiento automático está habilitado",
"If the generated message is shorter than this, trigger an auto-swipe": "Si el mensaje generado es más corto que esto, activar un deslizamiento automático",
"Reload and redraw the currently open chat": "Recargar y volver a dibujar el chat abierto actualmente",
- "Auto-Expand Message Actions": "Expansión Automática de Acciones de Mensaje",
+ "Auto-Expand Message Actions": "Expandir Automáticamente de Acciones de Mensaje",
"Not Connected": "No Conectado",
"Persona Management": "Gestión de Personas",
"Persona Description": "Descripción de Persona",
@@ -609,16 +609,16 @@
"In Story String / Chat Completion: Before Character Card": "En la Cadena de Historia / Completado de Chat: Antes de la Tarjeta de Personaje",
"In Story String / Chat Completion: After Character Card": "En la Cadena de Historia / Completado de Chat: Después de la Tarjeta de Personaje",
"In Story String / Prompt Manager": "En la Cadena de Historia / Administrador de Indicaciones",
- "Top of Author's Note": "Parte Superior de la Nota del Autor",
- "Bottom of Author's Note": "Parte Inferior de la Nota del Autor",
+ "Top of Author's Note": "Parte Superior de la Nota de Autor",
+ "Bottom of Author's Note": "Parte Inferior de la Nota de Autor",
"How do I use this?": "¿Cómo uso esto?",
"More...": "Más...",
- "Link to World Info": "Enlace a Información del Mundo",
- "Import Card Lore": "Importar Lore de Tarjeta",
+ "Link to World Info": "Enlazar a Información de Mundo (WI)",
+ "Import Card Lore": "Importar Historia de Tarjeta",
"Scenario Override": "Anulación de Escenario",
"Rename": "Renombrar",
"Character Description": "Descripción del Personaje",
- "Creator's Notes": "Notas del Creador",
+ "Creator's Notes": "Nota del Creador",
"A-Z": "A-Z",
"Z-A": "Z-A",
"Newest": "Más Reciente",
@@ -628,11 +628,11 @@
"Most chats": "Más Chats",
"Least chats": "Menos Chats",
"Back": "Volver",
- "Prompt Overrides (For OpenAI/Claude/Scale APIs, Window/OpenRouter, and Instruct mode)": "Sustituciones de Indicaciones (Para APIs de OpenAI/Claude/Scale, Ventana/OpenRouter y Modo Instrucción)",
- "Insert {{original}} into either box to include the respective default prompt from system settings.": "Inserte {{original}} en cualquiera de las casillas para incluir la indicación predeterminada respectiva de la configuración del sistema.",
- "Main Prompt": "Indicación Principal",
+ "Prompt Overrides (For OpenAI/Claude/Scale APIs, Window/OpenRouter, and Instruct mode)": "Anulaciones de Indicaciones (Para APIs de OpenAI/Claude/Scale, Window/OpenRouter y Modo Instrucción)",
+ "Insert {{original}} into either box to include the respective default prompt from system settings.": "Inserte {{original}} en cualquiera de las casillas para incluir las indicaciones predeterminadas respectivas de la configuración del sistema.",
+ "Main Prompt": "Indicaciones Principales",
"Jailbreak": "Jailbreak",
- "Creator's Metadata (Not sent with the AI prompt)": "Metadatos del Creador (No enviados con la indicación de la IA)",
+ "Creator's Metadata (Not sent with the AI prompt)": "Metadatos del Creador (No enviados con las indicaciones de la IA)",
"Everything here is optional": "Todo aquí es opcional",
"Created by": "Creado por",
"Character Version": "Versión del Personaje",
@@ -641,11 +641,11 @@
"Important to set the character's writing style.": "Importante para establecer el estilo de escritura del personaje.",
"ATTENTION!": "¡ATENCIÓN!",
"Samplers Order": "Orden de Muestreadores",
- "Samplers will be applied in a top-down order. Use with caution.": "Los Muestreadores se aplicarán en un orden de arriba hacia abajo. Úselo con precaución.",
+ "Samplers will be applied in a top-down order. Use with caution.": "Los Muestreadores se aplicarán en un orden de arriba hacia abajo. Úsalo con precaución.",
"Repetition Penalty": "Penalización por Repetición",
"Rep. Pen. Range.": "Rango de Pen. Rep.",
- "Rep. Pen. Freq.": "Frec. Pen. Rep.",
- "Rep. Pen. Presence": "Presencia Pen. Rep.",
+ "Rep. Pen. Freq.": "Frec. de Pen. Rep.",
+ "Rep. Pen. Presence": "Presencia de Pen. Rep.",
"Enter it in the box below:": "Introdúzcalo en la casilla de abajo:",
"separate with commas w/o space between": "separe con comas sin espacio entre ellas",
"Document": "Documento",
@@ -658,7 +658,7 @@
"Editing:": "Editando:",
"AI reply prefix": "Prefijo de Respuesta de IA",
"Custom Stopping Strings": "Cadenas de Detención Personalizadas",
- "JSON serialized array of strings": "Arreglo serializado JSON de cadenas",
+ "JSON serialized array of strings": "Arreglo de cadenas serializado en JSON",
"words you dont want generated separated by comma ','": "palabras que no desea generar separadas por coma ','",
"Extensions URL": "URL de Extensiones",
"API Key": "Clave de API",
@@ -670,10 +670,10 @@
"Chat Name (Optional)": "Nombre del Chat (Opcional)",
"Filter...": "Filtrar...",
"Search...": "Buscar...",
- "Any contents here will replace the default Main Prompt used for this character. (v2 spec: system_prompt)": "Cualquier contenido aquí reemplazará la Indicación Principal predeterminada utilizada para este personaje. (v2 especificación: system_prompt)",
- "Any contents here will replace the default Jailbreak Prompt used for this character. (v2 spec: post_history_instructions)": "Cualquier contenido aquí reemplazará la Indicación de Desbloqueo predeterminada utilizada para este personaje. (v2 especificación: post_history_instructions)",
+ "Any contents here will replace the default Main Prompt used for this character. (v2 spec: system_prompt)": "Cualquier contenido aquí reemplazará las Indicaciones Principales predeterminada utilizada para este personaje. (especificación v2: system_prompt)",
+ "Any contents here will replace the default Jailbreak Prompt used for this character. (v2 spec: post_history_instructions)": "Cualquier contenido aquí reemplazará las Indicaciones de Jailbreak predeterminada utilizada para este personaje. (especificación v2: post_history_instructions)",
"(Botmaker's name / Contact Info)": "(Nombre del creador del bot / Información de contacto)",
- "(If you want to track character versions)": "(Si desea rastrear las versiones de los personajes)",
+ "(If you want to track character versions)": "(Si desea rastrear versiones de personajes)",
"(Describe the bot, give use tips, or list the chat models it has been tested on. This will be displayed in the character list.)": "(Describa el bot, dé consejos de uso o enumere los modelos de chat en los que se ha probado. Esto se mostrará en la lista de personajes.)",
"(Write a comma-separated list of tags)": "(Escriba una lista de etiquetas separadas por comas)",
"(A brief description of the personality)": "(Una breve descripción de la personalidad)",
@@ -694,19 +694,19 @@
"Not connected to API!": "¡No conectado a la API!",
"AI Response Configuration": "Configuración de Respuesta de IA",
"AI Configuration panel will stay open": "El panel de Configuración de IA permanecerá abierto",
- "Update current preset": "Actualizar la configuración actual",
- "Create new preset": "Crear nueva configuración",
- "Import preset": "Importar configuración",
- "Export preset": "Exportar configuración",
- "Delete the preset": "Eliminar la configuración",
- "Auto-select this preset for Instruct Mode": "Auto-seleccionar esta configuración para el Modo Instrucción",
- "Auto-select this preset on API connection": "Auto-seleccionar esta configuración en la conexión de la API",
+ "Update current preset": "Actualizar el preajuste actual",
+ "Create new preset": "Crear nuevo preajuste",
+ "Import preset": "Importar preajuste",
+ "Export preset": "Exportar preajuste",
+ "Delete the preset": "Eliminar el preajuste",
+ "Auto-select this preset for Instruct Mode": "Auto-seleccionar este preajuste para el Modo Instrucción",
+ "Auto-select this preset on API connection": "Auto-seleccionar este preajuste en la conexión de la API",
"NSFW block goes first in the resulting prompt": "El bloque NSFW va primero en la indicación resultante",
- "Enables OpenAI completion streaming": "Permite la transmisión de completado de OpenAI",
+ "Enables OpenAI completion streaming": "Permite streaming de completado de OpenAI",
"Wrap user messages in quotes before sending": "Envolver los mensajes de usuario entre comillas antes de enviarlos",
- "Restore default prompt": "Restaurar la indicación predeterminada",
- "New preset": "Nueva configuración",
- "Delete preset": "Eliminar configuración",
+ "Restore default prompt": "Restaurar las indicaciones predeterminada",
+ "New preset": "Nuevo preajuste",
+ "Delete preset": "Eliminar preajuste",
"Restore default jailbreak": "Restaurar el jailbreak predeterminado",
"Restore default reply": "Restaurar la respuesta predeterminada",
"Restore default note": "Restaurar la nota predeterminada",
@@ -715,21 +715,21 @@
"Clear your API key": "Borrar tu clave de API",
"Refresh models": "Actualizar modelos",
"Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Obtenga su token de API de OpenRouter utilizando el flujo OAuth. Será redirigido a openrouter.ai",
- "Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!": "Verifica su conexión de API enviando un breve mensaje de prueba. ¡Tenga en cuenta que se le acreditará por ello!",
+ "Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!": "Verifica su conexión de API enviando un breve mensaje de prueba. ¡Tenga en cuenta que se le cobrará por ello!",
"Create New": "Crear Nuevo",
"Edit": "Editar",
"Locked = World Editor will stay open": "Bloqueado = El Editor de Mundo permanecerá abierto",
"Entries can activate other entries by mentioning their keywords": "Las entradas pueden activar otras entradas mencionando sus palabras clave",
- "Lookup for the entry keys in the context will respect the case": "La búsqueda de las claves de entrada en el contexto respetará el caso",
+ "Lookup for the entry keys in the context will respect the case": "La búsqueda de las claves de entrada en el contexto respetará mayúsculas y minúsculas",
"If the entry key consists of only one word, it would not be matched as part of other words": "Si la clave de entrada consiste en solo una palabra, no se emparejará como parte de otras palabras",
"Open all Entries": "Abrir Todas las Entradas",
"Close all Entries": "Cerrar Todas las Entradas",
"Create": "Crear",
- "Import World Info": "Importar Información del Mundo",
- "Export World Info": "Exportar Información del Mundo",
- "Delete World Info": "Eliminar Información del Mundo",
- "Duplicate World Info": "Duplicar Información del Mundo",
- "Rename World Info": "Renombrar Información del Mundo",
+ "Import World Info": "Importar Información de Mundo (WI)",
+ "Export World Info": "Exportar Información de Mundo (WI)",
+ "Delete World Info": "Eliminar Información de Mundo (WI)",
+ "Duplicate World Info": "Duplicar Información de Mundo (WI)",
+ "Rename World Info": "Renombrar Información de Mundo (WI)",
"Refresh": "Actualizar",
"Primary Keywords": "Palabras Clave Primarias",
"Logic": "Lógica",
@@ -752,13 +752,13 @@
"Character Management": "Gestión de Personajes",
"Locked = Character Management panel will stay open": "Bloqueado = El panel de Gestión de Personajes permanecerá abierto",
"Select/Create Characters": "Seleccionar/Crear Personajes",
- "Token counts may be inaccurate and provided just for reference.": "Las cuentas de tokens pueden ser inexactas y se proporcionan solo como referencia.",
+ "Token counts may be inaccurate and provided just for reference.": "El conteo de tokens pueden ser inexacto y se proporcionan solo como referencia.",
"Click to select a new avatar for this character": "Haga clic para seleccionar un nuevo avatar para este personaje",
"Example: [{{user}} is a 28-year-old Romanian cat girl.]": "Ejemplo: [{{user}} es una chica gata rumana de 28 años.]",
"Toggle grid view": "Alternar vista de cuadrícula",
"Add to Favorites": "Agregar a Favoritos",
"Advanced Definition": "Definición Avanzada",
- "Character Lore": "Trasfondo del personaje",
+ "Character Lore": "Historia (Trasfondo) del personaje",
"Export and Download": "Exportar y descargar",
"Duplicate Character": "Duplicar personaje",
"Create Character": "Crear personaje",
@@ -769,21 +769,21 @@
"Click to select a new avatar for this group": "Haz clic para seleccionar un nuevo avatar para este grupo",
"Set a group chat scenario": "Establecer un escenario de chat grupal",
"Restore collage avatar": "Restaurar avatar de collage",
- "Create New Character": "Crear nuevo personaje",
- "Import Character from File": "Importar personaje desde archivo",
+ "Create New Character": "Crear Nuevo Personaje",
+ "Import Character from File": "Importar Personaje desde Archivo",
"Import content from external URL": "Importar contenido desde URL externa",
- "Create New Chat Group": "Crear nuevo grupo de chat",
+ "Create New Chat Group": "Crear Nuevo Grupo de Chat",
"Characters sorting order": "Orden de clasificación de personajes",
"Add chat injection": "Agregar inyección de chat",
"Remove injection": "Eliminar inyección",
"Remove": "Eliminar",
- "Select a World Info file for": "Seleccionar un archivo de Información Mundial para",
- "Primary Lorebook": "Libro de historias primario",
- "A selected World Info will be bound to this character as its own Lorebook.": "Una Información Mundial seleccionada se vinculará a este personaje como su propio Libro de historias.",
+ "Select a World Info file for": "Seleccionar un archivo de Información de Mundo (WI) para",
+ "Primary Lorebook": "Libro de Historia primario",
+ "A selected World Info will be bound to this character as its own Lorebook.": "Una Información de Mundo (WI) seleccionada se vinculará a este personaje como su propio Libro de Historia.",
"When generating an AI reply, it will be combined with the entries from a global World Info selector.": "Al generar una respuesta de IA, se combinará con las entradas de un selector global de Información Mundial.",
- "Exporting a character would also export the selected Lorebook file embedded in the JSON data.": "Exportar un personaje también exportaría el archivo de Libro de historias seleccionado incrustado en los datos JSON.",
- "Additional Lorebooks": "Libros de historias adicionales",
- "Associate one or more auxillary Lorebooks with this character.": "Asociar uno o más Libros de historias auxiliares con este personaje.",
+ "Exporting a character would also export the selected Lorebook file embedded in the JSON data.": "Exportar un personaje también exportaría el archivo de Libro de Historia seleccionado incrustado en los datos JSON.",
+ "Additional Lorebooks": "Libros de Historia Adicionales",
+ "Associate one or more auxillary Lorebooks with this character.": "Asociar uno o más Libros de Historia auxiliares con este personaje.",
"NOTE: These choices are optional and won't be preserved on character export!": "NOTA: ¡Estas opciones son opcionales y no se conservarán al exportar el personaje!",
"Rename chat file": "Renombrar archivo de chat",
"Export JSONL chat file": "Exportar archivo de chat JSONL",
@@ -815,19 +815,19 @@
"Abort request": "Cancelar solicitud",
"Send a message": "Enviar un mensaje",
"Ask AI to write your message for you": "Pídele a la IA que escriba tu mensaje por ti",
- "Continue the last message": "Continuar con el último mensaje",
+ "Continue the last message": "Continuar el último mensaje",
"Bind user name to that avatar": "Vincular nombre de usuario a ese avatar",
- "Select this as default persona for the new chats.": "Seleccionar esto como persona predeterminada para los nuevos chats.",
+ "Select this as default persona for the new chats.": "Seleccionar esta persona como predeterminada para los nuevos chats.",
"Change persona image": "Cambiar imagen de persona",
"Delete persona": "Eliminar persona",
"Reduced Motion": "Movimiento reducido",
"Auto-select": "Auto-seleccionar",
"Automatically select a background based on the chat context": "Seleccionar automáticamente un fondo basado en el contexto del chat",
"Filter": "Filtro",
- "Exclude message from prompts": "Excluir mensaje de indicaciones",
- "Include message in prompts": "Incluir mensaje en indicaciones",
+ "Exclude message from prompts": "Excluir mensaje de las indicaciones",
+ "Include message in prompts": "Incluir mensaje en las indicaciones",
"Create checkpoint": "Crear punto de control",
- "Create Branch": "Crear rama",
+ "Create Branch": "Crear Rama",
"Embed file or image": "Insertar archivo o imagen",
"UI Theme": "Tema de interfaz de usuario",
"This message is invisible for the AI": "Este mensaje es invisible para la IA",
@@ -837,7 +837,7 @@
"Max Tokens Second": "Máximo de tokens por segundo",
"CFG": "CFG",
"No items": "Sin elementos",
- "Extras API key (optional)": "Clave API de extras (opcional)",
+ "Extras API key (optional)": "Clave API de Extras (opcional)",
"Notify on extension updates": "Notificar sobre actualizaciones de extensión",
"Toggle character grid view": "Alternar vista de cuadrícula de personajes",
"Bulk edit characters": "Editar personajes masivamente",
@@ -854,7 +854,7 @@
"Most tokens": "Más tokens",
"Least tokens": "Menos tokens",
"Random": "Aleatorio",
- "Skip Example Dialogues Formatting": "Omitir formato de diálogos de ejemplo",
+ "Skip Example Dialogues Formatting": "Omitir Formato de Diálogos de Ejemplo",
"Import a theme file": "Importar un archivo de tema",
"Export a theme file": "Exportar un archivo de tema",
"Unlocked Context Size": "Tamaño de contexto desbloqueado",
@@ -866,33 +866,33 @@
"Utility Prompts": "Indicaciones de utilidad",
"Add character names": "Agregar nombres de personajes",
"Send names in the message objects. Helps the model to associate messages with characters.": "Enviar nombres en los objetos de mensaje. Ayuda al modelo a asociar mensajes con personajes.",
- "Continue prefill": "Continuar con prefiltro",
+ "Continue prefill": "Continuar con prellenado",
"Continue sends the last message as assistant role instead of system message with instruction.": "Continuar envía el último mensaje como rol de asistente en lugar de mensaje del sistema con instrucciones.",
"Squash system messages": "Aplastar mensajes del sistema",
"Combines consecutive system messages into one (excluding example dialogues). May improve coherence for some models.": "Combina mensajes del sistema consecutivos en uno solo (excluyendo diálogos de ejemplo). Puede mejorar la coherencia para algunos modelos.",
"Send inline images": "Enviar imágenes en línea",
- "Assistant Prefill": "Prefiltro de asistente",
+ "Assistant Prefill": "Prellenado de Asistente",
"Start Claude's answer with...": "Iniciar la respuesta de Claude con...",
- "Use system prompt (Claude 2.1+ only)": "Usar indicación del sistema (solo Claude 2.1+)",
- "Send the system prompt for supported models. If disabled, the user message is added to the beginning of the prompt.": "Enviar la indicación del sistema para los modelos admitidos. Si está desactivado, el mensaje del usuario se agrega al principio de la indicación.",
+ "Use system prompt (Claude 2.1+ only)": "Usar indicación del sistema (solo para Claude 2.1+)",
+ "Send the system prompt for supported models. If disabled, the user message is added to the beginning of the prompt.": "Enviar la indicación del sistema para los modelos admitidos. Si está desactivado, el mensaje del usuario se agrega al principio de las indicaciónes.",
"Prompts": "Indicaciones",
"Total Tokens:": "Tokens totales:",
- "Insert prompt": "Insertar indicación",
- "Delete prompt": "Eliminar indicación",
+ "Insert prompt": "Insertar indicaciones",
+ "Delete prompt": "Eliminar indicaciones",
"Import a prompt list": "Importar una lista de indicaciones",
"Export this prompt list": "Exportar esta lista de indicaciones",
"Reset current character": "Restablecer personaje actual",
- "New prompt": "Nueva indicación",
+ "New prompt": "Nuevas indicaciones",
"Tokens": "Tokens",
"Want to update?": "¿Quieres actualizar?",
"How to start chatting?": "¿Cómo empezar a chatear?",
"Click": "Haz clic ",
- "and select a": "y selecciona un",
+ "and select a": "y selecciona una",
"Chat API": " API de chat",
"and pick a character": "y elige un personaje",
"in the chat bar": "en la barra de chat",
"Confused or lost?": "¿Confundido o perdido?",
- "click these icons!": "¡haz clic en estos iconos!",
+ "click these icons!": "¡Haz clic en estos iconos!",
"SillyTavern Documentation Site": "Sitio de documentación de SillyTavern",
"Extras Installation Guide": "Guía de instalación de extras",
"Still have questions?": "¿Todavía tienes preguntas?",
@@ -909,12 +909,12 @@
"Medium": "Medio",
"Aggressive": "Agresivo",
"Very aggressive": "Muy agresivo",
- "Eta cutoff is the main parameter of the special Eta Sampling technique.
In units of 1e-4; a reasonable value is 3.
Set to 0 to disable.
See the paper Truncation Sampling as Language Model Desmoothing by Hewitt et al. (2022) for details.": "El corte de Eta es el parámetro principal de la técnica especial de Muestreo Eta.
En unidades de 1e-4; un valor razonable es 3.
Establecer en 0 para desactivar.
Consulte el documento Truncation Sampling as Language Model Desmoothing de Hewitt et al. (2022) para más detalles.",
- "Learn how to contribute your idle GPU cycles to the Horde": "Aprende cómo contribuir con tus ciclos de GPU inactivos a la Horda",
+ "Eta cutoff is the main parameter of the special Eta Sampling technique.
In units of 1e-4; a reasonable value is 3.
Set to 0 to disable.
See the paper Truncation Sampling as Language Model Desmoothing by Hewitt et al. (2022) for details.": "El Corte de Eta es el parámetro principal de la técnica especial de Muestreo Eta.
En unidades de 1e-4; un valor razonable es 3.
Establecer en 0 para desactivar.
Consulte el documento Truncation Sampling as Language Model Desmoothing de Hewitt et al. (2022) para más detalles.",
+ "Learn how to contribute your idle GPU cycles to the Horde": "Aprende cómo contribuir con tus ciclos de GPU inactivos a Horde",
"Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "Usa el tokenizador apropiado para los modelos de Google a través de su API. Procesamiento de indicaciones más lento, pero ofrece un recuento de tokens mucho más preciso.",
- "Load koboldcpp order": "Cargar orden koboldcpp",
+ "Load koboldcpp order": "Cargar orden de koboldcpp",
"Use Google Tokenizer": "Usar Tokenizador de Google"
-}
\ No newline at end of file
+}
From f002b2d5cc378215400bf4265aa5e47d4ff52917 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Fri, 5 Apr 2024 22:15:50 +0300
Subject: [PATCH 237/255] #2016 Fix NovelAI endpoint
---
src/endpoints/novelai.js | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/endpoints/novelai.js b/src/endpoints/novelai.js
index e1158a304..c2def9f0e 100644
--- a/src/endpoints/novelai.js
+++ b/src/endpoints/novelai.js
@@ -6,6 +6,7 @@ const { readAllChunks, extractFileFromZipBuffer, forwardFetchResponse } = requir
const { jsonParser } = require('../express-common');
const API_NOVELAI = 'https://api.novelai.net';
+const IMAGE_NOVELAI = 'https://image.novelai.net';
// Ban bracket generation, plus defaults
const badWordsList = [
@@ -238,7 +239,7 @@ router.post('/generate-image', jsonParser, async (request, response) => {
try {
console.log('NAI Diffusion request:', request.body);
- const generateUrl = `${API_NOVELAI}/ai/generate-image`;
+ const generateUrl = `${IMAGE_NOVELAI}/ai/generate-image`;
const generateResult = await fetch(generateUrl, {
method: 'POST',
headers: {
@@ -289,7 +290,7 @@ router.post('/generate-image', jsonParser, async (request, response) => {
try {
console.debug('Upscaling image...');
- const upscaleUrl = `${API_NOVELAI}/ai/upscale`;
+ const upscaleUrl = `${IMAGE_NOVELAI}/ai/upscale`;
const upscaleResult = await fetch(upscaleUrl, {
method: 'POST',
headers: {
From 0debe2ca4d85b4cec08dd16ba993aa2646990551 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Fri, 5 Apr 2024 22:17:29 +0300
Subject: [PATCH 238/255] they did only move the imagegen towards that api
endpoint not upscaling
---
src/endpoints/novelai.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/endpoints/novelai.js b/src/endpoints/novelai.js
index c2def9f0e..e51f9c93a 100644
--- a/src/endpoints/novelai.js
+++ b/src/endpoints/novelai.js
@@ -290,7 +290,7 @@ router.post('/generate-image', jsonParser, async (request, response) => {
try {
console.debug('Upscaling image...');
- const upscaleUrl = `${IMAGE_NOVELAI}/ai/upscale`;
+ const upscaleUrl = `${API_NOVELAI}/ai/upscale`;
const upscaleResult = await fetch(upscaleUrl, {
method: 'POST',
headers: {
From d31e4a3bc4de2a663da2b326b0e181454e2a977e Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Fri, 5 Apr 2024 22:26:35 +0300
Subject: [PATCH 239/255] Bump package version
---
package-lock.json | 4 ++--
package.json | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 8be17c4f4..dca9e969d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "sillytavern",
- "version": "1.11.6",
+ "version": "1.11.7",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "sillytavern",
- "version": "1.11.6",
+ "version": "1.11.7",
"hasInstallScript": true,
"license": "AGPL-3.0",
"dependencies": {
diff --git a/package.json b/package.json
index 970b8efbc..d491384c0 100644
--- a/package.json
+++ b/package.json
@@ -59,7 +59,7 @@
"type": "git",
"url": "https://github.com/SillyTavern/SillyTavern.git"
},
- "version": "1.11.6",
+ "version": "1.11.7",
"scripts": {
"start": "node server.js",
"start-multi": "node server.js --disableCsrf",
From e75f5550e31cedafed8f2d9aeace1ff284f51a89 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Fri, 5 Apr 2024 22:33:16 +0300
Subject: [PATCH 240/255] Add /classify command
---
.../scripts/extensions/expressions/index.js | 19 ++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)
diff --git a/public/scripts/extensions/expressions/index.js b/public/scripts/extensions/expressions/index.js
index 9ed19a71f..f6beb9375 100644
--- a/public/scripts/extensions/expressions/index.js
+++ b/public/scripts/extensions/expressions/index.js
@@ -885,6 +885,22 @@ async function setSpriteSetCommand(_, folder) {
moduleWorker();
}
+async function classifyCommand(_, text) {
+ if (!text) {
+ console.log('No text provided');
+ return '';
+ }
+
+ if (!modules.includes('classify') && !extension_settings.expressions.local) {
+ toastr.warning('Text classification is disabled or not available');
+ return '';
+ }
+
+ const label = getExpressionLabel(text);
+ console.debug(`Classification result for "${text}": ${label}`);
+ return label;
+}
+
async function setSpriteSlashCommand(_, spriteId) {
if (!spriteId) {
console.log('No sprite id provided');
@@ -1758,5 +1774,6 @@ async function fetchImagesNoCache() {
registerSlashCommand('sprite', setSpriteSlashCommand, ['emote'], '(spriteId) – force sets the sprite for the current character', true, true);
registerSlashCommand('spriteoverride', setSpriteSetCommand, ['costume'], '(optional folder) – sets an override sprite folder for the current character. If the name starts with a slash or a backslash, selects a sub-folder in the character-named folder. Empty value to reset to default.', true, true);
registerSlashCommand('lastsprite', (_, value) => lastExpression[value.trim()] ?? '', [], '(charName) – Returns the last set sprite / expression for the named character.', true, true);
- registerSlashCommand('th', toggleTalkingHeadCommand, ['talkinghead'], '– Character Expressions: toggles Image Type - talkinghead (extras) on/off.');
+ registerSlashCommand('th', toggleTalkingHeadCommand, ['talkinghead'], '– Character Expressions: toggles Image Type - talkinghead (extras) on/off.', true, true);
+ registerSlashCommand('classify', classifyCommand, [], '(text) – classifies the text and returns a label.', true, true);
})();
From cdbd5c613008dfc762a2e886f777f4599ed6a467 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sat, 6 Apr 2024 00:45:38 +0300
Subject: [PATCH 241/255] /classify help text clarity
---
public/scripts/extensions/expressions/index.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/public/scripts/extensions/expressions/index.js b/public/scripts/extensions/expressions/index.js
index f6beb9375..ac0493e37 100644
--- a/public/scripts/extensions/expressions/index.js
+++ b/public/scripts/extensions/expressions/index.js
@@ -1775,5 +1775,5 @@ async function fetchImagesNoCache() {
registerSlashCommand('spriteoverride', setSpriteSetCommand, ['costume'], '(optional folder) – sets an override sprite folder for the current character. If the name starts with a slash or a backslash, selects a sub-folder in the character-named folder. Empty value to reset to default.', true, true);
registerSlashCommand('lastsprite', (_, value) => lastExpression[value.trim()] ?? '', [], '(charName) – Returns the last set sprite / expression for the named character.', true, true);
registerSlashCommand('th', toggleTalkingHeadCommand, ['talkinghead'], '– Character Expressions: toggles Image Type - talkinghead (extras) on/off.', true, true);
- registerSlashCommand('classify', classifyCommand, [], '(text) – classifies the text and returns a label.', true, true);
+ registerSlashCommand('classify', classifyCommand, [], '(text) – performs an emotion classification of the given text and returns a label.', true, true);
})();
From 6cf897219e86d6c90e4d6bfb7db14cf4f2dfafec Mon Sep 17 00:00:00 2001
From: johnbenac
Date: Fri, 5 Apr 2024 18:41:36 -0400
Subject: [PATCH 242/255] Added toastr messages to tts index file
---
public/scripts/extensions/tts/index.js | 3 +++
1 file changed, 3 insertions(+)
diff --git a/public/scripts/extensions/tts/index.js b/public/scripts/extensions/tts/index.js
index b86e42ede..b093e9053 100644
--- a/public/scripts/extensions/tts/index.js
+++ b/public/scripts/extensions/tts/index.js
@@ -465,6 +465,7 @@ async function processAudioJobQueue() {
playAudioData(currentAudioJob);
talkingAnimation(true);
} catch (error) {
+ toastr.error(error.toString());
console.error(error);
audioQueueProcessorReady = true;
}
@@ -579,6 +580,7 @@ async function processTtsQueue() {
}
tts(text, voiceId, char);
} catch (error) {
+ toastr.error(error.toString());
console.error(error);
currentTtsJob = null;
}
@@ -648,6 +650,7 @@ function onRefreshClick() {
initVoiceMap();
updateVoiceMap();
}).catch(error => {
+ toastr.error(error.toString());
console.error(error);
setTtsStatus(error, false);
});
From 3b6c32113f6d351a6971893bc70ee55fc4a8ba95 Mon Sep 17 00:00:00 2001
From: johnbenac
Date: Fri, 5 Apr 2024 18:57:51 -0400
Subject: [PATCH 243/255] added await to tts to properly catch the error on
this async function
---
public/scripts/extensions/tts/index.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/public/scripts/extensions/tts/index.js b/public/scripts/extensions/tts/index.js
index b093e9053..5b80b172b 100644
--- a/public/scripts/extensions/tts/index.js
+++ b/public/scripts/extensions/tts/index.js
@@ -578,7 +578,7 @@ async function processTtsQueue() {
toastr.error(`Specified voice for ${char} was not found. Check the TTS extension settings.`);
throw `Unable to attain voiceId for ${char}`;
}
- tts(text, voiceId, char);
+ await tts(text, voiceId, char);
} catch (error) {
toastr.error(error.toString());
console.error(error);
From fe8f0a8ff2ff87ed2afada8d0f7c62f0ca9f22af Mon Sep 17 00:00:00 2001
From: Wolfsblvt
Date: Sat, 6 Apr 2024 07:14:45 +0200
Subject: [PATCH 244/255] Limit drawing of tags to 50 with expander
- No matter where we draw tags, we'll draw a maximum of 50 tags
- Filtered tags (selected, excluded) will always be drawn
- Display "expander" icon/tag to show full tag list
- Cache the full tag list display so consecutive redraws respect it
---
public/css/tags.css | 5 +++
public/scripts/tags.js | 98 ++++++++++++++++++++++++++++++++++++------
2 files changed, 91 insertions(+), 12 deletions(-)
diff --git a/public/css/tags.css b/public/css/tags.css
index 9a3e02064..8c25eb1dd 100644
--- a/public/css/tags.css
+++ b/public/css/tags.css
@@ -73,6 +73,11 @@
background: none;
}
+.tag.placeholder-expander {
+ cursor: alias;
+ border: 0;
+}
+
.tagListHint {
align-self: center;
display: flex;
diff --git a/public/scripts/tags.js b/public/scripts/tags.js
index 7edd2feab..b6b099099 100644
--- a/public/scripts/tags.js
+++ b/public/scripts/tags.js
@@ -118,6 +118,7 @@ const TAG_FOLDER_DEFAULT_TYPE = 'NONE';
* @property {function} [action] - An optional function that gets executed when this tag is an actionable tag and is clicked on.
* @property {string} [class] - An optional css class added to the control representing this tag when printed. Used for custom tags in the filters.
* @property {string} [icon] - An optional css class of an icon representing this tag when printed. This will replace the tag name with the icon. Used for custom tags in the filters.
+ * @property {string} [title] - An optional title for the tooltip of this tag. If there is no tooltip specified, and "icon" is chosen, the tooltip will be the "name" property.
*/
/**
@@ -128,10 +129,17 @@ let tags = [];
/**
* A map representing the key of an entity (character avatar, group id, etc) with a corresponding array of tags this entity has assigned. The array might not exist if no tags were assigned yet.
- * @type {Object.}
+ * @type {{[identifier: string]: string[]?}}
*/
let tag_map = {};
+/**
+ * A cache of all cut-off tag lists that got expanded until the last reload. They will be printed expanded again.
+ * It contains the key of the entity.
+ * @type {string[]} ids
+ */
+let expanded_tags_cache = [];
+
/**
* Applies the basic filter for the current state of the tags and their selection on an entity list.
* @param {Array} entities List of entities for display, consisting of tags, characters and groups.
@@ -388,7 +396,7 @@ function getTagKey() {
* Robust method to find a valid tag key for any entity.
*
* @param {object|number|string} entityOrKey An entity with id property (character, group, tag), or directly an id or tag key.
- * @returns {string} The tag key that can be found.
+ * @returns {string|undefined} The tag key that can be found.
*/
export function getTagKeyForEntity(entityOrKey) {
let x = entityOrKey;
@@ -419,6 +427,30 @@ export function getTagKeyForEntity(entityOrKey) {
return undefined;
}
+/**
+ * Checks for a tag key based on an entity for a given element.
+ * It checks the given element and upwards parents for a set character id (chid) or group id (grid), and if there is any, returns its unique entity key.
+ *
+ * @param {JQuery} element - The element to search the entity id on
+ * @returns {string|undefined} The tag key that can be found.
+ */
+export function getTagKeyForEntityElement(element) {
+ // Start with the given element and traverse up the DOM tree
+ while (element.length && element.parent().length) {
+ const grid = element.attr('grid');
+ const chid = element.attr('chid');
+ if (grid || chid) {
+ const id = grid || chid;
+ return getTagKeyForEntity(id);
+ }
+
+ // Move up to the parent element
+ element = element.parent();
+ }
+
+ return undefined;
+}
+
function addTagToMap(tagId, characterId = null) {
const key = characterId !== null && characterId !== undefined ? getTagKeyForEntity(characterId) : getTagKey();
@@ -637,6 +669,16 @@ function printTagList(element, { tags = undefined, addTag = undefined, forEntity
const customAction = typeof tagActionSelector === 'function' ? tagActionSelector : null;
+ // Well, lets check if the tag list was expanded. Based on either a css class, or when any expand was clicked yet, then we search whether this element id matches
+ const expanded = element.hasClass('tags-expanded') || (expanded_tags_cache.length && expanded_tags_cache.indexOf(key ?? getTagKeyForEntityElement(element)) >= 0);
+
+ // We prepare some stuff. No matter which list we have, there is a maximum value of tags we are going to display
+ const TAGS_LIMIT = 50;
+ const MAX_TAGS = !expanded ? TAGS_LIMIT : Number.MAX_VALUE;
+ let totalPrinted = 0;
+ let hiddenTags = 0;
+ const filterActive = (/** @type {Tag} */ tag) => tag.filter_state && !isFilterState(tag.filter_state, FILTER_STATES.UNDEFINED);
+
for (const tag of printableTags) {
// If we have a custom action selector, we override that tag options for each tag
if (customAction) {
@@ -648,7 +690,36 @@ function printTagList(element, { tags = undefined, addTag = undefined, forEntity
}
}
- appendTagToList(element, tag, tagOptions);
+ // Check if we should print this tag
+ if (totalPrinted++ < MAX_TAGS || filterActive(tag)) {
+ appendTagToList(element, tag, tagOptions);
+ } else {
+ hiddenTags++;
+ }
+ }
+
+ // After the loop, check if we need to add the placeholder.
+ // The placehold if clicked expands the tags and remembers either via class or cache array which was expanded, so it'll stay expanded until the next reload.
+ if (hiddenTags > 0) {
+ const id = "placeholder_" + uuidv4();
+
+ // Add click event
+ const showHiddenTags = (event) => {
+ const elementKey = key ?? getTagKeyForEntityElement(element);
+ console.log(`Hidden tags shown for element ${elementKey}`);
+
+ // Mark the current char/group as expanded if we were in any. This will be kept in memory until reload
+ element.addClass('tags-expanded');
+ expanded_tags_cache.push(elementKey);
+
+ // Do not bubble further, we are just expanding
+ event.stopPropagation();
+ printTagList(element, { tags: tags, addTag: addTag, forEntityOrKey: forEntityOrKey, empty: empty, tagActionSelector: tagActionSelector, tagOptions: tagOptions });
+ };
+
+ /** @type {Tag} */
+ const placeholderTag = { id: id, name: "...", title: `${hiddenTags} tags not displayed.\n\nClick to expand remaining tags.`, color: 'transparent', action: showHiddenTags, class: 'placeholder-expander' };
+ appendTagToList(element, placeholderTag, tagOptions);
}
}
@@ -682,13 +753,19 @@ function appendTagToList(listElement, tag, { removable = false, selectable = fal
if (tag.class) {
tagElement.addClass(tag.class);
}
-
+ if (tag.title) {
+ tagElement.attr('title', tag.title);
+ }
if (tag.icon) {
- tagElement.find('.tag_name').text('').attr('title', tag.name).addClass(tag.icon);
+ tagElement.find('.tag_name').text('').attr('title', `${tag.name} ${tag.title}`.trim()).addClass(tag.icon);
+ tagElement.addClass('actionable');
}
+ // We could have multiple ways of actions passed in. The manual arguments have precendence in front of a specified tag action
+ const clickableAction = action ?? tag.action;
+
// If this is a tag for a general list and its either selectable or actionable, lets mark its current state
- if ((selectable || action) && isGeneralList) {
+ if ((selectable || clickableAction) && isGeneralList) {
toggleTagThreeState(tagElement, { stateOverride: tag.filter_state ?? DEFAULT_FILTER_STATE });
}
@@ -696,14 +773,11 @@ function appendTagToList(listElement, tag, { removable = false, selectable = fal
tagElement.on('click', () => onTagFilterClick.bind(tagElement)(listElement));
}
- if (action) {
+ if (clickableAction) {
const filter = getFilterHelper($(listElement));
- tagElement.on('click', () => action.bind(tagElement)(filter));
- tagElement.addClass('actionable');
+ tagElement.on('click', (e) => clickableAction.bind(tagElement)(e, filter));
+ tagElement.addClass('clickable-action');
}
- /*if (action && tag.id === 2) {
- tagElement.addClass('innerActionable hidden');
- }*/
$(listElement).append(tagElement);
}
From 9805215c284e33383e498dc69184c77cd27a9fd4 Mon Sep 17 00:00:00 2001
From: Wolfsblvt
Date: Sat, 6 Apr 2024 07:37:30 +0200
Subject: [PATCH 245/255] Fix expander button and group tags add
- Fix expander button to never be "removable" in any list
- Fix group tag list to actually work on adding tags
---
public/scripts/tags.js | 21 +++++++++++++--------
1 file changed, 13 insertions(+), 8 deletions(-)
diff --git a/public/scripts/tags.js b/public/scripts/tags.js
index b6b099099..58910de03 100644
--- a/public/scripts/tags.js
+++ b/public/scripts/tags.js
@@ -649,15 +649,16 @@ function createNewTag(tagName) {
/**
* Prints the list of tags
*
- * @param {JQuery} element - The container element where the tags are to be printed.
+ * @param {JQuery|string} element - The container element where the tags are to be printed. (Optionally can also be a string selector for the element, which will then be resolved)
* @param {PrintTagListOptions} [options] - Optional parameters for printing the tag list.
*/
function printTagList(element, { tags = undefined, addTag = undefined, forEntityOrKey = undefined, empty = true, tagActionSelector = undefined, tagOptions = {} } = {}) {
+ const $element = (typeof element === "string") ? $(element) : element;
const key = forEntityOrKey !== undefined ? getTagKeyForEntity(forEntityOrKey) : getTagKey();
let printableTags = tags ? (typeof tags === 'function' ? tags() : tags) : getTagsList(key);
if (empty === 'always' || (empty && (printableTags?.length > 0 || key))) {
- $(element).empty();
+ $element.empty();
}
if (addTag && (tagOptions.skipExistsCheck || !printableTags.some(x => x.id === addTag.id))) {
@@ -670,7 +671,7 @@ function printTagList(element, { tags = undefined, addTag = undefined, forEntity
const customAction = typeof tagActionSelector === 'function' ? tagActionSelector : null;
// Well, lets check if the tag list was expanded. Based on either a css class, or when any expand was clicked yet, then we search whether this element id matches
- const expanded = element.hasClass('tags-expanded') || (expanded_tags_cache.length && expanded_tags_cache.indexOf(key ?? getTagKeyForEntityElement(element)) >= 0);
+ const expanded = $element.hasClass('tags-expanded') || (expanded_tags_cache.length && expanded_tags_cache.indexOf(key ?? getTagKeyForEntityElement(element)) >= 0);
// We prepare some stuff. No matter which list we have, there is a maximum value of tags we are going to display
const TAGS_LIMIT = 50;
@@ -692,7 +693,7 @@ function printTagList(element, { tags = undefined, addTag = undefined, forEntity
// Check if we should print this tag
if (totalPrinted++ < MAX_TAGS || filterActive(tag)) {
- appendTagToList(element, tag, tagOptions);
+ appendTagToList($element, tag, tagOptions);
} else {
hiddenTags++;
}
@@ -705,21 +706,25 @@ function printTagList(element, { tags = undefined, addTag = undefined, forEntity
// Add click event
const showHiddenTags = (event) => {
- const elementKey = key ?? getTagKeyForEntityElement(element);
+ const elementKey = key ?? getTagKeyForEntityElement($element);
console.log(`Hidden tags shown for element ${elementKey}`);
// Mark the current char/group as expanded if we were in any. This will be kept in memory until reload
- element.addClass('tags-expanded');
+ $element.addClass('tags-expanded');
expanded_tags_cache.push(elementKey);
// Do not bubble further, we are just expanding
event.stopPropagation();
- printTagList(element, { tags: tags, addTag: addTag, forEntityOrKey: forEntityOrKey, empty: empty, tagActionSelector: tagActionSelector, tagOptions: tagOptions });
+ printTagList($element, { tags: tags, addTag: addTag, forEntityOrKey: forEntityOrKey, empty: empty, tagActionSelector: tagActionSelector, tagOptions: tagOptions });
};
+ // Print the placeholder object with its styling and action to show the remaining tags
/** @type {Tag} */
const placeholderTag = { id: id, name: "...", title: `${hiddenTags} tags not displayed.\n\nClick to expand remaining tags.`, color: 'transparent', action: showHiddenTags, class: 'placeholder-expander' };
- appendTagToList(element, placeholderTag, tagOptions);
+ // It should never be marked as a removable tag, because it's just an expander action
+ /** @type {TagOptions} */
+ const placeholderTagOptions = { ...tagOptions, removable: false };
+ appendTagToList($element, placeholderTag, placeholderTagOptions);
}
}
From 30b9b13070cf2c8a4330ba2ba95760f416a881a9 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sat, 6 Apr 2024 14:48:59 +0300
Subject: [PATCH 246/255] Use integers for max value. This is helpful if
someone has to render more than 9 quadrillion tags
---
public/scripts/tags.js | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/public/scripts/tags.js b/public/scripts/tags.js
index 58910de03..d08b170f4 100644
--- a/public/scripts/tags.js
+++ b/public/scripts/tags.js
@@ -431,10 +431,13 @@ export function getTagKeyForEntity(entityOrKey) {
* Checks for a tag key based on an entity for a given element.
* It checks the given element and upwards parents for a set character id (chid) or group id (grid), and if there is any, returns its unique entity key.
*
- * @param {JQuery} element - The element to search the entity id on
+ * @param {JQuery|string} element - The element to search the entity id on
* @returns {string|undefined} The tag key that can be found.
*/
export function getTagKeyForEntityElement(element) {
+ if (typeof element === 'string') {
+ element = $(element);
+ }
// Start with the given element and traverse up the DOM tree
while (element.length && element.parent().length) {
const grid = element.attr('grid');
@@ -653,7 +656,7 @@ function createNewTag(tagName) {
* @param {PrintTagListOptions} [options] - Optional parameters for printing the tag list.
*/
function printTagList(element, { tags = undefined, addTag = undefined, forEntityOrKey = undefined, empty = true, tagActionSelector = undefined, tagOptions = {} } = {}) {
- const $element = (typeof element === "string") ? $(element) : element;
+ const $element = (typeof element === 'string') ? $(element) : element;
const key = forEntityOrKey !== undefined ? getTagKeyForEntity(forEntityOrKey) : getTagKey();
let printableTags = tags ? (typeof tags === 'function' ? tags() : tags) : getTagsList(key);
@@ -675,7 +678,7 @@ function printTagList(element, { tags = undefined, addTag = undefined, forEntity
// We prepare some stuff. No matter which list we have, there is a maximum value of tags we are going to display
const TAGS_LIMIT = 50;
- const MAX_TAGS = !expanded ? TAGS_LIMIT : Number.MAX_VALUE;
+ const MAX_TAGS = !expanded ? TAGS_LIMIT : Number.MAX_SAFE_INTEGER;
let totalPrinted = 0;
let hiddenTags = 0;
const filterActive = (/** @type {Tag} */ tag) => tag.filter_state && !isFilterState(tag.filter_state, FILTER_STATES.UNDEFINED);
@@ -702,7 +705,7 @@ function printTagList(element, { tags = undefined, addTag = undefined, forEntity
// After the loop, check if we need to add the placeholder.
// The placehold if clicked expands the tags and remembers either via class or cache array which was expanded, so it'll stay expanded until the next reload.
if (hiddenTags > 0) {
- const id = "placeholder_" + uuidv4();
+ const id = 'placeholder_' + uuidv4();
// Add click event
const showHiddenTags = (event) => {
@@ -720,7 +723,7 @@ function printTagList(element, { tags = undefined, addTag = undefined, forEntity
// Print the placeholder object with its styling and action to show the remaining tags
/** @type {Tag} */
- const placeholderTag = { id: id, name: "...", title: `${hiddenTags} tags not displayed.\n\nClick to expand remaining tags.`, color: 'transparent', action: showHiddenTags, class: 'placeholder-expander' };
+ const placeholderTag = { id: id, name: '...', title: `${hiddenTags} tags not displayed.\n\nClick to expand remaining tags.`, color: 'transparent', action: showHiddenTags, class: 'placeholder-expander' };
// It should never be marked as a removable tag, because it's just an expander action
/** @type {TagOptions} */
const placeholderTagOptions = { ...tagOptions, removable: false };
From 495cf5d9ca2c1bb96ac9858be9af692ed37f0ae4 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Sat, 6 Apr 2024 15:47:11 +0300
Subject: [PATCH 247/255] Move context formatting help link
---
public/index.html | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/public/index.html b/public/index.html
index 107674261..352885649 100644
--- a/public/index.html
+++ b/public/index.html
@@ -2758,15 +2758,17 @@