From b46f89b3459eff2351e3eace5d4bd2ee8c9020e5 Mon Sep 17 00:00:00 2001 From: smirgol <14124899+smirgol@users.noreply.github.com> Date: Sun, 29 Oct 2023 17:18:21 +0100 Subject: [PATCH 001/632] add missing session handling to silerotts --- public/scripts/extensions/tts/silerotts.js | 32 +++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/public/scripts/extensions/tts/silerotts.js b/public/scripts/extensions/tts/silerotts.js index 8d9120f08..70fa6020a 100644 --- a/public/scripts/extensions/tts/silerotts.js +++ b/public/scripts/extensions/tts/silerotts.js @@ -32,6 +32,7 @@ class SileroTtsProvider { // Used when provider settings are updated from UI this.settings.provider_endpoint = $('#silero_tts_endpoint').val() saveTtsProviderSettings() + this.refreshSession() } async loadSettings(settings) { @@ -64,6 +65,7 @@ class SileroTtsProvider { $('#silero_tts_endpoint').val(this.settings.provider_endpoint) $('#silero_tts_endpoint').on("input", () => {this.onSettingsChange()}) + this.refreshSession() await this.checkReady() @@ -78,6 +80,10 @@ class SileroTtsProvider { async onRefreshClick() { return } + + async refreshSession() { + await this.initSession() + } //#################// // TTS Interfaces // @@ -125,7 +131,8 @@ class SileroTtsProvider { }, body: JSON.stringify({ "text": inputText, - "speaker": voiceId + "speaker": voiceId, + "session": "sillytavern" }) } ) @@ -135,6 +142,29 @@ class SileroTtsProvider { } return response } + + async initSession() { + console.info(`requesting new session`) + const response = await doExtrasFetch( + `${this.settings.provider_endpoint}/init_session`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Cache-Control': 'no-cache' // Added this line to disable caching of file so new files are always played - Rolyat 7/7/23 + }, + body: JSON.stringify({ + "path": "sillytavern" + }) + } + ) + + if (!response.ok && response.status !== 404) { + toastr.error(response.statusText, 'Fetching Session Failed'); + throw new Error(`HTTP ${response.status}: ${await response.text()}`); + } + return response + } // Interface not used by Silero TTS async fetchTtsFromHistory(history_item_id) { From 3346420527ebd301babb5c8d1dcc543cf4e6497f Mon Sep 17 00:00:00 2001 From: smirgol <14124899+smirgol@users.noreply.github.com> Date: Sun, 29 Oct 2023 19:58:02 +0100 Subject: [PATCH 002/632] fix api endpoint to match the current definition --- public/scripts/extensions/tts/silerotts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/extensions/tts/silerotts.js b/public/scripts/extensions/tts/silerotts.js index 70fa6020a..88f9f03c9 100644 --- a/public/scripts/extensions/tts/silerotts.js +++ b/public/scripts/extensions/tts/silerotts.js @@ -146,7 +146,7 @@ class SileroTtsProvider { async initSession() { console.info(`requesting new session`) const response = await doExtrasFetch( - `${this.settings.provider_endpoint}/init_session`, + `${this.settings.provider_endpoint}/session`, { method: 'POST', headers: { From 68370dbe30bdae37dd2a6a29b8a41801e94db1ed Mon Sep 17 00:00:00 2001 From: Huge Date: Thu, 23 Nov 2023 14:55:32 +0100 Subject: [PATCH 003/632] Update readme.md with node version check otherwise illegible error occurs --- .github/readme.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/readme.md b/.github/readme.md index 3fa3826b4..5774f76fd 100644 --- a/.github/readme.md +++ b/.github/readme.md @@ -162,8 +162,9 @@ Installing via ZIP download (discouraged) ### Linux - 1. Run the `start.sh` script. - 2. Enjoy. + 1. Ensure you have Node.js v14 or higher installed by running `node -v`. + 2. Run the `start.sh` script. + 3. Enjoy. ## API keys management From e48cd0a49d0a8e3be73497c2c3c4e4aff9a1cd29 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Thu, 23 Nov 2023 21:13:20 +0200 Subject: [PATCH 004/632] Fix version number + provide LTS guidance --- .github/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/readme.md b/.github/readme.md index 5774f76fd..83c91901a 100644 --- a/.github/readme.md +++ b/.github/readme.md @@ -162,7 +162,7 @@ Installing via ZIP download (discouraged) ### Linux - 1. Ensure you have Node.js v14 or higher installed by running `node -v`. + 1. Ensure you have Node.js v18 or higher (the latest [LTS release](https://nodejs.org/en/download/) is recommended) installed by running `node -v`. 2. Run the `start.sh` script. 3. Enjoy. From 3328df60760e6a808d880e22cb44125c1bb20196 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Thu, 23 Nov 2023 21:15:22 +0200 Subject: [PATCH 005/632] Update readme.md --- .github/readme.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/readme.md b/.github/readme.md index 83c91901a..6bff95a55 100644 --- a/.github/readme.md +++ b/.github/readme.md @@ -162,7 +162,8 @@ Installing via ZIP download (discouraged) ### Linux - 1. Ensure you have Node.js v18 or higher (the latest [LTS release](https://nodejs.org/en/download/) is recommended) installed by running `node -v`. + 1. Ensure you have Node.js v18 or higher (the latest [LTS version](https://nodejs.org/en/download/) is recommended) installed by running `node -v`. +Alternatively, use the [Node Version Manager](https://github.com/nvm-sh/nvm#installing-and-updating) script to quickly and easily manage your Node installations. 2. Run the `start.sh` script. 3. Enjoy. From 851a00630a600c4a712b3ec6f803d13d3872d0fb Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Fri, 24 Nov 2023 19:50:49 +0200 Subject: [PATCH 006/632] Add /popup command --- public/scripts/slash-commands.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index 3ff9fbc5f..c0154ee66 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -174,6 +174,7 @@ parser.addCommand('input', inputCallback, ['prompt'], '( parser.addCommand('run', runCallback, ['call', 'exec'], '(QR label) – runs a Quick Reply with the specified name from the current preset.', true, true); parser.addCommand('messages', getMessagesCallback, ['message'], '(names=off/on [message index or range]) – returns the specified message or range of messages as a string.', true, true); parser.addCommand('setinput', setInputCallback, [], '(text) – sets the user input to the specified text and passes it to the next command through the pipe.', true, true); +parser.addCommand('popup', popupCallback, [], '(text) – shows a blocking popup with the specified text.', true, true); registerVariableCommands(); const NARRATOR_NAME_KEY = 'narrator_name'; @@ -185,6 +186,14 @@ function setInputCallback(_, value) { return value; } +async function popupCallback(_, value) { + const safeValue = DOMPurify.sanitize(value || ''); + await delay(1); + await callPopup(safeValue, 'text'); + await delay(1); + return value; +} + function getMessagesCallback(args, value) { const includeNames = !isFalseBoolean(args?.names); const range = stringToRange(value, 0, chat.length - 1); From a6898365d11d67414eb339e35f1bb8b9563a1528 Mon Sep 17 00:00:00 2001 From: Aisu Wata Date: Fri, 24 Nov 2023 17:58:20 -0300 Subject: [PATCH 007/632] Claude system message order fix --- src/chat-completion.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/chat-completion.js b/src/chat-completion.js index e16cdd017..a513a078f 100644 --- a/src/chat-completion.js +++ b/src/chat-completion.js @@ -18,14 +18,20 @@ function convertClaudePrompt(messages, addHumanPrefix, addAssistantPostfix, with let systemPrompt = ''; if (withSystemPrompt) { - for (const message of messages) { + let lastSystemIdx = -1; + + for (let i = 0; i < messages.length - 1; i++) { + const message = messages[i]; if (message.role === "system" && !message.name) { systemPrompt += message.content + '\n\n'; - messages.splice(messages.indexOf(message), 1); } else { + lastSystemIdx = i - 1; break; } } + if (lastSystemIdx >= 0) { + messages.splice(0, lastSystemIdx + 1); + } } let requestPrompt = messages.map((v) => { From 389c2b54351ce56cced36390c0d46f5f1b4ab233 Mon Sep 17 00:00:00 2001 From: RossAscends <124905043+RossAscends@users.noreply.github.com> Date: Sat, 25 Nov 2023 23:40:27 +0900 Subject: [PATCH 008/632] force firstMes {{user}} update on persona switch --- public/scripts/personas.js | 1 + 1 file changed, 1 insertion(+) diff --git a/public/scripts/personas.js b/public/scripts/personas.js index cad220aaa..57cec688f 100644 --- a/public/scripts/personas.js +++ b/public/scripts/personas.js @@ -254,6 +254,7 @@ export function selectCurrentPersona() { } setPersonaDescription(); + $("#firstmessage_textarea").trigger('input') } } From 06ade803fab71a7d1a43f0892efa6f2453f4cb9e Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 25 Nov 2023 17:45:40 +0200 Subject: [PATCH 009/632] Concatenate strings in /addvar --- public/scripts/slash-commands.js | 3 ++- public/scripts/variables.js | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index c0154ee66..3df7dbb4e 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -264,7 +264,8 @@ async function delayCallback(_, amount) { async function inputCallback(_, prompt) { // Do not remove this delay, otherwise the prompt will not show up await delay(1); - const result = await callPopup(prompt || '', 'input'); + const safeValue = DOMPurify.sanitize(prompt || ''); + const result = await callPopup(safeValue, 'input'); await delay(1); return result || ''; } diff --git a/public/scripts/variables.js b/public/scripts/variables.js index 4ffe7b776..f6b06e49b 100644 --- a/public/scripts/variables.js +++ b/public/scripts/variables.js @@ -38,7 +38,9 @@ function addLocalVariable(name, value) { const increment = Number(value); if (isNaN(increment)) { - return ''; + const stringValue = String(currentValue || '') + value; + setLocalVariable(name, stringValue); + return stringValue; } const newValue = Number(currentValue) + increment; @@ -56,7 +58,9 @@ function addGlobalVariable(name, value) { const increment = Number(value); if (isNaN(increment)) { - return ''; + const stringValue = String(currentValue || '') + value; + setGlobalVariable(name, stringValue); + return stringValue; } const newValue = Number(currentValue) + increment; From 6894b7ef7265ac4560e411488710bb85c08b297f Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 25 Nov 2023 18:12:28 +0200 Subject: [PATCH 010/632] Replace macros in named args of boolean evaluation --- public/scripts/variables.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/scripts/variables.js b/public/scripts/variables.js index f6b06e49b..e5fb36888 100644 --- a/public/scripts/variables.js +++ b/public/scripts/variables.js @@ -1,4 +1,4 @@ -import { chat_metadata, getCurrentChatId, saveSettingsDebounced, sendSystemMessage, system_message_types } from "../script.js"; +import { chat_metadata, getCurrentChatId, saveSettingsDebounced, sendSystemMessage, substituteParams, system_message_types } from "../script.js"; import { extension_settings, saveMetadataDebounced } from "./extensions.js"; import { executeSlashCommands, registerSlashCommand } from "./slash-commands.js"; @@ -208,7 +208,7 @@ function parseBooleanOperands(args) { } const stringLiteral = String(operand); - return stringLiteral || ''; + return substituteParams(stringLiteral) || ''; } const left = getOperand(args.a || args.left || args.first || args.x); From d862005c1c3597f209599bc6fdc1189d16ff75a2 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 25 Nov 2023 18:16:53 +0200 Subject: [PATCH 011/632] Revert "Replace macros in named args of boolean evaluation" This reverts commit 6894b7ef7265ac4560e411488710bb85c08b297f. --- public/scripts/variables.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/scripts/variables.js b/public/scripts/variables.js index e5fb36888..f6b06e49b 100644 --- a/public/scripts/variables.js +++ b/public/scripts/variables.js @@ -1,4 +1,4 @@ -import { chat_metadata, getCurrentChatId, saveSettingsDebounced, sendSystemMessage, substituteParams, system_message_types } from "../script.js"; +import { chat_metadata, getCurrentChatId, saveSettingsDebounced, sendSystemMessage, system_message_types } from "../script.js"; import { extension_settings, saveMetadataDebounced } from "./extensions.js"; import { executeSlashCommands, registerSlashCommand } from "./slash-commands.js"; @@ -208,7 +208,7 @@ function parseBooleanOperands(args) { } const stringLiteral = String(operand); - return substituteParams(stringLiteral) || ''; + return stringLiteral || ''; } const left = getOperand(args.a || args.left || args.first || args.x); From b8d7b0922d1f2922fcf4b0ea82ad39d749ed8cd2 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 25 Nov 2023 18:18:57 +0200 Subject: [PATCH 012/632] Fix evaluation order of named args --- public/scripts/slash-commands.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index 3df7dbb4e..878918ae2 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -1206,13 +1206,15 @@ async function executeSlashCommands(text, unescape = false) { let unnamedArg = result.value || pipeResult; if (typeof result.args === 'object') { - for (const [key, value] of Object.entries(result.args)) { + for (let [key, value] of Object.entries(result.args)) { if (typeof value === 'string') { + value = substituteParams(value.trim()); + if (/{{pipe}}/i.test(value)) { - result.args[key] = value.replace(/{{pipe}}/i, pipeResult || ''); + value = value.replace(/{{pipe}}/i, pipeResult || ''); } - result.args[key] = substituteParams(value.trim()); + result.args[key] = value; } } } From 2bed9fde7035e9ca33c5d3700571728139c4d52a Mon Sep 17 00:00:00 2001 From: RossAscends <124905043+RossAscends@users.noreply.github.com> Date: Sun, 26 Nov 2023 01:28:33 +0900 Subject: [PATCH 013/632] {{random}} split on :: to allow empty items & commas in items --- public/script.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/public/script.js b/public/script.js index 3d8d419b0..c0b1f8707 100644 --- a/public/script.js +++ b/public/script.js @@ -2124,8 +2124,8 @@ function randomReplace(input, emptyListPlaceholder = '') { const randomPattern = /{{random[ : ]([^}]+)}}/gi; return input.replace(randomPattern, (match, listString) => { - const list = listString.split(',').map(item => item.trim()).filter(item => item.length > 0); - + //split on double colons instead of commas to allow for commas inside random items + const list = listString.split('::').filter(item => item.length > 0); if (list.length === 0) { return emptyListPlaceholder; } @@ -2134,7 +2134,8 @@ function randomReplace(input, emptyListPlaceholder = '') { const randomIndex = Math.floor(rng() * list.length); //const randomIndex = Math.floor(Math.random() * list.length); - return list[randomIndex]; + //trim() at the end to allow for empty random values + return list[randomIndex].trim(); }); } From 67174c8cf85a9bbfa8039f3cf5146cde684a9644 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 25 Nov 2023 19:53:00 +0200 Subject: [PATCH 014/632] Add functions to delete local and global variables --- public/scripts/variables.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/public/scripts/variables.js b/public/scripts/variables.js index f6b06e49b..1f395486f 100644 --- a/public/scripts/variables.js +++ b/public/scripts/variables.js @@ -301,6 +301,28 @@ async function executeSubCommands(command) { return result?.pipe || ''; } +function deleteLocalVariable(name) { + if (!existsLocalVariable(name)) { + console.warn(`The local variable "${name}" does not exist.`); + return ''; + } + + delete chat_metadata.variables[name]; + saveMetadataDebounced(); + return ''; +} + +function deleteGlobalVariable(name) { + if (!existsGlobalVariable(name)) { + console.warn(`The global variable "${name}" does not exist.`); + return ''; + } + + delete extension_settings.variables.global[name]; + saveSettingsDebounced(); + return ''; +} + export function registerVariableCommands() { registerSlashCommand('listvar', listVariablesCallback, [''], ' – list registered chat variables', true, true); registerSlashCommand('setvar', (args, value) => setLocalVariable(args.key || args.name, value), [], 'key=varname (value) – set a local variable value and pass it down the pipe, e.g. /setvar key=color green', true, true); @@ -311,4 +333,6 @@ export function registerVariableCommands() { registerSlashCommand('addglobalvar', (args, value) => addGlobalVariable(args.key || args.name, value), [], 'key=varname (increment) – add a value to a global variable and pass the result down the pipe, e.g. /addglobalvar score 10', true, true); registerSlashCommand('if', ifCallback, [], 'a=varname1 b=varname2 rule=comparison else="(alt.command)" "(command)" – compare the value of variable "a" with the value of variable "b", and if the condition yields true, then execute any valid slash command enclosed in quotes and pass the result of the command execution down the pipe. Numeric values and string literals for "a" and "b" supported. Available rules: gt => a > b, gte => a >= b, lt => a < b, lte => a <= b, eq => a == b, neq => a != b, not => !a, in (strings) => a includes b, nin (strings) => a not includes b, e.g. /if a=score a=10 rule=gte "/speak You win" triggers a /speak command if the value of "score" is greater or equals 10.', true, true); registerSlashCommand('while', whileCallback, [], 'a=varname1 b=varname2 rule=comparison "(command)" – compare the value of variable "a" with the value of variable "b", and if the condition yields true, then execute any valid slash command enclosed in quotes. Numeric values and string literals for "a" and "b" supported. Available rules: gt => a > b, gte => a >= b, lt => a < b, lte => a <= b, eq => a == b, neq => a != b, not => !a, in (strings) => a includes b, nin (strings) => a not includes b, e.g. /while a=i a=10 rule=let "/addvar i 1" adds 1 to the value of "i" until it reaches 10. Loops are limited to 100 iterations by default, pass guard=off to disable.', true, true); + registerSlashCommand('flushvar', (_, value) => deleteLocalVariable(value), [], '(key) – delete a local variable, e.g. /flushvar score', true, true); + registerSlashCommand('flushglobalvar', (_, value) => deleteGlobalVariable(value), [], '(key) – delete a global variable, e.g. /flushglobalvar score', true, true); } From 9645034b09c7c792d80a85c89a02b167fac93476 Mon Sep 17 00:00:00 2001 From: RossAscends <124905043+RossAscends@users.noreply.github.com> Date: Sun, 26 Nov 2023 03:08:03 +0900 Subject: [PATCH 015/632] reverse compatibility for old random method --- public/script.js | 44 ++++++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/public/script.js b/public/script.js index c0b1f8707..787b7e11f 100644 --- a/public/script.js +++ b/public/script.js @@ -2121,22 +2121,34 @@ function getTimeSinceLastMessage() { } function randomReplace(input, emptyListPlaceholder = '') { - const randomPattern = /{{random[ : ]([^}]+)}}/gi; + const randomPatternNew = /{{random\s?::\s?([^}]+)}}/gi; + const randomPatternOld = /{{random\s?:\s?([^}]+)}}/gi; - return input.replace(randomPattern, (match, listString) => { - //split on double colons instead of commas to allow for commas inside random items - const list = listString.split('::').filter(item => item.length > 0); - if (list.length === 0) { - return emptyListPlaceholder; - } - - var rng = new Math.seedrandom('added entropy.', { entropy: true }); - const randomIndex = Math.floor(rng() * list.length); - - //const randomIndex = Math.floor(Math.random() * list.length); - //trim() at the end to allow for empty random values - return list[randomIndex].trim(); - }); + if (randomPatternNew.test(input)) { + return input.replace(randomPatternNew, (match, listString) => { + //split on double colons instead of commas to allow for commas inside random items + const list = listString.split('::').filter(item => item.length > 0); + if (list.length === 0) { + return emptyListPlaceholder; + } + var rng = new Math.seedrandom('added entropy.', { entropy: true }); + const randomIndex = Math.floor(rng() * list.length); + //trim() at the end to allow for empty random values + return list[randomIndex].trim(); + }); + } else if (randomPatternOld.test(input)) { + return input.replace(randomPatternOld, (match, listString) => { + const list = listString.split(',').map(item => item.trim()).filter(item => item.length > 0); + if (list.length === 0) { + return emptyListPlaceholder; + } + var rng = new Math.seedrandom('added entropy.', { entropy: true }); + const randomIndex = Math.floor(rng() * list.length); + return list[randomIndex]; + }); + } else { + return input + } } function diceRollReplace(input, invalidRollPlaceholder = '') { @@ -8563,7 +8575,7 @@ jQuery(async function () { if (this_chid !== undefined || selected_group) { // Previously system messages we're allowed to be edited /*const message = $(this).closest(".mes"); - + if (message.data("isSystem")) { return; }*/ From 67fa9c9595d09aac72c1625cd3df8be31e6d079d Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 25 Nov 2023 20:11:47 +0200 Subject: [PATCH 016/632] Allow dice rolls in {{random}} --- public/script.js | 4 ++-- public/scripts/templates/macros.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/public/script.js b/public/script.js index 787b7e11f..731c6e70d 100644 --- a/public/script.js +++ b/public/script.js @@ -2055,8 +2055,8 @@ function substituteParams(content, _name1, _name2, _original, _group, _replaceCh const utcTime = moment().utc().utcOffset(utcOffset).format('LT'); return utcTime; }); - content = randomReplace(content); content = diceRollReplace(content); + content = randomReplace(content); content = bannedWordsReplace(content); return content; } @@ -8575,7 +8575,7 @@ jQuery(async function () { if (this_chid !== undefined || selected_group) { // Previously system messages we're allowed to be edited /*const message = $(this).closest(".mes"); - + if (message.data("isSystem")) { return; }*/ diff --git a/public/scripts/templates/macros.html b/public/scripts/templates/macros.html index fac3d145c..751607af8 100644 --- a/public/scripts/templates/macros.html +++ b/public/scripts/templates/macros.html @@ -27,8 +27,8 @@
  • {{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}} 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.
  • -
  • {{roll:(formula)}} – rolls a dice. (ex: {{roll:1d6}} will roll a 6- sided dice and return a number between 1 and 6)
  • {{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 0d9068f11e4bbec271ab2f8cc26df29783420813 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 25 Nov 2023 20:33:07 +0200 Subject: [PATCH 017/632] Don't replace {{addvar}} macros with the execution result --- public/scripts/variables.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/public/scripts/variables.js b/public/scripts/variables.js index 1f395486f..6950ec8bc 100644 --- a/public/scripts/variables.js +++ b/public/scripts/variables.js @@ -102,7 +102,8 @@ export function replaceVariableMacros(str) { // Replace {{addvar::name::value}} with empty string and add value to the variable value str = str.replace(/{{addvar::([^:]+)::([^}]+)}}/gi, (_, name, value) => { name = name.trim(); - return addLocalVariable(name, value);; + addLocalVariable(name, value);; + return ''; }); // Replace {{getglobalvar::name}} with the value of the global variable name @@ -121,7 +122,8 @@ export function replaceVariableMacros(str) { // Replace {{addglobalvar::name::value}} with empty string and add value to the global variable value str = str.replace(/{{addglobalvar::([^:]+)::([^}]+)}}/gi, (_, name, value) => { name = name.trim(); - return addGlobalVariable(name, value); + addGlobalVariable(name, value); + return ''; }); return str; From a5c3e228336d5f5572ad82b9269fdea176f2dd64 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 25 Nov 2023 20:41:46 +0200 Subject: [PATCH 018/632] #1055 Add new random syntax to docs --- public/scripts/templates/macros.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/public/scripts/templates/macros.html b/public/scripts/templates/macros.html index 751607af8..1b940e151 100644 --- a/public/scripts/templates/macros.html +++ b/public/scripts/templates/macros.html @@ -27,8 +27,9 @@
  • {{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}} 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.
  • +
  • {{roll:(formula)}} – rolls a dice. (ex: >{{roll:1d6}&rcub 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 04105400669e1799f0a58c0f49bd174dd0313113 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 25 Nov 2023 20:52:17 +0200 Subject: [PATCH 019/632] Process variable macros line by line --- public/scripts/variables.js | 85 +++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 36 deletions(-) diff --git a/public/scripts/variables.js b/public/scripts/variables.js index 6950ec8bc..540dffa2b 100644 --- a/public/scripts/variables.js +++ b/public/scripts/variables.js @@ -85,48 +85,61 @@ export function resolveVariable(name) { return name; } -export function replaceVariableMacros(str) { - // Replace {{getvar::name}} with the value of the variable name - str = str.replace(/{{getvar::([^}]+)}}/gi, (_, name) => { - name = name.trim(); - return getLocalVariable(name); - }); +export function replaceVariableMacros(input) { + const lines = input.split('\n'); - // Replace {{setvar::name::value}} with empty string and set the variable name to value - str = str.replace(/{{setvar::([^:]+)::([^}]+)}}/gi, (_, name, value) => { - name = name.trim(); - setLocalVariable(name, value); - return ''; - }); + for (let i = 0; i < lines.length; i++) { + let line = lines[i]; - // Replace {{addvar::name::value}} with empty string and add value to the variable value - str = str.replace(/{{addvar::([^:]+)::([^}]+)}}/gi, (_, name, value) => { - name = name.trim(); - addLocalVariable(name, value);; - return ''; - }); + // Skip lines without macros + if (!line || !line.includes('{{')) { + continue; + } - // Replace {{getglobalvar::name}} with the value of the global variable name - str = str.replace(/{{getglobalvar::([^}]+)}}/gi, (_, name) => { - name = name.trim(); - return getGlobalVariable(name); - }); + // Replace {{setvar::name::value}} with empty string and set the variable name to value + line = line.replace(/{{setvar::([^:]+)::([^}]+)}}/gi, (_, name, value) => { + name = name.trim(); + setLocalVariable(name, value); + return ''; + }); - // Replace {{setglobalvar::name::value}} with empty string and set the global variable name to value - str = str.replace(/{{setglobalvar::([^:]+)::([^}]+)}}/gi, (_, name, value) => { - name = name.trim(); - setGlobalVariable(name, value); - return ''; - }); + // Replace {{getvar::name}} with the value of the variable name + line = line.replace(/{{getvar::([^}]+)}}/gi, (_, name) => { + name = name.trim(); + return getLocalVariable(name); + }); - // Replace {{addglobalvar::name::value}} with empty string and add value to the global variable value - str = str.replace(/{{addglobalvar::([^:]+)::([^}]+)}}/gi, (_, name, value) => { - name = name.trim(); - addGlobalVariable(name, value); - return ''; - }); + // Replace {{addvar::name::value}} with empty string and add value to the variable value + line = line.replace(/{{addvar::([^:]+)::([^}]+)}}/gi, (_, name, value) => { + name = name.trim(); + addLocalVariable(name, value);; + return ''; + }); - return str; + // Replace {{setglobalvar::name::value}} with empty string and set the global variable name to value + line = line.replace(/{{setglobalvar::([^:]+)::([^}]+)}}/gi, (_, name, value) => { + name = name.trim(); + setGlobalVariable(name, value); + return ''; + }); + + // Replace {{getglobalvar::name}} with the value of the global variable name + line = line.replace(/{{getglobalvar::([^}]+)}}/gi, (_, name) => { + name = name.trim(); + return getGlobalVariable(name); + }); + + // Replace {{addglobalvar::name::value}} with empty string and add value to the global variable value + line = line.replace(/{{addglobalvar::([^:]+)::([^}]+)}}/gi, (_, name, value) => { + name = name.trim(); + addGlobalVariable(name, value); + return ''; + }); + + lines[i] = line; + } + + return lines.join('\n'); } function listVariablesCallback() { From b24d4f2340c3e7efeffbd54a55bbcd84667bf55e Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 25 Nov 2023 21:56:57 +0200 Subject: [PATCH 020/632] Add opt-in CORS bypass endpoint --- default/config.conf | 1 + server.js | 45 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/default/config.conf b/default/config.conf index 257db767e..a256fd234 100644 --- a/default/config.conf +++ b/default/config.conf @@ -11,6 +11,7 @@ const allowKeysExposure = false; // If true, private API keys could be fetched t const skipContentCheck = false; // If true, no new default content will be delivered to you. const thumbnailsQuality = 95; // Quality of thumbnails. 0-100 const disableChatBackup = false; // Disables the backup of chat logs to the /backups folder +const enableCorsProxy = false; // Enables the CORS proxy for the frontend // If true, Allows insecure settings for listen, whitelist, and authentication. // Change this setting only on "trusted networks". Do not change this value unless you are aware of the issues that can arise from changing this setting and configuring a insecure setting. diff --git a/server.js b/server.js index f5db86f8b..2106b9148 100644 --- a/server.js +++ b/server.js @@ -76,8 +76,11 @@ const cliArguments = yargs(hideBin(process.argv)) type: 'boolean', default: null, describe: 'Automatically launch SillyTavern in the browser.' - }) - .option('disableCsrf', { + }).option('corsProxy', { + type: 'boolean', + default: false, + describe: 'Enables CORS proxy', + }).option('disableCsrf', { type: 'boolean', default: false, describe: 'Disables CSRF protection' @@ -319,6 +322,44 @@ app.use(function (req, res, next) { next(); }); +if (config.enableCorsProxy === true || cliArguments.corsProxy === true) { + console.log('Enabling CORS proxy'); + + app.use('/proxy/:url', async (req, res) => { + const url = req.params.url; // get the url from the request path + + // Disallow circular requests + const serverUrl = req.protocol + '://' + req.get('host'); + if (url.startsWith(serverUrl)) { + return res.status(400).send('Circular requests are not allowed'); + } + + try { + const headers = JSON.parse(JSON.stringify(req.headers)); + delete headers['x-csrf-token']; + delete headers['host']; + delete headers['referer']; + delete headers['origin']; + delete headers['cookie']; + delete headers['sec-fetch-mode']; + delete headers['sec-fetch-site']; + delete headers['sec-fetch-dest']; + + const bodyMethods = ['POST', 'PUT', 'PATCH']; + + const response = await fetch(url, { + method: req.method, + headers: headers, + body: bodyMethods.includes(req.method) ? JSON.stringify(req.body) : undefined, + }); + + response.body.pipe(res); // pipe the response to the proxy response + + } catch (error) { + res.status(500).send('Error occurred while trying to proxy to: ' + url + ' ' + error); + } + }); +} app.use(express.static(process.cwd() + "/public", {})); From 0bbaeeaedd5c7d0b2825e3740662cca4b219aed9 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 25 Nov 2023 22:02:40 +0200 Subject: [PATCH 021/632] Revert to get/set/add order for variable macro --- public/scripts/variables.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/public/scripts/variables.js b/public/scripts/variables.js index 540dffa2b..ed6e21047 100644 --- a/public/scripts/variables.js +++ b/public/scripts/variables.js @@ -96,6 +96,12 @@ export function replaceVariableMacros(input) { continue; } + // Replace {{getvar::name}} with the value of the variable name + line = line.replace(/{{getvar::([^}]+)}}/gi, (_, name) => { + name = name.trim(); + return getLocalVariable(name); + }); + // Replace {{setvar::name::value}} with empty string and set the variable name to value line = line.replace(/{{setvar::([^:]+)::([^}]+)}}/gi, (_, name, value) => { name = name.trim(); @@ -103,12 +109,6 @@ export function replaceVariableMacros(input) { return ''; }); - // Replace {{getvar::name}} with the value of the variable name - line = line.replace(/{{getvar::([^}]+)}}/gi, (_, name) => { - name = name.trim(); - return getLocalVariable(name); - }); - // Replace {{addvar::name::value}} with empty string and add value to the variable value line = line.replace(/{{addvar::([^:]+)::([^}]+)}}/gi, (_, name, value) => { name = name.trim(); @@ -116,6 +116,12 @@ export function replaceVariableMacros(input) { return ''; }); + // Replace {{getglobalvar::name}} with the value of the global variable name + line = line.replace(/{{getglobalvar::([^}]+)}}/gi, (_, name) => { + name = name.trim(); + return getGlobalVariable(name); + }); + // Replace {{setglobalvar::name::value}} with empty string and set the global variable name to value line = line.replace(/{{setglobalvar::([^:]+)::([^}]+)}}/gi, (_, name, value) => { name = name.trim(); @@ -123,12 +129,6 @@ export function replaceVariableMacros(input) { return ''; }); - // Replace {{getglobalvar::name}} with the value of the global variable name - line = line.replace(/{{getglobalvar::([^}]+)}}/gi, (_, name) => { - name = name.trim(); - return getGlobalVariable(name); - }); - // Replace {{addglobalvar::name::value}} with empty string and add value to the global variable value line = line.replace(/{{addglobalvar::([^:]+)::([^}]+)}}/gi, (_, name, value) => { name = name.trim(); From 3ec692e76c5218528bf13b7af066978ca31e6d06 Mon Sep 17 00:00:00 2001 From: RossAscends <124905043+RossAscends@users.noreply.github.com> Date: Sun, 26 Nov 2023 05:26:41 +0900 Subject: [PATCH 022/632] fix /world unsetting function --- public/scripts/world-info.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public/scripts/world-info.js b/public/scripts/world-info.js index 720be4ccc..af0aa29a8 100644 --- a/public/scripts/world-info.js +++ b/public/scripts/world-info.js @@ -1574,7 +1574,7 @@ async function checkWorldInfo(chat, maxContext) { //for a NOT all entries must be checked, a single match invalidates activation if (selectiveLogic === 1 && notFlag) { console.debug(`${entry.uid}: activated; passed NOT filter`) - activatedNow.add(entry); + activatedNow.add(entry); } } } @@ -1931,7 +1931,7 @@ export async function importEmbeddedWorldInfo(skipPopup = false) { function onWorldInfoChange(_, text) { if (_ !== '__notSlashCommand__') { // if it's a slash command - if (text !== undefined) { // and args are provided + if (text.trim() !== '') { // and args are provided const slashInputSplitText = text.trim().toLowerCase().split(","); slashInputSplitText.forEach((worldName) => { @@ -1948,7 +1948,7 @@ function onWorldInfoChange(_, text) { } else { // if no args, unset all worlds toastr.success('Deactivated all worlds'); selected_world_info = []; - $("#world_info").val(""); + $("#world_info").val(null).trigger('change'); } } else { //if it's a pointer selection let tempWorldInfo = []; From df15a00430edf9f7285dbc8bea8978414bfb02c3 Mon Sep 17 00:00:00 2001 From: RossAscends <124905043+RossAscends@users.noreply.github.com> Date: Sun, 26 Nov 2023 06:27:13 +0900 Subject: [PATCH 023/632] resolve roll&random before parsing macro var commands --- public/script.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/public/script.js b/public/script.js index 731c6e70d..718cc0e0a 100644 --- a/public/script.js +++ b/public/script.js @@ -2009,7 +2009,8 @@ function substituteParams(content, _name1, _name2, _original, _group, _replaceCh if (typeof _original === 'string') { content = content.replace(/{{original}}/i, _original); } - + content = diceRollReplace(content); + content = randomReplace(content); content = replaceVariableMacros(content); content = content.replace(/{{newline}}/gi, "\n"); content = content.replace(/{{input}}/gi, String($('#send_textarea').val())); @@ -2055,8 +2056,6 @@ function substituteParams(content, _name1, _name2, _original, _group, _replaceCh const utcTime = moment().utc().utcOffset(utcOffset).format('LT'); return utcTime; }); - content = diceRollReplace(content); - content = randomReplace(content); content = bannedWordsReplace(content); return content; } From a7024a1d342eeffd679210c37f1aeb9f40cc30a2 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 25 Nov 2023 23:45:33 +0200 Subject: [PATCH 024/632] Migrate to config.yaml --- .github/readme-zh_cn.md | 10 ++-- .github/readme.md | 10 ++-- .gitignore | 2 + Dockerfile | 2 +- Remote-Link.cmd | 2 +- default/config.conf | 57 ------------------- default/config.yaml | 53 ++++++++++++++++++ docker/docker-entrypoint.sh | 16 +++--- package-lock.json | 9 +++ package.json | 1 + post-install.js | 106 +++++++++++++++++++++++++++++++++++- public/scripts/secrets.js | 2 +- server.js | 51 ++++++----------- src/content-manager.js | 4 +- src/secrets.js | 4 +- src/transformers.mjs | 2 +- src/util.js | 57 ++++++++++++++++--- 17 files changed, 261 insertions(+), 127 deletions(-) delete mode 100644 default/config.conf create mode 100644 default/config.yaml diff --git a/.github/readme-zh_cn.md b/.github/readme-zh_cn.md index 415fce222..9fafb2d86 100644 --- a/.github/readme-zh_cn.md +++ b/.github/readme-zh_cn.md @@ -170,7 +170,7 @@ SillyTavern 会将 API 密钥保存在目录中的 `secrets.json` 文件内。 如果要想通过点击 API 输入框旁边的按钮来查看密钥,请按照以下设置: -1. 打开 `config.conf` 文件,将里面的 `allowKeysExposure` 设置为 `true`。 +1. 打开 `config.yaml` 文件,将里面的 `allowKeysExposure` 设置为 `true`。 2. 然后重启 SillyTavern 服务。 ## 远程访问 @@ -207,7 +207,7 @@ SillyTavern 会将 API 密钥保存在目录中的 `secrets.json` 文件内。 然后,文件中设置的 IP 就可以访问 SillyTavern 了。 -*注意:"config.conf" 文件内也有一个 "whitelist" 设置,你可以用同样的方法设置它,但如果 "whitelist.txt" 文件存在,这个设置将被忽略。 +*注意:"config.yaml" 文件内也有一个 "whitelist" 设置,你可以用同样的方法设置它,但如果 "whitelist.txt" 文件存在,这个设置将被忽略。 ### 2.获取 SillyTavern 服务的 IP 地址 @@ -233,19 +233,19 @@ SillyTavern 会将 API 密钥保存在目录中的 `secrets.json` 文件内。 ### 向所有 IP 开放您的 SillyTavern 服务 -我们不建议这样做,但您可以打开 `config.conf` 并将里面的 `whitelist` 设置改为 `false`。 +我们不建议这样做,但您可以打开 `config.yaml` 并将里面的 `whitelist` 设置改为 `false`。 你必须删除(或重命名)SillyTavern 文件夹中的 `whitelist.txt` 文件(如果有的话)。 这通常是不安全的做法,所以我们要求在这样做时必须设置用户名和密码。 -用户名和密码在`config.conf`文件中设置。 +用户名和密码在`config.yaml`文件中设置。 重启 SillyTavern 服务后,只要知道用户名和密码,任何设备都可以访问。 ### 还是无法访问? -* 为 `config.conf` 文件中的端口创建一条入站/出站防火墙规则。切勿将此误认为是路由器上的端口转发,否则,有人可能会发现你的聊天隐私,那就大错特错了。 +* 为 `config.yaml` 文件中的端口创建一条入站/出站防火墙规则。切勿将此误认为是路由器上的端口转发,否则,有人可能会发现你的聊天隐私,那就大错特错了。 * 在 "设置" > "网络和 Internet" > "以太网" 中启用 "专用网络" 配置。这对 Windows 11 非常重要,否则即使添加了上述防火墙规则也无法连接。 ### 性能问题? diff --git a/.github/readme.md b/.github/readme.md index 3fa3826b4..cbcc8e304 100644 --- a/.github/readme.md +++ b/.github/readme.md @@ -173,7 +173,7 @@ By default, they will not be exposed to a frontend after you enter them and relo In order to enable viewing your keys by clicking a button in the API block: -1. Set the value of `allowKeysExposure` to `true` in `config.conf` file. +1. Set the value of `allowKeysExposure` to `true` in `config.yaml` file. 2. Restart the SillyTavern server. ## Remote connections @@ -211,7 +211,7 @@ CIDR masks are also accepted (eg. 10.0.0.0/24). Now devices which have the IP specified in the file will be able to connect. -*Note: `config.conf` also has a `whitelist` array, which you can use in the same way, but this array will be ignored if `whitelist.txt` exists.* +*Note: `config.yaml` also has a `whitelist` array, which you can use in the same way, but this array will be ignored if `whitelist.txt` exists.* ### 2. Getting the IP for the ST host machine @@ -237,19 +237,19 @@ Use http:// NOT https:// ### Opening your ST to all IPs -We do not recommend doing this, but you can open `config.conf` and change `whitelist` to `false`. +We do not recommend doing this, but you can open `config.yaml` and change `whitelist` to `false`. You must remove (or rename) `whitelist.txt` in the SillyTavern base install folder if it exists. This is usually an insecure practice, so we require you to set a username and password when you do this. -The username and password are set in `config.conf`. +The username and password are set in `config.yaml`. After restarting your ST server, any device will be able to connect to it, regardless of their IP as long as they know the username and password. ### Still Unable To Connect? -* Create an inbound/outbound firewall rule for the port found in `config.conf`. Do NOT mistake this for port-forwarding on your router, otherwise, someone could find your chat logs and that's a big no-no. +* Create an inbound/outbound firewall rule for the port found in `config.yaml`. Do NOT mistake this for port-forwarding on your router, otherwise, someone could find your chat logs and that's a big no-no. * Enable the Private Network profile type in Settings > Network and Internet > Ethernet. This is VERY important for Windows 11, otherwise, you would be unable to connect even with the aforementioned firewall rules. ## Performance issues? diff --git a/.gitignore b/.gitignore index 507f91617..ae782859a 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,8 @@ public/stats.json /uploads/ *.jsonl /config.conf +/config.yaml +/config.conf.bak /docker/config .DS_Store public/settings.json diff --git a/Dockerfile b/Dockerfile index 3afd3b7a5..ec0f2e946 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,7 +31,7 @@ RUN \ echo "*** Create symbolic links to config directory ***" && \ for R in $RESOURCES; do ln -s "../config/$R" "public/$R"; done || true && \ \ - ln -s "./config/config.conf" "config.conf" || true && \ + ln -s "./config/config.yaml" "config.yaml" || true && \ ln -s "../config/settings.json" "public/settings.json" || true && \ ln -s "../../config/bg_load.css" "public/css/bg_load.css" || true && \ mkdir "config" || true diff --git a/Remote-Link.cmd b/Remote-Link.cmd index f44f232f2..2747abdd2 100644 --- a/Remote-Link.cmd +++ b/Remote-Link.cmd @@ -4,7 +4,7 @@ echo WARNING: Cloudflare Tunnel! echo ======================================================================================================================== echo This script downloads and runs the latest cloudflared.exe from Cloudflare to set up an HTTPS tunnel to your SillyTavern! echo Using the randomly generated temporary tunnel URL, anyone can access your SillyTavern over the Internet while the tunnel -echo is active. Keep the URL safe and secure your SillyTavern installation by setting a username and password in config.conf! +echo is active. Keep the URL safe and secure your SillyTavern installation by setting a username and password in config.yaml! echo. echo See https://docs.sillytavern.app/usage/remoteconnections/ for more details about how to secure your SillyTavern install. echo. diff --git a/default/config.conf b/default/config.conf deleted file mode 100644 index a256fd234..000000000 --- a/default/config.conf +++ /dev/null @@ -1,57 +0,0 @@ -const port = 8000; -const whitelist = ['127.0.0.1']; //Example for add several IP in whitelist: ['127.0.0.1', '192.168.0.10'] -const whitelistMode = true; //Disabling enabling the ip whitelist mode. true/false -const basicAuthMode = false; //Toggle basic authentication for endpoints. -const basicAuthUser = {username: "user", password: "password"}; //Login credentials when basicAuthMode is true. -const disableThumbnails = false; //Disables the generation of thumbnails, opting to use the raw images instead -const autorun = true; //Autorun in the browser. true/false -const enableExtensions = true; //Enables support for TavernAI-extras project -const listen = true; // If true, Can be access from other device or PC. otherwise can be access only from hosting machine. -const allowKeysExposure = false; // If true, private API keys could be fetched to the frontend. -const skipContentCheck = false; // If true, no new default content will be delivered to you. -const thumbnailsQuality = 95; // Quality of thumbnails. 0-100 -const disableChatBackup = false; // Disables the backup of chat logs to the /backups folder -const enableCorsProxy = false; // Enables the CORS proxy for the frontend - -// If true, Allows insecure settings for listen, whitelist, and authentication. -// Change this setting only on "trusted networks". Do not change this value unless you are aware of the issues that can arise from changing this setting and configuring a insecure setting. -const securityOverride = false; - -// Additional settings for extra modules / extensions -const extras = { - // Disables auto-download of models from the HuggingFace Hub. - // You will need to manually download the models and put them into the /cache folder. - disableAutoDownload: false, - // Text classification model for sentiment analysis. HuggingFace ID of a model in ONNX format. - classificationModel: 'Cohee/distilbert-base-uncased-go-emotions-onnx', - // Image captioning model. HuggingFace ID of a model in ONNX format. - captioningModel: 'Xenova/vit-gpt2-image-captioning', - // Feature extraction model. HuggingFace ID of a model in ONNX format. - embeddingModel: 'Xenova/all-mpnet-base-v2', - // GPT-2 text generation model. HuggingFace ID of a model in ONNX format. - promptExpansionModel: 'Cohee/fooocus_expansion-onnx', -}; - -// Request overrides for additional headers -// Format is an array of objects: -// { hosts: [ "" ], headers: {
    : "" } } -const requestOverrides = []; - -module.exports = { - port, - whitelist, - whitelistMode, - basicAuthMode, - basicAuthUser, - autorun, - enableExtensions, - listen, - disableThumbnails, - allowKeysExposure, - securityOverride, - skipContentCheck, - requestOverrides, - thumbnailsQuality, - extras, - disableChatBackup, -}; diff --git a/default/config.yaml b/default/config.yaml new file mode 100644 index 000000000..c129b39ad --- /dev/null +++ b/default/config.yaml @@ -0,0 +1,53 @@ +# -- NETWORK CONFIGURATION -- +# Listen for incoming connections +listen: true +# Server port +port: 8000 +# Toggle whitelist mode +whitelistMode: true +# Whitelist of allowed IP addresses +whitelist: + - 127.0.0.1 +# Toggle basic authentication for endpoints +basicAuthMode: false +# Basic authentication credentials +basicAuthUser: + username: user + password: password +# Enables CORS proxy middleware +enableCorsProxy: false +# Disable security checks - NOT RECOMMENDED +securityOverride: false +# -- ADVANCED CONFIGURATION -- +# Open the browser automatically +autorun: true +# Disable thumbnail generation +disableThumbnails: false +# Thumbnail quality (0-100) +thumbnailsQuality: 95 +# Allow secret keys exposure via API +allowKeysExposure: false +# Skip new default content checks +skipContentCheck: false +# Disable automatic chats backup +disableChatBackup: false +# API request overrides (for KoboldAI and Text Completion APIs) +## Format is an array of objects: +## - hosts: +## - example.com +## headers: +## Content-Type: application/json +requestOverrides: [] +# -- PLUGIN CONFIGURATION -- +# Enable UI extensions +enableExtensions: true +# Extension settings +extras: + # Disables automatic model download from HuggingFace + disableAutoDownload: false + # Extra models for plugins. Expects model IDs from HuggingFace model hub in ONNX format + classificationModel: Cohee/distilbert-base-uncased-go-emotions-onnx + captioningModel: Xenova/vit-gpt2-image-captioning + embeddingModel: Xenova/all-mpnet-base-v2 + promptExpansionModel: Cohee/fooocus_expansion-onnx + diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh index f3dd2aae9..69d8aa68c 100644 --- a/docker/docker-entrypoint.sh +++ b/docker/docker-entrypoint.sh @@ -9,9 +9,9 @@ for R in $RESOURCES; do fi done -if [ ! -e "config/config.conf" ]; then - echo "Resource not found, copying from defaults: config.conf" - cp -r "default/config.conf" "config/config.conf" +if [ ! -e "config/config.yaml" ]; then + echo "Resource not found, copying from defaults: config.yaml" + cp -r "default/config.yaml" "config/config.yaml" fi if [ ! -e "config/settings.json" ]; then @@ -24,15 +24,15 @@ if [ ! -e "config/bg_load.css" ]; then cp -r "default/bg_load.css" "config/bg_load.css" fi -CONFIG_FILE="config.conf" +CONFIG_FILE="config.yaml" -if grep -q "listen = false" $CONFIG_FILE; then - echo -e "\033[1;31mThe listen parameter is set to false. If you can't connect to the server, edit the \"docker/config/config.conf\" file and restart the container.\033[0m" +if grep -q "listen: false" $CONFIG_FILE; then + echo -e "\033[1;31mThe listen parameter is set to false. If you can't connect to the server, edit the \"docker/config/config.yaml\" file and restart the container.\033[0m" sleep 5 fi -if grep -q "whitelistMode = true" $CONFIG_FILE; then - echo -e "\033[1;31mThe whitelistMode parameter is set to true. If you can't connect to the server, edit the \"docker/config/config.conf\" file and restart the container.\033[0m" +if grep -q "whitelistMode: true" $CONFIG_FILE; then + echo -e "\033[1;31mThe whitelistMode parameter is set to true. If you can't connect to the server, edit the \"docker/config/config.yaml\" file and restart the container.\033[0m" sleep 5 fi diff --git a/package-lock.json b/package-lock.json index 37ad9a260..3ca8ea045 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,7 @@ "vectra": "^0.2.2", "write-file-atomic": "^5.0.1", "ws": "^8.13.0", + "yaml": "^2.3.4", "yargs": "^17.7.1", "yauzl": "^2.10.0" }, @@ -4388,6 +4389,14 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/yaml": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "engines": { + "node": ">= 14" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/package.json b/package.json index 2b9f1030d..2289b0523 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "vectra": "^0.2.2", "write-file-atomic": "^5.0.1", "ws": "^8.13.0", + "yaml": "^2.3.4", "yargs": "^17.7.1", "yauzl": "^2.10.0" }, diff --git a/post-install.js b/post-install.js index 489bf8408..541ce35f9 100644 --- a/post-install.js +++ b/post-install.js @@ -4,6 +4,102 @@ const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); +const yaml = require('yaml'); +const _ = require('lodash'); + +/** + * Colorizes console output. + */ +const color = { + byNum: (mess, fgNum) => { + mess = mess || ''; + fgNum = fgNum === undefined ? 31 : fgNum; + return '\u001b[' + fgNum + 'm' + mess + '\u001b[39m'; + }, + black: (mess) => color.byNum(mess, 30), + red: (mess) => color.byNum(mess, 31), + green: (mess) => color.byNum(mess, 32), + yellow: (mess) => color.byNum(mess, 33), + blue: (mess) => color.byNum(mess, 34), + magenta: (mess) => color.byNum(mess, 35), + cyan: (mess) => color.byNum(mess, 36), + white: (mess) => color.byNum(mess, 37) +}; + +/** + * Gets all keys from an object recursively. + * @param {object} obj Object to get all keys from + * @param {string} prefix Prefix to prepend to all keys + * @returns {string[]} Array of all keys in the object + */ +function getAllKeys(obj, prefix = '') { + if (typeof obj !== 'object' || Array.isArray(obj)) { + return []; + } + + return _.flatMap(Object.keys(obj), key => { + const newPrefix = prefix ? `${prefix}.${key}` : key; + if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) { + return getAllKeys(obj[key], newPrefix); + } else { + return [newPrefix]; + } + }); +} + +/** + * Converts the old config.conf file to the new config.yaml format. + */ +function convertConfig() { + if (fs.existsSync('./config.conf')) { + if (fs.existsSync('./config.yaml')) { + console.log(color.yellow('Both config.conf and config.yaml exist. Please delete config.conf manually.')); + return; + } + + try { + console.log(color.blue('Converting config.conf to config.yaml. Your old config.conf will be renamed to config.conf.bak')); + const config = require(path.join(process.cwd(), './config.conf')); + fs.renameSync('./config.conf', './config.conf.bak'); + fs.writeFileSync('./config.yaml', yaml.stringify(config)); + console.log(color.green('Conversion successful. Please check your config.yaml and fix it if necessary.')); + } catch (error) { + console.error(color.red('FATAL: Config conversion failed. Please check your config.conf file and try again.')); + return; + } + } +} + +/** + * Compares the current config.yaml with the default config.yaml and adds any missing values. + */ +function addMissingConfigValues() { + try { + const defaultConfig = yaml.parse(fs.readFileSync(path.join(process.cwd(), './default/config.yaml'), 'utf8')); + let config = yaml.parse(fs.readFileSync(path.join(process.cwd(), './config.yaml'), 'utf8')); + + // Get all keys from the original config + const originalKeys = getAllKeys(config); + + // Use lodash's defaultsDeep function to recursively apply default properties + config = _.defaultsDeep(config, defaultConfig); + + // Get all keys from the updated config + const updatedKeys = getAllKeys(config); + + // Find the keys that were added + const addedKeys = _.difference(updatedKeys, originalKeys); + + if (addedKeys.length === 0) { + return; + } + + console.log('Adding missing config values to config.yaml:', addedKeys); + fs.writeFileSync('./config.yaml', yaml.stringify(config)); + } catch (error) { + console.error(color.red('FATAL: Could not add missing config values to config.yaml'), error); + } +} /** * Creates the default config files if they don't exist yet. @@ -12,7 +108,7 @@ function createDefaultFiles() { const files = { settings: './public/settings.json', bg_load: './public/css/bg_load.css', - config: './config.conf', + config: './config.yaml', user: './public/css/user.css', }; @@ -21,10 +117,10 @@ function createDefaultFiles() { if (!fs.existsSync(file)) { const defaultFilePath = path.join('./default', path.parse(file).base); fs.copyFileSync(defaultFilePath, file); - console.log(`Created default file: ${file}`); + console.log(color.green(`Created default file: ${file}`)); } } catch (error) { - console.error(`FATAL: Could not write default file: ${file}`, error); + console.error(color.red(`FATAL: Could not write default file: ${file}`), error); } } } @@ -73,10 +169,14 @@ function copyWasmFiles() { } try { + // 0. Convert config.conf to config.yaml + convertConfig(); // 1. Create default config files createDefaultFiles(); // 2. Copy transformers WASM binaries from node_modules copyWasmFiles(); + // 3. Add missing config values + addMissingConfigValues(); } catch (error) { console.error(error); } diff --git a/public/scripts/secrets.js b/public/scripts/secrets.js index 0e23eb4d4..395fce929 100644 --- a/public/scripts/secrets.js +++ b/public/scripts/secrets.js @@ -55,7 +55,7 @@ async function viewSecrets() { }); if (response.status == 403) { - callPopup('

    Forbidden

    To view your API keys here, set the value of allowKeysExposure to true in config.conf file and restart the SillyTavern server.

    ', 'text'); + callPopup('

    Forbidden

    To view your API keys here, set the value of allowKeysExposure to true in config.yaml file and restart the SillyTavern server.

    ', 'text'); return; } diff --git a/server.js b/server.js index 2106b9148..40c9fec9f 100644 --- a/server.js +++ b/server.js @@ -55,7 +55,7 @@ const characterCardParser = require('./src/character-card-parser.js'); const contentManager = require('./src/content-manager'); const statsHelpers = require('./statsHelpers.js'); const { readSecret, migrateSecrets, SECRET_KEYS } = require('./src/secrets'); -const { delay, getVersion, deepMerge } = require('./src/util'); +const { delay, getVersion, deepMerge, getConfigValue, color } = require('./src/util'); const { invalidateThumbnail, ensureThumbnailCache } = require('./src/thumbnails'); const { getTokenizerModel, getTiktokenTokenizer, loadTokenizers, TEXT_COMPLETION_MODELS, getSentencepiceTokenizer, sentencepieceTokenizers } = require('./src/tokenizers'); const { convertClaudePrompt } = require('./src/chat-completion'); @@ -109,12 +109,10 @@ app.use(responseTime()); // impoort from statsHelpers.js -const config = require(path.join(process.cwd(), './config.conf')); - -const server_port = process.env.SILLY_TAVERN_PORT || config.port; +const server_port = process.env.SILLY_TAVERN_PORT || getConfigValue('port'); const whitelistPath = path.join(process.cwd(), "./whitelist.txt"); -let whitelist = config.whitelist; +let whitelist = getConfigValue('whitelist', []); if (fs.existsSync(whitelistPath)) { try { @@ -123,10 +121,10 @@ if (fs.existsSync(whitelistPath)) { } catch (e) { } } -const whitelistMode = config.whitelistMode; -const autorun = config.autorun && cliArguments.autorun !== false && !cliArguments.ssl; -const enableExtensions = config.enableExtensions; -const listen = config.listen; +const whitelistMode = getConfigValue('whitelistMode', true); +const autorun = getConfigValue('autorun') && cliArguments.autorun !== false && !cliArguments.ssl; +const enableExtensions = getConfigValue('enableExtensions', true); +const listen = getConfigValue('listen', false); const API_OPENAI = "https://api.openai.com/v1"; const API_CLAUDE = "https://api.anthropic.com/v1"; @@ -138,22 +136,6 @@ let main_api = "kobold"; let characters = {}; let response_dw_bg; -let color = { - byNum: (mess, fgNum) => { - mess = mess || ''; - fgNum = fgNum === undefined ? 31 : fgNum; - return '\u001b[' + fgNum + 'm' + mess + '\u001b[39m'; - }, - black: (mess) => color.byNum(mess, 30), - red: (mess) => color.byNum(mess, 31), - green: (mess) => color.byNum(mess, 32), - yellow: (mess) => color.byNum(mess, 33), - blue: (mess) => color.byNum(mess, 34), - magenta: (mess) => color.byNum(mess, 35), - cyan: (mess) => color.byNum(mess, 36), - white: (mess) => color.byNum(mess, 37) -}; - function getMancerHeaders() { const apiKey = readSecret(SECRET_KEYS.MANCER); @@ -182,7 +164,8 @@ function getTabbyHeaders() { } function getOverrideHeaders(urlHost) { - const overrideHeaders = config.requestOverrides?.find((e) => e.hosts?.includes(urlHost))?.headers; + const requestOverrides = getConfigValue('requestOverrides', []); + const overrideHeaders = requestOverrides?.find((e) => e.hosts?.includes(urlHost))?.headers; if (overrideHeaders && urlHost) { return overrideHeaders; } else { @@ -277,7 +260,7 @@ const CORS = cors({ app.use(CORS); -if (listen && config.basicAuthMode) app.use(basicAuthMiddleware); +if (listen && getConfigValue('basicAuthMode', false)) app.use(basicAuthMiddleware); // IP Whitelist // let knownIPs = new Set(); @@ -316,13 +299,13 @@ app.use(function (req, res, 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.conf 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.conf in root of SillyTavern folder.'); + 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(); }); -if (config.enableCorsProxy === true || cliArguments.corsProxy === true) { +if (getConfigValue('enableCorsProxy', false) === true || cliArguments.corsProxy === true) { console.log('Enabling CORS proxy'); app.use('/proxy/:url', async (req, res) => { @@ -3670,12 +3653,12 @@ const setupTasks = async function () { console.log(color.green('SillyTavern is listening on: ' + tavernUrl)); 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.conf to "listen=false". Check "access.log" file in the SillyTavern directory if you want to inspect incoming connections.\n'); + 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 (listen && !config.whitelistMode && !config.basicAuthMode) { - if (config.securityOverride) { +if (listen && !getConfigValue('whitelistMode', true) && !getConfigValue('basicAuthMode', false)) { + if (getConfigValue('securityOverride', false)) { console.warn(color.red("Security has been overridden. If it's not a trusted network, change the settings.")); } else { @@ -3722,7 +3705,7 @@ function generateTimestamp() { */ function backupChat(name, chat) { try { - const isBackupDisabled = config.disableChatBackup; + const isBackupDisabled = getConfigValue('disableChatBackup', false); if (isBackupDisabled) { return; diff --git a/src/content-manager.js b/src/content-manager.js index 15e4dd5d9..9dfb70dcc 100644 --- a/src/content-manager.js +++ b/src/content-manager.js @@ -2,14 +2,14 @@ const fs = require('fs'); const path = require('path'); const fetch = require('node-fetch').default; const sanitize = require('sanitize-filename'); -const config = require(path.join(process.cwd(), './config.conf')); +const { getConfigValue } = require('./util'); const contentDirectory = path.join(process.cwd(), 'default/content'); const contentLogPath = path.join(contentDirectory, 'content.log'); const contentIndexPath = path.join(contentDirectory, 'index.json'); function checkForNewContent() { try { - if (config.skipContentCheck) { + if (getConfigValue('skipContentCheck', false)) { return; } diff --git a/src/secrets.js b/src/secrets.js index 5c577b1ef..cc7056271 100644 --- a/src/secrets.js +++ b/src/secrets.js @@ -173,7 +173,7 @@ function registerEndpoints(app, jsonParser) { const allowKeysExposure = getConfigValue('allowKeysExposure', false); if (!allowKeysExposure) { - console.error('secrets.json could not be viewed unless the value of allowKeysExposure in config.conf is set to true'); + console.error('secrets.json could not be viewed unless the value of allowKeysExposure in config.yaml is set to true'); return response.sendStatus(403); } @@ -195,7 +195,7 @@ function registerEndpoints(app, jsonParser) { const allowKeysExposure = getConfigValue('allowKeysExposure', false); if (!allowKeysExposure) { - console.error('Cannot fetch secrets unless allowKeysExposure in config.conf is set to true'); + console.error('Cannot fetch secrets unless allowKeysExposure in config.yaml is set to true'); return response.sendStatus(403); } diff --git a/src/transformers.mjs b/src/transformers.mjs index 03d4a25cd..db3649fb4 100644 --- a/src/transformers.mjs +++ b/src/transformers.mjs @@ -65,7 +65,7 @@ function getModelForTask(task) { const model = getConfigValue(tasks[task].configField, null); return model || defaultModel; } catch (error) { - console.warn('Failed to read config.conf, using default classification model.'); + console.warn('Failed to read config.yaml, using default classification model.'); return defaultModel; } } diff --git a/src/util.js b/src/util.js index 78a7f26f0..0dc7473cc 100644 --- a/src/util.js +++ b/src/util.js @@ -4,20 +4,46 @@ const commandExistsSync = require('command-exists').sync; const _ = require('lodash'); const yauzl = require('yauzl'); const mime = require('mime-types'); +const yaml = require('yaml'); const { default: simpleGit } = require('simple-git'); /** - * Returns the config object from the config.conf file. + * Returns the config object from the config.yaml file. * @returns {object} Config object */ function getConfig() { - try { - const config = require(path.join(process.cwd(), './config.conf')); - return config; - } catch (error) { - console.warn('Failed to read config.conf'); - return {}; + function getNewConfig() { + try { + const config = yaml.parse(fs.readFileSync(path.join(process.cwd(), './config.yaml'), 'utf8')); + return config; + } catch (error) { + console.warn('Failed to read config.yaml'); + return {}; + } } + + function getLegacyConfig() { + try { + console.log(color.yellow('WARNING: config.conf is deprecated. Please run "npm run postinstall" to convert to config.yaml')); + const config = require(path.join(process.cwd(), './config.conf')); + return config; + } catch (error) { + console.warn('Failed to read config.conf'); + return {}; + } + } + + if (fs.existsSync('./config.yaml')) { + return getNewConfig(); + } + + if (fs.existsSync('./config.conf')) { + return getLegacyConfig(); + } + + console.error(color.red('No config file found. Please create a config.yaml file. The default config file can be found in the /default folder.')); + console.error(color.red('The program will now exit.')); + process.exit(1); } /** @@ -217,6 +243,22 @@ function deepMerge(target, source) { return output; } +const color = { + byNum: (mess, fgNum) => { + mess = mess || ''; + fgNum = fgNum === undefined ? 31 : fgNum; + return '\u001b[' + fgNum + 'm' + mess + '\u001b[39m'; + }, + black: (mess) => color.byNum(mess, 30), + red: (mess) => color.byNum(mess, 31), + green: (mess) => color.byNum(mess, 32), + yellow: (mess) => color.byNum(mess, 33), + blue: (mess) => color.byNum(mess, 34), + magenta: (mess) => color.byNum(mess, 35), + cyan: (mess) => color.byNum(mess, 36), + white: (mess) => color.byNum(mess, 37) +}; + module.exports = { getConfig, getConfigValue, @@ -227,4 +269,5 @@ module.exports = { readAllChunks, delay, deepMerge, + color, }; From 0648da8d05e4bbb02bee1c4143f498329d8c17f3 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 26 Nov 2023 00:41:28 +0200 Subject: [PATCH 025/632] Docker fix --- Dockerfile | 1 + docker/docker-entrypoint.sh | 3 +++ 2 files changed, 4 insertions(+) diff --git a/Dockerfile b/Dockerfile index ec0f2e946..0f4e6c0be 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,6 +31,7 @@ RUN \ echo "*** Create symbolic links to config directory ***" && \ for R in $RESOURCES; do ln -s "../config/$R" "public/$R"; done || true && \ \ + rm -f "config.yaml" "public/settings.json" "public/css/bg_load.css" || true && \ ln -s "./config/config.yaml" "config.yaml" || true && \ ln -s "../config/settings.json" "public/settings.json" || true && \ ln -s "../../config/bg_load.css" "public/css/bg_load.css" || true && \ diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh index 69d8aa68c..8f0cdf86e 100644 --- a/docker/docker-entrypoint.sh +++ b/docker/docker-entrypoint.sh @@ -26,6 +26,9 @@ fi CONFIG_FILE="config.yaml" +echo "Starting with the following config:" +cat $CONFIG_FILE + if grep -q "listen: false" $CONFIG_FILE; then echo -e "\033[1;31mThe listen parameter is set to false. If you can't connect to the server, edit the \"docker/config/config.yaml\" file and restart the container.\033[0m" sleep 5 From 50322ed8b0ee43efa4bbb924a9dbcb22c37faa59 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 26 Nov 2023 00:52:00 +0200 Subject: [PATCH 026/632] Don't show auto-update toast if no extensions installed --- public/scripts/extensions.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/public/scripts/extensions.js b/public/scripts/extensions.js index 36474c486..08656b551 100644 --- a/public/scripts/extensions.js +++ b/public/scripts/extensions.js @@ -846,6 +846,10 @@ async function checkForExtensionUpdates(force) { } async function autoUpdateExtensions() { + if (!Object.values(manifests).some(x => x.auto_update)) { + return; + } + toastr.info('Auto-updating extensions. This may take several minutes.', 'Please wait...', { timeOut: 10000, extendedTimeOut: 20000 }); const promises = []; for (const [id, manifest] of Object.entries(manifests)) { From c6aea00e273be8bc3b28a58a8357cb804e00aaca Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 26 Nov 2023 00:56:55 +0200 Subject: [PATCH 027/632] Resolve ephemeral stop strings variables --- public/scripts/slash-commands.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index 878918ae2..eb10641a2 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -332,7 +332,7 @@ async function generateRawCallback(args, value) { deactivateSendButtons(); } - setEphemeralStopStrings(args?.stop); + setEphemeralStopStrings(resolveVariable(args?.stop)); const result = await generateRaw(value, '', isFalseBoolean(args?.instruct)); return result; } finally { @@ -358,7 +358,7 @@ async function generateCallback(args, value) { deactivateSendButtons(); } - setEphemeralStopStrings(args?.stop); + setEphemeralStopStrings(resolveVariable(args?.stop)); const result = await generateQuietPrompt(value, false, false, ''); return result; } finally { From c259c0a72a29f459655775128fe75b02820d7d1d Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 26 Nov 2023 01:15:19 +0200 Subject: [PATCH 028/632] Skip hidden messages from /message command --- public/scripts/slash-commands.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index eb10641a2..32f13647e 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -212,6 +212,10 @@ function getMessagesCallback(args, value) { continue; } + if (message.is_system) { + continue; + } + if (includeNames) { messages.push(`${message.name}: ${message.mes}`); } else { From 283d49a6ee614bd1e72f864cf2046faf94cf8bc2 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 26 Nov 2023 01:49:37 +0200 Subject: [PATCH 029/632] Add empty return value to /while --- public/scripts/variables.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/scripts/variables.js b/public/scripts/variables.js index ed6e21047..09ecd5cc8 100644 --- a/public/scripts/variables.js +++ b/public/scripts/variables.js @@ -176,6 +176,8 @@ async function whileCallback(args, command) { break; } } + + return ''; } async function ifCallback(args, command) { From 9587a704c5eccf135caed28404fc9943144d2f07 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 26 Nov 2023 01:52:41 +0200 Subject: [PATCH 030/632] Fix docstrings --- public/scripts/variables.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/scripts/variables.js b/public/scripts/variables.js index 09ecd5cc8..27eef66fc 100644 --- a/public/scripts/variables.js +++ b/public/scripts/variables.js @@ -348,8 +348,8 @@ export function registerVariableCommands() { registerSlashCommand('setglobalvar', (args, value) => setGlobalVariable(args.key || args.name, value), [], 'key=varname (value) – set a global variable value and pass it down the pipe, e.g. /setglobalvar key=color green', true, true); registerSlashCommand('getglobalvar', (_, value) => getGlobalVariable(value), [], '(key) – get a global variable value and pass it down the pipe, e.g. /getglobalvar height', true, true); registerSlashCommand('addglobalvar', (args, value) => addGlobalVariable(args.key || args.name, value), [], 'key=varname (increment) – add a value to a global variable and pass the result down the pipe, e.g. /addglobalvar score 10', true, true); - registerSlashCommand('if', ifCallback, [], 'a=varname1 b=varname2 rule=comparison else="(alt.command)" "(command)" – compare the value of variable "a" with the value of variable "b", and if the condition yields true, then execute any valid slash command enclosed in quotes and pass the result of the command execution down the pipe. Numeric values and string literals for "a" and "b" supported. Available rules: gt => a > b, gte => a >= b, lt => a < b, lte => a <= b, eq => a == b, neq => a != b, not => !a, in (strings) => a includes b, nin (strings) => a not includes b, e.g. /if a=score a=10 rule=gte "/speak You win" triggers a /speak command if the value of "score" is greater or equals 10.', true, true); - registerSlashCommand('while', whileCallback, [], 'a=varname1 b=varname2 rule=comparison "(command)" – compare the value of variable "a" with the value of variable "b", and if the condition yields true, then execute any valid slash command enclosed in quotes. Numeric values and string literals for "a" and "b" supported. Available rules: gt => a > b, gte => a >= b, lt => a < b, lte => a <= b, eq => a == b, neq => a != b, not => !a, in (strings) => a includes b, nin (strings) => a not includes b, e.g. /while a=i a=10 rule=let "/addvar i 1" adds 1 to the value of "i" until it reaches 10. Loops are limited to 100 iterations by default, pass guard=off to disable.', true, true); + registerSlashCommand('if', ifCallback, [], 'left=varname1 right=varname2 rule=comparison else="(alt.command)" "(command)" – compare the value of the left operand "a" with the value of the right operand "b", and if the condition yields true, then execute any valid slash command enclosed in quotes and pass the result of the command execution down the pipe. Numeric values and string literals for left and right operands supported. Available rules: gt => a > b, gte => a >= b, lt => a < b, lte => a <= b, eq => a == b, neq => a != b, not => !a, in (strings) => a includes b, nin (strings) => a not includes b, e.g. /if left=score right=10 rule=gte "/speak You win" triggers a /speak command if the value of "score" is greater or equals 10.', true, true); + registerSlashCommand('while', whileCallback, [], 'left=varname1 right=varname2 rule=comparison "(command)" – compare the value of the left operand "a" with the value of the right operand "b", and if the condition yields true, then execute any valid slash command enclosed in quotes. Numeric values and string literals for left and right operands supported. Available rules: gt => a > b, gte => a >= b, lt => a < b, lte => a <= b, eq => a == b, neq => a != b, not => !a, in (strings) => a includes b, nin (strings) => a not includes b, e.g. /setvar key=i 0 | /while left=i right=10 rule=let "/addvar key=i 1" adds 1 to the value of "i" until it reaches 10. Loops are limited to 100 iterations by default, pass guard=off to disable.', true, true); registerSlashCommand('flushvar', (_, value) => deleteLocalVariable(value), [], '(key) – delete a local variable, e.g. /flushvar score', true, true); registerSlashCommand('flushglobalvar', (_, value) => deleteGlobalVariable(value), [], '(key) – delete a global variable, e.g. /flushglobalvar score', true, true); } From c328d6f04a5924deb55664997ff594c0141bf3bc Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 26 Nov 2023 02:12:31 +0200 Subject: [PATCH 031/632] Add QR auto-exec on app startup --- public/script.js | 2 ++ .../quick-reply/contextMenuEditor.html | 4 ++++ .../scripts/extensions/quick-reply/index.js | 23 +++++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/public/script.js b/public/script.js index 718cc0e0a..0dbf0b39a 100644 --- a/public/script.js +++ b/public/script.js @@ -285,6 +285,7 @@ window["SillyTavern"] = {}; // Event source init export const event_types = { + APP_READY: 'app_ready', EXTRAS_CONNECTED: 'extras_connected', MESSAGE_SWIPED: 'message_swiped', MESSAGE_SENT: 'message_sent', @@ -744,6 +745,7 @@ async function firstLoadInit() { initCfg(); doDailyExtensionUpdatesCheck(); hideLoader(); + await eventSource.emit(event_types.APP_READY); } function cancelStatusCheck() { diff --git a/public/scripts/extensions/quick-reply/contextMenuEditor.html b/public/scripts/extensions/quick-reply/contextMenuEditor.html index bd52fbe81..41e30af9c 100644 --- a/public/scripts/extensions/quick-reply/contextMenuEditor.html +++ b/public/scripts/extensions/quick-reply/contextMenuEditor.html @@ -23,6 +23,10 @@ Invisible (auto-execute only) +
    diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js index a4120da1f..64a6ae1d4 100644 --- a/public/scripts/power-user.js +++ b/public/scripts/power-user.js @@ -66,6 +66,7 @@ export const ui_mode = { const avatar_styles = { ROUND: 0, RECTANGULAR: 1, + SQUARE: 2, } export const chat_styles = { @@ -856,6 +857,7 @@ function noShadows() { function applyAvatarStyle() { power_user.avatar_style = Number(localStorage.getItem(storage_keys.avatar_style) ?? avatar_styles.ROUND); $("body").toggleClass("big-avatars", power_user.avatar_style === avatar_styles.RECTANGULAR); + $("body").toggleClass("square-avatars", power_user.avatar_style === avatar_styles.SQUARE); $("#avatar_style").val(power_user.avatar_style).prop("selected", true); //$(`input[name="avatar_style"][value="${power_user.avatar_style}"]`).prop("checked", true); From 3bc91f10ec389a95855aeb9c25563baa3d22e638 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 26 Nov 2023 15:47:11 +0200 Subject: [PATCH 036/632] Fix command aliases --- public/scripts/slash-commands.js | 8 ++++---- public/scripts/variables.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index 6139122b0..e9396e6e8 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -170,14 +170,14 @@ parser.addCommand('genraw', generateRawCallback, [], '(l 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"] (search value) – performs a fuzzy match of the provided search using the provided list of value and passes the closest match to the next command through the pipe.', true, true); -parser.addCommand('pass', (_, arg) => arg, [], '(text) – passes the text 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); parser.addCommand('delay', delayCallback, ['wait', 'sleep'], '(milliseconds) – delays the next command in the pipe by the specified number of milliseconds.', true, true); parser.addCommand('input', inputCallback, ['prompt'], '(prompt) – shows a popup with the provided prompt and passes the user input to the next command through the pipe.', true, true); parser.addCommand('run', runCallback, ['call', 'exec'], '(QR label) – runs a Quick Reply with the specified name from the current preset.', true, true); parser.addCommand('messages', getMessagesCallback, ['message'], '(names=off/on [message index or range]) – returns the specified message or range of messages as a string.', true, true); parser.addCommand('setinput', setInputCallback, [], '(text) – sets the user input to the specified text and passes it to the next command through the pipe.', true, true); parser.addCommand('popup', popupCallback, [], '(text) – shows a blocking popup with the specified text.', true, true); -parser.addCommand('trimtokens', trimTokensCallback, [], '(direction=start/end limit=number [text]) – trims the start or end of text to the specified number of tokens.', true, true); +parser.addCommand('trimtokens', trimTokensCallback, [], 'limit=number (direction=start/end [text]) – trims the start or end of text to the specified number of tokens.', true, true); parser.addCommand('trimstart', trimStartCallback, [], '(text) – trims the text to the start of the first full sentence.', true, true); parser.addCommand('trimend', trimEndCallback, [], '(text) – trims the text to the end of the last full sentence.', true, true); registerVariableCommands(); @@ -258,7 +258,7 @@ function trimTokensCallback(arg, value) { async function popupCallback(_, value) { const safeValue = DOMPurify.sanitize(value || ''); await delay(1); - await callPopup(safeValue, 'text'); + await callPopup(safeValue, 'text', ''); await delay(1); return value; } @@ -338,7 +338,7 @@ async function inputCallback(_, prompt) { // Do not remove this delay, otherwise the prompt will not show up await delay(1); const safeValue = DOMPurify.sanitize(prompt || ''); - const result = await callPopup(safeValue, 'input'); + const result = await callPopup(safeValue, 'input', '', { okButton: 'Ok' }); await delay(1); return result || ''; } diff --git a/public/scripts/variables.js b/public/scripts/variables.js index 27eef66fc..b2b6ed0fd 100644 --- a/public/scripts/variables.js +++ b/public/scripts/variables.js @@ -341,7 +341,7 @@ function deleteGlobalVariable(name) { } export function registerVariableCommands() { - registerSlashCommand('listvar', listVariablesCallback, [''], ' – list registered chat variables', true, true); + registerSlashCommand('listvar', listVariablesCallback, [], ' – list registered chat variables', true, true); registerSlashCommand('setvar', (args, value) => setLocalVariable(args.key || args.name, value), [], 'key=varname (value) – set a local variable value and pass it down the pipe, e.g. /setvar key=color green', true, true); registerSlashCommand('getvar', (_, value) => getLocalVariable(value), [], '(key) – get a local variable value and pass it down the pipe, e.g. /getvar height', true, true); registerSlashCommand('addvar', (args, value) => addLocalVariable(args.key || args.name, value), [], 'key=varname (increment) – add a value to a local variable and pass the result down the pipe, e.g. /addvar score 10', true, true); From fb08552d4670f7d2aa95541ec327afe11e57979a Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 26 Nov 2023 16:37:05 +0200 Subject: [PATCH 037/632] Add instruct mode sequence macros --- public/script.js | 2 ++ public/scripts/instruct-mode.js | 25 +++++++++++++++++++++++++ public/scripts/templates/macros.html | 19 +++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/public/script.js b/public/script.js index 0dbf0b39a..354901156 100644 --- a/public/script.js +++ b/public/script.js @@ -186,6 +186,7 @@ import { getInstructStoppingSequences, autoSelectInstructPreset, formatInstructModeSystemPrompt, + replaceInstructMacros, } from "./scripts/instruct-mode.js"; import { applyLocale } from "./scripts/i18n.js"; import { getFriendlyTokenizerName, getTokenCount, getTokenizerModel, initTokenizers, saveTokenCache } from "./scripts/tokenizers.js"; @@ -2013,6 +2014,7 @@ function substituteParams(content, _name1, _name2, _original, _group, _replaceCh } content = diceRollReplace(content); content = randomReplace(content); + content = replaceInstructMacros(content); content = replaceVariableMacros(content); content = content.replace(/{{newline}}/gi, "\n"); content = content.replace(/{{input}}/gi, String($('#send_textarea').val())); diff --git a/public/scripts/instruct-mode.js b/public/scripts/instruct-mode.js index 60c18f11f..351f3a3b3 100644 --- a/public/scripts/instruct-mode.js +++ b/public/scripts/instruct-mode.js @@ -358,6 +358,31 @@ function selectMatchingContextTemplate(name) { } } +/** + * Replaces instruct mode macros in the given input string. + * @param {string} input Input string. + * @returns {string} String with macros replaced. + */ +export function replaceInstructMacros(input) { + if (!input) { + return ''; + } + + input = input.replace(/{{instructSystem}}/gi, power_user.instruct.enabled ? power_user.instruct.system_prompt : ''); + input = input.replace(/{{instructSystemPrefix}}/gi, power_user.instruct.enabled ? power_user.instruct.system_sequence_prefix : ''); + input = input.replace(/{{instructSystemSuffix}}/gi, power_user.instruct.enabled ? power_user.instruct.system_sequence_suffix : ''); + input = input.replace(/{{instructInput}}/gi, power_user.instruct.enabled ? power_user.instruct.input_sequence : ''); + input = input.replace(/{{instructOutput}}/gi, power_user.instruct.enabled ? power_user.instruct.output_sequence : ''); + input = input.replace(/{{instructFirstOutput}}/gi, power_user.instruct.enabled ? (power_user.instruct.first_output_sequence || power_user.instruct.output_sequence) : ''); + input = input.replace(/{{instructLastOutput}}/gi, power_user.instruct.enabled ? (power_user.instruct.last_output_sequence || power_user.instruct.output_sequence) : ''); + input = input.replace(/{{instructSeparator}}/gi, power_user.instruct.enabled ? power_user.instruct.separator_sequence : ''); + input = input.replace(/{{instructStop}}/gi, power_user.instruct.enabled ? power_user.instruct.stop_sequence : ''); + input = input.replace(/{{exampleSeparator}}/gi, power_user.context.example_separator); + input = input.replace(/{{chatStart}}/gi, power_user.context.chat_start); + + return input; +} + jQuery(() => { $('#instruct_set_default').on('click', function () { if (power_user.instruct.preset === power_user.default_instruct) { diff --git a/public/scripts/templates/macros.html b/public/scripts/templates/macros.html index 1b940e151..5b49fa9f0 100644 --- a/public/scripts/templates/macros.html +++ b/public/scripts/templates/macros.html @@ -32,6 +32,25 @@
  • {{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.
  • +
    + Instruct Mode and Context Template Macros: +
    +
    + (enabled in the Advanced Formatting settings) +
    +
      +
    • {{exampleSeparator}} – context template example dialogues separator
    • +
    • {{chatStart}} – context template chat start line
    • +
    • {{instructSystem}} – instruct system prompt
    • +
    • {{instructSystemPrefix}} – instruct system prompt prefix sequence
    • +
    • {{instructSystemSuffix}} – instruct system prompt suffix sequence
    • +
    • {{instructInput}} – instruct user input sequence
    • +
    • {{instructOutput}} – instruct assistant output sequence
    • +
    • {{instructFirstOutput}} – instruct assistant first output sequence
    • +
    • {{instructLastOutput}} – instruct assistant last output sequence
    • +
    • {{instructSeparator}} – instruct turn separator sequence
    • +
    • {{instructStop}} – instruct stop sequence
    • +
    Chat variables Macros:
    From 9c01a849cb32a5d2222b5df0194b93c157408e82 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 26 Nov 2023 17:05:55 +0200 Subject: [PATCH 038/632] Add buttons command --- public/scripts/slash-commands.js | 39 ++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index e9396e6e8..092b23cde 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -177,6 +177,7 @@ parser.addCommand('run', runCallback, ['call', 'exec'], '(names=off/on [message index or range]) – returns the specified message or range of messages as a string.', true, true); parser.addCommand('setinput', setInputCallback, [], '(text) – sets the user input to the specified text and passes it to the next command through the pipe.', true, true); parser.addCommand('popup', popupCallback, [], '(text) – shows a blocking popup with the specified text.', true, true); +parser.addCommand('buttons', buttonsCallback, [], 'labels=["a","b"] (text) – shows a blocking popup with the specified text and buttons. Returns the clicked button label into the pipe or empty string if canceled.', true, true); parser.addCommand('trimtokens', trimTokensCallback, [], 'limit=number (direction=start/end [text]) – trims the start or end of text to the specified number of tokens.', true, true); parser.addCommand('trimstart', trimStartCallback, [], '(text) – trims the text to the start of the first full sentence.', true, true); parser.addCommand('trimend', trimEndCallback, [], '(text) – trims the text to the end of the last full sentence.', true, true); @@ -255,6 +256,44 @@ function trimTokensCallback(arg, value) { } } +async function buttonsCallback(args, text) { + try { + const buttons = JSON.parse(resolveVariable(args?.labels)); + + if (!Array.isArray(buttons) || !buttons.length) { + console.warn('WARN: Invalid labels provided for /buttons command'); + return ''; + } + + return new Promise(async (resolve) => { + const safeValue = DOMPurify.sanitize(text || ''); + + const buttonContainer = document.createElement('div'); + buttonContainer.classList.add('flex-container', 'flexFlowColumn', 'wide100p', 'm-t-1'); + + for (const button of buttons) { + const buttonElement = document.createElement('div'); + buttonElement.classList.add('menu_button', 'wide100p'); + buttonElement.addEventListener('click', () => { + resolve(button); + $('#dialogue_popup_ok').trigger('click'); + }); + buttonElement.innerText = button; + buttonContainer.appendChild(buttonElement); + } + + const popupContainer = document.createElement('div'); + popupContainer.innerHTML = safeValue; + popupContainer.appendChild(buttonContainer); + callPopup(popupContainer, 'text', '', { okButton: 'Cancel' }) + .then(() => resolve('')) + .catch(() => resolve('')); + }) + } catch { + return ''; + } +} + async function popupCallback(_, value) { const safeValue = DOMPurify.sanitize(value || ''); await delay(1); From aaeaa643e3770b65ebcb81688473ec3696a72698 Mon Sep 17 00:00:00 2001 From: deffcolony <61471128+deffcolony@users.noreply.github.com> Date: Sun, 26 Nov 2023 16:55:49 +0100 Subject: [PATCH 039/632] resolution presets for image generation extension (#1394) + New drawer with resolution presets at image generation extension --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com> --- .../extensions/stable-diffusion/index.js | 32 +++++++++++++++++++ .../extensions/stable-diffusion/settings.html | 22 +++++++++++++ 2 files changed, 54 insertions(+) diff --git a/public/scripts/extensions/stable-diffusion/index.js b/public/scripts/extensions/stable-diffusion/index.js index ca1a872ca..5b91796d0 100644 --- a/public/scripts/extensions/stable-diffusion/index.js +++ b/public/scripts/extensions/stable-diffusion/index.js @@ -636,6 +636,37 @@ function onSamplerChange() { saveSettingsDebounced(); } +const resolutionOptions = { + sd_res_512x512: { width: 512, height: 512 }, + sd_res_600x600: { width: 600, height: 600 }, + sd_res_512x768: { width: 512, height: 768 }, + sd_res_768x512: { width: 768, height: 512 }, + sd_res_960x540: { width: 960, height: 540 }, + sd_res_540x960: { width: 540, height: 960 }, + sd_res_1920x1088: { width: 1920, height: 1088 }, + sd_res_1088x1920: { width: 1088, height: 1920 }, + sd_res_1280x720: { width: 1280, height: 720 }, + sd_res_720x1280: { width: 720, height: 1280 }, + sd_res_1024x1024: { width: 1024, height: 1024 }, + sd_res_1152x896: { width: 1152, height: 896 }, + sd_res_896x1152: { width: 896, height: 1152 }, + sd_res_1216x832: { width: 1216, height: 832 }, + sd_res_832x1216: { width: 832, height: 1216 }, + sd_res_1344x768: { width: 1344, height: 768 }, + sd_res_768x1344: { width: 768, height: 1344 }, + sd_res_1536x640: { width: 1536, height: 640 }, + sd_res_640x1536: { width: 640, height: 1536 }, +}; + +function onResolutionChange() { + const selectedOption = $("#sd_resolution").val(); + const selectedResolution = resolutionOptions[selectedOption]; + $("#sd_width_value").text(selectedResolution.width); + $("#sd_height_value").text(selectedResolution.height); + $("#sd_height").val(selectedResolution.height); + $("#sd_width").val(selectedResolution.width); +} + function onSchedulerChange() { extension_settings.sd.scheduler = $('#sd_scheduler').find(':selected').val(); saveSettingsDebounced(); @@ -2391,6 +2422,7 @@ jQuery(async () => { $('#sd_model').on('change', onModelChange); $('#sd_vae').on('change', onVaeChange); $('#sd_sampler').on('change', onSamplerChange); + $('#sd_resolution').on('change', onResolutionChange); $('#sd_scheduler').on('change', onSchedulerChange); $('#sd_prompt_prefix').on('input', onPromptPrefixInput); $('#sd_negative_prompt').on('input', onNegativePromptInput); diff --git a/public/scripts/extensions/stable-diffusion/settings.html b/public/scripts/extensions/stable-diffusion/settings.html index d3d7e6e20..7f508f1a1 100644 --- a/public/scripts/extensions/stable-diffusion/settings.html +++ b/public/scripts/extensions/stable-diffusion/settings.html @@ -155,6 +155,28 @@ + +
    From e587f208be747fa573176c19880ea56a0f0664a9 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 26 Nov 2023 18:19:37 +0200 Subject: [PATCH 040/632] Add resolution match on load --- .../extensions/stable-diffusion/index.js | 76 +++++++++++++------ .../extensions/stable-diffusion/settings.html | 22 +----- 2 files changed, 54 insertions(+), 44 deletions(-) diff --git a/public/scripts/extensions/stable-diffusion/index.js b/public/scripts/extensions/stable-diffusion/index.js index 5b91796d0..b310d98d3 100644 --- a/public/scripts/extensions/stable-diffusion/index.js +++ b/public/scripts/extensions/stable-diffusion/index.js @@ -391,6 +391,25 @@ async function loadSettings() { $('#sd_style').append(option); } + // Find a closest resolution option match for the current width and height + let resolutionId = null, minAspectDiff = Infinity, minResolutionDiff = Infinity; + for (const [id, resolution] of Object.entries(resolutionOptions)) { + const aspectDiff = Math.abs((resolution.width / resolution.height) - (extension_settings.sd.width / extension_settings.sd.height)); + const resolutionDiff = Math.abs(resolution.width * resolution.height - extension_settings.sd.width * extension_settings.sd.height); + + if (resolutionDiff < minResolutionDiff || (resolutionDiff === minResolutionDiff && aspectDiff < minAspectDiff)) { + resolutionId = id; + minAspectDiff = aspectDiff; + minResolutionDiff = resolutionDiff; + } + + if (resolutionDiff === 0 && aspectDiff === 0) { + break; + } + } + + $('#sd_resolution').val(resolutionId); + toggleSourceControls(); addPromptTemplates(); @@ -637,34 +656,38 @@ function onSamplerChange() { } const resolutionOptions = { - sd_res_512x512: { width: 512, height: 512 }, - sd_res_600x600: { width: 600, height: 600 }, - sd_res_512x768: { width: 512, height: 768 }, - sd_res_768x512: { width: 768, height: 512 }, - sd_res_960x540: { width: 960, height: 540 }, - sd_res_540x960: { width: 540, height: 960 }, - sd_res_1920x1088: { width: 1920, height: 1088 }, - sd_res_1088x1920: { width: 1088, height: 1920 }, - sd_res_1280x720: { width: 1280, height: 720 }, - sd_res_720x1280: { width: 720, height: 1280 }, - sd_res_1024x1024: { width: 1024, height: 1024 }, - sd_res_1152x896: { width: 1152, height: 896 }, - sd_res_896x1152: { width: 896, height: 1152 }, - sd_res_1216x832: { width: 1216, height: 832 }, - sd_res_832x1216: { width: 832, height: 1216 }, - sd_res_1344x768: { width: 1344, height: 768 }, - sd_res_768x1344: { width: 768, height: 1344 }, - sd_res_1536x640: { width: 1536, height: 640 }, - sd_res_640x1536: { width: 640, height: 1536 }, + sd_res_512x512: { width: 512, height: 512, name: '512x512 (1:1, icons, profile pictures)' }, + sd_res_600x600: { width: 600, height: 600, name: '600x600 (1:1, icons, profile pictures)' }, + sd_res_512x768: { width: 512, height: 768, name: '512x768 (2:3, vertical character card)' }, + sd_res_768x512: { width: 768, height: 512, name: '768x512 (3:2, horizontal 35-mm movie film)' }, + sd_res_960x540: { width: 960, height: 540, name: '960x540 (16:9, horizontal wallpaper)' }, + sd_res_540x960: { width: 540, height: 960, name: '540x960 (9:16, vertical wallpaper)' }, + sd_res_1920x1088: { width: 1920, height: 1088, name: '1920x1088 (16:9, 1080p, horizontal wallpaper)' }, + sd_res_1088x1920: { width: 1088, height: 1920, name: '1088x1920 (9:16, 1080p, vertical wallpaper)' }, + sd_res_1280x720: { width: 1280, height: 720, name: '1280x720 (16:9, 720p, horizontal wallpaper)' }, + sd_res_720x1280: { width: 720, height: 1280, name: '720x1280 (9:16, 720p, vertical wallpaper)' }, + sd_res_1024x1024: { width: 1024, height: 1024, name: '1024x1024 (1:1, SDXL)' }, + sd_res_1152x896: { width: 1152, height: 896, name: '1152x896 (9:7, SDXL)' }, + sd_res_896x1152: { width: 896, height: 1152, name: '896x1152 (7:9, SDXL)' }, + sd_res_1216x832: { width: 1216, height: 832, name: '1216x832 (19:13, SDXL)' }, + sd_res_832x1216: { width: 832, height: 1216, name: '832x1216 (13:19, SDXL)' }, + sd_res_1344x768: { width: 1344, height: 768, name: '1344x768 (4:3, SDXL)' }, + sd_res_768x1344: { width: 768, height: 1344, name: '768x1344 (3:4, SDXL)' }, + sd_res_1536x640: { width: 1536, height: 640, name: '1536x640 (24:10, SDXL)' }, + sd_res_640x1536: { width: 640, height: 1536, name: '640x1536 (10:24, SDXL)' }, }; function onResolutionChange() { const selectedOption = $("#sd_resolution").val(); const selectedResolution = resolutionOptions[selectedOption]; - $("#sd_width_value").text(selectedResolution.width); - $("#sd_height_value").text(selectedResolution.height); - $("#sd_height").val(selectedResolution.height); - $("#sd_width").val(selectedResolution.width); + + if (!selectedResolution) { + console.warn(`Could not find resolution option for ${selectedOption}`); + return; + } + + $("#sd_height").val(selectedResolution.height).trigger('input'); + $("#sd_width").val(selectedResolution.width).trigger('input'); } function onSchedulerChange() { @@ -2469,6 +2492,13 @@ jQuery(async () => { initScrollHeight($("#sd_character_prompt")); }) + for (const [key, value] of Object.entries(resolutionOptions)) { + const option = document.createElement('option'); + option.value = key; + option.text = value.name; + $('#sd_resolution').append(option); + } + eventSource.on(event_types.EXTRAS_CONNECTED, async () => { await loadSettingOptions(); }); diff --git a/public/scripts/extensions/stable-diffusion/settings.html b/public/scripts/extensions/stable-diffusion/settings.html index 7f508f1a1..7c84c28ae 100644 --- a/public/scripts/extensions/stable-diffusion/settings.html +++ b/public/scripts/extensions/stable-diffusion/settings.html @@ -156,27 +156,7 @@ - +
    From f04c277f03e8d6ea9c8a05c5bd740f7aaa3e9e31 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 26 Nov 2023 19:47:23 +0200 Subject: [PATCH 041/632] Add optional {{mesExamples}} to story string --- public/script.js | 1 + 1 file changed, 1 insertion(+) diff --git a/public/script.js b/public/script.js index 354901156..9f5985858 100644 --- a/public/script.js +++ b/public/script.js @@ -3146,6 +3146,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject, wiAfter: worldInfoAfter, loreBefore: worldInfoBefore, loreAfter: worldInfoAfter, + mesExamples: mesExamplesArray.join(''), }; const storyString = renderStoryString(storyStringParams); From 3eeb137416f59632448872ca1de90e8fe73c4a32 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 26 Nov 2023 19:56:19 +0200 Subject: [PATCH 042/632] Fix persona switch input trigger --- public/scripts/personas.js | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/public/scripts/personas.js b/public/scripts/personas.js index 57cec688f..2e0669077 100644 --- a/public/scripts/personas.js +++ b/public/scripts/personas.js @@ -1,4 +1,21 @@ -import { callPopup, characters, chat_metadata, default_avatar, eventSource, event_types, getRequestHeaders, getThumbnailUrl, getUserAvatars, name1, saveMetadata, saveSettingsDebounced, setUserName, this_chid, user_avatar } from "../script.js"; +import { + callPopup, + characters, + chat_metadata, + default_avatar, + eventSource, + event_types, + getRequestHeaders, + getThumbnailUrl, + getUserAvatars, + name1, + saveMetadata, + saveSettingsDebounced, + setUserName, + this_chid, + user_avatar, +} from "../script.js"; +import { getContext } from "./extensions.js"; import { persona_description_positions, power_user } from "./power-user.js"; import { getTokenCount } from "./tokenizers.js"; import { debounce, delay, download, parseJsonFile } from "./utils.js"; @@ -254,7 +271,12 @@ export function selectCurrentPersona() { } setPersonaDescription(); - $("#firstmessage_textarea").trigger('input') + + // force firstMes {{user}} update on persona switch + const context = getContext(); + if (context.characterId >= 0 && !context.groupId && context.chat.length === 1) { + $("#firstmessage_textarea").trigger('input') + } } } From fd0edd67a6649b9477f00179f1615b3d8e97ddf9 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Mon, 27 Nov 2023 02:18:36 +0200 Subject: [PATCH 043/632] Fix recursive QR auto-execution --- .../scripts/extensions/quick-reply/index.js | 57 +++++++++---- public/scripts/power-user.js | 12 +-- public/scripts/slash-commands.js | 79 +++++++++++-------- 3 files changed, 93 insertions(+), 55 deletions(-) diff --git a/public/scripts/extensions/quick-reply/index.js b/public/scripts/extensions/quick-reply/index.js index ee1ec828b..968460210 100644 --- a/public/scripts/extensions/quick-reply/index.js +++ b/public/scripts/extensions/quick-reply/index.js @@ -600,6 +600,10 @@ function saveQROrder() { }); } +let onMessageSentExecuting = false; +let onMessageReceivedExecuting = false; +let onChatChangedExecuting = false; + /** * Executes quick replies on message received. * @param {number} index New message index @@ -608,14 +612,21 @@ function saveQROrder() { async function onMessageReceived(index) { if (!extension_settings.quickReply.quickReplyEnabled) return; - for (let i = 0; i < extension_settings.quickReply.numberOfSlots; i++) { - const qr = extension_settings.quickReply.quickReplySlots[i]; - if (qr?.autoExecute_botMessage) { - const message = getContext().chat[index]; - if (message?.mes && message?.mes !== '...') { - await sendQuickReply(i); + if (onMessageReceivedExecuting) return; + + try { + onMessageReceivedExecuting = true; + for (let i = 0; i < extension_settings.quickReply.numberOfSlots; i++) { + const qr = extension_settings.quickReply.quickReplySlots[i]; + if (qr?.autoExecute_botMessage) { + const message = getContext().chat[index]; + if (message?.mes && message?.mes !== '...') { + await sendQuickReply(i); + } } } + } finally { + onMessageReceivedExecuting = false; } } @@ -627,14 +638,21 @@ async function onMessageReceived(index) { async function onMessageSent(index) { if (!extension_settings.quickReply.quickReplyEnabled) return; - for (let i = 0; i < extension_settings.quickReply.numberOfSlots; i++) { - const qr = extension_settings.quickReply.quickReplySlots[i]; - if (qr?.autoExecute_userMessage) { - const message = getContext().chat[index]; - if (message?.mes && message?.mes !== '...') { - await sendQuickReply(i); + if (onMessageSentExecuting) return; + + try { + onMessageSentExecuting = true; + for (let i = 0; i < extension_settings.quickReply.numberOfSlots; i++) { + const qr = extension_settings.quickReply.quickReplySlots[i]; + if (qr?.autoExecute_userMessage) { + const message = getContext().chat[index]; + if (message?.mes && message?.mes !== '...') { + await sendQuickReply(i); + } } } + } finally { + onMessageSentExecuting = false; } } @@ -646,11 +664,18 @@ async function onMessageSent(index) { async function onChatChanged(chatId) { if (!extension_settings.quickReply.quickReplyEnabled) return; - for (let i = 0; i < extension_settings.quickReply.numberOfSlots; i++) { - const qr = extension_settings.quickReply.quickReplySlots[i]; - if (qr?.autoExecute_chatLoad && chatId) { - await sendQuickReply(i); + if (onChatChangedExecuting) return; + + try { + onChatChangedExecuting = true; + for (let i = 0; i < extension_settings.quickReply.numberOfSlots; i++) { + const qr = extension_settings.quickReply.quickReplySlots[i]; + if (qr?.autoExecute_chatLoad && chatId) { + await sendQuickReply(i); + } } + } finally { + onChatChangedExecuting = false; } } diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js index 64a6ae1d4..83ca60715 100644 --- a/public/scripts/power-user.js +++ b/public/scripts/power-user.js @@ -1981,13 +1981,13 @@ function doNewChat() { }, 1); } -function doRandomChat() { +async function doRandomChat() { resetSelectedGroup(); - setCharacterId(Math.floor(Math.random() * characters.length).toString()); - setTimeout(() => { - reloadCurrentChat(); - }, 1); - + const characterId = Math.floor(Math.random() * characters.length).toString(); + setCharacterId(characterId); + await delay(1); + await reloadCurrentChat(); + return characters[characterId]?.name; } /** diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index 092b23cde..3b7ce858c 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -686,14 +686,14 @@ async function hideMessageCallback(_, arg) { async function unhideMessageCallback(_, arg) { if (!arg) { console.warn('WARN: No argument provided for /unhide command'); - return; + return ''; } const range = stringToRange(arg, 0, chat.length - 1); if (!range) { console.warn(`WARN: Invalid range provided for /unhide command: ${arg}`); - return; + return ''; } for (let messageId = range.start; messageId <= range.end; messageId++) { @@ -701,128 +701,136 @@ async function unhideMessageCallback(_, arg) { if (!messageBlock.length) { console.warn(`WARN: No message found with ID ${messageId}`); - return; + return ''; } await unhideChatMessage(messageId, messageBlock); } + + return ''; } async function disableGroupMemberCallback(_, arg) { if (!selected_group) { toastr.warning("Cannot run /disable command outside of a group chat."); - return; + return ''; } const chid = findGroupMemberId(arg); if (chid === undefined) { console.warn(`WARN: No group member found for argument ${arg}`); - return; + return ''; } $(`.group_member[chid="${chid}"] [data-action="disable"]`).trigger('click'); + return ''; } async function enableGroupMemberCallback(_, arg) { if (!selected_group) { toastr.warning("Cannot run /enable command outside of a group chat."); - return; + return ''; } const chid = findGroupMemberId(arg); if (chid === undefined) { console.warn(`WARN: No group member found for argument ${arg}`); - return; + return ''; } $(`.group_member[chid="${chid}"] [data-action="enable"]`).trigger('click'); + return ''; } async function moveGroupMemberUpCallback(_, arg) { if (!selected_group) { toastr.warning("Cannot run /memberup command outside of a group chat."); - return; + return ''; } const chid = findGroupMemberId(arg); if (chid === undefined) { console.warn(`WARN: No group member found for argument ${arg}`); - return; + return ''; } $(`.group_member[chid="${chid}"] [data-action="up"]`).trigger('click'); + return ''; } async function moveGroupMemberDownCallback(_, arg) { if (!selected_group) { toastr.warning("Cannot run /memberdown command outside of a group chat."); - return; + return ''; } const chid = findGroupMemberId(arg); if (chid === undefined) { console.warn(`WARN: No group member found for argument ${arg}`); - return; + return ''; } $(`.group_member[chid="${chid}"] [data-action="down"]`).trigger('click'); + return ''; } async function peekCallback(_, arg) { if (!selected_group) { toastr.warning("Cannot run /peek command outside of a group chat."); - return; + return ''; } if (is_group_generating) { toastr.warning("Cannot run /peek command while the group reply is generating."); - return; + return ''; } const chid = findGroupMemberId(arg); if (chid === undefined) { console.warn(`WARN: No group member found for argument ${arg}`); - return; + return ''; } $(`.group_member[chid="${chid}"] [data-action="view"]`).trigger('click'); + return ''; } async function removeGroupMemberCallback(_, arg) { if (!selected_group) { toastr.warning("Cannot run /memberremove command outside of a group chat."); - return; + return ''; } if (is_group_generating) { toastr.warning("Cannot run /memberremove command while the group reply is generating."); - return; + return ''; } const chid = findGroupMemberId(arg); if (chid === undefined) { console.warn(`WARN: No group member found for argument ${arg}`); - return; + return ''; } $(`.group_member[chid="${chid}"] [data-action="remove"]`).trigger('click'); + return ''; } async function addGroupMemberCallback(_, arg) { if (!selected_group) { toastr.warning("Cannot run /memberadd command outside of a group chat."); - return; + return ''; } if (!arg) { console.warn('WARN: No argument provided for /memberadd command'); - return; + return ''; } arg = arg.trim(); @@ -830,7 +838,7 @@ async function addGroupMemberCallback(_, arg) { if (chid === -1) { console.warn(`WARN: No character found for argument ${arg}`); - return; + return ''; } const character = characters[chid]; @@ -838,14 +846,14 @@ async function addGroupMemberCallback(_, arg) { if (!group || !Array.isArray(group.members)) { console.warn(`WARN: No group found for ID ${selected_group}`); - return; + return ''; } const avatar = character.avatar; if (group.members.includes(avatar)) { toastr.warning(`${character.name} is already a member of this group.`); - return; + return ''; } group.members.push(avatar); @@ -853,17 +861,18 @@ async function addGroupMemberCallback(_, arg) { // Trigger to reload group UI $('#rm_button_selected_ch').trigger('click'); + return character.name; } async function triggerGroupMessageCallback(_, arg) { if (!selected_group) { toastr.warning("Cannot run /trigger command outside of a group chat."); - return; + return ''; } if (is_group_generating) { toastr.warning("Cannot run trigger command while the group reply is generating."); - return; + return ''; } // Prevent generate recursion @@ -873,10 +882,11 @@ async function triggerGroupMessageCallback(_, arg) { if (chid === undefined) { console.warn(`WARN: No group member found for argument ${arg}`); - return; + return ''; } Generate('normal', { force_chid: chid }); + return ''; } async function sendUserMessageCallback(_, text) { @@ -888,6 +898,7 @@ async function sendUserMessageCallback(_, text) { text = text.trim(); const bias = extractMessageBias(text); await sendMessageAsUser(text, bias); + return ''; } async function deleteMessagesByNameCallback(_, name) { @@ -922,6 +933,7 @@ async function deleteMessagesByNameCallback(_, name) { await reloadCurrentChat(); toastr.info(`Deleted ${messagesToDelete.length} messages from ${name}`); + return ''; } function findCharacterIndex(name) { @@ -941,7 +953,7 @@ function findCharacterIndex(name) { return -1; } -function goToCharacterCallback(_, name) { +async function goToCharacterCallback(_, name) { if (!name) { console.warn('WARN: No character name provided for /go command'); return; @@ -951,18 +963,19 @@ function goToCharacterCallback(_, name) { const characterIndex = findCharacterIndex(name); if (characterIndex !== -1) { - openChat(new String(characterIndex)); + await openChat(new String(characterIndex)); + return characters[characterIndex]?.name; } else { console.warn(`No matches found for name "${name}"`); + return ''; } } -function openChat(id) { +async function openChat(id) { resetSelectedGroup(); setCharacterId(id); - setTimeout(() => { - reloadCurrentChat(); - }, 1); + await delay(1); + await reloadCurrentChat(); } function continueChatCallback() { @@ -1096,9 +1109,9 @@ export async function sendMessageAs(namedArgs, text) { }; chat.push(message); - await eventSource.emit(event_types.MESSAGE_SENT, (chat.length - 1)); + await eventSource.emit(event_types.MESSAGE_RECEIVED, (chat.length - 1)); addOneMessage(message); - await eventSource.emit(event_types.USER_MESSAGE_RENDERED, (chat.length - 1)); + await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, (chat.length - 1)); await saveChatConditional(); } From 313a6c984027d80ac5e44657adc1699d63c1f6ac Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Mon, 27 Nov 2023 02:32:41 +0200 Subject: [PATCH 044/632] Fix unclickable icons in burger menus --- public/style.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/style.css b/public/style.css index fe905d227..09c3d306b 100644 --- a/public/style.css +++ b/public/style.css @@ -688,6 +688,7 @@ hr { display: flex; align-items: center; justify-content: center; + pointer-events: none; } .options-content hr { @@ -3771,4 +3772,4 @@ a { height: 100vh; z-index: 9999; } -} \ No newline at end of file +} From 34d8588691a4895723b869f2fa9a27d127aa0cb0 Mon Sep 17 00:00:00 2001 From: RossAscends <124905043+RossAscends@users.noreply.github.com> Date: Mon, 27 Nov 2023 09:48:05 +0900 Subject: [PATCH 045/632] QR popout --- .../scripts/extensions/quick-reply/index.js | 71 ++++++++++++++++++- .../scripts/extensions/quick-reply/style.css | 10 ++- public/style.css | 2 +- 3 files changed, 78 insertions(+), 5 deletions(-) diff --git a/public/scripts/extensions/quick-reply/index.js b/public/scripts/extensions/quick-reply/index.js index ee1ec828b..22b750642 100644 --- a/public/scripts/extensions/quick-reply/index.js +++ b/public/scripts/extensions/quick-reply/index.js @@ -5,6 +5,8 @@ import { executeSlashCommands, registerSlashCommand } from "../../slash-commands import { ContextMenu } from "./src/ContextMenu.js"; import { MenuItem } from "./src/MenuItem.js"; import { MenuHeader } from "./src/MenuHeader.js"; +import { loadMovingUIState } from "../../power-user.js"; +import { dragElement } from "../../RossAscends-mods.js"; export { MODULE_NAME }; @@ -322,9 +324,67 @@ function buildContextMenu(qr, chainMes = null, hierarchy = [], labelHierarchy = }); return tree; } + +async function doQuickReplyBarPopout() { + //shared elements + const newQuickRepliesDiv = `
    ` + const popoutButtonClone = $("#quickReplyPopoutButton") + + if ($("#quickReplyBarPopout").length === 0) { + console.debug('did not see popout yet, creating') + const template = $('#zoomed_avatar_template').html(); + const controlBarHtml = `
    +
    +
    +
    ` + const newElement = $(template); + let quickRepliesClone = $('#quickReplies').html() + newElement.attr('id', 'quickReplyBarPopout') + .removeClass('zoomed_avatar') + .addClass('draggable scrollY') + .empty() + .append(controlBarHtml) + .append(newQuickRepliesDiv) + //empty original bar + $("#quickReplyBar").empty() + //add clone in popout + $('body').append(newElement); + $("#quickReplies").append(quickRepliesClone).css('margin-top', '1em') + $('.quickReplyButton').on('click', function () { + let index = $(this).data('index'); + sendQuickReply(index); + }); + + loadMovingUIState(); + $("#quickReplyBarPopout").fadeIn(250) + dragElement(newElement) + + $('#quickReplyBarPopoutClose').off('click').on('click', function () { + console.debug('saw existing popout, removing') + let quickRepliesClone = $('#quickReplies').html() + $("#quickReplyBar").append(newQuickRepliesDiv) + $("#quickReplies").prepend(quickRepliesClone) + $("#quickReplyBar").append(popoutButtonClone).fadeIn(250) + $("#quickReplyBarPopout").fadeOut(250, () => { $("#quickReplyBarPopout").remove() }); + $('.quickReplyButton').on('click', function () { + let index = $(this).data('index'); + sendQuickReply(index); + }); + $("#quickReplyPopoutButton").off('click').on('click', doQuickReplyBarPopout) + }) + + } +} + function addQuickReplyBar() { - $('#quickReplyBar').remove(); let quickReplyButtonHtml = ''; + var targetContainer; + if ($("#quickReplyBarPopout").length !== 0) { + targetContainer = 'popout' + } else { + targetContainer = 'bar' + $("#quickReplyBar").remove(); + } for (let i = 0; i < extension_settings.quickReply.numberOfSlots; i++) { const qr = extension_settings.quickReply.quickReplySlots[i]; @@ -343,15 +403,22 @@ function addQuickReplyBar() {
    ${quickReplyButtonHtml}
    +
    `; + console.log(targetContainer) + if (targetContainer === 'bar') { + $('#send_form').prepend(quickReplyBarFullHtml); + } else { + $("#quickReplies").empty().append(quickReplyButtonHtml) + } - $('#send_form').prepend(quickReplyBarFullHtml); $('.quickReplyButton').on('click', function () { let index = $(this).data('index'); sendQuickReply(index); }); + $("#quickReplyPopoutButton").off('click').on('click', doQuickReplyBarPopout) $('.quickReplyButton > .ctx-expander').on('click', function (evt) { evt.stopPropagation(); let index = $(this.closest('.quickReplyButton')).data('index'); diff --git a/public/scripts/extensions/quick-reply/style.css b/public/scripts/extensions/quick-reply/style.css index a20e53338..1010c8332 100644 --- a/public/scripts/extensions/quick-reply/style.css +++ b/public/scripts/extensions/quick-reply/style.css @@ -27,6 +27,12 @@ width: 100%; } +#quickReplyPopoutButton { + position: absolute; + right: 5px; + top: 0px; +} + #quickReplies div { color: var(--SmartThemeBodyColor); background-color: var(--black50a); @@ -34,7 +40,7 @@ border-radius: 10px; padding: 3px 5px; margin: 3px 0; - width: min-content; + /* width: min-content; */ cursor: pointer; transition: 0.3s; display: flex; @@ -104,4 +110,4 @@ .list-group .list-group-item.ctx-item { padding: 1em; } -} +} \ No newline at end of file diff --git a/public/style.css b/public/style.css index fe905d227..7b0b48c53 100644 --- a/public/style.css +++ b/public/style.css @@ -135,7 +135,7 @@ body { } .scrollY { - overflow-y: auto; + overflow-y: auto !important; } ::-webkit-scrollbar-thumb { From 37610062a736bbd1a494440f629cd5606e311fdf Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Mon, 27 Nov 2023 02:56:45 +0200 Subject: [PATCH 046/632] 1px less Safari cope --- public/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/style.css b/public/style.css index 798b7fc86..3c86f5399 100644 --- a/public/style.css +++ b/public/style.css @@ -548,7 +548,7 @@ hr { /* special case for desktop Safari to allow #sheld resizing */ @media only screen and (min-width: 1024px) and (-webkit-min-device-pixel-ratio: 2) and (pointer: fine) { #form_sheld { - margin-bottom: 5px; + margin-bottom: 4px; } } From c742251c5afffa3970780d4b4882864374f13875 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Mon, 27 Nov 2023 03:22:35 +0200 Subject: [PATCH 047/632] Mobile sprites fixes: hide non-VN sprite, fix group VN position, fix live2d conflicts --- public/css/mobile-styles.css | 12 ++++++++++-- public/scripts/extensions/expressions/style.css | 5 ----- public/style.css | 4 ++-- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/public/css/mobile-styles.css b/public/css/mobile-styles.css index 5b885aba6..a15fb4732 100644 --- a/public/css/mobile-styles.css +++ b/public/css/mobile-styles.css @@ -256,6 +256,14 @@ bottom: 0 !important; } + body:not(.waifuMode) #expression-wrapper { + visibility: hidden; + } + + #visual-novel-wrapper { + position: unset !important; + } + body.waifuMode .expression-holder { /*display: inline;*/ @@ -267,7 +275,7 @@ left: 0; right: 0; filter: drop-shadow(2px 2px 2px #51515199); - z-index: 1; + z-index: 1 !important; } body.waifuMode img.expression { @@ -457,4 +465,4 @@ #horde_model { height: unset; } -} \ No newline at end of file +} diff --git a/public/scripts/extensions/expressions/style.css b/public/scripts/extensions/expressions/style.css index a110cacdb..9d59124b0 100644 --- a/public/scripts/extensions/expressions/style.css +++ b/public/scripts/extensions/expressions/style.css @@ -190,8 +190,3 @@ img.expression.default { flex-direction: row; } -@media screen and (max-width:1200px) { - div.expression { - display: none; - } -} diff --git a/public/style.css b/public/style.css index 3c86f5399..174121ced 100644 --- a/public/style.css +++ b/public/style.css @@ -415,12 +415,12 @@ hr { #bg1 { background-image: url('backgrounds/tavern day.jpg'); - z-index: -2; + z-index: -3; } #bg_custom { background-image: none; - z-index: -1; + z-index: -2; } /*TOPPER margin*/ From 50ebd1cf66f0bbac479e351ea0203ebc6d8431f9 Mon Sep 17 00:00:00 2001 From: dllt98 <135049225+dllt98@users.noreply.github.com> Date: Mon, 27 Nov 2023 08:29:19 +0700 Subject: [PATCH 048/632] Add support for importing character from JanitorAI (#1401) * Add Janny support * Add description * Remove unofficial JanAI mirror --- public/script.js | 1 + src/content-manager.js | 82 +++++++++++++++++++++++++++++++++++------- 2 files changed, 70 insertions(+), 13 deletions(-) diff --git a/public/script.js b/public/script.js index 9f5985858..6a7bd1df8 100644 --- a/public/script.js +++ b/public/script.js @@ -9367,6 +9367,7 @@ jQuery(async function () {
    • Chub characters (direct link or id)
      Example: Anonymous/example-character
    • Chub lorebooks (direct link or id)
      Example: lorebooks/bartleby/example-lorebook
    • +
    • JanitorAI character (direct link or id)
      Example: https://janitorai.com/characters/ddd1498a-a370-4136-b138-a8cd9461fdfe_character-aqua-the-useless-goddess
    • More coming soon...
      • ` const input = await callPopup(html, 'input'); diff --git a/src/content-manager.js b/src/content-manager.js index 9dfb70dcc..ba0f6cfaf 100644 --- a/src/content-manager.js +++ b/src/content-manager.js @@ -176,6 +176,49 @@ function parseChubUrl(str) { return null; } +// Warning: Some characters might not exist in JannyAI.me +async function downloadJannyCharacter(uuid) { + // This endpoint is being guarded behind Bot Fight Mode of Cloudflare + // So hosted ST on Azure/AWS/GCP/Collab might get blocked by IP + // Should work normally on self-host PC/Android + const result = await fetch('https://api.janitorai.me/api/v1/download', { + method: 'POST', + headers: { 'Content-Type': 'application/json'}, + body: JSON.stringify({ + "characterId": uuid, + }) + }); + + if (result.ok) { + const downloadResult = await result.json(); + if (downloadResult.status === 'ok') { + const imageResult = await fetch(downloadResult.downloadUrl) + const buffer = await imageResult.buffer(); + const fileName = `${sanitize(uuid)}.png`; + const fileType = result.headers.get('content-type'); + + return { buffer, fileName, fileType }; + } + } + + console.log('Janny returned error', result.statusText, await result.text()); + throw new Error('Failed to download character'); +} + +/** +* @param {String} url +* @returns {String | null } UUID of the character +*/ +function parseJannyUrl(url) { + // Extract UUID from URL + const uuidRegex = /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/; + const matches = url.match(uuidRegex); + + // Check if UUID is found + const uuid = matches ? matches[0] : null; + return uuid +} + /** * Registers endpoints for custom content management * @param {import('express').Express} app Express app @@ -190,24 +233,37 @@ function registerEndpoints(app, jsonParser) { try { const url = request.body.url; let result; + let type; - const chubParsed = parseChubUrl(url); + const isJannnyContent = url.includes('janitorai') + if (isJannnyContent) { + const uuid = parseJannyUrl(url); + if (!uuid) { + return response.sendStatus(404); + } - if (chubParsed?.type === 'character') { - console.log('Downloading chub character:', chubParsed.id); - result = await downloadChubCharacter(chubParsed.id); - } - else if (chubParsed?.type === 'lorebook') { - console.log('Downloading chub lorebook:', chubParsed.id); - result = await downloadChubLorebook(chubParsed.id); - } - else { - return response.sendStatus(404); - } + type = 'character'; + result = await downloadJannyCharacter(uuid); + } else { + const chubParsed = parseChubUrl(url); + type = chubParsed?.type; + if (chubParsed?.type === 'character') { + console.log('Downloading chub character:', chubParsed.id); + result = await downloadChubCharacter(chubParsed.id); + } + else if (chubParsed?.type === 'lorebook') { + console.log('Downloading chub lorebook:', chubParsed.id); + result = await downloadChubLorebook(chubParsed.id); + } + else { + return response.sendStatus(404); + } + } + if (result.fileType) response.set('Content-Type', result.fileType) response.set('Content-Disposition', `attachment; filename="${result.fileName}"`); - response.set('X-Custom-Content-Type', chubParsed?.type); + response.set('X-Custom-Content-Type', type); return response.send(result.buffer); } catch (error) { console.log('Importing custom content failed', error); From 735c4e72681b6b362f4dffb326c4db02e0e4928f Mon Sep 17 00:00:00 2001 From: kingbri Date: Mon, 27 Nov 2023 00:17:07 -0500 Subject: [PATCH 049/632] Server: Fix CORS proxy with URLs Using slashes completely stripped the rest of the URL. Fix that. Signed-off-by: kingbri --- server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.js b/server.js index 463c338c7..fcea9b779 100644 --- a/server.js +++ b/server.js @@ -308,7 +308,7 @@ app.use(function (req, res, next) { if (getConfigValue('enableCorsProxy', false) === true || cliArguments.corsProxy === true) { console.log('Enabling CORS proxy'); - app.use('/proxy/:url', async (req, res) => { + app.use('/proxy/:url(*)', async (req, res) => { const url = req.params.url; // get the url from the request path // Disallow circular requests From edafb8dd13fa5a1a34038fbc4a38d0e663611741 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Mon, 27 Nov 2023 11:16:40 +0200 Subject: [PATCH 050/632] Repurpose trigger command not just for groups --- public/script.js | 4 +++- public/scripts/slash-commands.js | 27 +++++++++++++-------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/public/script.js b/public/script.js index 6a7bd1df8..bd3a39f54 100644 --- a/public/script.js +++ b/public/script.js @@ -7692,7 +7692,9 @@ jQuery(async function () { $("#send_but").on('click', function () { if (is_send_press == false) { - is_send_press = true; + // This prevents from running /trigger command with a send button + // But send on Enter doesn't set is_send_press (it is done by the Generate itself) + // is_send_press = true; Generate(); } }); diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index 3b7ce858c..bc4c1f266 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -29,6 +29,7 @@ import { deactivateSendButtons, activateSendButtons, main_api, + is_send_press, } from "../script.js"; import { getMessageTimeStamp } from "./RossAscends-mods.js"; import { findGroupMemberId, groups, is_group_generating, resetSelectedGroup, saveGroupChat, selected_group } from "./group-chats.js"; @@ -153,7 +154,7 @@ parser.addCommand('sysgen', generateSystemMessage, [], ' parser.addCommand('ask', askCharacter, [], '(prompt) – asks a specified character card a prompt', true, true); parser.addCommand('delname', deleteMessagesByNameCallback, ['cancel'], '(name) – deletes all messages attributed to a specified name', true, true); parser.addCommand('send', sendUserMessageCallback, ['add'], '(text) – adds a user message to the chat log without triggering a generation', true, true); -parser.addCommand('trigger', triggerGroupMessageCallback, [], '(member index or name) – triggers a message generation for the specified group member', true, true); +parser.addCommand('trigger', triggerGenerationCallback, [], ' – triggers a message generation. If in group, can trigger a message for the specified group member index or name.', true, true); parser.addCommand('hide', hideMessageCallback, [], '(message index or range) – hides a chat message from the prompt', true, true); parser.addCommand('unhide', unhideMessageCallback, [], '(message index or range) – unhides a message from the prompt', true, true); parser.addCommand('disable', disableGroupMemberCallback, [], '(member index or name) – disables a group member from being drafted for replies', true, true); @@ -864,28 +865,26 @@ async function addGroupMemberCallback(_, arg) { return character.name; } -async function triggerGroupMessageCallback(_, arg) { - if (!selected_group) { - toastr.warning("Cannot run /trigger command outside of a group chat."); - return ''; - } - - if (is_group_generating) { - toastr.warning("Cannot run trigger command while the group reply is generating."); +async function triggerGenerationCallback(_, arg) { + if (is_send_press || is_group_generating) { + toastr.warning("Cannot run trigger command while the reply is being generated."); return ''; } // Prevent generate recursion $('#send_textarea').val('').trigger('input'); - const chid = findGroupMemberId(arg); + let chid = undefined; - if (chid === undefined) { - console.warn(`WARN: No group member found for argument ${arg}`); - return ''; + if (selected_group && arg) { + chid = findGroupMemberId(arg); + + if (chid === undefined) { + console.warn(`WARN: No group member found for argument ${arg}`); + } } - Generate('normal', { force_chid: chid }); + setTimeout(() => Generate('normal', { force_chid: chid }), 100); return ''; } From ffc4f220124b1fc68d1007cc348a3eeea93123e7 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Mon, 27 Nov 2023 13:25:49 +0200 Subject: [PATCH 051/632] Add provider-specific TTS processing: XTTS - replace ellipsis, Novel - remove tildes. --- public/scripts/extensions/tts/index.js | 4 ++++ public/scripts/extensions/tts/novel.js | 11 +++++++++++ public/scripts/extensions/tts/readme.md | 17 +++++++++++------ public/scripts/extensions/tts/xtts.js | 13 +++++++++++++ 4 files changed, 39 insertions(+), 6 deletions(-) diff --git a/public/scripts/extensions/tts/index.js b/public/scripts/extensions/tts/index.js index 3b85868b2..110dc0838 100644 --- a/public/scripts/extensions/tts/index.js +++ b/public/scripts/extensions/tts/index.js @@ -503,6 +503,10 @@ async function processTtsQueue() { text = matches ? matches.join(partJoiner) : text; } + if (typeof ttsProvider?.processText === 'function') { + text = await ttsProvider.processText(text); + } + // Collapse newlines and spaces into single space text = text.replace(/\s+/g, ' '); diff --git a/public/scripts/extensions/tts/novel.js b/public/scripts/extensions/tts/novel.js index 02ccd9ad5..27eb46be4 100644 --- a/public/scripts/extensions/tts/novel.js +++ b/public/scripts/extensions/tts/novel.js @@ -19,6 +19,17 @@ class NovelTtsProvider { customVoices: [] } + /** + * Perform any text processing before passing to TTS engine. + * @param {string} text Input text + * @returns {string} Processed text + */ + processText(text) { + // Novel reads tilde as a word. Replace with full stop + text = text.replace(/~/g, '.'); + return text; + } + get settingsHtml() { let html = `
        diff --git a/public/scripts/extensions/tts/readme.md b/public/scripts/extensions/tts/readme.md index fb48e116b..26ad50f77 100644 --- a/public/scripts/extensions/tts/readme.md +++ b/public/scripts/extensions/tts/readme.md @@ -1,8 +1,8 @@ -# Provider Requirements. +# Provider Requirements. Because I don't know how, or if you can, and/or maybe I am just too lazy to implement interfaces in JS, here's the requirements of a provider that the extension needs to operate. ### class YourTtsProvider -#### Required +#### Required Exported for use in extension index.js, and added to providers list in index.js 1. generateTts(text, voiceId) 2. fetchTtsVoiceObjects() @@ -13,8 +13,9 @@ Exported for use in extension index.js, and added to providers list in index.js 7. settingsHtml field #### Optional -1. previewTtsVoice() +1. previewTtsVoice() 2. separator field +3. processText(text) # Requirement Descriptions ### generateTts(text, voiceId) @@ -49,14 +50,14 @@ Return without error to let TTS extension know that the provider is ready. Return an error to block the main TTS extension for initializing the provider and UI. The error will be put in the TTS extension UI directly. ### loadSettings(settingsObject) -Required. +Required. Handle the input settings from the TTS extension on provider load. Put code in here to load your provider settings. ### settings field Required, used for storing any provider state that needs to be saved. Anything stored in this field is automatically persisted under extension_settings[providerName] by the main extension in `saveTtsProviderSettings()`, as well as loaded when the provider is selected in `loadTtsProvider(provider)`. -TTS extension doesn't expect any specific contents. +TTS extension doesn't expect any specific contents. ### settingsHtml field Required, injected into the TTS extension UI. Besides adding it, not relied on by TTS extension directly. @@ -68,4 +69,8 @@ Function to handle playing previews of voice samples if no direct preview_url is ### separator field Optional. Used when narrate quoted text is enabled. -Defines the string of characters used to introduce separation between between the groups of extracted quoted text sent to the provider. The provider will use this to introduce pauses by default using `...` \ No newline at end of file +Defines the string of characters used to introduce separation between between the groups of extracted quoted text sent to the provider. The provider will use this to introduce pauses by default using `...` + +### processText(text) +Optional. +A function applied to the input text before passing it to the TTS generator. Can be async. diff --git a/public/scripts/extensions/tts/xtts.js b/public/scripts/extensions/tts/xtts.js index 624e926cc..3add9ed7e 100644 --- a/public/scripts/extensions/tts/xtts.js +++ b/public/scripts/extensions/tts/xtts.js @@ -13,6 +13,19 @@ class XTTSTtsProvider { voices = [] separator = '. ' + /** + * Perform any text processing before passing to TTS engine. + * @param {string} text Input text + * @returns {string} Processed text + */ + processText(text) { + // Replace fancy ellipsis with "..." + text = text.replace(/…/g, '...') + // Replace multiple "." with single "." + text = text.replace(/\.+/g, '.') + return text + } + languageLabels = { "Arabic": "ar", "Brazilian Portuguese": "pt", From 84811ec5180d348fd3fe00a9b2394f1b233f21b7 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Mon, 27 Nov 2023 16:10:42 +0200 Subject: [PATCH 052/632] Fix image gen prefix combining --- .../extensions/stable-diffusion/index.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/public/scripts/extensions/stable-diffusion/index.js b/public/scripts/extensions/stable-diffusion/index.js index b310d98d3..083f6da9b 100644 --- a/public/scripts/extensions/stable-diffusion/index.js +++ b/public/scripts/extensions/stable-diffusion/index.js @@ -603,17 +603,19 @@ function getCharacterPrefix() { * @returns {string} Combined string with a comma between them */ function combinePrefixes(str1, str2, macro = '') { + // Remove leading/trailing white spaces and commas from the strings + const process = (s) => s.trim().replace(/^,|,$/g, '').trim(); + if (!str2) { return str1; } - // Remove leading/trailing white spaces and commas from the strings - str1 = str1.trim().replace(/^,|,$/g, ''); - str2 = str2.trim().replace(/^,|,$/g, ''); + str1 = process(str1) + str2 = process(str2); // Combine the strings with a comma between them) const result = macro && str1.includes(macro) ? str1.replace(macro, str2) : `${str1}, ${str2},`; - return result; + return process(result); } function onExpandInput() { @@ -1297,8 +1299,8 @@ async function loadAutoModels() { async function loadOpenAiModels() { return [ - { value: 'dall-e-2', text: 'DALL-E 2' }, { value: 'dall-e-3', text: 'DALL-E 3' }, + { value: 'dall-e-2', text: 'DALL-E 2' }, ]; } @@ -1783,9 +1785,10 @@ async function generatePrompt(quietPrompt) { } async function sendGenerationRequest(generationType, prompt, characterName = null, callback) { - const prefix = (generationType !== generationMode.BACKGROUND && generationType !== generationMode.FREE) - ? combinePrefixes(extension_settings.sd.prompt_prefix, getCharacterPrefix()) - : extension_settings.sd.prompt_prefix; + const noCharPrefix = [generationMode.FREE, generationMode.BACKGROUND, generationMode.USER, generationMode.USER_MULTIMODAL]; + const prefix = noCharPrefix.includes(generationType) + ? extension_settings.sd.prompt_prefix + : combinePrefixes(extension_settings.sd.prompt_prefix, getCharacterPrefix()); const prefixedPrompt = combinePrefixes(prefix, prompt, '{prompt}'); From 188897a3df46df2f88ec99f4eb1cadbe6a48c42c Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Mon, 27 Nov 2023 19:48:49 +0200 Subject: [PATCH 053/632] Add "at" argument to commands sending a message --- public/script.js | 57 ++++++++++++++--------- public/scripts/slash-commands.js | 77 ++++++++++++++++++++++---------- 2 files changed, 91 insertions(+), 43 deletions(-) diff --git a/public/script.js b/public/script.js index bd3a39f54..9b8ab0c3f 100644 --- a/public/script.js +++ b/public/script.js @@ -3987,37 +3987,54 @@ export function replaceBiasMarkup(str) { return (str ?? '').replace(/\{\{[\s\S]*?\}\}/gm, ''); } -export async function sendMessageAsUser(textareaText, messageBias) { - textareaText = getRegexedString(textareaText, regex_placement.USER_INPUT); +/** + * Inserts a user message into the chat history. + * @param {string} messageText Message text. + * @param {string} messageBias Message bias. + * @param {number} [insertAt] Optional index to insert the message at. + * @returns {Promise} A promise that resolves when the message is inserted. + */ +export async function sendMessageAsUser(messageText, messageBias, insertAt = null) { + messageText = getRegexedString(messageText, regex_placement.USER_INPUT); - chat[chat.length] = {}; - chat[chat.length - 1]['name'] = name1; - chat[chat.length - 1]['is_user'] = true; - chat[chat.length - 1]['send_date'] = getMessageTimeStamp(); - chat[chat.length - 1]['mes'] = substituteParams(textareaText); - chat[chat.length - 1]['extra'] = {}; + const message = { + name: name1, + is_user: true, + is_system: false, + send_date: getMessageTimeStamp(), + mes: substituteParams(messageText), + extra: {}, + }; if (power_user.message_token_count_enabled) { - chat[chat.length - 1]['extra']['token_count'] = getTokenCount(chat[chat.length - 1]['mes'], 0); + message.extra.token_count = getTokenCount(message.mes, 0); } // Lock user avatar to a persona. if (user_avatar in power_user.personas) { - chat[chat.length - 1]['force_avatar'] = getUserAvatar(user_avatar); + message.force_avatar = getUserAvatar(user_avatar); } if (messageBias) { - console.debug('checking bias'); - chat[chat.length - 1]['extra']['bias'] = messageBias; + message.extra.bias = messageBias; + } + + await populateFileAttachment(message); + statMesProcess(message, 'user', characters, this_chid, ''); + + if (typeof insertAt === 'number' && insertAt >= 0 && insertAt <= chat.length) { + chat.splice(insertAt, 0, message); + await saveChatConditional(); + await eventSource.emit(event_types.MESSAGE_SENT, insertAt); + await reloadCurrentChat(); + await eventSource.emit(event_types.USER_MESSAGE_RENDERED, insertAt); + } else { + chat.push(message); + const chat_id = (chat.length - 1); + await eventSource.emit(event_types.MESSAGE_SENT, chat_id); + addOneMessage(message); + await eventSource.emit(event_types.USER_MESSAGE_RENDERED, chat_id); } - await populateFileAttachment(chat[chat.length - 1]); - statMesProcess(chat[chat.length - 1], 'user', characters, this_chid, ''); - // Wait for all handlers to finish before continuing with the prompt - const chat_id = (chat.length - 1); - await eventSource.emit(event_types.MESSAGE_SENT, chat_id); - addOneMessage(chat[chat_id]); - await eventSource.emit(event_types.USER_MESSAGE_RENDERED, chat_id); - console.debug('message sent as user'); } function getMaxContextSize() { diff --git a/public/scripts/slash-commands.js b/public/scripts/slash-commands.js index bc4c1f266..af9c47f84 100644 --- a/public/scripts/slash-commands.js +++ b/public/scripts/slash-commands.js @@ -628,7 +628,7 @@ async function askCharacter(_, text) { setCharacterName(character.name); - sendMessageAsUser(mesText) + await sendMessageAsUser(mesText, ''); const restoreCharacter = () => { setCharacterId(prevChId); @@ -888,7 +888,7 @@ async function triggerGenerationCallback(_, arg) { return ''; } -async function sendUserMessageCallback(_, text) { +async function sendUserMessageCallback(args, text) { if (!text) { console.warn('WARN: No text provided for /send command'); return; @@ -896,7 +896,8 @@ async function sendUserMessageCallback(_, text) { text = text.trim(); const bias = extractMessageBias(text); - await sendMessageAsUser(text, bias); + const insertAt = Number(resolveVariable(args?.at)); + await sendMessageAsUser(text, bias, insertAt); return ''; } @@ -1047,7 +1048,7 @@ async function setNarratorName(_, text) { await saveChatConditional(); } -export async function sendMessageAs(namedArgs, text) { +export async function sendMessageAs(args, text) { if (!text) { return; } @@ -1055,8 +1056,8 @@ export async function sendMessageAs(namedArgs, text) { let name; let mesText; - if (namedArgs.name) { - name = namedArgs.name.trim(); + if (args.name) { + name = args.name.trim(); mesText = text.trim(); if (!name && !text) { @@ -1107,14 +1108,24 @@ export async function sendMessageAs(namedArgs, text) { } }; - chat.push(message); - await eventSource.emit(event_types.MESSAGE_RECEIVED, (chat.length - 1)); - addOneMessage(message); - await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, (chat.length - 1)); - await saveChatConditional(); + const insertAt = Number(resolveVariable(args.at)); + + if (!isNaN(insertAt) && insertAt >= 0 && insertAt <= chat.length) { + chat.splice(insertAt, 0, message); + await saveChatConditional(); + await eventSource.emit(event_types.MESSAGE_RECEIVED, insertAt); + await reloadCurrentChat(); + await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, insertAt); + } else { + chat.push(message); + await eventSource.emit(event_types.MESSAGE_RECEIVED, (chat.length - 1)); + addOneMessage(message); + await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, (chat.length - 1)); + await saveChatConditional(); + } } -export async function sendNarratorMessage(_, text) { +export async function sendNarratorMessage(args, text) { if (!text) { return; } @@ -1138,11 +1149,21 @@ export async function sendNarratorMessage(_, text) { }, }; - chat.push(message); - await eventSource.emit(event_types.MESSAGE_SENT, (chat.length - 1)); - addOneMessage(message); - await eventSource.emit(event_types.USER_MESSAGE_RENDERED, (chat.length - 1)); - await saveChatConditional(); + const insertAt = Number(resolveVariable(args.at)); + + if (!isNaN(insertAt) && insertAt >= 0 && insertAt <= chat.length) { + chat.splice(insertAt, 0, message); + await saveChatConditional(); + await eventSource.emit(event_types.MESSAGE_SENT, insertAt); + await reloadCurrentChat(); + await eventSource.emit(event_types.USER_MESSAGE_RENDERED, insertAt); + } else { + chat.push(message); + await eventSource.emit(event_types.MESSAGE_SENT, (chat.length - 1)); + addOneMessage(message); + await eventSource.emit(event_types.USER_MESSAGE_RENDERED, (chat.length - 1)); + await saveChatConditional(); + } } export async function promptQuietForLoudResponse(who, text) { @@ -1184,7 +1205,7 @@ export async function promptQuietForLoudResponse(who, text) { } -async function sendCommentMessage(_, text) { +async function sendCommentMessage(args, text) { if (!text) { return; } @@ -1202,11 +1223,21 @@ async function sendCommentMessage(_, text) { }, }; - chat.push(message); - await eventSource.emit(event_types.MESSAGE_SENT, (chat.length - 1)); - addOneMessage(message); - await eventSource.emit(event_types.USER_MESSAGE_RENDERED, (chat.length - 1)); - await saveChatConditional(); + const insertAt = Number(resolveVariable(args.at)); + + if (!isNaN(insertAt) && insertAt >= 0 && insertAt <= chat.length) { + chat.splice(insertAt, 0, message); + await saveChatConditional(); + await eventSource.emit(event_types.MESSAGE_SENT, insertAt); + await reloadCurrentChat(); + await eventSource.emit(event_types.USER_MESSAGE_RENDERED, insertAt); + } else { + chat.push(message); + await eventSource.emit(event_types.MESSAGE_SENT, (chat.length - 1)); + addOneMessage(message); + await eventSource.emit(event_types.USER_MESSAGE_RENDERED, (chat.length - 1)); + await saveChatConditional(); + } } /** From 4c94bd0aa87350e071dfd41df08073e6f636efbc Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Mon, 27 Nov 2023 21:10:50 +0200 Subject: [PATCH 054/632] Add math operations --- public/scripts/variables.js | 102 ++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/public/scripts/variables.js b/public/scripts/variables.js index b2b6ed0fd..3b4fa26d8 100644 --- a/public/scripts/variables.js +++ b/public/scripts/variables.js @@ -340,6 +340,97 @@ function deleteGlobalVariable(name) { return ''; } +function parseNumericSeries(value) { + if (typeof value === 'number') { + return [value]; + } + + const array = value + .split(' ') + .map(i => i.trim()) + .filter(i => i !== '') + .map(i => isNaN(Number(i)) ? resolveVariable(i) : Number(i)) + .filter(i => !isNaN(i)); + + return array; +} + +function performOperation(value, operation, singleOperand = false) { + if (!value) { + return 0; + } + + const array = parseNumericSeries(value); + + if (array.length === 0) { + return 0; + } + + const result = singleOperand ? operation(array[0]) : operation(array); + + if (isNaN(result) || !isFinite(result)) { + return 0; + } + + return result; +} + +function addValuesCallback(value) { + return performOperation(value, (array) => array.reduce((a, b) => a + b, 0)); +} + +function mulValuesCallback(value) { + return performOperation(value, (array) => array.reduce((a, b) => a * b, 1)); +} + +function subValuesCallback(value) { + return performOperation(value, (array) => array[0] - array[1]); +} + +function divValuesCallback(value) { + return performOperation(value, (array) => { + if (array[1] === 0) { + console.warn('Division by zero.'); + return 0; + } + return array[0] / array[1]; + }); +} + +function modValuesCallback(value) { + return performOperation(value, (array) => { + if (array[1] === 0) { + console.warn('Division by zero.'); + return 0; + } + return array[0] % array[1]; + }); +} + +function powValuesCallback(value) { + return performOperation(value, (array) => Math.pow(array[0], array[1])); +} + +function sinValuesCallback(value) { + return performOperation(value, Math.sin, true); +} + +function cosValuesCallback(value) { + return performOperation(value, Math.cos, true); +} + +function logValuesCallback(value) { + return performOperation(value, Math.log, true); +} + +function roundValuesCallback(value) { + return performOperation(value, Math.round, true); +} + +function absValuesCallback(value) { + return performOperation(value, Math.abs, true); +} + export function registerVariableCommands() { registerSlashCommand('listvar', listVariablesCallback, [], ' – list registered chat variables', true, true); registerSlashCommand('setvar', (args, value) => setLocalVariable(args.key || args.name, value), [], 'key=varname (value) – set a local variable value and pass it down the pipe, e.g. /setvar key=color green', true, true); @@ -352,4 +443,15 @@ export function registerVariableCommands() { registerSlashCommand('while', whileCallback, [], 'left=varname1 right=varname2 rule=comparison "(command)" – compare the value of the left operand "a" with the value of the right operand "b", and if the condition yields true, then execute any valid slash command enclosed in quotes. Numeric values and string literals for left and right operands supported. Available rules: gt => a > b, gte => a >= b, lt => a < b, lte => a <= b, eq => a == b, neq => a != b, not => !a, in (strings) => a includes b, nin (strings) => a not includes b, e.g. /setvar key=i 0 | /while left=i right=10 rule=let "/addvar key=i 1" adds 1 to the value of "i" until it reaches 10. Loops are limited to 100 iterations by default, pass guard=off to disable.', true, true); registerSlashCommand('flushvar', (_, value) => deleteLocalVariable(value), [], '(key) – delete a local variable, e.g. /flushvar score', true, true); registerSlashCommand('flushglobalvar', (_, value) => deleteGlobalVariable(value), [], '(key) – delete a global variable, e.g. /flushglobalvar score', true, true); + registerSlashCommand('add', (_, value) => addValuesCallback(value), [], '(a b c d) – performs an addition of the set of values and passes the result down the pipe, can use variable names, e.g. /add 10 i 30 j', true, true); + registerSlashCommand('mul', (_, value) => mulValuesCallback(value), [], '(a b c d) – performs a multiplication of the set of values and passes the result down the pipe, can use variable names, e.g. /mul 10 i 30 j', true, true); + registerSlashCommand('sub', (_, value) => subValuesCallback(value), [], '(a b) – performs a subtraction of two values and passes the result down the pipe, can use variable names, e.g. /sub i 5', true, true); + registerSlashCommand('div', (_, value) => divValuesCallback(value), [], '(a b) – performs a division of two values and passes the result down the pipe, can use variable names, e.g. /div 10 i', true, true); + registerSlashCommand('mod', (_, value) => modValuesCallback(value), [], '(a b) – performs a modulo operation of two values and passes the result down the pipe, can use variable names, e.g. /mod i 2', true, true); + registerSlashCommand('pow', (_, value) => powValuesCallback(value), [], '(a b) – performs a power operation of two values and passes the result down the pipe, can use variable names, e.g. /pow i 2', true, true); + registerSlashCommand('sin', (_, value) => sinValuesCallback(value), [], '(a) – performs a sine operation of a value and passes the result down the pipe, can use variable names, e.g. /sin i', true, true); + registerSlashCommand('cos', (_, value) => cosValuesCallback(value), [], '(a) – performs a cosine operation of a value and passes the result down the pipe, can use variable names, e.g. /cos i', true, true); + registerSlashCommand('log', (_, value) => logValuesCallback(value), [], '(a) – performs a logarithm operation of a value and passes the result down the pipe, can use variable names, e.g. /log i', true, true); + registerSlashCommand('abs', (_, value) => absValuesCallback(value), [], '(a) – performs an absolute value operation of a value and passes the result down the pipe, can use variable names, e.g. /abs i', true, true); + registerSlashCommand('round', (_, value) => roundValuesCallback(value), [], '(a) – rounds a value and passes the result down the pipe, can use variable names, e.g. /round i', true, true); } From 8b517be98caf4e84854ad690cb16e207db1533e8 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Mon, 27 Nov 2023 22:00:33 +0200 Subject: [PATCH 055/632] Allow rep pen range -1 for text completions --- public/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/index.html b/public/index.html index ad3e24173..66aa1bcd0 100644 --- a/public/index.html +++ b/public/index.html @@ -1150,8 +1150,8 @@
        Repetition Penalty Range - - + +
        Encoder Penalty From d263760b2510dbd5e947525f6d5fa794e2e7dafe Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Mon, 27 Nov 2023 23:57:56 +0200 Subject: [PATCH 056/632] #1393 Configurable group nudges, scenario and personality templates for prompt manager --- public/index.html | 44 +++++++++++++++++++++++++++++ public/scripts/openai.js | 60 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 101 insertions(+), 3 deletions(-) diff --git a/public/index.html b/public/index.html index 66aa1bcd0..8f077b10b 100644 --- a/public/index.html +++ b/public/index.html @@ -575,6 +575,50 @@
    +
    +
    + Scenario format template +
    +
    +
    +
    +
    + Use {{scenario}} to mark a place where the content is inserted. +
    +
    + +
    +
    +
    +
    + Personality format template +
    +
    +
    +
    +
    + Use {{personality}} to mark a place where the content is inserted. +
    +
    + +
    +
    +
    +
    + Group Nudge prompt template +
    +
    +
    +
    +
    + + Sent at the end of the group chat history to force reply from a specific character. + +
    +
    + +
    +
    New Chat diff --git a/public/scripts/openai.js b/public/scripts/openai.js index 594591c5f..7382980a2 100644 --- a/public/scripts/openai.js +++ b/public/scripts/openai.js @@ -92,6 +92,9 @@ const default_new_group_chat_prompt = '[Start a new group chat. Group members: { const default_new_example_chat_prompt = '[Start a new Chat]'; const default_continue_nudge_prompt = '[Continue the following message. Do not include ANY parts of the original message. Use capitalization and punctuation as if your reply is a part of the original message: {{lastChatMessage}}]'; const default_bias = 'Default (none)'; +const default_personality_format = `[{{char}}'s personality: {{personality}}]`; +const default_scenario_format = `[Circumstances and context of the dialogue: {{scenario}}]`; +const default_group_nudge_prompt = `[Write the next reply only as {{char}}.]`; const default_bias_presets = { [default_bias]: [], 'Anti-bond': [ @@ -199,6 +202,9 @@ const default_settings = { bias_preset_selected: default_bias, bias_presets: default_bias_presets, wi_format: default_wi_format, + group_nudge_prompt: default_group_nudge_prompt, + scenario_format: default_scenario_format, + personality_format: default_personality_format, openai_model: 'gpt-3.5-turbo', claude_model: 'claude-instant-v1', ai21_model: 'j2-ultra', @@ -249,6 +255,9 @@ const oai_settings = { bias_preset_selected: default_bias, bias_presets: default_bias_presets, wi_format: default_wi_format, + group_nudge_prompt: default_group_nudge_prompt, + scenario_format: default_scenario_format, + personality_format: default_personality_format, openai_model: 'gpt-3.5-turbo', claude_model: 'claude-instant-v1', ai21_model: 'j2-ultra', @@ -903,9 +912,9 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm * @returns {Object} prompts - The prepared and merged system and user-defined prompts. */ function preparePromptsForChatCompletion({ Scenario, charPersonality, name2, worldInfoBefore, worldInfoAfter, charDescription, quietPrompt, bias, extensionPrompts, systemPromptOverride, jailbreakPromptOverride, personaDescription } = {}) { - const scenarioText = Scenario ? `[Circumstances and context of the dialogue: ${Scenario}]` : ''; - const charPersonalityText = charPersonality ? `[${name2}'s personality: ${charPersonality}]` : '' - const groupNudge = `[Write the next reply only as ${name2}]`; + const scenarioText = Scenario && oai_settings.scenario_format ? substituteParams(oai_settings.scenario_format) : ''; + const charPersonalityText = charPersonality && oai_settings.personality_format ? substituteParams(oai_settings.personality_format) : ''; + const groupNudge = substituteParams(oai_settings.group_nudge_prompt); // Create entries for system prompts const systemPrompts = [ @@ -2305,6 +2314,9 @@ function loadOpenAISettings(data, settings) { oai_settings.max_context_unlocked = settings.max_context_unlocked ?? default_settings.max_context_unlocked; oai_settings.send_if_empty = settings.send_if_empty ?? default_settings.send_if_empty; oai_settings.wi_format = settings.wi_format ?? default_settings.wi_format; + oai_settings.scenario_format = settings.scenario_format ?? default_settings.scenario_format; + oai_settings.personality_format = settings.personality_format ?? default_settings.personality_format; + oai_settings.group_nudge_prompt = settings.group_nudge_prompt ?? default_settings.group_nudge_prompt; oai_settings.claude_model = settings.claude_model ?? default_settings.claude_model; oai_settings.windowai_model = settings.windowai_model ?? default_settings.windowai_model; oai_settings.openrouter_model = settings.openrouter_model ?? default_settings.openrouter_model; @@ -2381,6 +2393,9 @@ function loadOpenAISettings(data, settings) { $('#continue_nudge_prompt_textarea').val(oai_settings.continue_nudge_prompt); $('#wi_format_textarea').val(oai_settings.wi_format); + $('#scenario_format_textarea').val(oai_settings.scenario_format); + $('#personality_format_textarea').val(oai_settings.personality_format); + $('#group_nudge_prompt_textarea').val(oai_settings.group_nudge_prompt); $('#send_if_empty_textarea').val(oai_settings.send_if_empty); $('#temp_openai').val(oai_settings.temp_openai); @@ -2565,6 +2580,9 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) { legacy_streaming: settings.legacy_streaming, max_context_unlocked: settings.max_context_unlocked, wi_format: settings.wi_format, + scenario_format: settings.scenario_format, + personality_format: settings.personality_format, + group_nudge_prompt: settings.group_nudge_prompt, stream_openai: settings.stream_openai, prompts: settings.prompts, prompt_order: settings.prompt_order, @@ -2920,6 +2938,9 @@ function onSettingsPresetChange() { reverse_proxy: ['#openai_reverse_proxy', 'reverse_proxy', false], legacy_streaming: ['#legacy_streaming', 'legacy_streaming', true], wi_format: ['#wi_format_textarea', 'wi_format', false], + scenario_format: ['#scenario_format_textarea', 'scenario_format', false], + personality_format: ['#personality_format_textarea', 'personality_format', false], + group_nudge_prompt: ['#group_nudge_prompt_textarea', 'group_nudge_prompt', false], stream_openai: ['#stream_toggle', 'stream_openai', true], prompts: ['', 'prompts', false], prompt_order: ['', 'prompt_order', false], @@ -3569,6 +3590,21 @@ $(document).ready(async function () { saveSettingsDebounced(); }); + $("#scenario_format_textarea").on('input', function () { + oai_settings.scenario_format = String($('#scenario_format_textarea').val()); + saveSettingsDebounced(); + }); + + $("#personality_format_textarea").on('input', function () { + oai_settings.personality_format = String($('#personality_format_textarea').val()); + saveSettingsDebounced(); + }); + + $("#group_nudge_prompt_textarea").on('input', function () { + oai_settings.group_nudge_prompt = String($('#group_nudge_prompt_textarea').val()); + saveSettingsDebounced(); + }); + // auto-select a preset based on character/group name $(document).on("click", ".character_select", function () { const chid = $(this).attr('chid'); @@ -3634,6 +3670,24 @@ $(document).ready(async function () { saveSettingsDebounced(); }); + $("#scenario_format_restore").on('click', function () { + oai_settings.scenario_format = default_scenario_format; + $('#scenario_format_textarea').val(oai_settings.scenario_format); + saveSettingsDebounced(); + }); + + $("#personality_format_restore").on('click', function () { + oai_settings.personality_format = default_personality_format; + $('#personality_format_textarea').val(oai_settings.personality_format); + saveSettingsDebounced(); + }); + + $("#group_nudge_prompt_restore").on('click', function () { + oai_settings.group_nudge_prompt = default_group_nudge_prompt; + $('#group_nudge_prompt_textarea').val(oai_settings.group_nudge_prompt); + saveSettingsDebounced(); + }); + $('#legacy_streaming').on('input', function () { oai_settings.legacy_streaming = !!$(this).prop('checked'); saveSettingsDebounced(); From fd8551b73bb6325db98535a5ecbff687a31ca2ae Mon Sep 17 00:00:00 2001 From: LenAnderson Date: Mon, 27 Nov 2023 22:02:20 +0000 Subject: [PATCH 057/632] fix context menu for Qr popout --- .../scripts/extensions/quick-reply/index.js | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/public/scripts/extensions/quick-reply/index.js b/public/scripts/extensions/quick-reply/index.js index 479e18510..821d3e193 100644 --- a/public/scripts/extensions/quick-reply/index.js +++ b/public/scripts/extensions/quick-reply/index.js @@ -354,6 +354,27 @@ async function doQuickReplyBarPopout() { let index = $(this).data('index'); sendQuickReply(index); }); + $('.quickReplyButton > .ctx-expander').on('click', function (evt) { + evt.stopPropagation(); + let index = $(this.closest('.quickReplyButton')).data('index'); + const qr = extension_settings.quickReply.quickReplySlots[index]; + if (qr.contextMenu?.length) { + evt.preventDefault(); + const tree = buildContextMenu(qr); + const menu = new ContextMenu(tree.children); + menu.show(evt); + } + }) + $('.quickReplyButton').on('contextmenu', function (evt) { + let index = $(this).data('index'); + const qr = extension_settings.quickReply.quickReplySlots[index]; + if (qr.contextMenu?.length) { + evt.preventDefault(); + const tree = buildContextMenu(qr); + const menu = new ContextMenu(tree.children); + menu.show(evt); + } + }); loadMovingUIState(); $("#quickReplyBarPopout").fadeIn(250) @@ -370,6 +391,27 @@ async function doQuickReplyBarPopout() { let index = $(this).data('index'); sendQuickReply(index); }); + $('.quickReplyButton > .ctx-expander').on('click', function (evt) { + evt.stopPropagation(); + let index = $(this.closest('.quickReplyButton')).data('index'); + const qr = extension_settings.quickReply.quickReplySlots[index]; + if (qr.contextMenu?.length) { + evt.preventDefault(); + const tree = buildContextMenu(qr); + const menu = new ContextMenu(tree.children); + menu.show(evt); + } + }) + $('.quickReplyButton').on('contextmenu', function (evt) { + let index = $(this).data('index'); + const qr = extension_settings.quickReply.quickReplySlots[index]; + if (qr.contextMenu?.length) { + evt.preventDefault(); + const tree = buildContextMenu(qr); + const menu = new ContextMenu(tree.children); + menu.show(evt); + } + }); $("#quickReplyPopoutButton").off('click').on('click', doQuickReplyBarPopout) }) From 87707b565fd1d8942d6f5f8b019bbe99f2c7e824 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Tue, 28 Nov 2023 00:29:34 +0200 Subject: [PATCH 058/632] Add checkbox for restoring user input on page refresh --- public/index.html | 4 ++++ public/scripts/RossAscends-mods.js | 21 ++++++++++++++++++++- public/scripts/power-user.js | 7 +++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/public/index.html b/public/index.html index 8f077b10b..a4c2bcce9 100644 --- a/public/index.html +++ b/public/index.html @@ -2950,6 +2950,10 @@ Lorebook Import Dialog +