From 3b8188877fe0aeb22d1fc0c2c18f17f3f3b7b6c2 Mon Sep 17 00:00:00 2001 From: deffcolony <61471128+deffcolony@users.noreply.github.com> Date: Sun, 31 Mar 2024 12:47:51 +0200 Subject: [PATCH 01/80] update ISSUE_TEMPLATE --- .github/ISSUE_TEMPLATE/bug-report.yml | 10 +++++----- .github/ISSUE_TEMPLATE/feature-request.yml | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 58f8ae2eb..53be9963f 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -69,16 +69,16 @@ body: required: false - type: checkboxes - id: idiot-check + id: user-check attributes: label: Please tick the boxes - description: Before submitting, please ensure that + description: Before submitting, please ensure that you have completed the following checklist options: - - label: You have explained the issue clearly, and included all relevant info + - label: I have explained the issue clearly, and I included all relevant info required: true - - label: You've checked that this [issue hasn't already been raised](https://github.com/SillyTavern/SillyTavern/issues?q=is%3Aissue) + - label: I have checked that this [issue hasn't already been raised](https://github.com/SillyTavern/SillyTavern/issues?q=is%3Aissue) required: true - - label: You've checked the [docs](https://docs.sillytavern.app/) ![important](https://img.shields.io/badge/Important!-F6094E) + - label: I have checked the [docs](https://docs.sillytavern.app/) ![important](https://img.shields.io/badge/Important!-F6094E) required: true - type: markdown diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml index 4180f93da..761dec374 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -15,7 +15,7 @@ body: - 'No' - 'Yes' validations: - required: false + required: true # Field 2 - Is it bug-related - type: textarea @@ -71,12 +71,12 @@ body: - type: dropdown id: canImplement attributes: - label: Is this something you would be keen to implement? + label: Are you willing to test this on staging/unstable branch if this is implemented? description: Are you raising this ticket in order to get an issue number for your PR? options: - 'No' - 'Maybe' - - 'Yes!' + - 'Yes' validations: required: false From 39f9ba0ef5818edf96bb91d6bc624e042b094e14 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sun, 31 Mar 2024 13:56:56 +0300 Subject: [PATCH 02/80] Update feature-request.yml --- .github/ISSUE_TEMPLATE/feature-request.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml index 761dec374..bbb97465e 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -67,12 +67,12 @@ body: validations: required: true - # Field 7 - Can the user implement + # Field 7 - Can the user user test in staging - type: dropdown - id: canImplement + id: canTestStaging attributes: label: Are you willing to test this on staging/unstable branch if this is implemented? - description: Are you raising this ticket in order to get an issue number for your PR? + description: Otherwise you'll need to wait until the next stable release after the feature is developed. options: - 'No' - 'Maybe' From 6f2adf2bcfdbdf7ffc4376bc2c7a4956a4b7aab7 Mon Sep 17 00:00:00 2001 From: deffcolony <61471128+deffcolony@users.noreply.github.com> Date: Wed, 3 Apr 2024 19:05:31 +0200 Subject: [PATCH 03/80] Update readme.md --- .github/readme.md | 143 +++++++++++++++++++++++++++------------------- 1 file changed, 85 insertions(+), 58 deletions(-) diff --git a/.github/readme.md b/.github/readme.md index 4b6892650..4032431d4 100644 --- a/.github/readme.md +++ b/.github/readme.md @@ -1,6 +1,8 @@ + + English | [中文](readme-zh_cn.md) | [日本語](readme-ja_jp.md) -![SillyTavern-Banner](https://github.com/SillyTavern/SillyTavern/assets/18619528/c2be4c3f-aada-4f64-87a3-ae35a68b61a4) +![][cover] Mobile-friendly layout, Multi-API (KoboldAI/CPP, Horde, NovelAI, Ooba, OpenAI, OpenRouter, Claude, Scale), VN-like Waifu Mode, Stable Diffusion, TTS, WorldInfo (lorebooks), customizable UI, auto-translate, and more prompt options than you'd ever want or need + ability to install third-party extensions. @@ -22,6 +24,11 @@ SillyTavern is a user interface you can install on your computer (and Android ph SillyTavern is a fork of TavernAI 1.2.8 which is under more active development and has added many major features. At this point, they can be thought of as completely independent programs. +## Screenshots + +image +image + ### Branches SillyTavern is being developed using a two-branch system to ensure a smooth experience for all users. @@ -31,36 +38,25 @@ SillyTavern is being developed using a two-branch system to ensure a smooth expe If you're not familiar with using the git CLI or don't understand what a branch is, don't worry! The release branch is always the preferable option for you. -### What do I need other than Tavern? +### What do I need other than SillyTavern? -On its own Tavern is useless, as it's just a user interface. You have to have access to an AI system backend that can act as the roleplay character. There are various supported backends: OpenAPI API (GPT), KoboldAI (either running locally or on Google Colab), and more. You can read more about this in [the FAQ](https://docs.sillytavern.app/usage/faq/). +On its own SillyTavern is useless, as it's just a user interface. You have to have access to an AI system backend that can act as the roleplay character. There are various supported backends: OpenAPI API (GPT), KoboldAI (either running locally or on Google Colab), and more. You can read more about this in [the FAQ](https://docs.sillytavern.app/usage/faq/). -### Do I need a powerful PC to run Tavern? +### Do I need a powerful PC to run SillyTavern? -Since Tavern is only a user interface, it has tiny hardware requirements, it will run on anything. It's the AI system backend that needs to be powerful. - -## Mobile support - -> **Note** - -> **This fork can be run natively on Android phones using Termux. Please refer to this guide by ArroganceComplex#2659:** - - +Since SillyTavern is only a user interface, it has tiny hardware requirements, it will run on anything. It's the AI system backend that needs to be powerful. ## Questions or suggestions? ### We now have a community Discord server -Get support, share favorite characters and prompts: +| [![][discord-shield-badge]][discord-link] | [Join our Discord community!](https://discord.gg/sillytavern) Get support, share favorite characters and prompts. | +| :---------------------------------------- | :----------------------------------------------------------------------------------------------------------------- | -### [Join](https://discord.gg/sillytavern) - -*** - -Get in touch with the developers directly: +Or get in touch with the developers directly: * Discord: cohee or rossascends -* Reddit: /u/RossAscends or /u/sillylossy +* Reddit: [/u/RossAscends](https://www.reddit.com/user/RossAscends/) or [/u/sillylossy](https://www.reddit.com/user/sillylossy/) * [Post a GitHub issue](https://github.com/SillyTavern/SillyTavern/issues) ## This version includes @@ -124,61 +120,88 @@ A full list of included extensions and tutorials on how to use them can be found * Customizable page colors for 'main text', 'quoted text', and 'italics text'. * Customizable UI background color and blur amount -## Installation +# ⌛ Installation -*NOTE: This software is intended for local install purposes, and has not been thoroughly tested on a colab or other cloud notebook service.* +> \[!WARNING] +> * DO NOT INSTALL INTO ANY WINDOWS CONTROLLED FOLDER (Program Files, System32, etc). +> * DO NOT RUN START.BAT WITH ADMIN PERMISSIONS +> * INSTALLATION ON WINDOWS 7 IS IMPOSSIBLE AS IT CAN NOT RUN NODEJS 18.16 -> **Warning** - -> DO NOT INSTALL INTO ANY WINDOWS CONTROLLED FOLDER (Program Files, System32, etc). - -> DO NOT RUN START.BAT WITH ADMIN PERMISSIONS - -### Windows - -Installing via Git (recommended for easy updating) - -An easy-to-follow guide with pretty pictures: - +## 🪟 Windows +## Installing via Git 1. Install [NodeJS](https://nodejs.org/en) (latest LTS version is recommended) - 2. Install [GitHub Desktop](https://central.github.com/deployments/desktop/desktop/latest/win32) + 2. Install [Git for Windows](https://gitforwindows.org/) 3. Open Windows Explorer (`Win+E`) 4. Browse to or Create a folder that is not controlled or monitored by Windows. (ex: C:\MySpecialFolder\) 5. Open a Command Prompt inside that folder by clicking in the 'Address Bar' at the top, typing `cmd`, and pressing Enter. 6. Once the black box (Command Prompt) pops up, type ONE of the following into it and press Enter: -* for Release Branch: `git clone https://github.com/SillyTavern/SillyTavern -b release` -* for Staging Branch: `git clone https://github.com/SillyTavern/SillyTavern -b staging` +- for Release Branch: `git clone https://github.com/SillyTavern/SillyTavern -b release` +- for Staging Branch: `git clone https://github.com/SillyTavern/SillyTavern -b staging` 7. Once everything is cloned, double-click `Start.bat` to make NodeJS install its requirements. 8. The server will then start, and SillyTavern will pop up in your browser. -Installing via ZIP download (discouraged) +## Installing via SillyTavern Launcher + 1. Install [Git for Windows](https://gitforwindows.org/) + 2. Open Windows Explorer (`Win+E`) and make or choose a folder where you wanna install the launcher to + 3. Open a Command Prompt inside that folder by clicking in the 'Address Bar' at the top, typing `cmd`, and pressing Enter. + 4. When you see a black box, insert the following command: `git clone https://github.com/SillyTavern/SillyTavern-Launcher.git` + 5. Double-click on `installer.bat` and choose what you wanna install + 6. After installation double-click on `launcher.bat` +## Installing via GitHub Desktop +(This allows git usage **only** in GitHub Desktop, if you want to use `git` on the command line too, you also need to install [Git for Windows](https://gitforwindows.org/)) 1. Install [NodeJS](https://nodejs.org/en) (latest LTS version is recommended) - 2. Download the zip from this GitHub repo. (Get the `Source code (zip)` from [Releases](https://github.com/SillyTavern/SillyTavern/releases/latest)) - 3. Unzip it into a folder of your choice - 4. Run `Start.bat` by double-clicking or in a command line. - 5. Once the server has prepared everything for you, it will open a tab in your browser. + 2. Install [GitHub Desktop](https://central.github.com/deployments/desktop/desktop/latest/win32) + 3. After installing GitHub Desktop, click on `Clone a repository from the internet....` (Note: You **do NOT need** to create a GitHub account for this step) + 4. On the menu, click the URL tab, enter this URL `https://github.com/SillyTavern/SillyTavern`, and click Clone. You can change the Local path to change where SillyTavern is going to be downloaded. + 6. To open SillyTavern, use Windows Explorer to browse into the folder where you cloned the repository. By default, the repository will be cloned here: `C:\Users\[Your Windows Username]\Documents\GitHub\SillyTavern` + 7. Double-click on the `start.bat` file. (Note: the `.bat` part of the file name might be hidden by your OS, in that case, it will look like a file called "`Start`". This is what you double-click to run SillyTavern) + 8. After double-clicking, a large black command console window should open and SillyTavern will begin to install what it needs to operate. + 9. After the installation process, if everything is working, the command console window should look like this and a SillyTavern tab should be open in your browser: + 10. Connect to any of the [supported APIs](https://docs.sillytavern.app/usage/api-connections/) and start chatting! -### Linux +## 🐧 Linux & 🍎 MacOS -#### Unofficial Debian/Ubuntu PKGBUILD +For MacOS / Linux all of these will be done in a Terminal. -> **This installation method is unofficial and not supported by the project. Report any issues to the PKGBUILD maintainer.** -> The method is intended for Debian-based distributions (Ubuntu, Mint, etc). +1. Install git and nodeJS (the method for doing this will vary depending on your OS) +2. Clone the repo -1. Install [makedeb](https://www.makedeb.org/). -2. Ensure you have Node.js v18 or higher installed by running `node -v`. If you need to upgrade, you can install a [node.js repo](https://mpr.makedeb.org/packages/nodejs-repo) (you'll might need to edit the version inside the PKGBUILD). As an alternative, install and configure [nvm](https://mpr.makedeb.org/packages/nvm) to manage multiple node.js installations. Finally, you can [install node.js manually](https://nodejs.org/en/download), but you will need to update the PATH variable of your environment. -3. Now build the [sillytavern package](https://mpr.makedeb.org/packages/sillytavern). The build needs to run with the correct node.js version. +- for Release Branch: `git clone https://github.com/SillyTavern/SillyTavern -b release` +- for Staging Branch: `git clone https://github.com/SillyTavern/SillyTavern -b staging` -#### Manual +3. `cd SillyTavern` to navigate into the install folder. +4. Run the `start.sh` script with one of these commands: + +- `./start.sh` +- `bash start.sh` + +## Installing via SillyTavern Launcher + +### For Linux users +1. Open your favorite terminal and install git +2. Download Sillytavern Launcher with: `git clone https://github.com/SillyTavern/SillyTavern-Launcher.git` +3. Navigate to the SillyTavern-Launcher with: `cd SillyTavern-Launcher` +4. Start the install launcher with: `chmod +x install.sh && ./install.sh` and choose what you wanna install +5. After installation start the launcher with: `chmod +x launcher.sh && ./launcher.sh` + +### For Mac users +1. Open a terminal and install brew with: `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"` +2. Then install git with: `brew install git` +3. Download Sillytavern Launcher with: `git clone https://github.com/SillyTavern/SillyTavern-Launcher.git` +4. Navigate to the SillyTavern-Launcher with: `cd SillyTavern-Launcher` +5. Start the install launcher with: `chmod +x install.sh && ./install.sh` and choose what you wanna install +6. After installation start the launcher with: `chmod +x launcher.sh && ./launcher.sh` + +## 📱 Mobile - Installing via termux + +> \[!NOTE] +> **SillyTavern can be run natively on Android phones using Termux. Please refer to this guide by ArroganceComplex#2659:** +> * - 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. ## API keys management @@ -222,7 +245,7 @@ or CIDR masks are also accepted (eg. 10.0.0.0/24). * Save the `whitelist.txt` file. -* Restart your TAI server. +* Restart your ST server. Now devices which have the IP specified in the file will be able to connect. @@ -293,10 +316,7 @@ You can find them archived here: -## Screenshots -image -image ## License and credits @@ -327,3 +347,10 @@ GNU Affero General Public License for more details.** * Korean translation by @doloroushyeonse * k_euler_a support for Horde by * Chinese translation by [@XXpE3](https://github.com/XXpE3), 中文 ISSUES 可以联系 @XXpE3 + + +[back-to-top]: https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square +[cover]: https://github.com/SillyTavern/SillyTavern/assets/18619528/c2be4c3f-aada-4f64-87a3-ae35a68b61a4 +[discord-link]: https://discord.gg/sillytavern +[discord-shield]: https://img.shields.io/discord/1127171173982154893?color=5865F2&label=discord&labelColor=black&logo=discord&logoColor=white&style=flat-square +[discord-shield-badge]: https://img.shields.io/discord/1127171173982154893?color=5865F2&label=discord&labelColor=black&logo=discord&logoColor=white&style=for-the-badge From 9d87b233e1cdc357d7b3d221e9d5c11332a3e9ee Mon Sep 17 00:00:00 2001 From: deffcolony <61471128+deffcolony@users.noreply.github.com> Date: Wed, 3 Apr 2024 19:24:52 +0200 Subject: [PATCH 04/80] Update readme.md --- .github/readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/readme.md b/.github/readme.md index 4032431d4..2504eec35 100644 --- a/.github/readme.md +++ b/.github/readme.md @@ -352,5 +352,5 @@ GNU Affero General Public License for more details.** [back-to-top]: https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square [cover]: https://github.com/SillyTavern/SillyTavern/assets/18619528/c2be4c3f-aada-4f64-87a3-ae35a68b61a4 [discord-link]: https://discord.gg/sillytavern -[discord-shield]: https://img.shields.io/discord/1127171173982154893?color=5865F2&label=discord&labelColor=black&logo=discord&logoColor=white&style=flat-square -[discord-shield-badge]: https://img.shields.io/discord/1127171173982154893?color=5865F2&label=discord&labelColor=black&logo=discord&logoColor=white&style=for-the-badge +[discord-shield]: https://img.shields.io/discord/1100685673633153084?color=5865F2&label=discord&labelColor=black&logo=discord&logoColor=white&style=flat-square +[discord-shield-badge]: https://img.shields.io/discord/1100685673633153084?color=5865F2&label=discord&labelColor=black&logo=discord&logoColor=white&style=for-the-badge From 95c910a5218f69609616d24cf00c22e127569218 Mon Sep 17 00:00:00 2001 From: Aisu Wata Date: Thu, 4 Apr 2024 02:56:39 -0300 Subject: [PATCH 05/80] fix: WI min activations skips seen buffer --- public/scripts/world-info.js | 50 +++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 12 deletions(-) diff --git a/public/scripts/world-info.js b/public/scripts/world-info.js index 29582a1f0..ca632fc6d 100644 --- a/public/scripts/world-info.js +++ b/public/scripts/world-info.js @@ -95,6 +95,11 @@ class WorldInfoBuffer { */ #skew = 0; + /** + * @type {number} The starting depth of the global scan depth. Incremented by "min activations" feature to not repeat scans. When > 0 it means a complete scan was done up to #startDepth already, and `advanceScanPosition` was called. + */ + #startDepth = 0; + /** * Initialize the buffer with the given messages. * @param {string[]} messages Array of messages to add to the buffer @@ -137,7 +142,10 @@ class WorldInfoBuffer { * @returns {string} A slice of buffer until the given depth (inclusive) */ get(entry) { - let depth = entry.scanDepth ?? (world_info_depth + this.#skew); + let depth = entry.scanDepth ?? this.getDepth(); + if (depth <= this.#startDepth) { + return ''; + } if (depth < 0) { console.error(`Invalid WI scan depth ${depth}. Must be >= 0`); @@ -149,7 +157,7 @@ class WorldInfoBuffer { depth = MAX_SCAN_DEPTH; } - let result = this.#depthBuffer.slice(0, depth).join('\n'); + let result = this.#depthBuffer.slice(this.#startDepth, depth).join('\n'); if (this.#recurseBuffer.length > 0) { result += '\n' + this.#recurseBuffer.join('\n'); @@ -197,11 +205,26 @@ class WorldInfoBuffer { } /** - * Adds an increment to depth skew. + * Empties recursion buffer. */ - addSkew() { + recurseReset() { + this.#recurseBuffer = []; + } + + /** + * Increments skew and sets startDepth to previous depth. + */ + advanceScanPosition() { + this.#startDepth = this.getDepth(); this.#skew++; } + + /** + * @returns {number} Settings' depth + current skew. + */ + getDepth() { + return world_info_depth + this.#skew; + } } export function getWorldInfoSettings() { @@ -2009,7 +2032,6 @@ async function checkWorldInfo(chat, maxContext) { const buffer = new WorldInfoBuffer(chat); // Combine the chat - let minActivationMsgIndex = world_info_depth; // tracks chat index to satisfy `world_info_min_activations` // Add the depth or AN if enabled // Put this code here since otherwise, the chat reference is modified @@ -2214,6 +2236,9 @@ async function checkWorldInfo(chat, maxContext) { } if (needsToScan) { + // If you're here from a previous loop, clear recurse buffer + buffer.recurseReset(); + const text = newEntries .filter(x => !failedProbabilityChecks.has(x)) .filter(x => !x.preventRecursion) @@ -2225,15 +2250,16 @@ async function checkWorldInfo(chat, maxContext) { // world_info_min_activations if (!needsToScan && !token_budget_overflowed) { if (world_info_min_activations > 0 && (allActivatedEntries.size < world_info_min_activations)) { - let over_max = false; - over_max = ( + let over_max = ( world_info_min_activations_depth_max > 0 && - minActivationMsgIndex > world_info_min_activations_depth_max - ) || (minActivationMsgIndex >= chat.length); + buffer.getDepth() > world_info_min_activations_depth_max + ) || (buffer.getDepth() > chat.length); + if (!over_max) { - needsToScan = true; - minActivationMsgIndex += 1; - buffer.addSkew(); + needsToScan = true; // loop + buffer.advanceScanPosition(); + // No recurse was added, since `!needsToScan`, but clear previous one since it was checked already + buffer.recurseReset(); } } } From 5ab9d9b8630adba9488e5f91b00ff322efa8e202 Mon Sep 17 00:00:00 2001 From: Aisu Wata Date: Thu, 4 Apr 2024 03:08:17 -0300 Subject: [PATCH 06/80] removed some remnant debug logs --- public/script.js | 1 - public/scripts/world-info.js | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/public/script.js b/public/script.js index 76a0b4d91..a5207c022 100644 --- a/public/script.js +++ b/public/script.js @@ -752,7 +752,6 @@ function reloadMarkdownProcessor(render_formulas = false) { } function getCurrentChatId() { - console.debug(`selectedGroup:${selected_group}, this_chid:${this_chid}`); if (selected_group) { return groups.find(x => x.id == selected_group)?.chat_id; } diff --git a/public/scripts/world-info.js b/public/scripts/world-info.js index 29582a1f0..4f9bd733f 100644 --- a/public/scripts/world-info.js +++ b/public/scripts/world-info.js @@ -2102,8 +2102,6 @@ async function checkWorldInfo(chat, maxContext) { const substituted = substituteParams(key); const textToScan = buffer.get(entry); - console.debug(`${entry.uid}: ${substituted}`); - if (substituted && buffer.matchKeys(textToScan, substituted.trim(), entry)) { console.debug(`WI UID ${entry.uid} found by primary match: ${substituted}.`); @@ -2160,7 +2158,7 @@ async function checkWorldInfo(chat, maxContext) { activatedNow.add(entry); break primary; } - } else { console.debug(`No active entries for logic checks for word: ${substituted}.`); } + } } } } From 0d57f7ea4fb5ff505631d6194c48efc0b9ea195b Mon Sep 17 00:00:00 2001 From: Aisu Wata Date: Thu, 4 Apr 2024 15:19:39 -0300 Subject: [PATCH 07/80] fix: removed `recurseReset()` --- public/scripts/world-info.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/public/scripts/world-info.js b/public/scripts/world-info.js index ca632fc6d..4acdb225f 100644 --- a/public/scripts/world-info.js +++ b/public/scripts/world-info.js @@ -204,13 +204,6 @@ class WorldInfoBuffer { this.#recurseBuffer.push(message); } - /** - * Empties recursion buffer. - */ - recurseReset() { - this.#recurseBuffer = []; - } - /** * Increments skew and sets startDepth to previous depth. */ @@ -2236,9 +2229,6 @@ async function checkWorldInfo(chat, maxContext) { } if (needsToScan) { - // If you're here from a previous loop, clear recurse buffer - buffer.recurseReset(); - const text = newEntries .filter(x => !failedProbabilityChecks.has(x)) .filter(x => !x.preventRecursion) @@ -2258,8 +2248,6 @@ async function checkWorldInfo(chat, maxContext) { if (!over_max) { needsToScan = true; // loop buffer.advanceScanPosition(); - // No recurse was added, since `!needsToScan`, but clear previous one since it was checked already - buffer.recurseReset(); } } } From 813476d72ac9aefb45b42aed7855c77e5d1c42ac Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Thu, 4 Apr 2024 21:20:10 +0300 Subject: [PATCH 08/80] Fix stream error parsing when using Smooth Streaming --- public/scripts/sse-stream.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/public/scripts/sse-stream.js b/public/scripts/sse-stream.js index cc3160039..8e6a16f7c 100644 --- a/public/scripts/sse-stream.js +++ b/public/scripts/sse-stream.js @@ -227,7 +227,7 @@ async function* parseStreamData(json) { } } - return null; + yield null; } /** @@ -243,6 +243,12 @@ export class SmoothEventSourceStream extends EventSourceStream { const data = event.data; try { const hasFocus = document.hasFocus(); + + if (data === '[DONE]') { + lastStr = ''; + return controller.enqueue(event); + } + const json = JSON.parse(data); if (!json) { @@ -261,7 +267,8 @@ export class SmoothEventSourceStream extends EventSourceStream { lastStr = parsed.chunk; hasFocus && await eventSource.emit(event_types.SMOOTH_STREAM_TOKEN_RECEIVED, parsed.chunk); } - } catch { + } catch (error) { + console.error('Smooth Streaming parsing error', error); controller.enqueue(event); } }, From ee3718ad7a82bba6b6bddb5a0c9ce5167b945217 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Thu, 4 Apr 2024 21:20:30 +0300 Subject: [PATCH 09/80] Forward error messages from Cohere streams --- src/endpoints/backends/chat-completions.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/endpoints/backends/chat-completions.js b/src/endpoints/backends/chat-completions.js index 8fe7cb6bf..593f034b2 100644 --- a/src/endpoints/backends/chat-completions.js +++ b/src/endpoints/backends/chat-completions.js @@ -36,7 +36,13 @@ async function parseCohereStream(jsonStream, request, response) { } catch (e) { break; } - if (json.event_type === 'text-generation') { + if (json.message) { + const message = json.message || 'Unknown error'; + const chunk = { error: { message: message } }; + response.write(`data: ${JSON.stringify(chunk)}\n\n`); + partialData = ''; + break; + } else if (json.event_type === 'text-generation') { const text = json.text || ''; const chunk = { choices: [{ text }] }; response.write(`data: ${JSON.stringify(chunk)}\n\n`); From 6cc73c2a0ba7104421c62b295c70b0dc110ed2c6 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Thu, 4 Apr 2024 22:27:08 +0300 Subject: [PATCH 10/80] Add instruct last system sequence --- .../content/presets/instruct/Adventure.json | 3 +- .../presets/instruct/Alpaca-Roleplay.json | 1 + .../presets/instruct/Alpaca-Single-Turn.json | 3 +- default/content/presets/instruct/Alpaca.json | 1 + default/content/presets/instruct/ChatML.json | 1 + .../instruct/DreamGen Role-Play V1.json | 3 +- default/content/presets/instruct/Koala.json | 3 +- .../content/presets/instruct/Libra-32B.json | 3 +- .../presets/instruct/Lightning 1.1.json | 3 +- .../presets/instruct/Llama 2 Chat.json | 1 + .../content/presets/instruct/Metharme.json | 3 +- default/content/presets/instruct/Mistral.json | 1 + .../presets/instruct/OpenOrca-OpenChat.json | 3 +- .../content/presets/instruct/Pygmalion.json | 3 +- default/content/presets/instruct/Story.json | 3 +- default/content/presets/instruct/Synthia.json | 1 + .../content/presets/instruct/Vicuna 1.0.json | 3 +- .../content/presets/instruct/Vicuna 1.1.json | 3 +- .../presets/instruct/WizardLM-13B.json | 3 +- .../content/presets/instruct/WizardLM.json | 3 +- .../instruct/simple-proxy-for-tavern.json | 3 +- public/index.html | 18 ++++++-- public/scripts/instruct-mode.js | 44 ++++++++++++------- public/scripts/power-user.js | 1 + public/scripts/templates/macros.html | 1 + 25 files changed, 80 insertions(+), 35 deletions(-) diff --git a/default/content/presets/instruct/Adventure.json b/default/content/presets/instruct/Adventure.json index 827c6d5c9..a4093dff7 100644 --- a/default/content/presets/instruct/Adventure.json +++ b/default/content/presets/instruct/Adventure.json @@ -19,5 +19,6 @@ "system_suffix": "", "user_alignment_message": "", "system_same_as_user": false, + "last_system_sequence": "", "name": "Adventure" -} \ No newline at end of file +} diff --git a/default/content/presets/instruct/Alpaca-Roleplay.json b/default/content/presets/instruct/Alpaca-Roleplay.json index 5a5054340..b5aec1c92 100644 --- a/default/content/presets/instruct/Alpaca-Roleplay.json +++ b/default/content/presets/instruct/Alpaca-Roleplay.json @@ -19,5 +19,6 @@ "system_suffix": "\n\n", "user_alignment_message": "", "system_same_as_user": false, + "last_system_sequence": "", "name": "Alpaca-Roleplay" } diff --git a/default/content/presets/instruct/Alpaca-Single-Turn.json b/default/content/presets/instruct/Alpaca-Single-Turn.json index 6a6f052d2..9baca108c 100644 --- a/default/content/presets/instruct/Alpaca-Single-Turn.json +++ b/default/content/presets/instruct/Alpaca-Single-Turn.json @@ -19,5 +19,6 @@ "system_suffix": "", "user_alignment_message": "", "system_same_as_user": false, + "last_system_sequence": "", "name": "Alpaca-Single-Turn" -} \ No newline at end of file +} diff --git a/default/content/presets/instruct/Alpaca.json b/default/content/presets/instruct/Alpaca.json index 96fd2cc83..28b2065fb 100644 --- a/default/content/presets/instruct/Alpaca.json +++ b/default/content/presets/instruct/Alpaca.json @@ -19,5 +19,6 @@ "system_suffix": "\n\n", "user_alignment_message": "", "system_same_as_user": false, + "last_system_sequence": "", "name": "Alpaca" } diff --git a/default/content/presets/instruct/ChatML.json b/default/content/presets/instruct/ChatML.json index 348ae2458..513a72820 100644 --- a/default/content/presets/instruct/ChatML.json +++ b/default/content/presets/instruct/ChatML.json @@ -19,5 +19,6 @@ "system_suffix": "<|im_end|>\n", "user_alignment_message": "", "system_same_as_user": false, + "last_system_sequence": "", "name": "ChatML" } diff --git a/default/content/presets/instruct/DreamGen Role-Play V1.json b/default/content/presets/instruct/DreamGen Role-Play V1.json index 07f0301fc..002878b4d 100644 --- a/default/content/presets/instruct/DreamGen Role-Play V1.json +++ b/default/content/presets/instruct/DreamGen Role-Play V1.json @@ -19,5 +19,6 @@ "system_suffix": "", "user_alignment_message": "", "system_same_as_user": true, + "last_system_sequence": "", "name": "DreamGen Role-Play V1" -} \ No newline at end of file +} diff --git a/default/content/presets/instruct/Koala.json b/default/content/presets/instruct/Koala.json index 980482c1a..f5db8ff48 100644 --- a/default/content/presets/instruct/Koala.json +++ b/default/content/presets/instruct/Koala.json @@ -19,5 +19,6 @@ "system_suffix": "", "user_alignment_message": "", "system_same_as_user": true, + "last_system_sequence": "", "name": "Koala" -} \ No newline at end of file +} diff --git a/default/content/presets/instruct/Libra-32B.json b/default/content/presets/instruct/Libra-32B.json index 6014546f6..c665eb364 100644 --- a/default/content/presets/instruct/Libra-32B.json +++ b/default/content/presets/instruct/Libra-32B.json @@ -19,5 +19,6 @@ "system_suffix": "", "user_alignment_message": "", "system_same_as_user": false, + "last_system_sequence": "", "name": "Libra-32B" -} \ No newline at end of file +} diff --git a/default/content/presets/instruct/Lightning 1.1.json b/default/content/presets/instruct/Lightning 1.1.json index bf79e1358..9f9bd7ccf 100644 --- a/default/content/presets/instruct/Lightning 1.1.json +++ b/default/content/presets/instruct/Lightning 1.1.json @@ -19,5 +19,6 @@ "system_suffix": "", "user_alignment_message": "", "system_same_as_user": true, + "last_system_sequence": "", "name": "Lightning 1.1" -} \ No newline at end of file +} diff --git a/default/content/presets/instruct/Llama 2 Chat.json b/default/content/presets/instruct/Llama 2 Chat.json index aeb4e13fd..dc507b777 100644 --- a/default/content/presets/instruct/Llama 2 Chat.json +++ b/default/content/presets/instruct/Llama 2 Chat.json @@ -19,5 +19,6 @@ "system_suffix": "", "user_alignment_message": "Let's get started. Please respond based on the information and instructions provided above.", "system_same_as_user": true, + "last_system_sequence": "", "name": "Llama 2 Chat" } diff --git a/default/content/presets/instruct/Metharme.json b/default/content/presets/instruct/Metharme.json index 1c8474cdf..195fe5260 100644 --- a/default/content/presets/instruct/Metharme.json +++ b/default/content/presets/instruct/Metharme.json @@ -19,5 +19,6 @@ "system_suffix": "", "user_alignment_message": "", "system_same_as_user": true, + "last_system_sequence": "", "name": "Metharme" -} \ No newline at end of file +} diff --git a/default/content/presets/instruct/Mistral.json b/default/content/presets/instruct/Mistral.json index 4f35139fa..bd3a9ff3c 100644 --- a/default/content/presets/instruct/Mistral.json +++ b/default/content/presets/instruct/Mistral.json @@ -19,5 +19,6 @@ "system_suffix": "", "user_alignment_message": "Let's get started. Please respond based on the information and instructions provided above.", "system_same_as_user": true, + "last_system_sequence": "", "name": "Mistral" } diff --git a/default/content/presets/instruct/OpenOrca-OpenChat.json b/default/content/presets/instruct/OpenOrca-OpenChat.json index 924ea94f7..04d526d4d 100644 --- a/default/content/presets/instruct/OpenOrca-OpenChat.json +++ b/default/content/presets/instruct/OpenOrca-OpenChat.json @@ -19,5 +19,6 @@ "system_suffix": "", "user_alignment_message": "", "system_same_as_user": false, + "last_system_sequence": "", "name": "OpenOrca-OpenChat" -} \ No newline at end of file +} diff --git a/default/content/presets/instruct/Pygmalion.json b/default/content/presets/instruct/Pygmalion.json index 6278c0d23..cb5b60d8a 100644 --- a/default/content/presets/instruct/Pygmalion.json +++ b/default/content/presets/instruct/Pygmalion.json @@ -19,5 +19,6 @@ "system_suffix": "", "user_alignment_message": "", "system_same_as_user": true, + "last_system_sequence": "", "name": "Pygmalion" -} \ No newline at end of file +} diff --git a/default/content/presets/instruct/Story.json b/default/content/presets/instruct/Story.json index 1e42d3281..5c6b00cf0 100644 --- a/default/content/presets/instruct/Story.json +++ b/default/content/presets/instruct/Story.json @@ -19,5 +19,6 @@ "system_suffix": "", "user_alignment_message": "", "system_same_as_user": false, + "last_system_sequence": "", "name": "Story" -} \ No newline at end of file +} diff --git a/default/content/presets/instruct/Synthia.json b/default/content/presets/instruct/Synthia.json index 24ec4849e..21fa535c0 100644 --- a/default/content/presets/instruct/Synthia.json +++ b/default/content/presets/instruct/Synthia.json @@ -19,5 +19,6 @@ "system_suffix": "\n", "user_alignment_message": "Let's get started. Please respond based on the information and instructions provided above.", "system_same_as_user": false, + "last_system_sequence": "", "name": "Synthia" } diff --git a/default/content/presets/instruct/Vicuna 1.0.json b/default/content/presets/instruct/Vicuna 1.0.json index fbc8a2bf5..d96bf4cb2 100644 --- a/default/content/presets/instruct/Vicuna 1.0.json +++ b/default/content/presets/instruct/Vicuna 1.0.json @@ -19,5 +19,6 @@ "system_suffix": "", "user_alignment_message": "", "system_same_as_user": true, + "last_system_sequence": "", "name": "Vicuna 1.0" -} \ No newline at end of file +} diff --git a/default/content/presets/instruct/Vicuna 1.1.json b/default/content/presets/instruct/Vicuna 1.1.json index a31698d03..a42e4fbfc 100644 --- a/default/content/presets/instruct/Vicuna 1.1.json +++ b/default/content/presets/instruct/Vicuna 1.1.json @@ -19,5 +19,6 @@ "system_suffix": "", "user_alignment_message": "", "system_same_as_user": true, + "last_system_sequence": "", "name": "Vicuna 1.1" -} \ No newline at end of file +} diff --git a/default/content/presets/instruct/WizardLM-13B.json b/default/content/presets/instruct/WizardLM-13B.json index 21e7bd555..b15fea56f 100644 --- a/default/content/presets/instruct/WizardLM-13B.json +++ b/default/content/presets/instruct/WizardLM-13B.json @@ -19,5 +19,6 @@ "system_suffix": "", "user_alignment_message": "", "system_same_as_user": true, + "last_system_sequence": "", "name": "WizardLM-13B" -} \ No newline at end of file +} diff --git a/default/content/presets/instruct/WizardLM.json b/default/content/presets/instruct/WizardLM.json index 198f6a062..18e808da4 100644 --- a/default/content/presets/instruct/WizardLM.json +++ b/default/content/presets/instruct/WizardLM.json @@ -19,5 +19,6 @@ "system_suffix": "", "user_alignment_message": "", "system_same_as_user": false, + "last_system_sequence": "", "name": "WizardLM" -} \ No newline at end of file +} diff --git a/default/content/presets/instruct/simple-proxy-for-tavern.json b/default/content/presets/instruct/simple-proxy-for-tavern.json index 14d32d86c..986da1697 100644 --- a/default/content/presets/instruct/simple-proxy-for-tavern.json +++ b/default/content/presets/instruct/simple-proxy-for-tavern.json @@ -19,5 +19,6 @@ "system_suffix": "", "user_alignment_message": "", "system_same_as_user": false, + "last_system_sequence": "", "name": "simple-proxy-for-tavern" -} \ No newline at end of file +} diff --git a/public/index.html b/public/index.html index f4b1586c7..034942754 100644 --- a/public/index.html +++ b/public/index.html @@ -3034,12 +3034,12 @@
-
-
+
+
+ +
+ +
+
+
diff --git a/public/scripts/instruct-mode.js b/public/scripts/instruct-mode.js index d90ffb9bc..7fc924274 100644 --- a/public/scripts/instruct-mode.js +++ b/public/scripts/instruct-mode.js @@ -26,6 +26,7 @@ const controls = [ { id: 'instruct_output_suffix', property: 'output_suffix', isCheckbox: false }, { id: 'instruct_system_sequence', property: 'system_sequence', isCheckbox: false }, { id: 'instruct_system_suffix', property: 'system_suffix', isCheckbox: false }, + { id: 'instruct_last_system_sequence', property: 'last_system_sequence', isCheckbox: false }, { id: 'instruct_user_alignment_message', property: 'user_alignment_message', isCheckbox: false }, { id: 'instruct_stop_sequence', property: 'stop_sequence', isCheckbox: false }, { id: 'instruct_names', property: 'names', isCheckbox: true }, @@ -56,6 +57,7 @@ function migrateInstructModeSettings(settings) { system_sequence: '', system_suffix: '', user_alignment_message: '', + last_system_sequence: '', names_force_groups: true, skip_examples: false, system_same_as_user: false, @@ -249,8 +251,9 @@ export function getInstructStoppingSequences() { const first_output_sequence = power_user.instruct.first_output_sequence?.replace(/{{name}}/gi, name2) || ''; const last_output_sequence = power_user.instruct.last_output_sequence?.replace(/{{name}}/gi, name2) || ''; const system_sequence = power_user.instruct.system_sequence?.replace(/{{name}}/gi, 'System') || ''; + const last_system_sequence = power_user.instruct.last_system_sequence?.replace(/{{name}}/gi, 'System') || ''; - const combined_sequence = `${stop_sequence}\n${input_sequence}\n${output_sequence}\n${first_output_sequence}\n${last_output_sequence}\n${system_sequence}`; + const combined_sequence = `${stop_sequence}\n${input_sequence}\n${output_sequence}\n${first_output_sequence}\n${last_output_sequence}\n${system_sequence}\n${last_system_sequence}`; combined_sequence.split('\n').filter((line, index, self) => self.indexOf(line) === index).forEach(addInstructSequence); } @@ -452,9 +455,10 @@ export function formatInstructModePrompt(name, isImpersonate, promptBias, name1, return power_user.instruct.input_sequence; } - // Neutral / system prompt + // Neutral / system / quiet prompt + // Use a special quiet instruct sequence if defined, or assistant's output sequence otherwise if (isQuiet && !isQuietToLoud) { - return power_user.instruct.output_sequence; + return power_user.instruct.last_system_sequence || power_user.instruct.output_sequence; } // Quiet in-character prompt @@ -517,20 +521,28 @@ export function replaceInstructMacros(input) { if (!input) { return ''; } + const instructMacros = { + 'instructSystem|instructSystemPrompt': power_user.instruct.system_prompt, + 'instructSystemPromptPrefix': power_user.instruct.system_sequence_prefix, + 'instructSystemPromptSuffix': power_user.instruct.system_sequence_suffix, + 'instructInput|instructUserPrefix': power_user.instruct.input_sequence, + 'instructUserSuffix': power_user.instruct.input_suffix, + 'instructOutput|instructAssistantPrefix': power_user.instruct.output_sequence, + 'instructSeparator|instructAssistantSuffix': power_user.instruct.output_suffix, + 'instructSystemPrefix': power_user.instruct.system_sequence, + 'instructSystemSuffix': power_user.instruct.system_suffix, + 'instructFirstOutput|instructFirstAssistantPrefix': power_user.instruct.first_output_sequence || power_user.instruct.output_sequence, + 'instructLastOutput|instructLastAssistantPrefix': power_user.instruct.last_output_sequence || power_user.instruct.output_sequence, + 'instructStop': power_user.instruct.stop_sequence, + 'instructUserFiller': power_user.instruct.user_alignment_message, + 'instructSystemInstructionPrefix': power_user.instruct.last_system_sequence, + }; + + for (const [placeholder, value] of Object.entries(instructMacros)) { + const regex = new RegExp(`{{(${placeholder})}}`, 'gi'); + input = input.replace(regex, power_user.instruct.enabled ? value : ''); + } - input = input.replace(/{{(instructSystem|instructSystemPrompt)}}/gi, power_user.instruct.enabled ? power_user.instruct.system_prompt : ''); - input = input.replace(/{{instructSystemPromptPrefix}}/gi, power_user.instruct.enabled ? power_user.instruct.system_sequence_prefix : ''); - input = input.replace(/{{instructSystemPromptSuffix}}/gi, power_user.instruct.enabled ? power_user.instruct.system_sequence_suffix : ''); - input = input.replace(/{{(instructInput|instructUserPrefix)}}/gi, power_user.instruct.enabled ? power_user.instruct.input_sequence : ''); - input = input.replace(/{{instructUserSuffix}}/gi, power_user.instruct.enabled ? power_user.instruct.input_suffix : ''); - input = input.replace(/{{(instructOutput|instructAssistantPrefix)}}/gi, power_user.instruct.enabled ? power_user.instruct.output_sequence : ''); - input = input.replace(/{{(instructSeparator|instructAssistantSuffix)}}/gi, power_user.instruct.enabled ? power_user.instruct.output_suffix : ''); - input = input.replace(/{{instructSystemPrefix}}/gi, power_user.instruct.enabled ? power_user.instruct.system_sequence : ''); - input = input.replace(/{{instructSystemSuffix}}/gi, power_user.instruct.enabled ? power_user.instruct.system_suffix : ''); - input = input.replace(/{{(instructFirstOutput|instructFirstAssistantPrefix)}}/gi, power_user.instruct.enabled ? (power_user.instruct.first_output_sequence || power_user.instruct.output_sequence) : ''); - input = input.replace(/{{(instructLastOutput|instructLastAssistantPrefix)}}/gi, power_user.instruct.enabled ? (power_user.instruct.last_output_sequence || power_user.instruct.output_sequence) : ''); - input = input.replace(/{{instructStop}}/gi, power_user.instruct.enabled ? power_user.instruct.stop_sequence : ''); - input = input.replace(/{{instructUserFiller}}/gi, power_user.instruct.enabled ? power_user.instruct.user_alignment_message : ''); input = input.replace(/{{exampleSeparator}}/gi, power_user.context.example_separator); input = input.replace(/{{chatStart}}/gi, power_user.context.chat_start); diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js index bc9cb0ca6..6ef84bbf6 100644 --- a/public/scripts/power-user.js +++ b/public/scripts/power-user.js @@ -204,6 +204,7 @@ let power_user = { output_suffix: '', system_sequence: '', system_suffix: '', + last_system_sequence: '', first_output_sequence: '', last_output_sequence: '', system_sequence_prefix: '', diff --git a/public/scripts/templates/macros.html b/public/scripts/templates/macros.html index a0f18ab9d..f3291333f 100644 --- a/public/scripts/templates/macros.html +++ b/public/scripts/templates/macros.html @@ -60,6 +60,7 @@
  • {{instructLastAssistantPrefix}} – instruct assistant last output sequence
  • {{instructSystemPrefix}} – instruct system message prefix sequence
  • {{instructSystemSuffix}} – instruct system message suffix sequence
  • +
  • {{instructSystemInstructionPrefix}} – instruct system instruction prefix
  • {{instructUserFiller}} – instruct first user message filler
  • {{instructStop}} – instruct stop sequence
  • From 080484380574a1f83c7fec4eec840bebf75c6953 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Fri, 5 Apr 2024 00:39:54 +0300 Subject: [PATCH 11/80] Add per-character and per-group overrides for external media --- public/index.html | 51 +++++++++++++++++++--- public/script.js | 38 ++++++++++++++-- public/scripts/chats.js | 82 +++++++++++++++++++++++++++++++++++ public/scripts/group-chats.js | 12 ++++- public/scripts/power-user.js | 31 +++++++++---- 5 files changed, 196 insertions(+), 18 deletions(-) diff --git a/public/index.html b/public/index.html index 034942754..674403e6e 100644 --- a/public/index.html +++ b/public/index.html @@ -4268,12 +4268,21 @@

    -
    - Description - - - - +
    +
    + Description + + + + +
    +
    @@ -4373,6 +4382,10 @@ + @@ -5380,6 +5393,32 @@
    +
    +
    +

    + Ability of the current character/group to use external media in chats. +

    + + Media: images, videos, audio. External: not hosted on the local server. + + + + +
    +
    CHAR is typing
    diff --git a/public/script.js b/public/script.js index a5207c022..76335794c 100644 --- a/public/script.js +++ b/public/script.js @@ -208,7 +208,7 @@ import { getBackgrounds, initBackgrounds, loadBackgroundSettings, background_set import { hideLoader, showLoader } from './scripts/loader.js'; import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js'; import { loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadAphroditeModels, loadDreamGenModels } from './scripts/textgen-models.js'; -import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags } from './scripts/chats.js'; +import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId } from './scripts/chats.js'; import { initPresetManager } from './scripts/preset-manager.js'; import { evaluateMacros } from './scripts/macros.js'; @@ -324,10 +324,13 @@ DOMPurify.addHook('uponSanitizeElement', (node, _, config) => { return; } - if (!power_user.forbid_external_images) { + const isMediaAllowed = isExternalMediaAllowed(); + if (isMediaAllowed) { return; } + let mediaBlocked = false; + switch (node.tagName) { case 'AUDIO': case 'VIDEO': @@ -350,6 +353,7 @@ DOMPurify.addHook('uponSanitizeElement', (node, _, config) => { if (isExternalUrl(url)) { console.warn('External media blocked', url); node.remove(); + mediaBlocked = true; break; } } @@ -357,16 +361,37 @@ DOMPurify.addHook('uponSanitizeElement', (node, _, config) => { if (src && isExternalUrl(src)) { console.warn('External media blocked', src); + mediaBlocked = true; node.remove(); } if (data && isExternalUrl(data)) { console.warn('External media blocked', data); + mediaBlocked = true; node.remove(); } } break; } + + if (mediaBlocked) { + const entityId = getCurrentEntityId(); + const warningShownKey = `mediaWarningShown:${entityId}`; + + if (localStorage.getItem(warningShownKey) === null) { + const warningToast = toastr.warning( + 'Use the "Ext. Media" button to allow it. Click on this message to dismiss.', + 'External media has been blocked', + { + timeOut: 0, + preventDuplicates: true, + onclick: () => toastr.clear(warningToast), + }, + ); + + localStorage.setItem(warningShownKey, 'true'); + } + } }); // API OBJECT FOR EXTERNAL WIRING @@ -1692,7 +1717,7 @@ export async function reloadCurrentChat() { chat.length = 0; if (selected_group) { - await getGroupChat(selected_group); + await getGroupChat(selected_group, true); } else if (this_chid) { await getChat(); @@ -6899,6 +6924,12 @@ export function select_selected_character(chid) { $('#form_create').attr('actiontype', 'editcharacter'); $('.form_create_bottom_buttons_block .chat_lorebook_button').show(); + + const externalMediaState = isExternalMediaAllowed(); + $('#character_open_media_overrides').toggle(!selected_group); + $('#character_media_allowed_icon').toggle(externalMediaState); + $('#character_media_forbidden_icon').toggle(!externalMediaState); + saveSettingsDebounced(); } @@ -6959,6 +6990,7 @@ function select_rm_create() { $('#form_create').attr('actiontype', 'createcharacter'); $('.form_create_bottom_buttons_block .chat_lorebook_button').hide(); + $('#character_open_media_overrides').hide(); } function select_rm_characters() { diff --git a/public/scripts/chats.js b/public/scripts/chats.js index 847915fc4..41e54fd48 100644 --- a/public/scripts/chats.js +++ b/public/scripts/chats.js @@ -5,6 +5,7 @@ import { addCopyToCodeBlocks, appendMediaToMessage, callPopup, + characters, chat, eventSource, event_types, @@ -12,9 +13,14 @@ import { getRequestHeaders, hideSwipeButtons, name2, + reloadCurrentChat, saveChatDebounced, + saveSettingsDebounced, showSwipeButtons, + this_chid, } from '../script.js'; +import { selected_group } from './group-chats.js'; +import { power_user } from './power-user.js'; import { extractTextFromHTML, extractTextFromMarkdown, @@ -416,6 +422,56 @@ export function decodeStyleTags(text) { }); } +async function openExternalMediaOverridesDialog() { + const entityId = getCurrentEntityId(); + + if (!entityId) { + toastr.info('No character or group selected'); + return; + } + + const template = $('#forbid_media_override_template > .forbid_media_override').clone(); + template.find('.forbid_media_global_state_forbidden').toggle(power_user.forbid_external_images); + template.find('.forbid_media_global_state_allowed').toggle(!power_user.forbid_external_images); + + if (power_user.external_media_allowed_overrides.includes(entityId)) { + template.find('#forbid_media_override_allowed').prop('checked', true); + } + else if (power_user.external_media_forbidden_overrides.includes(entityId)) { + template.find('#forbid_media_override_forbidden').prop('checked', true); + } + else { + template.find('#forbid_media_override_global').prop('checked', true); + } + + callPopup(template, 'text', '', { wide: false, large: false }); +} + +export function getCurrentEntityId() { + if (selected_group) { + return String(selected_group); + } + + return characters[this_chid]?.avatar ?? null; +} + +export function isExternalMediaAllowed() { + const entityId = getCurrentEntityId(); + if (!entityId) { + return !power_user.forbid_external_images; + } + + if (power_user.external_media_allowed_overrides.includes(entityId)) { + return true; + } + + if (power_user.external_media_forbidden_overrides.includes(entityId)) { + return false; + } + + return !power_user.forbid_external_images; +} + jQuery(function () { $(document).on('click', '.mes_hide', async function () { const messageBlock = $(this).closest('.mes'); @@ -511,6 +567,32 @@ jQuery(function () { $(this).closest('.mes').find('.mes_edit').trigger('click'); }); + $(document).on('click', '.open_media_overrides', openExternalMediaOverridesDialog); + $(document).on('input', '#forbid_media_override_allowed', function () { + const entityId = getCurrentEntityId(); + if (!entityId) return; + power_user.external_media_allowed_overrides.push(entityId); + power_user.external_media_forbidden_overrides = power_user.external_media_forbidden_overrides.filter((v) => v !== entityId); + saveSettingsDebounced(); + reloadCurrentChat(); + }); + $(document).on('input', '#forbid_media_override_forbidden', function () { + const entityId = getCurrentEntityId(); + if (!entityId) return; + power_user.external_media_forbidden_overrides.push(entityId); + power_user.external_media_allowed_overrides = power_user.external_media_allowed_overrides.filter((v) => v !== entityId); + saveSettingsDebounced(); + reloadCurrentChat(); + }); + $(document).on('input', '#forbid_media_override_global', function () { + const entityId = getCurrentEntityId(); + if (!entityId) return; + power_user.external_media_allowed_overrides = power_user.external_media_allowed_overrides.filter((v) => v !== entityId); + power_user.external_media_forbidden_overrides = power_user.external_media_forbidden_overrides.filter((v) => v !== entityId); + saveSettingsDebounced(); + reloadCurrentChat(); + }); + $('#file_form_input').on('change', onFileAttach); $('#file_form').on('reset', function () { $('#file_form').addClass('displayNone'); diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js index 592b7c2fd..412f52aaa 100644 --- a/public/scripts/group-chats.js +++ b/public/scripts/group-chats.js @@ -73,6 +73,7 @@ import { } from '../script.js'; import { printTagList, createTagMapFromList, applyTagsOnCharacterSelect, tag_map } from './tags.js'; import { FILTER_TYPES, FilterHelper } from './filters.js'; +import { isExternalMediaAllowed } from './chats.js'; export { selected_group, @@ -176,7 +177,7 @@ async function loadGroupChat(chatId) { return []; } -export async function getGroupChat(groupId) { +export async function getGroupChat(groupId, reload = false) { const group = groups.find((x) => x.id === groupId); const chat_id = group.chat_id; const data = await loadGroupChat(chat_id); @@ -216,6 +217,10 @@ export async function getGroupChat(groupId) { updateChatMetadata(metadata, true); } + if (reload) { + select_group_chats(groupId, true); + } + await eventSource.emit(event_types.CHAT_CHANGED, getCurrentChatId()); } @@ -1306,6 +1311,10 @@ function select_group_chats(groupId, skipAnimation) { $('#rm_group_delete').show(); $('#rm_group_scenario').show(); $('#group-metadata-controls .chat_lorebook_button').removeClass('disabled').prop('disabled', false); + $('#group_open_media_overrides').show(); + const isMediaAllowed = isExternalMediaAllowed(); + $('#group_media_allowed_icon').toggle(isMediaAllowed); + $('#group_media_forbidden_icon').toggle(!isMediaAllowed); } else { $('#rm_group_submit').show(); if ($('#groupAddMemberListToggle .inline-drawer-content').css('display') !== 'block') { @@ -1314,6 +1323,7 @@ function select_group_chats(groupId, skipAnimation) { $('#rm_group_delete').hide(); $('#rm_group_scenario').hide(); $('#group-metadata-controls .chat_lorebook_button').addClass('disabled').prop('disabled', true); + $('#group_open_media_overrides').hide(); } updateFavButtonState(group?.fav ?? false); diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js index 6ef84bbf6..7f7ea524a 100644 --- a/public/scripts/power-user.js +++ b/public/scripts/power-user.js @@ -255,6 +255,8 @@ let power_user = { auto_connect: false, auto_load_chat: false, forbid_external_images: false, + external_media_allowed_overrides: [], + external_media_forbidden_overrides: [], }; let themes = []; @@ -2761,22 +2763,35 @@ export function getCustomStoppingStrings(limit = undefined) { } $(document).ready(() => { + const adjustAutocompleteDebounced = debounce(() => { + $('.ui-autocomplete-input').each(function () { + const isOpen = $(this).autocomplete('widget')[0].style.display !== 'none'; + if (isOpen) { + $(this).autocomplete('search'); + } + }); + }); - $(window).on('resize', async () => { - if (isMobile()) { - return; - } - - //console.log('Window resized!'); + const reportZoomLevelDebounced = debounce(() => { const zoomLevel = Number(window.devicePixelRatio).toFixed(2); const winWidth = window.innerWidth; const winHeight = window.innerHeight; console.debug(`Zoom: ${zoomLevel}, X:${winWidth}, Y:${winHeight}`); + }); + + $(window).on('resize', async () => { + adjustAutocompleteDebounced(); + setHotswapsDebounced(); + + if (isMobile()) { + return; + } + + reportZoomLevelDebounced(); + if (Object.keys(power_user.movingUIState).length > 0) { resetMovablePanels('resize'); } - // Adjust layout and styling here - setHotswapsDebounced(); }); // Settings that go to settings.json From b948e31a89f15eee37cc2792cc4f0ef34f73b11e Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Fri, 5 Apr 2024 00:54:17 +0300 Subject: [PATCH 12/80] Remove tag debug logs if state unchanged --- public/scripts/tags.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/public/scripts/tags.js b/public/scripts/tags.js index ac2e722da..553a6132c 100644 --- a/public/scripts/tags.js +++ b/public/scripts/tags.js @@ -765,7 +765,9 @@ function toggleTagThreeState(element, { stateOverride = undefined, simulateClick element.toggleClass(FILTER_STATES[state].class, state === states[targetStateIndex]); }); - console.debug('toggle three-way filter from', states[currentStateIndex], 'to', states[targetStateIndex], 'on', element); + if (states[currentStateIndex] !== states[targetStateIndex]) { + console.debug('toggle three-way filter from', states[currentStateIndex], 'to', states[targetStateIndex], 'on', element); + } } From 144d115d6afc1208872f9855df26f8e5da92b6d7 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Fri, 5 Apr 2024 01:05:50 +0300 Subject: [PATCH 13/80] Fix position of dynamic pop-outs control bar --- public/style.css | 1 + 1 file changed, 1 insertion(+) diff --git a/public/style.css b/public/style.css index 711d4fcca..f6516beed 100644 --- a/public/style.css +++ b/public/style.css @@ -539,6 +539,7 @@ body.reduced-motion #bg_custom { margin-right: 5px; z-index: 2000; min-width: 55px; + justify-content: flex-end; } .panelControlBar .drag-grabber { From 7221549c65f51501ac4e11fce4373b05b6c372b4 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Fri, 5 Apr 2024 01:25:48 +0300 Subject: [PATCH 14/80] #2013 Fix smooth stream event processing. --- public/scripts/sse-stream.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/public/scripts/sse-stream.js b/public/scripts/sse-stream.js index 8e6a16f7c..9e335600d 100644 --- a/public/scripts/sse-stream.js +++ b/public/scripts/sse-stream.js @@ -106,7 +106,7 @@ function getDelay(s) { /** * Parses the stream data and returns the parsed data and the chunk to be sent. * @param {object} json The JSON data. - * @returns {AsyncGenerator<{data: object, chunk: string} | null>} The parsed data and the chunk to be sent. + * @returns {AsyncGenerator<{data: object, chunk: string}>} The parsed data and the chunk to be sent. */ async function* parseStreamData(json) { // Claude @@ -120,6 +120,7 @@ async function* parseStreamData(json) { }; } } + return; } // MakerSuite else if (Array.isArray(json.candidates)) { @@ -145,6 +146,7 @@ async function* parseStreamData(json) { } } } + return; } // NovelAI / KoboldCpp Classic else if (typeof json.token === 'string' && json.token.length > 0) { @@ -155,6 +157,7 @@ async function* parseStreamData(json) { chunk: str, }; } + return; } // llama.cpp? else if (typeof json.content === 'string' && json.content.length > 0) { @@ -165,6 +168,7 @@ async function* parseStreamData(json) { chunk: str, }; } + return; } // OpenAI-likes else if (Array.isArray(json.choices)) { @@ -184,6 +188,7 @@ async function* parseStreamData(json) { chunk: str, }; } + return; } else if (typeof json.choices[0].delta === 'object') { if (typeof json.choices[0].delta.text === 'string' && json.choices[0].delta.text.length > 0) { @@ -197,6 +202,7 @@ async function* parseStreamData(json) { chunk: str, }; } + return; } else if (typeof json.choices[0].delta.content === 'string' && json.choices[0].delta.content.length > 0) { for (let j = 0; j < json.choices[0].delta.content.length; j++) { @@ -209,6 +215,7 @@ async function* parseStreamData(json) { chunk: str, }; } + return; } } else if (typeof json.choices[0].message === 'object') { @@ -223,11 +230,12 @@ async function* parseStreamData(json) { chunk: str, }; } + return; } } } - yield null; + throw new Error('Unknown event data format'); } /** @@ -257,11 +265,6 @@ export class SmoothEventSourceStream extends EventSourceStream { } for await (const parsed of parseStreamData(json)) { - if (!parsed) { - lastStr = ''; - return controller.enqueue(event); - } - hasFocus && await delay(getDelay(lastStr)); controller.enqueue(new MessageEvent(event.type, { data: JSON.stringify(parsed.data) })); lastStr = parsed.chunk; From 2e9c96d1c92b23075e9ab3a0e383be859ce257e1 Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Fri, 5 Apr 2024 00:53:32 +0200 Subject: [PATCH 15/80] Fix multi char import on button - Fixes #1983 - importCharacter has to be async await to await user input on tag creation --- public/script.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/script.js b/public/script.js index a5207c022..23c2655c4 100644 --- a/public/script.js +++ b/public/script.js @@ -9907,14 +9907,14 @@ jQuery(async function () { $('#character_import_file').click(); }); - $('#character_import_file').on('change', function (e) { + $('#character_import_file').on('change', async function (e) { $('#rm_info_avatar').html(''); if (!e.target.files.length) { return; } for (const file of e.target.files) { - importCharacter(file); + await importCharacter(file); } }); From 8f6e41428fd1db10cefbc2d47f880abd4a1d456d Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Fri, 5 Apr 2024 12:43:43 +0300 Subject: [PATCH 16/80] Optimize tags template references --- public/scripts/tags.js | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/public/scripts/tags.js b/public/scripts/tags.js index 553a6132c..7edd2feab 100644 --- a/public/scripts/tags.js +++ b/public/scripts/tags.js @@ -43,6 +43,9 @@ export { const CHARACTER_FILTER_SELECTOR = '#rm_characters_block .rm_tag_filter'; const GROUP_FILTER_SELECTOR = '#rm_group_chats_block .rm_tag_filter'; +const TAG_TEMPLATE = $('#tag_template .tag'); +const FOLDER_TEMPLATE = $('#bogus_folder_template .bogus_folder_select'); +const VIEW_TAG_TEMPLATE = $('#tag_view_template .tag_view_item'); function getFilterHelper(listSelector) { return $(listSelector).is(GROUP_FILTER_SELECTOR) ? groupCandidatesFilter : entitiesFilter; @@ -271,7 +274,7 @@ function getTagBlock(tag, entities, hidden = 0) { const tagFolder = TAG_FOLDER_TYPES[tag.folder_type]; - const template = $('#bogus_folder_template .bogus_folder_select').clone(); + const template = FOLDER_TEMPLATE.clone(); template.addClass(tagFolder.class); template.attr({ 'tagid': tag.id, 'id': `BogusFolder${tag.id}` }); template.find('.avatar').css({ 'background-color': tag.color, 'color': tag.color2 }).attr('title', `[Folder] ${tag.name}`); @@ -665,7 +668,7 @@ function appendTagToList(listElement, tag, { removable = false, selectable = fal return; } - let tagElement = $('#tag_template .tag').clone(); + let tagElement = TAG_TEMPLATE.clone(); tagElement.attr('id', tag.id); //tagElement.css('color', 'var(--SmartThemeBodyColor)'); @@ -1131,7 +1134,7 @@ function onTagCreateClick() { function appendViewTagToList(list, tag, everything) { const count = everything.filter(x => x == tag.id).length; - const template = $('#tag_view_template .tag_view_item').clone(); + const template = VIEW_TAG_TEMPLATE.clone(); template.attr('id', tag.id); template.find('.tag_view_counter_value').text(count); template.find('.tag_view_name').text(tag.name); @@ -1148,16 +1151,18 @@ function appendViewTagToList(list, tag, everything) { template.find('.tag_as_folder').hide(); } - template.find('.tagColorPickerHolder').html( - ``, - ); - template.find('.tagColorPicker2Holder').html( - ``, - ); + const primaryColorPicker = $('') + .addClass('tag-color') + .attr({ id: colorPickerId, color: tag.color }); + + const secondaryColorPicker = $('') + .addClass('tag-color2') + .attr({ id: colorPicker2Id, color: tag.color2 }); + + template.find('.tagColorPickerHolder').append(primaryColorPicker); + template.find('.tagColorPicker2Holder').append(secondaryColorPicker); template.find('.tag_as_folder').attr('id', tagAsFolderId); - template.find('.tag-color').attr('id', colorPickerId); - template.find('.tag-color2').attr('id', colorPicker2Id); list.append(template); From 3a0ceae80aaf20979fc4e90621942f6e196c5a31 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Fri, 5 Apr 2024 13:45:28 +0300 Subject: [PATCH 17/80] Optimize scroll height resets on WI entry render, remove silly logs --- public/index.html | 6 ++--- public/scripts/world-info.js | 47 +++++++++++++++--------------------- 2 files changed, 23 insertions(+), 30 deletions(-) diff --git a/public/index.html b/public/index.html index 674403e6e..e807b3d67 100644 --- a/public/index.html +++ b/public/index.html @@ -4838,7 +4838,7 @@
    - +
    +
    Logic @@ -4920,7 +4920,7 @@ Optional Filter
    - +
    diff --git a/public/scripts/world-info.js b/public/scripts/world-info.js index 83d4a1727..ae601bcea 100644 --- a/public/scripts/world-info.js +++ b/public/scripts/world-info.js @@ -42,6 +42,8 @@ const world_info_logic = { AND_ALL: 3, }; +const WI_ENTRY_EDIT_TEMPLATE = $('#entry_edit_template .world_entry'); + let world_info = {}; let selected_world_info = []; let world_names; @@ -799,6 +801,11 @@ function displayWorldEntries(name, data, navigation = navigation_option.none) { afterSizeSelectorChange: function (e) { localStorage.setItem(storageKey, e.target.value); }, + afterPaging: function () { + $('#world_popup_entries_list textarea[name="comment"]').each(function () { + initScrollHeight($(this)); + }); + }, }); if (typeof navigation === 'number' && Number(navigation) >= 0) { @@ -986,7 +993,7 @@ function getWorldEntry(name, data, entry) { return; } - const template = $('#entry_edit_template .world_entry').clone(); + const template = WI_ENTRY_EDIT_TEMPLATE.clone(); template.data('uid', entry.uid); template.attr('uid', entry.uid); @@ -998,10 +1005,10 @@ function getWorldEntry(name, data, entry) { event.stopPropagation(); }); - keyInput.on('input', function () { + keyInput.on('input', function (_, { skipReset } = {}) { const uid = $(this).data('uid'); const value = String($(this).val()); - resetScrollHeight(this); + !skipReset && resetScrollHeight(this); data.entries[uid].key = value .split(',') .map((x) => x.trim()) @@ -1010,7 +1017,7 @@ function getWorldEntry(name, data, entry) { setOriginalDataValue(data, uid, 'keys', data.entries[uid].key); saveWorldInfo(name, data); }); - keyInput.val(entry.key.join(', ')).trigger('input'); + keyInput.val(entry.key.join(', ')).trigger('input', { skipReset: true }); //initScrollHeight(keyInput); // logic AND/NOT @@ -1024,7 +1031,6 @@ function getWorldEntry(name, data, entry) { selectiveLogicDropdown.on('input', function () { const uid = $(this).data('uid'); const value = Number($(this).val()); - console.debug(`logic for ${entry.uid} set to ${value}`); data.entries[uid].selectiveLogic = !isNaN(value) ? value : world_info_logic.AND_ANY; setOriginalDataValue(data, uid, 'selectiveLogic', data.entries[uid].selectiveLogic); saveWorldInfo(name, data); @@ -1134,10 +1140,10 @@ function getWorldEntry(name, data, entry) { // keysecondary const keySecondaryInput = template.find('textarea[name="keysecondary"]'); keySecondaryInput.data('uid', entry.uid); - keySecondaryInput.on('input', function () { + keySecondaryInput.on('input', function (_, { skipReset } = {}) { const uid = $(this).data('uid'); const value = String($(this).val()); - resetScrollHeight(this); + !skipReset && resetScrollHeight(this); data.entries[uid].keysecondary = value .split(',') .map((x) => x.trim()) @@ -1147,17 +1153,17 @@ function getWorldEntry(name, data, entry) { saveWorldInfo(name, data); }); - keySecondaryInput.val(entry.keysecondary.join(', ')).trigger('input'); - initScrollHeight(keySecondaryInput); + keySecondaryInput.val(entry.keysecondary.join(', ')).trigger('input', { skipReset: true }); + //initScrollHeight(keySecondaryInput); // comment const commentInput = template.find('textarea[name="comment"]'); const commentToggle = template.find('input[name="addMemo"]'); commentInput.data('uid', entry.uid); - commentInput.on('input', function () { + commentInput.on('input', function (_, { skipReset } = {}) { const uid = $(this).data('uid'); const value = $(this).val(); - resetScrollHeight(this); + !skipReset && resetScrollHeight(this); data.entries[uid].comment = value; setOriginalDataValue(data, uid, 'comment', data.entries[uid].comment); @@ -1176,8 +1182,8 @@ function getWorldEntry(name, data, entry) { value ? commentContainer.show() : commentContainer.hide(); }); - commentInput.val(entry.comment).trigger('input'); - initScrollHeight(commentInput); + commentInput.val(entry.comment).trigger('input', { skipReset: true }); + //initScrollHeight(commentInput); commentToggle.prop('checked', true /* entry.addMemo */).trigger('input'); commentToggle.parent().hide(); @@ -1378,7 +1384,7 @@ function getWorldEntry(name, data, entry) { } const positionInput = template.find('select[name="position"]'); - initScrollHeight(positionInput); + //initScrollHeight(positionInput); positionInput.data('uid', entry.uid); positionInput.on('click', function (event) { // Prevent closing the drawer on clicking the input @@ -1435,7 +1441,6 @@ function getWorldEntry(name, data, entry) { //new tri-state selector for constant/normal/disabled const entryStateSelector = template.find('select[name="entryStateSelector"]'); entryStateSelector.data('uid', entry.uid); - console.log(entry.uid); entryStateSelector.on('click', function (event) { // Prevent closing the drawer on clicking the input event.stopPropagation(); @@ -1450,7 +1455,6 @@ function getWorldEntry(name, data, entry) { setOriginalDataValue(data, uid, 'enabled', true); setOriginalDataValue(data, uid, 'constant', true); template.removeClass('disabledWIEntry'); - console.debug('set to constant'); break; case 'normal': data.entries[uid].constant = false; @@ -1458,7 +1462,6 @@ function getWorldEntry(name, data, entry) { setOriginalDataValue(data, uid, 'enabled', true); setOriginalDataValue(data, uid, 'constant', false); template.removeClass('disabledWIEntry'); - console.debug('set to normal'); break; case 'disabled': data.entries[uid].constant = false; @@ -1466,7 +1469,6 @@ function getWorldEntry(name, data, entry) { setOriginalDataValue(data, uid, 'enabled', false); setOriginalDataValue(data, uid, 'constant', false); template.addClass('disabledWIEntry'); - console.debug('set to disabled'); break; } saveWorldInfo(name, data); @@ -1474,19 +1476,13 @@ function getWorldEntry(name, data, entry) { }); const entryState = function () { - - console.log(`constant: ${entry.constant}, disabled: ${entry.disable}`); if (entry.constant === true) { - console.debug('found constant'); return 'constant'; } else if (entry.disable === true) { - console.debug('found disabled'); return 'disabled'; } else { - console.debug('found normal'); return 'normal'; } - }; template .find(`select[name="entryStateSelector"] option[value=${entryState()}]`) @@ -1982,15 +1978,12 @@ async function getSortedEntries() { switch (Number(world_info_character_strategy)) { case world_info_insertion_strategy.evenly: - console.debug('WI using evenly'); entries = [...globalLore, ...characterLore].sort(sortFn); break; case world_info_insertion_strategy.character_first: - console.debug('WI using char first'); entries = [...characterLore.sort(sortFn), ...globalLore.sort(sortFn)]; break; case world_info_insertion_strategy.global_first: - console.debug('WI using global first'); entries = [...globalLore.sort(sortFn), ...characterLore.sort(sortFn)]; break; default: From b1c2617b0dcaf86a2dcf8a012d2cfbe5e548dcb0 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Fri, 5 Apr 2024 13:53:39 +0300 Subject: [PATCH 18/80] Only init scroll height of WI keys when first opening the drawer --- public/index.html | 4 ++-- public/scripts/world-info.js | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/public/index.html b/public/index.html index e807b3d67..107674261 100644 --- a/public/index.html +++ b/public/index.html @@ -4901,7 +4901,7 @@ Primary Keywords - +
    Logic @@ -4920,7 +4920,7 @@ Optional Filter
    - +
    diff --git a/public/scripts/world-info.js b/public/scripts/world-info.js index ae601bcea..a8999e228 100644 --- a/public/scripts/world-info.js +++ b/public/scripts/world-info.js @@ -1218,6 +1218,8 @@ function getWorldEntry(name, data, entry) { if (counter.data('first-run')) { counter.data('first-run', false); countTokensDebounced(counter, contentInput.val()); + initScrollHeight(keyInput); + initScrollHeight(keySecondaryInput); } }); From acb623c6d84d5830a4261929e9c25b5a8441cb32 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Fri, 5 Apr 2024 17:27:08 +0300 Subject: [PATCH 19/80] Adjust automation id layout --- public/scripts/extensions/quick-reply/html/qrEditor.html | 2 +- public/scripts/extensions/quick-reply/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/scripts/extensions/quick-reply/html/qrEditor.html b/public/scripts/extensions/quick-reply/html/qrEditor.html index 24a149333..08cecbc23 100644 --- a/public/scripts/extensions/quick-reply/html/qrEditor.html +++ b/public/scripts/extensions/quick-reply/html/qrEditor.html @@ -78,7 +78,7 @@ Execute before group member message -
    +
    Automation ID
    diff --git a/public/scripts/extensions/quick-reply/index.js b/public/scripts/extensions/quick-reply/index.js index a032a12d6..7b58f4aaa 100644 --- a/public/scripts/extensions/quick-reply/index.js +++ b/public/scripts/extensions/quick-reply/index.js @@ -104,7 +104,7 @@ const loadSets = async () => { qr.executeOnAi = slot.autoExecute_botMessage ?? false; qr.executeOnChatChange = slot.autoExecute_chatLoad ?? false; qr.executeOnGroupMemberDraft = slot.autoExecute_groupMemberDraft ?? false; - qr.automationId = slot.automationId ?? false; + qr.automationId = slot.automationId ?? ''; qr.contextList = (slot.contextMenu ?? []).map(it=>({ set: it.preset, isChained: it.chain, From 9e4b765db133f979e7e673aef09a9ed907bf660e Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Fri, 5 Apr 2024 17:59:12 +0300 Subject: [PATCH 20/80] #2012 Replace all comfy seeds --- public/scripts/extensions/stable-diffusion/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/extensions/stable-diffusion/index.js b/public/scripts/extensions/stable-diffusion/index.js index f4e47ca84..6b656e549 100644 --- a/public/scripts/extensions/stable-diffusion/index.js +++ b/public/scripts/extensions/stable-diffusion/index.js @@ -2524,7 +2524,7 @@ async function generateComfyImage(prompt, negativePrompt) { } let workflow = (await workflowResponse.json()).replace('"%prompt%"', JSON.stringify(prompt)); workflow = workflow.replace('"%negative_prompt%"', JSON.stringify(negativePrompt)); - workflow = workflow.replace('"%seed%"', JSON.stringify(Math.round(Math.random() * Number.MAX_SAFE_INTEGER))); + workflow = workflow.replaceAll('"%seed%"', JSON.stringify(Math.round(Math.random() * Number.MAX_SAFE_INTEGER))); placeholders.forEach(ph => { workflow = workflow.replace(`"%${ph}%"`, JSON.stringify(extension_settings.sd[ph])); }); From a1a8d7fe4ca9d35d1a46f94fe333b86d3b297ee6 Mon Sep 17 00:00:00 2001 From: caesarw Date: Fri, 5 Apr 2024 15:39:06 +0000 Subject: [PATCH 21/80] Added workflows for nightly staging build * the nightly staging build starts at 00:00 UTC everyday * multi-arch build support is added (amd64, arm64) --- .github/workflows/docker-publish.yml | 73 ++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 20 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 06e8eac9e..588fcd80c 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -1,45 +1,78 @@ # This workflow will publish a docker image for every full release to the GitHub package repository -name: Create Docker Image on Release +name: Create Docker Image (Release and Staging) on: release: # Allow pre-releases types: [published] + schedule: + # Build the staging image everyday at 00:00 UTC + - cron: "0 0 * * *" env: # This should allow creation of docker images even in forked repositories - # Image name may not contain uppercase characters, so we can not use the repository name - # Creates a string like: ghcr.io/SillyTavern/sillytavern - image_name: ghcr.io/sillytavern/sillytavern + IMAGE_NAME: ${{ github.repository }} + REGISTRY: ghcr.io jobs: - build: - runs-on: ubuntu-latest steps: - - name: Checkout + # Using the following workaround because currently GitHub Actions + # does not support logical AND/OR operations on triggers + # It's currently not possible to have `branches` under the `schedule` trigger + - name: Checkout the release branch + if: ${{ github.event_name == 'release' }} uses: actions/checkout@v3 + with: + ref: "release" - # Build docker image using dockerfile and tag it with branch name - # Assumes branch name is the version number - - name: Build the Docker image - run: | - docker build . --file Dockerfile --tag $image_name:${{ github.ref_name }} + - name: Checkout the staging branch + if: ${{ github.event_name == 'schedule' }} + uses: actions/checkout@v3 + with: + ref: "staging" + + # Setting up QEMU for multi-arch image build + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Extract metadata (tags, labels) for the image + uses: docker/metadata-action@v5.5.1 + id: metadata + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: ${{ github.ref_name }} # Login into package repository as the person who created the release - - name: Login to GitHub Container Registry - uses: docker/login-action@v1 + - name: Log in to the Container registry + uses: docker/login-action@v3 with: - registry: ghcr.io + registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - # Assumes release is the latest and marks image as such - - name: Docker Tag and Push + # Build docker image using dockerfile for amd64 and arm64 + # Tag it with branch name + # Assumes branch name is the version number + - name: Build and push + uses: docker/build-push-action@v5.3.0 + with: + context: . + platforms: linux/amd64,linux/arm64 + file: Dockerfile + push: true + tags: ${{ steps.metadata.outputs.tags }} + labels: ${{ steps.metadata.outputs.labels }} + + # If the workflow is triggered by a release, marks and push the image as such + - name: Docker tag latest and push + if: ${{ github.event_name == 'release' }} run: | - docker tag $image_name:${{ github.ref_name }} $image_name:latest - docker push $image_name:${{ github.ref_name }} - docker push $image_name:latest + docker tag $IMAGE_NAME:${{ github.ref_name }} $IMAGE_NAME:latest + docker push $IMAGE_NAME:latest From c0213c086c0680ee62da354c08d16e80785d7d19 Mon Sep 17 00:00:00 2001 From: KegaPlayer <128023803+KegaPlayer@users.noreply.github.com> Date: Fri, 5 Apr 2024 12:12:32 -0500 Subject: [PATCH 22/80] Update es-es.json Proofreading work done by a Spanish language native on the es-es.json file. Mostly centered around making some terminology consistent and a few minor grammar fixes. --- public/locales/es-es.json | 398 +++++++++++++++++++------------------- 1 file changed, 199 insertions(+), 199 deletions(-) diff --git a/public/locales/es-es.json b/public/locales/es-es.json index c16ba2902..1f919c909 100644 --- a/public/locales/es-es.json +++ b/public/locales/es-es.json @@ -5,7 +5,7 @@ "novelaipreserts": "Preajustes de NovelAI", "default": "Predeterminado", "openaipresets": "Preajustes de OpenAI", - "text gen webio(ooba) presets": "Preajustes de generación de texto WebUI(ooba)", + "text gen webio(ooba) presets": "Preajustes de Text Gen WebUI(ooba)", "response legth(tokens)": "Longitud de respuesta (tokens)", "select": "Seleccionar", "context size(tokens)": "Tamaño de contexto (tokens)", @@ -13,17 +13,17 @@ "Only select models support context sizes greater than 4096 tokens. Increase only if you know what you're doing.": "Solo algunos modelos admiten tamaños de contexto mayores de 4096 tokens. Aumenta solo si sabes lo que estás haciendo.", "rep.pen": "Penalización de repetición", "WI Entry Status:🔵 Constant🟢 Normal❌ Disabled": "Estado de entrada de WI:🔵 Constante🟢 Normal❌ Desactivado", - "rep.pen range": "Rango de penalización de repetición", + "rep.pen range": "rango de penalización de repetición", "Temperature controls the randomness in token selection": "La temperatura controla la aleatoriedad en la selección de tokens", "temperature": "Temperatura", "Top K sets a maximum amount of top tokens that can be chosen from": "Top K establece una cantidad máxima de tokens principales que se pueden elegir", "Top P (a.k.a. nucleus sampling)": "Top P (también conocido como muestreo de núcleo)", - "Typical P Sampling prioritizes tokens based on their deviation from the average entropy of the set": "El muestreo P típico prioriza tokens según su desviación de la entropía promedio del conjunto", + "Typical P Sampling prioritizes tokens based on their deviation from the average entropy of the set": "El Muestreo P Típico prioriza tokens según su desviación de la entropía promedio del conjunto", "Min P sets a base minimum probability": "Min P establece una probabilidad mínima base", "Top A sets a threshold for token selection based on the square of the highest token probability": "Top A establece un umbral para la selección de tokens basado en el cuadrado de la probabilidad de token más alta", "Tail-Free Sampling (TFS)": "Muestreo sin cola (TFS)", - "Epsilon cutoff sets a probability floor below which tokens are excluded from being sampled": "El corte epsilon establece un límite de probabilidad por debajo del cual se excluyen los tokens de ser muestreados", - "Scale Temperature dynamically per token, based on the variation of probabilities": "Escalas de temperatura dinámicamente por token, basado en la variación de probabilidades", + "Epsilon cutoff sets a probability floor below which tokens are excluded from being sampled": "El corte Epsilon establece un límite de probabilidad por debajo del cual se excluyen los tokens de ser muestreados", + "Scale Temperature dynamically per token, based on the variation of probabilities": "Escala la Temperatura dinámicamente por token, basado en la variación de probabilidades", "Minimum Temp": "Temperatura mínima", "Maximum Temp": "Temperatura máxima", "Exponent": "Exponente", @@ -33,11 +33,11 @@ "Variability parameter for Mirostat outputs": "Parámetro de variabilidad para las salidas de Mirostat", "Learning rate of Mirostat": "Tasa de aprendizaje de Mirostat", "Strength of the Contrastive Search regularization term. Set to 0 to disable CS": "Fuerza del término de regularización de la Búsqueda Contrastiva. Establece en 0 para deshabilitar CS.", - "Temperature Last": "Última temperatura", + "Temperature Last": "Temperatura de Último", "Use the temperature sampler last": "Usar el muestreador de temperatura al final", "LLaMA / Mistral / Yi models only": "Solo modelos LLaMA / Mistral / Yi", "Example: some text [42, 69, 1337]": "Ejemplo: algún texto [42, 69, 1337]", - "Classifier Free Guidance. More helpful tip coming soon": "Guía libre de clasificadores. Pronto llegará un consejo más útil", + "Classifier Free Guidance. More helpful tip coming soon": "Guía Libre de Clasificadores. Pronto llegará un consejo más útil", "Scale": "Escala", "GBNF Grammar": "Gramática GBNF", "Usage Stats": "Estadísticas de uso", @@ -74,7 +74,7 @@ "Add BOS Token": "Agregar token BOS", "Add the bos_token to the beginning of prompts. Disabling this can make the replies more creative": "Agrega el token BOS al principio de las indicaciones. Desactivar esto puede hacer que las respuestas sean más creativas", "Ban EOS Token": "Prohibir token EOS", - "Ban the eos_token. This forces the model to never end the generation prematurely": "Prohibir el token eos. Esto obliga al modelo a nunca terminar la generación prematuramente", + "Ban the eos_token. This forces the model to never end the generation prematurely": "Prohibir el token EOS. Esto obliga al modelo a nunca terminar la generación prematuramente", "Skip Special Tokens": "Omitir tokens especiales", "Beam search": "Búsqueda de haz", "Number of Beams": "Número de haces", @@ -83,9 +83,9 @@ "Contrastive search": "Búsqueda contrastiva", "Penalty Alpha": "Alfa de penalización", "Seed": "Semilla", - "Epsilon Cutoff": "Corte epsilon", - "Eta Cutoff": "Corte eta", - "Negative Prompt": "Indicación negativa", + "Epsilon Cutoff": "Corte Epsilon", + "Eta Cutoff": "Corte Eta", + "Negative Prompt": "Indicaciónes negativas", "Mirostat (mode=1 is only for llama.cpp)": "Mirostat (modo=1 es solo para llama.cpp)", "Mirostat is a thermostat for output perplexity": "Mirostat es un termostato para la perplejidad de salida", "Add text here that would make the AI generate things you don't want in your outputs.": "Agrega aquí texto que haría que la IA genere cosas que no quieres en tus salidas.", @@ -102,32 +102,32 @@ "NSFW Encouraged": "NSFW Alentado", "Tell the AI that NSFW is allowed.": "Indica a la IA que se permite contenido NSFW.", "NSFW Prioritized": "NSFW Priorizado", - "NSFW prompt text goes first in the prompt to emphasize its effect.": "El texto de la indicación NSFW va primero en la indicación para enfatizar su efecto.", - "Streaming": "Transmisión", + "NSFW prompt text goes first in the prompt to emphasize its effect.": "El texto de las indicaciones NSFW va primero en la indicación para enfatizar su efecto.", + "Streaming": "Transmisión (Streaming)", "Dynamic Temperature": "Temperatura dinámica", - "Restore current preset": "Restaurar la configuración actual", - "Neutralize Samplers": "Neutralizar los muestreadores", - "Text Completion presets": "Preajustes de completado de texto", + "Restore current preset": "Restaurar el preajuste actual", + "Neutralize Samplers": "Neutralizar muestreadores", + "Text Completion presets": "Preajustes de Completado de Texto", "Documentation on sampling parameters": "Documentación sobre parámetros de muestreo", "Set all samplers to their neutral/disabled state.": "Establecer todos los muestreadores en su estado neutral/desactivado.", "Only enable this if your model supports context sizes greater than 4096 tokens": "Habilita esto solo si tu modelo admite tamaños de contexto mayores de 4096 tokens", "Display the response bit by bit as it is generated": "Mostrar la respuesta poco a poco según se genera", "Generate only one line per request (KoboldAI only, ignored by KoboldCpp).": "Generar solo una línea por solicitud (solo KoboldAI, ignorado por KoboldCpp).", - "Ban the End-of-Sequence (EOS) token (with KoboldCpp, and possibly also other tokens with KoboldAI).": "Prohibir el token Fin-de-secuencia (EOS) (con KoboldCpp, y posiblemente también otros tokens con KoboldAI).", + "Ban the End-of-Sequence (EOS) token (with KoboldCpp, and possibly also other tokens with KoboldAI).": "Prohibir el token Fin-de-Secuencia (EOS) (con KoboldCpp, y posiblemente también otros tokens con KoboldAI).", "Good for story writing, but should not be used for chat and instruct mode.": "Bueno para escribir historias, pero no debería usarse para el modo de chat e instrucción.", "Enhance Definitions": "Mejorar Definiciones", "Use OAI knowledge base to enhance definitions for public figures and known fictional characters": "Utilizar la base de conocimientos de OAI para mejorar las definiciones de figuras públicas y personajes ficticios conocidos", "Wrap in Quotes": "Envolver entre comillas", "Wrap entire user message in quotes before sending.": "Envolver todo el mensaje del usuario entre comillas antes de enviarlo.", - "Leave off if you use quotes manually for speech.": "Omite esto si usas comillas manualmente para el discurso.", - "Main prompt": "Indicación principal", - "The main prompt used to set the model behavior": "La indicación principal utilizada para establecer el comportamiento del modelo", - "NSFW prompt": "Indicación NSFW", - "Prompt that is used when the NSFW toggle is on": "Indicación que se utiliza cuando el interruptor NSFW está activado", - "Jailbreak prompt": "Indicación de jailbreak", - "Prompt that is used when the Jailbreak toggle is on": "Indicación que se utiliza cuando el interruptor Jailbreak está activado", - "Impersonation prompt": "Indicación de suplantación de identidad", - "Prompt that is used for Impersonation function": "Indicación que se utiliza para la función de suplantación de identidad", + "Leave off if you use quotes manually for speech.": "Omite esto si usas comillas manualmente para diálogo.", + "Main prompt": "Indicaciónes principales", + "The main prompt used to set the model behavior": "Las indicaciónes principales utilizadas para establecer el comportamiento del modelo", + "NSFW prompt": "Indicaciónes NSFW", + "Prompt that is used when the NSFW toggle is on": "Indicaciónes que se utilizan cuando el interruptor NSFW está activado", + "Jailbreak prompt": "Indicaciónes de jailbreak", + "Prompt that is used when the Jailbreak toggle is on": "Indicaciónes que se utilizan cuando el interruptor Jailbreak está activado", + "Impersonation prompt": "Indicaciónes de Suplantación", + "Prompt that is used for Impersonation function": "Indicación que se utiliza para la función de Suplantación", "Logit Bias": "Sesgo de logit", "Helps to ban or reenforce the usage of certain words": "Ayuda a prohibir o reforzar el uso de ciertas palabras", "View / Edit bias preset": "Ver / Editar preajuste de sesgo", @@ -136,17 +136,17 @@ "Message to send when auto-jailbreak is on.": "Mensaje para enviar cuando el auto-jailbreak está activado.", "Jailbreak confirmation reply": "Respuesta de confirmación de jailbreak", "Bot must send this back to confirm jailbreak": "El bot debe enviar esto de vuelta para confirmar el jailbreak", - "Character Note": "Nota del personaje", + "Character Note": "Nota de personaje", "Influences bot behavior in its responses": "Influye en el comportamiento del bot en sus respuestas", "Connect": "Conectar", "Test Message": "Mensaje de prueba", "API": "API", "KoboldAI": "KoboldAI", - "Use Horde": "Usar Horda", + "Use Horde": "Usar Horde", "API url": "URL de la API", "PygmalionAI/aphrodite-engine": "PygmalionAI/aphrodite-engine (Modo envolvente para API de OpenAI)", - "Register a Horde account for faster queue times": "Registra una cuenta de la Horda para tiempos de espera más rápidos", - "Learn how to contribute your idle GPU cycles to the Hord": "Aprende cómo contribuir con tus ciclos de GPU inactivos a la Horda", + "Register a Horde account for faster queue times": "Registra una cuenta de Horde para tiempos de espera más rápidos", + "Learn how to contribute your idle GPU cycles to the Hord": "Aprende cómo contribuir con tus ciclos de GPU inactivos a Horde", "Adjust context size to worker capabilities": "Ajusta el tamaño del contexto a las capacidades del trabajador", "Adjust response length to worker capabilities": "Ajusta la longitud de la respuesta a las capacidades del trabajador", "API key": "Clave API", @@ -168,7 +168,7 @@ "For privacy reasons": "Por razones de privacidad, la clave API se oculta después de actualizar la página", "Models": "Modelos", "Hold Control / Command key to select multiple models.": "Mantén presionada la tecla Control / Comando para seleccionar varios modelos.", - "Horde models not loaded": "Modelos de la Horda no cargados", + "Horde models not loaded": "Modelos de Horde no cargados", "Not connected...": "No conectado...", "Novel API key": "Clave API de Novel", "Follow": "Seguir", @@ -199,7 +199,7 @@ "OpenAI Model": "Modelo de OpenAI", "Claude API Key": "Clave API de Claude", "Get your key from": "Obtén tu clave desde", - "Anthropic's developer console": "consola de desarrolladores de Anthropic", + "Anthropic's developer console": "la consola de desarrolladores de Anthropic", "Slack and Poe cookies will not work here, do not bother trying.": "Las cookies de Slack y Poe no funcionarán aquí, no te molestes en intentarlo.", "Claude Model": "Modelo de Claude", "Scale API Key": "Clave API de Scale", @@ -214,72 +214,72 @@ "OpenRouter API Key": "Clave API de OpenRouter", "Connect to the API": "Conectar a la API", "OpenRouter Model": "Modelo de OpenRouter", - "View Remaining Credits": "Ver créditos restantes", + "View Remaining Credits": "Ver Créditos Restantes", "Click Authorize below or get the key from": "Haz clic en Autorizar a continuación o obtén la clave desde", "Auto-connect to Last Server": "Conexión automática al último servidor", "View hidden API keys": "Ver claves API ocultas", "Advanced Formatting": "Formato avanzado", - "Context Template": "Plantilla de contexto", + "Context Template": "Plantilla de Contexto", "AutoFormat Overrides": "Anulaciones de AutoFormato", "Disable description formatting": "Desactivar formato de descripción", "Disable personality formatting": "Desactivar formato de personalidad", "Disable scenario formatting": "Desactivar formato de escenario", "Disable example chats formatting": "Desactivar formato de chats de ejemplo", "Disable chat start formatting": "Desactivar formato de inicio de chat", - "Custom Chat Separator": "Separador de chat personalizado", - "Replace Macro in Custom Stopping Strings": "Reemplazar macro en cadenas de detención personalizadas", - "Strip Example Messages from Prompt": "Eliminar mensajes de ejemplo de la solicitud", + "Custom Chat Separator": "Separador de Chat Personalizado", + "Replace Macro in Custom Stopping Strings": "Reemplazar macro en Cadenas de Detención Personalizadas", + "Strip Example Messages from Prompt": "Eliminar Mensajes de Ejemplo de las Indicaciones", "Story String": "Cadena de historia", "Example Separator": "Separador de ejemplo", "Chat Start": "Inicio de chat", "Activation Regex": "Regex de activación", - "Instruct Mode": "Modo de instrucción", - "Wrap Sequences with Newline": "Envolver secuencias con nueva línea", - "Include Names": "Incluir nombres", - "Force for Groups and Personas": "Forzar para grupos y personas", - "System Prompt": "Solicitud del sistema", - "Instruct Mode Sequences": "Secuencias en modo de instrucción", - "Input Sequence": "Secuencia de entrada", - "Output Sequence": "Secuencia de salida", - "First Output Sequence": "Primera secuencia de salida", - "Last Output Sequence": "Última secuencia de salida", - "System Sequence Prefix": "Prefijo de secuencia del sistema", - "System Sequence Suffix": "Sufijo de secuencia del sistema", - "Stop Sequence": "Secuencia de parada", - "Context Formatting": "Formato de contexto", - "(Saved to Context Template)": "(Guardado en plantilla de contexto)", + "Instruct Mode": "Modo Instrucción", + "Wrap Sequences with Newline": "Envolver Secuencias con Nueva línea", + "Include Names": "Incluir Nombres", + "Force for Groups and Personas": "Forzar para Grupos y Personas", + "System Prompt": "Indicaciones del Sistema", + "Instruct Mode Sequences": "Secuencias en Modo Instrucción", + "Input Sequence": "Secuencia de Entrada", + "Output Sequence": "Secuencia de Salida", + "First Output Sequence": "Primera Secuencia de Salida", + "Last Output Sequence": "Última Secuencia de Salida", + "System Sequence Prefix": "Prefijo de Secuencia del Sistema", + "System Sequence Suffix": "Sufijo de Secuencia del Sistema", + "Stop Sequence": "Secuencia de Parada", + "Context Formatting": "Formato de Contexto", + "(Saved to Context Template)": "(Guardado en Plantilla de Contexto)", "Tokenizer": "Tokenizador", "None / Estimated": "Ninguno / Estimado", "Sentencepiece (LLaMA)": "Sentencepiece (LLaMA)", "Token Padding": "Relleno de token", "Save preset as": "Guardar preajuste como", - "Always add character's name to prompt": "Siempre agregar el nombre del personaje a la solicitud", - "Use as Stop Strings": "Usar como cadenas de parada", - "Bind to Context": "Vincular al contexto", + "Always add character's name to prompt": "Siempre agregar el nombre del personaje a las indicaciones", + "Use as Stop Strings": "Usar como Cadenas de Parada", + "Bind to Context": "Vincular al Contexto", "Generate only one line per request": "Generar solo una línea por solicitud", - "Misc. Settings": "Configuraciones misceláneas", + "Misc. Settings": "Configuraciones Misceláneas", "Auto-Continue": "Autocontinuar", - "Collapse Consecutive Newlines": "Colapsar nuevas líneas consecutivas", - "Allow for Chat Completion APIs": "Permitir APIs de finalización de chat", + "Collapse Consecutive Newlines": "Colapsar Nuevas líneas Consecutivas", + "Allow for Chat Completion APIs": "Permitir para APIs de Completado de Chat", "Target length (tokens)": "Longitud objetivo (tokens)", - "Keep Example Messages in Prompt": "Mantener mensajes de ejemplo en la solicitud", - "Remove Empty New Lines from Output": "Eliminar nuevas líneas vacías de la salida", + "Keep Example Messages in Prompt": "Mantener Mensajes de Ejemplo en las indicaciones", + "Remove Empty New Lines from Output": "Eliminar Nuevas líneas vacías de la salida", "Disabled for all models": "Desactivado para todos los modelos", "Automatic (based on model name)": "Automático (basado en el nombre del modelo)", "Enabled for all models": "Activado para todos los modelos", - "Anchors Order": "Orden de anclajes", + "Anchors Order": "Orden de Anclajes", "Character then Style": "Personaje luego estilo", "Style then Character": "Estilo luego personaje", "Character Anchor": "Anclaje de personaje", "Style Anchor": "Anclaje de estilo", - "World Info": "Información del mundo", + "World Info": "Información de Mundo (WI)", "Scan Depth": "Profundidad de escaneo", "Case-Sensitive": "Sensible a mayúsculas y minúsculas", "Match Whole Words": "Coincidir con palabras completas", "Use global setting": "Usar configuración global", "Yes": "Sí", "No": "No", - "Context %": "Contexto %", + "Context %": "% de Contexto", "Budget Cap": "Límite de presupuesto", "(0 = disabled)": "(0 = desactivado)", "depth": "profundidad", @@ -289,29 +289,29 @@ "None": "Ninguno", "User Settings": "Configuraciones de usuario", "UI Mode": "Modo de IU", - "UI Language": "Idioma", + "UI Language": "Idioma de la UI", "MovingUI Preset": "Preajuste de MovingUI", "UI Customization": "Personalización de la IU", - "Avatar Style": "Estilo de avatar", + "Avatar Style": "Estilo de Avatar", "Circle": "Círculo", "Rectangle": "Rectángulo", "Square": "Cuadrado", - "Chat Style": "Estilo de chat", + "Chat Style": "Estilo de Chat", "Default": "Predeterminado", "Bubbles": "Burbujas", "No Blur Effect": "Sin efecto de desenfoque", "No Text Shadows": "Sin sombras de texto", "Waifu Mode": "Modo Waifu", "Message Timer": "Temporizador de mensajes", - "Model Icon": "Ícono del modelo", + "Model Icon": "Ícono del Modelo", "# of messages (0 = disabled)": "# de mensajes (0 = desactivado)", - "Advanced Character Search": "Búsqueda avanzada de personajes", + "Advanced Character Search": "Búsqueda Avanzada de Personajes", "Allow {{char}}: in bot messages": "Permitir {{char}}: en mensajes de bot", "Allow {{user}}: in bot messages": "Permitir {{user}}: en mensajes de bot", "Show tags in responses": "Mostrar etiquetas en respuestas", "Aux List Field": "Campo de lista auxiliar", - "Lorebook Import Dialog": "Diálogo de importación de libro de historia", - "MUI Preset": "Preset MUI", + "Lorebook Import Dialog": "Diálogo de Importación de Libro de Historia", + "MUI Preset": "Preajuste MUI", "If set in the advanced character definitions, this field will be displayed in the characters list.": "Si se establece en las definiciones avanzadas de personajes, este campo se mostrará en la lista de personajes.", "Relaxed API URLS": "URLS de API relajadas", "Custom CSS": "CSS personalizado", @@ -322,7 +322,7 @@ "Relax message trim in Groups": "Relajar recorte de mensajes en Grupos", "Characters Hotswap": "Cambio rápido de personajes", "Request token probabilities": "Solicitar probabilidades de tokens", - "Movable UI Panels": "Paneles de UI móviles", + "Movable UI Panels": "Paneles de UI Móviles", "Reset Panels": "Restablecer paneles", "UI Colors": "Colores de UI", "Main Text": "Texto principal", @@ -331,44 +331,44 @@ "Shadow Color": "Color de sombra", "FastUI BG": "Fondo de FastUI", "Blur Tint": "Tinte de desenfoque", - "Font Scale": "Escala de fuente", + "Font Scale": "Tamaño de fuente", "Blur Strength": "Fuerza de desenfoque", "Text Shadow Width": "Ancho de sombra de texto", - "UI Theme Preset": "Preset de tema de UI", + "UI Theme Preset": "Preajuste de tema de UI", "Power User Options": "Opciones avanzadas de usuario", "Swipes": "Deslizamientos", "Miscellaneous": "Varios", "Theme Toggles": "Conmutadores de tema", - "Background Sound Only": "Solo sonido de fondo", + "Background Sound Only": "Solo Sonido de Fondo", "Auto-load Last Chat": "Cargar automáticamente el último chat", - "Auto-save Message Edits": "Guardar automáticamente las ediciones de mensajes", + "Auto-save Message Edits": "Guardar automáticamente las Ediciones de Mensajes", "Auto-fix Markdown": "Auto-corregir Markdown", "Allow : in bot messages": "Permitir : en mensajes de bot", - "Auto-scroll Chat": "Chat de desplazamiento automático", + "Auto-scroll Chat": "Desplazamiento de Chat Automático", "Render Formulas": "Renderizar fórmulas", "Send on Enter": "Enviar al presionar Enter", "Always disabled": "Siempre desactivado", "Automatic (desktop)": "Automático (escritorio)", "Always enabled": "Siempre activado", "Debug Menu": "Menú de depuración", - "Restore User Input": "Restaurar entrada de usuario", + "Restore User Input": "Restaurar Entrada de Usuario", "Character Handling": "Manipulación de personajes", "Example Messages Behavior": "Comportamiento de mensajes de ejemplo", - "Gradual push-out": "Empuje gradual", - "Chat/Message Handling": "Manipulación de chat/mensaje", + "Gradual push-out": "Expulsión gradual", + "Chat/Message Handling": "Manipulación de Chat/Mensaje", "Always include examples": "Siempre incluir ejemplos", "Never include examples": "Nunca incluir ejemplos", - "Forbid External Media": "Prohibir medios externos", - "System Backgrounds": "Fondos del sistema", + "Forbid External Media": "Prohibir Medios Externos", + "System Backgrounds": "Fondos del Sistema", "Name": "Nombre", "Your Avatar": "Tu avatar", - "Extensions API:": "API de extensiones:", + "Extensions API:": "API de Extensiones:", "SillyTavern-extras": "Extras de SillyTavern", "Auto-connect": "Conexión automática", - "Active extensions": "Extensiones activas", - "Extension settings": "Configuraciones de extensión", + "Active extensions": "Extensiones Activas", + "Extension settings": "Configuraciones de Extensión", "Description": "Descripción", - "First message": "Primer mensaje", + "First message": "Primer Mensaje", "Group Controls": "Controles de grupo", "Group reply strategy": "Estrategia de respuesta de grupo", "Natural order": "Orden natural", @@ -387,19 +387,19 @@ "Circumstances and context of the dialogue": "Circunstancias y contexto del diálogo", "Talkativeness": "Habladuría", "How often the chracter speaks in": "Con qué frecuencia habla el personaje en", - "group chats!": "chats de grupo!", + "group chats!": "chats grupales!", "Shy": "Tímido", "Normal": "Normal", - "Chatty": "Charlatán", + "Chatty": "Parlanchín", "Examples of dialogue": "Ejemplos de diálogo", "Forms a personality more clearly": "Forma una personalidad más clara", "Save": "Guardar", - "World Info Editor": "Editor de información del mundo", + "World Info Editor": "Editor de Información de Mundo (WI)", "New summary": "Nuevo resumen", "Export": "Exportar", "Delete World": "Eliminar mundo", "Chat History": "Historial de chat", - "Group Chat Scenario Override": "Anulación de escenario de chat grupal", + "Group Chat Scenario Override": "Anulación de escenario de Chat Grupal", "All group members will use the following scenario text instead of what is specified in their character cards.": "Todos los miembros del grupo usarán el siguiente texto de escenario en lugar de lo que se especifica en sus tarjetas de personaje.", "Keywords": "Palabras clave", "Separate with commas": "Separar con comas", @@ -424,60 +424,60 @@ "Start new chat": "Iniciar nuevo chat", "View past chats": "Ver chats anteriores", "Delete messages": "Eliminar mensajes", - "Impersonate": "Hacerse pasar por", + "Impersonate": "Suplantar", "Regenerate": "Regenerar", "PNG": "PNG", "JSON": "JSON", - "presets": "ajustes preestablecidos", + "presets": "preajustes", "Message Sound": "Sonido de mensaje", "Author's Note": "Nota del autor", "Send Jailbreak": "Enviar Jailbreak", "Replace empty message": "Reemplazar mensaje vacío", "Send this text instead of nothing when the text box is empty.": "Enviar este texto en lugar de nada cuando el cuadro de texto está vacío.", - "NSFW avoidance prompt": "Indicación de evitación de NSFW", - "Prompt that is used when the NSFW toggle is off": "Indicación que se usa cuando el interruptor de NSFW está apagado", - "Advanced prompt bits": "Bits de indicación avanzada", - "World Info format": "Formato de información del mundo", - "Wraps activated World Info entries before inserting into the prompt. Use {0} to mark a place where the content is inserted.": "Envuelve las entradas de información del mundo activadas antes de insertarlas en la indicación. Use {0} para marcar un lugar donde se inserta el contenido.", + "NSFW avoidance prompt": "Indicaciones de evitación de NSFW", + "Prompt that is used when the NSFW toggle is off": "Indicaciones que se usa cuando el interruptor de NSFW está apagado", + "Advanced prompt bits": "Bits de Indicaciones Avanzadas", + "World Info format": "Formato de Información de Mundo (WI)", + "Wraps activated World Info entries before inserting into the prompt. Use {0} to mark a place where the content is inserted.": "Envuelve las entradas de Información de Mundo (WI) activadas antes de insertarlas en las indicaciones. Use {0} para marcar un lugar donde se inserta el contenido.", "Unrestricted maximum value for the context slider": "Valor máximo sin restricciones para el control deslizante de contexto", - "Chat Completion Source": "Fuente de completado de chat", - "Avoid sending sensitive information to the Horde.": "Evite enviar información sensible a la Horda.", + "Chat Completion Source": "Fuente de Completado de Chat", + "Avoid sending sensitive information to the Horde.": "Evite enviar información sensible a Horde.", "Review the Privacy statement": "Revise la declaración de privacidad", - "Learn how to contribute your idel GPU cycles to the Horde": "Aprende cómo contribuir con tus ciclos de GPU inactivos a la Horda", + "Learn how to contribute your idel GPU cycles to the Horde": "Aprende cómo contribuir con tus ciclos de GPU inactivos a Horde", "Trusted workers only": "Solo trabajadores de confianza", "For privacy reasons, your API key will be hidden after you reload the page.": "Por razones de privacidad, su clave de API se ocultará después de que vuelva a cargar la página.", - "-- Horde models not loaded --": "-- Modelos de la Horda no cargados --", + "-- Horde models not loaded --": "-- Modelos de Horde no cargados --", "Example: http://127.0.0.1:5000/api ": "Ejemplo: http://127.0.0.1:5000/api", "No connection...": "Sin conexión...", - "Get your NovelAI API Key": "Obtenga su clave de API de NovelAI", - "KoboldAI Horde": "Horda de KoboldAI", + "Get your NovelAI API Key": "Obtenga su Clave de API de NovelAI", + "KoboldAI Horde": "Horde de KoboldAI", "Text Gen WebUI (ooba)": "Text Gen WebUI (ooba)", "NovelAI": "NovelAI", - "Chat Completion (OpenAI, Claude, Window/OpenRouter, Scale)": "Completado de chat (OpenAI, Claude, Window/OpenRouter, Scale)", + "Chat Completion (OpenAI, Claude, Window/OpenRouter, Scale)": "Completado de Chat (OpenAI, Claude, Window/OpenRouter, Scale)", "OpenAI API key": "Clave de API de OpenAI", "Trim spaces": "Recortar espacios", - "Trim Incomplete Sentences": "Recortar oraciones incompletas", - "Include Newline": "Incluir nueva línea", + "Trim Incomplete Sentences": "Recortar Oraciones Incompletas", + "Include Newline": "Incluir Nueva línea", "Non-markdown strings": "Cadenas no Markdown", - "Replace Macro in Sequences": "Reemplazar macro en secuencias", - "Presets": "Ajustes preestablecidos", + "Replace Macro in Sequences": "Reemplazar Macro en Secuencias", + "Presets": "Preajustes", "Separator": "Separador", - "Start Reply With": "Iniciar respuesta con", + "Start Reply With": "Iniciar Respuesta con", "Show reply prefix in chat": "Mostrar prefijo de respuesta en el chat", - "Worlds/Lorebooks": "Mundos/Libros de historia", - "Active World(s)": "Mundo(s) activo(s)", + "Worlds/Lorebooks": "Mundos/Libros de Historia", + "Active World(s)": "Mundo(s) Activo(s)", "Activation Settings": "Configuraciones de activación", - "Character Lore Insertion Strategy": "Estrategia de inserción de lore de personajes", + "Character Lore Insertion Strategy": "Estrategia de Inserción de Historia de Dersonajes", "Sorted Evenly": "Ordenado uniformemente", - "Active World(s) for all chats": "Mundo(s) activo(s) para todos los chats", - "-- World Info not found --": "-- Información del mundo no encontrada --", + "Active World(s) for all chats": "Mundo(s) Activo(s) para todos los chats", + "-- World Info not found --": "-- Información de Mundo (WI) no encontrada --", "--- Pick to Edit ---": "--- Seleccionar para editar ---", "or": "o", "New": "Nuevo", "Priority": "Prioridad", "Custom": "Personalizado", - "Title A-Z": "Título de la A a la Z", - "Title Z-A": "Título de la Z a la A", + "Title A-Z": "Título de A a Z", + "Title Z-A": "Título de Z a A", "Tokens ↗": "Tokens ↗", "Tokens ↘": "Tokens ↘", "Depth ↗": "Profundidad ↗", @@ -486,26 +486,26 @@ "Order ↘": "Orden ↘", "UID ↗": "UID ↗", "UID ↘": "UID ↘", - "Trigger% ↗": "Desencadenar% ↗", - "Trigger% ↘": "Desencadenar% ↘", + "Trigger% ↗": "Activador% ↗", + "Trigger% ↘": "Activador% ↘", "Order:": "Orden:", "Depth:": "Profundidad:", - "Character Lore First": "Lore del personaje primero", - "Global Lore First": "Lore global primero", - "Recursive Scan": "Exploración recursiva", + "Character Lore First": "Historia de Personaje Primero", + "Global Lore First": "Historia Global Primero", + "Recursive Scan": "Escaneo Recursiva", "Case Sensitive": "Sensible a mayúsculas y minúsculas", "Match whole words": "Coincidir palabras completas", - "Alert On Overflow": "Alerta en desbordamiento", - "World/Lore Editor": "Editor de mundo/Lore", + "Alert On Overflow": "Alerta en Desbordamiento", + "World/Lore Editor": "Editor de Mundo/Historia", "--- None ---": "--- Ninguno ---", "Comma separated (ignored if empty)": "Separado por comas (ignorado si está vacío)", "Use Probability": "Usar Probabilidad", "Exclude from recursion": "Excluir de la recursión", "Entry Title/Memo": "Título/Memo", "Position:": "Posición:", - "T_Position": "↑Char: antes de definiciones de caracteres\n↓Char: después de definiciones de caracteres\n↑AN: antes de notas del autor\n↓AN: después de notas del autor\n@D: en profundidad", - "Before Char Defs": "Antes de Definiciones de Caracteres", - "After Char Defs": "Después de Definiciones de Caracteres", + "T_Position": "↑Char: antes de definiciones de personajes\n↓Char: después de definiciones de personajes\n↑AN: antes de notas del autor\n↓AN: después de notas del autor\n@D: en profundidad", + "Before Char Defs": "Antes de Def. de Personaje", + "After Char Defs": "Después de Def. de Personaje", "Before AN": "Antes de AN", "After AN": "Después de AN", "at Depth": "en Profundidad", @@ -521,7 +521,7 @@ "Chat Background": "Fondo de Chat", "UI Background": "Fondo de IU", "Mad Lab Mode": "Modo Laboratorio Loco", - "Show Message Token Count": "Mostrar Conteo de Tokens de Mensaje", + "Show Message Token Count": "Mostrar Conteo de Tokens en Mensaje", "Compact Input Area (Mobile)": "Área de Entrada Compacta (Móvil)", "Zen Sliders": "Deslizadores Zen", "UI Border": "Borde de IU", @@ -534,9 +534,9 @@ "Streaming FPS": "FPS de Transmisión", "Gestures": "Gestos", "Message IDs": "IDs de Mensaje", - "Prefer Character Card Prompt": "Preferir Tarjeta de Personaje con Indicación", - "Prefer Character Card Jailbreak": "Preferir Jailbreak de Tarjeta de Personaje", - "Press Send to continue": "Presione Enviar para continuar", + "Prefer Character Card Prompt": "Preferir Indicaciones en Tarjeta de Personaje", + "Prefer Character Card Jailbreak": "Preferir Jailbreak en Tarjeta de Personaje", + "Press Send to continue": "Presionar Enviar para continuar", "Quick 'Continue' button": "Botón 'Continuar' Rápido", "Log prompts to console": "Registrar indicaciones en la consola", "Never resize avatars": "Nunca redimensionar avatares", @@ -569,11 +569,11 @@ "Show the number of tokens in each message in the chat log": "Mostrar el número de tokens en cada mensaje en el registro de chat", "Single-row message input area. Mobile only, no effect on PC": "Área de entrada de mensaje de una sola fila. Solo móvil, sin efecto en PC", "In the Character Management panel, show quick selection buttons for favorited characters": "En el panel de Gestión de Personajes, mostrar botones de selección rápida para personajes favoritos", - "Show tagged character folders in the character list": "Mostrar carpetas de personajes etiquetadas en la lista de personajes", + "Show tagged character folders in the character list": "Mostrar carpetas de personajes etiquetados en la lista de personajes", "Play a sound when a message generation finishes": "Reproducir un sonido cuando finaliza la generación de un mensaje", "Only play a sound when ST's browser tab is unfocused": "Solo reproducir un sonido cuando la pestaña del navegador de ST no está enfocada", "Reduce the formatting requirements on API URLs": "Reducir los requisitos de formato en las URL de API", - "Ask to import the World Info/Lorebook for every new character with embedded lorebook. If unchecked, a brief message will be shown instead": "Pedir importar la Información Mundial/Libro de Leyendas para cada nuevo personaje con un lorebook incrustado. Si no está marcado, se mostrará un mensaje breve en su lugar", + "Ask to import the World Info/Lorebook for every new character with embedded lorebook. If unchecked, a brief message will be shown instead": "Pedir importar Información de Mundo (WI)/Libro de Historia para cada nuevo personaje con un Libro de Historia incrustado. Si no está marcado, se mostrará un mensaje breve en su lugar", "Restore unsaved user input on page refresh": "Restaurar la entrada de usuario no guardada al actualizar la página", "Allow repositioning certain UI elements by dragging them. PC only, no effect on mobile": "Permitir reposicionar ciertos elementos de IU arrastrándolos. Solo PC, sin efecto en móviles", "MovingUI preset. Predefined/saved draggable positions": "Preconfiguración MovingUI. Posiciones arrastrables predefinidas/guardadas", @@ -581,7 +581,7 @@ "Apply a custom CSS style to all of the ST GUI": "Aplicar un estilo CSS personalizado a toda la GUI de ST", "Use fuzzy matching, and search characters in the list by all data fields, not just by a name substring": "Usar coincidencia difusa y buscar personajes en la lista por todos los campos de datos, no solo por una subcadena de nombre", "If checked and the character card contains a prompt override (System Prompt), use that instead": "Si está marcado y la tarjeta de personaje contiene una anulación de indicación (Indicación del sistema), usar eso en su lugar", - "If checked and the character card contains a jailbreak override (Post History Instruction), use that instead": "Si está marcado y la tarjeta de personaje contiene una anulación de jailbreak (Instrucción de Historial de Publicaciones), usar eso en su lugar", + "If checked and the character card contains a jailbreak override (Post History Instruction), use that instead": "Si está marcado y la tarjeta de personaje contiene una anulación de jailbreak (Instrucciones Post Historial), usar eso en su lugar", "Avoid cropping and resizing imported character images. When off, crop/resize to 400x600": "Evitar recortar y redimensionar imágenes de personajes importadas. Cuando esté desactivado, recortar/redimensionar a 400x600", "Show actual file names on the disk, in the characters list display only": "Mostrar nombres de archivo reales en el disco, solo en la visualización de la lista de personajes", "Prompt to import embedded card tags on character import. Otherwise embedded tags are ignored": "Solicitar importar etiquetas de tarjeta incrustadas al importar un personaje. De lo contrario, las etiquetas incrustadas se ignoran", @@ -590,7 +590,7 @@ "Show arrow buttons on the last in-chat message to generate alternative AI responses. Both PC and mobile": "Mostrar botones de flecha en el último mensaje del chat para generar respuestas alternativas de la IA. Tanto PC como móvil", "Allow using swiping gestures on the last in-chat message to trigger swipe generation. Mobile only, no effect on PC": "Permitir el uso de gestos de deslizamiento en el último mensaje del chat para activar la generación de deslizamiento. Solo móvil, sin efecto en PC", "Save edits to messages without confirmation as you type": "Guardar ediciones en mensajes sin confirmación mientras escribe", - "Render LaTeX and AsciiMath equation notation in chat messages. Powered by KaTeX": "Renderizar notación de ecuaciones LaTeX y AsciiMath en mensajes de chat. Alimentado por KaTeX", + "Render LaTeX and AsciiMath equation notation in chat messages. Powered by KaTeX": "Renderizar notación de ecuaciones LaTeX y AsciiMath en mensajes de chat. Impulsado por KaTeX", "Disalow embedded media from other domains in chat messages": "No permitir medios incrustados de otros dominios en mensajes de chat", "Skip encoding and characters in message text, allowing a subset of HTML markup as well as Markdown": "Omitir la codificación de los caracteres en el texto del mensaje, permitiendo un subconjunto de marcado HTML, así como Markdown", "Allow AI messages in groups to contain lines spoken by other group members": "Permitir que los mensajes de IA en grupos contengan líneas habladas por otros miembros del grupo", @@ -599,7 +599,7 @@ "Enable the auto-swipe function. Settings in this section only have an effect when auto-swipe is enabled": "Habilitar la función de deslizamiento automático. La configuración en esta sección solo tiene efecto cuando el deslizamiento automático está habilitado", "If the generated message is shorter than this, trigger an auto-swipe": "Si el mensaje generado es más corto que esto, activar un deslizamiento automático", "Reload and redraw the currently open chat": "Recargar y volver a dibujar el chat abierto actualmente", - "Auto-Expand Message Actions": "Expansión Automática de Acciones de Mensaje", + "Auto-Expand Message Actions": "Expandir Automáticamente de Acciones de Mensaje", "Not Connected": "No Conectado", "Persona Management": "Gestión de Personas", "Persona Description": "Descripción de Persona", @@ -609,16 +609,16 @@ "In Story String / Chat Completion: Before Character Card": "En la Cadena de Historia / Completado de Chat: Antes de la Tarjeta de Personaje", "In Story String / Chat Completion: After Character Card": "En la Cadena de Historia / Completado de Chat: Después de la Tarjeta de Personaje", "In Story String / Prompt Manager": "En la Cadena de Historia / Administrador de Indicaciones", - "Top of Author's Note": "Parte Superior de la Nota del Autor", - "Bottom of Author's Note": "Parte Inferior de la Nota del Autor", + "Top of Author's Note": "Parte Superior de la Nota de Autor", + "Bottom of Author's Note": "Parte Inferior de la Nota de Autor", "How do I use this?": "¿Cómo uso esto?", "More...": "Más...", - "Link to World Info": "Enlace a Información del Mundo", - "Import Card Lore": "Importar Lore de Tarjeta", + "Link to World Info": "Enlazar a Información de Mundo (WI)", + "Import Card Lore": "Importar Historia de Tarjeta", "Scenario Override": "Anulación de Escenario", "Rename": "Renombrar", "Character Description": "Descripción del Personaje", - "Creator's Notes": "Notas del Creador", + "Creator's Notes": "Nota del Creador", "A-Z": "A-Z", "Z-A": "Z-A", "Newest": "Más Reciente", @@ -628,11 +628,11 @@ "Most chats": "Más Chats", "Least chats": "Menos Chats", "Back": "Volver", - "Prompt Overrides (For OpenAI/Claude/Scale APIs, Window/OpenRouter, and Instruct mode)": "Sustituciones de Indicaciones (Para APIs de OpenAI/Claude/Scale, Ventana/OpenRouter y Modo Instrucción)", - "Insert {{original}} into either box to include the respective default prompt from system settings.": "Inserte {{original}} en cualquiera de las casillas para incluir la indicación predeterminada respectiva de la configuración del sistema.", - "Main Prompt": "Indicación Principal", + "Prompt Overrides (For OpenAI/Claude/Scale APIs, Window/OpenRouter, and Instruct mode)": "Anulaciones de Indicaciones (Para APIs de OpenAI/Claude/Scale, Window/OpenRouter y Modo Instrucción)", + "Insert {{original}} into either box to include the respective default prompt from system settings.": "Inserte {{original}} en cualquiera de las casillas para incluir las indicaciones predeterminadas respectivas de la configuración del sistema.", + "Main Prompt": "Indicaciones Principales", "Jailbreak": "Jailbreak", - "Creator's Metadata (Not sent with the AI prompt)": "Metadatos del Creador (No enviados con la indicación de la IA)", + "Creator's Metadata (Not sent with the AI prompt)": "Metadatos del Creador (No enviados con las indicaciones de la IA)", "Everything here is optional": "Todo aquí es opcional", "Created by": "Creado por", "Character Version": "Versión del Personaje", @@ -641,11 +641,11 @@ "Important to set the character's writing style.": "Importante para establecer el estilo de escritura del personaje.", "ATTENTION!": "¡ATENCIÓN!", "Samplers Order": "Orden de Muestreadores", - "Samplers will be applied in a top-down order. Use with caution.": "Los Muestreadores se aplicarán en un orden de arriba hacia abajo. Úselo con precaución.", + "Samplers will be applied in a top-down order. Use with caution.": "Los Muestreadores se aplicarán en un orden de arriba hacia abajo. Úsalo con precaución.", "Repetition Penalty": "Penalización por Repetición", "Rep. Pen. Range.": "Rango de Pen. Rep.", - "Rep. Pen. Freq.": "Frec. Pen. Rep.", - "Rep. Pen. Presence": "Presencia Pen. Rep.", + "Rep. Pen. Freq.": "Frec. de Pen. Rep.", + "Rep. Pen. Presence": "Presencia de Pen. Rep.", "Enter it in the box below:": "Introdúzcalo en la casilla de abajo:", "separate with commas w/o space between": "separe con comas sin espacio entre ellas", "Document": "Documento", @@ -658,7 +658,7 @@ "Editing:": "Editando:", "AI reply prefix": "Prefijo de Respuesta de IA", "Custom Stopping Strings": "Cadenas de Detención Personalizadas", - "JSON serialized array of strings": "Arreglo serializado JSON de cadenas", + "JSON serialized array of strings": "Arreglo de cadenas serializado en JSON", "words you dont want generated separated by comma ','": "palabras que no desea generar separadas por coma ','", "Extensions URL": "URL de Extensiones", "API Key": "Clave de API", @@ -670,10 +670,10 @@ "Chat Name (Optional)": "Nombre del Chat (Opcional)", "Filter...": "Filtrar...", "Search...": "Buscar...", - "Any contents here will replace the default Main Prompt used for this character. (v2 spec: system_prompt)": "Cualquier contenido aquí reemplazará la Indicación Principal predeterminada utilizada para este personaje. (v2 especificación: system_prompt)", - "Any contents here will replace the default Jailbreak Prompt used for this character. (v2 spec: post_history_instructions)": "Cualquier contenido aquí reemplazará la Indicación de Desbloqueo predeterminada utilizada para este personaje. (v2 especificación: post_history_instructions)", + "Any contents here will replace the default Main Prompt used for this character. (v2 spec: system_prompt)": "Cualquier contenido aquí reemplazará las Indicaciones Principales predeterminada utilizada para este personaje. (especificación v2: system_prompt)", + "Any contents here will replace the default Jailbreak Prompt used for this character. (v2 spec: post_history_instructions)": "Cualquier contenido aquí reemplazará las Indicaciones de Jailbreak predeterminada utilizada para este personaje. (especificación v2: post_history_instructions)", "(Botmaker's name / Contact Info)": "(Nombre del creador del bot / Información de contacto)", - "(If you want to track character versions)": "(Si desea rastrear las versiones de los personajes)", + "(If you want to track character versions)": "(Si desea rastrear versiones de personajes)", "(Describe the bot, give use tips, or list the chat models it has been tested on. This will be displayed in the character list.)": "(Describa el bot, dé consejos de uso o enumere los modelos de chat en los que se ha probado. Esto se mostrará en la lista de personajes.)", "(Write a comma-separated list of tags)": "(Escriba una lista de etiquetas separadas por comas)", "(A brief description of the personality)": "(Una breve descripción de la personalidad)", @@ -694,19 +694,19 @@ "Not connected to API!": "¡No conectado a la API!", "AI Response Configuration": "Configuración de Respuesta de IA", "AI Configuration panel will stay open": "El panel de Configuración de IA permanecerá abierto", - "Update current preset": "Actualizar la configuración actual", - "Create new preset": "Crear nueva configuración", - "Import preset": "Importar configuración", - "Export preset": "Exportar configuración", - "Delete the preset": "Eliminar la configuración", - "Auto-select this preset for Instruct Mode": "Auto-seleccionar esta configuración para el Modo Instrucción", - "Auto-select this preset on API connection": "Auto-seleccionar esta configuración en la conexión de la API", + "Update current preset": "Actualizar el preajuste actual", + "Create new preset": "Crear nuevo preajuste", + "Import preset": "Importar preajuste", + "Export preset": "Exportar preajuste", + "Delete the preset": "Eliminar el preajuste", + "Auto-select this preset for Instruct Mode": "Auto-seleccionar este preajuste para el Modo Instrucción", + "Auto-select this preset on API connection": "Auto-seleccionar este preajuste en la conexión de la API", "NSFW block goes first in the resulting prompt": "El bloque NSFW va primero en la indicación resultante", - "Enables OpenAI completion streaming": "Permite la transmisión de completado de OpenAI", + "Enables OpenAI completion streaming": "Permite streaming de completado de OpenAI", "Wrap user messages in quotes before sending": "Envolver los mensajes de usuario entre comillas antes de enviarlos", - "Restore default prompt": "Restaurar la indicación predeterminada", - "New preset": "Nueva configuración", - "Delete preset": "Eliminar configuración", + "Restore default prompt": "Restaurar las indicaciones predeterminada", + "New preset": "Nuevo preajuste", + "Delete preset": "Eliminar preajuste", "Restore default jailbreak": "Restaurar el jailbreak predeterminado", "Restore default reply": "Restaurar la respuesta predeterminada", "Restore default note": "Restaurar la nota predeterminada", @@ -715,21 +715,21 @@ "Clear your API key": "Borrar tu clave de API", "Refresh models": "Actualizar modelos", "Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai": "Obtenga su token de API de OpenRouter utilizando el flujo OAuth. Será redirigido a openrouter.ai", - "Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!": "Verifica su conexión de API enviando un breve mensaje de prueba. ¡Tenga en cuenta que se le acreditará por ello!", + "Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!": "Verifica su conexión de API enviando un breve mensaje de prueba. ¡Tenga en cuenta que se le cobrará por ello!", "Create New": "Crear Nuevo", "Edit": "Editar", "Locked = World Editor will stay open": "Bloqueado = El Editor de Mundo permanecerá abierto", "Entries can activate other entries by mentioning their keywords": "Las entradas pueden activar otras entradas mencionando sus palabras clave", - "Lookup for the entry keys in the context will respect the case": "La búsqueda de las claves de entrada en el contexto respetará el caso", + "Lookup for the entry keys in the context will respect the case": "La búsqueda de las claves de entrada en el contexto respetará mayúsculas y minúsculas", "If the entry key consists of only one word, it would not be matched as part of other words": "Si la clave de entrada consiste en solo una palabra, no se emparejará como parte de otras palabras", "Open all Entries": "Abrir Todas las Entradas", "Close all Entries": "Cerrar Todas las Entradas", "Create": "Crear", - "Import World Info": "Importar Información del Mundo", - "Export World Info": "Exportar Información del Mundo", - "Delete World Info": "Eliminar Información del Mundo", - "Duplicate World Info": "Duplicar Información del Mundo", - "Rename World Info": "Renombrar Información del Mundo", + "Import World Info": "Importar Información de Mundo (WI)", + "Export World Info": "Exportar Información de Mundo (WI)", + "Delete World Info": "Eliminar Información de Mundo (WI)", + "Duplicate World Info": "Duplicar Información de Mundo (WI)", + "Rename World Info": "Renombrar Información de Mundo (WI)", "Refresh": "Actualizar", "Primary Keywords": "Palabras Clave Primarias", "Logic": "Lógica", @@ -752,13 +752,13 @@ "Character Management": "Gestión de Personajes", "Locked = Character Management panel will stay open": "Bloqueado = El panel de Gestión de Personajes permanecerá abierto", "Select/Create Characters": "Seleccionar/Crear Personajes", - "Token counts may be inaccurate and provided just for reference.": "Las cuentas de tokens pueden ser inexactas y se proporcionan solo como referencia.", + "Token counts may be inaccurate and provided just for reference.": "El conteo de tokens pueden ser inexacto y se proporcionan solo como referencia.", "Click to select a new avatar for this character": "Haga clic para seleccionar un nuevo avatar para este personaje", "Example: [{{user}} is a 28-year-old Romanian cat girl.]": "Ejemplo: [{{user}} es una chica gata rumana de 28 años.]", "Toggle grid view": "Alternar vista de cuadrícula", "Add to Favorites": "Agregar a Favoritos", "Advanced Definition": "Definición Avanzada", - "Character Lore": "Trasfondo del personaje", + "Character Lore": "Historia (Trasfondo) del personaje", "Export and Download": "Exportar y descargar", "Duplicate Character": "Duplicar personaje", "Create Character": "Crear personaje", @@ -769,21 +769,21 @@ "Click to select a new avatar for this group": "Haz clic para seleccionar un nuevo avatar para este grupo", "Set a group chat scenario": "Establecer un escenario de chat grupal", "Restore collage avatar": "Restaurar avatar de collage", - "Create New Character": "Crear nuevo personaje", - "Import Character from File": "Importar personaje desde archivo", + "Create New Character": "Crear Nuevo Personaje", + "Import Character from File": "Importar Personaje desde Archivo", "Import content from external URL": "Importar contenido desde URL externa", - "Create New Chat Group": "Crear nuevo grupo de chat", + "Create New Chat Group": "Crear Nuevo Grupo de Chat", "Characters sorting order": "Orden de clasificación de personajes", "Add chat injection": "Agregar inyección de chat", "Remove injection": "Eliminar inyección", "Remove": "Eliminar", - "Select a World Info file for": "Seleccionar un archivo de Información Mundial para", - "Primary Lorebook": "Libro de historias primario", - "A selected World Info will be bound to this character as its own Lorebook.": "Una Información Mundial seleccionada se vinculará a este personaje como su propio Libro de historias.", + "Select a World Info file for": "Seleccionar un archivo de Información de Mundo (WI) para", + "Primary Lorebook": "Libro de Historia primario", + "A selected World Info will be bound to this character as its own Lorebook.": "Una Información de Mundo (WI) seleccionada se vinculará a este personaje como su propio Libro de Historia.", "When generating an AI reply, it will be combined with the entries from a global World Info selector.": "Al generar una respuesta de IA, se combinará con las entradas de un selector global de Información Mundial.", - "Exporting a character would also export the selected Lorebook file embedded in the JSON data.": "Exportar un personaje también exportaría el archivo de Libro de historias seleccionado incrustado en los datos JSON.", - "Additional Lorebooks": "Libros de historias adicionales", - "Associate one or more auxillary Lorebooks with this character.": "Asociar uno o más Libros de historias auxiliares con este personaje.", + "Exporting a character would also export the selected Lorebook file embedded in the JSON data.": "Exportar un personaje también exportaría el archivo de Libro de Historia seleccionado incrustado en los datos JSON.", + "Additional Lorebooks": "Libros de Historia Adicionales", + "Associate one or more auxillary Lorebooks with this character.": "Asociar uno o más Libros de Historia auxiliares con este personaje.", "NOTE: These choices are optional and won't be preserved on character export!": "NOTA: ¡Estas opciones son opcionales y no se conservarán al exportar el personaje!", "Rename chat file": "Renombrar archivo de chat", "Export JSONL chat file": "Exportar archivo de chat JSONL", @@ -815,19 +815,19 @@ "Abort request": "Cancelar solicitud", "Send a message": "Enviar un mensaje", "Ask AI to write your message for you": "Pídele a la IA que escriba tu mensaje por ti", - "Continue the last message": "Continuar con el último mensaje", + "Continue the last message": "Continuar el último mensaje", "Bind user name to that avatar": "Vincular nombre de usuario a ese avatar", - "Select this as default persona for the new chats.": "Seleccionar esto como persona predeterminada para los nuevos chats.", + "Select this as default persona for the new chats.": "Seleccionar esta persona como predeterminada para los nuevos chats.", "Change persona image": "Cambiar imagen de persona", "Delete persona": "Eliminar persona", "Reduced Motion": "Movimiento reducido", "Auto-select": "Auto-seleccionar", "Automatically select a background based on the chat context": "Seleccionar automáticamente un fondo basado en el contexto del chat", "Filter": "Filtro", - "Exclude message from prompts": "Excluir mensaje de indicaciones", - "Include message in prompts": "Incluir mensaje en indicaciones", + "Exclude message from prompts": "Excluir mensaje de las indicaciones", + "Include message in prompts": "Incluir mensaje en las indicaciones", "Create checkpoint": "Crear punto de control", - "Create Branch": "Crear rama", + "Create Branch": "Crear Rama", "Embed file or image": "Insertar archivo o imagen", "UI Theme": "Tema de interfaz de usuario", "This message is invisible for the AI": "Este mensaje es invisible para la IA", @@ -837,7 +837,7 @@ "Max Tokens Second": "Máximo de tokens por segundo", "CFG": "CFG", "No items": "Sin elementos", - "Extras API key (optional)": "Clave API de extras (opcional)", + "Extras API key (optional)": "Clave API de Extras (opcional)", "Notify on extension updates": "Notificar sobre actualizaciones de extensión", "Toggle character grid view": "Alternar vista de cuadrícula de personajes", "Bulk edit characters": "Editar personajes masivamente", @@ -854,7 +854,7 @@ "Most tokens": "Más tokens", "Least tokens": "Menos tokens", "Random": "Aleatorio", - "Skip Example Dialogues Formatting": "Omitir formato de diálogos de ejemplo", + "Skip Example Dialogues Formatting": "Omitir Formato de Diálogos de Ejemplo", "Import a theme file": "Importar un archivo de tema", "Export a theme file": "Exportar un archivo de tema", "Unlocked Context Size": "Tamaño de contexto desbloqueado", @@ -866,33 +866,33 @@ "Utility Prompts": "Indicaciones de utilidad", "Add character names": "Agregar nombres de personajes", "Send names in the message objects. Helps the model to associate messages with characters.": "Enviar nombres en los objetos de mensaje. Ayuda al modelo a asociar mensajes con personajes.", - "Continue prefill": "Continuar con prefiltro", + "Continue prefill": "Continuar con prellenado", "Continue sends the last message as assistant role instead of system message with instruction.": "Continuar envía el último mensaje como rol de asistente en lugar de mensaje del sistema con instrucciones.", "Squash system messages": "Aplastar mensajes del sistema", "Combines consecutive system messages into one (excluding example dialogues). May improve coherence for some models.": "Combina mensajes del sistema consecutivos en uno solo (excluyendo diálogos de ejemplo). Puede mejorar la coherencia para algunos modelos.", "Send inline images": "Enviar imágenes en línea", - "Assistant Prefill": "Prefiltro de asistente", + "Assistant Prefill": "Prellenado de Asistente", "Start Claude's answer with...": "Iniciar la respuesta de Claude con...", - "Use system prompt (Claude 2.1+ only)": "Usar indicación del sistema (solo Claude 2.1+)", - "Send the system prompt for supported models. If disabled, the user message is added to the beginning of the prompt.": "Enviar la indicación del sistema para los modelos admitidos. Si está desactivado, el mensaje del usuario se agrega al principio de la indicación.", + "Use system prompt (Claude 2.1+ only)": "Usar indicación del sistema (solo para Claude 2.1+)", + "Send the system prompt for supported models. If disabled, the user message is added to the beginning of the prompt.": "Enviar la indicación del sistema para los modelos admitidos. Si está desactivado, el mensaje del usuario se agrega al principio de las indicaciónes.", "Prompts": "Indicaciones", "Total Tokens:": "Tokens totales:", - "Insert prompt": "Insertar indicación", - "Delete prompt": "Eliminar indicación", + "Insert prompt": "Insertar indicaciones", + "Delete prompt": "Eliminar indicaciones", "Import a prompt list": "Importar una lista de indicaciones", "Export this prompt list": "Exportar esta lista de indicaciones", "Reset current character": "Restablecer personaje actual", - "New prompt": "Nueva indicación", + "New prompt": "Nuevas indicaciones", "Tokens": "Tokens", "Want to update?": "¿Quieres actualizar?", "How to start chatting?": "¿Cómo empezar a chatear?", "Click": "Haz clic ", - "and select a": "y selecciona un", + "and select a": "y selecciona una", "Chat API": " API de chat", "and pick a character": "y elige un personaje", "in the chat bar": "en la barra de chat", "Confused or lost?": "¿Confundido o perdido?", - "click these icons!": "¡haz clic en estos iconos!", + "click these icons!": "¡Haz clic en estos iconos!", "SillyTavern Documentation Site": "Sitio de documentación de SillyTavern", "Extras Installation Guide": "Guía de instalación de extras", "Still have questions?": "¿Todavía tienes preguntas?", @@ -909,12 +909,12 @@ "Medium": "Medio", "Aggressive": "Agresivo", "Very aggressive": "Muy agresivo", - "Eta cutoff is the main parameter of the special Eta Sampling technique. In units of 1e-4; a reasonable value is 3. Set to 0 to disable. See the paper Truncation Sampling as Language Model Desmoothing by Hewitt et al. (2022) for details.": "El corte de Eta es el parámetro principal de la técnica especial de Muestreo Eta. En unidades de 1e-4; un valor razonable es 3. Establecer en 0 para desactivar. Consulte el documento Truncation Sampling as Language Model Desmoothing de Hewitt et al. (2022) para más detalles.", - "Learn how to contribute your idle GPU cycles to the Horde": "Aprende cómo contribuir con tus ciclos de GPU inactivos a la Horda", + "Eta cutoff is the main parameter of the special Eta Sampling technique. In units of 1e-4; a reasonable value is 3. Set to 0 to disable. See the paper Truncation Sampling as Language Model Desmoothing by Hewitt et al. (2022) for details.": "El Corte de Eta es el parámetro principal de la técnica especial de Muestreo Eta. En unidades de 1e-4; un valor razonable es 3. Establecer en 0 para desactivar. Consulte el documento Truncation Sampling as Language Model Desmoothing de Hewitt et al. (2022) para más detalles.", + "Learn how to contribute your idle GPU cycles to the Horde": "Aprende cómo contribuir con tus ciclos de GPU inactivos a Horde", "Use the appropriate tokenizer for Google models via their API. Slower prompt processing, but offers much more accurate token counting.": "Usa el tokenizador apropiado para los modelos de Google a través de su API. Procesamiento de indicaciones más lento, pero ofrece un recuento de tokens mucho más preciso.", - "Load koboldcpp order": "Cargar orden koboldcpp", + "Load koboldcpp order": "Cargar orden de koboldcpp", "Use Google Tokenizer": "Usar Tokenizador de Google" -} \ No newline at end of file +} From f002b2d5cc378215400bf4265aa5e47d4ff52917 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Fri, 5 Apr 2024 22:15:50 +0300 Subject: [PATCH 23/80] #2016 Fix NovelAI endpoint --- src/endpoints/novelai.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/endpoints/novelai.js b/src/endpoints/novelai.js index e1158a304..c2def9f0e 100644 --- a/src/endpoints/novelai.js +++ b/src/endpoints/novelai.js @@ -6,6 +6,7 @@ const { readAllChunks, extractFileFromZipBuffer, forwardFetchResponse } = requir const { jsonParser } = require('../express-common'); const API_NOVELAI = 'https://api.novelai.net'; +const IMAGE_NOVELAI = 'https://image.novelai.net'; // Ban bracket generation, plus defaults const badWordsList = [ @@ -238,7 +239,7 @@ router.post('/generate-image', jsonParser, async (request, response) => { try { console.log('NAI Diffusion request:', request.body); - const generateUrl = `${API_NOVELAI}/ai/generate-image`; + const generateUrl = `${IMAGE_NOVELAI}/ai/generate-image`; const generateResult = await fetch(generateUrl, { method: 'POST', headers: { @@ -289,7 +290,7 @@ router.post('/generate-image', jsonParser, async (request, response) => { try { console.debug('Upscaling image...'); - const upscaleUrl = `${API_NOVELAI}/ai/upscale`; + const upscaleUrl = `${IMAGE_NOVELAI}/ai/upscale`; const upscaleResult = await fetch(upscaleUrl, { method: 'POST', headers: { From 0debe2ca4d85b4cec08dd16ba993aa2646990551 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Fri, 5 Apr 2024 22:17:29 +0300 Subject: [PATCH 24/80] they did only move the imagegen towards that api endpoint not upscaling --- src/endpoints/novelai.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/endpoints/novelai.js b/src/endpoints/novelai.js index c2def9f0e..e51f9c93a 100644 --- a/src/endpoints/novelai.js +++ b/src/endpoints/novelai.js @@ -290,7 +290,7 @@ router.post('/generate-image', jsonParser, async (request, response) => { try { console.debug('Upscaling image...'); - const upscaleUrl = `${IMAGE_NOVELAI}/ai/upscale`; + const upscaleUrl = `${API_NOVELAI}/ai/upscale`; const upscaleResult = await fetch(upscaleUrl, { method: 'POST', headers: { From d31e4a3bc4de2a663da2b326b0e181454e2a977e Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Fri, 5 Apr 2024 22:26:35 +0300 Subject: [PATCH 25/80] Bump package version --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8be17c4f4..dca9e969d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sillytavern", - "version": "1.11.6", + "version": "1.11.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sillytavern", - "version": "1.11.6", + "version": "1.11.7", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index 970b8efbc..d491384c0 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "type": "git", "url": "https://github.com/SillyTavern/SillyTavern.git" }, - "version": "1.11.6", + "version": "1.11.7", "scripts": { "start": "node server.js", "start-multi": "node server.js --disableCsrf", From e75f5550e31cedafed8f2d9aeace1ff284f51a89 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Fri, 5 Apr 2024 22:33:16 +0300 Subject: [PATCH 26/80] Add /classify command --- .../scripts/extensions/expressions/index.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/public/scripts/extensions/expressions/index.js b/public/scripts/extensions/expressions/index.js index 9ed19a71f..f6beb9375 100644 --- a/public/scripts/extensions/expressions/index.js +++ b/public/scripts/extensions/expressions/index.js @@ -885,6 +885,22 @@ async function setSpriteSetCommand(_, folder) { moduleWorker(); } +async function classifyCommand(_, text) { + if (!text) { + console.log('No text provided'); + return ''; + } + + if (!modules.includes('classify') && !extension_settings.expressions.local) { + toastr.warning('Text classification is disabled or not available'); + return ''; + } + + const label = getExpressionLabel(text); + console.debug(`Classification result for "${text}": ${label}`); + return label; +} + async function setSpriteSlashCommand(_, spriteId) { if (!spriteId) { console.log('No sprite id provided'); @@ -1758,5 +1774,6 @@ async function fetchImagesNoCache() { registerSlashCommand('sprite', setSpriteSlashCommand, ['emote'], '(spriteId) – force sets the sprite for the current character', true, true); registerSlashCommand('spriteoverride', setSpriteSetCommand, ['costume'], '(optional folder) – sets an override sprite folder for the current character. If the name starts with a slash or a backslash, selects a sub-folder in the character-named folder. Empty value to reset to default.', true, true); registerSlashCommand('lastsprite', (_, value) => lastExpression[value.trim()] ?? '', [], '(charName) – Returns the last set sprite / expression for the named character.', true, true); - registerSlashCommand('th', toggleTalkingHeadCommand, ['talkinghead'], '– Character Expressions: toggles Image Type - talkinghead (extras) on/off.'); + registerSlashCommand('th', toggleTalkingHeadCommand, ['talkinghead'], '– Character Expressions: toggles Image Type - talkinghead (extras) on/off.', true, true); + registerSlashCommand('classify', classifyCommand, [], '(text) – classifies the text and returns a label.', true, true); })(); From cdbd5c613008dfc762a2e886f777f4599ed6a467 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 6 Apr 2024 00:45:38 +0300 Subject: [PATCH 27/80] /classify help text clarity --- public/scripts/extensions/expressions/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/extensions/expressions/index.js b/public/scripts/extensions/expressions/index.js index f6beb9375..ac0493e37 100644 --- a/public/scripts/extensions/expressions/index.js +++ b/public/scripts/extensions/expressions/index.js @@ -1775,5 +1775,5 @@ async function fetchImagesNoCache() { registerSlashCommand('spriteoverride', setSpriteSetCommand, ['costume'], '(optional folder) – sets an override sprite folder for the current character. If the name starts with a slash or a backslash, selects a sub-folder in the character-named folder. Empty value to reset to default.', true, true); registerSlashCommand('lastsprite', (_, value) => lastExpression[value.trim()] ?? '', [], '(charName) – Returns the last set sprite / expression for the named character.', true, true); registerSlashCommand('th', toggleTalkingHeadCommand, ['talkinghead'], '– Character Expressions: toggles Image Type - talkinghead (extras) on/off.', true, true); - registerSlashCommand('classify', classifyCommand, [], '(text) – classifies the text and returns a label.', true, true); + registerSlashCommand('classify', classifyCommand, [], '(text) – performs an emotion classification of the given text and returns a label.', true, true); })(); From 6cf897219e86d6c90e4d6bfb7db14cf4f2dfafec Mon Sep 17 00:00:00 2001 From: johnbenac Date: Fri, 5 Apr 2024 18:41:36 -0400 Subject: [PATCH 28/80] Added toastr messages to tts index file --- public/scripts/extensions/tts/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/public/scripts/extensions/tts/index.js b/public/scripts/extensions/tts/index.js index b86e42ede..b093e9053 100644 --- a/public/scripts/extensions/tts/index.js +++ b/public/scripts/extensions/tts/index.js @@ -465,6 +465,7 @@ async function processAudioJobQueue() { playAudioData(currentAudioJob); talkingAnimation(true); } catch (error) { + toastr.error(error.toString()); console.error(error); audioQueueProcessorReady = true; } @@ -579,6 +580,7 @@ async function processTtsQueue() { } tts(text, voiceId, char); } catch (error) { + toastr.error(error.toString()); console.error(error); currentTtsJob = null; } @@ -648,6 +650,7 @@ function onRefreshClick() { initVoiceMap(); updateVoiceMap(); }).catch(error => { + toastr.error(error.toString()); console.error(error); setTtsStatus(error, false); }); From 3b6c32113f6d351a6971893bc70ee55fc4a8ba95 Mon Sep 17 00:00:00 2001 From: johnbenac Date: Fri, 5 Apr 2024 18:57:51 -0400 Subject: [PATCH 29/80] added await to tts to properly catch the error on this async function --- public/scripts/extensions/tts/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/extensions/tts/index.js b/public/scripts/extensions/tts/index.js index b093e9053..5b80b172b 100644 --- a/public/scripts/extensions/tts/index.js +++ b/public/scripts/extensions/tts/index.js @@ -578,7 +578,7 @@ async function processTtsQueue() { toastr.error(`Specified voice for ${char} was not found. Check the TTS extension settings.`); throw `Unable to attain voiceId for ${char}`; } - tts(text, voiceId, char); + await tts(text, voiceId, char); } catch (error) { toastr.error(error.toString()); console.error(error); From fe8f0a8ff2ff87ed2afada8d0f7c62f0ca9f22af Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Sat, 6 Apr 2024 07:14:45 +0200 Subject: [PATCH 30/80] Limit drawing of tags to 50 with expander - No matter where we draw tags, we'll draw a maximum of 50 tags - Filtered tags (selected, excluded) will always be drawn - Display "expander" icon/tag to show full tag list - Cache the full tag list display so consecutive redraws respect it --- public/css/tags.css | 5 +++ public/scripts/tags.js | 98 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 91 insertions(+), 12 deletions(-) diff --git a/public/css/tags.css b/public/css/tags.css index 9a3e02064..8c25eb1dd 100644 --- a/public/css/tags.css +++ b/public/css/tags.css @@ -73,6 +73,11 @@ background: none; } +.tag.placeholder-expander { + cursor: alias; + border: 0; +} + .tagListHint { align-self: center; display: flex; diff --git a/public/scripts/tags.js b/public/scripts/tags.js index 7edd2feab..b6b099099 100644 --- a/public/scripts/tags.js +++ b/public/scripts/tags.js @@ -118,6 +118,7 @@ const TAG_FOLDER_DEFAULT_TYPE = 'NONE'; * @property {function} [action] - An optional function that gets executed when this tag is an actionable tag and is clicked on. * @property {string} [class] - An optional css class added to the control representing this tag when printed. Used for custom tags in the filters. * @property {string} [icon] - An optional css class of an icon representing this tag when printed. This will replace the tag name with the icon. Used for custom tags in the filters. + * @property {string} [title] - An optional title for the tooltip of this tag. If there is no tooltip specified, and "icon" is chosen, the tooltip will be the "name" property. */ /** @@ -128,10 +129,17 @@ let tags = []; /** * A map representing the key of an entity (character avatar, group id, etc) with a corresponding array of tags this entity has assigned. The array might not exist if no tags were assigned yet. - * @type {Object.} + * @type {{[identifier: string]: string[]?}} */ let tag_map = {}; +/** + * A cache of all cut-off tag lists that got expanded until the last reload. They will be printed expanded again. + * It contains the key of the entity. + * @type {string[]} ids + */ +let expanded_tags_cache = []; + /** * Applies the basic filter for the current state of the tags and their selection on an entity list. * @param {Array} entities List of entities for display, consisting of tags, characters and groups. @@ -388,7 +396,7 @@ function getTagKey() { * Robust method to find a valid tag key for any entity. * * @param {object|number|string} entityOrKey An entity with id property (character, group, tag), or directly an id or tag key. - * @returns {string} The tag key that can be found. + * @returns {string|undefined} The tag key that can be found. */ export function getTagKeyForEntity(entityOrKey) { let x = entityOrKey; @@ -419,6 +427,30 @@ export function getTagKeyForEntity(entityOrKey) { return undefined; } +/** + * Checks for a tag key based on an entity for a given element. + * It checks the given element and upwards parents for a set character id (chid) or group id (grid), and if there is any, returns its unique entity key. + * + * @param {JQuery} element - The element to search the entity id on + * @returns {string|undefined} The tag key that can be found. + */ +export function getTagKeyForEntityElement(element) { + // Start with the given element and traverse up the DOM tree + while (element.length && element.parent().length) { + const grid = element.attr('grid'); + const chid = element.attr('chid'); + if (grid || chid) { + const id = grid || chid; + return getTagKeyForEntity(id); + } + + // Move up to the parent element + element = element.parent(); + } + + return undefined; +} + function addTagToMap(tagId, characterId = null) { const key = characterId !== null && characterId !== undefined ? getTagKeyForEntity(characterId) : getTagKey(); @@ -637,6 +669,16 @@ function printTagList(element, { tags = undefined, addTag = undefined, forEntity const customAction = typeof tagActionSelector === 'function' ? tagActionSelector : null; + // Well, lets check if the tag list was expanded. Based on either a css class, or when any expand was clicked yet, then we search whether this element id matches + const expanded = element.hasClass('tags-expanded') || (expanded_tags_cache.length && expanded_tags_cache.indexOf(key ?? getTagKeyForEntityElement(element)) >= 0); + + // We prepare some stuff. No matter which list we have, there is a maximum value of tags we are going to display + const TAGS_LIMIT = 50; + const MAX_TAGS = !expanded ? TAGS_LIMIT : Number.MAX_VALUE; + let totalPrinted = 0; + let hiddenTags = 0; + const filterActive = (/** @type {Tag} */ tag) => tag.filter_state && !isFilterState(tag.filter_state, FILTER_STATES.UNDEFINED); + for (const tag of printableTags) { // If we have a custom action selector, we override that tag options for each tag if (customAction) { @@ -648,7 +690,36 @@ function printTagList(element, { tags = undefined, addTag = undefined, forEntity } } - appendTagToList(element, tag, tagOptions); + // Check if we should print this tag + if (totalPrinted++ < MAX_TAGS || filterActive(tag)) { + appendTagToList(element, tag, tagOptions); + } else { + hiddenTags++; + } + } + + // After the loop, check if we need to add the placeholder. + // The placehold if clicked expands the tags and remembers either via class or cache array which was expanded, so it'll stay expanded until the next reload. + if (hiddenTags > 0) { + const id = "placeholder_" + uuidv4(); + + // Add click event + const showHiddenTags = (event) => { + const elementKey = key ?? getTagKeyForEntityElement(element); + console.log(`Hidden tags shown for element ${elementKey}`); + + // Mark the current char/group as expanded if we were in any. This will be kept in memory until reload + element.addClass('tags-expanded'); + expanded_tags_cache.push(elementKey); + + // Do not bubble further, we are just expanding + event.stopPropagation(); + printTagList(element, { tags: tags, addTag: addTag, forEntityOrKey: forEntityOrKey, empty: empty, tagActionSelector: tagActionSelector, tagOptions: tagOptions }); + }; + + /** @type {Tag} */ + const placeholderTag = { id: id, name: "...", title: `${hiddenTags} tags not displayed.\n\nClick to expand remaining tags.`, color: 'transparent', action: showHiddenTags, class: 'placeholder-expander' }; + appendTagToList(element, placeholderTag, tagOptions); } } @@ -682,13 +753,19 @@ function appendTagToList(listElement, tag, { removable = false, selectable = fal if (tag.class) { tagElement.addClass(tag.class); } - + if (tag.title) { + tagElement.attr('title', tag.title); + } if (tag.icon) { - tagElement.find('.tag_name').text('').attr('title', tag.name).addClass(tag.icon); + tagElement.find('.tag_name').text('').attr('title', `${tag.name} ${tag.title}`.trim()).addClass(tag.icon); + tagElement.addClass('actionable'); } + // We could have multiple ways of actions passed in. The manual arguments have precendence in front of a specified tag action + const clickableAction = action ?? tag.action; + // If this is a tag for a general list and its either selectable or actionable, lets mark its current state - if ((selectable || action) && isGeneralList) { + if ((selectable || clickableAction) && isGeneralList) { toggleTagThreeState(tagElement, { stateOverride: tag.filter_state ?? DEFAULT_FILTER_STATE }); } @@ -696,14 +773,11 @@ function appendTagToList(listElement, tag, { removable = false, selectable = fal tagElement.on('click', () => onTagFilterClick.bind(tagElement)(listElement)); } - if (action) { + if (clickableAction) { const filter = getFilterHelper($(listElement)); - tagElement.on('click', () => action.bind(tagElement)(filter)); - tagElement.addClass('actionable'); + tagElement.on('click', (e) => clickableAction.bind(tagElement)(e, filter)); + tagElement.addClass('clickable-action'); } - /*if (action && tag.id === 2) { - tagElement.addClass('innerActionable hidden'); - }*/ $(listElement).append(tagElement); } From 9805215c284e33383e498dc69184c77cd27a9fd4 Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Sat, 6 Apr 2024 07:37:30 +0200 Subject: [PATCH 31/80] Fix expander button and group tags add - Fix expander button to never be "removable" in any list - Fix group tag list to actually work on adding tags --- public/scripts/tags.js | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/public/scripts/tags.js b/public/scripts/tags.js index b6b099099..58910de03 100644 --- a/public/scripts/tags.js +++ b/public/scripts/tags.js @@ -649,15 +649,16 @@ function createNewTag(tagName) { /** * Prints the list of tags * - * @param {JQuery} element - The container element where the tags are to be printed. + * @param {JQuery|string} element - The container element where the tags are to be printed. (Optionally can also be a string selector for the element, which will then be resolved) * @param {PrintTagListOptions} [options] - Optional parameters for printing the tag list. */ function printTagList(element, { tags = undefined, addTag = undefined, forEntityOrKey = undefined, empty = true, tagActionSelector = undefined, tagOptions = {} } = {}) { + const $element = (typeof element === "string") ? $(element) : element; const key = forEntityOrKey !== undefined ? getTagKeyForEntity(forEntityOrKey) : getTagKey(); let printableTags = tags ? (typeof tags === 'function' ? tags() : tags) : getTagsList(key); if (empty === 'always' || (empty && (printableTags?.length > 0 || key))) { - $(element).empty(); + $element.empty(); } if (addTag && (tagOptions.skipExistsCheck || !printableTags.some(x => x.id === addTag.id))) { @@ -670,7 +671,7 @@ function printTagList(element, { tags = undefined, addTag = undefined, forEntity const customAction = typeof tagActionSelector === 'function' ? tagActionSelector : null; // Well, lets check if the tag list was expanded. Based on either a css class, or when any expand was clicked yet, then we search whether this element id matches - const expanded = element.hasClass('tags-expanded') || (expanded_tags_cache.length && expanded_tags_cache.indexOf(key ?? getTagKeyForEntityElement(element)) >= 0); + const expanded = $element.hasClass('tags-expanded') || (expanded_tags_cache.length && expanded_tags_cache.indexOf(key ?? getTagKeyForEntityElement(element)) >= 0); // We prepare some stuff. No matter which list we have, there is a maximum value of tags we are going to display const TAGS_LIMIT = 50; @@ -692,7 +693,7 @@ function printTagList(element, { tags = undefined, addTag = undefined, forEntity // Check if we should print this tag if (totalPrinted++ < MAX_TAGS || filterActive(tag)) { - appendTagToList(element, tag, tagOptions); + appendTagToList($element, tag, tagOptions); } else { hiddenTags++; } @@ -705,21 +706,25 @@ function printTagList(element, { tags = undefined, addTag = undefined, forEntity // Add click event const showHiddenTags = (event) => { - const elementKey = key ?? getTagKeyForEntityElement(element); + const elementKey = key ?? getTagKeyForEntityElement($element); console.log(`Hidden tags shown for element ${elementKey}`); // Mark the current char/group as expanded if we were in any. This will be kept in memory until reload - element.addClass('tags-expanded'); + $element.addClass('tags-expanded'); expanded_tags_cache.push(elementKey); // Do not bubble further, we are just expanding event.stopPropagation(); - printTagList(element, { tags: tags, addTag: addTag, forEntityOrKey: forEntityOrKey, empty: empty, tagActionSelector: tagActionSelector, tagOptions: tagOptions }); + printTagList($element, { tags: tags, addTag: addTag, forEntityOrKey: forEntityOrKey, empty: empty, tagActionSelector: tagActionSelector, tagOptions: tagOptions }); }; + // Print the placeholder object with its styling and action to show the remaining tags /** @type {Tag} */ const placeholderTag = { id: id, name: "...", title: `${hiddenTags} tags not displayed.\n\nClick to expand remaining tags.`, color: 'transparent', action: showHiddenTags, class: 'placeholder-expander' }; - appendTagToList(element, placeholderTag, tagOptions); + // It should never be marked as a removable tag, because it's just an expander action + /** @type {TagOptions} */ + const placeholderTagOptions = { ...tagOptions, removable: false }; + appendTagToList($element, placeholderTag, placeholderTagOptions); } } From 30b9b13070cf2c8a4330ba2ba95760f416a881a9 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 6 Apr 2024 14:48:59 +0300 Subject: [PATCH 32/80] Use integers for max value. This is helpful if someone has to render more than 9 quadrillion tags --- public/scripts/tags.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/public/scripts/tags.js b/public/scripts/tags.js index 58910de03..d08b170f4 100644 --- a/public/scripts/tags.js +++ b/public/scripts/tags.js @@ -431,10 +431,13 @@ export function getTagKeyForEntity(entityOrKey) { * Checks for a tag key based on an entity for a given element. * It checks the given element and upwards parents for a set character id (chid) or group id (grid), and if there is any, returns its unique entity key. * - * @param {JQuery} element - The element to search the entity id on + * @param {JQuery|string} element - The element to search the entity id on * @returns {string|undefined} The tag key that can be found. */ export function getTagKeyForEntityElement(element) { + if (typeof element === 'string') { + element = $(element); + } // Start with the given element and traverse up the DOM tree while (element.length && element.parent().length) { const grid = element.attr('grid'); @@ -653,7 +656,7 @@ function createNewTag(tagName) { * @param {PrintTagListOptions} [options] - Optional parameters for printing the tag list. */ function printTagList(element, { tags = undefined, addTag = undefined, forEntityOrKey = undefined, empty = true, tagActionSelector = undefined, tagOptions = {} } = {}) { - const $element = (typeof element === "string") ? $(element) : element; + const $element = (typeof element === 'string') ? $(element) : element; const key = forEntityOrKey !== undefined ? getTagKeyForEntity(forEntityOrKey) : getTagKey(); let printableTags = tags ? (typeof tags === 'function' ? tags() : tags) : getTagsList(key); @@ -675,7 +678,7 @@ function printTagList(element, { tags = undefined, addTag = undefined, forEntity // We prepare some stuff. No matter which list we have, there is a maximum value of tags we are going to display const TAGS_LIMIT = 50; - const MAX_TAGS = !expanded ? TAGS_LIMIT : Number.MAX_VALUE; + const MAX_TAGS = !expanded ? TAGS_LIMIT : Number.MAX_SAFE_INTEGER; let totalPrinted = 0; let hiddenTags = 0; const filterActive = (/** @type {Tag} */ tag) => tag.filter_state && !isFilterState(tag.filter_state, FILTER_STATES.UNDEFINED); @@ -702,7 +705,7 @@ function printTagList(element, { tags = undefined, addTag = undefined, forEntity // After the loop, check if we need to add the placeholder. // The placehold if clicked expands the tags and remembers either via class or cache array which was expanded, so it'll stay expanded until the next reload. if (hiddenTags > 0) { - const id = "placeholder_" + uuidv4(); + const id = 'placeholder_' + uuidv4(); // Add click event const showHiddenTags = (event) => { @@ -720,7 +723,7 @@ function printTagList(element, { tags = undefined, addTag = undefined, forEntity // Print the placeholder object with its styling and action to show the remaining tags /** @type {Tag} */ - const placeholderTag = { id: id, name: "...", title: `${hiddenTags} tags not displayed.\n\nClick to expand remaining tags.`, color: 'transparent', action: showHiddenTags, class: 'placeholder-expander' }; + const placeholderTag = { id: id, name: '...', title: `${hiddenTags} tags not displayed.\n\nClick to expand remaining tags.`, color: 'transparent', action: showHiddenTags, class: 'placeholder-expander' }; // It should never be marked as a removable tag, because it's just an expander action /** @type {TagOptions} */ const placeholderTagOptions = { ...tagOptions, removable: false }; From 495cf5d9ca2c1bb96ac9858be9af692ed37f0ae4 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 6 Apr 2024 15:47:11 +0300 Subject: [PATCH 33/80] Move context formatting help link --- public/index.html | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/public/index.html b/public/index.html index 107674261..352885649 100644 --- a/public/index.html +++ b/public/index.html @@ -2758,15 +2758,17 @@

    Advanced Formatting - - -

    - Context Template +
    + Context Template + + + +
    From c752a54c6273bf1fb54a09c6c5e62e0454a6422b Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 6 Apr 2024 20:14:55 +0300 Subject: [PATCH 34/80] Fix actionable filters --- public/scripts/tags.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/tags.js b/public/scripts/tags.js index d08b170f4..a7422a888 100644 --- a/public/scripts/tags.js +++ b/public/scripts/tags.js @@ -783,7 +783,7 @@ function appendTagToList(listElement, tag, { removable = false, selectable = fal if (clickableAction) { const filter = getFilterHelper($(listElement)); - tagElement.on('click', (e) => clickableAction.bind(tagElement)(e, filter)); + tagElement.on('click', (e) => clickableAction.bind(tagElement)(filter)); tagElement.addClass('clickable-action'); } From 866f514d19c0fc08041ed3bd624914f6a2ee7283 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 6 Apr 2024 20:17:58 +0300 Subject: [PATCH 35/80] Fix undefined in tag tooltip --- public/scripts/tags.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/tags.js b/public/scripts/tags.js index a7422a888..5fc477b02 100644 --- a/public/scripts/tags.js +++ b/public/scripts/tags.js @@ -765,7 +765,7 @@ function appendTagToList(listElement, tag, { removable = false, selectable = fal tagElement.attr('title', tag.title); } if (tag.icon) { - tagElement.find('.tag_name').text('').attr('title', `${tag.name} ${tag.title}`.trim()).addClass(tag.icon); + tagElement.find('.tag_name').text('').attr('title', `${tag.name} ${tag.title || ''}`.trim()).addClass(tag.icon); tagElement.addClass('actionable'); } From fcc8051d38238fc99c5080915c47dac3e52c10d1 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Sat, 6 Apr 2024 20:35:25 +0300 Subject: [PATCH 36/80] Fix event propagation --- public/scripts/tags.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/scripts/tags.js b/public/scripts/tags.js index 5fc477b02..b1c40c020 100644 --- a/public/scripts/tags.js +++ b/public/scripts/tags.js @@ -708,7 +708,7 @@ function printTagList(element, { tags = undefined, addTag = undefined, forEntity const id = 'placeholder_' + uuidv4(); // Add click event - const showHiddenTags = (event) => { + const showHiddenTags = (_, event) => { const elementKey = key ?? getTagKeyForEntityElement($element); console.log(`Hidden tags shown for element ${elementKey}`); @@ -783,7 +783,7 @@ function appendTagToList(listElement, tag, { removable = false, selectable = fal if (clickableAction) { const filter = getFilterHelper($(listElement)); - tagElement.on('click', (e) => clickableAction.bind(tagElement)(filter)); + tagElement.on('click', (e) => clickableAction.bind(tagElement)(filter, e)); tagElement.addClass('clickable-action'); } From d5cb4f403af747bc837aff244243ac0f42baf4d6 Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Sun, 7 Apr 2024 00:06:38 +0200 Subject: [PATCH 37/80] Fix currently reswiped messages being returned on macros - Build one utility function to get the last message id matching a criteria - Fix all macros referencing last message still returning a now being reswiped message --- public/scripts/macros.js | 147 +++++++++++++++++---------------------- 1 file changed, 64 insertions(+), 83 deletions(-) diff --git a/public/scripts/macros.js b/public/scripts/macros.js index 504b05596..a9df01987 100644 --- a/public/scripts/macros.js +++ b/public/scripts/macros.js @@ -7,122 +7,103 @@ import { replaceVariableMacros } from './variables.js'; // Register any macro that you want to leave in the compiled story string Handlebars.registerHelper('trim', () => '{{trim}}'); -/** - * Returns the ID of the last message in the chat. - * @returns {string} The ID of the last message in the chat. - */ -function getLastMessageId() { - const index = chat?.length - 1; - if (!isNaN(index) && index >= 0) { - return String(index); +/** + * Returns the ID of the last message in the chat + * + * Optionally can only choose specific messages, if a filter is provided. + * + * @param {object} param0 - Optional arguments + * @param {boolean} [param0.exclude_swipe_in_propress=true] - Whether a message that is currently being swiped should be ignored + * @param {function(object):boolean} [param0.filter] - A filter applied to the search, ignoring all messages that don't match the criteria. For example to only find user messages, etc. + * @returns {number|null} The message id, or null if none was found + */ +export function getLastMessageId({ exclude_swipe_in_propress = true, filter = null } = {}) { + for (let i = chat?.length - 1; i >= 0; i--) { + let message = chat[i]; + + // If ignoring swipes and the message is being swiped, continue + // We can check if a message is being swiped by checking whether the current swipe id is not in the list of finished swipes yet + if (exclude_swipe_in_propress && message.swipes && message.swipe_id >= message.swipes.length) { + continue; + } + + // Check if no filter is provided, or if the message passes the filter + if (!filter || filter(message)) { + return i; + } } - return ''; + return null; } /** - * Returns the ID of the first message included in the context. - * @returns {string} The ID of the first message in the context. + * Returns the ID of the first message included in the context + * + * @returns {number|null} The ID of the first message in the context */ function getFirstIncludedMessageId() { - const index = document.querySelector('.lastInContext')?.getAttribute('mesid'); + const index = Number(document.querySelector('.lastInContext')?.getAttribute('mesid')); if (!isNaN(index) && index >= 0) { - return String(index); + return index; } - return ''; + return null; } /** - * Returns the last message in the chat. - * @returns {string} The last message in the chat. + * Returns the last message in the chat + * + * @returns {string} The last message in the chat */ function getLastMessage() { - const index = chat?.length - 1; - - if (!isNaN(index) && index >= 0) { - return chat[index].mes; - } - - return ''; + const mid = getLastMessageId(); + return chat[mid]?.mes ?? ''; } /** - * Returns the last message from the user. - * @returns {string} The last message from the user. + * Returns the last message from the user + * + * @returns {string} The last message from the user */ function getLastUserMessage() { - if (!Array.isArray(chat) || chat.length === 0) { - return ''; - } - - for (let i = chat.length - 1; i >= 0; i--) { - if (chat[i].is_user && !chat[i].is_system) { - return chat[i].mes; - } - } - - return ''; + const mid = getLastMessageId({ filter: m => m.is_user && !m.is_system }); + return chat[mid]?.mes ?? ''; } /** - * Returns the last message from the bot. - * @returns {string} The last message from the bot. + * Returns the last message from the bot + * + * @returns {string} The last message from the bot */ function getLastCharMessage() { - if (!Array.isArray(chat) || chat.length === 0) { - return ''; - } - - for (let i = chat.length - 1; i >= 0; i--) { - if (!chat[i].is_user && !chat[i].is_system) { - return chat[i].mes; - } - } - - return ''; + const mid = getLastMessageId({ filter: m => !m.is_user && !m.is_system }); + return chat[mid]?.mes ?? ''; } /** - * Returns the ID of the last swipe. - * @returns {string} The 1-based ID of the last swipe + * Returns the 1-based ID (number) of the last swipe + * + * @returns {number|null} The 1-based ID of the last swipe */ function getLastSwipeId() { - const index = chat?.length - 1; - - if (!isNaN(index) && index >= 0) { - const swipes = chat[index].swipes; - - if (!Array.isArray(swipes) || swipes.length === 0) { - return ''; - } - - return String(swipes.length); - } - - return ''; + // For swipe macro, we are accepting using the message that is currently being swiped + const mid = getLastMessageId({ exclude_swipe_in_propress: false }); + const swipes = chat[mid]?.swipes; + return swipes?.length; } /** - * Returns the ID of the current swipe. - * @returns {string} The 1-based ID of the current swipe. + * Returns the 1-based ID (number) of the current swipe + * + * @returns {number|null} The 1-based ID of the current swipe */ function getCurrentSwipeId() { - const index = chat?.length - 1; - - if (!isNaN(index) && index >= 0) { - const swipeId = chat[index].swipe_id; - - if (swipeId === undefined || isNaN(swipeId)) { - return ''; - } - - return String(swipeId + 1); - } - - return ''; + // For swipe macro, we are accepting using the message that is currently being swiped + const mid = getLastMessageId({ exclude_swipe_in_propress: false }); + const swipeId = chat[mid]?.swipe_id; + return swipeId ? swipeId + 1 : null; } /** @@ -292,12 +273,12 @@ export function evaluateMacros(content, env) { content = content.replace(/{{maxPrompt}}/gi, () => String(getMaxContextSize())); content = content.replace(/{{lastMessage}}/gi, () => getLastMessage()); - content = content.replace(/{{lastMessageId}}/gi, () => getLastMessageId()); + content = content.replace(/{{lastMessageId}}/gi, () => String(getLastMessageId() ?? '')); content = content.replace(/{{lastUserMessage}}/gi, () => getLastUserMessage()); content = content.replace(/{{lastCharMessage}}/gi, () => getLastCharMessage()); - content = content.replace(/{{firstIncludedMessageId}}/gi, () => getFirstIncludedMessageId()); - content = content.replace(/{{lastSwipeId}}/gi, () => getLastSwipeId()); - content = content.replace(/{{currentSwipeId}}/gi, () => getCurrentSwipeId()); + content = content.replace(/{{firstIncludedMessageId}}/gi, () => String(getFirstIncludedMessageId() ?? '')); + content = content.replace(/{{lastSwipeId}}/gi, () => String(getLastSwipeId() ?? '')); + content = content.replace(/{{currentSwipeId}}/gi, () => String(getCurrentSwipeId() ?? '')); content = content.replace(/\{\{\/\/([\s\S]*?)\}\}/gm, ''); From 5df454640f2b7f3249d168cd3e246985ba3c2740 Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Sun, 7 Apr 2024 01:57:59 +0200 Subject: [PATCH 38/80] Fix forced persona name not being added to examples --- public/scripts/instruct-mode.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/public/scripts/instruct-mode.js b/public/scripts/instruct-mode.js index 7fc924274..3167f3bb5 100644 --- a/public/scripts/instruct-mode.js +++ b/public/scripts/instruct-mode.js @@ -419,10 +419,13 @@ export function formatInstructModeExamples(mesExamplesArray, name1, name2) { } for (const example of blockExamples) { + // If force group/persona names is set, we should override the include names for the user placeholder + const includeThisName = includeNames || (power_user.instruct.names_force_groups && example.name == 'example_user'); + const prefix = example.name == 'example_user' ? inputPrefix : outputPrefix; const suffix = example.name == 'example_user' ? inputSuffix : outputSuffix; const name = example.name == 'example_user' ? name1 : name2; - const messageContent = includeNames ? `${name}: ${example.content}` : example.content; + const messageContent = includeThisName ? `${name}: ${example.content}` : example.content; const formattedMessage = [prefix, messageContent + suffix].filter(x => x).join(separator); formattedExamples.push(formattedMessage); } From 9c3f9f0ee1d9474e113f30a6ceae9a091bca115b Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Sun, 7 Apr 2024 03:57:58 +0200 Subject: [PATCH 39/80] Fix empty lines on group squashing of char fields --- public/scripts/group-chats.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js index 412f52aaa..2b5267ff2 100644 --- a/public/scripts/group-chats.js +++ b/public/scripts/group-chats.js @@ -378,10 +378,10 @@ export function getGroupCharacterCards(groupId, characterId) { mesExamplesArray.push(baseChatReplace(character.mes_example.trim(), name1, character.name)); } - const description = descriptions.join('\n'); - const personality = personalities.join('\n'); - const scenario = scenarioOverride?.trim() || scenarios.join('\n'); - const mesExamples = mesExamplesArray.join('\n'); + const description = descriptions.filter(x => x.length).join('\n'); + const personality = personalities.filter(x => x.length).join('\n'); + const scenario = scenarioOverride?.trim() || scenarios.filter(x => x.length).join('\n'); + const mesExamples = mesExamplesArray.filter(x => x.length).join('\n'); return { description, personality, scenario, mesExamples }; } From 82a30b6ba53a121be1daa83e6010a6d4d49bbf24 Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Sun, 7 Apr 2024 04:40:15 +0200 Subject: [PATCH 40/80] Fix pick macro rerolling on branches/renames --- public/scripts/macros.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/public/scripts/macros.js b/public/scripts/macros.js index a9df01987..ff3aff0d7 100644 --- a/public/scripts/macros.js +++ b/public/scripts/macros.js @@ -1,8 +1,9 @@ -import { chat, main_api, getMaxContextSize, getCurrentChatId } from '../script.js'; +import { chat, chat_metadata, main_api, getMaxContextSize, getCurrentChatId } from '../script.js'; import { timestampToMoment, isDigitsOnly, getStringHash } from './utils.js'; import { textgenerationwebui_banned_in_macros } from './textgen-settings.js'; import { replaceInstructMacros } from './instruct-mode.js'; import { replaceVariableMacros } from './variables.js'; +import { saveMetadataDebounced } from './extensions.js'; // Register any macro that you want to leave in the compiled story string Handlebars.registerHelper('trim', () => '{{trim}}'); @@ -186,7 +187,13 @@ function randomReplace(input, emptyListPlaceholder = '') { function pickReplace(input, rawContent, emptyListPlaceholder = '') { const pickPattern = /{{pick\s?::?([^}]+)}}/gi; - const chatIdHash = getStringHash(getCurrentChatId()); + + // We need to have a consistent chat hash, otherwise we'll lose rolls on chat file rename or branch switches + const chatIdHash = chat_metadata['chat_id_hash']; + if (!chatIdHash) { + chat_metadata['chat_id_hash'] = getStringHash(getCurrentChatId()); + saveMetadataDebounced(); + } const rawContentHash = getStringHash(rawContent); return input.replace(pickPattern, (match, listString, offset) => { From 2ffb44b4e13acdcc46a51ab283c086b678c27d88 Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Sun, 7 Apr 2024 06:12:52 +0200 Subject: [PATCH 41/80] Add extension setting for fallback expression --- .../scripts/extensions/expressions/index.js | 66 +++++++++++++++---- .../extensions/expressions/settings.html | 5 ++ 2 files changed, 58 insertions(+), 13 deletions(-) diff --git a/public/scripts/extensions/expressions/index.js b/public/scripts/extensions/expressions/index.js index ac0493e37..f73c3325a 100644 --- a/public/scripts/extensions/expressions/index.js +++ b/public/scripts/extensions/expressions/index.js @@ -11,7 +11,7 @@ const MODULE_NAME = 'expressions'; const UPDATE_INTERVAL = 2000; const STREAMING_UPDATE_INTERVAL = 6000; const TALKINGCHECK_UPDATE_INTERVAL = 500; -const FALLBACK_EXPRESSION = 'joy'; +const DEFAULT_FALLBACK_EXPRESSION = 'joy'; const DEFAULT_EXPRESSIONS = [ 'talkinghead', 'admiration', @@ -58,6 +58,14 @@ function isTalkingHeadEnabled() { return extension_settings.expressions.talkinghead && !extension_settings.expressions.local; } +/** + * Returns the fallback expression if explicitly chosen, otherwise the default one + * @returns {string} expression name + */ +function getFallbackExpression() { + return extension_settings.expressions.fallback_expression ?? DEFAULT_FALLBACK_EXPRESSION; +} + /** * Toggles Talkinghead mode on/off. * @@ -157,7 +165,8 @@ async function visualNovelSetCharacterSprites(container, name, expression) { const sprites = spriteCache[spriteFolderName]; const expressionImage = container.find(`.expression-holder[data-avatar="${avatar}"]`); - const defaultSpritePath = sprites.find(x => x.label === FALLBACK_EXPRESSION)?.path; + const defaultExpression = getFallbackExpression(); + const defaultSpritePath = sprites.find(x => x.label === defaultExpression)?.path; const noSprites = sprites.length === 0; if (expressionImage.length > 0) { @@ -568,7 +577,7 @@ function handleImageChange() { // This preserves the same expression Talkinghead had at the moment it was switched off. const charName = getContext().name2; const last = lastExpression[charName]; - const targetExpression = last ? last : FALLBACK_EXPRESSION; + const targetExpression = last ? last : getFallbackExpression(); setExpression(charName, targetExpression, true); } } @@ -691,8 +700,8 @@ async function moduleWorker() { const force = !!context.groupId; // Character won't be angry on you for swiping - if (currentLastMessage.mes == '...' && expressionsList.includes(FALLBACK_EXPRESSION)) { - expression = FALLBACK_EXPRESSION; + if (currentLastMessage.mes == '...' && expressionsList.includes(getFallbackExpression())) { + expression = getFallbackExpression(); } await sendExpressionCall(spriteFolderName, expression, force, vnMode); @@ -965,7 +974,7 @@ function sampleClassifyText(text) { async function getExpressionLabel(text) { // Return if text is undefined, saving a costly fetch request if ((!modules.includes('classify') && !extension_settings.expressions.local) || !text) { - return FALLBACK_EXPRESSION; + return getFallbackExpression(); } text = sampleClassifyText(text); @@ -1004,7 +1013,7 @@ async function getExpressionLabel(text) { } } catch (error) { console.log(error); - return FALLBACK_EXPRESSION; + return getFallbackExpression(); } } @@ -1108,6 +1117,11 @@ async function getSpritesList(name) { } } +async function renderAdditionalExpressionSettings() { + renderCustomExpressions(); + await renderFallbackExpressionPicker(); +} + function renderCustomExpressions() { if (!Array.isArray(extension_settings.expressions.custom)) { extension_settings.expressions.custom = []; @@ -1128,6 +1142,23 @@ function renderCustomExpressions() { } } +async function renderFallbackExpressionPicker() { + const expressions = await getExpressionsList(); + + const defaultPicker = $('#expression_fallback'); + defaultPicker.empty(); + + const fallbackExpression = getFallbackExpression(); + + for (const expression of expressions) { + const option = document.createElement('option'); + option.value = expression; + option.text = expression; + option.selected = expression == fallbackExpression; + defaultPicker.append(option); + } +} + async function getExpressionsList() { // Return cached list if available if (Array.isArray(expressionsList)) { @@ -1365,7 +1396,7 @@ async function onClickExpressionAddCustom() { // Add custom expression into settings extension_settings.expressions.custom.push(expressionName); - renderCustomExpressions(); + await renderAdditionalExpressionSettings(); saveSettingsDebounced(); // Force refresh sprites list @@ -1392,7 +1423,7 @@ async function onClickExpressionRemoveCustom() { // Remove custom expression from settings const index = extension_settings.expressions.custom.indexOf(selectedExpression); extension_settings.expressions.custom.splice(index, 1); - renderCustomExpressions(); + await renderAdditionalExpressionSettings(); saveSettingsDebounced(); // Force refresh sprites list @@ -1401,6 +1432,14 @@ async function onClickExpressionRemoveCustom() { moduleWorker(); } +function onExpressionFallbackChanged() { + const expression = this.value; + if (expression) { + extension_settings.expressions.fallback_expression = expression; + saveSettingsDebounced(); + } +} + async function handleFileUpload(url, formData) { try { const data = await jQuery.ajax({ @@ -1648,7 +1687,7 @@ async function fetchImagesNoCache() { return await Promise.allSettled(promises); } -(function () { +(async function () { function addExpressionImage() { const html = `
    @@ -1668,7 +1707,7 @@ async function fetchImagesNoCache() { element.hide(); $('body').append(element); } - function addSettings() { + async function addSettings() { $('#extensions_settings').append(renderExtensionTemplate(MODULE_NAME, 'settings')); $('#expression_override_button').on('click', onClickExpressionOverrideButton); $('#expressions_show_default').on('input', onExpressionsShowDefaultInput); @@ -1696,10 +1735,11 @@ async function fetchImagesNoCache() { } }); - renderCustomExpressions(); + await renderAdditionalExpressionSettings(); $('#expression_custom_add').on('click', onClickExpressionAddCustom); $('#expression_custom_remove').on('click', onClickExpressionRemoveCustom); + $('#expression_fallback').on('change', onExpressionFallbackChanged) } // Pause Talkinghead to save resources when the ST tab is not visible or the window is minimized. @@ -1732,7 +1772,7 @@ async function fetchImagesNoCache() { addExpressionImage(); addVisualNovelMode(); - addSettings(); + await addSettings(); const wrapper = new ModuleWorkerWrapper(moduleWorker); const updateFunction = wrapper.update.bind(wrapper); setInterval(updateFunction, UPDATE_INTERVAL); diff --git a/public/scripts/extensions/expressions/settings.html b/public/scripts/extensions/expressions/settings.html index 89e73fa63..4abc49fcc 100644 --- a/public/scripts/extensions/expressions/settings.html +++ b/public/scripts/extensions/expressions/settings.html @@ -18,6 +18,11 @@ Image Type - talkinghead (extras) +
    + + Set the default and fallback expression being used when no matching expression is found. + +
    Can be set manually or with an /emote slash command. From e26c7827bc72499a9698745cd9b0816c6b76ddc6 Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Sun, 7 Apr 2024 06:26:48 +0200 Subject: [PATCH 42/80] Fallback expression reset if custom expression deleted --- public/scripts/extensions/expressions/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/public/scripts/extensions/expressions/index.js b/public/scripts/extensions/expressions/index.js index f73c3325a..4ee04f552 100644 --- a/public/scripts/extensions/expressions/index.js +++ b/public/scripts/extensions/expressions/index.js @@ -1423,6 +1423,10 @@ async function onClickExpressionRemoveCustom() { // Remove custom expression from settings const index = extension_settings.expressions.custom.indexOf(selectedExpression); extension_settings.expressions.custom.splice(index, 1); + if (selectedExpression == getFallbackExpression()) { + toastr.warning(`Deleted custom expression '${selectedExpression}' that was also selected as the fallback expression.\nFallback expression has been reset to '${DEFAULT_FALLBACK_EXPRESSION}'.`); + extension_settings.expressions.fallback_expression = DEFAULT_FALLBACK_EXPRESSION; + } await renderAdditionalExpressionSettings(); saveSettingsDebounced(); From b027c04066c48a05419b2b56c5860a1a58e9f10f Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Sun, 7 Apr 2024 20:37:05 +0200 Subject: [PATCH 43/80] Remove chat save on pick macro --- public/scripts/macros.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/scripts/macros.js b/public/scripts/macros.js index ff3aff0d7..a2514da8c 100644 --- a/public/scripts/macros.js +++ b/public/scripts/macros.js @@ -189,10 +189,10 @@ function pickReplace(input, rawContent, emptyListPlaceholder = '') { const pickPattern = /{{pick\s?::?([^}]+)}}/gi; // We need to have a consistent chat hash, otherwise we'll lose rolls on chat file rename or branch switches + // No need to save metadata here - branching and renaming will implicitly do the save for us, and until then loading it like this is consistent const chatIdHash = chat_metadata['chat_id_hash']; if (!chatIdHash) { - chat_metadata['chat_id_hash'] = getStringHash(getCurrentChatId()); - saveMetadataDebounced(); + chat_metadata['chat_id_hash'] = getStringHash(chat_metadata['main_chat'] ?? getCurrentChatId()); } const rawContentHash = getStringHash(rawContent); From 4a4296127ce91ebe87c95796854fed1ee78b7148 Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Sun, 7 Apr 2024 21:12:41 +0200 Subject: [PATCH 44/80] Group chat-specific join prefix/suffix for char fields - Add group chat setting fields for "prefix" and "suffix" - Settings will be visible when any "join" setting is selected - each part will be surrounded, which optional macro replacements on the prefix/suffix --- public/css/rm-groups.css | 5 +++ public/index.html | 28 ++++++++++++++--- public/scripts/group-chats.js | 57 ++++++++++++++++++++++++++++++++--- 3 files changed, 81 insertions(+), 9 deletions(-) diff --git a/public/css/rm-groups.css b/public/css/rm-groups.css index 4bc82ee6b..e7cbb2c95 100644 --- a/public/css/rm-groups.css +++ b/public/css/rm-groups.css @@ -58,6 +58,11 @@ cursor: unset; } +#rm_group_buttons textarea { + margin: 0px; + min-width: 200px; +} + #rm_group_members, #rm_group_add_members { margin-top: 0.25rem; diff --git a/public/index.html b/public/index.html index 352885649..98170d1a8 100644 --- a/public/index.html +++ b/public/index.html @@ -4360,24 +4360,44 @@
    -
    +
    +
    -
    +
    +
    +
    + + +
    +
    + + +
    diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js index 2b5267ff2..e994893e3 100644 --- a/public/scripts/group-chats.js +++ b/public/scripts/group-chats.js @@ -9,9 +9,10 @@ import { saveBase64AsFile, PAGINATION_TEMPLATE, getBase64Async, + resetScrollHeight, } from './utils.js'; import { RA_CountCharTokens, humanizedDateTime, dragElement, favsToHotswap, getMessageTimeStamp } from './RossAscends-mods.js'; -import { loadMovingUIState, sortEntitiesList } from './power-user.js'; +import { power_user, loadMovingUIState, sortEntitiesList } from './power-user.js'; import { chat, @@ -351,6 +352,30 @@ export function getGroupCharacterCards(groupId, characterId) { return null; } + /** Runs the macro engine on a text, with custom replace @param {string} value @param {string} characterName @param {string} fieldName @returns {string} */ + function customBaseChatReplace(value, fieldName, characterName) { + // We should do the custom field name replacement first, and then run it through the normal macro engine with provided names + value = value.replace(//gi, fieldName); + return baseChatReplace(value.trim(), name1, characterName); + } + + /** Prepares text with prefix/suffix for a character field @param {string} value @param {string} characterName @param {string} fieldName @returns {string} */ + function replaceAndPrepareForJoin(value, characterName, fieldName) { + value = value.trim(); + if (!value) { + return ''; + } + + // Prepare and replace prefixes + const prefix = customBaseChatReplace(group.generation_mode_join_prefix, fieldName, characterName); + const suffix = customBaseChatReplace(group.generation_mode_join_suffix, fieldName, characterName); + const separator = power_user.instruct.wrap ? '\n' : ''; + // Also run the macro replacement on the actual content + value = customBaseChatReplace(value, fieldName, characterName); + + return `${prefix}${separator}${value}${separator}${suffix}`; + } + const scenarioOverride = chat_metadata['scenario']; let descriptions = []; @@ -372,10 +397,10 @@ export function getGroupCharacterCards(groupId, characterId) { continue; } - descriptions.push(baseChatReplace(character.description.trim(), name1, character.name)); - personalities.push(baseChatReplace(character.personality.trim(), name1, character.name)); - scenarios.push(baseChatReplace(character.scenario.trim(), name1, character.name)); - mesExamplesArray.push(baseChatReplace(character.mes_example.trim(), name1, character.name)); + descriptions.push(replaceAndPrepareForJoin(character.description, character.name, 'Description')); + personalities.push(replaceAndPrepareForJoin(character.personality, character.name, 'Personality')); + scenarios.push(replaceAndPrepareForJoin(character.scenario, character.name, 'Scenario')); + mesExamplesArray.push(replaceAndPrepareForJoin(character.mes_example, character.name, 'Example Messages')); } const description = descriptions.filter(x => x.length).join('\n'); @@ -1093,6 +1118,10 @@ async function onGroupGenerationModeInput(e) { let _thisGroup = groups.find((x) => x.id == openGroupId); _thisGroup.generation_mode = Number(e.target.value); await editGroup(openGroupId, false, false); + + const isJoin = [group_generation_mode.APPEND, group_generation_mode.APPEND_DISABLED].includes(_thisGroup.generation_mode); + $('#rm_group_generation_mode_join_prefix').parent().toggle(isJoin); + $('#rm_group_generation_mode_join_suffix').parent().toggle(isJoin); } } @@ -1105,6 +1134,15 @@ async function onGroupAutoModeDelayInput(e) { } } +async function onGroupGenerationModeTemplateInput(e) { + if (openGroupId) { + let _thisGroup = groups.find((x) => x.id == openGroupId); + const prop = $(e.target).attr('setting'); + _thisGroup[prop] = String(e.target.value); + await editGroup(openGroupId, false, false); + } +} + async function onGroupNameInput() { if (openGroupId) { let _thisGroup = groups.find((x) => x.id == openGroupId); @@ -1305,6 +1343,9 @@ function select_group_chats(groupId, skipAnimation) { $('#rm_group_hidemutedsprites').prop('checked', group && group.hideMutedSprites); $('#rm_group_automode_delay').val(group?.auto_mode_delay ?? DEFAULT_AUTO_MODE_DELAY); + $('#rm_group_generation_mode_join_prefix').val(group?.generation_mode_join_prefix).attr('setting', 'generation_mode_join_prefix'); + $('#rm_group_generation_mode_join_suffix').val(group?.generation_mode_join_suffix).attr('setting', 'generation_mode_join_suffix'); + // bottom buttons if (openGroupId) { $('#rm_group_submit').hide(); @@ -1796,6 +1837,10 @@ function doCurMemberListPopout() { } jQuery(() => { + $(document).on('input', '#rm_group_chats_block .autoSetHeight', function () { + resetScrollHeight($(this)); + }); + $(document).on('click', '.group_select', function () { const groupId = $(this).attr('chid') || $(this).attr('grid') || $(this).data('id'); openGroupById(groupId); @@ -1823,6 +1868,8 @@ jQuery(() => { $('#rm_group_activation_strategy').on('change', onGroupActivationStrategyInput); $('#rm_group_generation_mode').on('change', onGroupGenerationModeInput); $('#rm_group_automode_delay').on('input', onGroupAutoModeDelayInput); + $('#rm_group_generation_mode_join_prefix').on('input', onGroupGenerationModeTemplateInput); + $('#rm_group_generation_mode_join_suffix').on('input', onGroupGenerationModeTemplateInput); $('#group_avatar_button').on('input', uploadGroupAvatar); $('#rm_group_restore_avatar').on('click', restoreGroupAvatar); $(document).on('click', '.group_member .right_menu_button', onGroupActionClick); From f4fcbff17e2f710e1f38dcb061373fb4fa215cf6 Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Sun, 7 Apr 2024 21:23:45 +0200 Subject: [PATCH 45/80] Fix spacing on field join for empty suffix/prefix --- public/scripts/group-chats.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js index e994893e3..ba9483c69 100644 --- a/public/scripts/group-chats.js +++ b/public/scripts/group-chats.js @@ -373,7 +373,7 @@ export function getGroupCharacterCards(groupId, characterId) { // Also run the macro replacement on the actual content value = customBaseChatReplace(value, fieldName, characterName); - return `${prefix}${separator}${value}${separator}${suffix}`; + return `${prefix ? prefix + separator : ''}${value}${suffix ? separator + suffix : ''}`; } const scenarioOverride = chat_metadata['scenario']; From 3ed0564d732ae0e9b00cffea0117ddadc9ae2396 Mon Sep 17 00:00:00 2001 From: LenAnderson Date: Sun, 7 Apr 2024 16:59:50 -0400 Subject: [PATCH 46/80] add default value to qr editor wrap setting --- public/scripts/extensions/quick-reply/src/QuickReply.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/extensions/quick-reply/src/QuickReply.js b/public/scripts/extensions/quick-reply/src/QuickReply.js index 2cc817fb5..8ad5001a3 100644 --- a/public/scripts/extensions/quick-reply/src/QuickReply.js +++ b/public/scripts/extensions/quick-reply/src/QuickReply.js @@ -209,7 +209,7 @@ export class QuickReply { }); /**@type {HTMLInputElement}*/ const wrap = dom.querySelector('#qr--modal-wrap'); - wrap.checked = JSON.parse(localStorage.getItem('qr--wrap')); + wrap.checked = JSON.parse(localStorage.getItem('qr--wrap') ?? 'false'); wrap.addEventListener('click', () => { localStorage.setItem('qr--wrap', JSON.stringify(wrap.checked)); updateWrap(); From 21d3a7dc3edd68ecf6dc96aba53efff1f534421c Mon Sep 17 00:00:00 2001 From: LenAnderson Date: Sun, 7 Apr 2024 17:00:36 -0400 Subject: [PATCH 47/80] add tab size setting to QR editor --- .../extensions/quick-reply/html/qrEditor.html | 8 ++++++-- .../extensions/quick-reply/src/QuickReply.js | 11 +++++++++++ public/scripts/extensions/quick-reply/style.css | 13 +++++++++++++ public/scripts/extensions/quick-reply/style.less | 13 +++++++++++++ 4 files changed, 43 insertions(+), 2 deletions(-) diff --git a/public/scripts/extensions/quick-reply/html/qrEditor.html b/public/scripts/extensions/quick-reply/html/qrEditor.html index 08cecbc23..736b87f69 100644 --- a/public/scripts/extensions/quick-reply/html/qrEditor.html +++ b/public/scripts/extensions/quick-reply/html/qrEditor.html @@ -16,12 +16,16 @@ - +
    - + +
    diff --git a/public/scripts/extensions/quick-reply/src/QuickReply.js b/public/scripts/extensions/quick-reply/src/QuickReply.js index 8ad5001a3..609290d02 100644 --- a/public/scripts/extensions/quick-reply/src/QuickReply.js +++ b/public/scripts/extensions/quick-reply/src/QuickReply.js @@ -221,9 +221,20 @@ export class QuickReply { message.style.whiteSpace = 'pre'; } }; + /**@type {HTMLInputElement}*/ + const tabSize = dom.querySelector('#qr--modal-tabSize'); + tabSize.value = JSON.parse(localStorage.getItem('qr--tabSize') ?? '4'); + const updateTabSize = () => { + message.style.tabSize = tabSize.value; + }; + tabSize.addEventListener('change', () => { + localStorage.setItem('qr--tabSize', JSON.stringify(Number(tabSize.value))); + updateTabSize(); + }); /**@type {HTMLTextAreaElement}*/ const message = dom.querySelector('#qr--modal-message'); updateWrap(); + updateTabSize(); message.value = this.message; message.addEventListener('input', () => { this.updateMessage(message.value); diff --git a/public/scripts/extensions/quick-reply/style.css b/public/scripts/extensions/quick-reply/style.css index 74bf8134f..653b858da 100644 --- a/public/scripts/extensions/quick-reply/style.css +++ b/public/scripts/extensions/quick-reply/style.css @@ -269,6 +269,19 @@ display: flex; flex-direction: column; } +#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings { + display: flex; + flex-direction: row; + gap: 1em; + color: var(--grey70); + font-size: smaller; +} +#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings > .checkbox_label { + white-space: nowrap; +} +#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings > .checkbox_label > input { + font-size: inherit; +} #dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-message { flex: 1 1 auto; } diff --git a/public/scripts/extensions/quick-reply/style.less b/public/scripts/extensions/quick-reply/style.less index e6271a2c0..413512e5d 100644 --- a/public/scripts/extensions/quick-reply/style.less +++ b/public/scripts/extensions/quick-reply/style.less @@ -293,6 +293,19 @@ flex: 1 1 auto; display: flex; flex-direction: column; + > .qr--modal-editorSettings { + display: flex; + flex-direction: row; + gap: 1em; + color: var(--grey70); + font-size: smaller; + > .checkbox_label { + white-space: nowrap; + > input { + font-size: inherit; + } + } + } > #qr--modal-message { flex: 1 1 auto; } From 63cbfda9b113bca523da29f0479596af98459580 Mon Sep 17 00:00:00 2001 From: LenAnderson Date: Sun, 7 Apr 2024 17:01:26 -0400 Subject: [PATCH 48/80] add ctrl-enter to execute from editor --- .../extensions/quick-reply/html/qrEditor.html | 4 ++ .../extensions/quick-reply/src/QuickReply.js | 55 +++++++++++++------ 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/public/scripts/extensions/quick-reply/html/qrEditor.html b/public/scripts/extensions/quick-reply/html/qrEditor.html index 736b87f69..8ed195520 100644 --- a/public/scripts/extensions/quick-reply/html/qrEditor.html +++ b/public/scripts/extensions/quick-reply/html/qrEditor.html @@ -25,6 +25,10 @@ Tab size: +

    diff --git a/public/scripts/extensions/quick-reply/src/QuickReply.js b/public/scripts/extensions/quick-reply/src/QuickReply.js index 609290d02..3e6ea982b 100644 --- a/public/scripts/extensions/quick-reply/src/QuickReply.js +++ b/public/scripts/extensions/quick-reply/src/QuickReply.js @@ -44,6 +44,11 @@ export class QuickReply { /**@type {HTMLInputElement}*/ settingsDomLabel; /**@type {HTMLTextAreaElement}*/ settingsDomMessage; + /**@type {HTMLElement}*/ editorExecuteBtn; + /**@type {HTMLElement}*/ editorExecuteErrors; + /**@type {HTMLInputElement}*/ editorExecuteHide; + /**@type {Promise}*/ editorExecutePromise; + get hasContext() { return this.contextList && this.contextList.length > 0; @@ -231,6 +236,12 @@ export class QuickReply { localStorage.setItem('qr--tabSize', JSON.stringify(Number(tabSize.value))); updateTabSize(); }); + /**@type {HTMLInputElement}*/ + const executeShortcut = dom.querySelector('#qr--modal-executeShortcut'); + executeShortcut.checked = JSON.parse(localStorage.getItem('qr--executeShortcut') ?? 'true'); + executeShortcut.addEventListener('click', () => { + localStorage.setItem('qr--executeShortcut', JSON.stringify(executeShortcut.checked)); + }); /**@type {HTMLTextAreaElement}*/ const message = dom.querySelector('#qr--modal-message'); updateWrap(); @@ -268,6 +279,12 @@ export class QuickReply { message.selectionStart = start - 1; message.selectionEnd = end - count; this.updateMessage(message.value); + } else if (evt.key == 'Enter' && evt.ctrlKey && !evt.shiftKey && !evt.altKey) { + evt.stopPropagation(); + evt.preventDefault(); + if (executeShortcut.checked) { + this.executeFromEditor(); + } } }); @@ -396,27 +413,15 @@ export class QuickReply { /**@type {HTMLElement}*/ const executeErrors = dom.querySelector('#qr--modal-executeErrors'); + this.editorExecuteErrors = executeErrors; /**@type {HTMLInputElement}*/ const executeHide = dom.querySelector('#qr--modal-executeHide'); - let executePromise; + this.editorExecuteHide = executeHide; /**@type {HTMLElement}*/ const executeBtn = dom.querySelector('#qr--modal-execute'); + this.editorExecuteBtn = executeBtn; executeBtn.addEventListener('click', async()=>{ - if (executePromise) return; - executeBtn.classList.add('qr--busy'); - executeErrors.innerHTML = ''; - if (executeHide.checked) { - document.querySelector('#shadow_popup').classList.add('qr--hide'); - } - try { - executePromise = this.execute(); - await executePromise; - } catch (ex) { - executeErrors.textContent = ex.message; - } - executePromise = null; - executeBtn.classList.remove('qr--busy'); - document.querySelector('#shadow_popup').classList.remove('qr--hide'); + await this.executeFromEditor(); }); await popupResult; @@ -425,6 +430,24 @@ export class QuickReply { } } + async executeFromEditor() { + if (this.editorExecutePromise) return; + this.editorExecuteBtn.classList.add('qr--busy'); + this.editorExecuteErrors.innerHTML = ''; + if (this.editorExecuteHide.checked) { + document.querySelector('#shadow_popup').classList.add('qr--hide'); + } + try { + this.editorExecutePromise = this.execute(); + await this.editorExecutePromise; + } catch (ex) { + this.editorExecuteErrors.textContent = ex.message; + } + this.editorExecutePromise = null; + this.editorExecuteBtn.classList.remove('qr--busy'); + document.querySelector('#shadow_popup').classList.remove('qr--hide'); + } + From 40b3640fa2a7befe84d6c1a078bbe93203a2e62e Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Mon, 8 Apr 2024 00:34:21 +0200 Subject: [PATCH 49/80] Fix group join prefix/suffix controls state on render --- public/scripts/group-chats.js | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js index ba9483c69..c7a853baa 100644 --- a/public/scripts/group-chats.js +++ b/public/scripts/group-chats.js @@ -1119,9 +1119,7 @@ async function onGroupGenerationModeInput(e) { _thisGroup.generation_mode = Number(e.target.value); await editGroup(openGroupId, false, false); - const isJoin = [group_generation_mode.APPEND, group_generation_mode.APPEND_DISABLED].includes(_thisGroup.generation_mode); - $('#rm_group_generation_mode_join_prefix').parent().toggle(isJoin); - $('#rm_group_generation_mode_join_suffix').parent().toggle(isJoin); + toggleHiddenControls(_thisGroup); } } @@ -1308,6 +1306,12 @@ async function onHideMutedSpritesClick(value) { } } +function toggleHiddenControls(group, generationMode = null) { + const isJoin = [group_generation_mode.APPEND, group_generation_mode.APPEND_DISABLED].includes(generationMode ?? group?.generation_mode); + $('#rm_group_generation_mode_join_prefix').parent().toggle(isJoin); + $('#rm_group_generation_mode_join_suffix').parent().toggle(isJoin); +} + function select_group_chats(groupId, skipAnimation) { openGroupId = groupId; newGroupMembers = []; @@ -1343,8 +1347,9 @@ function select_group_chats(groupId, skipAnimation) { $('#rm_group_hidemutedsprites').prop('checked', group && group.hideMutedSprites); $('#rm_group_automode_delay').val(group?.auto_mode_delay ?? DEFAULT_AUTO_MODE_DELAY); - $('#rm_group_generation_mode_join_prefix').val(group?.generation_mode_join_prefix).attr('setting', 'generation_mode_join_prefix'); - $('#rm_group_generation_mode_join_suffix').val(group?.generation_mode_join_suffix).attr('setting', 'generation_mode_join_suffix'); + $('#rm_group_generation_mode_join_prefix').val(group?.generation_mode_join_prefix ?? '').attr('setting', 'generation_mode_join_prefix'); + $('#rm_group_generation_mode_join_suffix').val(group?.generation_mode_join_suffix ?? '').attr('setting', 'generation_mode_join_suffix'); + toggleHiddenControls(group, generationMode); // bottom buttons if (openGroupId) { @@ -1379,6 +1384,11 @@ function select_group_chats(groupId, skipAnimation) { $('#rm_group_automode_label').hide(); } + // Toggle textbox sizes, as input events have not fired here + $('#rm_group_chats_block .autoSetHeight').each(element => { + resetScrollHeight(element); + }); + eventSource.emit('groupSelected', { detail: { id: openGroupId, group: group } }); } From c55181f955b45d8edb4f18471a62a735ff0c41cd Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Mon, 8 Apr 2024 14:55:31 +0300 Subject: [PATCH 50/80] Fix control alignment funkiness --- public/scripts/extensions/quick-reply/style.css | 1 + public/scripts/extensions/quick-reply/style.less | 1 + 2 files changed, 2 insertions(+) diff --git a/public/scripts/extensions/quick-reply/style.css b/public/scripts/extensions/quick-reply/style.css index 653b858da..132fe008a 100644 --- a/public/scripts/extensions/quick-reply/style.css +++ b/public/scripts/extensions/quick-reply/style.css @@ -275,6 +275,7 @@ gap: 1em; color: var(--grey70); font-size: smaller; + align-items: baseline; } #dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings > .checkbox_label { white-space: nowrap; diff --git a/public/scripts/extensions/quick-reply/style.less b/public/scripts/extensions/quick-reply/style.less index 413512e5d..725cf97e2 100644 --- a/public/scripts/extensions/quick-reply/style.less +++ b/public/scripts/extensions/quick-reply/style.less @@ -299,6 +299,7 @@ gap: 1em; color: var(--grey70); font-size: smaller; + align-items: baseline; > .checkbox_label { white-space: nowrap; > input { From 0c41ab609039681322e7960a8855bee205cd0d82 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Mon, 8 Apr 2024 15:10:15 +0300 Subject: [PATCH 51/80] Fix chatIdHash being empty on the first evaluation --- public/scripts/macros.js | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/public/scripts/macros.js b/public/scripts/macros.js index a2514da8c..42e3fad72 100644 --- a/public/scripts/macros.js +++ b/public/scripts/macros.js @@ -3,11 +3,29 @@ import { timestampToMoment, isDigitsOnly, getStringHash } from './utils.js'; import { textgenerationwebui_banned_in_macros } from './textgen-settings.js'; import { replaceInstructMacros } from './instruct-mode.js'; import { replaceVariableMacros } from './variables.js'; -import { saveMetadataDebounced } from './extensions.js'; // Register any macro that you want to leave in the compiled story string Handlebars.registerHelper('trim', () => '{{trim}}'); +/** + * Gets a hashed id of the current chat from the metadata. + * If no metadata exists, creates a new hash and saves it. + * @returns {number} The hashed chat id + */ +function getChatIdHash() { + const cachedIdHash = chat_metadata['chat_id_hash']; + + // If chat_id_hash is not already set, calculate it + if (!cachedIdHash) { + // Use the main_chat if it's available, otherwise get the current chat ID + const chatId = chat_metadata['main_chat'] ?? getCurrentChatId(); + const chatIdHash = getStringHash(chatId); + chat_metadata['chat_id_hash'] = chatIdHash; + return chatIdHash; + } + + return cachedIdHash; +} /** * Returns the ID of the last message in the chat @@ -190,10 +208,7 @@ function pickReplace(input, rawContent, emptyListPlaceholder = '') { // We need to have a consistent chat hash, otherwise we'll lose rolls on chat file rename or branch switches // No need to save metadata here - branching and renaming will implicitly do the save for us, and until then loading it like this is consistent - const chatIdHash = chat_metadata['chat_id_hash']; - if (!chatIdHash) { - chat_metadata['chat_id_hash'] = getStringHash(chat_metadata['main_chat'] ?? getCurrentChatId()); - } + const chatIdHash = getChatIdHash(); const rawContentHash = getStringHash(rawContent); return input.replace(pickPattern, (match, listString, offset) => { From 6e250eafcdbb2a126e9b7451caf6ef4ef5841ae4 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Mon, 8 Apr 2024 15:18:08 +0300 Subject: [PATCH 52/80] Fix shrunken group wrapper controls --- public/scripts/group-chats.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js index c7a853baa..36f324b94 100644 --- a/public/scripts/group-chats.js +++ b/public/scripts/group-chats.js @@ -10,6 +10,7 @@ import { PAGINATION_TEMPLATE, getBase64Async, resetScrollHeight, + initScrollHeight, } from './utils.js'; import { RA_CountCharTokens, humanizedDateTime, dragElement, favsToHotswap, getMessageTimeStamp } from './RossAscends-mods.js'; import { power_user, loadMovingUIState, sortEntitiesList } from './power-user.js'; @@ -1310,6 +1311,8 @@ function toggleHiddenControls(group, generationMode = null) { const isJoin = [group_generation_mode.APPEND, group_generation_mode.APPEND_DISABLED].includes(generationMode ?? group?.generation_mode); $('#rm_group_generation_mode_join_prefix').parent().toggle(isJoin); $('#rm_group_generation_mode_join_suffix').parent().toggle(isJoin); + initScrollHeight($('#rm_group_generation_mode_join_prefix')); + initScrollHeight($('#rm_group_generation_mode_join_suffix')); } function select_group_chats(groupId, skipAnimation) { From d56722a4b64520f675d6fc767cbc415fa37e3aed Mon Sep 17 00:00:00 2001 From: RossAscends <124905043+RossAscends@users.noreply.github.com> Date: Tue, 9 Apr 2024 02:48:39 +0900 Subject: [PATCH 53/80] helper text and repo link tooltip for asset list items --- public/scripts/extensions/assets/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/public/scripts/extensions/assets/index.js b/public/scripts/extensions/assets/index.js index c71ffb620..8c74f52eb 100644 --- a/public/scripts/extensions/assets/index.js +++ b/public/scripts/extensions/assets/index.js @@ -103,7 +103,8 @@ function downloadAssetsList(url) { if (assetType == 'extension') { assetTypeMenu.append(`
    - To download extensions from this page, you need to have Git installed. + To download extensions from this page, you need to have Git installed.
    + Click the icon to visit the Extension's repo for tips on how to use it.
    `); } @@ -187,7 +188,7 @@ function downloadAssetsList(url) { .append(`
    ${displayName} - + From 845409a2395a95535aef675562f24a7f6cce97d4 Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Mon, 8 Apr 2024 20:05:59 +0200 Subject: [PATCH 54/80] Add a main {{systemPrompt}} macro --- public/scripts/instruct-mode.js | 4 +++- public/scripts/macros.js | 2 +- public/scripts/templates/macros.html | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/public/scripts/instruct-mode.js b/public/scripts/instruct-mode.js index 3167f3bb5..d6a8039b4 100644 --- a/public/scripts/instruct-mode.js +++ b/public/scripts/instruct-mode.js @@ -518,13 +518,15 @@ function selectMatchingContextTemplate(name) { /** * Replaces instruct mode macros in the given input string. * @param {string} input Input string. + * @param {Object} env - Map of macro names to the values they'll be substituted with. If the param * @returns {string} String with macros replaced. */ -export function replaceInstructMacros(input) { +export function replaceInstructMacros(input, env) { if (!input) { return ''; } const instructMacros = { + 'systemPrompt': (power_user.prefer_character_prompt && env.charPrompt ? env.charPrompt : power_user.instruct.system_prompt), 'instructSystem|instructSystemPrompt': power_user.instruct.system_prompt, 'instructSystemPromptPrefix': power_user.instruct.system_sequence_prefix, 'instructSystemPromptSuffix': power_user.instruct.system_sequence_suffix, diff --git a/public/scripts/macros.js b/public/scripts/macros.js index a9df01987..70d4d9bb3 100644 --- a/public/scripts/macros.js +++ b/public/scripts/macros.js @@ -257,7 +257,7 @@ export function evaluateMacros(content, env) { } content = diceRollReplace(content); - content = replaceInstructMacros(content); + content = replaceInstructMacros(content, env); content = replaceVariableMacros(content); content = content.replace(/{{newline}}/gi, '\n'); content = content.replace(/\n*{{trim}}\n*/gi, ''); diff --git a/public/scripts/templates/macros.html b/public/scripts/templates/macros.html index f3291333f..2eb9ca7b7 100644 --- a/public/scripts/templates/macros.html +++ b/public/scripts/templates/macros.html @@ -49,6 +49,7 @@
  • {{maxPrompt}} – max allowed prompt length in tokens = (context size - response length)
  • {{exampleSeparator}} – context template example dialogues separator
  • {{chatStart}} – context template chat start line
  • +
  • {{systemPrompt}} – main system prompt (either character prompt override if chosen, or instructSystemPrompt)
  • {{instructSystemPrompt}} – instruct system prompt
  • {{instructSystemPromptPrefix}} – instruct system prompt prefix sequence
  • {{instructSystemPromptSuffix}} – instruct system prompt suffix sequence
  • From 0e2a82ce00e2dd68600f86c7687589f78aae8fcf Mon Sep 17 00:00:00 2001 From: LenAnderson Date: Mon, 8 Apr 2024 18:42:33 -0400 Subject: [PATCH 55/80] add generic popups with their own elements --- public/index.html | 16 +++ public/script.js | 2 +- public/scripts/popup.js | 225 ++++++++++++++++++++++++++++++++++++++++ public/style.css | 29 ++++-- 4 files changed, 262 insertions(+), 10 deletions(-) create mode 100644 public/scripts/popup.js diff --git a/public/index.html b/public/index.html index 98170d1a8..b2916a859 100644 --- a/public/index.html +++ b/public/index.html @@ -4511,6 +4511,22 @@
    +
    diff --git a/public/script.js b/public/script.js index 18e15e8e2..234964eab 100644 --- a/public/script.js +++ b/public/script.js @@ -821,7 +821,7 @@ let create_save = { //animation right menu export const ANIMATION_DURATION_DEFAULT = 125; export let animation_duration = ANIMATION_DURATION_DEFAULT; -let animation_easing = 'ease-in-out'; +export let animation_easing = 'ease-in-out'; let popup_type = ''; let chat_file_for_del = ''; let online_status = 'no_connection'; diff --git a/public/scripts/popup.js b/public/scripts/popup.js new file mode 100644 index 000000000..b793f3a66 --- /dev/null +++ b/public/scripts/popup.js @@ -0,0 +1,225 @@ +import { animation_duration, animation_easing } from '../script.js'; +import { delay } from './utils.js'; + + + +/**@readonly*/ +/**@enum {Number}*/ +export const POPUP_TYPE = { + 'TEXT': 1, + 'CONFIRM': 2, + 'INPUT': 3, +}; + +/**@readonly*/ +/**@enum {Boolean}*/ +export const POPUP_RESULT = { + 'AFFIRMATIVE': true, + 'NEGATIVE': false, + 'CANCELLED': undefined, +}; + + + +export class Popup { + /**@type {POPUP_TYPE}*/ type; + + /**@type {HTMLElement}*/ dom; + /**@type {HTMLElement}*/ dlg; + /**@type {HTMLElement}*/ text; + /**@type {HTMLTextAreaElement}*/ input; + /**@type {HTMLElement}*/ ok; + /**@type {HTMLElement}*/ cancel; + + /**@type {POPUP_RESULT}*/ result; + /**@type {any}*/ value; + + /**@type {Promise}*/ promise; + /**@type {Function}*/ resolver; + + /**@type {Function}*/ keyListenerBound; + + + + /** + * @typedef {{okButton?: string, cancelButton?: string, rows?: number, wide?: boolean, large?: boolean, allowHorizontalScrolling?: boolean, allowVerticalScrolling?: boolean }} PopupOptions - Options for the popup. + * @param {JQuery|string|Element} text - Text to display in the popup. + * @param {POPUP_TYPE} type - One of Popup.TYPE + * @param {string} inputValue - Value to set the input to. + * @param {PopupOptions} options - Options for the popup. + */ + constructor(text, type, inputValue = '', { okButton, cancelButton, rows, wide, large, allowHorizontalScrolling, allowVerticalScrolling } = {}) { + this.type = type; + + /**@type {HTMLTemplateElement}*/ + const template = document.querySelector('#shadow_popup_template'); + // @ts-ignore + this.dom = template.content.cloneNode(true).querySelector('.shadow_popup'); + const dlg = this.dom.querySelector('.dialogue_popup'); + // @ts-ignore + this.dlg = dlg; + this.text = this.dom.querySelector('.dialogue_popup_text'); + this.input = this.dom.querySelector('.dialogue_popup_input'); + this.ok = this.dom.querySelector('.dialogue_popup_ok'); + this.cancel = this.dom.querySelector('.dialogue_popup_cancel'); + + if (wide) dlg.classList.add('wide_dialogue_popup'); + if (large) dlg.classList.add('large_dialogue_popup'); + if (allowHorizontalScrolling) dlg.classList.add('horizontal_scrolling_dialogue_popup'); + if (allowVerticalScrolling) dlg.classList.add('vertical_scrolling_dialogue_popup'); + + this.ok.textContent = okButton ?? 'OK'; + this.cancel.textContent = cancelButton ?? 'Cancel'; + + switch(type) { + case POPUP_TYPE.TEXT: { + this.input.style.display = 'none'; + this.cancel.style.display = 'none'; + break; + } + case POPUP_TYPE.CONFIRM: { + this.input.style.display = 'none'; + this.ok.textContent = okButton ?? 'Yes'; + this.cancel.textContent = cancelButton ?? 'No'; + break; + } + case POPUP_TYPE.INPUT: { + this.input.style.display = 'block'; + this.ok.textContent = okButton ?? 'Save'; + break; + } + default: { + // illegal argument + } + } + + this.input.value = inputValue; + this.input.rows = rows ?? 1; + + this.text.innerHTML = ''; + if (text instanceof jQuery) { + $(this.text).append(text); + } else if (text instanceof HTMLElement) { + this.text.append(text); + } else if (typeof text == 'string') { + this.text.innerHTML = text; + } else { + // illegal argument + } + + this.ok.addEventListener('click', ()=>this.completeAffirmative()); + this.cancel.addEventListener('click', ()=>this.completeNegative()); + const keyListener = (evt)=>{ + switch (evt.key) { + case 'Escape': { + evt.preventDefault(); + evt.stopPropagation(); + this.completeCancelled(); + window.removeEventListener('keydown', keyListenerBound); + break; + } + } + }; + const keyListenerBound = keyListener.bind(this); + window.addEventListener('keydown', keyListenerBound); + } + + async show() { + document.body.append(this.dom); + this.dom.style.display = 'block'; + switch(this.type) { + case POPUP_TYPE.INPUT: { + this.input.focus(); + break; + } + } + + $(this.dom).transition({ + opacity: 1, + duration: animation_duration, + easing: animation_easing, + }); + + this.promise = new Promise((resolve) => { + this.resolver = resolve; + }); + return this.promise; + } + + completeAffirmative() { + switch (this.type) { + case POPUP_TYPE.TEXT: + case POPUP_TYPE.CONFIRM: { + this.value = true; + break; + } + case POPUP_TYPE.INPUT: { + this.value = this.input.value; + break; + } + } + this.result = POPUP_RESULT.AFFIRMATIVE; + this.hide(); + } + + completeNegative() { + switch (this.type) { + case POPUP_TYPE.TEXT: + case POPUP_TYPE.CONFIRM: + case POPUP_TYPE.INPUT: { + this.value = false; + break; + } + } + this.result = POPUP_RESULT.NEGATIVE; + this.hide(); + } + + completeCancelled() { + switch (this.type) { + case POPUP_TYPE.TEXT: + case POPUP_TYPE.CONFIRM: + case POPUP_TYPE.INPUT: { + this.value = null; + break; + } + } + this.result = POPUP_RESULT.CANCELLED; + this.hide(); + } + + + + hide() { + $(this.dom).transition({ + opacity: 0, + duration: animation_duration, + easing: animation_easing, + }); + delay(animation_duration).then(()=>{ + this.dom.remove(); + }); + + this.resolver(this.value); + } +} + + + +/** + * Displays a blocking popup with a given text and type. + * @param {JQuery|string|Element} text - Text to display in the popup. + * @param {POPUP_TYPE} type + * @param {string} inputValue - Value to set the input to. + * @param {PopupOptions} options - Options for the popup. + * @returns + */ +export function callGenericPopup(text, type, inputValue = '', { okButton, cancelButton, rows, wide, large, allowHorizontalScrolling, allowVerticalScrolling } = {}) { + const popup = new Popup( + text, + type, + inputValue, + { okButton, rows, wide, large, allowHorizontalScrolling, allowVerticalScrolling }, + ); + return popup.show(); +} diff --git a/public/style.css b/public/style.css index f6516beed..2ff9ef394 100644 --- a/public/style.css +++ b/public/style.css @@ -2059,7 +2059,8 @@ grammarly-extension { /* Focus */ #bulk_tag_popup, -#dialogue_popup { +#dialogue_popup, +.dialogue_popup { width: 500px; max-width: 90vw; max-width: 90svw; @@ -2112,7 +2113,8 @@ grammarly-extension { } #bulk_tag_popup_holder, -#dialogue_popup_holder { +#dialogue_popup_holder, +.dialogue_popup_holder { display: flex; flex-direction: column; height: 100%; @@ -2120,13 +2122,15 @@ grammarly-extension { padding: 0 10px; } -#dialogue_popup_text { +#dialogue_popup_text, +.dialogue_popup_text { flex-grow: 1; overflow-y: auto; height: 100%; } -#dialogue_popup_controls { +#dialogue_popup_controls, +.dialogue_popup_controls { display: flex; align-self: center; gap: 20px; @@ -2134,14 +2138,16 @@ grammarly-extension { #bulk_tag_popup_reset, #bulk_tag_popup_remove_mutual, -#dialogue_popup_ok { +#dialogue_popup_ok, +.dialogue_popup_ok { background-color: var(--crimson70a); cursor: pointer; } #bulk_tag_popup_reset:hover, #bulk_tag_popup_remove_mutual:hover, -#dialogue_popup_ok:hover { +#dialogue_popup_ok:hover, +.dialogue_popup_ok:hover { background-color: var(--crimson-hover); } @@ -2149,13 +2155,15 @@ grammarly-extension { max-height: 70vh; } -#dialogue_popup_input { +#dialogue_popup_input, +.dialogue_popup_input { margin: 10px 0; width: 100%; } #bulk_tag_popup_cancel, -#dialogue_popup_cancel { +#dialogue_popup_cancel, +.dialogue_popup_cancel { cursor: pointer; } @@ -2220,7 +2228,7 @@ grammarly-extension { margin-right: 25px; } -#shadow_popup { +#shadow_popup, .shadow_popup { backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2)); -webkit-backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2)); background-color: var(--black30a); @@ -2232,6 +2240,9 @@ grammarly-extension { height: 100svh; z-index: 9999; top: 0; + &.shadow_popup { + z-index: 9998; + } } #bgtest { From b461c6f0bb7482460511302de7c011b14b885e82 Mon Sep 17 00:00:00 2001 From: LenAnderson Date: Mon, 8 Apr 2024 18:43:21 -0400 Subject: [PATCH 56/80] use generic popups for QR editor --- .../extensions/quick-reply/src/QuickReply.js | 11 +++-- .../scripts/extensions/quick-reply/style.css | 40 +++++++++---------- .../scripts/extensions/quick-reply/style.less | 8 ++-- 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/public/scripts/extensions/quick-reply/src/QuickReply.js b/public/scripts/extensions/quick-reply/src/QuickReply.js index 3e6ea982b..33a1d05f6 100644 --- a/public/scripts/extensions/quick-reply/src/QuickReply.js +++ b/public/scripts/extensions/quick-reply/src/QuickReply.js @@ -1,4 +1,4 @@ -import { callPopup } from '../../../../script.js'; +import { POPUP_TYPE, Popup } from '../../../popup.js'; import { getSortableDelay } from '../../../utils.js'; import { log, warn } from '../index.js'; import { QuickReplyContextLink } from './QuickReplyContextLink.js'; @@ -44,6 +44,8 @@ export class QuickReply { /**@type {HTMLInputElement}*/ settingsDomLabel; /**@type {HTMLTextAreaElement}*/ settingsDomMessage; + /**@type {Popup}*/ editorPopup; + /**@type {HTMLElement}*/ editorExecuteBtn; /**@type {HTMLElement}*/ editorExecuteErrors; /**@type {HTMLInputElement}*/ editorExecuteHide; @@ -197,7 +199,8 @@ export class QuickReply { /**@type {HTMLElement} */ // @ts-ignore const dom = this.template.cloneNode(true); - const popupResult = callPopup(dom, 'text', undefined, { okButton: 'OK', wide: true, large: true, rows: 1 }); + this.editorPopup = new Popup(dom, POPUP_TYPE.TEXT, undefined, { okButton: 'OK', wide: true, large: true, rows: 1 }); + const popupResult = this.editorPopup.show(); // basics /**@type {HTMLInputElement}*/ @@ -435,7 +438,7 @@ export class QuickReply { this.editorExecuteBtn.classList.add('qr--busy'); this.editorExecuteErrors.innerHTML = ''; if (this.editorExecuteHide.checked) { - document.querySelector('#shadow_popup').classList.add('qr--hide'); + this.editorPopup.dom.classList.add('qr--hide'); } try { this.editorExecutePromise = this.execute(); @@ -445,7 +448,7 @@ export class QuickReply { } this.editorExecutePromise = null; this.editorExecuteBtn.classList.remove('qr--busy'); - document.querySelector('#shadow_popup').classList.remove('qr--hide'); + this.editorPopup.dom.classList.remove('qr--hide'); } diff --git a/public/scripts/extensions/quick-reply/style.css b/public/scripts/extensions/quick-reply/style.css index 132fe008a..bff6ee067 100644 --- a/public/scripts/extensions/quick-reply/style.css +++ b/public/scripts/extensions/quick-reply/style.css @@ -216,60 +216,60 @@ align-items: baseline; } @media screen and (max-width: 750px) { - body #dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor { + body .dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor { flex-direction: column; } - body #dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels { + body .dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels { flex-direction: column; } - body #dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-message { + body .dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-message { min-height: 90svh; } } -#dialogue_popup:has(#qr--modalEditor) { +.dialogue_popup:has(#qr--modalEditor) { aspect-ratio: unset; } -#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text { +.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text { display: flex; flex-direction: column; } -#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor { +.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor { flex: 1 1 auto; display: flex; flex-direction: row; gap: 1em; } -#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main { +.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main { flex: 1 1 auto; display: flex; flex-direction: column; } -#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels { +.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels { flex: 0 0 auto; display: flex; flex-direction: row; gap: 0.5em; } -#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels > label { +.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels > label { flex: 1 1 1px; display: flex; flex-direction: column; } -#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels > label > .qr--labelText { +.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels > label > .qr--labelText { flex: 1 1 auto; } -#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels > label > .qr--labelHint { +.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels > label > .qr--labelHint { flex: 1 1 auto; } -#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels > label > input { +.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--labels > label > input { flex: 0 0 auto; } -#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer { +.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer { flex: 1 1 auto; display: flex; flex-direction: column; } -#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings { +.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings { display: flex; flex-direction: row; gap: 1em; @@ -277,24 +277,24 @@ font-size: smaller; align-items: baseline; } -#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings > .checkbox_label { +.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings > .checkbox_label { white-space: nowrap; } -#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings > .checkbox_label > input { +.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings > .checkbox_label > input { font-size: inherit; } -#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-message { +.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > #qr--modal-message { flex: 1 1 auto; } -#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor #qr--modal-execute { +.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-execute { display: flex; flex-direction: row; gap: 0.5em; } -#dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor #qr--modal-execute.qr--busy { +.dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor #qr--modal-execute.qr--busy { opacity: 0.5; cursor: wait; } -#shadow_popup.qr--hide { +.shadow_popup.qr--hide { opacity: 0 !important; } diff --git a/public/scripts/extensions/quick-reply/style.less b/public/scripts/extensions/quick-reply/style.less index 725cf97e2..b91d7b444 100644 --- a/public/scripts/extensions/quick-reply/style.less +++ b/public/scripts/extensions/quick-reply/style.less @@ -242,7 +242,7 @@ @media screen and (max-width: 750px) { - body #dialogue_popup:has(#qr--modalEditor) #dialogue_popup_text > #qr--modalEditor { + body .dialogue_popup:has(#qr--modalEditor) .dialogue_popup_text > #qr--modalEditor { flex-direction: column; > #qr--main > .qr--labels { flex-direction: column; @@ -252,10 +252,10 @@ } } } -#dialogue_popup:has(#qr--modalEditor) { +.dialogue_popup:has(#qr--modalEditor) { aspect-ratio: unset; - #dialogue_popup_text { + .dialogue_popup_text { display: flex; flex-direction: column; @@ -326,6 +326,6 @@ } } -#shadow_popup.qr--hide { +.shadow_popup.qr--hide { opacity: 0 !important; } From 0594859fb9524e1d8ba5a31a9b7ab7f16ffeae79 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Tue, 9 Apr 2024 10:11:05 +0300 Subject: [PATCH 57/80] #2046 Fix for undefined fields --- public/scripts/group-chats.js | 20 ++++++++++++++++++-- src/endpoints/groups.js | 2 ++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/public/scripts/group-chats.js b/public/scripts/group-chats.js index 36f324b94..3ce9f0d29 100644 --- a/public/scripts/group-chats.js +++ b/public/scripts/group-chats.js @@ -353,14 +353,30 @@ export function getGroupCharacterCards(groupId, characterId) { return null; } - /** Runs the macro engine on a text, with custom replace @param {string} value @param {string} characterName @param {string} fieldName @returns {string} */ + /** + * Runs the macro engine on a text, with custom replace + * @param {string} value Value to replace + * @param {string} fieldName Name of the field + * @param {string} characterName Name of the character + * @returns {string} Replaced text + * */ function customBaseChatReplace(value, fieldName, characterName) { + if (!value) { + return ''; + } + // We should do the custom field name replacement first, and then run it through the normal macro engine with provided names value = value.replace(//gi, fieldName); return baseChatReplace(value.trim(), name1, characterName); } - /** Prepares text with prefix/suffix for a character field @param {string} value @param {string} characterName @param {string} fieldName @returns {string} */ + /** + * Prepares text with prefix/suffix for a character field + * @param {string} value Value to replace + * @param {string} characterName Name of the character + * @param {string} fieldName Name of the field + * @returns {string} Prepared text + * */ function replaceAndPrepareForJoin(value, characterName, fieldName) { value = value.trim(); if (!value) { diff --git a/src/endpoints/groups.js b/src/endpoints/groups.js index f8a117e84..ea2cc7377 100644 --- a/src/endpoints/groups.js +++ b/src/endpoints/groups.js @@ -74,6 +74,8 @@ router.post('/create', jsonParser, (request, response) => { chat_id: request.body.chat_id ?? id, chats: request.body.chats ?? [id], auto_mode_delay: request.body.auto_mode_delay ?? 5, + generation_mode_join_prefix: request.body.generation_mode_join_prefix ?? '', + generation_mode_join_suffix: request.body.generation_mode_join_suffix ?? '', }; const pathToFile = path.join(DIRECTORIES.groups, `${id}.json`); const fileData = JSON.stringify(groupMetadata); From 0391ef63d627772d033fc0b4bb7e0a6a761d32ff Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Tue, 9 Apr 2024 10:20:59 +0300 Subject: [PATCH 58/80] Fix bottom text of the comment --- public/scripts/instruct-mode.js | 1 + 1 file changed, 1 insertion(+) diff --git a/public/scripts/instruct-mode.js b/public/scripts/instruct-mode.js index d6a8039b4..1a7f05aff 100644 --- a/public/scripts/instruct-mode.js +++ b/public/scripts/instruct-mode.js @@ -519,6 +519,7 @@ function selectMatchingContextTemplate(name) { * Replaces instruct mode macros in the given input string. * @param {string} input Input string. * @param {Object} env - Map of macro names to the values they'll be substituted with. If the param + * values are functions, those functions will be called and their return values are used. * @returns {string} String with macros replaced. */ export function replaceInstructMacros(input, env) { From d4f428d4bc1c11576af62bb02b74bb0d9ece0c81 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Tue, 9 Apr 2024 11:10:40 +0300 Subject: [PATCH 59/80] Add new popup to extensions API --- public/script.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/script.js b/public/script.js index 234964eab..ee4bb3dbe 100644 --- a/public/script.js +++ b/public/script.js @@ -211,6 +211,7 @@ import { loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermati import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId } from './scripts/chats.js'; import { initPresetManager } from './scripts/preset-manager.js'; import { evaluateMacros } from './scripts/macros.js'; +import { callGenericPopup } from './scripts/popup.js'; //exporting functions and vars for mods export { @@ -7808,6 +7809,7 @@ window['SillyTavern'].getContext = function () { registedDebugFunction: registerDebugFunction, renderExtensionTemplate: renderExtensionTemplate, callPopup: callPopup, + callGenericPopup: callGenericPopup, mainApi: main_api, extensionSettings: extension_settings, ModuleWorkerWrapper: ModuleWorkerWrapper, From 6d65d47f008febd36b9a44357cbf484c219c8014 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Tue, 9 Apr 2024 11:24:16 +0300 Subject: [PATCH 60/80] Fix macro not being subbed in example separators for instruct --- public/scripts/instruct-mode.js | 2 +- public/scripts/openai.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/public/scripts/instruct-mode.js b/public/scripts/instruct-mode.js index 1a7f05aff..ba1b1506e 100644 --- a/public/scripts/instruct-mode.js +++ b/public/scripts/instruct-mode.js @@ -372,7 +372,7 @@ export function formatInstructModeSystemPrompt(systemPrompt) { * @returns {string[]} Formatted example messages string. */ export function formatInstructModeExamples(mesExamplesArray, name1, name2) { - const blockHeading = power_user.context.example_separator ? power_user.context.example_separator + '\n' : ''; + const blockHeading = power_user.context.example_separator ? `${substituteParams(power_user.context.example_separator)}\n` : ''; if (power_user.instruct.skip_examples) { return mesExamplesArray.map(x => x.replace(/\n/i, blockHeading)); diff --git a/public/scripts/openai.js b/public/scripts/openai.js index 59256df75..19268a005 100644 --- a/public/scripts/openai.js +++ b/public/scripts/openai.js @@ -431,15 +431,15 @@ function convertChatCompletionToInstruct(messages, type) { const exampleMessages = messages.filter(x => x.role === 'system' && (x.name === 'example_user' || x.name === 'example_assistant')); if (exampleMessages.length) { - examplesText = power_user.context.example_separator + '\n'; - examplesText += exampleMessages.map(toString).join('\n'); - examplesText = formatInstructModeExamples(examplesText, name1, name2); + const blockHeading = power_user.context.example_separator ? (substituteParams(power_user.context.example_separator) + '\n') : ''; + const examplesArray = exampleMessages.map(m => '\n' + toString(m)); + examplesText = blockHeading + formatInstructModeExamples(examplesArray, name1, name2).join(''); } const chatMessages = messages.slice(firstChatMessage); if (chatMessages.length) { - chatMessagesText = power_user.context.chat_start + '\n'; + chatMessagesText = substituteParams(power_user.context.chat_start) + '\n'; for (const message of chatMessages) { const name = getPrefix(message); From 235afb5d151ff74542648a86396d929ddc26f0ce Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Tue, 9 Apr 2024 11:30:24 +0300 Subject: [PATCH 61/80] Fix asset title for non-extension types --- public/scripts/extensions/assets/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/scripts/extensions/assets/index.js b/public/scripts/extensions/assets/index.js index 8c74f52eb..66f5e55cb 100644 --- a/public/scripts/extensions/assets/index.js +++ b/public/scripts/extensions/assets/index.js @@ -181,6 +181,7 @@ function downloadAssetsList(url) { const displayName = DOMPurify.sanitize(asset['name'] || asset['id']); const description = DOMPurify.sanitize(asset['description'] || ''); const url = isValidUrl(asset['url']) ? asset['url'] : ''; + const title = assetType === 'extension' ? `Extension repo/guide: ${url}` : 'Preview in browser'; const previewIcon = (assetType === 'extension' || assetType === 'character') ? 'fa-arrow-up-right-from-square' : 'fa-headphones-simple'; const assetBlock = $('') @@ -188,7 +189,7 @@ function downloadAssetsList(url) { .append(`
    ${displayName} - + From 7b6ebfc27067a9c5f8d6bf587da558e224bb3b40 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Tue, 9 Apr 2024 13:56:34 +0300 Subject: [PATCH 62/80] Backport gitignore from neo-server --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 72a123efd..64b33ddb2 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,4 @@ access.log /cache/ public/css/user.css /plugins/ +/data From 877824a4f970594f2daa4026228723d610976dcd Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Tue, 9 Apr 2024 16:20:38 +0300 Subject: [PATCH 63/80] Add deprecated endpoint redirection --- server.js | 1 + 1 file changed, 1 insertion(+) diff --git a/server.js b/server.js index b6f59f0a3..81c1a3119 100644 --- a/server.js +++ b/server.js @@ -341,6 +341,7 @@ redirect('/savequickreply', '/api/quick-replies/save'); // Redirect deprecated image endpoints redirect('/uploadimage', '/api/images/upload'); redirect('/listimgfiles/:folder', '/api/images/list/:folder'); +redirect('/api/content/import', '/api/content/importURL'); // Redirect deprecated moving UI endpoints redirect('/savemovingui', '/api/moving-ui/save'); From fc1896dcff96907d78efe6e1eed5d7bdc8f2dab4 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Tue, 9 Apr 2024 17:50:27 +0300 Subject: [PATCH 64/80] #2047 (WIP) Refactor TTS worker to use event source --- public/scripts/extensions/tts/index.js | 205 +++++++++++-------------- 1 file changed, 92 insertions(+), 113 deletions(-) diff --git a/public/scripts/extensions/tts/index.js b/public/scripts/extensions/tts/index.js index 556f6b967..f00641082 100644 --- a/public/scripts/extensions/tts/index.js +++ b/public/scripts/extensions/tts/index.js @@ -19,8 +19,9 @@ const UPDATE_INTERVAL = 1000; let voiceMapEntries = []; let voiceMap = {}; // {charName:voiceid, charName2:voiceid2} -let storedvalue = false; +let talkingHeadState = false; let lastChatId = null; +let lastMessage = null; let lastMessageHash = null; const DEFAULT_VOICE_MARKER = '[Default Voice]'; @@ -67,7 +68,7 @@ export function getPreviewString(lang) { return previewStrings[lang] ?? fallbackPreview; } -let ttsProviders = { +const ttsProviders = { ElevenLabs: ElevenLabsTtsProvider, Silero: SileroTtsProvider, XTTSv2: XTTSTtsProvider, @@ -82,7 +83,6 @@ let ttsProviders = { let ttsProvider; let ttsProviderName; -let ttsLastMessage = null; async function onNarrateOneMessage() { audioElement.src = '/sounds/silence.mp3'; @@ -130,103 +130,13 @@ async function onNarrateText(args, text) { } async function moduleWorker() { - // Primarily determining when to add new chat to the TTS queue - const enabled = $('#tts_enabled').is(':checked'); - $('body').toggleClass('tts', enabled); - if (!enabled) { + if (!extension_settings.tts.enabled) { return; } - const context = getContext(); - const chat = context.chat; - processTtsQueue(); processAudioJobQueue(); updateUiAudioPlayState(); - - // Auto generation is disabled - if (extension_settings.tts.auto_generation == false) { - return; - } - - // no characters or group selected - if (!context.groupId && context.characterId === undefined) { - return; - } - - // Chat changed - if ( - context.chatId !== lastChatId - ) { - currentMessageNumber = context.chat.length ? context.chat.length : 0; - saveLastValues(); - - // Force to speak on the first message in the new chat - if (context.chat.length === 1) { - lastMessageHash = -1; - } - - return; - } - - // take the count of messages - let lastMessageNumber = context.chat.length ? context.chat.length : 0; - - // There's no new messages - let diff = lastMessageNumber - currentMessageNumber; - let hashNew = getStringHash((chat.length && chat[chat.length - 1].mes) ?? ''); - - // if messages got deleted, diff will be < 0 - if (diff < 0) { - // necessary actions will be taken by the onChatDeleted() handler - return; - } - - // if no new messages, or same message, or same message hash, do nothing - if (diff == 0 && hashNew === lastMessageHash) { - return; - } - - // If streaming, wait for streaming to finish before processing new messages - if (context.streamingProcessor && !context.streamingProcessor.isFinished) { - return; - } - - // clone message object, as things go haywire if message object is altered below (it's passed by reference) - const message = structuredClone(chat[chat.length - 1]); - - // if last message within current message, message got extended. only send diff to TTS. - if (ttsLastMessage !== null && message.mes.indexOf(ttsLastMessage) !== -1) { - let tmp = message.mes; - message.mes = message.mes.replace(ttsLastMessage, ''); - ttsLastMessage = tmp; - } else { - ttsLastMessage = message.mes; - } - - // We're currently swiping. Don't generate voice - if (!message || message.mes === '...' || message.mes === '') { - return; - } - - // Don't generate if message doesn't have a display text - if (extension_settings.tts.narrate_translated_only && !(message?.extra?.display_text)) { - return; - } - - // Don't generate if message is a user message and user message narration is disabled - if (message.is_user && !extension_settings.tts.narrate_user) { - return; - } - - // New messages, add new chat to history - lastMessageHash = hashNew; - currentMessageNumber = lastMessageNumber; - - console.debug( - `Adding message from ${message.name} for TTS processing: "${message.mes}"`, - ); - ttsJobQueue.push(message); } function talkingAnimation(switchValue) { @@ -238,11 +148,11 @@ function talkingAnimation(switchValue) { const apiUrl = getApiUrl(); const animationType = switchValue ? 'start' : 'stop'; - if (switchValue !== storedvalue) { + if (switchValue !== talkingHeadState) { try { console.log(animationType + ' Talking Animation'); doExtrasFetch(`${apiUrl}/api/talkinghead/${animationType}_talking`); - storedvalue = switchValue; // Update the storedvalue to the current switchValue + talkingHeadState = switchValue; } catch (error) { // Handle the error here or simply ignore it to prevent logging } @@ -289,7 +199,6 @@ function debugTtsPlayback() { { 'ttsProviderName': ttsProviderName, 'voiceMap': voiceMap, - 'currentMessageNumber': currentMessageNumber, 'audioPaused': audioPaused, 'audioJobQueue': audioJobQueue, 'currentAudioJob': currentAudioJob, @@ -477,21 +386,12 @@ async function processAudioJobQueue() { let ttsJobQueue = []; let currentTtsJob; // Null if nothing is currently being processed -let currentMessageNumber = 0; function completeTtsJob() { console.info(`Current TTS job for ${currentTtsJob?.name} completed.`); currentTtsJob = null; } -function saveLastValues() { - const context = getContext(); - lastChatId = context.chatId; - lastMessageHash = getStringHash( - (context.chat.length && context.chat[context.chat.length - 1].mes) ?? '', - ); -} - async function tts(text, voiceId, char) { async function processResponse(response) { // RVC injection @@ -764,26 +664,103 @@ async function onChatChanged() { await resetTtsPlayback(); const voiceMapInit = initVoiceMap(); await Promise.race([voiceMapInit, delay(1000)]); - ttsLastMessage = null; + lastMessage = null; } -async function onChatDeleted() { +async function onMessageEvent(messageId) { + // If TTS is disabled, do nothing + if (!extension_settings.tts.enabled) { + return; + } + + // Auto generation is disabled + if (!extension_settings.tts.auto_generation) { + return; + } + + const context = getContext(); + + // no characters or group selected + if (!context.groupId && context.characterId === undefined) { + return; + } + + // Chat changed + if (context.chatId !== lastChatId) { + lastChatId = context.chatId; + lastMessageHash = getStringHash(context.chat[messageId]?.mes ?? ''); + + // Force to speak on the first message in the new chat + if (context.chat.length === 1) { + lastMessageHash = -1; + } + } + + // clone message object, as things go haywire if message object is altered below (it's passed by reference) + const message = structuredClone(context.chat[messageId]); + const hashNew = getStringHash(message?.mes ?? ''); + + // if no new messages, or same message, or same message hash, do nothing + if (hashNew === lastMessageHash) { + return; + } + + const isLastMessageInCurrent = () => + lastMessage && + typeof lastMessage === 'object' && + message.swipe_id === lastMessage.swipe_id && + message.name === lastMessage.name && + message.is_user === lastMessage.is_user && + message.mes.indexOf(lastMessage.mes) !== -1; + + // if last message within current message, message got extended. only send diff to TTS. + if (isLastMessageInCurrent()) { + const tmp = structuredClone(message); + message.mes = message.mes.replace(lastMessage.mes, ''); + lastMessage = tmp; + } else { + lastMessage = structuredClone(message); + } + + // We're currently swiping. Don't generate voice + if (!message || message.mes === '...' || message.mes === '') { + return; + } + + // Don't generate if message doesn't have a display text + if (extension_settings.tts.narrate_translated_only && !(message?.extra?.display_text)) { + return; + } + + // Don't generate if message is a user message and user message narration is disabled + if (message.is_user && !extension_settings.tts.narrate_user) { + return; + } + + // New messages, add new chat to history + lastMessageHash = hashNew; + lastChatId = context.chatId; + + console.debug(`Adding message from ${message.name} for TTS processing: "${message.mes}"`); + ttsJobQueue.push(message); +} + +async function onMessageDeleted() { const context = getContext(); // update internal references to new last message lastChatId = context.chatId; - currentMessageNumber = context.chat.length ? context.chat.length : 0; // compare against lastMessageHash. If it's the same, we did not delete the last chat item, so no need to reset tts queue - let messageHash = getStringHash((context.chat.length && context.chat[context.chat.length - 1].mes) ?? ''); + const messageHash = getStringHash((context.chat.length && context.chat[context.chat.length - 1].mes) ?? ''); if (messageHash === lastMessageHash) { return; } lastMessageHash = messageHash; - ttsLastMessage = (context.chat.length && context.chat[context.chat.length - 1].mes) ?? ''; + lastMessage = context.chat.length ? structuredClone(context.chat[context.chat.length - 1]) : null; // stop any tts playback since message might not exist anymore - await resetTtsPlayback(); + resetTtsPlayback(); } /** @@ -1079,8 +1056,10 @@ $(document).ready(function () { setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL); // Init depends on all the things eventSource.on(event_types.MESSAGE_SWIPED, resetTtsPlayback); eventSource.on(event_types.CHAT_CHANGED, onChatChanged); - eventSource.on(event_types.MESSAGE_DELETED, onChatDeleted); + eventSource.on(event_types.MESSAGE_DELETED, onMessageDeleted); eventSource.on(event_types.GROUP_UPDATED, onChatChanged); + eventSource.on(event_types.MESSAGE_SENT, onMessageEvent); + eventSource.on(event_types.MESSAGE_RECEIVED, onMessageEvent); registerSlashCommand('speak', onNarrateText, ['narrate', 'tts'], '(text) – narrate any text using currently selected character\'s voice. Use voice="Character Name" argument to set other voice from the voice map, example: /speak voice="Donald Duck" Quack!', true, true); document.body.appendChild(audioElement); }); From 3fd40a33de5bd430b40ac1c66b0ef0c9882c45f5 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Tue, 9 Apr 2024 19:06:10 +0300 Subject: [PATCH 65/80] Fix double count of chat injects for message fitting logic --- public/script.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/public/script.js b/public/script.js index ee4bb3dbe..4b9b7d42b 100644 --- a/public/script.js +++ b/public/script.js @@ -3467,7 +3467,6 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu // Add persona description to prompt addPersonaDescriptionExtensionPrompt(); // Call combined AN into Generate - let allAnchors = getAllExtensionPrompts(); const beforeScenarioAnchor = getExtensionPrompt(extension_prompt_types.BEFORE_PROMPT).trimStart(); const afterScenarioAnchor = getExtensionPrompt(extension_prompt_types.IN_PROMPT); @@ -3514,10 +3513,11 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu function getMessagesTokenCount() { const encodeString = [ + beforeScenarioAnchor, storyString, + afterScenarioAnchor, examplesString, chatString, - allAnchors, quiet_prompt, cyclePrompt, userAlignmentMessage, @@ -3785,12 +3785,13 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu console.debug('---checking Prompt size'); setPromptString(); const prompt = [ + beforeScenarioAnchor, storyString, + afterScenarioAnchor, mesExmString, mesSend.map((e) => `${e.extensionPrompts.join('')}${e.message}`).join(''), '\n', generatedPromptCache, - allAnchors, quiet_prompt, ].join('').replace(/\r/gm, ''); let thisPromptContextSize = getTokenCount(prompt, power_user.token_padding); @@ -4026,7 +4027,7 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu ...thisPromptBits[currentArrayEntry], rawPrompt: generate_data.prompt || generate_data.input, mesId: getNextMessageId(type), - allAnchors: allAnchors, + allAnchors: '', summarizeString: (extension_prompts['1_memory']?.value || ''), authorsNoteString: (extension_prompts['2_floating_prompt']?.value || ''), smartContextString: (extension_prompts['chromadb']?.value || ''), From da01384cb621c9e8af03c1feda29f3db6ab19be2 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Tue, 9 Apr 2024 19:24:49 +0300 Subject: [PATCH 66/80] Itemization: Deduct chat injects from total chat messages --- public/script.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/public/script.js b/public/script.js index 4b9b7d42b..b329c2c56 100644 --- a/public/script.js +++ b/public/script.js @@ -4027,7 +4027,8 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu ...thisPromptBits[currentArrayEntry], rawPrompt: generate_data.prompt || generate_data.input, mesId: getNextMessageId(type), - allAnchors: '', + allAnchors: getAllExtensionPrompts(), + chatInjects: injectedIndices?.map(index => arrMes[arrMes.length - index - 1])?.join('') || '', summarizeString: (extension_prompts['1_memory']?.value || ''), authorsNoteString: (extension_prompts['2_floating_prompt']?.value || ''), smartContextString: (extension_prompts['chromadb']?.value || ''), @@ -4651,8 +4652,13 @@ function promptItemize(itemizedPrompts, requestedMesId) { zeroDepthAnchorTokens: getTokenCount(itemizedPrompts[thisPromptSet].zeroDepthAnchor), // TODO: unused thisPrompt_padding: itemizedPrompts[thisPromptSet].padding, this_main_api: itemizedPrompts[thisPromptSet].main_api, + chatInjects: getTokenCount(itemizedPrompts[thisPromptSet].chatInjects), }; + if (params.chatInjects){ + params.ActualChatHistoryTokens = params.ActualChatHistoryTokens - params.chatInjects; + } + if (params.this_main_api == 'openai') { //for OAI API //console.log('-- Counting OAI Tokens'); From 99a7925be44af3765bcef920abfd7c875054442c Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Wed, 10 Apr 2024 00:04:20 +0300 Subject: [PATCH 67/80] Don't force a newline for story string if instruct wrap is disabled --- public/scripts/power-user.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js index 7f7ea524a..0d7f8ca0e 100644 --- a/public/scripts/power-user.js +++ b/public/scripts/power-user.js @@ -1942,7 +1942,9 @@ export function renderStoryString(params) { // add a newline to the end of the story string if it doesn't have one if (output.length > 0 && !output.endsWith('\n')) { - output += '\n'; + if (!power_user.instruct.enabled || power_user.instruct.wrap) { + output += '\n'; + } } return output; From 69d219cd7e96b749e54e2c2c54f31013d4619e9f Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Wed, 10 Apr 2024 00:32:53 +0300 Subject: [PATCH 68/80] Allow trimming chat start with {{trim}} macro --- public/script.js | 2 +- public/scripts/templates/macros.html | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/public/script.js b/public/script.js index b329c2c56..84a0e9d5d 100644 --- a/public/script.js +++ b/public/script.js @@ -4575,7 +4575,7 @@ function addChatsPreamble(mesSendString) { function addChatsSeparator(mesSendString) { if (power_user.context.chat_start) { - return substituteParams(power_user.context.chat_start) + '\n' + mesSendString; + return substituteParams(power_user.context.chat_start + '\n') + mesSendString; } else { diff --git a/public/scripts/templates/macros.html b/public/scripts/templates/macros.html index 2eb9ca7b7..46336c083 100644 --- a/public/scripts/templates/macros.html +++ b/public/scripts/templates/macros.html @@ -4,6 +4,7 @@
    • {{pipe}} – only for slash command batching. Replaced with the returned result of the previous command.
    • {{newline}} – just inserts a newline.
    • +
    • {{trim}} – trims newlines surrounding this macro.
    • {{original}} – global prompts defined in API settings. Only valid in Advanced Definitions prompt overrides.
    • {{input}} – the user input
    • {{charPrompt}} – the Character's Main Prompt override
    • From 42e1ade148038a53a09d72bf96cae107e8274469 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Wed, 10 Apr 2024 01:04:12 +0300 Subject: [PATCH 69/80] Add a {{noop}} macro --- public/scripts/macros.js | 1 + public/scripts/templates/macros.html | 1 + 2 files changed, 2 insertions(+) diff --git a/public/scripts/macros.js b/public/scripts/macros.js index f89197d3f..74c89b715 100644 --- a/public/scripts/macros.js +++ b/public/scripts/macros.js @@ -283,6 +283,7 @@ export function evaluateMacros(content, env) { content = replaceVariableMacros(content); content = content.replace(/{{newline}}/gi, '\n'); content = content.replace(/\n*{{trim}}\n*/gi, ''); + content = content.replace(/{{noop}}/gi, ''); content = content.replace(/{{input}}/gi, () => String($('#send_textarea').val())); // Substitute passed-in variables diff --git a/public/scripts/templates/macros.html b/public/scripts/templates/macros.html index 46336c083..dbb50db80 100644 --- a/public/scripts/templates/macros.html +++ b/public/scripts/templates/macros.html @@ -5,6 +5,7 @@
    • {{pipe}} – only for slash command batching. Replaced with the returned result of the previous command.
    • {{newline}} – just inserts a newline.
    • {{trim}} – trims newlines surrounding this macro.
    • +
    • {{noop}} – no operation, just an empty string.
    • {{original}} – global prompts defined in API settings. Only valid in Advanced Definitions prompt overrides.
    • {{input}} – the user input
    • {{charPrompt}} – the Character's Main Prompt override
    • From b8b49f001229aa40f7605b0582852c2fbac1291b Mon Sep 17 00:00:00 2001 From: kingbri Date: Tue, 9 Apr 2024 22:08:46 -0400 Subject: [PATCH 70/80] TextgenSettings: Fix JSON schema fallback Did not fall back if the provided string was empty, resulting in errors Signed-off-by: kingbri --- public/scripts/textgen-settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/textgen-settings.js b/public/scripts/textgen-settings.js index f871434a3..4f8156a38 100644 --- a/public/scripts/textgen-settings.js +++ b/public/scripts/textgen-settings.js @@ -568,7 +568,7 @@ jQuery(function () { const json_schema_string = String($(this).val()); try { - settings.json_schema = JSON.parse(json_schema_string ?? '{}'); + settings.json_schema = JSON.parse(json_schema_string || '{}'); } catch { // Ignore errors from here } From 540cddf300389b92b86f948427312422769c0e8e Mon Sep 17 00:00:00 2001 From: based Date: Wed, 10 Apr 2024 14:24:43 +1000 Subject: [PATCH 71/80] new turbo model --- public/index.html | 12 ++++++++---- public/scripts/extensions/caption/index.js | 5 +++-- public/scripts/extensions/shared.js | 4 ++-- public/scripts/openai.js | 5 +++-- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/public/index.html b/public/index.html index b2916a859..df5a56257 100644 --- a/public/index.html +++ b/public/index.html @@ -2404,16 +2404,20 @@ - - - - + + + + + + + + diff --git a/public/scripts/extensions/caption/index.js b/public/scripts/extensions/caption/index.js index fff8a798a..8534fd0f6 100644 --- a/public/scripts/extensions/caption/index.js +++ b/public/scripts/extensions/caption/index.js @@ -30,7 +30,7 @@ function migrateSettings() { if (extension_settings.caption.source === 'openai') { extension_settings.caption.source = 'multimodal'; extension_settings.caption.multimodal_api = 'openai'; - extension_settings.caption.multimodal_model = 'gpt-4-vision-preview'; + extension_settings.caption.multimodal_model = 'gpt-4-turbo'; } if (!extension_settings.caption.multimodal_api) { @@ -38,7 +38,7 @@ function migrateSettings() { } if (!extension_settings.caption.multimodal_model) { - extension_settings.caption.multimodal_model = 'gpt-4-vision-preview'; + extension_settings.caption.multimodal_model = 'gpt-4-turbo'; } if (!extension_settings.caption.prompt) { @@ -369,6 +369,7 @@ jQuery(function () { - -
      -
      -
      -
      - - - - - - - - - - - - - -
      -

      diff --git a/public/style.css b/public/style.css index 2ff9ef394..04e5cf64d 100644 --- a/public/style.css +++ b/public/style.css @@ -939,8 +939,8 @@ body.reduced-motion #bg_custom { } .avatar img { - width: var(--avatar-base-width); - height: var(--avatar-base-height); + width: calc(var(--avatar-base-width) + 5px); + height: calc(var(--avatar-base-height) + 5px); object-fit: cover; object-position: center center; border-radius: var(--avatar-base-border-radius-round); @@ -985,9 +985,7 @@ body.reduced-motion #bg_custom { } .avatars_inline .avatar { - margin-top: calc(var(--avatar-base-border-radius)); - margin-left: calc(var(--avatar-base-border-radius)); - margin-bottom: calc(var(--avatar-base-border-radius)); + margin: calc(var(--avatar-base-border-radius)); } .avatars_inline .avatar:last-of-type { @@ -1652,6 +1650,7 @@ input[type=search]:focus::-webkit-search-cancel-button { .missing-avatar.inline_avatar { padding: unset; border-radius: var(--avatar-base-border-radius-round); + width: fit-content; } /*applies to char list and mes_text char display name*/ @@ -2000,6 +1999,19 @@ grammarly-extension { justify-content: center; align-items: center; align-self: center !important; + width: 100%; + height: 100%; + /* Avoids cutting off the box shadow on the avatar*/ + margin: 10px; +} + +#avatar_controls { + height: 100%; + width: 100%; + flex-grow: 1; + justify-content: flex-end; + flex-flow: column; + padding: 10px 10px 10px 0; } #description_div, @@ -2210,11 +2222,11 @@ grammarly-extension { font-weight: bold; padding: 5px; margin: 0; - height: 26px; filter: grayscale(0.5); text-align: center; font-size: 17px; aspect-ratio: 1 / 1; + flex: 0.075; } .menu_button:hover, @@ -2633,7 +2645,11 @@ input[type="range"]::-webkit-slider-thumb { color: var(--SmartThemeBodyColor); } -#char-management-dropdown, +#char-management-dropdown { + height: 100%; + margin-bottom: 0; +} + #tagInput { height: 26px; margin-bottom: 0; From d4adbf496f722046cea2055603ed27630e5ad33d Mon Sep 17 00:00:00 2001 From: Kristan Schlikow Date: Tue, 9 Apr 2024 19:25:56 +0200 Subject: [PATCH 76/80] Address styling issues --- public/css/mobile-styles.css | 8 +++++--- public/style.css | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/public/css/mobile-styles.css b/public/css/mobile-styles.css index 5a27ed760..c5e4eb1a7 100644 --- a/public/css/mobile-styles.css +++ b/public/css/mobile-styles.css @@ -208,6 +208,8 @@ #cfgConfig, #logprobsViewer, #movingDivs > div { + /* 100vh are fallback units for browsers that don't support svh */ + height: calc(100vh - 45px); height: calc(100svh - 45px); min-width: 100% !important; width: 100% !important; @@ -220,6 +222,9 @@ top: var(--topBarBlockSize) !important; left: 0 !important; backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2)); + } + + #right-nav-panel { padding-right: 15px; } @@ -241,9 +246,6 @@ /* .avatar_div { margin-top: 5px; } */ - #avatar_controls { - padding: - } #character_popup { width: 100%; diff --git a/public/style.css b/public/style.css index 04e5cf64d..bc85a734b 100644 --- a/public/style.css +++ b/public/style.css @@ -939,8 +939,8 @@ body.reduced-motion #bg_custom { } .avatar img { - width: calc(var(--avatar-base-width) + 5px); - height: calc(var(--avatar-base-height) + 5px); + width: var(--avatar-base-width); + height: var(--avatar-base-height); object-fit: cover; object-position: center center; border-radius: var(--avatar-base-border-radius-round); From b811d690685bb9958563f17963902f5bb83e1814 Mon Sep 17 00:00:00 2001 From: Kristan Schlikow Date: Wed, 10 Apr 2024 19:10:15 +0200 Subject: [PATCH 77/80] Fix sizing on smaller resolutions --- public/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/index.html b/public/index.html index 7c9f9deee..95595144a 100644 --- a/public/index.html +++ b/public/index.html @@ -4187,7 +4187,7 @@ Tokens: counting...
      -
      +
      -
      +