Merge pull request #4316 from SillyTavern/staging Staging
Signed| @@ -1,6 +1,5 @@ | |||
| 1 | > [!IMPORTANT] | 1 | > [!IMPORTANT] |
| 2 | > Die hier veröffentlichten Informationen sind möglicherweise veraltet oder unvollständig. Für aktuelle Informationen nutzen Sie bitte die englische Version. | 2 | > Die hier veröffentlichten Informationen sind möglicherweise veraltet oder unvollständig. Für aktuelle Informationen nutzen Sie bitte die englische Version. |
| 3 | > Letztes Update dieser README: 28.9.2024 | ||
| 4 | 3 | ||
| 5 | <a name="readme-top"></a> | 4 | <a name="readme-top"></a> |
| 6 | 5 | ||
| @@ -11,7 +10,7 @@ | |||
| 11 | [English](readme.md) | German | [中文](readme-zh_cn.md) | [繁體中文](readme-zh_tw.md) | [日本語](readme-ja_jp.md) | [Русский](readme-ru_ru.md) | [한국어](readme-ko_kr.md) | 10 | [English](readme.md) | German | [中文](readme-zh_cn.md) | [繁體中文](readme-zh_tw.md) | [日本語](readme-ja_jp.md) | [Русский](readme-ru_ru.md) | [한국어](readme-ko_kr.md) |
| 12 | 11 | ||
| 13 | [](https://github.com/SillyTavern/SillyTavern/stargazers) | 12 | [](https://github.com/SillyTavern/SillyTavern/stargazers) |
| 14 | [](https://github.com/SillyTavern/SillyTavern/network) | 13 | [](https://github.com/SillyTavern/SillyTavern/forks) |
| 15 | [](https://github.com/SillyTavern/SillyTavern/issues) | 14 | [](https://github.com/SillyTavern/SillyTavern/issues) |
| 16 | [](https://github.com/SillyTavern/SillyTavern/pulls) | 15 | [](https://github.com/SillyTavern/SillyTavern/pulls) |
| 17 | 16 | ||
| @@ -27,7 +26,7 @@ Wir haben eine [Dokumentationswebsite](https://docs.sillytavern.app/), um die me | |||
| 27 | 26 | ||
| 28 | SillyTavern (oder ST abgekürtz) ist eine lokal installierte Benutzeroberfläche, die es dir ermöglicht, mit Textgenerations-LLMs, Bildgenerierungsmaschinen und TTS-Sprachmodellen zu interagieren. | 27 | SillyTavern (oder ST abgekürtz) ist eine lokal installierte Benutzeroberfläche, die es dir ermöglicht, mit Textgenerations-LLMs, Bildgenerierungsmaschinen und TTS-Sprachmodellen zu interagieren. |
| 29 | 28 | ||
| 30 | Angefangen im Februar 2023 als Fork von TavernAI 1.2.8 hat SillyTavern nun über 100 Mitwirkende und 2 Jahre unabhängiger Entwicklung hinter sich und dient weiterhin als führende Software für versierte KI-Hobbyisten. | 29 | Angefangen im Februar 2023 als Fork von TavernAI 1.2.8 hat SillyTavern nun über 200 Mitwirkende und 2 Jahre unabhängiger Entwicklung hinter sich und dient weiterhin als führende Software für versierte KI-Hobbyisten. |
| 31 | 30 | ||
| 32 | ## Unsere Vision | 31 | ## Unsere Vision |
| 33 | 32 | ||
| @@ -35,29 +34,9 @@ Angefangen im Februar 2023 als Fork von TavernAI 1.2.8 hat SillyTavern nun über | |||
| 35 | 2. Wir bieten weder Online- oder gehosteten Dienste an, noch verfolgen wir programmgesteuert Benutzerdaten. | 34 | 2. Wir bieten weder Online- oder gehosteten Dienste an, noch verfolgen wir programmgesteuert Benutzerdaten. |
| 36 | 3. SillyTavern ist ein Herzensprojekt, das von einer engagierten Community von LLM-Enthusiasten unterstützt wird, und wird immer kostenlos und Open Source sein. | 35 | 3. SillyTavern ist ein Herzensprojekt, das von einer engagierten Community von LLM-Enthusiasten unterstützt wird, und wird immer kostenlos und Open Source sein. |
| 37 | 36 | ||
| 38 | ## Branches | 37 | ## Brauche ich einen leistungsstarken PC, um SillyTavern auszuführen? |
| 39 | 38 | ||
| 40 | SillyTavern wird mit einem Branchsystem entwickelt, um ein reibungsloses Erlebnis für alle Nutzer zu gewährleisten. | 39 | Die Hardwareanforderungen sind minimal: Es läuft auf allem, was NodeJS 18 oder höher ausführen kann. Wenn du LLM-Inferenz auf deinem lokalen Rechner durchführen möchtest, empfehlen wir eine NVIDIA-Grafikkarte der 3000er-Serie mit mindestens 6 GB VRAM, aber die tatsächlichen Anforderungen können je nach Modell und Backend, das du verwendest, variieren. |
| 41 | |||
| 42 | * `release` -🌟 **Empfohlen für die meisten Nutzer.** Dies ist der stabilste und empfohlene Branch, der nur aktualisiert wird, wenn wichtige Versionen veröffentlicht werden. Er ist für die Mehrheit der Nutzer geeignet. Typischerweise einmal im Monat aktualisiert. | ||
| 43 | * `staging` - ⚠️ **Nicht für den gelegentlichen Gebrauch empfohlen.** Dieser Branch enthält die neuesten Funktionen, kann jedoch jederzeit instabil sein. Nur für Power-User und Enthusiasten. Mehrmals täglich aktualisiert. | ||
| 44 | |||
| 45 | Wenn du nicht vertraut mit der Verwendung der git CLI bist oder nicht verstehst, was ein Branch ist, mach dir keine Sorgen! Der Release-Branch ist immer die bevorzugte Option für dich. | ||
| 46 | |||
| 47 | ## Was brauche ich zusätzlich zu SillyTavern? | ||
| 48 | |||
| 49 | Da SillyTavern nur eine Benutzeroberfläche ist, benötigst du Zugriff auf ein LLM-Backend, um Inferenz bereitzustellen. Du kannst AI Horde für sofortiges Chatten ohne weitere Einrichtung verwenden. Darüber hinaus unterstützen wir viele andere lokale und cloudbasierte LLM-Backends: OpenAI-kompatible API, KoboldAI, Tabby und viele mehr. Du kannst mehr über unsere unterstützten APIs in [der FAQ](https://docs.sillytavern.app/usage/api-connections/) lesen. | ||
| 50 | |||
| 51 | ### Brauche ich einen leistungsstarken PC, um SillyTavern auszuführen? | ||
| 52 | |||
| 53 | Die Hardwareanforderungen sind minimal: Es läuft auf allem, was NodeJS 18 oder höher ausführen kann. Wenn du LLM-Inferenz auf deinem lokalen Rechner durchführen möchtest, empfehlen wir eine NVIDIA-Grafikkarte der 3000er-Serie mit mindestens 6 GB VRAM. Überprüfe die Dokumentation deines Backends für weitere Einzelheiten. | ||
| 54 | |||
| 55 | ### Vorgeschlagene Backends (keine Partnerschaft oder Werbebeziehung) | ||
| 56 | |||
| 57 | * [AI Horde](https://aihorde.net/) - verwende Modelle, die von Freiwilligen gehostet werden. Erfordert keine weitere Einrichtung | ||
| 58 | * [KoboldCpp](https://github.com/LostRuins/koboldcpp) - ein Favorit der Community, um GGUF-Modelle lokal auszuführen | ||
| 59 | * [tabbyAPI](https://github.com/theroyallab/tabbyAPI) - eine beliebte, portable, speicherplatzoptimierte und lokal gehostete exl2 Inferenz-API | ||
| 60 | * [OpenRouter](https://openrouter.ai) - eine einzige API für viele Cloud-Anbieter (OpenAI, Claude, Meta Llama usw.) sowie beliebte Community-Modelle. | ||
| 61 | 40 | ||
| 62 | ## Fragen oder Vorschläge? | 41 | ## Fragen oder Vorschläge? |
| 63 | 42 | ||
| @@ -83,287 +62,18 @@ Oder nimm direkt Kontakt mit den Entwicklern auf: | |||
| 83 | <img width="500" alt="image" src="https://github.com/user-attachments/assets/9b5f32f0-c3b3-4102-b3f5-0e9213c0f50f"> | 62 | <img width="500" alt="image" src="https://github.com/user-attachments/assets/9b5f32f0-c3b3-4102-b3f5-0e9213c0f50f"> |
| 84 | <img width="500" alt="image" src="https://github.com/user-attachments/assets/913fdbaa-7d33-42f1-ae2c-89dca41c53d1"> | 63 | <img width="500" alt="image" src="https://github.com/user-attachments/assets/913fdbaa-7d33-42f1-ae2c-89dca41c53d1"> |
| 85 | 64 | ||
| 86 | ## Charakterkarten | 65 | ## Installation |
| 87 | |||
| 88 | SillyTavern basiert auf dem Konzept der "Charakterkarten". Eine Charakterkarte ist eine Sammlung von Prompts, die das Verhalten des LLM festlegen und erforderlich sind, um persistente Gespräche in SillyTavern zu führen. Sie funktionieren ähnlich wie ChatGPT's GPTs oder Poe's Bots. Der Inhalt einer Charakterkarte kann alles sein: ein abstraktes Szenario, ein Assistent, der für eine bestimmte Aufgabe maßgeschneidert ist, eine berühmte Persönlichkeit oder ein fiktiver Charakter. | ||
| 89 | |||
| 90 | Das Namensfeld ist der einzige erforderliche Eingabewert für die Charakterkarte. Um ein neutrales Gespräch mit dem LLM zu beginnen, erstelle eine neue Karte, die einfach "Assistent" genannt wird, und lasse die restlichen Felder leer. Für einen thematischeren Chat kannst du dem LLM verschiedene Hintergrundinformationen, Verhaltensweisen und Schreibmuster sowie ein Szenario geben, um das Gespräch zu beginnen. | ||
| 91 | |||
| 92 | Um ein schnelles Gespräch zu führen, ohne eine Charakterkarte auszuwählen, oder um einfach die LLM-Verbindung zu testen, gib einfach dein Prompt in die Eingabezeile auf dem Willkommensbildschirm ein, nachdem du SillyTavern geöffnet hast. Bitte beachte, dass solche Chats vorübergehend sind und nicht gespeichert werden. | ||
| 93 | |||
| 94 | Um eine allgemeine Vorstellung davon zu bekommen, wie man Charakterkarten definiert, sieh dir die mitgelieferte Charakterkarte (Seraphina) an oder lade ausgewählte von der Community erstellte Karten im Menü "Erweiterungen & Assets herunterladen" herunter. | ||
| 95 | |||
| 96 | ## Wichtigste Features | ||
| 97 | |||
| 98 | * Erweiterte Text-Generierungs-Einstellungen mit vielen von der Community erstellten mitgelieferten Einstellungen (Presets) | ||
| 99 | * Unterstützung für World Info: Erstelle reichhaltige Lore oder reduziere die Tokens in deiner Charakterkarte | ||
| 100 | * Gruppenchats: Multi-Bot-Räume für Charaktere, die mit dir und/oder untereinander sprechen | ||
| 101 | * Reichhaltige UI-Anpassungsoptionen: Themes zur Farbenwahl, Hintergrundbilder, benutzerdefiniertes CSS und mehr | ||
| 102 | * Benutzer-Personas: Lass die KI ein wenig über dich wissen, um die Immersion zu erhöhen | ||
| 103 | * Eingebaute RAG-Unterstützung: Füge Dokumente zu deinen Chats hinzu, auf die die KI verweisen kann | ||
| 104 | * Umfangreiches "Chat-Befehle"-System und eigene [Scripting-Engine](https://docs.sillytavern.app/usage/st-script/) | ||
| 105 | |||
| 106 | ## Erweiterungen | ||
| 107 | |||
| 108 | SillyTavern unterstützt Erweiterungen. | ||
| 109 | |||
| 110 | * Emotionale Ausdrucksformen von Charakteren (Sprites) | ||
| 111 | * Automatische Zusammenfassung des Chatverlaufs | ||
| 112 | * Automatische UI- und Chat-Übersetzung | ||
| 113 | * Bildgenerierung mit Stable Diffusion/FLUX/DALL-E | ||
| 114 | * Text-to-Speech für KI-Antwortnachrichten (über ElevenLabs, Silero oder die TTS-Funktion des Betriebssystems) | ||
| 115 | * Websuchfunktionen zum Hinzufügen zusätzlicher realer Kontexte zu deinen Eingabeaufforderungen | ||
| 116 | * Viele weitere sind im Menü "Erweiterungen & Assets herunterladen" verfügbar. | ||
| 117 | |||
| 118 | Tutorials zur Nutzung findest du in der [Dokumentation](https://docs.sillytavern.app/). | ||
| 119 | |||
| 120 | # ⌛ Installation | ||
| 121 | |||
| 122 | > \[!WARNING] | ||
| 123 | > | ||
| 124 | > * INSTALLIERE NICHT IN EINEM VON WINDOWS KONTROLLIERTEN ORDNER (Programme, System32 usw.). | ||
| 125 | > * FÜHRE START.BAT NICHT MIT ADMIN-BERECHTIGUNGEN AUS. | ||
| 126 | > * DIE INSTALLATION AUF WINDOWS 7 IST UNMÖGLICH, DA ES NODEJS 18.16 NICHT AUSFÜHREN KANN. | ||
| 127 | |||
| 128 | ## 🪟 Windows | ||
| 129 | |||
| 130 | ### Installation über Git | ||
| 131 | |||
| 132 | 1. Installiere [NodeJS](https://nodejs.org/en) (die neueste LTS-Version wird empfohlen). | ||
| 133 | 2. Installiere [Git für Windows](https://gitforwindows.org/). | ||
| 134 | 3. Öffne den Windows-Explorer (`Win+E`). | ||
| 135 | 4. Gehe zu oder erstelle einen Ordner, der nicht von Windows kontrolliert oder überwacht wird. (z.B.: C:\MySpecialFolder\) | ||
| 136 | 5. Öffne ein Eingabeaufforderungsfenster in diesem Ordner, indem du in die 'Adressleiste' oben klickst, `cmd` eingibst und Enter drückst. | ||
| 137 | 6. Sobald das schwarze Fenster (Eingabeaufforderung) erscheint, gib EINE der folgenden Optionen ein und drücke Enter: | ||
| 138 | |||
| 139 | * für den Release-Branch: `git clone https://github.com/SillyTavern/SillyTavern -b release` | ||
| 140 | * für den Staging-Branch: `git clone https://github.com/SillyTavern/SillyTavern -b staging` | ||
| 141 | |||
| 142 | 7. Sobald alles geklont ist, doppelklicke auf `Start.bat`, damit NodeJS seine Anforderungen installiert. | ||
| 143 | 8. Der Server wird dann gestartet, und SillyTavern wird in deinem Browser geöffnet. | ||
| 144 | |||
| 145 | ### Installation über GitHub Desktop | ||
| 146 | |||
| 147 | (Dies ermöglicht die Nutzung von git **nur** in GitHub Desktop. Wenn du `git` auch in der Eingabeaufforderung verwenden möchtest, musst du auch [Git für Windows](https://gitforwindows.org/) installieren.) | ||
| 148 | |||
| 149 | 1. Installiere [NodeJS](https://nodejs.org/en) (die neueste LTS-Version wird empfohlen). | ||
| 150 | 2. Installiere [GitHub Desktop](https://central.github.com/deployments/desktop/desktop/latest/win32). | ||
| 151 | 3. Klicke nach der Installation von GitHub Desktop auf `Ein Repository aus dem Internet klonen....` (Hinweis: Du **musst kein** GitHub-Konto für diesen Schritt erstellen). | ||
| 152 | 4. Klicke im Menü auf den Tab URL, gib diese URL ein `https://github.com/SillyTavern/SillyTavern` und klicke auf Klonen. Du kannst den lokalen Pfad ändern, um festzulegen, wohin SillyTavern heruntergeladen werden soll. | ||
| 153 | 5. Um SillyTavern zu öffnen, durchsuche mit dem Windows-Explorer den Ordner, in den du das Repository geklont hast. Standardmäßig wird das Repository hier hin geklont: `C:\Users\[Dein Windows-Benutzername]\Documents\GitHub\SillyTavern`. | ||
| 154 | 6. Doppelklicke auf die Datei `start.bat`. (Hinweis: Der Teil `.bat` des Dateinamens könnte von deinem Betriebssystem verborgen sein. In diesem Fall sieht es aus wie eine Datei namens "`Start`". Dies ist die Datei, auf die du doppelklickst, um SillyTavern auszuführen.) | ||
| 155 | 7. Nach dem Doppelklicken sollte ein großes schwarzes Konsolenfenster erscheinen, und SillyTavern beginnt, das zu installieren, was es zum Betrieb benötigt. | ||
| 156 | 8. Nach dem Installationsprozess sollte das Konsolenfenster so aussehen, und ein SillyTavern-Tab sollte in deinem Browser geöffnet sein. | ||
| 157 | 9. Verbinde dich mit einer der [unterstützten APIs](https://docs.sillytavern.app/usage/api-connections/) und beginne zu chatten! | ||
| 158 | |||
| 159 | ## 🐧 Linux & 🍎 MacOS | ||
| 160 | |||
| 161 | Für MacOS/Linux werden all diese Schritte in einem Terminal durchgeführt. | ||
| 162 | |||
| 163 | 1. Installiere git und nodeJS (die Methode zur Durchführung hängt von deinem Betriebssystem ab). | ||
| 164 | 2. Klone das Repository. | ||
| 165 | |||
| 166 | * für den Release-Branch: `git clone https://github.com/SillyTavern/SillyTavern -b release` | ||
| 167 | * für den Staging-Branch: `git clone https://github.com/SillyTavern/SillyTavern -b staging` | ||
| 168 | |||
| 169 | 3. `cd SillyTavern`, um in den Installationsordner zu navigieren. | ||
| 170 | 4. Führe das Skript `start.sh` mit einem dieser Befehle aus: | ||
| 171 | |||
| 172 | * `./start.sh` | ||
| 173 | * `bash start.sh` | ||
| 174 | |||
| 175 | ## ⚡ Installation über SillyTavern Launcher | ||
| 176 | |||
| 177 | Der SillyTavern Launcher ist ein Installationsassistent, der dir bei der Einrichtung mit vielen Optionen helfen wird, einschließlich der Installation eines Backends für lokale Inferenz. | ||
| 178 | |||
| 179 | ### Für Windows-Nutzer | ||
| 180 | |||
| 181 | 1. Drücke auf deiner Tastatur **`WINDOWS + R`**, um das Ausführen-Dialogfeld zu öffnen. Führe dann den folgenden Befehl aus, um git zu installieren: | ||
| 182 | |||
| 183 | ```shell | ||
| 184 | cmd /c winget install -e --id Git.Git | ||
| 185 | ``` | ||
| 186 | |||
| 187 | 2. Drücke auf deiner Tastatur **`WINDOWS + E`**, um den Datei-Explorer zu öffnen, und navigiere dann zu dem Ordner, in dem du den Launcher installieren möchtest. Gib im gewünschten Ordner in die Adressleiste `cmd` ein und drücke Enter. Führe dann den folgenden Befehl aus: | ||
| 188 | |||
| 189 | ```shell | ||
| 190 | git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher && start installer.bat | ||
| 191 | ``` | ||
| 192 | |||
| 193 | ### Für Linux-Nutzer | ||
| 194 | |||
| 195 | 1. Öffne dein bevorzugtes Terminal und installiere git. | ||
| 196 | 2. Klone den SillyTavern-Launcher mit: | ||
| 197 | |||
| 198 | ```shell | ||
| 199 | git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher | ||
| 200 | ``` | ||
| 201 | |||
| 202 | 3. Starte die installer.sh mit: | ||
| 203 | |||
| 204 | ```shell | ||
| 205 | chmod +x install.sh && ./install.sh | ||
| 206 | ``` | ||
| 207 | |||
| 208 | 4. Nach der Installation starte die launcher.sh mit: | ||
| 209 | |||
| 210 | ```shell | ||
| 211 | chmod +x launcher.sh && ./launcher.sh | ||
| 212 | ``` | ||
| 213 | |||
| 214 | ### Für Mac-Nutzer | ||
| 215 | |||
| 216 | 1. Öffne ein Terminal und installiere brew mit: | ||
| 217 | |||
| 218 | ```shell | ||
| 219 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" | ||
| 220 | ``` | ||
| 221 | |||
| 222 | 2. Installiere git mit: | ||
| 223 | |||
| 224 | ```shell | ||
| 225 | brew install git | ||
| 226 | ``` | ||
| 227 | |||
| 228 | 3. Klone den SillyTavern-Launcher mit: | ||
| 229 | |||
| 230 | ```shell | ||
| 231 | git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher | ||
| 232 | ``` | ||
| 233 | |||
| 234 | 4. Starte die installer.sh mit: | ||
| 235 | |||
| 236 | ```shell | ||
| 237 | chmod +x install.sh && ./install.sh | ||
| 238 | ``` | ||
| 239 | |||
| 240 | 5. Nach der Installation starte die launcher.sh mit: | ||
| 241 | |||
| 242 | ```shell | ||
| 243 | chmod +x launcher.sh && ./launcher.sh | ||
| 244 | ``` | ||
| 245 | |||
| 246 | ## 🐋 Installation über Docker | ||
| 247 | |||
| 248 | Diese Anweisungen setzen voraus, dass du Docker installiert hast, auf deine Befehlszeile für die Installation von Containern zugreifen kannst und mit deren allgemeiner Funktionsweise vertraut bist. | ||
| 249 | |||
| 250 | ### Image selbst bauen | ||
| 251 | |||
| 252 | Wir haben einen umfassenden Leitfaden zur Nutzung von SillyTavern in Docker [hier](http://docs.sillytavern.app/installation/docker/) der die Installationen auf Windows, macOS und Linux abdeckt! Lies ihn, wenn du das Image selbst bauen möchtest. | ||
| 253 | |||
| 254 | ### Verwendung der GitHub Container Registry (am einfachsten) | ||
| 255 | |||
| 256 | Du benötigst zwei zwingende Verzeichniszuordnungen und eine Portzuordnung, um SillyTavern funktionsfähig zu machen. Ersetze in dem Befehl deine Auswahl an den folgenden Stellen: | ||
| 257 | |||
| 258 | #### Container-Variablen | ||
| 259 | |||
| 260 | ##### Volumen-Zuordnungen | ||
| 261 | |||
| 262 | * [config] - Das Verzeichnis, in dem die SillyTavern-Konfigurationsdateien auf deinem Host-Computer gespeichert werden | ||
| 263 | * [data] - Das Verzeichnis, in dem die Benutzerdaten von SillyTavern (einschließlich Charaktere) auf deinem Host-Computer gespeichert werden | ||
| 264 | * [plugins] - (optional) Das Verzeichnis, in dem die SillyTavern-Server-Plugins auf deinem Host-Computer gespeichert werden | ||
| 265 | |||
| 266 | ##### Port-Zuordnungen | ||
| 267 | |||
| 268 | * [PublicPort] - Der Port, über den der Datenverkehr ausgegeben werden soll. Dies ist zwingend erforderlich, da du auf die Instanz von außerhalb des virtuellen Maschinencontainers zugreifst. EXPOSIERE DIES NICHT IM INTERNET, OHNE EINEN GETRENNTEN SERVICE FÜR DIE SICHERHEIT ZU IMPLEMENTIEREN. | ||
| 269 | |||
| 270 | ##### Zusätzliche Einstellungen | ||
| 271 | |||
| 272 | * [DockerNet] - Das Docker-Netzwerk, mit dem der Container erstellt werden soll. Wenn du nicht weißt, was das ist, sieh dir die [offizielle Docker-Dokumentation](https://docs.docker.com/reference/cli/docker/network/) an. | ||
| 273 | * [version] - Auf der rechten Seite dieser GitHub-Seite siehst du "Packages". Wähle das Paket "sillytavern" und du siehst die Imageversionen. Das Image-Tag "latest" hält dich auf dem Laufenden mit dem aktuellen Release. Du kannst auch "staging" und "release" Tags nutzen, die auf die nightly images der jeweiligen Branche verweisen, aber das könnte unangemessen sein, wenn du Erweiterungen verwendest, die möglicherweise kaputt sind und Zeit benötigen, um aktualisiert zu werden. | ||
| 274 | |||
| 275 | #### Installationsbefehl | ||
| 276 | |||
| 277 | 1. Öffne deine Befehlszeile. | ||
| 278 | 2. Führe den folgenden Befehl aus: | ||
| 279 | |||
| 280 | `docker create --name='sillytavern' --net='[DockerNet]' -p '8000:8000/tcp' -v '[plugins]':'/home/node/app/plugins':'rw' -v '[config]':'/home/node/app/config':'rw' -v '[data]':'/home/node/app/data':'rw' 'ghcr.io/sillytavern/sillytavern:[version]'` | ||
| 281 | |||
| 282 | > Beachte, dass 8000 ein Standard-Listening-Port ist. Vergiss nicht, einen geeigneten Port zu verwenden, wenn du ihn in der Konfiguration änderst. | ||
| 283 | |||
| 284 | ## 📱 Installation über Termux auf Android OS | ||
| 285 | |||
| 286 | > \[!HINWEIS] | ||
| 287 | > **SillyTavern kann nativ auf Android-Geräten über Termux ausgeführt werden, aber wir bieten keine offizielle Unterstützung für diesen Anwendungsfall.** | ||
| 288 | > | ||
| 289 | > **Bitte beziehe dich auf diesen Leitfaden von ArroganceComplex#2659:** | ||
| 290 | > | ||
| 291 | > * <https://rentry.org/STAI-Termux> | ||
| 292 | |||
| 293 | **Nicht unterstützte Plattform: android arm LEtime-web.** 32-Bit-Android benötigt eine externe Abhängigkeit, die nicht mit npm installiert werden kann. Verwende den folgenden Befehl, um sie zu installieren: `pkg install esbuild`. Führe dann die üblichen Installationsschritte aus. | ||
| 294 | |||
| 295 | ## API-Schlüsselverwaltung | ||
| 296 | |||
| 297 | SillyTavern speichert deine API-Schlüssel in einer Datei `secrets.json` im Benutzerdatenverzeichnis (`/data/default-user/secrets.json` ist der Standardpfad). | ||
| 298 | |||
| 299 | Standardmäßig sind API-Schlüssel nach dem Speichern und Aktualisieren der Seite nicht mehr über die Benutzeroberfläche sichtbar. | ||
| 300 | |||
| 301 | Um die Ansicht deiner Schlüssel zu aktivieren: | ||
| 302 | |||
| 303 | 1. Setze den Wert von `allowKeysExposure` auf `true` in der Datei `config.yaml`. | ||
| 304 | 2. Starte den SillyTavern-Server neu. | ||
| 305 | 3. Klicke auf den Link 'Verborgene API-Schlüssel anzeigen' in der unteren rechten Ecke des API-Verbindungsfeldes. | ||
| 306 | |||
| 307 | ## Befehlszeilenargumente | ||
| 308 | |||
| 309 | Sie können Befehlszeilenargumente an den Start des SillyTavern-Servers übergeben, um einige Einstellungen in `config.yaml` zu überschreiben. | ||
| 310 | |||
| 311 | ### Beispiele | ||
| 312 | |||
| 313 | ```shell | ||
| 314 | node server.js --port 8000 --listen false | ||
| 315 | # oder | ||
| 316 | npm run start -- --port 8000 --listen false | ||
| 317 | # oder (nur Windows) | ||
| 318 | Start.bat --port 8000 --listen false | ||
| 319 | ``` | ||
| 320 | |||
| 321 | ### Unterstützte Argumente | ||
| 322 | |||
| 323 | | Option | Beschreibung | Typ | | ||
| 324 | |----------------------------------|--------------------------------------------------------------------------------------------------|----------| | ||
| 325 | | `--version` | Versionsnummer anzeigen | boolean | | ||
| 326 | | `--enableIPv6` | Aktiviert IPv6. | boolean | | ||
| 327 | | `--enableIPv4` | Aktiviert IPv4. | boolean | | ||
| 328 | | `--port` | Legt den Port fest, unter dem SillyTavern ausgeführt wird. | number | | ||
| 329 | | `--dnsPreferIPv6` | Bevorzugt IPv6 für DNS. Wenn nicht angegeben. | boolean | | ||
| 330 | | `--browserLaunchEnabled` | Startet SillyTavern automatisch im Browser. | boolean | | ||
| 331 | | `--browserLaunchHostname` | Der Autorun-Hostname, am besten auf „auto" belassen. | string | | ||
| 332 | | `--browserLaunchPort` | Überschreibt den Port für Autorun. | string | | ||
| 333 | | `--browserLaunchAvoidLocalhost` | Vermeidet die Verwendung von „localhost" für Autorun im Auto-Modus. | boolean | | ||
| 334 | | `--listen` | SillyTavern lauscht auf allen Netzwerkschnittstellen. | boolean | | ||
| 335 | | `--corsProxy` | Aktiviert CORS-Proxy. Wenn nicht angegeben. | boolean | | ||
| 336 | | `--disableCsrf` | Deaktiviert CSRF-Schutz | boolean | | ||
| 337 | | `--ssl` | Aktiviert SSL | boolean | | ||
| 338 | | `--certPath` | Pfad zu Ihrer Zertifikatsdatei. | string | | ||
| 339 | | `--keyPath` | Pfad zu Ihrer privaten Schlüsseldatei. | string | | ||
| 340 | | `--whitelist` | Aktiviert den Whitelist-Modus | boolean | | ||
| 341 | | `--dataRoot` | Stammverzeichnis für Datenspeicherung | string | | ||
| 342 | | `--basicAuthMode` | Aktiviert die grundlegende Authentifizierung | boolean | | ||
| 343 | | `--requestProxyEnabled` | Aktiviert die Verwendung eines Proxys für ausgehende Anfragen | boolean | | ||
| 344 | | `--requestProxyUrl` | Proxy-URL anfordern (HTTP- oder SOCKS-Protokolle) | string | | ||
| 345 | | `--requestProxyBypass` | Proxy-Bypass-Liste anfordern (durch Leerzeichen getrennte Liste von Hosts) | Array | | ||
| 346 | |||
| 347 | ## Remoteverbindungen | ||
| 348 | |||
| 349 | Dies ist in den meisten Fällen für Personen gedacht, die SillyTavern auf ihren Mobiltelefonen verwenden möchten, während ihr PC den ST-Server im selben WLAN-Netzwerk betreibt. Es kann jedoch auch verwendet werden, um Remoteverbindungen von überall her zu ermöglichen. | ||
| 350 | |||
| 351 | Lies die ausführliche Anleitung zum Einrichten von Remoteverbindungen in den [Docs](https://docs.sillytavern.app/usage/remoteconnections/). | ||
| 352 | |||
| 353 | Möglicherweise möchtest du SillyTavern-Benutzerprofile auch mit (optionalem) Kennwortschutz konfigurieren: [Benutzer](https://docs.sillytavern.app/installation/st-1.12.0-migration-guide/#users). | ||
| 354 | 66 | ||
| 355 | ## Leistungsprobleme? | 67 | Für detaillierte Installationsanweisungen besuche bitte unsere Dokumentation: |
| 356 | 68 | ||
| 357 | 1. Deaktiviere den Unschärfeeffekt und aktiviere "Verringerte Bewegung" im Bedienfeld "Benutzereinstellungen" (UI-Design schaltet Kategorie um). | 69 | * **[Windows Installationsanleitung](https://docs.sillytavern.app/installation/windows/)** |
| 358 | 2. Wenn du Response Streaming verwendest, stelle die Streaming-FPS auf einen niedrigeren Wert ein (10-15 FPS werden empfohlen). | 70 | * **[MacOS/Linux Installationsanleitung](https://docs.sillytavern.app/installation/linuxmacos/)** |
| 359 | 3. Stelle sicher, dass der Browser die GPU-Beschleunigung zum Rendern verwenden kann. | 71 | * **[Android (Termux) Installationsanleitung](https://docs.sillytavern.app/installation/android-(termux)/)** |
| 72 | * **[Docker Installationsanleitung](https://docs.sillytavern.app/installation/docker/)** | ||
| 360 | 73 | ||
| 361 | ## Lizenz und Danksagungen | 74 | ## Lizenz und Danksagungen |
| 362 | 75 | ||
| 363 | **Dieses Programm wird in der Hoffnung verbreitet, dass es nützlich ist, | 76 | **Dieses Programm wird in der Hoffnung verbreitet, dass es nützlich ist, aber OHNE JEGLICHE GARANTIE; nicht einmal die stillschweigende Garantie der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK. Siehe die GNU Affero General Public License für weitere Details.** |
| 364 | aber OHNE JEGLICHE GARANTIE; nicht einmal die stillschweigende Garantie der | ||
| 365 | MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK. Siehe die | ||
| 366 | GNU Affero General Public License für weitere Details.** | ||
| 367 | 77 | ||
| 368 | * [TavernAI](https://github.com/TavernAI/TavernAI) 1.2.8 von Humi: MIT-Lizenz | 78 | * [TavernAI](https://github.com/TavernAI/TavernAI) 1.2.8 von Humi: MIT-Lizenz |
| 369 | * Teile von CncAnons TavernAITurbo-Mod werden mit Genehmigung verwendet | 79 | * Teile von CncAnons TavernAITurbo-Mod werden mit Genehmigung verwendet |
| @@ -372,6 +82,7 @@ GNU Affero General Public License für weitere Details.** | |||
| 372 | * Symboldesign von Font Awesome <https://fontawesome.com> (Symbole: CC BY 4.0, Schriftarten: SIL OFL 1.1, Code: MIT-Lizenz) | 82 | * Symboldesign von Font Awesome <https://fontawesome.com> (Symbole: CC BY 4.0, Schriftarten: SIL OFL 1.1, Code: MIT-Lizenz) |
| 373 | * Standardinhalt von @OtisAlejandro (Seraphina-Charakter und Lorebook) und @kallmeflocc (10.000 Discord-Benutzer-Feierhintergrund) | 83 | * Standardinhalt von @OtisAlejandro (Seraphina-Charakter und Lorebook) und @kallmeflocc (10.000 Discord-Benutzer-Feierhintergrund) |
| 374 | * Docker-Anleitung von [@mrguymiah](https://github.com/mrguymiah) und [@Bronya-Rand](https://github.com/Bronya-Rand) | 84 | * Docker-Anleitung von [@mrguymiah](https://github.com/mrguymiah) und [@Bronya-Rand](https://github.com/Bronya-Rand) |
| 85 | * kokoro-js library by [@hexgrad](https://github.com/hexgrad) (Apache-2.0 License) | ||
| 375 | 86 | ||
| 376 | ## Top Contributors | 87 | ## Top Contributors |
| 377 | 88 | ||
| @@ -1,6 +1,8 @@ | |||
| 1 | > [!IMPORTANT] | 1 | > [!IMPORTANT] |
| 2 | > ここに掲載されている情報は、古かったり不完全であったりする可能性があります。最新の情報は英語版をご利用ください。 | 2 | > ここに掲載されている情報は、古かったり不完全であったりする可能性があります。最新の情報は英語版をご利用ください。 |
| 3 | 3 | ||
| 4 | <a name="readme-top"></a> | ||
| 5 | |||
| 4 | ![][cover] | 6 | ![][cover] |
| 5 | 7 | ||
| 6 | <div align="center"> | 8 | <div align="center"> |
| @@ -8,7 +10,7 @@ | |||
| 8 | [English](readme.md) | [German](readme-de_de.md) | [中文](readme-zh_cn.md) | [繁體中文](readme-zh_tw.md) | 日本語 | [Русский](readme-ru_ru.md) | [한국어](readme-ko_kr.md) | 10 | [English](readme.md) | [German](readme-de_de.md) | [中文](readme-zh_cn.md) | [繁體中文](readme-zh_tw.md) | 日本語 | [Русский](readme-ru_ru.md) | [한국어](readme-ko_kr.md) |
| 9 | 11 | ||
| 10 | [](https://github.com/SillyTavern/SillyTavern/stargazers) | 12 | [](https://github.com/SillyTavern/SillyTavern/stargazers) |
| 11 | [](https://github.com/SillyTavern/SillyTavern/network) | 13 | [](https://github.com/SillyTavern/SillyTavern/forks) |
| 12 | [](https://github.com/SillyTavern/SillyTavern/issues) | 14 | [](https://github.com/SillyTavern/SillyTavern/issues) |
| 13 | [](https://github.com/SillyTavern/SillyTavern/pulls) | 15 | [](https://github.com/SillyTavern/SillyTavern/pulls) |
| 14 | 16 | ||
| @@ -16,315 +18,77 @@ | |||
| 16 | 18 | ||
| 17 | --- | 19 | --- |
| 18 | 20 | ||
| 19 | モバイルデバイスにも適したレイアウト・マルチAPI(KoboldAI/CPP、Horde、NovelAI、Ooba、OpenAI、OpenRouter、Claude、Scale)、VN ライクな Waifu モード、Stable Diffusion、TTS、WorldInfo(伝承本)、カスタマイズ可能なUI、自動翻訳、大量のプロンプトオプション+サードパーティの拡張機能をインストールする機能 | 21 | SillyTavernは、多くのLLM API(KoboldAI/CPP、Horde、NovelAI、Ooba、Tabby、OpenAI、OpenRouter、Claude、Mistralなど)に対応した統一インターフェース、モバイルフレンドリーなレイアウト、ビジュアルノベルモード、Automatic1111 & ComfyUI API画像生成連携、TTS、WorldInfo(伝承本)、カスタマイズ可能なUI、自動翻訳、必要以上に豊富なプロンプトオプション、そしてサードパーティ製拡張機能による無限の成長可能性を提供します。 |
| 20 | |||
| 21 | [TavernAI](https://github.com/TavernAI/TavernAI) v.1.2.8 のフォークに基づいています。 | ||
| 22 | |||
| 23 | ## 重要事項 | ||
| 24 | |||
| 25 | 1. 私たちは[ドキュメントウェブサイト](https://docs.sillytavern.app/) を作成し、ほとんどの質問にお答えしています。 | ||
| 26 | |||
| 27 | 2. アップデートしたら使っていた拡張機能を見失いましたか?リリースバージョン 1.10.6 以降、これまで内蔵されていた拡張機能のほとんどがダウンロード可能なアドオンに変更されました。ダウンロードは、拡張機能パネル(トップバーのスタックドブロックアイコン)にある内蔵の "Download Extensions and Assets" メニューから行えます。 | ||
| 28 | |||
| 29 | ### Cohee、RossAscends、SillyTavern コミュニティがお届けします | ||
| 30 | |||
| 31 | ### SillyTavern または TavernAI とは何ですか? | ||
| 32 | |||
| 33 | SillyTavern は、あなたのコンピュータ(および Android スマホ)にインストールできるユーザーインターフェイスで、テキスト生成 AI と対話したり、あなたやコミュニティが作成したキャラクターとチャットやロールプレイをすることができます。 | ||
| 34 | |||
| 35 | SillyTavern は TavernAI 1.2.8 のフォークで、より活発な開発が行われており、多くの主要な機能が追加されています。現時点では、これらは完全に独立したプログラムと考えることができます。 | ||
| 36 | |||
| 37 | ### ブランチ | ||
| 38 | |||
| 39 | SillyTavern は、すべてのユーザーにスムーズな体験を保証するために、2 つのブランチシステムを使用して開発されています。 | ||
| 40 | |||
| 41 | * release -🌟 **ほとんどのユーザーにお勧め。** これは最も安定した推奨ブランチで、メジャーリリースがプッシュされた時のみ更新されます。大半のユーザーに適しています。 | ||
| 42 | * staging - ⚠️ **カジュアルな使用にはお勧めしない。** このブランチには最新の機能がありますが、いつ壊れるかわからないので注意してください。パワーユーザーとマニア向けです。 | ||
| 43 | |||
| 44 | git CLI の使い方に慣れていなかったり、ブランチが何なのかわからなかったりしても、心配はいりません!releaseブランチが良い選択肢となります。 | ||
| 45 | |||
| 46 | ### Tavern 以外に何が必要ですか? | ||
| 47 | |||
| 48 | Tavern は単なるユーザーインターフェイスなので、それだけでは何もできません。ロールプレイキャラクターとして機能する AI システムのバックエンドにアクセスする必要があります。様々なバックエンドがサポートされています。リストはこちらです: OpenAPI API (GPT)、KoboldAI (ローカルまたは Google Colab 上で動作)、その他。詳しくは [FAQ](https://docs.sillytavern.app/usage/faq/) をご覧ください。 | ||
| 49 | |||
| 50 | ### Tavern を実行するには、ハイスペックなPCが必要ですか? | ||
| 51 | |||
| 52 | Tavern は単なるフロントエンドのUIであり、必要なハードウェアはごくわずかです。パワフルである必要があるのは、AI システムのバックエンドです。 | ||
| 53 | |||
| 54 | ## モバイルサポート | ||
| 55 | |||
| 56 | > **注** | ||
| 57 | |||
| 58 | > **このフォークは Termux を使って Android スマホで実行できます。ArroganceComplex#2659 のガイドを参照してください:** | ||
| 59 | |||
| 60 | <https://rentry.org/STAI-Termux> | ||
| 61 | |||
| 62 | ## ご質問やご提案 | ||
| 63 | |||
| 64 | ### コミュニティ Discord サーバーを開設しました | ||
| 65 | |||
| 66 | サポートを受けたり、作成したキャラを共有したり、コミュニティの作ったキャラと遊びたいですか?: | ||
| 67 | 22 | ||
| 68 | ### [参加](https://discord.gg/RZdyAEUPvj) | 23 | 私たちは[ドキュメントウェブサイト](https://docs.sillytavern.app/)を用意しており、ほとんどの質問に答え、入門の手助けをします。 |
| 69 | 24 | ||
| 70 | *** | 25 | ## SillyTavernとは? |
| 71 | 26 | ||
| 72 | 開発者と直接連絡: | 27 | SillyTavern(略してST)は、テキスト生成LLM、画像生成エンジン、TTS音声モデルと対話するための、ローカルにインストールされるユーザーインターフェースです。 |
| 73 | 28 | ||
| 74 | * Discord: cohee または rossascends | 29 | 2023年2月にTavernAI 1.2.8のフォークとして始まり、SillyTavernは現在200人以上の貢献者と2年間の独立した開発を経て、知識豊富なAI愛好家のための主要なソフトウェアとして機能し続けています。 |
| 75 | * Reddit: /u/RossAscends または /u/sillylossy | ||
| 76 | * [GitHub issue を投稿](https://github.com/SillyTavern/SillyTavern/issues) | ||
| 77 | 30 | ||
| 78 | ## このバージョンには以下のコードが含まれます | 31 | ## 私たちのビジョン |
| 79 | 32 | ||
| 80 | * 大幅に修正された TavernAI 1.2.8 (コードの 50% 以上が書き換えまたは最適化されています) | 33 | 1. 私たちは、ユーザーにできるだけ多くの実用性とLLMプロンプトの制御権限を与えることを目指しています。急な学習曲線も楽しみの一部です! |
| 81 | * スワイプ | 34 | 2. 私たちはオンラインサービスやホストされたサービスを提供せず、プログラム的にユーザーデータを追跡することもありません。 |
| 82 | * グループチャット: キャラクター同士が会話できるマルチボットルーム | 35 | 3. SillyTavernは、熱心なLLM愛好家のコミュニティによってもたらされた情熱的なプロジェクトであり、常に無料でオープンソースです。 |
| 83 | * チャットチェックポイント / ブランチ | ||
| 84 | * 高度なKoboldAI / TextGen生成設定と、コミュニティが作成した多くのプリセット | ||
| 85 | * ワールド情報サポート: 豊富な伝承を作成したり、キャラクターカードにトークンを保存したりできます | ||
| 86 | * [OpenRouter](https://openrouter.ai) 各種 API(Claude、GPT-4/3.5 など)の接続 | ||
| 87 | * [Oobabooga's TextGen WebUI](https://github.com/oobabooga/text-generation-webui) API 接続 | ||
| 88 | * [AI Horde](https://aihorde.net/) 接続 | ||
| 89 | * プロンプト生成フォーマットの調整 | ||
| 90 | 36 | ||
| 91 | ## 拡張機能 | 37 | ## SillyTavernを実行するには強力なPCが必要ですか? |
| 92 | 38 | ||
| 93 | SillyTavern は拡張できるようになっており。 | 39 | ハードウェア要件は最小限です。NodeJS 18以上を実行できるものであれば何でも動作します。ローカルマシンでLLM推論を行う場合は、少なくとも6GBのVRAMを搭載した3000シリーズのNVIDIAグラフィックスカードを推奨しますが、実際の要件は使用するモデルやバックエンドによって異なる場合があります。 |
| 94 | 40 | ||
| 95 | * キャラクターの感情表現(スプライト) | 41 | ## 質問や提案はありますか? |
| 96 | * チャット履歴の自動サマリー | ||
| 97 | * チャットに画像を送り、AI が内容を解釈する | ||
| 98 | * Stable Diffusion 画像生成 (5 つのチャット関連プリセットと 'free mode') | ||
| 99 | * AI 応答メッセージの音声合成(ElevenLabs、Silero、または OS のシステム TTS 経由) | ||
| 100 | 42 | ||
| 101 | 含まれている拡張機能の完全なリストとその使い方のチュートリアルは [Docs](https://docs.sillytavern.app/) にあります。 | 43 | ### Discordサーバー |
| 102 | 44 | ||
| 103 | ## RossAscends による UI/CSS/クオリティオブライフの調整 | 45 | | [![][discord-shield-badge]][discord-link] | [私たちのDiscordコミュニティに参加してください!](https://discord.gg/sillytavern) サポートを受けたり、お気に入りのキャラクターやプロンプトを共有したりできます。 | |
| 46 | | :---------------------------------------- | :----------------------------------------------------------------------------------------------------------------- | | ||
| 104 | 47 | ||
| 105 | * iOS 用に最適化されたモバイル UI で、ホーム画面へのショートカット保存とフルスクリーンモードでの起動をサポート。 | 48 | または、開発者に直接連絡してください: |
| 106 | * ホットキー | ||
| 107 | * Up = チャットの最後のメッセージを編集する | ||
| 108 | * Ctrl+Up = チャットで最後のユーザーメッセージを編集する | ||
| 109 | * Left = 左スワイプ | ||
| 110 | * Right = 右スワイプ (注: チャットバーに何か入力されている場合、スワイプホットキーが無効になります) | ||
| 111 | * Ctrl+Left = ローカルに保存された変数を見る(ブラウザのコンソールウィンドウにて) | ||
| 112 | * Enter (チャットバー選択時) = AI にメッセージを送る | ||
| 113 | * Ctrl+Enter = 最後の AIのレスポンスを再生成する | ||
| 114 | 49 | ||
| 115 | * ユーザー名の変更と文字の削除でページが更新されなくなりました。 | 50 | * Discord: cohee, rossascends, wolfsblvt |
| 51 | * Reddit: [/u/RossAscends](https://www.reddit.com/user/RossAscends/), [/u/sillylossy](https://www.reddit.com/user/sillylossy/), [u/Wolfsblvt](https://www.reddit.com/user/Wolfsblvt/) | ||
| 52 | * [GitHub issueを投稿](https://github.com/SillyTavern/SillyTavern/issues) | ||
| 116 | 53 | ||
| 117 | * ページロード時に API に自動的に接続するかどうかを切り替えます。 | 54 | ### このプロジェクトが気に入りました!どうすれば貢献できますか? |
| 118 | * ページの読み込み時に、最近見た文字を自動的に読み込むかどうかを切り替えます。 | ||
| 119 | * より良いトークンカウンター - 保存されていないキャラクターに対して機能し、永続的なトークンと一時的なトークンの両方を表示する。 | ||
| 120 | 55 | ||
| 121 | * より良い過去のチャット | 56 | 1. プルリクエストを送ってください。貢献する方法については、[CONTRIBUTING.md](../CONTRIBUTING.md)をご覧ください。 |
| 122 | * 新しいチャットのファイル名は、"(文字) - (作成日)" という読みやすい形式で保存されます | 57 | 2. 提供されたテンプレートを使用して、機能の提案や問題の報告を送ってください。 |
| 123 | * チャットのプレビューが 40 文字から 300 文字に増加。 | 58 | 3. 重複した問題を避けるために、まずこのreadmeファイル全体とドキュメントウェブサイトを確認してください。 |
| 124 | * 文字リストの並べ替えに複数のオプション(名前順、作成日順、チャットサイズ順)があります。 | ||
| 125 | 59 | ||
| 126 | * デフォルトでは、左右の設定パネルはクリックすると閉じます。 | 60 | ## スクリーンショット |
| 127 | * ナビパネルのロックをクリックすると、パネルが開いたままになり、この設定はセッションをまたいで記憶されます。 | ||
| 128 | * ナビパネルの開閉状態もセッションをまたいで保存されます。 | ||
| 129 | 61 | ||
| 130 | * カスタマイズ可能なチャット UI: | 62 | <img width="500" alt="image" src="https://github.com/user-attachments/assets/9b5f32f0-c3b3-4102-b3f5-0e9213c0f50f"> |
| 131 | * 新しいメッセージが届いたときにサウンドを再生する | 63 | <img width="500" alt="image" src="https://github.com/user-attachments/assets/913fdbaa-7d33-42f1-ae2c-89dca41c53d1"> |
| 132 | * 丸型、長方形のアバタースタイルの切り替え | ||
| 133 | * デスクトップのチャットウィンドウを広くする | ||
| 134 | * オプションの半透明ガラス風パネル | ||
| 135 | * 'メインテキスト'、'引用テキスト'、'斜体テキスト'のページカラーをカスタマイズ可能。 | ||
| 136 | * カスタマイズ可能な UI 背景色とぼかし量 | ||
| 137 | 64 | ||
| 138 | ## インストール | 65 | ## インストール |
| 139 | 66 | ||
| 140 | *注: このソフトウェアはローカルにインストールすることを目的としており、colab や他のクラウドノートブックサービス上では十分にテストされていません。* | 67 | 詳細なインストール手順については、私たちのドキュメントをご覧ください: |
| 141 | |||
| 142 | > **警告** | ||
| 143 | |||
| 144 | > WINDOWS が管理しているフォルダ(Program Files、System32 など)にはインストールしないでください | ||
| 145 | |||
| 146 | > START.BAT を管理者権限で実行しないでください | ||
| 147 | |||
| 148 | ### Windows | ||
| 149 | |||
| 150 | Git 経由でのインストール(更新を容易にするため推奨) | ||
| 151 | |||
| 152 | 写真付きのわかりやすいガイドはこちらです: | ||
| 153 | <https://docs.sillytavern.app/installation/windows/> | ||
| 154 | |||
| 155 | 1. [NodeJS](https://nodejs.org/en) をインストール(最新の LTS 版を推奨) | ||
| 156 | 2. [GitHub Desktop](https://central.github.com/deployments/desktop/desktop/latest/win32) をインストールする | ||
| 157 | 3. Windows エクスプローラーを開く (`Win+E`) | ||
| 158 | 4. Windows によって制御または監視されていないフォルダを参照または作成する。(例: C:\MySpecialFolder\) | ||
| 159 | 5. 上部のアドレスバーをクリックし、`cmd` と入力して Enter キーを押し、そのフォルダーの中にコマンドプロンプトを開きます。 | ||
| 160 | 6. 黒いボックス(コマンドプロンプト)がポップアップしたら、そこに以下のいずれかを入力し、Enter を押します: | ||
| 161 | |||
| 162 | * Release ブランチの場合: `git clone https://github.com/SillyTavern/SillyTavern -b release` | ||
| 163 | * Staging ブランチの場合: `git clone https://github.com/SillyTavern/SillyTavern -b staging` | ||
| 164 | |||
| 165 | 7. すべてをクローンしたら、`Start.bat` をダブルクリックして、NodeJS に要件をインストールさせる。 | ||
| 166 | 8. サーバーが起動し、SillyTavern がブラウザにポップアップ表示されます。 | ||
| 167 | |||
| 168 | ZIP ダウンロードによるインストール(推奨しない) | ||
| 169 | |||
| 170 | 1. [NodeJS](https://nodejs.org/en) をインストールする(最新の LTS 版を推奨) | ||
| 171 | 2. GitHub のリポジトリから zip をダウンロードする。(`ソースコード(zip)` は [Releases](https://github.com/SillyTavern/SillyTavern/releases/latest) から入手) | ||
| 172 | 3. お好きなフォルダに解凍してください | ||
| 173 | 4. `Start.bat` をダブルクリックまたはコマンドラインで実行する。 | ||
| 174 | 5. サーバーが準備できれば、ブラウザのタブを開きます。 | ||
| 175 | |||
| 176 | ### Linux | ||
| 177 | |||
| 178 | 1. `node -v` を実行して、Node.js v18 以上(最新の [LTS バージョン](https://nodejs.org/en/download/) を推奨)がインストールされていることを確認してください。 | ||
| 179 | または、[Node Version Manager](https://github.com/nvm-sh/nvm#installing-and-updating) スクリプトを使用して、迅速かつ簡単に Node のインストールを管理します。 | ||
| 180 | 2. `start.sh` スクリプトを実行する。 | ||
| 181 | 3. お楽しみください。 | ||
| 182 | |||
| 183 | ## API キー管理 | ||
| 184 | |||
| 185 | SillyTavern は API キーをサーバーディレクトリの `secrets.json` ファイルに保存します。 | ||
| 186 | |||
| 187 | デフォルトでは、入力後にページをリロードしても、フロントエンドには表示されません。 | ||
| 188 | |||
| 189 | API ブロックのボタンをクリックして、キーを閲覧できるようにする: | ||
| 190 | |||
| 191 | 1. ファイル `config.yaml` で `allowKeysExposure` の値を `true` に設定する。 | ||
| 192 | 2. SillyTavern サーバを再起動します。 | ||
| 193 | |||
| 194 | ## リモート接続 | ||
| 195 | |||
| 196 | SillyTavern をスマホで使用しながら、同じ Wifi ネットワーク上で ST サーバーを PC で実行したい場合に使用します。 | ||
| 197 | 68 | ||
| 198 | しかし、これはどこからでもリモート接続を許可するために使用することができます。 | 69 | * **[Windowsインストールガイド](https://docs.sillytavern.app/installation/windows/)** |
| 70 | * **[MacOS/Linuxインストールガイド](https://docs.sillytavern.app/installation/linuxmacos/)** | ||
| 71 | * **[Android (Termux)インストールガイド](https://docs.sillytavern.app/installation/android-(termux)/)** | ||
| 72 | * **[Dockerインストールガイド](https://docs.sillytavern.app/installation/docker/)** | ||
| 199 | 73 | ||
| 200 | **重要: SillyTavern はシングルユーザーのプログラムなので、ログインすれば誰でもすべてのキャラクターとチャットを見ることができ、UI 内で設定を変更することができます。** | 74 | ## ライセンスとクレジット |
| 201 | |||
| 202 | ### 1. ホワイトリスト IP の管理 | ||
| 203 | |||
| 204 | * SillyTavern のベースインストールフォルダ内に `whitelist.txt` という新しいテキストファイルを作成します。 | ||
| 205 | * テキストエディタでこのファイルを開き、接続を許可したい IP のリストを追加します。 | ||
| 206 | |||
| 207 | *個々の IP とワイルドカード IP 範囲の両方が受け入れられる。例:* | ||
| 208 | |||
| 209 | ```txt | ||
| 210 | 192.168.0.1 | ||
| 211 | 192.168.0.20 | ||
| 212 | ``` | ||
| 213 | |||
| 214 | または | ||
| 215 | |||
| 216 | ```txt | ||
| 217 | 192.168.0.* | ||
| 218 | ``` | ||
| 219 | |||
| 220 | (上記のワイルドカード IP 範囲は、ローカルネットワーク上のどのデバイスでも) | ||
| 221 | |||
| 222 | CIDR マスクも受け付ける(例:10.0.0.0/24)。 | ||
| 223 | |||
| 224 | * `whitelist.txt` ファイルを保存する。 | ||
| 225 | * TAI サーバーを再起動する。 | ||
| 226 | |||
| 227 | これでファイルに指定された IP を持つデバイスが接続できるようになります。 | ||
| 228 | |||
| 229 | *注: `config.yaml` にも `whitelist` 配列があり、同じように使うことができるが、`whitelist.txt` が存在する場合、この配列は無視される。* | ||
| 230 | |||
| 231 | ### 2. ST ホストマシンの IP の取得 | ||
| 232 | |||
| 233 | ホワイトリストの設定後、ST ホストデバイスの IP が必要になります。 | ||
| 234 | |||
| 235 | ST ホストデバイスが同じ無線 LAN ネットワーク上にある場合、ST ホストの内部無線 LAN IP を使用します: | ||
| 236 | |||
| 237 | * Windows の場合: ウィンドウズボタン > 検索バーに `cmd.exe` と入力 > コンソールに `ipconfig` と入力して Enter > `IPv4` のリストを探す。 | ||
| 238 | |||
| 239 | 同じネットワーク上にいない状態で、ホストしているSTに接続したい場合は、STホスト機器のパブリックIPが必要です。 | ||
| 240 | |||
| 241 | * ST ホストデバイスを使用中に、[このページ](https://whatismyipaddress.com/)にアクセスし、`IPv4` を探してください。これはリモートデバイスからの接続に使用するものです。 | ||
| 242 | |||
| 243 | ### 3. リモートデバイスを ST ホストマシンに接続します。 | ||
| 244 | |||
| 245 | 最終的に使用する IP が何であれ、その IP アドレスとポート番号をリモートデバイスのウェブブラウザに入力します。 | ||
| 246 | |||
| 247 | 同じ無線 LAN ネットワーク上の ST ホストの典型的なアドレスは以下のようになります: | ||
| 248 | |||
| 249 | `http://192.168.0.5:8000` | ||
| 250 | |||
| 251 | http:// を使用し、https:// は使用しないでください | ||
| 252 | |||
| 253 | ### ST をすべての IP に開放する | ||
| 254 | |||
| 255 | これはお勧めしませんが、`config.yaml` を開き、`whitelistMode` を `false` に変更してください。 | ||
| 256 | |||
| 257 | SillyTavern のベースインストールフォルダにある `whitelist.txt` が存在する場合は削除(または名前の変更)する必要があります。 | ||
| 258 | |||
| 259 | これは通常安全ではないので、これを行う際にはユーザー名とパスワードを設定する必要があります。 | ||
| 260 | |||
| 261 | ユーザー名とパスワードは `config.yaml` で設定します。 | ||
| 262 | |||
| 263 | ST サーバを再起動すると、ユーザ名とパスワードさえ知っていれば、IP に関係なくどのデバイスでも ST サーバに接続できるようになる。 | ||
| 264 | |||
| 265 | ### まだ接続できませんか? | ||
| 266 | |||
| 267 | * `config.yaml` で見つかったポートに対して、インバウンド/アウトバウンドのファイアウォールルールを作成します。これをルーターのポートフォワーディングと間違えないでください。そうしないと、誰かがあなたのチャットログを見つける可能性があり、それはマジで止めましょう。 | ||
| 268 | * 設定 > ネットワークとインターネット > イーサネットで、プライベートネットワークのプロファイルタイプを有効にします。そうしないと、前述のファイアウォールルールを使っても接続できません。 | ||
| 269 | |||
| 270 | ## パフォーマンスに問題がありますか? | ||
| 271 | |||
| 272 | ユーザー設定パネルでブラー効果なし(高速 UI)モードを有効にしてみてください。 | ||
| 273 | |||
| 274 | ## このプロジェクトが気に入りました!どうすれば貢献できますか? | ||
| 275 | |||
| 276 | ### やるべきこと | ||
| 277 | |||
| 278 | 1. プルリクエストを送る | ||
| 279 | 2. 確立されたテンプレートを使って機能提案と課題レポートを送る | ||
| 280 | 3. 何か質問する前に、readme ファイルや組み込みのドキュメントを読んでください | ||
| 281 | |||
| 282 | ### やるべきではないこと | ||
| 283 | |||
| 284 | 1. 金銭の寄付を申し出る | ||
| 285 | 2. 何の脈絡もなくバグ報告を送る | ||
| 286 | 3. すでに何度も回答されている質問をする | ||
| 287 | |||
| 288 | ## 古い背景画像はどこにありますか? | ||
| 289 | |||
| 290 | 100% オリジナルコンテンツのみのポリシーに移行しているため、古い背景画像はこのリポジトリから削除されました。 | ||
| 291 | |||
| 292 | アーカイブはこちら: | ||
| 293 | |||
| 294 | <https://files.catbox.moe/1xevnc.zip> | ||
| 295 | 75 | ||
| 296 | ## スクリーンショット | 76 | **このプログラムは有用であることを期待して配布されていますが、いかなる保証もありません。商品性または特定目的への適合性の黙示の保証さえもありません。詳細はGNU Affero General Public Licenseをご覧ください。** |
| 297 | 77 | ||
| 298 | <img width="500" alt="image" src="https://github.com/user-attachments/assets/9b5f32f0-c3b3-4102-b3f5-0e9213c0f50f"> | 78 | * [TavernAI](https://github.com/TavernAI/TavernAI) 1.2.8 by Humi: MITライセンス |
| 299 | <img width="500" alt="image" src="https://github.com/user-attachments/assets/913fdbaa-7d33-42f1-ae2c-89dca41c53d1"> | 79 | * CncAnonのTavernAITurbo modの一部を許可を得て使用 |
| 80 | * PepperTacoの作品に触発されたビジュアルノベルモード (<https://github.com/peppertaco/Tavern/>) | ||
| 81 | * GoogleによるNoto Sansフォント (OFLライセンス) | ||
| 82 | * Font Awesomeによるアイコンテーマ <https://fontawesome.com> (アイコン: CC BY 4.0, フォント: SIL OFL 1.1, コード: MITライセンス) | ||
| 83 | * @OtisAlejandroによるデフォルトコンテンツ(Seraphinaキャラクターと伝承本)と@kallmefloccによる10K Discordユーザー記念背景 | ||
| 84 | * [@mrguymiah](https://github.com/mrguymiah)と[@Bronya-Rand](https://github.com/Bronya-Rand)によるDockerガイド | ||
| 85 | * [@hexgrad](https://github.com/hexgrad)によるkokoro-jsライブラリ (Apache-2.0ライセンス) | ||
| 300 | 86 | ||
| 301 | ## ライセンスとクレジット | 87 | ## トップコントリビューター |
| 302 | 88 | ||
| 303 | **このプログラムは有用であることを願って配布されていますが、いかなる保証もありません; | 89 | [](https://github.com/SillyTavern/SillyTavern/graphs/contributors) |
| 304 | また、商品性または特定目的への適合性についての黙示の保証もありません。 | ||
| 305 | 詳細は GNU Affero General Public License をご覧ください。** | ||
| 306 | |||
| 307 | * Humi によるTAI Base: 不明ライセンス | ||
| 308 | * Cohee の修正と派生コード: AGPL v3 | ||
| 309 | * RossAscends の追加: AGPL v3 | ||
| 310 | * CncAnon の TavernAITurbo 改造の一部: 不明ライセンス | ||
| 311 | * kingbri のさまざまなコミットと提案 (<https://github.com/bdashore3>) | ||
| 312 | * city_unit の拡張機能と様々な QoL 機能 (<https://github.com/city-unit>) | ||
| 313 | * StefanDanielSchwarz のさまざまなコミットとバグ報告 (<https://github.com/StefanDanielSchwarz>) | ||
| 314 | * PepperTaco の作品にインスパイアされた Waifu モード (<https:/fugithub.com/peppertaco/Tavern/>) | ||
| 315 | * ピグマリオン大学の皆さん、素晴らしいテスターとしてクールな機能を提案してくれてありがとう! | ||
| 316 | * TextGen のプリセットをコンパイルしてくれた obabooga に感謝 | ||
| 317 | * KAI Lite の KoboldAI プリセット: <https://lite.koboldai.net/> | ||
| 318 | * Google による Noto Sans フォント(OFLライセンス) | ||
| 319 | * Font Awesome によるアイコンテーマ <https://fontawesome.com> (アイコン: CC BY 4.0、フォント: SIL OFL 1.1、コード: MIT License) | ||
| 320 | * ZeldaFan0225 による AI Horde クライアントライブラリ: <https://github.com/ZeldaFan0225/ai_horde> | ||
| 321 | * AlpinDale による Linux 起動スクリプト | ||
| 322 | * FAQ を提供してくれた paniphons に感謝 | ||
| 323 | * 10K ディスコード・ユーザー記念背景 by @kallmeflocc | ||
| 324 | * デフォルトコンテンツ(キャラクターと伝承書)の提供: @OtisAlejandro、@RossAscends、@kallmeflocc | ||
| 325 | * @doloroushyeonse による韓国語翻訳 | ||
| 326 | * k_euler_a による Horde のサポート <https://github.com/Teashrock> | ||
| 327 | * [@XXpE3](https://github.com/XXpE3) による中国語翻訳、中国語 ISSUES の連絡先は @XXpE3 | ||
| 328 | 90 | ||
| 329 | <!-- LINK GROUP --> | 91 | <!-- LINK GROUP --> |
| 330 | [cover]: https://github.com/user-attachments/assets/01a6ae9a-16aa-45f2-8bff-32b5dc587e44 | 92 | [cover]: https://github.com/user-attachments/assets/01a6ae9a-16aa-45f2-8bff-32b5dc587e44 |
| 93 | [discord-link]: https://discord.gg/sillytavern | ||
| 94 | [discord-shield-badge]: https://img.shields.io/discord/1100685673633153084?color=5865F2&label=discord&labelColor=black&logo=discord&logoColor=white&style=for-the-badge | ||
| @@ -10,7 +10,7 @@ | |||
| 10 | [English](readme.md) | [German](readme-de_de.md) | [中文](readme-zh_cn.md) | [繁體中文](readme-zh_tw.md) | [日本語](readme-ja_jp.md) | [Русский](readme-ru_ru.md) | 한국어 | 10 | [English](readme.md) | [German](readme-de_de.md) | [中文](readme-zh_cn.md) | [繁體中文](readme-zh_tw.md) | [日本語](readme-ja_jp.md) | [Русский](readme-ru_ru.md) | 한국어 |
| 11 | 11 | ||
| 12 | [](https://github.com/SillyTavern/SillyTavern/stargazers) | 12 | [](https://github.com/SillyTavern/SillyTavern/stargazers) |
| 13 | [](https://github.com/SillyTavern/SillyTavern/network) | 13 | [](https://github.com/SillyTavern/SillyTavern/forks) |
| 14 | [](https://github.com/SillyTavern/SillyTavern/issues) | 14 | [](https://github.com/SillyTavern/SillyTavern/issues) |
| 15 | [](https://github.com/SillyTavern/SillyTavern/pulls) | 15 | [](https://github.com/SillyTavern/SillyTavern/pulls) |
| 16 | 16 | ||
| @@ -26,8 +26,7 @@ SillyTavern은 많은 LLM API(KoboldAI/CPP, Horde, NovelAI, Ooba, Tabby, OpenAI, | |||
| 26 | 26 | ||
| 27 | SillyTavern(짧게는 ST)은 텍스트 생성 LLM, 이미지 생성 엔진, TTS 음성 모델 등과 상호작할 수 있는 로컬 설치형 UI 입니다. | 27 | SillyTavern(짧게는 ST)은 텍스트 생성 LLM, 이미지 생성 엔진, TTS 음성 모델 등과 상호작할 수 있는 로컬 설치형 UI 입니다. |
| 28 | 28 | ||
| 29 | 2023년 2월, TavernAI 1.2.8의 포크로 시작한 SillyTavern은 현재 100명이 넘는 기여자를 보유하고 있으며, 2년간의 독자적인 개발을 거쳐 숙련된 AI 애호가들을 위한 선도적인 소프트웨어로 자리매김하고 있습니다. | 29 | 2023년 2월, TavernAI 1.2.8의 포크로 시작한 SillyTavern은 현재 200명이 넘는 기여자를 보유하고 있으며, 2년간의 독자적인 개발을 거쳐 숙련된 AI 애호가들을 위한 선도적인 소프트웨어로 자리매김하고 있습니다. |
| 30 | |||
| 31 | 30 | ||
| 32 | ## 우리의 비전 | 31 | ## 우리의 비전 |
| 33 | 32 | ||
| @@ -35,30 +34,9 @@ SillyTavern(짧게는 ST)은 텍스트 생성 LLM, 이미지 생성 엔진, TTS | |||
| 35 | 2. 저희는 어떠한 온라인 및 호스팅 서브시도 제공하지 않으며, 프로그래밍으로 사용자의 데이터를 추적하지 않습니다. | 34 | 2. 저희는 어떠한 온라인 및 호스팅 서브시도 제공하지 않으며, 프로그래밍으로 사용자의 데이터를 추적하지 않습니다. |
| 36 | 3. SillyTavern은 헌신적인 LLM 커뮤니티가 여러분에게 제공하는 열정적인 프로젝트이며, 언제나 무료이며 오픈소스로 제공될 것입니다. | 35 | 3. SillyTavern은 헌신적인 LLM 커뮤니티가 여러분에게 제공하는 열정적인 프로젝트이며, 언제나 무료이며 오픈소스로 제공될 것입니다. |
| 37 | 36 | ||
| 38 | ## 브랜치 | 37 | ## SillyTavern을 위해서 좋은 성능의 PC가 필요한가요? |
| 39 | |||
| 40 | SillyTavern은 모든 사용자가 원활한 경험을 할 수 있도록 두 개의 브랜치를 활용하여 개발되고 있습니다. | ||
| 41 | |||
| 42 | |||
| 43 | * `release` -🌟 **대부분의 사용자에게 추천됨.** 가장 안정적이고 권장되는 브랜치이며, 주요 릴리스가 배포될 때만 업데이트됩니다. 대부분의 사용자에게 적합합니다. 일반적으로 한달에 한번 업데이트됩니다. | ||
| 44 | * `staging` - ⚠️ **일반적인 사용에 추천되지 않음.** 최신 기능을 가지고 있지만, 언제든지 문제가 발생할 수 있습니다. 고급 사용자 및 숙련자 전용입니다. 하루에 여러번 업데이트됩니다. | ||
| 45 | |||
| 46 | 만약 git CLI 사용에 익숙하지 않거나 브랜치가 무엇인지 모르겠다면 release 브랜치가 더 나은 선택입니다. | ||
| 47 | |||
| 48 | ## SillyTavern 외에 무엇이 필요한가요? | ||
| 49 | |||
| 50 | SillyTavern은 인터페이스 역할만 하기 때문에, 실제로 채팅하려면 LLM 백엔드에 대한 액세스 권한이 필요합니다. 즉시 사용 가능한 채팅을 위해 AI Horde를 사용할 수 있습니다. 그 외에도 OpenAI 호환 API, KoboldAI, Tabby 등 많은 로컬 및 클라우드 기반 LLM 백엔드를 지원합니다. 지원되는 API에 대한 자세한 내용은 [FAQ](https://docs.sillytavern.app/usage/api-connections/)에서 확인할 수 있습니다. | ||
| 51 | 38 | ||
| 52 | ### SillyTavern을 위해서 좋은 성능의 PC가 필요한가요? | 39 | 하드웨어 요구 사항은 거의 없습니다: NodeJS 18 이상을 실행할 수 있는 모든 환경에서 작동합니다. 다만 로컬 LLM 모델을 사용할 경우, 최소 6GB VRAM 이상의 3000번대 NVIDIA 그래픽 카드를 권장하지만, 실제 요구 사항은 사용하는 모델과 백엔드에 따라 달라질 수 있습니다. |
| 53 | |||
| 54 | 하드웨어 요구 사항은 거의 없습니다: NodeJS 18 이상을 실행할 수 있는 모든 환경에서 작동합니다. 다만 로컬 LLM 모델을 사용할 경우, 최소 6GB VRAM 이상의 3000번대 NVIDIA 그래픽 카드를 권장합니다. 자세한 내용은 백엔드 문서를 참고하세요. | ||
| 55 | |||
| 56 | ### 추천되는 백엔드 (제휴 없음) | ||
| 57 | |||
| 58 | * [AI Horde](https://aihorde.net/) - 자원 봉사자들이 호스팅하는 모델을 사용합니다. 추가 설정이 필요하지 않습니다. | ||
| 59 | * [KoboldCpp](https://github.com/LostRuins/koboldcpp) - 로컬에서 GGUF 모델을 실행하기 위한 커뮤니티에서 선호하는 옵션입니다. | ||
| 60 | * [tabbyAPI](https://github.com/theroyallab/tabbyAPI) - 인기 있는 경량 로컬 exl2 추론 API입니다. | ||
| 61 | * [OpenRouter](https://openrouter.ai) - OpenAI, Claude, Meta Llama 등 다양한 클라우드 제공업체와 인기 있는 커뮤니티 모델을 위한 단일 API입니다. | ||
| 62 | 40 | ||
| 63 | ## 질문이나 제안이 있으신가요? | 41 | ## 질문이나 제안이 있으신가요? |
| 64 | 42 | ||
| @@ -84,293 +62,14 @@ SillyTavern은 인터페이스 역할만 하기 때문에, 실제로 채팅하 | |||
| 84 | <img width="500" alt="image" src="https://github.com/user-attachments/assets/9b5f32f0-c3b3-4102-b3f5-0e9213c0f50f"> | 62 | <img width="500" alt="image" src="https://github.com/user-attachments/assets/9b5f32f0-c3b3-4102-b3f5-0e9213c0f50f"> |
| 85 | <img width="500" alt="image" src="https://github.com/user-attachments/assets/913fdbaa-7d33-42f1-ae2c-89dca41c53d1"> | 63 | <img width="500" alt="image" src="https://github.com/user-attachments/assets/913fdbaa-7d33-42f1-ae2c-89dca41c53d1"> |
| 86 | 64 | ||
| 65 | ## 설치 | ||
| 87 | 66 | ||
| 88 | ## 캐릭터 카드 | 67 | 자세한 설치 방법은 저희의 문서를 확인하세요: |
| 89 | |||
| 90 | SillyTavern은 "캐릭터 카드"라는 개념을 중심으로 구축되었습니다. 캐릭터 카드는 LLM의 동작을 설정하는 프롬프트 모음이며, SillyTavern에서 지속적인 대화를 하려면 필수적입니다. 이는 ChatGPT의 GPT 또는 Poe의 봇과 유사하게 작동합니다. 캐릭터 카드의 내용은 추상적인 시나리오, 특정 작업에 맞춰진 도우미, 유명 인사 또는 가상 인물 등 무엇이든 될 수 있습니다. | ||
| 91 | |||
| 92 | 이름 필드는 유일한 필수 캐릭터 카드 입력 항목입니다. 언어 모델과 중립적인 대화를 시작하려면 "도우미"라고 간단히 이름 지은 새 카드를 만들고 나머지 상자는 비워 두세요. 더 주제가 있는 채팅을 원한다면 언어 모델에 다양한 배경 정보, 행동 및 작문 패턴, 그리고 채팅을 바로 시작할 시나리오를 제공할 수 있습니다. | ||
| 93 | |||
| 94 | 캐릭터 카드를 선택하지 않고 빠른 대화를 하거나 LLM 연결을 테스트하려면 SillyTavern을 연 후 시작 화면의 입력 창에 프롬프트 입력을 입력하기만 하면 됩니다. 이러한 채팅은 임시적이며 저장되지 않습니다. | ||
| 95 | |||
| 96 | 캐릭터 카드를 정의하는 방법에 대한 일반적인 아이디어를 얻으려면 기본 캐릭터(Seraphina)를 보거나 "확장 프로그램 및 에셋 다운로드" 메뉴에서 선택된 커뮤니티 제작 카드를 다운로드하세요. | ||
| 97 | |||
| 98 | |||
| 99 | ## 핵심 기능 | ||
| 100 | |||
| 101 | * 고급 텍스트 생성 설정과 다양한 커뮤니티 제작 프리셋 | ||
| 102 | * 월드 인포 지원: 풍부한 설정을 만들거나 캐릭터 카드에 토큰 저장 | ||
| 103 | * 그룹 채팅: 캐릭터가 사용자 혹은 다른 캐릭터와 대화할 수 있는 방 | ||
| 104 | * 다양한 UI 커스텀 옵션: 테마 색, 뱌경 이미지, 커스텀 CSS 등 | ||
| 105 | * 유저 페르소나: AI에게 사용자에 대한 정보를 주어 더욱 몰입감을 높임 | ||
| 106 | * 내장 RAG 지원: AI가 참조할 수 있도록 채팅에 문서를 추가 | ||
| 107 | * 광범위한 채팅 명령어 시스템 및 자체 [스크립트](https://docs.sillytavern.app/usage/st-script/) | ||
| 108 | |||
| 109 | ## 확장 | ||
| 110 | |||
| 111 | SillyTavern은 확장(익스텐션)을 지원합니다. | ||
| 112 | |||
| 113 | * 캐릭터 감정 표현 (스프라이트) | ||
| 114 | * 채팅 기록 자동 요약 | ||
| 115 | * 자동 UI 및 채팅 번역 | ||
| 116 | * Stable Diffusion/FLUX/DALL-E 이미지 생성 | ||
| 117 | * AI 응답 메시지 텍스트 음성 변환 (ElevenLabs, Silero 또는 OS 시스템 TTS 사용) | ||
| 118 | * 프롬프트에 추가적인 현실 세계 맥락을 추가하기 위한 웹 검색 기능 | ||
| 119 | * "확장 프로그램 및 에셋 다운로드" 메뉴에서 더 많은 기능을 다운로드할 수 있습니다. | ||
| 120 | |||
| 121 | |||
| 122 | 사용 방법에 대한 튜토리얼은 [Docs](https://docs.sillytavern.app/)에서 확인할 수 있습니다. | ||
| 123 | |||
| 124 | # ⌛ Installation | ||
| 125 | |||
| 126 | > \[!WARNING] | ||
| 127 | > | ||
| 128 | > * **윈도우 제어 폴더에는 설치하지 마십시오 (Program Files, System32 등).** | ||
| 129 | > * **권리자 권한으로 START.BAT을 실행하지 마십시오.** | ||
| 130 | > * **Windows 7에서는 NodeJS 18.16을 실행할 수 없으므로 설치가 불가능합니다.** | ||
| 131 | |||
| 132 | ## 🪟 Windows | ||
| 133 | |||
| 134 | ### Git을 통해 설치하기 | ||
| 135 | |||
| 136 | 1. [NodeJS](https://nodejs.org/ko) 설치 (최신 LTS 버전 권장) | ||
| 137 | 2. [Git for Windows](https://gitforwindows.org/) 설치 | ||
| 138 | 3. 파일 탐색기 열기 (`Win+E`) | ||
| 139 | 4. Windows에서 제어하거나 모니터하지 않는 폴더를 찾거나 만드세요. (ex: C:\MySpecialFolder\) | ||
| 140 | 5. 상단의 주소 표시줄을 클릭하고 `cmd`를 입력한 후 Enter 키를 눌러 해당 폴더 내에서 명령 프롬프트를 여세요. | ||
| 141 | 6. 검은색 창(명령 프롬프트)이 나타나면 다음 중 하나를 입력하고 Enter 키를 누르세요. | ||
| 142 | |||
| 143 | * Release 브랜치: `git clone https://github.com/SillyTavern/SillyTavern -b release` | ||
| 144 | * Staging 브랜치: `git clone https://github.com/SillyTavern/SillyTavern -b staging` | ||
| 145 | |||
| 146 | 7. clone이 완료되면, `Start.bat`을 더블 클릭하여 NodeJS가 필요한 구성요소를 설치하도록 하세요. | ||
| 147 | 8. 그러면 서버가 시작하고, SillyTavern이 브라우저에 나타납니다. | ||
| 148 | |||
| 149 | ### GitHub Desktop을 통해 설치하기 | ||
| 150 | |||
| 151 | (이 방법은 **오직** GitHub Desktop에서만 git 사용이 가능합니다. 명령 프롬프트에서 git을 사용하려면 [Git for Windows](https://gitforwindows.org/)를 설치해야 합니다.) | ||
| 152 | |||
| 153 | |||
| 154 | |||
| 155 | 1. [NodeJS](https://nodejs.org/ko) 설치 (최신 LTS 버전 권장) | ||
| 156 | 2. [GitHub Desktop](https://central.github.com/deployments/desktop/desktop/latest/win32) 설치 | ||
| 157 | 3. GitHub Desktop을 설치했으면, `Clone a repository from the internet....`를 클릭하세요. (참고: 이 과정에서는 Github 계정이 **필요하지 않습니다**.) | ||
| 158 | 4. 메뉴에서 URL 탭을 클릭하고, 다음 URL을 입력한 후 복제를 클릭합니다: `https://github.com/SillyTavern/SillyTavern` 리포지토리가 다운로드될 위치를 변경하려면 로컬 경로를 변경할 수 있습니다. | ||
| 159 | 5. SillyTavern을 열려면 파일 탐색기를 사용하여 리포지토리를 복제한 폴더로 이동합니다. 기본적으로 리포지토리는 다음 위치에 복제됩니다: `C:\Users\[사용자 Windows 사용자 이름]\Documents\GitHub\SillyTavern` | ||
| 160 | 6. `start.bat` 파일을 더블 클릭 하세요. (참고: `.bat` 확장자 명은 OS 설정에 따라 보이지 않을 수 있습니다, 그럴 때는 파일 이름이 "`Start`" 처럼 보일 수 있습니다. 이 파일을 더블 클릭해 SillyTavern을 실행하세요.) | ||
| 161 | 7. 더블 클릭하면, 검고 큰 명령 프롬프트 창이 열리고 SillyTavern이 작동하는데 필요한 항목을 설치하기 시작합니다. | ||
| 162 | 8. 설치 과정이 끝나고 모든 것이 잘 작동한다면, 브라우저에 SillyTavern 탭이 열려 있어야 하고, 명령 프롬프트 창에 다음과 같이 표시되어야 합니다: | ||
| 163 | 9. Connect to any of the [supported APIs](https://docs.sillytavern.app/usage/api-connections/) and start chatting! | ||
| 164 | |||
| 165 | ## 🐧 Linux & 🍎 MacOS | ||
| 166 | |||
| 167 | MacOS / Linux 에서는 이 모든 작업이 터미널에서 수행됩니다. | ||
| 168 | |||
| 169 | 1. git과 nodeJS 설치 (이 작업은 OS에 따라 달라집니다.) | ||
| 170 | 2. 리포지토리 clone하기 | ||
| 171 | |||
| 172 | * Release 브랜치: `git clone https://github.com/SillyTavern/SillyTavern -b release` | ||
| 173 | * Staging 브랜치: `git clone https://github.com/SillyTavern/SillyTavern -b staging` | ||
| 174 | |||
| 175 | 3. `cd SillyTavern` 를 입력해 설치 폴더로 이동하기 | ||
| 176 | 4. `start.sh` 스크립트를 아래의 명령어 중 하나로 실행하기: | ||
| 177 | |||
| 178 | * `./start.sh` | ||
| 179 | * `bash start.sh` | ||
| 180 | |||
| 181 | ## ⚡ SillyTavern Launcher를 통해 설치하기 | ||
| 182 | |||
| 183 | SillyTavern 런처는 로컬 LLM 사용을 위한 백엔드 설치를 포함하여 다양한 설정을 도와주는 설치 마법사입니다. | ||
| 184 | |||
| 185 | |||
| 186 | ### Windows 사용자 | ||
| 187 | |||
| 188 | 1. 키보드에서 **`WINDOWS + R`** 키를 눌러 실행 창을 여세요. 그리고 아래의 명령어를 입력해 git을 설치하세요. | ||
| 189 | |||
| 190 | ```shell | ||
| 191 | cmd /c winget install -e --id Git.Git | ||
| 192 | ``` | ||
| 193 | |||
| 194 | 2. 키보드에서 **`WINDOWS + E`** 키를 눌러 파일 탐색기를 열고 런처를 설치할 폴더로 이동합니다. 원하는 폴더에 도착하면 주소 표시줄에 `cmd`를 입력하고 Enter 키를 누릅니다. 그 후 아래의 명령어를 입력합니다. | ||
| 195 | |||
| 196 | |||
| 197 | ```shell | ||
| 198 | git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher && start installer.bat | ||
| 199 | ``` | ||
| 200 | |||
| 201 | ### Linux 사용자 | ||
| 202 | |||
| 203 | 1. 선호하는 터미널을 열고 git을 설치하세요. | ||
| 204 | 2. SillyTavern-Launcher를 clone 하세요: | ||
| 205 | |||
| 206 | ```shell | ||
| 207 | git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher | ||
| 208 | ``` | ||
| 209 | |||
| 210 | 3. installer.sh를 실행하세요: | ||
| 211 | |||
| 212 | ```shell | ||
| 213 | chmod +x install.sh && ./install.sh | ||
| 214 | ``` | ||
| 215 | |||
| 216 | 4. 설치가 끝나면 launcher.sh를 실행하세요: | ||
| 217 | |||
| 218 | ```shell | ||
| 219 | chmod +x launcher.sh && ./launcher.sh | ||
| 220 | ``` | ||
| 221 | |||
| 222 | ### Mac 사용자 | ||
| 223 | |||
| 224 | 1. 터미널을 열고 Brew를 설치하세요: | ||
| 225 | |||
| 226 | ```shell | ||
| 227 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" | ||
| 228 | ``` | ||
| 229 | |||
| 230 | 2. git을 설치하세요: | ||
| 231 | |||
| 232 | ```shell | ||
| 233 | brew install git | ||
| 234 | ``` | ||
| 235 | |||
| 236 | 3. SillyTavern-Launcher를 clone 하세요: | ||
| 237 | |||
| 238 | ```shell | ||
| 239 | git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher | ||
| 240 | ``` | ||
| 241 | |||
| 242 | 4. installer.sh를 실행하세요: | ||
| 243 | |||
| 244 | ```shell | ||
| 245 | chmod +x install.sh && ./install.sh | ||
| 246 | ``` | ||
| 247 | |||
| 248 | 5. 설치가 끝나면 launcher.sh를 실행하세요: | ||
| 249 | |||
| 250 | ```shell | ||
| 251 | chmod +x launcher.sh && ./launcher.sh | ||
| 252 | ``` | ||
| 253 | |||
| 254 | ## 🐋 Docker를 통해 설치하기 | ||
| 255 | |||
| 256 | 이 방법은 Docker가 설치되어 있고, Docker 설치를 위해 커맨드 라인에 접근할 수 있으며, Docker의 일반적인 작동 방식에 익숙하다고 가정합니다. | ||
| 257 | |||
| 258 | ### 이미지 직접 빌드하기 | ||
| 259 | |||
| 260 | SillyTavern을 Docker에서 사용하는 방법에 대한 포괄적인 가이드는 [여기서](http://docs.sillytavern.app/installation/docker/) 확인할 수 있습니다. 이 가이드는 Windows, macOS 및 Linux에서의 설치를 다룹니다! 직접 이미지를 빌드하려면 이 가이드를 읽어보세요. | ||
| 261 | |||
| 262 | ### GitHub Container Registry 사용하기 (가장 쉬움) | ||
| 263 | |||
| 264 | SillyTavern이 작동하려면 두 개의 필수 디렉터리 매핑과 하나의 포트 매핑이 필요합니다. 명령에서 다음 위치의 선택 항목을 바꿔주세요. | ||
| 265 | |||
| 266 | |||
| 267 | #### Container Variables | ||
| 268 | |||
| 269 | ##### Volume Mappings | ||
| 270 | |||
| 271 | * [config] - SillyTavern 구성 파일이 호스트 컴퓨터에 저장될 디렉터리 | ||
| 272 | * [data] - 캐릭터를 포함한 SillyTavern 사용자 데이터가 호스트 컴퓨터에 저장될 디렉터리 | ||
| 273 | * [plugins] - (선택 사항) SillyTavern 서버 플러그인이 호스트 컴퓨터에 저장될 디렉터리 | ||
| 274 | |||
| 275 | ##### Port Mappings | ||
| 276 | |||
| 277 | * [PublicPort] - 트래픽을 노출할 포트입니다. 가상 머신 컨테이너 외부에서 인스턴스에 접근하므로 필수 사항입니다. 보안을 위한 별도의 서비스를 구현하지 않고는 인터넷에 노출하지 마십시오. | ||
| 278 | |||
| 279 | |||
| 280 | ##### Additional Settings | ||
| 281 | |||
| 282 | * [DockerNet] - 컨테이너가 연결되어 생성되어야 하는 Docker 네트워크입니다. 해당 내용을 모르는 경우 [공식 Docker 문서](https://docs.docker.com/reference/cli/docker/network/)를 참조하세요. | ||
| 283 | * [version] - 이 GitHub 페이지의 오른쪽에서 "Packages"를 선택하면 "sillytavern" 패키지를 볼 수 있습니다. "latest" 이미지 태그는 현재 릴리스와 함께 최신 상태를 유지합니다. 각 브랜치의 야간 이미지를 가리키는 "staging" 및 "release" 태그를 사용할 수도 있지만, 업데이트에 시간이 걸릴 수 있고 중단될 수 있는 확장 프로그램을 사용하는 경우에는 적합하지 않을 수 있습니다. | ||
| 284 | |||
| 285 | |||
| 286 | #### 설치 명령어 | ||
| 287 | |||
| 288 | 1. 커맨드 라인 열기 | ||
| 289 | 2. 아래의 명령어 실행 | ||
| 290 | |||
| 291 | `docker create --name='sillytavern' --net='[DockerNet]' -p '8000:8000/tcp' -v '[plugins]':'/home/node/app/plugins':'rw' -v '[config]':'/home/node/app/config':'rw' -v '[data]':'/home/node/app/data':'rw' 'ghcr.io/sillytavern/sillytavern:[version]'` | ||
| 292 | |||
| 293 | > 8000은 기본 리스닝 포트입니다. 구성에서 포트를 변경한 경우 적절한 포트를 사용하는 것을 잊지 마세요. | ||
| 294 | ## 📱 Termux를 통해 Android OS에 설치하기 | ||
| 295 | |||
| 296 | > \[!NOTE] | ||
| 297 | > **SillyTavern은 Termux를 사용하여 Android 기기에서 기본적으로 실행할 수 있지만, 이러한 사용 사례에 대한 공식적인 지원은 제공하지 않습니다.** | ||
| 298 | > | ||
| 299 | > **ArroganceComplex#2659의 가이드를 참조하세요:** | ||
| 300 | > | ||
| 301 | > * <https://rentry.org/STAI-Termux> | ||
| 302 | |||
| 303 | **지원되지 않는 플랫폼: android arm LEtime-web.** 32비트 Android는 npm으로 설치할 수 없는 외부 종속성이 필요합니다. 다음 명령어를 사용하여 설치하세요: pkg install esbuild. 그런 다음 일반적인 설치 단계를 진행하세요. | ||
| 304 | |||
| 305 | |||
| 306 | ## API 키 관리 | ||
| 307 | |||
| 308 | SillyTavern은 API 키를 사용자 데이터 디렉터리의 `secrets.json` 파일에 저장합니다 (`/data/default-user/secrets.json`이 기본 경로입니다). | ||
| 309 | |||
| 310 | |||
| 311 | |||
| 312 | 기본적으로 API 키는 저장하고 페이지를 새로 고침한 후에는 인터페이스에서 보이지 않습니다. | ||
| 313 | |||
| 314 | 키 보기 기능을 활성화하려면 다음 단계를 따르세요: | ||
| 315 | |||
| 316 | 1. `config.yaml` 파일에서 `allowKeysExposure` 값을 `true로` 설정합니다. | ||
| 317 | 2. SillyTavern 서버를 다시 시작합니다. | ||
| 318 | 3. API 연결 패널 오른쪽 하단에 있는 '숨겨진 API 키 보기' 링크를 클릭합니다. | ||
| 319 | |||
| 320 | ## 커맨드 라인 인수 | ||
| 321 | |||
| 322 | `config.yaml`의 일부 설정을 덮어쓰기 위해 SillyTavern 서버 시작 시 커맨드 라인 인수를 전달할 수 있습니다. | ||
| 323 | |||
| 324 | |||
| 325 | ### 예시 | ||
| 326 | |||
| 327 | ```shell | ||
| 328 | node server.js --port 8000 --listen false | ||
| 329 | # 혹은 | ||
| 330 | npm run start -- --port 8000 --listen false | ||
| 331 | # 혹은 (Windows 전용) | ||
| 332 | Start.bat --port 8000 --listen false | ||
| 333 | ``` | ||
| 334 | |||
| 335 | ### 지원되는 인수 | ||
| 336 | |||
| 337 | | 옵션 | 설명 | 타입 | | ||
| 338 | |-----------------------------------|--------------------------------------------------------------------------------------|----------| | ||
| 339 | | `--version` | 버전 표시 | boolean | | ||
| 340 | | `--enableIPv6` | IPv6 활성화 | boolean | | ||
| 341 | | `--enableIPv4` | IPv4 활성화 | boolean | | ||
| 342 | | `--port` | SillyTavern이 실행될 포트를 설정합니다. | number | | ||
| 343 | | `--dnsPreferIPv6` | DNS에 IPv6를 우선으로 할당합니다. | boolean | | ||
| 344 | | `--browserLaunchEnabled` | 브라우저에서 SillyTavern을 자동으로 실행합니다. | boolean | | ||
| 345 | | `--browserLaunchHostname` | 자동 실행 호스트 이름, 'auto'가 최적의 설정일 것입니다. | string | | ||
| 346 | | `--browserLaunchPort` | 자동 실행 포트 덮어쓰기 | string | | ||
| 347 | | `--browserLaunchAvoidLocalhost` | 자동 모드에서 자동 실행 시 'localhost' 사용 방지 | boolean | | ||
| 348 | | `--listen` | SillyTavern이 모든 네트워크 인터페이스에서 수신 대기합니다. | boolean | | ||
| 349 | | `--corsProxy` | CORS 프록시 활성화. | boolean | | ||
| 350 | | `--disableCsrf` | CSRF 보호 비활성화 | boolean | | ||
| 351 | | `--ssl` | SSL 활성화 | boolean | | ||
| 352 | | `--certPath` | 인증서 파일 경로 | string | | ||
| 353 | | `--keyPath` | 프라이빗 키 파일 경로 | string | | ||
| 354 | | `--whitelist` | 화이트리스트 모드 활성화 | boolean | | ||
| 355 | | `--dataRoot` | 데이터 스토리지의 루트 디렉토리 | string | | ||
| 356 | | `--basicAuthMode` | 기본 인증 활성화 | boolean | | ||
| 357 | | `--requestProxyEnabled` | 외부 리퀘스트 프록시 활성화 | boolean | | ||
| 358 | | `--requestProxyUrl` | 프록시 URL 리퀘스트 (HTTP 혹은 SOCKS 프로토콜) | string | | ||
| 359 | | `--requestProxyBypass` | 프록시 바이패스 리스트 리퀘스트 (공백으로 구분된 호스트 목록) | array | | ||
| 360 | |||
| 361 | ## 원격 연결 | ||
| 362 | |||
| 363 | 대부분의 경우 이는 PC에서 ST 서버를 실행하는 동안 모바일 장치에서 SillyTavern을 사용하려는 사람들을 위한 것입니다. 그러나 원격 연결을 다른 곳에서도 허용하도록 사용할 수 있습니다. | ||
| 364 | |||
| 365 | 원격 연결 설정 방법에 대한 자세한 가이드는 [Docs](https://docs.sillytavern.app/usage/remoteconnections/)에서 확인할 수 있습니다. | ||
| 366 | |||
| 367 | 또한 암호 보호 기능이 포함된 SillyTavern 사용자 프로필을 구성할 수 있습니다 (선택 사항): [Users](https://docs.sillytavern.app/installation/st-1.12.0-migration-guide/#users). | ||
| 368 | |||
| 369 | ## 성능 이슈가 발생하나요? | ||
| 370 | 68 | ||
| 371 | 1. 사용자 설정 패널(UI 테마 전환 카테고리)에서 흐림 효과를 비활성화하고 동작 줄이기를 활성화합니다. | 69 | * **[Windows 설치 가이드](https://docs.sillytavern.app/installation/windows/)** |
| 372 | 2. 응답 스트리밍을 사용하는 경우 스트리밍 FPS를 더 낮은 값(10-15 FPS 권장)으로 설정합니다. | 70 | * **[MacOS/Linux 설치 가이드](https://docs.sillytavern.app/installation/linuxmacos/)** |
| 373 | 3. 브라우저에서 렌더링에 GPU 가속을 사용하도록 설정되어 있는지 확인합니다. | 71 | * **[Android (Termux) 설치 가이드](https://docs.sillytavern.app/installation/android-(termux)/)** |
| 72 | * **[Docker 설치 가이드](https://docs.sillytavern.app/installation/docker/)** | ||
| 374 | 73 | ||
| 375 | ## 라이센스 및 크레딧 | 74 | ## 라이센스 및 크레딧 |
| 376 | 75 | ||
| @@ -383,6 +82,7 @@ Start.bat --port 8000 --listen false | |||
| 383 | * Font Awesome의 아이콘 테마 <https://fontawesome.com> (아이콘: CC BY 4.0, 폰트: SIL OFL 1.1, 코드: MIT 라이선스) | 82 | * Font Awesome의 아이콘 테마 <https://fontawesome.com> (아이콘: CC BY 4.0, 폰트: SIL OFL 1.1, 코드: MIT 라이선스) |
| 384 | * 기본 콘텐츠는 @OtisAlejandro (Seraphina 캐릭터 및 로어북)와 @kallmeflocc (10K 디스코드 사용자 축전 배경화면)가 제공함 | 83 | * 기본 콘텐츠는 @OtisAlejandro (Seraphina 캐릭터 및 로어북)와 @kallmeflocc (10K 디스코드 사용자 축전 배경화면)가 제공함 |
| 385 | * [@mrguymiah](https://github.com/mrguymiah)와 [@Bronya-Rand](https://github.com/Bronya-Rand)의 Docker 가이드 | 84 | * [@mrguymiah](https://github.com/mrguymiah)와 [@Bronya-Rand](https://github.com/Bronya-Rand)의 Docker 가이드 |
| 85 | * [@hexgrad](https://github.com/hexgrad)의 kokoro-js 라이브러리 (Apache-2.0 라이선스) | ||
| 386 | 86 | ||
| 387 | ## 상위 기여자 | 87 | ## 상위 기여자 |
| 388 | 88 | ||
| @@ -10,7 +10,7 @@ | |||
| 10 | [English](readme.md) | [German](readme-de_de.md) | [中文](readme-zh_cn.md) | [繁體中文](readme-zh_tw.md) | [日本語](readme-ja_jp.md) | Русский | [한국어](readme-ko_kr.md) | 10 | [English](readme.md) | [German](readme-de_de.md) | [中文](readme-zh_cn.md) | [繁體中文](readme-zh_tw.md) | [日本語](readme-ja_jp.md) | Русский | [한국어](readme-ko_kr.md) |
| 11 | 11 | ||
| 12 | [](https://github.com/SillyTavern/SillyTavern/stargazers) | 12 | [](https://github.com/SillyTavern/SillyTavern/stargazers) |
| 13 | [](https://github.com/SillyTavern/SillyTavern/network) | 13 | [](https://github.com/SillyTavern/SillyTavern/forks) |
| 14 | [](https://github.com/SillyTavern/SillyTavern/issues) | 14 | [](https://github.com/SillyTavern/SillyTavern/issues) |
| 15 | [](https://github.com/SillyTavern/SillyTavern/pulls) | 15 | [](https://github.com/SillyTavern/SillyTavern/pulls) |
| 16 | 16 | ||
| @@ -18,433 +18,77 @@ | |||
| 18 | 18 | ||
| 19 | --- | 19 | --- |
| 20 | 20 | ||
| 21 | Мобайл-френдли интерфейс, поддержка множества API (KoboldAI/CPP, Horde, NovelAI, Ooba, OpenAI, OpenRouter, Claude, Scale), ВН-образный режим Вайфу, Stable Diffusion, TTS, поддержка миров (лорбуков), кастомизируемый UI, автоперевод, тончайшая настройка промптов + возможность устанавливать расширения. | 21 | SillyTavern предоставляет единый интерфейс для многих LLM API (KoboldAI/CPP, Horde, NovelAI, Ooba, Tabby, OpenAI, OpenRouter, Claude, Mistral и других), мобайл-френдли макет, режим визуальной новеллы, интеграцию с генерацией изображений через API Automatic1111 и ComfyUI, TTS, WorldInfo (лорбуки), кастомизируемый UI, автоперевод, тончайшую настройку промптов, и возможность устанавливать расширения. |
| 22 | 22 | ||
| 23 | Основано на форке [TavernAI](https://github.com/TavernAI/TavernAI) версии 1.2.8 | 23 | Чтобы помочь вам быстрее разобраться в SillyTavern, мы создали [сайт с документацией](https://docs.sillytavern.app/). Ответы на большинство вопросов можно найти там. |
| 24 | 24 | ||
| 25 | ## Важные новости! | 25 | ## Что такое SillyTavern? |
| 26 | 26 | ||
| 27 | 1. Чтобы помочь вам быстрее разобраться в SillyTavern, мы создали [сайт с документацией](https://docs.sillytavern.app/). Ответы на большинство вопросов можно найти там. | 27 | SillyTavern (или сокращенно ST) - это локально устанавливаемый пользовательский интерфейс, который позволяет вам взаимодействовать с LLM для генерации текста, движками для генерации изображений и моделями голоса TTS. |
| 28 | 28 | ||
| 29 | 2. Почему пропали расширения после апдейта? Начиная с версии 1.10.6, большинство встроенных расширений были конвертированы в формат загружаемых аддонов. Их можно установить обратно через меню "Download Extensions and Assets" на панели расширений (значок с тремя кубиками сверху). | 29 | Начавшись в феврале 2023 года как форк TavernAI 1.2.8, SillyTavern теперь насчитывает более 200 контрибьюторов и 2 года независимой разработки, и продолжает служить ведущим программным обеспечением для опытных энтузиастов ИИ. |
| 30 | 30 | ||
| 31 | 3. Не поддерживается следующая платформа: android arm LEtime-web. 32-битный Android требует внешнюю зависимость, которую нельзя установить посредством npm. Для её установки потребуется следующая команда: `pkg install esbuild`. После этого продолжайте установку по общей инструкции. | 31 | ## Наше видение |
| 32 | 32 | ||
| 33 | ### Разрабатывается Cohee, RossAscends и всем сообществом SillyTavern | 33 | 1. Мы стремимся предоставить пользователям как можно больше полезности и контроля над их промптами LLM. Крутая кривая обучения - это часть веселья! |
| 34 | 2. Мы не предоставляем никаких онлайн или хостинговых услуг, а также программно не отслеживаем данные пользователей. | ||
| 35 | 3. SillyTavern - это проект, созданный преданным сообществом энтузиастов LLM, и он всегда будет бесплатным и с открытым исходным кодом. | ||
| 34 | 36 | ||
| 35 | ### Что такое SillyTavern и TavernAI? | 37 | ## Нужен ли мне мощный компьютер для запуска SillyTavern? |
| 36 | 38 | ||
| 37 | SillyTavern — это интерфейс, который устанавливается на ПК (и на Android), который даёт возможность общаться с генеративным ИИ и чатиться/ролеплеить с вашими собственными персонажами или персонажами других пользователей. | 39 | Требования к оборудованию минимальны: он будет работать на всем, что может запустить NodeJS 18 или выше. Если вы собираетесь выполнять инференс LLM на своем локальном компьютере, мы рекомендуем видеокарту NVIDIA 3000-й серии с не менее чем 6 ГБ видеопамяти, но фактические требования могут варьироваться в зависимости от модели и используемого вами бэкенда. |
| 38 | 40 | ||
| 39 | SillyTavern — это форк версии TavernAI 1.2.8, который разрабатывается более активно и имеет множество новых функций. Сейчас уже можно сказать, что это две отдельные и абсолютно самостоятельные программы. | 41 | ## Вопросы или предложения? |
| 40 | 42 | ||
| 41 | ## Скриншоты | 43 | ### Сервер в Discord |
| 42 | |||
| 43 | <img width="500" alt="image" src="https://github.com/user-attachments/assets/9b5f32f0-c3b3-4102-b3f5-0e9213c0f50f"> | ||
| 44 | <img width="500" alt="image" src="https://github.com/user-attachments/assets/913fdbaa-7d33-42f1-ae2c-89dca41c53d1"> | ||
| 45 | |||
| 46 | ### Ветки | ||
| 47 | |||
| 48 | SillyTavern разрабатывается в двух ветках, чтобы всем категориям пользователей было удобно. | ||
| 49 | |||
| 50 | * release -🌟 **Рекомендовано для большинства пользователей.** Самая стабильная ветка, рекомендуем именно её. Обновляется только в момент крупных релизов. Подходит для большей части пользователей. | ||
| 51 | * staging - ⚠️ **Не рекомендуется для повседневного использования.** В этой ветке весь самый свежий и новый функционал, но будьте аккуратны, поскольку сломаться может в любом месте и в любое время. Только для продвинутых пользователей и энтузиастов. | ||
| 52 | |||
| 53 | Если вы не умеете обращаться с git через командную строку, или не знаете, что такое ветка, то не переживайте! Наилучшим вариантом всегда остаётся ветка release. | ||
| 54 | |||
| 55 | ### Что ещё нужно, кроме SillyTavern? | ||
| 56 | |||
| 57 | Сама по себе SillyTavern бесполезна, ведь это просто интерфейс. Вам потребуется доступ к бэкенду с ИИ, который и будет отыгрывать выбранного вами персонажа. Поддерживаются разные виды бэкендов: OpenAPI API (GPT), KoboldAI (локально или на Google Colab), и многое другое. Больше информации в [FAQ](https://docs.sillytavern.app/usage/faq/). | ||
| 58 | |||
| 59 | ### Требуется ли для SillyTavern мощный ПК? | ||
| 60 | |||
| 61 | SillyTavern — это просто интерфейс, поэтому запустить его можно на любой картошке. Мощным должен быть бэкенд с ИИ. | ||
| 62 | |||
| 63 | ## Есть вопросы или предложения? | ||
| 64 | |||
| 65 | ### У нас появился сервер в Discord | ||
| 66 | 44 | ||
| 67 | | [![][discord-shield-badge]][discord-link] | [Вступайте в наше Discord-сообщество!](https://discord.gg/sillytavern) Задавайте вопросы, делитесь любимыми персонажами и промптами. | | 45 | | [![][discord-shield-badge]][discord-link] | [Вступайте в наше Discord-сообщество!](https://discord.gg/sillytavern) Получайте поддержку, делитесь любимыми персонажами и промптами. | |
| 68 | | :---------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------- | | 46 | | :---------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------- | |
| 69 | 47 | ||
| 70 | Также можно написать разработчикам напрямую: | 48 | Или свяжитесь с разработчиками напрямую: |
| 71 | |||
| 72 | * Discord: cohee или rossascends | ||
| 73 | * Reddit: [/u/RossAscends](https://www.reddit.com/user/RossAscends/) или [/u/sillylossy](https://www.reddit.com/user/sillylossy/) | ||
| 74 | * [Запостить issue на GitHub](https://github.com/SillyTavern/SillyTavern/issues) | ||
| 75 | |||
| 76 | ## Эта версия включает | ||
| 77 | |||
| 78 | * Глубоко переработанную TavernAI 1.2.8 (переписано и оптимизировано более 50% кода) | ||
| 79 | * Свайпы | ||
| 80 | * Групповые чаты: комнаты для нескольких ботов, где персонажи могут говорить друг с другом и с вами | ||
| 81 | * Чекпоинты и ветки для чатов | ||
| 82 | * Продвинутые настройки для KoboldAI / TextGen со множеством созданных сообществом пресетов | ||
| 83 | * Поддержка миров (функция "Информация о мире" / WorldInfo): создавайте свой богатый лор, или экономьте токены для карточек персонажей | ||
| 84 | * Соединение через [OpenRouter](https://openrouter.ai) для разных API (Claude, GPT-4/3.5 и других) | ||
| 85 | * Соединение с API [Oobabooga's TextGen WebUI](https://github.com/oobabooga/text-generation-webui) | ||
| 86 | * Соединение с [AI Horde](https://aihorde.net/) | ||
| 87 | * Настройку форматирования промптов | ||
| 88 | |||
| 89 | ## Расширения | ||
| 90 | |||
| 91 | SillyTavern поддерживает расширения. | ||
| 92 | |||
| 93 | * Эмоции для персонажей (спрайты) | ||
| 94 | * Автоматический саммарайз (краткий пересказ) истории чата | ||
| 95 | * Возможность отправить в чат картинку, которую ИИ сможет рассмотреть и понять | ||
| 96 | * Генерация картинок в Stable Diffusion (5 пресетов для чата, плюс свободный режим) | ||
| 97 | * Text-to-speech для сообщений ИИ (с помощью ElevenLabs, Silero, или родной TTS вашей ОС) | ||
| 98 | |||
| 99 | Полный список расширений и инструкций к ним можно найти в [документации](https://docs.sillytavern.app/). | ||
| 100 | |||
| 101 | ## Улучшения от RossAscends для UI/CSS/общего удобства | ||
| 102 | |||
| 103 | * Мобильный интерфейс адаптирован для iOS, добавлена возможность сохранить ярлык на главный экран и открыть приложение в полноэкранном режиме. | ||
| 104 | * Горячие клавиши | ||
| 105 | * Up = Редактировать последнее сообщение в чате | ||
| 106 | * Ctrl+Up = Редактировать ВАШЕ последнее сообщение в чате | ||
| 107 | * Left = свайп влево | ||
| 108 | * Right = свайп вправо (ОБРАТИТЕ ВНИМАНИЕ: когда в окне ввода что-то напечатано, клавиши для свайпа не работают) | ||
| 109 | * Ctrl+Left = посмотреть локальные переменные (в консоли браузера) | ||
| 110 | * Enter (при нахождении внутри окна ввода) = отправить ваше сообщение ИИ | ||
| 111 | * Ctrl+Enter = Повторная генерация последнего ответа ИИ | ||
| 112 | |||
| 113 | * Страница больше не перезагружается при смене имени пользователя или удалении персонажа | ||
| 114 | |||
| 115 | * Отключаемая возможность автоматически соединяться с API при загрузке страницы. | ||
| 116 | * Отключаемая возможность автоматически загружать последнего открытого персонажа при загрузке страницы. | ||
| 117 | * Улучшенный счётчик токенов - работает с несохранёнными персонажами, отображает и перманентные, и временные токены | ||
| 118 | |||
| 119 | * Улучшенный менеджер чатов | ||
| 120 | * Файлы с новыми чатами получают читабельные названия вида "(персонаж) - (когда создано)" | ||
| 121 | * Увеличен размер превью чата с 40 символов до 300. | ||
| 122 | * Несколько вариантов сортировки списка персонажей (по имени, дате создания, размеру чата). | ||
| 123 | |||
| 124 | * Панели настроек слева и справа автоматически скрываются, если щёлкнуть за их пределы. | ||
| 125 | * При нажатии на значок замка навигационная панель будет закреплена на экране, и эта настройка сохранится между сессиями | ||
| 126 | * Сам статус панели (открыта или закрыта) также сохраняется между сессиями | ||
| 127 | |||
| 128 | * Кастомизируемый интерфейс чата: | ||
| 129 | * Настройте звук при получении нового ответа | ||
| 130 | * Переключайтесь между круглыми и прямоугольными аватарками | ||
| 131 | * Увеличенное вширь окно чата для стационарных ПК | ||
| 132 | * Возможность включать полупрозрачные панели, стилизованные под стекло | ||
| 133 | * Настраиваемые цвета для обычного текста, курсива, цитат | ||
| 134 | * Настраиваемый цвет фона и интенсивность размытия | ||
| 135 | |||
| 136 | # ⌛ Установка | ||
| 137 | |||
| 138 | > **Внимание!** | ||
| 139 | > * НЕ УСТАНАВЛИВАЙТЕ В ПАПКИ, КОТОРЫЕ КОНТРОЛИРУЕТ WINDOWS (Program Files, System32 и т.п.). | ||
| 140 | > * НЕ ЗАПУСКАЙТЕ START.BAT С ПРАВАМИ АДМИНИСТРАТОРА | ||
| 141 | > * УСТАНОВКА НА WINDOWS 7 НЕВОЗМОЖНА ИЗ-ЗА ОТСУТСТВИЯ NODEJS 18.16 | ||
| 142 | |||
| 143 | ## 🪟 Windows | ||
| 144 | |||
| 145 | ## Установка через Git | ||
| 146 | 1. Установите [NodeJS](https://nodejs.org/en) (рекомендуется последняя LTS-версия) | ||
| 147 | 2. Установите [Git for Windows](https://gitforwindows.org/) | ||
| 148 | 3. Откройте Проводник (`Win+E`) | ||
| 149 | 4. Перейдите в папку, которую не контролирует Windows, или создайте её. (пример: C:\MySpecialFolder\) | ||
| 150 | 5. Откройте командную строку. Для этого нажмите на адресную строку (сверху), введите `cmd` и нажмите Enter. | ||
| 151 | 6. Когда появится чёрное окошко (командная строка), введите ОДНУ из перечисленных ниже команд: | ||
| 152 | |||
| 153 | - для ветки release: `git clone https://github.com/SillyTavern/SillyTavern -b release` | ||
| 154 | - для ветки staging: `git clone https://github.com/SillyTavern/SillyTavern -b staging` | ||
| 155 | |||
| 156 | 7. Когда клонирование закончится, дважды щёлкните по `Start.bat`, чтобы установить зависимости для NodeJS. | ||
| 157 | 8. После этого сервер запустится, и SillyTavern откроется в вашем браузере. | ||
| 158 | |||
| 159 | ## Установка с помощью SillyTavern Launcher | ||
| 160 | 1. Установите [Git for Windows](https://gitforwindows.org/) | ||
| 161 | 2. Откройте Проводник (`Win+E`) и создайте или выберите папку, в которую будет установлен лаунчер | ||
| 162 | 3. Откройте командную строку. Для этого нажмите на адресную строку (сверху), введите `cmd` и нажмите Enter. | ||
| 163 | 4. Когда появится чёрное окошко, введите следующую команду: `git clone https://github.com/SillyTavern/SillyTavern-Launcher.git` | ||
| 164 | 5. Дважды щёлкните по `installer.bat` и выберите, что именно хотите установить | ||
| 165 | 6. После завершения установки дважды щёлкните по `launcher.bat` | ||
| 166 | |||
| 167 | ## Установка с помощью GitHub Desktop | ||
| 168 | (Тут речь про git **только** в рамках GitHub Desktop, если хотите использовать `git` в командной строке, вам также понадобится [Git for Windows](https://gitforwindows.org/)) | ||
| 169 | 1. Установите [NodeJS](https://nodejs.org/en) (latest LTS version is recommended) | ||
| 170 | 2. Установите [GitHub Desktop](https://central.github.com/deployments/desktop/desktop/latest/win32) | ||
| 171 | 3. После завершения установки GitHub Desktop, нажмите `Clone a repository from the internet....` (обратите внимание: для этого шага **НЕ требуется** аккаунт на GitHub) | ||
| 172 | 4. В меню перейдите на вкладку URL, введите адрес `https://github.com/SillyTavern/SillyTavern`, и нажмите Clone. В поле Local path можно изменить директорию, в которую будет загружена SillyTavern. | ||
| 173 | 6. Чтобы запустить SillyTavern, откройте Проводник и перейдите в выбранную на предыдущем шаге папку. По умолчанию репозиторий будет склонирован сюда: `C:\Users\[Имя пользователя]\Documents\GitHub\SillyTavern` | ||
| 174 | 7. Дважды щёлкните по файлу `start.bat`. (обратите внимание: окончание `.bat` может быть скрыто настройками вашей ОС. Таким образом, имя файла будет выглядеть как "`Start`". Дважды щёлкните по нему, чтобы запустить SillyTavern) | ||
| 175 | 8. После того, как вы дважды щёлкнули по файлу, должно открыться чёрное окошко, и SillyTavern начнёт устанавливать свои зависимости. | ||
| 176 | 9. Если установка прошла успешно, то в командной строке будет вот такое, а в браузере откроется вкладка с SillyTavern: | ||
| 177 | 10. Подключайтесь к любому из [поддерживаемых API](https://docs.sillytavern.app/usage/api-connections/) и начинайте переписку! | ||
| 178 | |||
| 179 | ## 🐧 Linux и 🍎 MacOS | ||
| 180 | |||
| 181 | В MacOS и Linux всё это делается через Терминал. | ||
| 182 | |||
| 183 | 1. Установите git и nodeJS (как именно - зависит от вашей ОС) | ||
| 184 | 2. Клонируйте репозиторий | ||
| 185 | |||
| 186 | - для ветки release: `git clone https://github.com/SillyTavern/SillyTavern -b release` | ||
| 187 | - для ветки staging: `git clone https://github.com/SillyTavern/SillyTavern -b staging` | ||
| 188 | |||
| 189 | 3. Перейдите в папку установки с помощью `cd SillyTavern`. | ||
| 190 | 4. Запустите скрипт `start.sh` с помощью одной из команд: | ||
| 191 | |||
| 192 | - `./start.sh` | ||
| 193 | - `bash start.sh` | ||
| 194 | |||
| 195 | ## Установка с помощью SillyTavern Launcher | ||
| 196 | |||
| 197 | ### Для пользователей Linux | ||
| 198 | 1. Откройте любимый терминал и установите git | ||
| 199 | 2. Загрузите Sillytavern Launcher с помощью команды: `git clone https://github.com/SillyTavern/SillyTavern-Launcher.git` | ||
| 200 | 3. Перейдите в SillyTavern-Launcher: `cd SillyTavern-Launcher` | ||
| 201 | 4. Запустите лаунчер установки: `chmod +x install.sh && ./install.sh`, затем выберите, что конкретно хотите установить | ||
| 202 | 5. После завершения установки, запустите лаунчер следующей командой: `chmod +x launcher.sh && ./launcher.sh` | ||
| 203 | |||
| 204 | ### Для пользователей Mac | ||
| 205 | 1. Откройте терминал и установите brew: `/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"` | ||
| 206 | 2. Затем установите git: `brew install git` | ||
| 207 | 3. Загрузите Sillytavern Launcher: `git clone https://github.com/SillyTavern/SillyTavern-Launcher.git` | ||
| 208 | 4. Перейдите в SillyTavern-Launcher: `cd SillyTavern-Launcher` | ||
| 209 | 5. Запустите лаунчер установки: `chmod +x install.sh && ./install.sh` and choose what you wanna install | ||
| 210 | 6. После завершения установки, запустите лаунчер следующей командой: `chmod +x launcher.sh && ./launcher.sh` | ||
| 211 | |||
| 212 | ## 🐋 Установка с помощью Docker | ||
| 213 | |||
| 214 | Предполагается, что вы уже установили Docker, имеете доступ к командной строке для установки контейнеров и знакомы с их базовым управлением. | ||
| 215 | |||
| 216 | ### Сборка образа самостоятельно | ||
| 217 | |||
| 218 | У нас есть подробное руководство по использованию SillyTavern в Docker [здесь](http://docs.sillytavern.app/installation/docker/), которое охватывает установку на Windows, macOS и Linux! Ознакомьтесь с ним, если хотите создать образ самостоятельно. | ||
| 219 | |||
| 220 | ### Использование реестра контейнеров GitHub (самый простой способ) | ||
| 221 | |||
| 222 | Для работы SillyTavern вам понадобятся две обязательные настройки каталогов и одна настройка порта. В команде замените указанные значения на свои: | ||
| 223 | |||
| 224 | #### Переменные контейнера | ||
| 225 | |||
| 226 | ##### Маппинг томов | ||
| 227 | |||
| 228 | * [config] - директория, где на вашем хосте будут храниться файлы конфигурации SillyTavern. | ||
| 229 | * [data] - директория, где на вашем хосте будут храниться пользовательские данные SillyTavern (включая персонажей). | ||
| 230 | * [plugins] - (необязательно) директория, где на вашем хосте будут храниться плагины сервера SillyTavern. | ||
| 231 | |||
| 232 | ##### Маппинг портов | ||
| 233 | |||
| 234 | * [PublicPort] - Порт, через который будет передаваться трафик. Это обязательно, так как вы будете обращаться к контейнеру извне его виртуальной машины. НЕ ОТКРЫВАЙТЕ этот порт в интернет без реализации дополнительного уровня безопасности. | ||
| 235 | |||
| 236 | ##### Дополнительные настройки | ||
| 237 | |||
| 238 | * [DockerNet] - Docker сеть, к которой контейнер должен быть подключен. Если вы не знаете, что это, обратитесь к [официальной документации Docker](https://docs.docker.com/reference/cli/docker/network/). | ||
| 239 | * [version] - на правой части этой страницы GitHub вы найдете раздел "Packages". Выберите пакет "sillytavern", чтобы увидеть версии образов. Тег "latest" позволит вам обновляться до текущего релиза. Также доступны теги "staging" и "release", которые соответствуют ночным сборкам соответствующих веток. Однако это может быть нецелесообразно, если вы используете расширения, которые могут ломаться и требуют времени для обновления. | ||
| 240 | |||
| 241 | #### Команда установки | ||
| 242 | |||
| 243 | 1. Откройте командную строку | ||
| 244 | 2. Выполните следующую команду | ||
| 245 | |||
| 246 | `docker create --name='sillytavern' --net='[DockerNet]' -p '8000:8000/tcp' -v '[plugins]':'/home/node/app/plugins':'rw' -v '[config]':'/home/node/app/config':'rw' -v '[data]':'/home/node/app/data':'rw' 'ghcr.io/sillytavern/sillytavern:[version]'` | ||
| 247 | |||
| 248 | > Заметьте, что 8000 является портом по умолчанию. Не забудьте использовать соответствующий порт, если вы измените его в конфиге. | ||
| 249 | |||
| 250 | ## 📱 Мобильные устройства - Установка при помощи termux | ||
| 251 | |||
| 252 | > **ОБРАТИТЕ ВНИМАНИЕ!** | ||
| 253 | > | ||
| 254 | > **На Android-телефонах SillyTavern можно запускать нативно посредством Termux. Обратитесь к гайду, написанному ArroganceComplex#2659:** | ||
| 255 | > | ||
| 256 | > * <https://rentry.org/STAI-Termux> | ||
| 257 | |||
| 258 | 49 | ||
| 259 | ## Управление ключами от API | 50 | * Discord: cohee, rossascends, wolfsblvt |
| 51 | * Reddit: [/u/RossAscends](https://www.reddit.com/user/RossAscends/), [/u/sillylossy](https://www.reddit.com/user/sillylossy/), [u/Wolfsblvt](https://www.reddit.com/user/Wolfsblvt/) | ||
| 52 | * [Опубликовать issue на GitHub](https://github.com/SillyTavern/SillyTavern/issues) | ||
| 260 | 53 | ||
| 261 | SillyTavern сохраняет ключи от ваших API в файле `secrets.json` в папке на сервере. | 54 | ### Мне нравится ваш проект! Как я могу внести свой вклад? |
| 262 | 55 | ||
| 263 | По умолчанию, ключи не будут отображаться на фронте после их ввода и перезагрузки страницы. | 56 | 1. Отправляйте пулл-реквесты. Узнайте, как внести свой вклад: [CONTRIBUTING.md](../CONTRIBUTING.md) |
| 57 | 2. Отправляйте предложения по функциям и отчеты о проблемах, используя предоставленные шаблоны. | ||
| 58 | 3. Прочтите весь этот файл readme и сайт документации, чтобы избежать отправки дублирующихся проблем. | ||
| 264 | 59 | ||
| 265 | Чтобы включить возможность отображения ключей путём нажатия кнопки в блоке API: | 60 | ## Скриншоты |
| 266 | |||
| 267 | 1. Зайдите в файл `config.yaml` и установите `allowKeysExposure` в положение `true`. | ||
| 268 | 2. Перезапустите сервер SillyTavern. | ||
| 269 | |||
| 270 | ## Аргументы командной строки | ||
| 271 | |||
| 272 | Вы можете передавать аргументы командной строки при запуске сервера SillyTavern, чтобы переопределять настройки из `config.yaml`. | ||
| 273 | |||
| 274 | ### Примеры | ||
| 275 | |||
| 276 | ```shell | ||
| 277 | node server.js --port 8000 --listen false | ||
| 278 | # или | ||
| 279 | npm run start -- --port 8000 --listen false | ||
| 280 | # или (только на Windows) | ||
| 281 | Start.bat --port 8000 --listen false | ||
| 282 | ``` | ||
| 283 | |||
| 284 | ### Поддерживаемые аргументы | ||
| 285 | |||
| 286 | | Аргумент | Описание | Тип | | ||
| 287 | |------------------------------|-----------------------------------------------------------------------------------------------------------|---------| | ||
| 288 | | `--version` | Показывает номер версии. | boolean | | ||
| 289 | | `--enableIPv6` | Включает IPv6. | boolean | | ||
| 290 | | `--enableIPv4` | Включает IPv4. | boolean | | ||
| 291 | | `--port` | Устанавливает порт, который будет использовать SillyTavern. | number | | ||
| 292 | | `--dnsPreferIPv6` | Отдает предпочтение IPv6 для dns. | boolean | | ||
| 293 | | `--browserLaunchEnabled` | Автоматический запуск SillyTavern в браузере. | boolean | | ||
| 294 | | `--browserLaunchHostname` | Имя хоста автозапуска, лучше оставить на 'auto'. | string | | ||
| 295 | | `--browserLaunchPort` | Переопределяет порт для автозапуска. | string | | ||
| 296 | | `--browserLaunchAvoidLocalhost`| Избегает использования 'localhost' для автозапуска в режиме 'auto'. | boolean | | ||
| 297 | | `--listen` | SillyTavern будет прослушивать все сетевые интерфейсы. | boolean | | ||
| 298 | | `--corsProxy` | Включает CORS-прокси. | boolean | | ||
| 299 | | `--disableCsrf` | Отключает защиту от CSRF. | boolean | | ||
| 300 | | `--ssl` | Включает SSL. | boolean | | ||
| 301 | | `--certPath` | Путь к файлу c сертификатом. | string | | ||
| 302 | | `--keyPath` | Путь к файлу с закрытым ключом. | string | | ||
| 303 | | `--whitelist` | Включает режим белого списка. | boolean | | ||
| 304 | | `--dataRoot` | Корневой каталог для хранения данных. | string | | ||
| 305 | | `--basicAuthMode` | Включает простую аутентификацию. | boolean | | ||
| 306 | | `--requestProxyEnabled` | Разрешает использование прокси для исходящих запросов. | boolean | | ||
| 307 | | `--requestProxyUrl` | URL-адрес прокси (протоколы HTTP или SOCKS). | string | | ||
| 308 | | `--requestProxyBypass` | Bypass список прокси (список хостов, разделенных пробелами). | array | | ||
| 309 | |||
| 310 | ## Удалённое подключение | ||
| 311 | |||
| 312 | В основном этим пользуются тогда, когда хотят использовать SillyTavern с телефона, запустив сервер SillyTavern на стационарном ПК в той же Wi-Fi-сети. | ||
| 313 | |||
| 314 | Однако это позволит подключаться откуда угодно, а не только вам. | ||
| 315 | |||
| 316 | **ВАЖНО: в SillyTavern не предусмотрена возможность использования программы несколькими людьми. Поэтому любой, кто подключится к вашему серверу, получит доступ ко всем вашим персонажам и чатам, а также сможет менять настройки через UI.** | ||
| 317 | |||
| 318 | ### 1. Заведение "белого списка" IP-адресов | ||
| 319 | |||
| 320 | * Создайте в корневой папке SillyTavern файл с названием `whitelist.txt`. | ||
| 321 | * Откройте файл в текстовом редакторе и внесите список IP-адресов, с которых хотите разрешить подключение. | ||
| 322 | |||
| 323 | *Принимаются как обычные IP-адреса, так и целые диапазоны, размеченные с помощью астериска. Примеры:* | ||
| 324 | |||
| 325 | ```txt | ||
| 326 | 192.168.0.1 | ||
| 327 | 192.168.0.20 | ||
| 328 | ``` | ||
| 329 | |||
| 330 | или | ||
| 331 | |||
| 332 | ```txt | ||
| 333 | 192.168.0.* | ||
| 334 | ``` | ||
| 335 | |||
| 336 | (диапазон из примера сверху позволит подключаться всем устройствам в локальной сети) | ||
| 337 | |||
| 338 | Также принимаются маски CIDR (вида 10.0.0.0/24). | ||
| 339 | |||
| 340 | * Сохраните файл `whitelist.txt`. | ||
| 341 | * Перезапустите сервер ST. | ||
| 342 | |||
| 343 | После этого устройства из белого списка смогут подключаться к вашему серверу. | ||
| 344 | |||
| 345 | *Обратите внимание: в файле `config.yaml` также имеется массив `whitelist`, который работает по тому же принципу. Однако если существует файл `whitelist.txt`, то этот массив игнорируется.* | ||
| 346 | |||
| 347 | ### 2. Получение IP хост-машины с ST | ||
| 348 | |||
| 349 | После настройки белого списка адресов, следующим шагом будет получение IP-адреса хост-машины, на которой запущена SillyTavern. | ||
| 350 | |||
| 351 | Если хост-машина находится в той же Wi-Fi-сети, то можно воспользоваться её внутренним Wi-Fi-IP-адресом: | ||
| 352 | |||
| 353 | * На Windows: нажмите Пуск > введите `cmd.exe` в поиске > в консоли введите команду `ipconfig` и нажмите Enter > найдите пункт `IPv4-адрес`. | ||
| 354 | |||
| 355 | Если вы (или кто-то другой) хотите подключаться к хост-машине из другой сети, то вам понадобится ваш публичный IP-адрес. | ||
| 356 | |||
| 357 | * Откройте [эту страницу](https://whatismyipaddress.com/) с вашей хост-машины и найдите пункт `IPv4`. На этот адрес и будет подключаться удалённое устройство. | ||
| 358 | |||
| 359 | ### 3. Соединить удалённое устройство с хост-машиной ST | ||
| 360 | |||
| 361 | Какой бы IP-адрес вы ни выбрали, вам нужно будет вводить его в адресной строке браузера вашего удалённого устройства. | ||
| 362 | |||
| 363 | Обычный адрес хост-машины, находящейся в той же Wi-Fi-сети, выглядит примерно так: | ||
| 364 | |||
| 365 | `http://192.168.0.5:8000` | ||
| 366 | |||
| 367 | НЕ используйте https:// | ||
| 368 | Только http:// | ||
| 369 | |||
| 370 | ### Открытие доступа до ST для всех IP-адресов | ||
| 371 | |||
| 372 | Мы не рекомендуем так делать, но вы можете открыть файл `config.yaml` и изменить `whitelistMode` на `false`. | ||
| 373 | |||
| 374 | Обязательно нужно удалить (или переименовать) файл `whitelist.txt`, если такой файл есть в корневой директории SillyTavern. | ||
| 375 | |||
| 376 | Эта практика считается небезопасной, поэтому, если вы решите так сделать, мы попросим вас установить логин и пароль. | ||
| 377 | |||
| 378 | Оба этих параметра настраиваются в `config.yaml` (username и password). | ||
| 379 | |||
| 380 | Останется только перезапустить сервер ST, и после этого к вам сможет подключиться любой пользователь вне зависимости от IP-адреса его устройства. Главное, чтобы он знал логин и пароль. | ||
| 381 | |||
| 382 | ### Не получается соединиться? | ||
| 383 | |||
| 384 | * Создайте входящее/исходящее правило в вашем фаерволле для порта, указанного в `config.yaml`. НЕ ПУТАЙТЕ этот процесс с пробросом портов на роутере. Если по ошибке перепутаете, то на ваш сервер сможет забраться посторонний человек и украсть ваши логи, этого следует избегать. | ||
| 385 | * Переключите Сетевой профиль на значение "Частные". Для этого зайдите в Параметры > Сеть и Интернет > Ethernet. КРАЙНЕ важно для Windows 11, без этого не получится подключиться даже с правилом фаервола. | ||
| 386 | |||
| 387 | ## Проблемы с производительностью? | ||
| 388 | |||
| 389 | Попробуйте включить опцию "Отключить эффект размытия" в меню "Пользовательские настройки". | ||
| 390 | |||
| 391 | ## Нравится ваш проект! Как помочь? | ||
| 392 | |||
| 393 | ### ЧТО ДЕЛАТЬ | ||
| 394 | |||
| 395 | 1. Присылайте пулл реквесты | ||
| 396 | 2. Присылайте идеи и баг-репорты, оформленные по установленным шаблонам | ||
| 397 | 3. Прежде чем задавать вопросы, прочтите readme и документацию | ||
| 398 | |||
| 399 | ### ЧЕГО НЕ ДЕЛАТЬ | ||
| 400 | |||
| 401 | 1. Предлагать донаты | ||
| 402 | 2. Присылать баг-репорты безо всякого контекста | ||
| 403 | 3. Задавать вопросы, на которые уже отвечали | ||
| 404 | |||
| 405 | ## Где найти старые фоны? | ||
| 406 | 61 | ||
| 407 | Мы двигаемся в сторону 100% уникальности всего используемого контента, поэтому старые фоны были убраны из репозитория. | 62 | <img width="500" alt="image" src="https://github.com/user-attachments/assets/9b5f32f0-c3b3-4102-b3f5-0e9213c0f50f"> |
| 63 | <img width="500" alt="image" src="https://github.com/user-attachments/assets/913fdbaa-7d33-42f1-ae2c-89dca41c53d1"> | ||
| 408 | 64 | ||
| 409 | Они отправлены в архив, скачать их можно здесь: | 65 | ## Установка |
| 410 | 66 | ||
| 411 | <https://files.catbox.moe/1xevnc.zip> | 67 | Для получения подробных инструкций по установке, пожалуйста, посетите нашу документацию: |
| 412 | 68 | ||
| 69 | * **[Руководство по установке для Windows](https://docs.sillytavern.app/installation/windows/)** | ||
| 70 | * **[Руководство по установке для MacOS/Linux](https://docs.sillytavern.app/installation/linuxmacos/)** | ||
| 71 | * **[Руководство по установке для Android (Termux)](https://docs.sillytavern.app/installation/android-(termux)/)** | ||
| 72 | * **[Руководство по установке Docker](https://docs.sillytavern.app/installation/docker/)** | ||
| 413 | 73 | ||
| 74 | ## Лицензия и благодарности | ||
| 414 | 75 | ||
| 76 | **Эта программа распространяется в надежде, что она будет полезна, но БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ; даже без подразумеваемой гарантии ТОВАРНОЙ ПРИГОДНОСТИ или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННОЙ ЦЕЛИ. Смотрите GNU Affero General Public License для получения более подробной информации.** | ||
| 415 | 77 | ||
| 416 | ## Авторы и лицензии | 78 | * [TavernAI](https://github.com/TavernAI/TavernAI) 1.2.8 от Humi: лицензия MIT |
| 79 | * Части мода CncAnon TavernAITurbo используются с разрешения | ||
| 80 | * Режим визуальной новеллы вдохновлен работой PepperTaco (<https://github.com/peppertaco/Tavern/>) | ||
| 81 | * Шрифт Noto Sans от Google (лицензия OFL) | ||
| 82 | * Тема иконок от Font Awesome <https://fontawesome.com> (Иконки: CC BY 4.0, Шрифты: SIL OFL 1.1, Код: лицензия MIT) | ||
| 83 | * Стандартный контент от @OtisAlejandro (персонаж Seraphina и лорбук) и @kallmeflocc (фон в честь 10 тысяч пользователей Discord) | ||
| 84 | * Руководство по Docker от [@mrguymiah](https://github.com/mrguymiah) и [@Bronya-Rand](https://github.com/Bronya-Rand) | ||
| 85 | * Библиотека kokoro-js от [@hexgrad](https://github.com/hexgrad) (лицензия Apache-2.0) | ||
| 417 | 86 | ||
| 418 | **Мы надеемся, что эта программа принесёт людям пользу, | 87 | ## Ведущие контрибьюторы |
| 419 | но мы не даём НИКАКИХ ГАРАНТИЙ; мы ни в коем случае не гарантируем того, | ||
| 420 | что программа СООТВЕТСТВУЕТ КАКИМ-ЛИБО КРИТЕРИЯМ или ПРИГОДНА ДЛЯ КАКОЙ-ЛИБО ЦЕЛИ. | ||
| 421 | Подробнее можно узнать в GNU Affero General Public License.** | ||
| 422 | 88 | ||
| 423 | * Базовая TAI от Humi: Лицензия неизвестна | 89 | [](https://github.com/SillyTavern/SillyTavern/graphs/contributors) |
| 424 | * Модификации от Cohee и производная кодовая база: AGPL v3 | ||
| 425 | * Дополнения RossAscends: AGPL v3 | ||
| 426 | * Кусочки TavernAITurbo мода от CncAnon: Лицензия неизвестна | ||
| 427 | * Различные коммиты и предложения от kingbri (<https://github.com/bdashore3>) | ||
| 428 | * Расширения и внедрение разного рода удобств - city_unit (<https://github.com/city-unit>) | ||
| 429 | * Различные коммиты и баг-репорты от StefanDanielSchwarz (<https://github.com/StefanDanielSchwarz>) | ||
| 430 | * Режим Вайфу вдохновлён работой PepperTaco (<https://github.com/peppertaco/Tavern/>) | ||
| 431 | * Благодарность Pygmalion University за прекрасную работу по тестированию и за все предлагаемые крутые фичи! | ||
| 432 | * Благодарность oobabooga за компиляцию пресетов для TextGen | ||
| 433 | * Пресеты для KoboldAI из KAI Lite: <https://lite.koboldai.net/> | ||
| 434 | * Шрифт Noto Sans от Google (OFL license) | ||
| 435 | * Тема Font Awesome <https://fontawesome.com> (Иконки: CC BY 4.0, Шрифты: SIL OFL 1.1, Код: MIT License) | ||
| 436 | * Клиентская библиотека для AI Horde от ZeldaFan0225: <https://github.com/ZeldaFan0225/ai_horde> | ||
| 437 | * Пусковой скрипт для Linux от AlpinDale | ||
| 438 | * Благодарность paniphons за оформление документа с FAQ | ||
| 439 | * Фон в честь 10 тысяч пользователей в Discord от @kallmeflocc | ||
| 440 | * Стандартный контент (персонажи и лорбуки) предоставлен пользователями @OtisAlejandro, @RossAscends и @kallmeflocc | ||
| 441 | * Корейский перевод от @doloroushyeonse | ||
| 442 | * Поддержка k_euler_a для Horde от <https://github.com/Teashrock> | ||
| 443 | * Китайский перевод от [@XXpE3](https://github.com/XXpE3), 中文 ISSUES 可以联系 @XXpE3 | ||
| 444 | 90 | ||
| 445 | <!-- LINK GROUP --> | 91 | <!-- LINK GROUP --> |
| 446 | [back-to-top]: https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square | ||
| 447 | [cover]: https://github.com/user-attachments/assets/01a6ae9a-16aa-45f2-8bff-32b5dc587e44 | 92 | [cover]: https://github.com/user-attachments/assets/01a6ae9a-16aa-45f2-8bff-32b5dc587e44 |
| 448 | [discord-link]: https://discord.gg/sillytavern | 93 | [discord-link]: https://discord.gg/sillytavern |
| 449 | [discord-shield]: https://img.shields.io/discord/1100685673633153084?color=5865F2&label=discord&labelColor=black&logo=discord&logoColor=white&style=flat-square | ||
| 450 | [discord-shield-badge]: https://img.shields.io/discord/1100685673633153084?color=5865F2&label=discord&labelColor=black&logo=discord&logoColor=white&style=for-the-badge | 94 | [discord-shield-badge]: https://img.shields.io/discord/1100685673633153084?color=5865F2&label=discord&labelColor=black&logo=discord&logoColor=white&style=for-the-badge |
| @@ -34,29 +34,9 @@ SillyTavern 于 2023 年 2 月作为 TavernAI 1.2.8 的一个分支开始,如 | |||
| 34 | 2. 我们不提供任何在线或托管服务,也不会以编程方式跟踪任何用户数据。 | 34 | 2. 我们不提供任何在线或托管服务,也不会以编程方式跟踪任何用户数据。 |
| 35 | 3. SillyTavern 是一个由专注的 LLM 爱好者社区为您带来的充满激情的项目,并且将永远是免费和开源的。 | 35 | 3. SillyTavern 是一个由专注的 LLM 爱好者社区为您带来的充满激情的项目,并且将永远是免费和开源的。 |
| 36 | 36 | ||
| 37 | ## 分支 | 37 | ## 我需要一台性能强大的电脑来运行 SillyTavern 吗? |
| 38 | 38 | ||
| 39 | SillyTavern 采用双分支进行开发,以确保所有用户都能获得流畅的使用体验。 | 39 | 硬件要求很低:任何可以运行 NodeJS 18 或更高版本的设备都可以运行它。如果您打算在本地计算机上进行 LLM 推理,我们建议使用至少具有 6GB VRAM 的 3000 系列 NVIDIA 显卡,但实际要求可能会根据模型和您使用的后端而有所不同。 |
| 40 | |||
| 41 | - `release` -🌟 **推荐给大多数用户。** 这是最稳定、最推荐的分支,只有在重大版本推送时才会更新。适合大多数用户使用。通常每月更新一次。 | ||
| 42 | - `staging` - ⚠️ **不建议随意使用。** 该分支拥有最新功能,但要谨慎,因为它随时可能崩溃。仅适用于高级用户和爱好者。每天更新数次。 | ||
| 43 | |||
| 44 | 如果你不熟悉使用 git 命令行,或者不了解什么是分支,别担心!`release` 分支始终是您的首选。 | ||
| 45 | |||
| 46 | ## 除了 SillyTavern,我还需要什么? | ||
| 47 | |||
| 48 | 由于 SillyTavern 只是一个界面,您需要接入一个 LLM 后端来进行推理。您可以使用 AI Horde 进行开箱即用的聊天。除此之外,我们还支持许多其他本地和基于云的 LLM 后端:OpenAI 兼容 API、KoboldAI、Tabby 等等。您可以在[文档](https://docs.sillytavern.app/usage/api-connections/)中阅读更多关于我们支持的 API 的信息。 | ||
| 49 | |||
| 50 | ### 我需要一台性能强大的电脑来运行 SillyTavern 吗? | ||
| 51 | |||
| 52 | 硬件要求很低:任何可以运行 NodeJS 18 或更高版本的设备都可以运行它。如果您打算在本地计算机上进行 LLM 推理,我们建议使用至少具有 6GB VRAM 的 3000 系列 NVIDIA 显卡。有关更多详细信息,请查看您后端的文档。 | ||
| 53 | |||
| 54 | ### 建议的后端(非附属) | ||
| 55 | |||
| 56 | - [AI Horde](https://aihorde.net/) - 使用志愿者托管的模型。无需进一步设置。 | ||
| 57 | - [KoboldCpp](https://github.com/LostRuins/koboldcpp) - 社区最喜欢的在本地运行 GGUF 模型的工具。 | ||
| 58 | - [tabbyAPI](https://github.com/theroyallab/tabbyAPI) - 一款流行的、轻量级的、本地托管的 exl2 推理 API。 | ||
| 59 | - [OpenRouter](https://openrouter.ai) - 一个适用于许多云提供商(OpenAI、Claude、Meta Llama 等)以及流行社区模型的单一 API。 | ||
| 60 | 40 | ||
| 61 | ## 有问题或建议? | 41 | ## 有问题或建议? |
| 62 | 42 | ||
| @@ -67,9 +47,9 @@ SillyTavern 采用双分支进行开发,以确保所有用户都能获得流 | |||
| 67 | 47 | ||
| 68 | 或者直接与开发人员联系: | 48 | 或者直接与开发人员联系: |
| 69 | 49 | ||
| 70 | - Discord: cohee, rossascends, wolfsblvt | 50 | * Discord: cohee, rossascends, wolfsblvt |
| 71 | - Reddit: [/u/RossAscends](https://www.reddit.com/user/RossAscends/), [/u/sillylossy](https://www.reddit.com/user/sillylossy/), [u/Wolfsblvt](https://www.reddit.com/user/Wolfsblvt/) | 51 | * Reddit: [/u/RossAscends](https://www.reddit.com/user/RossAscends/), [/u/sillylossy](https://www.reddit.com/user/sillylossy/), [u/Wolfsblvt](https://www.reddit.com/user/Wolfsblvt/) |
| 72 | - [提交 GitHub 问题](https://github.com/SillyTavern/SillyTavern/issues) | 52 | * [提交 GitHub 问题](https://github.com/SillyTavern/SillyTavern/issues) |
| 73 | 53 | ||
| 74 | ### 我喜欢你的项目!我该如何贡献自己的力量? | 54 | ### 我喜欢你的项目!我该如何贡献自己的力量? |
| 75 | 55 | ||
| @@ -82,315 +62,33 @@ SillyTavern 采用双分支进行开发,以确保所有用户都能获得流 | |||
| 82 | <img width="500" alt="image" src="https://github.com/user-attachments/assets/9b5f32f0-c3b3-4102-b3f5-0e9213c0f50f"> | 62 | <img width="500" alt="image" src="https://github.com/user-attachments/assets/9b5f32f0-c3b3-4102-b3f5-0e9213c0f50f"> |
| 83 | <img width="500" alt="image" src="https://github.com/user-attachments/assets/913fdbaa-7d33-42f1-ae2c-89dca41c53d1"> | 63 | <img width="500" alt="image" src="https://github.com/user-attachments/assets/913fdbaa-7d33-42f1-ae2c-89dca41c53d1"> |
| 84 | 64 | ||
| 85 | ## 角色卡 | 65 | ## 安装 |
| 86 | |||
| 87 | SillyTavern 围绕“角色卡”的概念构建。角色卡是设定 LLM 行为的 Prompt 集合,是在 SillyTavern 中进行持久对话所必需的。它们的功能类似于 ChatGPT 的 GPTs 或 Poe 的 bots。角色卡的内容可以是任何东西:一个抽象的场景、一个为特定任务量身定制的助手、一个著名人物或一个虚构角色。 | ||
| 88 | |||
| 89 | 要在不选择角色卡的情况下进行快速对话或仅测试 LLM 连接,只需在打开 SillyTavern 后在欢迎屏幕的输入栏中键入您的 Prompt 输入。这将创建一个空的“助手”角色卡,您可以稍后自定义。 | ||
| 90 | |||
| 91 | 要大致了解如何定义角色卡,请参阅默认角色(Seraphina)或从“下载扩展和资源”菜单中下载选定的社区制作卡片。 | ||
| 92 | |||
| 93 | ## 主要功能 | ||
| 94 | |||
| 95 | - 高级文本生成设置,包含许多社区制作的预设 | ||
| 96 | - 世界书支持:创建丰富的传说或节省角色卡上的 Token | ||
| 97 | - 群聊:多机器人房间,供角色与您或彼此交谈 | ||
| 98 | - 丰富的 UI 自定义选项:主题颜色、背景图片、自定义 CSS 等 | ||
| 99 | - 用户角色:让 AI 了解一些关于您的信息,以获得更强的沉浸感 | ||
| 100 | - 内置 RAG 支持:将文档添加到您的聊天中供 AI 参考 | ||
| 101 | - 广泛的聊天命令子系统和自己的[脚本引擎](https://docs.sillytavern.app/usage/st-script/) | ||
| 102 | |||
| 103 | ## 扩展 | ||
| 104 | |||
| 105 | SillyTavern 支持扩展。 | ||
| 106 | |||
| 107 | - 角色情绪表达 | ||
| 108 | - 聊天记录自动摘要 | ||
| 109 | - 自动 UI 和聊天翻译 | ||
| 110 | - Stable Diffusion/FLUX/DALL-E 图像生成 | ||
| 111 | - AI 回复消息的文本转语音(通过 ElevenLabs、Silero 或操作系统的 TTS) | ||
| 112 | - 网络搜索功能,为您的 Prompt 添加额外的现实世界背景信息 | ||
| 113 | - 更多扩展可从“下载扩展和资源”菜单中下载。 | ||
| 114 | |||
| 115 | 有关如何使用它们的使用教程,请参阅[文档](https://docs.sillytavern.app/)。 | ||
| 116 | |||
| 117 | ## ⌛ 安装 | ||
| 118 | |||
| 119 | ### 🪟 Windows | ||
| 120 | |||
| 121 | > \[!WARNING] | ||
| 122 | > | ||
| 123 | > - 请勿安装到任何受 Windows 控制的文件夹(Program Files、System32 等)中。 | ||
| 124 | > - 请勿以管理员权限运行 Start.bat | ||
| 125 | > - 无法在 Windows 7 上安装,因为它无法运行 NodeJS 18.16 | ||
| 126 | |||
| 127 | #### 通过 Git 安装(推荐) | ||
| 128 | |||
| 129 | 1. 安装 [NodeJS](https://nodejs.org/en)(建议使用最新的 LTS 版本) | ||
| 130 | 2. 安装 [Git for Windows](https://gitforwindows.org/) | ||
| 131 | 3. 打开 Windows 资源管理器 (`Win+E`) | ||
| 132 | 4. 浏览或创建一个不受 Windows 控制或监控的文件夹(例如:C:\MySpecialFolder\) | ||
| 133 | 5. 通过点击顶部的“地址栏”,输入 `cmd`,然后按 Enter,在该文件夹内打开命令提示符。 | ||
| 134 | 6. 弹出黑框(命令提示符)后,键入以下其中一项并按 Enter: | ||
| 135 | |||
| 136 | - Release 分支:`git clone https://github.com/SillyTavern/SillyTavern -b release` | ||
| 137 | - Staging 分支: `git clone https://github.com/SillyTavern/SillyTavern -b staging` | ||
| 138 | |||
| 139 | 7. 等待所有内容克隆完成后,双击 `Start.bat` 以使 NodeJS 安装其依赖项。 | ||
| 140 | 8. 然后服务器将启动,SillyTavern 将在您的浏览器中弹出。 | ||
| 141 | |||
| 142 | #### 通过 GitHub Desktop 安装 | ||
| 143 | |||
| 144 | (这**仅**允许在 GitHub Desktop 中使用 git,如果您也想在命令行上使用 `git`,则还需要安装 [Git for Windows](https://gitforwindows.org/)) | ||
| 145 | |||
| 146 | 1. 安装 [NodeJS](https://nodejs.org/en)(建议使用最新的 LTS 版本) | ||
| 147 | 2. 安装 [GitHub Desktop](https://central.github.com/deployments/desktop/desktop/latest/win32) | ||
| 148 | 3. 安装 GitHub Desktop 后,点击 `Clone a repository from the internet....`(注意:此步骤**无需**创建 GitHub 帐户) | ||
| 149 | 4. 在菜单中,点击 URL 选项卡,输入此 URL `https://github.com/SillyTavern/SillyTavern`,然后点击 Clone。您可以更改本地路径以更改 SillyTavern 的下载位置。 | ||
| 150 | 5. 要打开 SillyTavern,请使用 Windows 资源管理器浏览到克隆存储库的文件夹。默认情况下,存储库将克隆到此处:`C:\Users\[您的 Windows 用户名]\Documents\GitHub\SillyTavern` | ||
| 151 | 6. 双击 `start.bat` 文件。(注意:文件名的 `.bat` 部分可能被您的操作系统隐藏,在这种情况下,它将显示为一个名为“`Start`”的文件。双击此文件以运行 SillyTavern) | ||
| 152 | 7. 双击后,应打开一个大的黑色命令控制台窗口,SillyTavern 将开始安装其运行所需的组件。 | ||
| 153 | 8. 安装过程完成后,如果一切正常,命令控制台窗口应如下所示,并且您的浏览器中应打开一个 SillyTavern 选项卡: | ||
| 154 | 9. 连接到任何[支持的 API](https://docs.sillytavern.app/usage/api-connections/) 并开始聊天! | ||
| 155 | |||
| 156 | ### 🐧 Linux & 🍎 MacOS | ||
| 157 | |||
| 158 | 对于 MacOS / Linux,所有这些都将在终端中完成。 | ||
| 159 | |||
| 160 | 1. 安装 git 和 nodeJS(具体方法取决于您的操作系统) | ||
| 161 | 2. 克隆仓库 | ||
| 162 | |||
| 163 | - Release 分支:`git clone https://github.com/SillyTavern/SillyTavern -b release` | ||
| 164 | - Staging 分支: `git clone https://github.com/SillyTavern/SillyTavern -b staging` | ||
| 165 | |||
| 166 | 3. `cd SillyTavern` 导航到安装文件夹。 | ||
| 167 | 4. 使用以下命令之一运行 `start.sh` 脚本: | ||
| 168 | |||
| 169 | - `./start.sh` | ||
| 170 | - `bash start.sh` | ||
| 171 | |||
| 172 | ## 🐋 通过 Docker 安装 | ||
| 173 | |||
| 174 | 这些说明假定您已安装 Docker,能够访问命令行以安装容器,并熟悉其常规操作。 | ||
| 175 | |||
| 176 | ### 使用 GitHub Container Registry | ||
| 177 | |||
| 178 | #### Docker Compose (最简单) | ||
| 179 | |||
| 180 | 从 [GitHub 仓库](https://github.com/SillyTavern/SillyTavern/blob/release/docker/docker-compose.yml) 获取 `docker-compose.yml` 文件,并在文件所在目录中运行以下命令。这将从 GitHub Container Registry 中拉取最新的 release 镜像并启动容器,自动创建必要的卷。 | ||
| 181 | |||
| 182 | ```shell | ||
| 183 | docker-compose up | ||
| 184 | ``` | ||
| 185 | |||
| 186 | 根据您的需求自定义 `docker-compose.yml` 文件。默认端口为 8000。如果您想使用环境变量调整服务器配置,请在此处阅读文档:[链接](https://docs.sillytavern.app/administration/config-yaml/#environment-variables)。 | ||
| 187 | |||
| 188 | #### Docker CLI (高级) | ||
| 189 | |||
| 190 | 您将需要两个强制性的目录映射和一个端口映射才能使 SillyTavern 正常运行。在命令中,替换以下位置中的选项: | ||
| 191 | |||
| 192 | #### 容器变量 | ||
| 193 | |||
| 194 | ##### 卷映射 | ||
| 195 | |||
| 196 | - `CONFIG_PATH` - SillyTavern 配置文件将存储在主机上的目录 | ||
| 197 | - `DATA_PATH` - SillyTavern 用户数据(包括角色)将存储在主机上的目录 | ||
| 198 | - `PLUGINS_PATH` - (可选) SillyTavern 服务器插件将存储在主机上的目录 | ||
| 199 | - `EXTENSIONS_PATH` - (可选) 全局 UI 扩展将存储在主机上的目录 | ||
| 200 | |||
| 201 | ##### 端口映射 | ||
| 202 | |||
| 203 | - `PUBLIC_PORT` - 暴露流量的端口。这是强制性的,因为您将从其虚拟机容器外部访问实例。**在未实现单独的安全服务的情况下,请勿将其暴露给互联网。** | ||
| 204 | 66 | ||
| 205 | ##### 附加设置 | 67 | 有关详细的安装说明,请访问我们的文档: |
| 206 | 68 | ||
| 207 | - `SILLYTAVERN_VERSION` - 在此 GitHub 页面的右侧,您会看到“Packages”。选择“sillytavern”包,您将看到镜像版本。镜像标签“latest”将使您与当前 release 保持同步。您还可以使用指向相应分支的每日镜像的“staging”标签。 | 69 | * **[Windows 安装指南](https://docs.sillytavern.app/installation/windows/)** |
| 208 | 70 | * **[MacOS/Linux 安装指南](https://docs.sillytavern.app/installation/linuxmacos/)** | |
| 209 | #### 运行容器 | 71 | * **[Android (Termux) 安装指南](https://docs.sillytavern.app/installation/android-(termux)/)** |
| 210 | 72 | * **[Docker 安装指南](https://docs.sillytavern.app/installation/docker/)** | |
| 211 | 1. 打开您的命令行 | ||
| 212 | 2. 在您要存储配置和数据文件的文件夹中运行以下命令: | ||
| 213 | |||
| 214 | ```bash | ||
| 215 | SILLYTAVERN_VERSION="latest" | ||
| 216 | PUBLIC_PORT="8000" | ||
| 217 | CONFIG_PATH="./config" | ||
| 218 | DATA_PATH="./data" | ||
| 219 | PLUGINS_PATH="./plugins" | ||
| 220 | EXTENSIONS_PATH="./extensions" | ||
| 221 | |||
| 222 | docker run \ | ||
| 223 | --name="sillytavern" \ | ||
| 224 | -p "$PUBLIC_PORT:8000/tcp" \ | ||
| 225 | -v "$CONFIG_PATH:/home/node/app/config:rw" \ | ||
| 226 | -v "$DATA_PATH:/home/node/app/data:rw" \ | ||
| 227 | -v "$EXTENSIONS_PATH:/home/node/app/public/scripts/extensions/third-party:rw" \ | ||
| 228 | -v "$PLUGINS_PATH:/home/node/app/plugins:rw" \ | ||
| 229 | ghcr.io/sillytavern/sillytavern:"$SILLYTAVERN_VERSION" | ||
| 230 | ``` | ||
| 231 | |||
| 232 | > 默认情况下,容器将在前台运行。如果要在后台运行它,请将 `-d` 标志添加到 `docker run` 命令中。 | ||
| 233 | |||
| 234 | ### 自己构建镜像 | ||
| 235 | |||
| 236 | 我们有一个关于在 Docker 中使用 SillyTavern 的综合指南[在此处](http://docs.sillytavern.app/installation/docker/),涵盖了 Windows、macOS 和 Linux 上的安装!如果您希望自己构建镜像,请阅读它。 | ||
| 237 | |||
| 238 | ## ⚡ 通过 SillyTavern Launcher 安装 | ||
| 239 | |||
| 240 | SillyTavern Launcher 是一个安装向导,可帮助您进行多种选项的设置,包括为本地推理安装后端。 | ||
| 241 | |||
| 242 | ### 对于 Windows 用户 | ||
| 243 | |||
| 244 | 1. 在键盘上:按 **`WINDOWS + R`** 打开“运行”对话框。然后,运行以下命令安装 git: | ||
| 245 | |||
| 246 | ```shell | ||
| 247 | cmd /c winget install -e --id Git.Git | ||
| 248 | ``` | ||
| 249 | |||
| 250 | 2. 在键盘上:按 **`WINDOWS + E`** 打开文件资源管理器,然后导航到要安装启动器的文件夹。进入所需文件夹后,在地址栏中键入 `cmd` 并按 Enter。然后,运行以下命令: | ||
| 251 | |||
| 252 | ```shell | ||
| 253 | git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher && start installer.bat | ||
| 254 | ``` | ||
| 255 | |||
| 256 | ### 对于 Linux 用户 | ||
| 257 | |||
| 258 | 1. 打开您喜欢的终端并安装 git | ||
| 259 | 2. 使用以下命令克隆 SillyTavern-Launcher: | ||
| 260 | |||
| 261 | ```shell | ||
| 262 | git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher | ||
| 263 | ``` | ||
| 264 | |||
| 265 | 3. 使用以下命令启动 installer.sh: | ||
| 266 | |||
| 267 | ```shell | ||
| 268 | chmod +x install.sh && ./install.sh | ||
| 269 | ``` | ||
| 270 | |||
| 271 | 4. 安装后使用以下命令启动 launcher.sh: | ||
| 272 | |||
| 273 | ```shell | ||
| 274 | chmod +x launcher.sh && ./launcher.sh | ||
| 275 | ``` | ||
| 276 | |||
| 277 | ### 对于 Mac 用户 | ||
| 278 | |||
| 279 | 1. 打开终端并使用以下命令安装 brew: | ||
| 280 | |||
| 281 | ```shell | ||
| 282 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" | ||
| 283 | ``` | ||
| 284 | |||
| 285 | 2. 使用以下命令安装 git: | ||
| 286 | |||
| 287 | ```shell | ||
| 288 | brew install git | ||
| 289 | ``` | ||
| 290 | |||
| 291 | 3. 使用以下命令克隆 SillyTavern-Launcher: | ||
| 292 | |||
| 293 | ```shell | ||
| 294 | git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher | ||
| 295 | ``` | ||
| 296 | |||
| 297 | 4. 使用以下命令启动 installer.sh: | ||
| 298 | |||
| 299 | ```shell | ||
| 300 | chmod +x install.sh && ./install.sh | ||
| 301 | ``` | ||
| 302 | |||
| 303 | 5. 安装后使用以下命令启动 launcher.sh: | ||
| 304 | |||
| 305 | ```shell | ||
| 306 | chmod +x launcher.sh && ./launcher.sh | ||
| 307 | ``` | ||
| 308 | |||
| 309 | ## 📱 通过 Termux 在 Android OS 上安装 | ||
| 310 | |||
| 311 | > \[!NOTE] | ||
| 312 | > **SillyTavern 可以在 Android 设备上使用 Termux 原生运行,但我们不为此用例提供官方支持。** | ||
| 313 | > | ||
| 314 | > **请参阅 ArroganceComplex#2659 编写的本指南:** | ||
| 315 | > | ||
| 316 | > - <https://rentry.org/STAI-Termux> | ||
| 317 | |||
| 318 | **不支持的平台:android arm LEtime-web。** 32 位 Android 需要一个无法通过 npm 安装的外部依赖项。使用以下命令安装它:`pkg install esbuild`。然后运行常规安装步骤。 | ||
| 319 | |||
| 320 | ## 命令行参数 | ||
| 321 | |||
| 322 | 您可以将命令行参数传递给 SillyTavern 服务器启动脚本,以覆盖 `config.yaml` 中的某些设置。 | ||
| 323 | |||
| 324 | ### 示例 | ||
| 325 | |||
| 326 | ```shell | ||
| 327 | node server.js --port 8000 --listen false | ||
| 328 | # 或 | ||
| 329 | npm run start -- --port 8000 --listen false | ||
| 330 | # 或 (仅限 Windows) | ||
| 331 | Start.bat --port 8000 --listen false | ||
| 332 | ``` | ||
| 333 | |||
| 334 | ### 支持的参数 | ||
| 335 | |||
| 336 | > \[!TIP] | ||
| 337 | > 所有参数都不是必需的。如果您不提供它们,SillyTavern 将使用 `config.yaml` 中的设置。 | ||
| 338 | |||
| 339 | | 选项 | 描述 | 类型 | | ||
| 340 | | -------------------------------- | ---------------------------------------------- | ------- | | ||
| 341 | | `--version` | 显示版本号 | boolean | | ||
| 342 | | `--configPath` | 覆盖 config.yaml 文件的路径 | string | | ||
| 343 | | `--dataRoot` | 数据存储的根目录 | string | | ||
| 344 | | `--port` | 设置 SillyTavern 将在其下运行的端口 | number | | ||
| 345 | | `--listen` | SillyTavern 将侦听所有网络接口 | boolean | | ||
| 346 | | `--whitelist` | 启用白名单模式 | boolean | | ||
| 347 | | `--basicAuthMode` | 启用基本身份验证 | boolean | | ||
| 348 | | `--enableIPv4` | 启用 IPv4 协议 | boolean | | ||
| 349 | | `--enableIPv6` | 启用 IPv6 协议 | boolean | | ||
| 350 | | `--listenAddressIPv4` | 要侦听的特定 IPv4 地址 | string | | ||
| 351 | | `--listenAddressIPv6` | 要侦听的特定 IPv6 地址 | string | | ||
| 352 | | `--dnsPreferIPv6` | DNS 首选 IPv6 | boolean | | ||
| 353 | | `--ssl` | 启用 SSL | boolean | | ||
| 354 | | `--certPath` | 您的证书文件路径 | string | | ||
| 355 | | `--keyPath` | 您的私钥文件路径 | string | | ||
| 356 | | `--browserLaunchEnabled` | 自动在浏览器中启动 SillyTavern | boolean | | ||
| 357 | | `--browserLaunchHostname` | 自动运行主机名 | string | | ||
| 358 | | `--browserLaunchPort` | 覆盖自动运行的端口 | string | | ||
| 359 | | `--browserLaunchAvoidLocalhost` | 在自动模式下避免使用 'localhost' 进行自动运行 | boolean | | ||
| 360 | | `--corsProxy` | 启用 CORS 代理 | boolean | | ||
| 361 | | `--requestProxyEnabled` | 为传出请求启用代理 | boolean | | ||
| 362 | | `--requestProxyUrl` | 请求代理 URL(HTTP 或 SOCKS 协议) | string | | ||
| 363 | | `--requestProxyBypass` | 请求代理绕过列表(以空格分隔的主机列表) | array | | ||
| 364 | | `--disableCsrf` | 禁用 CSRF 保护(不推荐) | boolean | | ||
| 365 | |||
| 366 | ## 远程连接 | ||
| 367 | |||
| 368 | 这通常适用于那些想在手机上使用 SillyTavern,而他们的电脑在同一 Wi-Fi 网络上运行 ST 服务器的人。但是,它也可以用于允许从任何地方进行远程连接。 | ||
| 369 | |||
| 370 | 请在[文档](https://docs.sillytavern.app/usage/remoteconnections/)中阅读有关如何设置远程连接的详细指南。 | ||
| 371 | |||
| 372 | 您可能还需要配置 SillyTavern 用户配置文件(可选密码保护):[用户](https://docs.sillytavern.app/administration/multi-user/)。 | ||
| 373 | 73 | ||
| 374 | ## 许可证和致谢 | 74 | ## 许可证和致谢 |
| 375 | 75 | ||
| 376 | **本程序的分发是希望它能有用,但不提供任何保证;甚至没有对适销性或特定用途适用性的默示保证。有关更多详细信息,请参阅 GNU Affero 通用公共许可证。** | 76 | **本程序的分发是希望它能有用,但不提供任何保证;甚至没有对适销性或特定用途适用性的默示保证。有关更多详细信息,请参阅 GNU Affero 通用公共许可证。** |
| 377 | 77 | ||
| 378 | - [TavernAI](https://github.com/TavernAI/TavernAI) 1.2.8 by Humi: MIT 许可证 | 78 | * [TavernAI](https://github.com/TavernAI/TavernAI) 1.2.8 by Humi: MIT 许可证 |
| 379 | - CncAnon 的 TavernAITurbo mod 的部分内容经许可使用 | 79 | * CncAnon 的 TavernAITurbo mod 的部分内容经许可使用 |
| 380 | - 视觉小说模式的灵感来自 PepperTaco 的工作 (<https://github.com/peppertaco/Tavern/>) | 80 | * 视觉小说模式的灵感来自 PepperTaco 的工作 (<https://github.com/peppertaco/Tavern/>) |
| 381 | - Noto Sans 字体 by Google (OFL 许可证) | 81 | * Noto Sans 字体 by Google (OFL 许可证) |
| 382 | - 图标主题 by Font Awesome <https://fontawesome.com> (图标: CC BY 4.0, 字体: SIL OFL 1.1, 代码: MIT 许可证) | 82 | * 图标主题 by Font Awesome <https://fontawesome.com> (图标: CC BY 4.0, 字体: SIL OFL 1.1, 代码: MIT 许可证) |
| 383 | - 默认内容由 @OtisAlejandro (Seraphina 角色和世界书) 和 @kallmeflocc (10K Discord 用户庆祝背景) 提供 | 83 | * 默认内容由 @OtisAlejandro (Seraphina 角色和世界书) 和 @kallmeflocc (10K Discord 用户庆祝背景) 提供 |
| 384 | - Docker 指南由 [@mrguymiah](https://github.com/mrguymiah) 和 [@Bronya-Rand](https://github.com/Bronya-Rand) 提供 | 84 | * Docker 指南由 [@mrguymiah](https://github.com/mrguymiah) 和 [@Bronya-Rand](https://github.com/Bronya-Rand) 提供 |
| 385 | - kokoro-js 库由 [@hexgrad](https://github.com/hexgrad) 提供 (Apache-2.0 许可证) | 85 | * kokoro-js 库由 [@hexgrad](https://github.com/hexgrad) 提供 (Apache-2.0 许可证) |
| 386 | - 中文翻译由 [@XXpE3](https://github.com/XXpE3) 完成,中文 ISSUES 可以联系 @XXpE3 | ||
| 387 | 86 | ||
| 388 | ## 主要贡献者 | 87 | ## 主要贡献者 |
| 389 | 88 | ||
| 390 | [](https://github.com/SillyTavern/SillyTavern/graphs/contributors) | 89 | [](https://github.com/SillyTavern/SillyTavern/graphs/contributors) |
| 391 | 90 | ||
| 392 | <!-- LINK GROUP --> | 91 | <!-- LINK GROUP --> |
| 393 | |||
| 394 | [cover]: https://github.com/user-attachments/assets/01a6ae9a-16aa-45f2-8bff-32b5dc587e44 | 92 | [cover]: https://github.com/user-attachments/assets/01a6ae9a-16aa-45f2-8bff-32b5dc587e44 |
| 395 | [discord-link]: https://discord.gg/sillytavern | 93 | [discord-link]: https://discord.gg/sillytavern |
| 396 | [discord-shield-badge]: https://img.shields.io/discord/1100685673633153084?color=5865F2&label=discord&labelColor=black&logo=discord&logoColor=white&style=for-the-badge | 94 | [discord-shield-badge]: https://img.shields.io/discord/1100685673633153084?color=5865F2&label=discord&labelColor=black&logo=discord&logoColor=white&style=for-the-badge |
| @@ -10,7 +10,7 @@ | |||
| 10 | [English](readme.md) | [German](readme-de_de.md) | [中文](readme-zh_cn.md) | 繁體中文 | [日本語](readme-ja_jp.md) | [Русский](readme-ru_ru.md) | [한국어](readme-ko_kr.md) | 10 | [English](readme.md) | [German](readme-de_de.md) | [中文](readme-zh_cn.md) | 繁體中文 | [日本語](readme-ja_jp.md) | [Русский](readme-ru_ru.md) | [한국어](readme-ko_kr.md) |
| 11 | 11 | ||
| 12 | [](https://github.com/SillyTavern/SillyTavern/stargazers) | 12 | [](https://github.com/SillyTavern/SillyTavern/stargazers) |
| 13 | [](https://github.com/SillyTavern/SillyTavern/network) | 13 | [](https://github.com/SillyTavern/SillyTavern/forks) |
| 14 | [](https://github.com/SillyTavern/SillyTavern/issues) | 14 | [](https://github.com/SillyTavern/SillyTavern/issues) |
| 15 | [](https://github.com/SillyTavern/SillyTavern/pulls) | 15 | [](https://github.com/SillyTavern/SillyTavern/pulls) |
| 16 | 16 | ||
| @@ -26,7 +26,7 @@ SillyTavern 提供一個統一的前端介面,整合多種大型語言模型 | |||
| 26 | 26 | ||
| 27 | SillyTavern(簡稱 ST)是一款本地安裝的使用者介面,讓您能與大型語言模型(LLM)、影像生成引擎以及語音合成模型互動的前端。 | 27 | SillyTavern(簡稱 ST)是一款本地安裝的使用者介面,讓您能與大型語言模型(LLM)、影像生成引擎以及語音合成模型互動的前端。 |
| 28 | 28 | ||
| 29 | SillyTavern 起源於 2023 年 2 月,作為 TavernAI 1.2.8 的分支版本發展至今。目前已有超過 100 位貢獻者,並擁有超過兩年的獨立開發歷史。如今,它已成為 AI 愛好者中備受推崇的軟體之一。 | 29 | SillyTavern 起源於 2023 年 2 月,作為 TavernAI 1.2.8 的分支版本發展至今。目前已有超過 200 位貢獻者,並擁有超過兩年的獨立開發歷史。如今,它已成為 AI 愛好者中備受推崇的軟體之一。 |
| 30 | 30 | ||
| 31 | ## 我們的願景 | 31 | ## 我們的願景 |
| 32 | 32 | ||
| @@ -34,29 +34,9 @@ SillyTavern 起源於 2023 年 2 月,作為 TavernAI 1.2.8 的分支版本發 | |||
| 34 | 2. 我們不提供任何線上或託管服務,也不會程式化追蹤任何使用者數據。 | 34 | 2. 我們不提供任何線上或託管服務,也不會程式化追蹤任何使用者數據。 |
| 35 | 3. SillyTavern 是由一群熱衷於 LLM 的開發者社群所打造的專案,並將永遠保持免費與開源。 | 35 | 3. SillyTavern 是由一群熱衷於 LLM 的開發者社群所打造的專案,並將永遠保持免費與開源。 |
| 36 | 36 | ||
| 37 | ## 分支介紹 | 37 | ## 我需要高效能電腦才能運行 SillyTavern 嗎? |
| 38 | 38 | ||
| 39 | SillyTavern 採用雙分支開發模式,確保為所有使用者提供流暢的體驗。 | 39 | SillyTavern 的硬體需求相當低。任何能夠運行 NodeJS 18 或更高版本的設備都可以執行。若您打算在本地機器上進行 LLM 推理,我們建議使用擁有至少 6GB VRAM 的 3000 系列 NVIDIA 顯示卡,但實際需求可能因模型和您使用的後端而異。 |
| 40 | |||
| 41 | * `release`(穩定版):🌟 **推薦給大部分的使用者使用。** 此分支最為穩定,僅在主要版本發布時更新。適合大多數人,通常每月更新一次。 | ||
| 42 | * `staging`(開發版):⚠️ **不建議普通使用者使用。** 此分支包含最新功能,但可能隨時出現問題。適合進階使用者與愛好者,每日多次更新。 | ||
| 43 | |||
| 44 | 如果您不熟悉 git CLI 或對分支概念不清楚,請放心,對您來說,`release`(穩定版)分支永遠是首選。 | ||
| 45 | |||
| 46 | ## 使用 SillyTavern 需要什麼? | ||
| 47 | |||
| 48 | 由於 SillyTavern 僅是一個介面,您需要一個 LLM 後端來提供推理能力。您可以使用 AI Horde 以立即開始聊天。此外,我們支持許多其他本地和雲端 LLM 後端,例如 OpenAI 兼容 API、KoboldAI、Tabby 等。更多支持的 API 資訊,請參閱 [常見問題](https://docs.sillytavern.app/usage/api-connections/)。 | ||
| 49 | |||
| 50 | ### 我需要高效能電腦才能運行 SillyTavern 嗎? | ||
| 51 | |||
| 52 | SillyTavern 的硬體需求相當低。任何能夠運行 NodeJS 18 或更高版本的設備都可以執行。若您打算在本地機器上進行 LLM 推理,我們建議使用擁有至少 6GB VRAM 的 3000 系列 NVIDIA 顯示卡。更多詳細資訊,請參考您使用的後端文檔。 | ||
| 53 | |||
| 54 | ### 推薦後端(僅為推薦,非官方合作和隸屬關係) | ||
| 55 | |||
| 56 | * [AI Horde](https://aihorde.net/):使用志願者託管的模型,無需進一步設定 | ||
| 57 | * [KoboldCpp](https://github.com/LostRuins/koboldcpp):社群推崇的選擇,可在本地運行 GGUF 模型 | ||
| 58 | * [tabbyAPI](https://github.com/theroyallab/tabbyAPI):一個流行且輕量的本地託管 exl2 推理 API | ||
| 59 | * [OpenRouter](https://openrouter.ai):提供多個雲端 LLM 提供商(如 OpenAI、Claude、Meta Llama 等)及熱門社群模型的單一 API | ||
| 60 | 40 | ||
| 61 | ## 有任何問題或建議? | 41 | ## 有任何問題或建議? |
| 62 | 42 | ||
| @@ -82,287 +62,19 @@ SillyTavern 的硬體需求相當低。任何能夠運行 NodeJS 18 或更高版 | |||
| 82 | <img width="500" alt="image" src="https://github.com/user-attachments/assets/9b5f32f0-c3b3-4102-b3f5-0e9213c0f50f"> | 62 | <img width="500" alt="image" src="https://github.com/user-attachments/assets/9b5f32f0-c3b3-4102-b3f5-0e9213c0f50f"> |
| 83 | <img width="500" alt="image" src="https://github.com/user-attachments/assets/913fdbaa-7d33-42f1-ae2c-89dca41c53d1"> | 63 | <img width="500" alt="image" src="https://github.com/user-attachments/assets/913fdbaa-7d33-42f1-ae2c-89dca41c53d1"> |
| 84 | 64 | ||
| 85 | ## 角色卡 | 65 | ## 安裝指南 |
| 86 | |||
| 87 | SillyTavern 的核心概念是「角色卡」(Character Cards)。角色卡是一組設定 LLM 行為的提示詞,用於 SillyTavern 中進行持續性對話。功能類似於 ChatGPT 的 GPT 或 Poe 的聊天機器人。角色卡的內容可以是任何形式:抽象場景、針對特定任務設計的助手、知名人物,或者虛構角色。 | ||
| 88 | |||
| 89 | 角色卡中唯一必填的項目是名稱欄位。若想與語言模型開始一般對話,您只需創建一個名稱為「Assistant」的新卡片,其餘欄位皆可保持空白。若希望進行更具主題性的對話,則可以提供語言模型背景資訊、行為模式、寫作風格以及特定情境來啟動聊天。 | ||
| 90 | |||
| 91 | 如果僅想進行快速對話而不選擇角色卡片,或想測試 LLM 的連線,則可在開啟 SillyTavern 後,於歡迎頁面的輸入欄位中直接輸入您的提示內容。請注意,這類對話是暫時的,不會被永久保存。 | ||
| 92 | |||
| 93 | 若想了解如何設定角色卡,可參考預設角色(如 Seraphina)或從「下載擴充功能 & 資源」(Download Extensions & Assets)選單中下載社群製作的角色卡。 | ||
| 94 | |||
| 95 | ## 核心功能 | ||
| 96 | |||
| 97 | * 進階文本生成設定:內含許多社群製作的預設設定 | ||
| 98 | * 支援世界資訊(World Info):創建豐富的背景故事,或節省角色卡中的 Token(符元)使用 | ||
| 99 | * 群組聊天:多角色聊天室,可讓角色與您或彼此對話 | ||
| 100 | * 豐富的 UI 自定義選項:主題顏色、背景圖片、自定義 CSS 等 | ||
| 101 | * 使用者設定:讓 AI 更了解您並提升沉浸感 | ||
| 102 | * 內建 RAG 支持:可將文檔加入對話,供 AI 參考 | ||
| 103 | * 強大的聊天指令子系統:內含 [腳本引擎(Scripting Engine)](https://docs.sillytavern.app/usage/st-script/) | ||
| 104 | |||
| 105 | ## 擴充功能 | ||
| 106 | |||
| 107 | SillyTavern 支持多種擴充功能。 | ||
| 108 | |||
| 109 | * 角色情感表達:使用視覺圖片(立繪)呈現情緒表達 | ||
| 110 | * 聊天記錄自動摘要 | ||
| 111 | * 自動化介面與聊天翻譯 | ||
| 112 | * 穩定擴散(Stable Diffusion)、FLUX 和 DALL-E 的影像生成整合 | ||
| 113 | * 語音合成:AI 回應的訊息可使用 ElevenLabs、Silero 或系統 TTS 語音合成 | ||
| 114 | * 網頁搜尋功能:為提示詞添加真實世界的上下文資訊 | ||
| 115 | * 更多擴展:可從「下載擴充功能 & 資源」(Download Extensions & Assets)選單中下載 | ||
| 116 | |||
| 117 | 想了解如何使用這些擴充功能,請參考:[官方說明文件](https://docs.sillytavern.app/) | ||
| 118 | |||
| 119 | # ⌛ 安裝指南 | ||
| 120 | |||
| 121 | > \[!WARNING] | ||
| 122 | > | ||
| 123 | > * 請勿將程式安裝到 Windows 的系統控制資料夾(如 Program Files、System32 等) | ||
| 124 | > * 請勿以管理員權限執行 Start.bat | ||
| 125 | > * 無法在 Windows 7 系統上安裝,因為它無法執行 NodeJS 18.16 | ||
| 126 | |||
| 127 | ## 🪟 Windows | ||
| 128 | |||
| 129 | ### 使用 Git 安裝 | ||
| 130 | |||
| 131 | 1. 安裝 [NodeJS](https://nodejs.org/en)(建議使用最新的 LTS 版本) | ||
| 132 | 2. 安裝 [Git for Windows](https://gitforwindows.org/) | ||
| 133 | 3. 打開 Windows 檔案總管(`Win+E`) | ||
| 134 | 4. 創建/使用一個不受 Windows 系統控制或監控的資料夾(例如:C:\MySpecialFolder\) | ||
| 135 | 5. 在該資料夾內開啟命令提示字元(Command Prompt):點擊地址欄,輸入 `cmd` 並按下 Enter | ||
| 136 | 6. 當命令提示字元黑框彈出時,輸入以下其中一條指令後,按下 Enter: | ||
| 137 | |||
| 138 | * 安裝 Release(穩定版)分支:`git clone https://github.com/SillyTavern/SillyTavern -b release` | ||
| 139 | * 安裝 Staging(開發板)分支:`git clone https://github.com/SillyTavern/SillyTavern -b staging` | ||
| 140 | |||
| 141 | 7. 當程式碼下載完成後,雙擊 `Start.bat`,NodeJS 將自動安裝所需的依賴項 | ||
| 142 | 8. 本地伺服器啟動後,SillyTavern 將自動在您的瀏覽器中打開 | ||
| 143 | |||
| 144 | ### 使用 GitHub Desktop 安裝 | ||
| 145 | |||
| 146 | (此方式僅允許通過 GitHub Desktop 使用 git。如果您也希望在命令列中使用 `git`,則需額外安裝 [Git for Windows](https://gitforwindows.org/)) | ||
| 147 | |||
| 148 | 1. 安裝 [NodeJS](https://nodejs.org/en)(建議使用最新的 LTS 版本) | ||
| 149 | 2. 安裝 [GitHub Desktop](https://central.github.com/deployments/desktop/desktop/latest/win32) | ||
| 150 | 3. 安裝完成後,打開 GitHub Desktop,點擊 `Clone a repository from the internet....` (注意:此步驟 **無需創建 GitHub 帳號**。) | ||
| 151 | 4. 在彈出選單中,點擊「URL」選項,輸入此網址:`https://github.com/SillyTavern/SillyTavern`,然後點擊「Clone」。您可以更改「Local path」來選擇 SillyTavern 的下載位置 | ||
| 152 | 6. 若想開啟 SillyTavern,需使用 Windows 檔案總管以進入您複製儲存庫的資料夾。預設位置為:`C:\Users\[您的 Windows 使用者名稱]\Documents\GitHub\SillyTavern` | ||
| 153 | 7. 雙擊 `start.bat` 文件。(請注意:若您的作業系統隱藏了 `.bat` 副檔名,該文件可能顯示為「`Start`」。這就是您需要雙擊運行的文件。) | ||
| 154 | 8. 雙擊後,將會彈出一個大型黑色的命令提示字元視窗,SillyTavern 會開始安裝其運行所需的文件與依賴 | ||
| 155 | 9. 安裝完成後,若一切正常,命令提示字元視窗應顯示運行中的訊息,且您的瀏覽器會自動打開 SillyTavern 頁籤 | ||
| 156 | 10. 連接到任何 SillyTavern [支援的 APIs](https://docs.sillytavern.app/usage/api-connections/) 並開始聊天吧! | ||
| 157 | |||
| 158 | ## 🐧 Linux & 🍎 MacOS | ||
| 159 | |||
| 160 | 對於 MacOS 和 Linux 系統,所有操作都將在終端機(Terminal)中完成。 | ||
| 161 | |||
| 162 | 1. 安裝 git 和 NodeJS(具體方法因操作系統而異) | ||
| 163 | 2. 複製儲存庫(Clone the repo): | ||
| 164 | |||
| 165 | * 安裝 Release(穩定版)分支:`git clone https://github.com/SillyTavern/SillyTavern -b release` | ||
| 166 | * 安裝 Staging(開發板)分支:`git clone https://github.com/SillyTavern/SillyTavern -b staging` | ||
| 167 | |||
| 168 | 3. 使用命令 `cd SillyTavern` 以進入安裝資料夾 | ||
| 169 | 4. 使用以下其中一條命令,以執行 `start.sh` 腳本: | ||
| 170 | |||
| 171 | * `./start.sh` | ||
| 172 | * `bash start.sh` | ||
| 173 | |||
| 174 | ## ⚡ 使用 SillyTavern Launcher 安裝 | ||
| 175 | |||
| 176 | SillyTavern Launcher 是一個安裝嚮導,協助您設定多種選項,包括安裝本地推理(inference)的後端。 | ||
| 177 | |||
| 178 | ### 對於 Windows 使用者 | ||
| 179 | |||
| 180 | 1. 在鍵盤上按下 **`WINDOWS + R`** 打開「執行」對話框,然後輸入以下指令以安裝 git: | ||
| 181 | |||
| 182 | ```shell | ||
| 183 | cmd /c winget install -e --id Git.Git | ||
| 184 | ``` | ||
| 185 | |||
| 186 | 2. 在鍵盤上按下 **`WINDOWS + E`** 打開檔案總管,導航至您想要安裝 Launcher 的資料夾。在目標資料夾的地址欄輸入 `cmd` 並按下 Enter。接著執行以下命令: | ||
| 187 | |||
| 188 | ```shell | ||
| 189 | git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher && start installer.bat | ||
| 190 | ``` | ||
| 191 | |||
| 192 | ### 對於 Linux 使用者 | ||
| 193 | |||
| 194 | 1. 打開您喜歡的終端機(Terminal),安裝 git | ||
| 195 | 2. 使用以下指令以複製 Sillytavern-Launcher: | ||
| 196 | |||
| 197 | ```shell | ||
| 198 | git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher | ||
| 199 | ``` | ||
| 200 | |||
| 201 | 3. 執行安裝腳本(installer.sh): | ||
| 202 | |||
| 203 | ```shell | ||
| 204 | chmod +x install.sh && ./install.sh | ||
| 205 | ``` | ||
| 206 | |||
| 207 | 4. 安裝完成後,執行啟動腳本(launcher.sh): | ||
| 208 | |||
| 209 | ```shell | ||
| 210 | chmod +x launcher.sh && ./launcher.sh | ||
| 211 | ``` | ||
| 212 | 66 | ||
| 213 | ### 對於 Mac 使用者 | 67 | 有關詳細的安裝說明,請訪問我們的文檔: |
| 214 | 68 | ||
| 215 | 1. 打開終端機(Terminal),並使用以下指令安裝 Homebrew: | 69 | * **[Windows 安裝指南](https://docs.sillytavern.app/installation/windows/)** |
| 216 | 70 | * **[MacOS/Linux 安裝指南](https://docs.sillytavern.app/installation/linuxmacos/)** | |
| 217 | ```shell | 71 | * **[Android (Termux) 安裝指南](https://docs.sillytavern.app/installation/android-(termux)/)** |
| 218 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" | 72 | * **[Docker 安裝指南](https://docs.sillytavern.app/installation/docker/)** |
| 219 | ``` | ||
| 220 | |||
| 221 | 2. 使用 Homebrew 以安裝 git: | ||
| 222 | |||
| 223 | ```shell | ||
| 224 | brew install git | ||
| 225 | ``` | ||
| 226 | |||
| 227 | 3. 使用以下指令以複製 Sillytavern-Launcher: | ||
| 228 | |||
| 229 | ```shell | ||
| 230 | git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher | ||
| 231 | ``` | ||
| 232 | |||
| 233 | 4. 執行安裝腳本(installer.sh): | ||
| 234 | |||
| 235 | ```shell | ||
| 236 | chmod +x install.sh && ./install.sh | ||
| 237 | ``` | ||
| 238 | |||
| 239 | 5. 安裝完成後,執行啟動腳本(launcher.sh): | ||
| 240 | |||
| 241 | ```shell | ||
| 242 | chmod +x launcher.sh && ./launcher.sh | ||
| 243 | ``` | ||
| 244 | |||
| 245 | ## 🐋 使用 Docker 安裝 | ||
| 246 | |||
| 247 | 以下指南已假設您安裝 Docker,能夠訪問命令列進行容器安裝,並熟悉 Docker 的基本使用。 | ||
| 248 | |||
| 249 | ### 自行構建映像 | ||
| 250 | |||
| 251 | 我們提供了一份完整的 [SillyTavern Docker 使用指南](http://docs.sillytavern.app/installation/docker/)。該指南涵蓋了 Windows、macOS 和 Linux 的安裝過程。若您希望自行構建映像,建議先閱讀該文檔。 | ||
| 252 | |||
| 253 | ### 使用 GitHub 容器註冊表(最簡易的方式) | ||
| 254 | |||
| 255 | 您需要設定兩個必要的目錄映射(directory mappings)和一個端口映射(port mapping)來使 SillyTavern 正常運行。在執行指令時,請將以下佔位符替換為您的實際配置: | ||
| 256 | |||
| 257 | #### 容器變數 | ||
| 258 | |||
| 259 | ##### 目錄映射(Volume Mappings) | ||
| 260 | |||
| 261 | * [config]:用於存放 SillyTavern 設定文件的本地資料夾 | ||
| 262 | * [data]:用於存放 SillyTavern 使用者數據(包括角色)的本地資料夾 | ||
| 263 | * [plugins](可選):用於存放 SillyTavern 擴充功能的本地資料夾 | ||
| 264 | |||
| 265 | ##### 端口映射(Port Mappings) | ||
| 266 | |||
| 267 | * [PublicPort]:對外流量的訪問端口。這是必需的,因為您將從虛擬機容器外部訪問實例。除非實施了額外的安全服務,否則請勿將此端口暴露於網路 | ||
| 268 | |||
| 269 | ##### 其他設定(Additional Settings) | ||
| 270 | |||
| 271 | * [DockerNet]:容器應連接的 Docker 網路。如果您不熟悉此概念,請參閱 [Docker 官方說明文件](https://docs.docker.com/reference/cli/docker/network/) | ||
| 272 | * [version]:在 GitHub 頁面的右側,您可以找到「Packages」。選擇「sillytavern」包,然後查看映像版本。「latest」標籤會使您保持與當前版本同步。您也可以選擇「staging」或「release」標籤,但這可能不適用於依賴擴充功能的使用者,因為擴充功能可能需要時間進行更新 | ||
| 273 | |||
| 274 | #### 安裝命令 | ||
| 275 | |||
| 276 | 1. 打開命令列(Command Line) | ||
| 277 | 2. 執行以下指令: | ||
| 278 | |||
| 279 | `docker create --name='sillytavern' --net='[DockerNet]' -p '8000:8000/tcp' -v '[plugins]':'/home/node/app/plugins':'rw' -v '[config]':'/home/node/app/config':'rw' -v '[data]':'/home/node/app/data':'rw' 'ghcr.io/sillytavern/sillytavern:[version]'` | ||
| 280 | |||
| 281 | > 請注意:默認的監聽端口為 8000。如果您在設定文件中更改了此端口,請務必使用適當的端口號 | ||
| 282 | |||
| 283 | ## 📱 於 Android 系統中使用 Termux 安裝 | ||
| 284 | |||
| 285 | > \[!NOTE] | ||
| 286 | > **雖然您可以在 Android 設備上使用 Termux 直接運行 SillyTavern,但這不在我們的官方支持範圍內。** | ||
| 287 | > | ||
| 288 | > **請參閱 ArroganceComplex#2659 所提供的指南:** | ||
| 289 | > | ||
| 290 | > * <https://rentry.org/STAI-Termux> | ||
| 291 | |||
| 292 | **不支援:Android ARM LEtime-web。** 32 位 Android 系統需要額外的依賴項,這無法通過 npm 安裝。請使用以下命令安裝:`pkg install esbuild`。完成後,請按照普通的安裝步驟進行操作 | ||
| 293 | |||
| 294 | ## API 金鑰管理 | ||
| 295 | |||
| 296 | SillyTavern 將您的 API 金鑰(Keys)保存在使用者數據目錄中的 `secrets.json` 文件內(默認路徑為`/data/default-user/secrets.json`) | ||
| 297 | |||
| 298 | 默認情況下,API 金鑰在您保存並重新載入頁面後,將不會自介面中顯示 | ||
| 299 | |||
| 300 | 如需啟用查看金鑰功能: | ||
| 301 | |||
| 302 | 1. 在 `config.yaml` 文件中,將 `allowKeysExposure` 的「值」設為 `true` | ||
| 303 | 2. 重新啟動 SillyTavern 伺服器 | ||
| 304 | 3. 點擊 API 連線頁面右下角的「查看隱藏的 API 金鑰(View hidden API keys)」超連結 | ||
| 305 | |||
| 306 | ## 命令列參數(Command-line Arguments) | ||
| 307 | |||
| 308 | 您可以在啟動 SillyTavern 伺服器時傳遞命令列參數,以覆蓋 `config.yaml` 文件中的某些設定。 | ||
| 309 | |||
| 310 | ### 範例 | ||
| 311 | |||
| 312 | ```shell | ||
| 313 | node server.js --port 8000 --listen false | ||
| 314 | # or | ||
| 315 | npm run start -- --port 8000 --listen false | ||
| 316 | # or(僅適用於 Windows) | ||
| 317 | Start.bat --port 8000 --listen false | ||
| 318 | ``` | ||
| 319 | |||
| 320 | ### Supported arguments | ||
| 321 | |||
| 322 | | Option | Description | Type | | ||
| 323 | |----------------------------------|---------------------------------------------|---------| | ||
| 324 | | `--version` | 顯示版本序號 | boolean | | ||
| 325 | | `--enableIPv6` | 啟用 IPv6 | boolean | | ||
| 326 | | `--enableIPv4` | 啟用 IPv4 | boolean | | ||
| 327 | | `--port` | 設定 SillyTavern 運行的端口。 | number | | ||
| 328 | | `--dnsPreferIPv6` | 偏好使用 IPv6 解析 DNS。 | boolean | | ||
| 329 | | `--browserLaunchEnabled` | 自動在瀏覽器中啟動 SillyTavern。 | boolean | | ||
| 330 | | `--browserLaunchHostname` | 自動啟動時的主機名稱,通常建議保持為 'auto' | string | | ||
| 331 | | `--browserLaunchPort` | 覆蓋自動啟動的端口設定 | string | | ||
| 332 | | `--browserLaunchAvoidLocalhost` | 在自動模式下避免使用 'localhost' | boolean | | ||
| 333 | | `--listen` | SillyTavern 是否可監聽所有網路接口。 | boolean | | ||
| 334 | | `--corsProxy` | 啟用 CORS 代理。 | boolean | | ||
| 335 | | `--disableCsrf` | 停用 CSRF 保護 | boolean | | ||
| 336 | | `--ssl` | 啟用 SSL | boolean | | ||
| 337 | | `--certPath` | 設定您證書文件的路徑 | string | | ||
| 338 | | `--keyPath` | 設定您私人金鑰文件的路徑 | string | | ||
| 339 | | `--whitelist` | 啟用白名單模式 | boolean | | ||
| 340 | | `--dataRoot` | 設定數據儲存的根目錄 | string | | ||
| 341 | | `--basicAuthMode` | 啟用基本身份驗證模式 | boolean | | ||
| 342 | | `--requestProxyEnabled` | 啟用代理以處理外部請求 | boolean | | ||
| 343 | | `--requestProxyUrl` | 設定請求代理的 URL(支持 HTTP 或 SOCKS 協議)| string | | ||
| 344 | | `--requestProxyBypass` | 請求代理的例外主機清單(主機列表需以空格分隔)| array | | ||
| 345 | |||
| 346 | ## 遠端連線 | ||
| 347 | |||
| 348 | 遠端連線功能最常用於希望在手機上使用 SillyTavern 的使用者。此時伺服器將由同一 Wi-Fi 網路上的 PC 運行。不過,您也可以設定來自其他網路的遠端連線。 | ||
| 349 | |||
| 350 | 詳細設定指南請參閱 [官方說明文件](https://docs.sillytavern.app/usage/remoteconnections/)。 | ||
| 351 | |||
| 352 | 您還可以選擇設定 SillyTavern 的使用者檔案,並開啟密碼保護(可選):[使用者設定指南](https://docs.sillytavern.app/installation/st-1.12.0-migration-guide/#users)。 | ||
| 353 | |||
| 354 | ## 遇到任何效能問題? | ||
| 355 | |||
| 356 | 1. 在「使用者設定」選單(設定介面主題)中,禁用模糊效果(Blur Effect),並開啟「減少動畫效果」(Reduced Motion) | ||
| 357 | 2. 若使用響應串流傳輸,請將串流的 FPS 設定為較低的值(建議設定為 10-15 FPS) | ||
| 358 | 3. 確保瀏覽器已啟用 GPU 加速以進行渲染 | ||
| 359 | 73 | ||
| 360 | ## 授權與致謝 | 74 | ## 授權與致謝 |
| 361 | 75 | ||
| 362 | **本程式(SillyTavern)的發布是基於其可能對使用者有所幫助的期許,但不提供任何形式的保證;包括但不限於對可銷售性(marketability)或特定用途適用性的隱含保證。如需更多詳情,請參閱 GNU Affero 通用公共許可證。** | 76 | **本程式(SillyTavern)的發布是基於其可能對使用者有所幫助的期許,但不提供任何形式的保證;包括但不限於對可銷售性(marketability)或特定用途適用性的隱含保證。如需更多詳情,請參閱 GNU Affero 通用公共許可證。** |
| 363 | 77 | ||
| 364 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. | ||
| 365 | |||
| 366 | * [TavernAI](https://github.com/TavernAI/TavernAI) 1.2.8 由 Humi 提供:MIT 許可 | 78 | * [TavernAI](https://github.com/TavernAI/TavernAI) 1.2.8 由 Humi 提供:MIT 許可 |
| 367 | * 經授權使用部分來自 CncAnon 的 TavernAITurbo 模組 | 79 | * 經授權使用部分來自 CncAnon 的 TavernAITurbo 模組 |
| 368 | * 視覺小說模式(Visual Novel Mode)的靈感,來源於 PepperTaco 的貢獻(<https://github.com/peppertaco/Tavern/>) | 80 | * 視覺小說模式(Visual Novel Mode)的靈感,來源於 PepperTaco 的貢獻(<https://github.com/peppertaco/Tavern/>) |
| @@ -370,6 +82,7 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| 370 | * 主題圖示由 Font Awesome <https://fontawesome.com> 提供(圖示:CC BY 4.0,字體:SIL OFL 1.1,程式碼:MIT 許可) | 82 | * 主題圖示由 Font Awesome <https://fontawesome.com> 提供(圖示:CC BY 4.0,字體:SIL OFL 1.1,程式碼:MIT 許可) |
| 371 | * 預設資源來源於 @OtisAlejandro(包含角色 Seraphina 與知識書)與 @kallmeflocc(SillyTavern 官方 Discord 伺服器成員突破 10K 的慶祝背景) | 83 | * 預設資源來源於 @OtisAlejandro(包含角色 Seraphina 與知識書)與 @kallmeflocc(SillyTavern 官方 Discord 伺服器成員突破 10K 的慶祝背景) |
| 372 | * Docker 安裝指南由 [@mrguymiah](https://github.com/mrguymiah) 和 [@Bronya-Rand](https://github.com/Bronya-Rand) 編寫 | 84 | * Docker 安裝指南由 [@mrguymiah](https://github.com/mrguymiah) 和 [@Bronya-Rand](https://github.com/Bronya-Rand) 編寫 |
| 85 | * kokoro-js 函式庫由 [@hexgrad](https://github.com/hexgrad) 提供 (Apache-2.0 許可) | ||
| 373 | 86 | ||
| 374 | ## 主要貢獻者 | 87 | ## 主要貢獻者 |
| 375 | 88 | ||
| @@ -31,29 +31,9 @@ Beginning in February 2023 as a fork of TavernAI 1.2.8, SillyTavern now has over | |||
| 31 | 2. We do not provide any online or hosted services, nor programmatically track any user data. | 31 | 2. We do not provide any online or hosted services, nor programmatically track any user data. |
| 32 | 3. SillyTavern is a passion project brought to you by a dedicated community of LLM enthusiasts, and will always be free and open sourced. | 32 | 3. SillyTavern is a passion project brought to you by a dedicated community of LLM enthusiasts, and will always be free and open sourced. |
| 33 | 33 | ||
| 34 | ## Branches | 34 | ## Do I need a powerful PC to run SillyTavern? |
| 35 | 35 | ||
| 36 | SillyTavern is being developed using a two-branch system to ensure a smooth experience for all users. | 36 | The hardware requirements are minimal: it will run on anything that can run NodeJS 18 or higher. If you intend to do LLM inference on your local machine, we recommend a 3000-series NVIDIA graphics card with at least 6GB of VRAM, but actual requirements may vary depending on the model and backend you choose to use. |
| 37 | |||
| 38 | * `release` -🌟 **Recommended for most users.** This is the most stable and recommended branch, updated only when major releases are pushed. It's suitable for the majority of users. Typically updated once a month. | ||
| 39 | * `staging` - ⚠️ **Not recommended for casual use.** This branch has the latest features, but be cautious as it may break at any time. Only for power users and enthusiasts. Updates several times daily. | ||
| 40 | |||
| 41 | 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. | ||
| 42 | |||
| 43 | ## What do I need other than SillyTavern? | ||
| 44 | |||
| 45 | Since SillyTavern is only an interface, you will need access to an LLM backend to provide inference. You can use AI Horde for instant out-of-the-box chatting. Aside from that, we support many other local and cloud-based LLM backends: OpenAI-compatible API, KoboldAI, Tabby, and many more. You can read more about our supported APIs in [the Docs](https://docs.sillytavern.app/usage/api-connections/). | ||
| 46 | |||
| 47 | ### Do I need a powerful PC to run SillyTavern? | ||
| 48 | |||
| 49 | The hardware requirements are minimal: it will run on anything that can run NodeJS 18 or higher. If you intend to do LLM inference on your local machine, we recommend a 3000-series NVIDIA graphics card with at least 6GB of VRAM. Check your backend's documentation for more details. | ||
| 50 | |||
| 51 | ### Suggested Backends (not affiliated) | ||
| 52 | |||
| 53 | * [AI Horde](https://aihorde.net/) - use models hosted by volunteers. Requires no further setup | ||
| 54 | * [KoboldCpp](https://github.com/LostRuins/koboldcpp) - a community's favorite for running GGUF models locally | ||
| 55 | * [tabbyAPI](https://github.com/theroyallab/tabbyAPI) - a popular, lightweight, locally-hosted exl2 inference API | ||
| 56 | * [OpenRouter](https://openrouter.ai) - a single API for many cloud providers (OpenAI, Claude, Meta Llama, etc.) as well as popular community models. | ||
| 57 | 37 | ||
| 58 | ## Questions or suggestions? | 38 | ## Questions or suggestions? |
| 59 | 39 | ||
| @@ -79,316 +59,14 @@ Or get in touch with the developers directly: | |||
| 79 | <img width="500" alt="image" src="https://github.com/user-attachments/assets/9b5f32f0-c3b3-4102-b3f5-0e9213c0f50f"> | 59 | <img width="500" alt="image" src="https://github.com/user-attachments/assets/9b5f32f0-c3b3-4102-b3f5-0e9213c0f50f"> |
| 80 | <img width="500" alt="image" src="https://github.com/user-attachments/assets/913fdbaa-7d33-42f1-ae2c-89dca41c53d1"> | 60 | <img width="500" alt="image" src="https://github.com/user-attachments/assets/913fdbaa-7d33-42f1-ae2c-89dca41c53d1"> |
| 81 | 61 | ||
| 82 | ## Character Cards | 62 | ## Installation |
| 83 | |||
| 84 | SillyTavern is built around the concept of "character cards". A character card is a collection of prompts that set the behavior of the LLM and is required to have persistent conversations in SillyTavern. They function similarly to ChatGPT's GPTs or Poe's bots. The content of a character card can be anything: an abstract scenario, an assistant tailored for a specific task, a famous personality or a fictional character. | ||
| 85 | |||
| 86 | To have a quick conversation without selecting a character card or to just test the LLM connection, simply type your prompt input into the input bar on the Welcome Screen after opening SillyTavern. This will create an empty "Assistant" character card that you can customize later. | ||
| 87 | |||
| 88 | To get a general idea on how to define character cards, see the default character (Seraphina) or download selected community-made cards from the "Download Extensions & Assets" menu. | ||
| 89 | |||
| 90 | ## Key Features | ||
| 91 | |||
| 92 | * Advanced text generation settings with many community-made presets | ||
| 93 | * World Info support: create rich lore or save tokens on your character card | ||
| 94 | * Group chats: multi-bot rooms for characters to talk to you and/or each other | ||
| 95 | * Rich UI customization options: theme colors, background images, custom CSS, and more | ||
| 96 | * User personas: let the AI know a bit about you for greater immersion | ||
| 97 | * Built-in RAG support: add documents to your chats for the AI to reference | ||
| 98 | * Extensive chat commands subsystem and own [scripting engine](https://docs.sillytavern.app/usage/st-script/) | ||
| 99 | |||
| 100 | ## Extensions | ||
| 101 | |||
| 102 | SillyTavern has extensibility support. | ||
| 103 | |||
| 104 | * Character emotional expressions (sprites) | ||
| 105 | * Auto-Summary of the chat history | ||
| 106 | * Automatic UI and chat translation | ||
| 107 | * Stable Diffusion/FLUX/DALL-E image generation | ||
| 108 | * Text-to-speech for AI response messages (via ElevenLabs, Silero, or the OS's System TTS) | ||
| 109 | * Web Search capabilities for adding additional real world context to your prompts | ||
| 110 | * Many more are available to download from the "Download Extensions & Assets" menu. | ||
| 111 | |||
| 112 | Tutorials on how to use them can be found in the [Docs](https://docs.sillytavern.app/). | ||
| 113 | |||
| 114 | ## ⌛ Installation | ||
| 115 | |||
| 116 | ### 🪟 Windows | ||
| 117 | |||
| 118 | > \[!WARNING] | ||
| 119 | > | ||
| 120 | > * DO NOT INSTALL INTO ANY WINDOWS CONTROLLED FOLDER (Program Files, System32, etc). | ||
| 121 | > * DO NOT RUN START.BAT WITH ADMIN PERMISSIONS | ||
| 122 | > * INSTALLATION ON WINDOWS 7 IS IMPOSSIBLE AS IT CAN NOT RUN NODEJS 18.16 | ||
| 123 | |||
| 124 | #### Installing via Git (recommended) | ||
| 125 | |||
| 126 | 1. Install [NodeJS](https://nodejs.org/en) (latest LTS version is recommended) | ||
| 127 | 2. Install [Git for Windows](https://gitforwindows.org/) | ||
| 128 | 3. Open Windows Explorer (`Win+E`) | ||
| 129 | 4. Browse to or Create a folder that is not controlled or monitored by Windows. (ex: C:\MySpecialFolder\) | ||
| 130 | 5. Open a Command Prompt inside that folder by clicking in the 'Address Bar' at the top, typing `cmd`, and pressing Enter. | ||
| 131 | 6. Once the black box (Command Prompt) pops up, type ONE of the following into it and press Enter: | ||
| 132 | |||
| 133 | * for Release Branch: `git clone https://github.com/SillyTavern/SillyTavern -b release` | ||
| 134 | * for Staging Branch: `git clone https://github.com/SillyTavern/SillyTavern -b staging` | ||
| 135 | |||
| 136 | 7. Once everything is cloned, double-click `Start.bat` to make NodeJS install its requirements. | ||
| 137 | 8. The server will then start, and SillyTavern will pop up in your browser. | ||
| 138 | |||
| 139 | #### Installing via GitHub Desktop | ||
| 140 | |||
| 141 | (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/)) | ||
| 142 | |||
| 143 | 1. Install [NodeJS](https://nodejs.org/en) (latest LTS version is recommended) | ||
| 144 | 2. Install [GitHub Desktop](https://central.github.com/deployments/desktop/desktop/latest/win32) | ||
| 145 | 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) | ||
| 146 | 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. | ||
| 147 | 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` | ||
| 148 | 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) | ||
| 149 | 8. After double-clicking, a large black command console window should open and SillyTavern will begin to install what it needs to operate. | ||
| 150 | 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: | ||
| 151 | 10. Connect to any of the [supported APIs](https://docs.sillytavern.app/usage/api-connections/) and start chatting! | ||
| 152 | |||
| 153 | ### 🐧 Linux & 🍎 MacOS | ||
| 154 | |||
| 155 | For MacOS / Linux all of these will be done in a Terminal. | ||
| 156 | |||
| 157 | 1. Install git and nodeJS (the method for doing this will vary depending on your OS) | ||
| 158 | 2. Clone the repo | ||
| 159 | |||
| 160 | * for Release Branch: `git clone https://github.com/SillyTavern/SillyTavern -b release` | ||
| 161 | * for Staging Branch: `git clone https://github.com/SillyTavern/SillyTavern -b staging` | ||
| 162 | |||
| 163 | 3. `cd SillyTavern` to navigate into the install folder. | ||
| 164 | 4. Run the `start.sh` script with one of these commands: | ||
| 165 | |||
| 166 | * `./start.sh` | ||
| 167 | * `bash start.sh` | ||
| 168 | |||
| 169 | ## 🐋 Installing via Docker | ||
| 170 | |||
| 171 | These instructions assume you have installed Docker, are able to access your command line for the installation of containers, and familiar with their general operation. | ||
| 172 | |||
| 173 | ### Using the GitHub Container Registry | ||
| 174 | |||
| 175 | #### Docker Compose (easiest) | ||
| 176 | |||
| 177 | Grab the `docker-compose.yml` file from the [GitHub Repository](https://github.com/SillyTavern/SillyTavern/blob/release/docker/docker-compose.yml) and run the following command in the directory where the file is located. This will pull the latest release image from the GitHub Container Registry and start the container, automatically creating the necessary volumes. | ||
| 178 | |||
| 179 | ```shell | ||
| 180 | docker-compose up | ||
| 181 | ``` | ||
| 182 | |||
| 183 | Customize the `docker-compose.yml` file to your needs. The default port is 8000. If you want to adjust the server configuration using environment variables, read the documentation [here](https://docs.sillytavern.app/administration/config-yaml/#environment-variables). | ||
| 184 | |||
| 185 | #### Docker CLI (advanced) | ||
| 186 | |||
| 187 | You will need two mandatory directory mappings and a port mapping to allow SillyTavern to function. In the command, replace your selections in the following places: | ||
| 188 | |||
| 189 | #### Container Variables | ||
| 190 | |||
| 191 | ##### Volume Mappings | ||
| 192 | |||
| 193 | * `CONFIG_PATH` - The directory where SillyTavern configuration files will be stored on your host machine | ||
| 194 | * `DATA_PATH` - The directory where SillyTavern user data (including characters) will be stored on your host machine | ||
| 195 | * `PLUGINS_PATH` - (optional) The directory where SillyTavern server plugins will be stored on your host machine | ||
| 196 | * `EXTENSIONS_PATH` - (optional) The directory where global UI extensions will be stored on your host machine | ||
| 197 | |||
| 198 | ##### Port Mappings | ||
| 199 | |||
| 200 | * `PUBLIC_PORT` - The port to expose the traffic on. This is mandatory, as you will be accessing the instance from outside of its virtual machine container. DO NOT expose this to the internet without implementing a separate service for security. | ||
| 201 | |||
| 202 | ##### Additional Settings | ||
| 203 | |||
| 204 | * `SILLYTAVERN_VERSION` - On the right-hand side of this GitHub page, you'll see "Packages". Select the "sillytavern" package and you'll see the image versions. The image tag "latest" will keep you up-to-date with the current release. You can also utilize "staging" that points to the nightly image of the respective branch. | ||
| 205 | |||
| 206 | #### Running the container | ||
| 207 | |||
| 208 | 1. Open your Command Line | ||
| 209 | 2. Run the following command in a folder where you want to store the configuration and data files: | ||
| 210 | |||
| 211 | ```bash | ||
| 212 | SILLYTAVERN_VERSION="latest" | ||
| 213 | PUBLIC_PORT="8000" | ||
| 214 | CONFIG_PATH="./config" | ||
| 215 | DATA_PATH="./data" | ||
| 216 | PLUGINS_PATH="./plugins" | ||
| 217 | EXTENSIONS_PATH="./extensions" | ||
| 218 | |||
| 219 | docker run \ | ||
| 220 | --name="sillytavern" \ | ||
| 221 | -p "$PUBLIC_PORT:8000/tcp" \ | ||
| 222 | -v "$CONFIG_PATH:/home/node/app/config:rw" \ | ||
| 223 | -v "$DATA_PATH:/home/node/app/data:rw" \ | ||
| 224 | -v "$EXTENSIONS_PATH:/home/node/app/public/scripts/extensions/third-party:rw" \ | ||
| 225 | -v "$PLUGINS_PATH:/home/node/app/plugins:rw" \ | ||
| 226 | ghcr.io/sillytavern/sillytavern:"$SILLYTAVERN_VERSION" | ||
| 227 | ``` | ||
| 228 | |||
| 229 | > By default the container will run in the foreground. If you want to run it in the background, add the `-d` flag to the `docker run` command. | ||
| 230 | |||
| 231 | ### Building the image yourself | ||
| 232 | |||
| 233 | We have a comprehensive guide on using SillyTavern in Docker [here](http://docs.sillytavern.app/installation/docker/) which covers installations on Windows, macOS and Linux! Give it a read if you wish to build the image yourself. | ||
| 234 | |||
| 235 | ### Common issues with Docker | ||
| 236 | |||
| 237 | #### SELinux Permission Issues with Mounted Volumes | ||
| 238 | |||
| 239 | Linux distributions with SELinux enabled (such as RHEL, CentOS, Fedora, etc.) may prevent Docker containers from accessing mounted volumes due to security policies. This can result in permission denied errors when the container tries to read or write to the mounted directories. | ||
| 240 | |||
| 241 | Two suffixes `:z` or `:Z` can be added to the volume mount. These suffixes tell Docker to relabel file objects on the shared volumes. | ||
| 242 | |||
| 243 | * The `z` option is used when the volume content will be shared between containers. | ||
| 244 | * The `Z` option is used when the volume content should only be used by the current container. | ||
| 245 | |||
| 246 | Example: | ||
| 247 | |||
| 248 | ```yaml | ||
| 249 | # docker-compose.yml | ||
| 250 | volumes: | ||
| 251 | ## Shared volume | ||
| 252 | - ./config:/home/node/app/config:z | ||
| 253 | ## Private volume | ||
| 254 | - ./data:/home/node/app/data:Z | ||
| 255 | ``` | ||
| 256 | |||
| 257 | ## ⚡ Installing via SillyTavern Launcher | ||
| 258 | |||
| 259 | SillyTavern Launcher is an installation wizard that will help you get setup with many options, including installing a backend for local inference. | ||
| 260 | |||
| 261 | ### For Windows users | ||
| 262 | |||
| 263 | 1. On your keyboard: press **`WINDOWS + R`** to open Run dialog box. Then, run the following command to install git: | ||
| 264 | |||
| 265 | ```shell | ||
| 266 | cmd /c winget install -e --id Git.Git | ||
| 267 | ``` | ||
| 268 | |||
| 269 | 2. On your keyboard: press **`WINDOWS + E`** to open File Explorer, then navigate to the folder where you want to install the launcher. Once in the desired folder, type `cmd` into the address bar and press enter. Then, run the following command: | ||
| 270 | |||
| 271 | ```shell | ||
| 272 | git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher && start installer.bat | ||
| 273 | ``` | ||
| 274 | |||
| 275 | ### For Linux users | ||
| 276 | |||
| 277 | 1. Open your favorite terminal and install git | ||
| 278 | 2. Git clone the Sillytavern-Launcher with: | ||
| 279 | |||
| 280 | ```shell | ||
| 281 | git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher | ||
| 282 | ``` | ||
| 283 | |||
| 284 | 3. Start the installer.sh with: | ||
| 285 | |||
| 286 | ```shell | ||
| 287 | chmod +x install.sh && ./install.sh | ||
| 288 | ``` | ||
| 289 | |||
| 290 | 4. After installation start the launcher.sh with: | ||
| 291 | |||
| 292 | ```shell | ||
| 293 | chmod +x launcher.sh && ./launcher.sh | ||
| 294 | ``` | ||
| 295 | |||
| 296 | ### For Mac users | ||
| 297 | |||
| 298 | 1. Open a terminal and install brew with: | ||
| 299 | |||
| 300 | ```shell | ||
| 301 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" | ||
| 302 | ``` | ||
| 303 | |||
| 304 | 2. Install git with: | ||
| 305 | |||
| 306 | ```shell | ||
| 307 | brew install git | ||
| 308 | ``` | ||
| 309 | |||
| 310 | 3. Git clone the Sillytavern-Launcher with: | ||
| 311 | |||
| 312 | ```shell | ||
| 313 | git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher | ||
| 314 | ``` | ||
| 315 | |||
| 316 | 4. Start the installer.sh with: | ||
| 317 | |||
| 318 | ```shell | ||
| 319 | chmod +x install.sh && ./install.sh | ||
| 320 | ``` | ||
| 321 | |||
| 322 | 5. After installation start the launcher.sh with: | ||
| 323 | |||
| 324 | ```shell | ||
| 325 | chmod +x launcher.sh && ./launcher.sh | ||
| 326 | ``` | ||
| 327 | |||
| 328 | ## 📱 Installing via Termux on Android OS | ||
| 329 | |||
| 330 | > \[!NOTE] | ||
| 331 | > **SillyTavern can be run natively on Android devices using Termux, but we do not provide official support for this use case.** | ||
| 332 | > | ||
| 333 | > **Please refer to this guide by ArroganceComplex#2659:** | ||
| 334 | > | ||
| 335 | > * <https://rentry.org/STAI-Termux> | ||
| 336 | |||
| 337 | **Unsupported platform: android arm LEtime-web.** 32-bit Android requires an external dependency that can't be installed with npm. Use the following command to install it: `pkg install esbuild`. Then run the usual installation steps. | ||
| 338 | |||
| 339 | ## Command-line arguments | ||
| 340 | |||
| 341 | You can pass command-line arguments to SillyTavern server startup to override some settings in `config.yaml`. | ||
| 342 | |||
| 343 | ### Examples | ||
| 344 | |||
| 345 | ```shell | ||
| 346 | node server.js --port 8000 --listen false | ||
| 347 | # or | ||
| 348 | npm run start -- --port 8000 --listen false | ||
| 349 | # or (Windows only) | ||
| 350 | Start.bat --port 8000 --listen false | ||
| 351 | ``` | ||
| 352 | |||
| 353 | ### Supported arguments | ||
| 354 | |||
| 355 | > \[!TIP] | ||
| 356 | > None of the arguments are required. If you don't provide them, SillyTavern will use the settings in `config.yaml`. | ||
| 357 | |||
| 358 | | Option | Description | Type | | ||
| 359 | |---------------------------------|----------------------------------------------------------------------|----------| | ||
| 360 | | `--version` | Show version number | boolean | | ||
| 361 | | `--configPath` | Override the path to the config.yaml file | string | | ||
| 362 | | `--dataRoot` | Root directory for data storage | string | | ||
| 363 | | `--port` | Sets the port under which SillyTavern will run | number | | ||
| 364 | | `--listen` | SillyTavern will listen on all network interfaces | boolean | | ||
| 365 | | `--whitelist` | Enables whitelist mode | boolean | | ||
| 366 | | `--basicAuthMode` | Enables basic authentication | boolean | | ||
| 367 | | `--enableIPv4` | Enables IPv4 protocol | boolean | | ||
| 368 | | `--enableIPv6` | Enables IPv6 protocol | boolean | | ||
| 369 | | `--listenAddressIPv4` | Specific IPv4 address to listen to | string | | ||
| 370 | | `--listenAddressIPv6` | Specific IPv6 address to listen to | string | | ||
| 371 | | `--dnsPreferIPv6` | Prefers IPv6 for DNS | boolean | | ||
| 372 | | `--ssl` | Enables SSL | boolean | | ||
| 373 | | `--certPath` | Path to your certificate file | string | | ||
| 374 | | `--keyPath` | Path to your private key file | string | | ||
| 375 | | `--browserLaunchEnabled` | Automatically launch SillyTavern in the browser | boolean | | ||
| 376 | | `--browserLaunchHostname` | Browser launch hostname | string | | ||
| 377 | | `--browserLaunchPort` | Overrides the port for browser launch | string | | ||
| 378 | | `--browserLaunchAvoidLocalhost` | Avoids using 'localhost' for browser launch in auto mode | boolean | | ||
| 379 | | `--corsProxy` | Enables CORS proxy | boolean | | ||
| 380 | | `--requestProxyEnabled` | Enables a use of proxy for outgoing requests | boolean | | ||
| 381 | | `--requestProxyUrl` | Request proxy URL (HTTP or SOCKS protocols) | string | | ||
| 382 | | `--requestProxyBypass` | Request proxy bypass list (space separated list of hosts) | array | | ||
| 383 | | `--disableCsrf` | Disables CSRF protection (NOT RECOMMENDED) | boolean | | ||
| 384 | |||
| 385 | ## Remote connections | ||
| 386 | |||
| 387 | Most often this is for people who want to use SillyTavern on their mobile phones while their PC runs the ST server on the same Wi-Fi network. However, it can be used to allow remote connections from anywhere as well. | ||
| 388 | 63 | ||
| 389 | Read the detailed guide on how to set up remote connections in the [Docs](https://docs.sillytavern.app/usage/remoteconnections/). | 64 | For detailed installation instructions, please visit our documentation: |
| 390 | 65 | ||
| 391 | You may also want to configure SillyTavern user profiles with (optional) password protection: [Users](https://docs.sillytavern.app/administration/multi-user/). | 66 | * **[Windows Installation Guide](https://docs.sillytavern.app/installation/windows/)** |
| 67 | * **[MacOS/Linux Installation Guide](https://docs.sillytavern.app/installation/linuxmacos/)** | ||
| 68 | * **[Android (Termux) Installation Guide](https://docs.sillytavern.app/installation/android-(termux)/)** | ||
| 69 | * **[Docker Installation Guide](https://docs.sillytavern.app/installation/docker/)** | ||
| 392 | 70 | ||
| 393 | ## License and credits | 71 | ## License and credits |
| 394 | 72 | ||
| @@ -100,7 +100,7 @@ disableCsrfProtection: false | |||
| 100 | securityOverride: false | 100 | securityOverride: false |
| 101 | # -- LOGGING CONFIGURATION -- | 101 | # -- LOGGING CONFIGURATION -- |
| 102 | logging: | 102 | logging: |
| 103 | # Enable access logging to access.log file | 103 | # Enable access logging to access.log file and console output |
| 104 | # Records new connections with timestamp, IP address and user agent | 104 | # Records new connections with timestamp, IP address and user agent |
| 105 | enableAccessLog: true | 105 | enableAccessLog: true |
| 106 | # Minimum log level to display in the terminal (DEBUG = 0, INFO = 1, WARN = 2, ERROR = 3) | 106 | # Minimum log level to display in the terminal (DEBUG = 0, INFO = 1, WARN = 2, ERROR = 3) |
| @@ -139,7 +139,7 @@ thumbnails: | |||
| 139 | # JPG thumbnail quality (0-100) | 139 | # JPG thumbnail quality (0-100) |
| 140 | quality: 95 | 140 | quality: 95 |
| 141 | # Maximum thumbnail dimensions per type [width, height] | 141 | # Maximum thumbnail dimensions per type [width, height] |
| 142 | dimensions: { 'bg': [160, 90], 'avatar': [96, 144] } | 142 | dimensions: { 'bg': [160, 90], 'avatar': [96, 144], 'persona': [96, 144] } |
| 143 | 143 | ||
| 144 | # PERFORMANCE-RELATED CONFIGURATION | 144 | # PERFORMANCE-RELATED CONFIGURATION |
| 145 | performance: | 145 | performance: |
| @@ -151,6 +151,15 @@ performance: | |||
| 151 | # Enables disk caching for character cards. Improves performances with large card libraries. | 151 | # Enables disk caching for character cards. Improves performances with large card libraries. |
| 152 | useDiskCache: true | 152 | useDiskCache: true |
| 153 | 153 | ||
| 154 | # CACHE BUSTER CONFIGURATION | ||
| 155 | # IMPORTANT: Requires localhost or a domain with HTTPS, otherwise will not work! | ||
| 156 | cacheBuster: | ||
| 157 | # Clear browser cache on first load or after uploading image files | ||
| 158 | enabled: false | ||
| 159 | # Only clear cache for the specified user agent regex pattern | ||
| 160 | # Example: 'firefox|safari' (case-insensitive) | ||
| 161 | userAgentPattern: '' | ||
| 162 | |||
| 154 | # Allow secret keys exposure via API | 163 | # Allow secret keys exposure via API |
| 155 | allowKeysExposure: false | 164 | allowKeysExposure: false |
| 156 | # Skip new default content checks | 165 | # Skip new default content checks |
| @@ -688,6 +688,10 @@ | |||
| 688 | "type": "instruct" | 688 | "type": "instruct" |
| 689 | }, | 689 | }, |
| 690 | { | 690 | { |
| 691 | "filename": "presets/instruct/KoboldAI.json", | ||
| 692 | "type": "instruct" | ||
| 693 | }, | ||
| 694 | { | ||
| 691 | "filename": "presets/context/Llama-3-Instruct-Names.json", | 695 | "filename": "presets/context/Llama-3-Instruct-Names.json", |
| 692 | "type": "context" | 696 | "type": "context" |
| 693 | }, | 697 | }, |
| @@ -818,5 +822,21 @@ | |||
| 818 | { | 822 | { |
| 819 | "filename": "presets/context/Dots1.json", | 823 | "filename": "presets/context/Dots1.json", |
| 820 | "type": "context" | 824 | "type": "context" |
| 825 | }, | ||
| 826 | { | ||
| 827 | "filename": "presets/context/Mistral V7-Tekken.json", | ||
| 828 | "type": "context" | ||
| 829 | }, | ||
| 830 | { | ||
| 831 | "filename": "presets/instruct/Mistral V7-Tekken.json", | ||
| 832 | "type": "instruct" | ||
| 833 | }, | ||
| 834 | { | ||
| 835 | "filename": "presets/instruct/Moonshot AI.json", | ||
| 836 | "type": "instruct" | ||
| 837 | }, | ||
| 838 | { | ||
| 839 | "filename": "presets/context/Moonshot AI.json", | ||
| 840 | "type": "context" | ||
| 821 | } | 841 | } |
| 822 | ] | 842 | ] |
| @@ -0,0 +1,10 @@ | |||
| 1 | { | ||
| 2 | "story_string": "[SYSTEM_PROMPT]{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{personality}}\n{{/if}}{{#if scenario}}{{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}{{trim}}[/SYSTEM_PROMPT]", | ||
| 3 | "example_separator": "", | ||
| 4 | "chat_start": "", | ||
| 5 | "use_stop_strings": false, | ||
| 6 | "always_force_name2": true, | ||
| 7 | "trim_sentences": false, | ||
| 8 | "single_line": false, | ||
| 9 | "name": "Mistral V7-Tekken" | ||
| 10 | } | ||
| @@ -0,0 +1,11 @@ | |||
| 1 | { | ||
| 2 | "story_string": "<|im_system|>system<|im_middle|>{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}{{trim}}<|im_end|>", | ||
| 3 | "example_separator": "", | ||
| 4 | "chat_start": "", | ||
| 5 | "use_stop_strings": false, | ||
| 6 | "names_as_stop_strings": true, | ||
| 7 | "always_force_name2": true, | ||
| 8 | "trim_sentences": false, | ||
| 9 | "single_line": false, | ||
| 10 | "name": "Moonshot AI" | ||
| 11 | } | ||
| \ No newline at end of file | 11 | \ No newline at end of file | |
| @@ -0,0 +1,22 @@ | |||
| 1 | { | ||
| 2 | "input_sequence": "{{[INPUT]}}", | ||
| 3 | "output_sequence": "{{[OUPUT]}}", | ||
| 4 | "last_output_sequence": "", | ||
| 5 | "system_sequence": "{{[SYSTEM]}}", | ||
| 6 | "stop_sequence": "", | ||
| 7 | "wrap": true, | ||
| 8 | "macro": true, | ||
| 9 | "names_behavior": "force", | ||
| 10 | "activation_regex": "", | ||
| 11 | "system_sequence_prefix": "", | ||
| 12 | "system_sequence_suffix": "", | ||
| 13 | "first_output_sequence": "", | ||
| 14 | "skip_examples": false, | ||
| 15 | "output_suffix": "{{[OUTPUT_END]}}", | ||
| 16 | "input_suffix": "{{[INPUT_END]}}", | ||
| 17 | "system_suffix": "{{[SYSTEM_END]}}", | ||
| 18 | "user_alignment_message": "", | ||
| 19 | "system_same_as_user": false, | ||
| 20 | "last_system_sequence": "", | ||
| 21 | "name": "KoboldAI" | ||
| 22 | } | ||
| @@ -0,0 +1,22 @@ | |||
| 1 | { | ||
| 2 | "input_sequence": "[INST]", | ||
| 3 | "output_sequence": " ", | ||
| 4 | "last_output_sequence": "", | ||
| 5 | "system_sequence": "[SYSTEM_PROMPT]", | ||
| 6 | "stop_sequence": "</s>", | ||
| 7 | "wrap": false, | ||
| 8 | "macro": true, | ||
| 9 | "names_behavior": "force", | ||
| 10 | "activation_regex": "", | ||
| 11 | "system_sequence_prefix": "", | ||
| 12 | "system_sequence_suffix": "", | ||
| 13 | "first_output_sequence": "", | ||
| 14 | "skip_examples": false, | ||
| 15 | "output_suffix": "</s>", | ||
| 16 | "input_suffix": "[/INST]", | ||
| 17 | "system_suffix": "[/SYSTEM_PROMPT]", | ||
| 18 | "user_alignment_message": "", | ||
| 19 | "system_same_as_user": false, | ||
| 20 | "last_system_sequence": "", | ||
| 21 | "name": "Mistral V7-Tekken" | ||
| 22 | } | ||
| @@ -0,0 +1,24 @@ | |||
| 1 | { | ||
| 2 | "input_sequence": "<|im_user|>user<|im_middle|>", | ||
| 3 | "output_sequence": "<|im_assistant|>assistant<|im_middle|>", | ||
| 4 | "last_output_sequence": "", | ||
| 5 | "system_sequence": "<|im_system|>system<|im_middle|>", | ||
| 6 | "stop_sequence": "<|im_end|>", | ||
| 7 | "wrap": false, | ||
| 8 | "macro": true, | ||
| 9 | "activation_regex": "", | ||
| 10 | "system_sequence_prefix": "", | ||
| 11 | "system_sequence_suffix": "", | ||
| 12 | "first_output_sequence": "", | ||
| 13 | "skip_examples": false, | ||
| 14 | "output_suffix": "<|im_end|>", | ||
| 15 | "input_suffix": "<|im_end|>", | ||
| 16 | "system_suffix": "<|im_end|>", | ||
| 17 | "user_alignment_message": "", | ||
| 18 | "system_same_as_user": false, | ||
| 19 | "last_system_sequence": "", | ||
| 20 | "first_input_sequence": "", | ||
| 21 | "last_input_sequence": "", | ||
| 22 | "names_behavior": "force", | ||
| 23 | "name": "Moonshot AI" | ||
| 24 | } | ||
| @@ -2,12 +2,11 @@ | |||
| 2 | "chat_completion_source": "openai", | 2 | "chat_completion_source": "openai", |
| 3 | "openai_model": "gpt-4-turbo", | 3 | "openai_model": "gpt-4-turbo", |
| 4 | "claude_model": "claude-3-5-sonnet-20240620", | 4 | "claude_model": "claude-3-5-sonnet-20240620", |
| 5 | "windowai_model": "", | ||
| 6 | "openrouter_model": "OR_Website", | 5 | "openrouter_model": "OR_Website", |
| 7 | "openrouter_use_fallback": false, | 6 | "openrouter_use_fallback": false, |
| 8 | "openrouter_group_models": false, | 7 | "openrouter_group_models": false, |
| 9 | "openrouter_sort_models": "alphabetically", | 8 | "openrouter_sort_models": "alphabetically", |
| 10 | "ai21_model": "jamba-1.5-large", | 9 | "ai21_model": "jamba-large", |
| 11 | "mistralai_model": "mistral-large-latest", | 10 | "mistralai_model": "mistral-large-latest", |
| 12 | "custom_model": "", | 11 | "custom_model": "", |
| 13 | "custom_url": "", | 12 | "custom_url": "", |
| @@ -226,12 +225,10 @@ | |||
| 226 | ] | 225 | ] |
| 227 | } | 226 | } |
| 228 | ], | 227 | ], |
| 229 | "api_url_scale": "", | ||
| 230 | "show_external_models": false, | 228 | "show_external_models": false, |
| 231 | "assistant_prefill": "", | 229 | "assistant_prefill": "", |
| 232 | "assistant_impersonation": "", | 230 | "assistant_impersonation": "", |
| 233 | "claude_use_sysprompt": false, | 231 | "claude_use_sysprompt": false, |
| 234 | "use_alt_scale": false, | ||
| 235 | "squash_system_messages": false, | 232 | "squash_system_messages": false, |
| 236 | "image_inlining": false, | 233 | "image_inlining": false, |
| 237 | "bypass_status_check": false, | 234 | "bypass_status_check": false, |
| @@ -623,8 +623,7 @@ | |||
| 623 | "wi_format": "{0}", | 623 | "wi_format": "{0}", |
| 624 | "openai_model": "gpt-4-turbo", | 624 | "openai_model": "gpt-4-turbo", |
| 625 | "claude_model": "claude-3-5-sonnet-20240620", | 625 | "claude_model": "claude-3-5-sonnet-20240620", |
| 626 | "ai21_model": "jamba-1.5-large", | 626 | "ai21_model": "jamba-large", |
| 627 | "windowai_model": "", | ||
| 628 | "openrouter_model": "OR_Website", | 627 | "openrouter_model": "OR_Website", |
| 629 | "reverse_proxy": "", | 628 | "reverse_proxy": "", |
| 630 | "chat_completion_source": "openai", | 629 | "chat_completion_source": "openai", |
| @@ -66,4 +66,9 @@ declare global { | |||
| 66 | * Parsed command line arguments. | 66 | * Parsed command line arguments. |
| 67 | */ | 67 | */ |
| 68 | var COMMAND_LINE_ARGS: CommandLineArguments; | 68 | var COMMAND_LINE_ARGS: CommandLineArguments; |
| 69 | |||
| 70 | /** | ||
| 71 | * Forces a global mode if set to `true` before parsing the CLI arguments. | ||
| 72 | */ | ||
| 73 | var FORCE_GLOBAL_MODE: boolean; | ||
| 69 | } | 74 | } |
| @@ -1,12 +1,12 @@ | |||
| 1 | { | 1 | { |
| 2 | "name": "sillytavern", | 2 | "name": "sillytavern", |
| 3 | "version": "1.13.1", | 3 | "version": "1.13.2", |
| 4 | "lockfileVersion": 3, | 4 | "lockfileVersion": 3, |
| 5 | "requires": true, | 5 | "requires": true, |
| 6 | "packages": { | 6 | "packages": { |
| 7 | "": { | 7 | "": { |
| 8 | "name": "sillytavern", | 8 | "name": "sillytavern", |
| 9 | "version": "1.13.1", | 9 | "version": "1.13.2", |
| 10 | "hasInstallScript": true, | 10 | "hasInstallScript": true, |
| 11 | "license": "AGPL-3.0", | 11 | "license": "AGPL-3.0", |
| 12 | "dependencies": { | 12 | "dependencies": { |
| @@ -40,7 +40,7 @@ | |||
| 40 | "@popperjs/core": "^2.11.8", | 40 | "@popperjs/core": "^2.11.8", |
| 41 | "@zeldafan0225/ai_horde": "^5.2.0", | 41 | "@zeldafan0225/ai_horde": "^5.2.0", |
| 42 | "archiver": "^7.0.1", | 42 | "archiver": "^7.0.1", |
| 43 | "bing-translate-api": "^4.0.2", | 43 | "bing-translate-api": "^4.1.0", |
| 44 | "body-parser": "^1.20.2", | 44 | "body-parser": "^1.20.2", |
| 45 | "bowser": "^2.11.0", | 45 | "bowser": "^2.11.0", |
| 46 | "bytes": "^3.1.2", | 46 | "bytes": "^3.1.2", |
| @@ -55,8 +55,9 @@ | |||
| 55 | "diff-match-patch": "^1.0.5", | 55 | "diff-match-patch": "^1.0.5", |
| 56 | "dompurify": "^3.2.6", | 56 | "dompurify": "^3.2.6", |
| 57 | "droll": "^0.2.1", | 57 | "droll": "^0.2.1", |
| 58 | "env-paths": "^3.0.0", | ||
| 58 | "express": "^4.21.0", | 59 | "express": "^4.21.0", |
| 59 | "form-data": "^4.0.3", | 60 | "form-data": "^4.0.4", |
| 60 | "fuse.js": "^7.1.0", | 61 | "fuse.js": "^7.1.0", |
| 61 | "google-translate-api-browser": "^3.0.1", | 62 | "google-translate-api-browser": "^3.0.1", |
| 62 | "google-translate-api-x": "^10.7.2", | 63 | "google-translate-api-x": "^10.7.2", |
| @@ -77,7 +78,7 @@ | |||
| 77 | "multer": "^2.0.2", | 78 | "multer": "^2.0.2", |
| 78 | "node-fetch": "^3.3.2", | 79 | "node-fetch": "^3.3.2", |
| 79 | "node-persist": "^4.0.4", | 80 | "node-persist": "^4.0.4", |
| 80 | "open": "^10.1.2", | 81 | "open": "^10.2.0", |
| 81 | "png-chunk-text": "^1.0.0", | 82 | "png-chunk-text": "^1.0.0", |
| 82 | "png-chunks-extract": "^1.0.0", | 83 | "png-chunks-extract": "^1.0.0", |
| 83 | "proxy-agent": "^6.5.0", | 84 | "proxy-agent": "^6.5.0", |
| @@ -95,13 +96,13 @@ | |||
| 95 | "wavefile": "^11.0.0", | 96 | "wavefile": "^11.0.0", |
| 96 | "webpack": "^5.98.0", | 97 | "webpack": "^5.98.0", |
| 97 | "write-file-atomic": "^5.0.1", | 98 | "write-file-atomic": "^5.0.1", |
| 98 | "ws": "^8.18.2", | 99 | "ws": "^8.18.3", |
| 99 | "yaml": "^2.8.0", | 100 | "yaml": "^2.8.0", |
| 100 | "yargs": "^17.7.1", | 101 | "yargs": "^17.7.1", |
| 101 | "yauzl": "^3.2.0" | 102 | "yauzl": "^3.2.0" |
| 102 | }, | 103 | }, |
| 103 | "bin": { | 104 | "bin": { |
| 104 | "sillytavern": "server.js" | 105 | "sillytavern": "src/server-global.js" |
| 105 | }, | 106 | }, |
| 106 | "devDependencies": { | 107 | "devDependencies": { |
| 107 | "@types/archiver": "^6.0.3", | 108 | "@types/archiver": "^6.0.3", |
| @@ -117,9 +118,9 @@ | |||
| 117 | "@types/jquery-cropper": "^1.0.4", | 118 | "@types/jquery-cropper": "^1.0.4", |
| 118 | "@types/jquery.transit": "^0.9.33", | 119 | "@types/jquery.transit": "^0.9.33", |
| 119 | "@types/jqueryui": "^1.12.24", | 120 | "@types/jqueryui": "^1.12.24", |
| 120 | "@types/lodash": "^4.17.17", | 121 | "@types/lodash": "^4.17.20", |
| 121 | "@types/mime-types": "^3.0.1", | 122 | "@types/mime-types": "^3.0.1", |
| 122 | "@types/multer": "^1.4.13", | 123 | "@types/multer": "^2.0.0", |
| 123 | "@types/node": "^18.19.84", | 124 | "@types/node": "^18.19.84", |
| 124 | "@types/node-persist": "^3.1.8", | 125 | "@types/node-persist": "^3.1.8", |
| 125 | "@types/png-chunk-text": "^1.0.3", | 126 | "@types/png-chunk-text": "^1.0.3", |
| @@ -1972,9 +1973,9 @@ | |||
| 1972 | } | 1973 | } |
| 1973 | }, | 1974 | }, |
| 1974 | "node_modules/@types/lodash": { | 1975 | "node_modules/@types/lodash": { |
| 1975 | "version": "4.17.17", | 1976 | "version": "4.17.20", |
| 1976 | "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.17.tgz", | 1977 | "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", |
| 1977 | "integrity": "sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQ==", | 1978 | "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==", |
| 1978 | "dev": true, | 1979 | "dev": true, |
| 1979 | "license": "MIT" | 1980 | "license": "MIT" |
| 1980 | }, | 1981 | }, |
| @@ -1999,9 +2000,9 @@ | |||
| 1999 | "license": "MIT" | 2000 | "license": "MIT" |
| 2000 | }, | 2001 | }, |
| 2001 | "node_modules/@types/multer": { | 2002 | "node_modules/@types/multer": { |
| 2002 | "version": "1.4.13", | 2003 | "version": "2.0.0", |
| 2003 | "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.13.tgz", | 2004 | "resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.0.0.tgz", |
| 2004 | "integrity": "sha512-bhhdtPw7JqCiEfC9Jimx5LqX9BDIPJEh2q/fQ4bqbBPtyEZYr3cvF22NwG0DmPZNYA0CAf2CnqDB4KIGGpJcaw==", | 2005 | "integrity": "sha512-C3Z9v9Evij2yST3RSBktxP9STm6OdMc5uR1xF1SGr98uv8dUlAL2hqwrZ3GVB3uyMyiegnscEK6PGtYvNrjTjw==", |
| 2005 | "dev": true, | 2006 | "dev": true, |
| 2006 | "license": "MIT", | 2007 | "license": "MIT", |
| 2007 | "dependencies": { | 2008 | "dependencies": { |
| @@ -2832,9 +2833,9 @@ | |||
| 2832 | } | 2833 | } |
| 2833 | }, | 2834 | }, |
| 2834 | "node_modules/bing-translate-api": { | 2835 | "node_modules/bing-translate-api": { |
| 2835 | "version": "4.0.2", | 2836 | "version": "4.1.0", |
| 2836 | "resolved": "https://registry.npmjs.org/bing-translate-api/-/bing-translate-api-4.0.2.tgz", | 2837 | "resolved": "https://registry.npmjs.org/bing-translate-api/-/bing-translate-api-4.1.0.tgz", |
| 2837 | "integrity": "sha512-JJ8XUehnxzOhHU91oy86xEtp8OOMjVEjCZJX042fKxoO19NNvxJ5omeCcxQNFoPbDqVpBJwqiGVquL0oPdQm1Q==", | 2838 | "integrity": "sha512-oP2663Yd5MXX4kbB/3LdS9YgPiE+ls9+2iFZH2ZXigWhWyHT3R4m6aCup4TNJd3/U4gqHHnQoxTaIW7uOf4+vA==", |
| 2838 | "license": "MIT", | 2839 | "license": "MIT", |
| 2839 | "dependencies": { | 2840 | "dependencies": { |
| 2840 | "got": "^11.8.6" | 2841 | "got": "^11.8.6" |
| @@ -4087,6 +4088,18 @@ | |||
| 4087 | "url": "https://github.com/fb55/entities?sponsor=1" | 4088 | "url": "https://github.com/fb55/entities?sponsor=1" |
| 4088 | } | 4089 | } |
| 4089 | }, | 4090 | }, |
| 4091 | "node_modules/env-paths": { | ||
| 4092 | "version": "3.0.0", | ||
| 4093 | "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", | ||
| 4094 | "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", | ||
| 4095 | "license": "MIT", | ||
| 4096 | "engines": { | ||
| 4097 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" | ||
| 4098 | }, | ||
| 4099 | "funding": { | ||
| 4100 | "url": "https://github.com/sponsors/sindresorhus" | ||
| 4101 | } | ||
| 4102 | }, | ||
| 4090 | "node_modules/es-define-property": { | 4103 | "node_modules/es-define-property": { |
| 4091 | "version": "1.0.1", | 4104 | "version": "1.0.1", |
| 4092 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", | 4105 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", |
| @@ -6420,15 +6433,15 @@ | |||
| 6420 | } | 6433 | } |
| 6421 | }, | 6434 | }, |
| 6422 | "node_modules/open": { | 6435 | "node_modules/open": { |
| 6423 | "version": "10.1.2", | 6436 | "version": "10.2.0", |
| 6424 | "resolved": "https://registry.npmjs.org/open/-/open-10.1.2.tgz", | 6437 | "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", |
| 6425 | "integrity": "sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw==", | 6438 | "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", |
| 6426 | "license": "MIT", | 6439 | "license": "MIT", |
| 6427 | "dependencies": { | 6440 | "dependencies": { |
| 6428 | "default-browser": "^5.2.1", | 6441 | "default-browser": "^5.2.1", |
| 6429 | "define-lazy-prop": "^3.0.0", | 6442 | "define-lazy-prop": "^3.0.0", |
| 6430 | "is-inside-container": "^1.0.0", | 6443 | "is-inside-container": "^1.0.0", |
| 6431 | "is-wsl": "^3.1.0" | 6444 | "wsl-utils": "^0.1.0" |
| 6432 | }, | 6445 | }, |
| 6433 | "engines": { | 6446 | "engines": { |
| 6434 | "node": ">=18" | 6447 | "node": ">=18" |
| @@ -8503,9 +8516,9 @@ | |||
| 8503 | } | 8516 | } |
| 8504 | }, | 8517 | }, |
| 8505 | "node_modules/ws": { | 8518 | "node_modules/ws": { |
| 8506 | "version": "8.18.2", | 8519 | "version": "8.18.3", |
| 8507 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", | 8520 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", |
| 8508 | "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", | 8521 | "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", |
| 8509 | "license": "MIT", | 8522 | "license": "MIT", |
| 8510 | "engines": { | 8523 | "engines": { |
| 8511 | "node": ">=10.0.0" | 8524 | "node": ">=10.0.0" |
| @@ -8523,6 +8536,21 @@ | |||
| 8523 | } | 8536 | } |
| 8524 | } | 8537 | } |
| 8525 | }, | 8538 | }, |
| 8539 | "node_modules/wsl-utils": { | ||
| 8540 | "version": "0.1.0", | ||
| 8541 | "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", | ||
| 8542 | "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", | ||
| 8543 | "license": "MIT", | ||
| 8544 | "dependencies": { | ||
| 8545 | "is-wsl": "^3.1.0" | ||
| 8546 | }, | ||
| 8547 | "engines": { | ||
| 8548 | "node": ">=18" | ||
| 8549 | }, | ||
| 8550 | "funding": { | ||
| 8551 | "url": "https://github.com/sponsors/sindresorhus" | ||
| 8552 | } | ||
| 8553 | }, | ||
| 8526 | "node_modules/xhr": { | 8554 | "node_modules/xhr": { |
| 8527 | "version": "2.6.0", | 8555 | "version": "2.6.0", |
| 8528 | "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", | 8556 | "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", |
| @@ -30,7 +30,7 @@ | |||
| 30 | "@popperjs/core": "^2.11.8", | 30 | "@popperjs/core": "^2.11.8", |
| 31 | "@zeldafan0225/ai_horde": "^5.2.0", | 31 | "@zeldafan0225/ai_horde": "^5.2.0", |
| 32 | "archiver": "^7.0.1", | 32 | "archiver": "^7.0.1", |
| 33 | "bing-translate-api": "^4.0.2", | 33 | "bing-translate-api": "^4.1.0", |
| 34 | "body-parser": "^1.20.2", | 34 | "body-parser": "^1.20.2", |
| 35 | "bowser": "^2.11.0", | 35 | "bowser": "^2.11.0", |
| 36 | "bytes": "^3.1.2", | 36 | "bytes": "^3.1.2", |
| @@ -45,8 +45,9 @@ | |||
| 45 | "diff-match-patch": "^1.0.5", | 45 | "diff-match-patch": "^1.0.5", |
| 46 | "dompurify": "^3.2.6", | 46 | "dompurify": "^3.2.6", |
| 47 | "droll": "^0.2.1", | 47 | "droll": "^0.2.1", |
| 48 | "env-paths": "^3.0.0", | ||
| 48 | "express": "^4.21.0", | 49 | "express": "^4.21.0", |
| 49 | "form-data": "^4.0.3", | 50 | "form-data": "^4.0.4", |
| 50 | "fuse.js": "^7.1.0", | 51 | "fuse.js": "^7.1.0", |
| 51 | "google-translate-api-browser": "^3.0.1", | 52 | "google-translate-api-browser": "^3.0.1", |
| 52 | "google-translate-api-x": "^10.7.2", | 53 | "google-translate-api-x": "^10.7.2", |
| @@ -67,7 +68,7 @@ | |||
| 67 | "multer": "^2.0.2", | 68 | "multer": "^2.0.2", |
| 68 | "node-fetch": "^3.3.2", | 69 | "node-fetch": "^3.3.2", |
| 69 | "node-persist": "^4.0.4", | 70 | "node-persist": "^4.0.4", |
| 70 | "open": "^10.1.2", | 71 | "open": "^10.2.0", |
| 71 | "png-chunk-text": "^1.0.0", | 72 | "png-chunk-text": "^1.0.0", |
| 72 | "png-chunks-extract": "^1.0.0", | 73 | "png-chunks-extract": "^1.0.0", |
| 73 | "proxy-agent": "^6.5.0", | 74 | "proxy-agent": "^6.5.0", |
| @@ -85,7 +86,7 @@ | |||
| 85 | "wavefile": "^11.0.0", | 86 | "wavefile": "^11.0.0", |
| 86 | "webpack": "^5.98.0", | 87 | "webpack": "^5.98.0", |
| 87 | "write-file-atomic": "^5.0.1", | 88 | "write-file-atomic": "^5.0.1", |
| 88 | "ws": "^8.18.2", | 89 | "ws": "^8.18.3", |
| 89 | "yaml": "^2.8.0", | 90 | "yaml": "^2.8.0", |
| 90 | "yargs": "^17.7.1", | 91 | "yargs": "^17.7.1", |
| 91 | "yauzl": "^3.2.0" | 92 | "yauzl": "^3.2.0" |
| @@ -111,10 +112,11 @@ | |||
| 111 | "type": "git", | 112 | "type": "git", |
| 112 | "url": "https://github.com/SillyTavern/SillyTavern.git" | 113 | "url": "https://github.com/SillyTavern/SillyTavern.git" |
| 113 | }, | 114 | }, |
| 114 | "version": "1.13.1", | 115 | "version": "1.13.2", |
| 115 | "scripts": { | 116 | "scripts": { |
| 116 | "start": "node server.js", | 117 | "start": "node server.js", |
| 117 | "debug": "node --inspect server.js", | 118 | "debug": "node --inspect server.js", |
| 119 | "start:global": "node server.js --global", | ||
| 118 | "start:electron": "cd ./src/electron && npm run start", | 120 | "start:electron": "cd ./src/electron && npm run start", |
| 119 | "start:deno": "deno run --allow-run --allow-net --allow-read --allow-write --allow-sys --allow-env server.js", | 121 | "start:deno": "deno run --allow-run --allow-net --allow-read --allow-write --allow-sys --allow-env server.js", |
| 120 | "start:bun": "bun server.js", | 122 | "start:bun": "bun server.js", |
| @@ -126,7 +128,7 @@ | |||
| 126 | "plugins:install": "node plugins install" | 128 | "plugins:install": "node plugins install" |
| 127 | }, | 129 | }, |
| 128 | "bin": { | 130 | "bin": { |
| 129 | "sillytavern": "./server.js" | 131 | "sillytavern": "./src/server-global.js" |
| 130 | }, | 132 | }, |
| 131 | "rules": { | 133 | "rules": { |
| 132 | "no-path-concat": "off", | 134 | "no-path-concat": "off", |
| @@ -147,9 +149,9 @@ | |||
| 147 | "@types/jquery-cropper": "^1.0.4", | 149 | "@types/jquery-cropper": "^1.0.4", |
| 148 | "@types/jquery.transit": "^0.9.33", | 150 | "@types/jquery.transit": "^0.9.33", |
| 149 | "@types/jqueryui": "^1.12.24", | 151 | "@types/jqueryui": "^1.12.24", |
| 150 | "@types/lodash": "^4.17.17", | 152 | "@types/lodash": "^4.17.20", |
| 151 | "@types/mime-types": "^3.0.1", | 153 | "@types/mime-types": "^3.0.1", |
| 152 | "@types/multer": "^1.4.13", | 154 | "@types/multer": "^2.0.0", |
| 153 | "@types/node": "^18.19.84", | 155 | "@types/node": "^18.19.84", |
| 154 | "@types/node-persist": "^3.1.8", | 156 | "@types/node-persist": "^3.1.8", |
| 155 | "@types/png-chunk-text": "^1.0.3", | 157 | "@types/png-chunk-text": "^1.0.3", |
| @@ -112,6 +112,10 @@ | |||
| 112 | flex-direction: column; | 112 | flex-direction: column; |
| 113 | } | 113 | } |
| 114 | 114 | ||
| 115 | .completion_prompt_manager_popup_entry_form .select2-container { | ||
| 116 | margin: 5px 0; | ||
| 117 | } | ||
| 118 | |||
| 115 | #completion_prompt_manager_popup .completion_prompt_manager_popup_entry_form_control:has(#completion_prompt_manager_popup_entry_form_prompt) { | 119 | #completion_prompt_manager_popup .completion_prompt_manager_popup_entry_form_control:has(#completion_prompt_manager_popup_entry_form_prompt) { |
| 116 | flex: 1; | 120 | flex: 1; |
| 117 | display: flex; | 121 | display: flex; |
| @@ -19,14 +19,15 @@ | |||
| 19 | .select2-container .select2-selection .select2-selection__clear { | 19 | .select2-container .select2-selection .select2-selection__clear { |
| 20 | color: var(--SmartThemeBodyColor); | 20 | color: var(--SmartThemeBodyColor); |
| 21 | font-size: 20px; | 21 | font-size: 20px; |
| 22 | padding: 0; | ||
| 23 | position: absolute; | 22 | position: absolute; |
| 24 | right: 5px; | 23 | right: 5px; |
| 25 | top: 0; | 24 | margin-top: 0; /* compensate for the increased font size */ |
| 25 | margin-right: 0; | ||
| 26 | } | 26 | } |
| 27 | 27 | ||
| 28 | .select2-container .select2-search__field { | 28 | .select2-container .select2-search--inline textarea.select2-search__field { |
| 29 | opacity: 0.8; | 29 | opacity: 0.8; |
| 30 | margin-top: 7px; /* get placeholder text to the center */ | ||
| 30 | } | 31 | } |
| 31 | 32 | ||
| 32 | .select2-selection--single .select2-selection__placeholder { | 33 | .select2-selection--single .select2-selection__placeholder { |
| @@ -39,8 +40,8 @@ | |||
| 39 | 40 | ||
| 40 | .select2-container .select2-selection--single .select2-selection__rendered { | 41 | .select2-container .select2-selection--single .select2-selection__rendered { |
| 41 | color: var(--SmartThemeBodyColor); | 42 | color: var(--SmartThemeBodyColor); |
| 42 | line-height: revert; | 43 | line-height: unset; |
| 43 | padding-left: unset; | 44 | padding: 3px 5px; |
| 44 | } | 45 | } |
| 45 | 46 | ||
| 46 | .select2-container .select2-results>.select2-results__options { | 47 | .select2-container .select2-results>.select2-results__options { |
| @@ -63,9 +64,10 @@ | |||
| 63 | background-color: var(--black30a); | 64 | background-color: var(--black30a); |
| 64 | color: var(--SmartThemeBodyColor); | 65 | color: var(--SmartThemeBodyColor); |
| 65 | border: 1px solid var(--SmartThemeBorderColor); | 66 | border: 1px solid var(--SmartThemeBorderColor); |
| 66 | border-radius: 7px; | ||
| 67 | font-family: var(--mainFontFamily); | 67 | font-family: var(--mainFontFamily); |
| 68 | padding: 3px 5px; | 68 | padding: 3px 5px; |
| 69 | display: unset; /* override the ST textarea display style */ | ||
| 70 | border-radius: 0; /* border radius was interfering with cursor */ | ||
| 69 | } | 71 | } |
| 70 | 72 | ||
| 71 | /* Customize the selected option */ | 73 | /* Customize the selected option */ |
| @@ -93,7 +95,10 @@ | |||
| 93 | border: 1px solid var(--SmartThemeBorderColor); | 95 | border: 1px solid var(--SmartThemeBorderColor); |
| 94 | border-radius: 7px; | 96 | border-radius: 7px; |
| 95 | font-family: var(--mainFontFamily); | 97 | font-family: var(--mainFontFamily); |
| 96 | padding: 3px 5px; | 98 | } |
| 99 | |||
| 100 | .select2-container .select2-selection--multiple.select2-selection--clearable { | ||
| 101 | padding-right: 20px; | ||
| 97 | } | 102 | } |
| 98 | 103 | ||
| 99 | .select2-container.select2-container--focus .select2-selection--multiple, | 104 | .select2-container.select2-container--focus .select2-selection--multiple, |
| @@ -230,55 +235,3 @@ span.select2.select2-container .select2-selection__choice__remove:hover { | |||
| 230 | .select2_choice_clickable_buttonstyle+span.select2-container .select2-selection__choice__display:hover { | 235 | .select2_choice_clickable_buttonstyle+span.select2-container .select2-selection__choice__display:hover { |
| 231 | background-color: var(--white30a); | 236 | background-color: var(--white30a); |
| 232 | } | 237 | } |
| 233 | |||
| 234 | /* Custom class to support same line multi inputs of select2 controls */ | ||
| 235 | .select2_multi_sameline+span.select2-container .select2-selection--multiple { | ||
| 236 | display: flex; | ||
| 237 | flex-wrap: wrap; | ||
| 238 | } | ||
| 239 | |||
| 240 | .select2_multi_sameline+span.select2-container .select2-selection--multiple .select2-search--inline { | ||
| 241 | /* Allow search placeholder to take up all space if needed */ | ||
| 242 | flex-grow: 1; | ||
| 243 | } | ||
| 244 | |||
| 245 | .select2_multi_sameline+span.select2-container .select2-selection--multiple .select2-selection__rendered { | ||
| 246 | /* Fix weird styling choice or huge margin around selected options */ | ||
| 247 | margin-block-start: 2px; | ||
| 248 | margin-block-end: 2px; | ||
| 249 | display: flex; | ||
| 250 | align-items: center; | ||
| 251 | flex-wrap: wrap; | ||
| 252 | row-gap: 5px; | ||
| 253 | } | ||
| 254 | |||
| 255 | .select2_multi_sameline+span.select2-container .select2-selection--multiple .select2-selection__choice { | ||
| 256 | margin-top: 0px; | ||
| 257 | } | ||
| 258 | |||
| 259 | .select2_multi_sameline+span.select2-container .select2-selection--multiple .select2-search__field { | ||
| 260 | /* Min height to reserve spacing */ | ||
| 261 | min-height: calc(var(--mainFontSize) + 13px); | ||
| 262 | /* Min width to be clickable */ | ||
| 263 | min-width: 4em; | ||
| 264 | align-content: center; | ||
| 265 | /* Fix search textarea alignment issue with UL elements */ | ||
| 266 | margin-top: 0px; | ||
| 267 | height: unset; | ||
| 268 | /* Prevent height from jumping around when input is focused */ | ||
| 269 | line-height: 1; | ||
| 270 | } | ||
| 271 | |||
| 272 | .select2_multi_sameline+span.select2-container .select2-selection--multiple .select2-selection__rendered { | ||
| 273 | /* Min height to reserve spacing */ | ||
| 274 | min-height: calc(var(--mainFontSize) + 13px); | ||
| 275 | } | ||
| 276 | |||
| 277 | /* Make search bar invisible unless the select2 is active, to save space */ | ||
| 278 | .select2_multi_sameline+span.select2-container .select2-selection--multiple .select2-search--inline { | ||
| 279 | height: 1px; | ||
| 280 | } | ||
| 281 | |||
| 282 | .select2_multi_sameline+span.select2-container.select2-container--focus .select2-selection--multiple .select2-search--inline { | ||
| 283 | height: unset; | ||
| 284 | } | ||
| @@ -470,14 +470,6 @@ | |||
| 470 | margin-left: 20px; | 470 | margin-left: 20px; |
| 471 | } | 471 | } |
| 472 | 472 | ||
| 473 | /*used to fix smallness of certain FontAwesome glyph which break button squareness*/ | ||
| 474 | /*currently used on: CharList Import*/ | ||
| 475 | |||
| 476 | .faSmallFontSquareFix { | ||
| 477 | font-size: calc(var(--mainFontSize) *1.25); | ||
| 478 | width: calc(var(--mainFontSize) *1.95); | ||
| 479 | } | ||
| 480 | |||
| 481 | .textarea_compact { | 473 | .textarea_compact { |
| 482 | font-size: calc(var(--mainFontSize) * 0.95); | 474 | font-size: calc(var(--mainFontSize) * 0.95); |
| 483 | line-height: 1.2; | 475 | line-height: 1.2; |
| @@ -157,7 +157,9 @@ | |||
| 157 | flex-direction: row; | 157 | flex-direction: row; |
| 158 | flex-wrap: wrap; | 158 | flex-wrap: wrap; |
| 159 | align-items: flex-start; | 159 | align-items: flex-start; |
| 160 | margin: 5px; | 160 | margin-top: 5px; |
| 161 | max-height: 40dvh; | ||
| 162 | overflow-y: auto; | ||
| 161 | } | 163 | } |
| 162 | 164 | ||
| 163 | .rm_tag_filter .tag { | 165 | .rm_tag_filter .tag { |
| @@ -93,7 +93,8 @@ body.charListGrid #rm_print_characters_block .group_select .ch_name, | |||
| 93 | body.charListGrid #rm_print_characters_block .group_select .group_select_counter, | 93 | body.charListGrid #rm_print_characters_block .group_select .group_select_counter, |
| 94 | #user_avatar_block.gridView .avatar-container .ch_name, | 94 | #user_avatar_block.gridView .avatar-container .ch_name, |
| 95 | #user_avatar_block.gridView .avatar-container .bogus_folder_counter, | 95 | #user_avatar_block.gridView .avatar-container .bogus_folder_counter, |
| 96 | #user_avatar_block.gridView .avatar-container .group_select_counter { | 96 | #user_avatar_block.gridView .avatar-container .group_select_counter, |
| 97 | #user_avatar_block.gridView .avatar-container .ch_additional_info { | ||
| 97 | width: 100%; | 98 | width: 100%; |
| 98 | max-width: 100px; | 99 | max-width: 100px; |
| 99 | text-align: center; | 100 | text-align: center; |
| @@ -107,7 +107,6 @@ | |||
| 107 | height: auto; | 107 | height: auto; |
| 108 | margin-top: 0; | 108 | margin-top: 0; |
| 109 | margin-bottom: 0; | 109 | margin-bottom: 0; |
| 110 | min-height: calc(var(--mainFontSize) + 14px); | ||
| 111 | } | 110 | } |
| 112 | 111 | ||
| 113 | .delete_entry_button { | 112 | .delete_entry_button { |
| @@ -286,3 +285,17 @@ select.keyselect+span.select2-container .select2-selection--multiple { | |||
| 286 | .world_entry:has(input[name="delay_until_recursion"]:not(:checked)) .world_entry_form_control:has(input[name="delayUntilRecursionLevel"]) { | 285 | .world_entry:has(input[name="delay_until_recursion"]:not(:checked)) .world_entry_form_control:has(input[name="delayUntilRecursionLevel"]) { |
| 287 | display: none; | 286 | display: none; |
| 288 | } | 287 | } |
| 288 | |||
| 289 | .world_entry label[for="__invisible"] { | ||
| 290 | visibility: hidden; | ||
| 291 | pointer-events: none; | ||
| 292 | opacity: 0; | ||
| 293 | width: 0; | ||
| 294 | overflow: hidden; | ||
| 295 | } | ||
| 296 | |||
| 297 | #WIMultiSelector .select2-container .select2-selection--multiple { | ||
| 298 | max-height: 25vh; | ||
| 299 | overflow-y: auto; | ||
| 300 | overflow-x: hidden; | ||
| 301 | } | ||
| @@ -73,4 +73,10 @@ declare global { | |||
| 73 | * @param args - The arguments for the conversion function. | 73 | * @param args - The arguments for the conversion function. |
| 74 | */ | 74 | */ |
| 75 | function convertVideoToAnimatedWebp(args: ConvertVideoArgs): Promise<Uint8Array>; | 75 | function convertVideoToAnimatedWebp(args: ConvertVideoArgs): Promise<Uint8Array>; |
| 76 | |||
| 77 | interface ColorPickerEvent extends JQuery.ChangeEvent<HTMLElement> { | ||
| 78 | detail: { | ||
| 79 | rgba: string; | ||
| 80 | }; | ||
| 81 | } | ||
| 76 | } | 82 | } |
| @@ -1,43 +1 @@ | |||
| 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | 1 | <svg xmlns="http://www.w3.org/2000/svg" width="734.222" height="756.943" viewBox="0 0 734.222 756.943"><path d="M357.038 52.288l-73.383 52.313-55.764 142.923-54.617 143.196c1.171.279 425.288-132.081 426.226-133.018C600.225 256.976 432.641.031 431.422 0c-.55-.014-34.023 23.515-74.384 52.288m181.711 32.471l-10.329 8.35c-.665.67 99.325 156.949 102.358 159.98 1.875 1.874 79.763 35.16 81.138 34.675 1.14-.403-160.629-210.253-162.275-210.505-.67-.103-5.572 3.272-10.892 7.5M57.422 239.973L.14 314.826c-1.188 1.714 3.842 4.638 68.856 40.032l71.106 37.861c1.12-.373 72.985-183.777 72.538-185.118-.294-.884-96.296-40.583-98.218-40.615-.55-.01-26.2 32.835-57 72.987m568.524 51.223l-91.43 199.175-88.086 193.416c.271.271.657.305.859.076s65.024-80.248 144.049-177.819l142.882-178.1c-.886-.775-103.789-42.998-104.792-42.998-.347 0-1.914 2.813-3.482 6.25m-243.024 63.521l-211.816 65.765c-.466.388 206.21 336.458 206.917 336.461.728.004 217.289-466.962 216.767-467.41-.202-.174-95.543 29.159-211.868 65.184m-343 16.73c0 .732 221.624 257.149 222.982 257.988.56.346 1.018.38 1.018.076 0-.611-122.164-199.524-125.049-203.612-1.152-1.632-18.448-11.562-48.125-27.63l-50.826-26.822"/></svg> |
| 2 | <!-- Created with Inkscape (http://www.inkscape.org/) --> | ||
| 3 | |||
| 4 | <svg | ||
| 5 | width="190.91216mm" | ||
| 6 | height="201.47243mm" | ||
| 7 | viewBox="0 0 190.91216 201.47243" | ||
| 8 | version="1.1" | ||
| 9 | id="svg1" | ||
| 10 | xmlns="http://www.w3.org/2000/svg" | ||
| 11 | xmlns:svg="http://www.w3.org/2000/svg"> | ||
| 12 | <defs | ||
| 13 | id="defs1" /> | ||
| 14 | <g | ||
| 15 | id="layer1" | ||
| 16 | transform="translate(-2.2086436)"> | ||
| 17 | <g | ||
| 18 | id="g8" | ||
| 19 | transform="matrix(0.99583856,0,0,1.0472862,2.2086437,-7.9847956)"> | ||
| 20 | <path | ||
| 21 | style="font-weight:bold;font-size:89.3125px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';letter-spacing:-8.37308px;stroke-width:1.73283" | ||
| 22 | d="m 161.17775,117.85719 v 23.36554 q 0,20.76935 -10.30578,39.77331 -10.21695,19.00395 -29.31819,19.00395 -11.01653,0 -18.65701,-5.60771 -7.551611,-5.71157 -14.392485,-20.8732 -6.752041,-15.26549 -6.752041,-32.29635 v -23.36554 q 0,-20.665499 10.216893,-39.669461 10.216893,-19.107806 29.318113,-19.107806 10.92769,0 18.47935,5.607725 7.64049,5.607727 14.48139,20.873203 6.92976,15.161629 6.92976,32.296339 z m -18.30165,0 q 0,-17.3424 -8.26241,-30.32325 -4.70867,-7.165428 -13.2376,-7.165428 -8.26239,0 -13.50413,7.892355 -7.906998,11.942383 -7.906998,29.596323 v 23.36554 q 0,17.44626 8.351218,30.32326 4.61983,7.16543 13.2376,7.16543 8.17356,0 13.4153,-7.89237 7.90702,-11.94237 7.90702,-29.59632 z" | ||
| 23 | id="path8" /> | ||
| 24 | <path | ||
| 25 | style="font-weight:bold;font-size:89.3125px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';letter-spacing:-8.37308px;stroke-width:1.67461" | ||
| 26 | d="M 54.800839,58.349248 V 166.67311 H 73.27526 q 6.604382,0 9.371083,2.79982 2.855949,2.70327 2.855949,7.14436 0,4.34453 -2.855949,7.14435 -2.766701,2.70327 -9.371083,2.70327 H 18.030494 q -6.604382,0 -9.4603308,-2.70327 -2.7667007,-2.79982 -2.7667007,-7.2409 0,-4.34454 2.7667007,-7.04781 2.8559488,-2.79982 9.4603308,-2.79982 H 36.504915 V 84.126852 l -16.332459,4.634176 q -3.926929,1.158544 -5.890395,1.158544 -3.480687,0 -6.0688915,-2.992905 -2.4989554,-2.992906 -2.4989554,-7.337446 0,-3.958358 1.8742165,-6.178902 1.8742167,-2.317087 7.8538604,-4.054903 z" | ||
| 27 | transform="matrix(0.99545738,0,0,1.0756271,-5.6882567,-3.6821097)" | ||
| 28 | id="path7" /> | ||
| 29 | </g> | ||
| 30 | <g | ||
| 31 | id="g1" | ||
| 32 | transform="matrix(0.84984007,0,0,0.87855774,28.999003,5.2337341e-7)"> | ||
| 33 | <path | ||
| 34 | style="font-weight:bold;font-size:89.3125px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';letter-spacing:-8.37308px;stroke-width:1.73283" | ||
| 35 | d="m 184.13459,37.01017 q -3.34269,3.095908 -6.12102,4.409323 -2.77834,1.266508 -5.86056,1.266508 -7.1629,0 -12.54593,-6.191815 -5.33961,-6.191816 -5.33961,-15.385722 0,-7.176877 3.68997,-13.0403372 Q 162.99318,4.3096483e-6 172.15301,4.3096483e-6 q 6.55514,0 11.46064,4.2685995903517 4.90551,4.2216915 7.20632,12.2429061 2.30081,7.974308 2.30081,16.65223 0,14.729014 -10.549,23.641474 -8.16137,6.848524 -17.88555,6.848524 -5.38303,0 -8.33501,-2.345385 -1.99693,-1.547954 -1.99693,-4.080969 0,-2.063938 1.25893,-3.424261 1.30235,-1.407231 3.16905,-1.407231 1.04187,0 2.34422,0.703615 1.69304,0.938154 3.55974,0.938154 6.72879,0 12.4157,-4.925307 5.68691,-4.972215 7.03266,-12.102184 z m -1.25893,-15.151183 q -1.43258,-6.660892 -4.25433,-9.428446 -2.77834,-2.814461 -6.38149,-2.814461 -3.77681,0 -6.42491,3.142816 -2.6481,3.095907 -2.6481,8.255753 0,5.394384 2.6481,8.72483 2.69151,3.330446 6.12102,3.330446 6.03421,0 10.93971,-11.210938 z" | ||
| 36 | id="path6" /> | ||
| 37 | <path | ||
| 38 | style="font-weight:bold;font-size:89.3125px;font-family:'Courier New';-inkscape-font-specification:'Courier New Bold';letter-spacing:-8.37308px;stroke-width:1.51638" | ||
| 39 | d="m 142.10195,39.730816 h -22.30654 q -3.49058,0 -4.15545,-0.234538 -0.99731,-0.375262 -1.69543,-1.641769 -0.69812,-1.313415 -0.69812,-2.955185 0,-1.594861 0.69812,-2.908276 0.69812,-1.313416 1.72867,-1.641769 0.66488,-0.234539 4.12221,-0.234539 h 22.30654 q 3.49059,0 4.15545,0.234539 0.99732,0.375261 1.69543,1.688676 0.69812,1.266508 0.69812,2.861369 0,1.64177 -0.69812,2.955185 -0.69811,1.266507 -1.72867,1.641769 -0.66487,0.234538 -4.12221,0.234538 z" | ||
| 40 | id="text2" /> | ||
| 41 | </g> | ||
| 42 | </g> | ||
| 43 | </svg> | ||
| @@ -300,7 +300,7 @@ | |||
| 300 | </label> | 300 | </label> |
| 301 | </div> | 301 | </div> |
| 302 | </div> | 302 | </div> |
| 303 | <small data-tg-type="openrouter"> | 303 | <small id="prompt_cost_block" data-tg-type="openrouter"> |
| 304 | <span data-i18n="Max prompt cost:">Max prompt cost:</span> <span id="or_prompt_cost">–</span> | 304 | <span data-i18n="Max prompt cost:">Max prompt cost:</span> <span id="or_prompt_cost">–</span> |
| 305 | </small> | 305 | </small> |
| 306 | </div> | 306 | </div> |
| @@ -691,7 +691,7 @@ | |||
| 691 | </span> | 691 | </span> |
| 692 | </div> | 692 | </div> |
| 693 | </div> | 693 | </div> |
| 694 | <div class="range-block" data-source="openai,claude,windowai,aimlapi,openrouter,ai21,scale,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek,xai,pollinations"> | 694 | <div class="range-block" data-source="openai,claude,aimlapi,openrouter,ai21,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek,xai,pollinations"> |
| 695 | <div class="range-block-title" data-i18n="Temperature"> | 695 | <div class="range-block-title" data-i18n="Temperature"> |
| 696 | Temperature | 696 | Temperature |
| 697 | </div> | 697 | </div> |
| @@ -743,7 +743,7 @@ | |||
| 743 | </div> | 743 | </div> |
| 744 | </div> | 744 | </div> |
| 745 | </div> | 745 | </div> |
| 746 | <div class="range-block" data-source="openai,claude,aimlapi,openrouter,ai21,scale,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek,xai,pollinations"> | 746 | <div class="range-block" data-source="openai,claude,aimlapi,openrouter,ai21,makersuite,vertexai,mistralai,custom,cohere,perplexity,groq,01ai,nanogpt,deepseek,xai,pollinations"> |
| 747 | <div class="range-block-title" data-i18n="Top P"> | 747 | <div class="range-block-title" data-i18n="Top P"> |
| 748 | Top P | 748 | Top P |
| 749 | </div> | 749 | </div> |
| @@ -1314,7 +1314,7 @@ | |||
| 1314 | <input class="neo-range-slider" type="range" id="epsilon_cutoff_textgenerationwebui" name="volume" min="0" max="9" step="0.01"> | 1314 | <input class="neo-range-slider" type="range" id="epsilon_cutoff_textgenerationwebui" name="volume" min="0" max="9" step="0.01"> |
| 1315 | <input class="neo-range-input" type="number" min="0" max="9" step="0.01" data-for="epsilon_cutoff_textgenerationwebui" id="epsilon_cutoff_counter_textgenerationwebui"> | 1315 | <input class="neo-range-input" type="number" min="0" max="9" step="0.01" data-for="epsilon_cutoff_textgenerationwebui" id="epsilon_cutoff_counter_textgenerationwebui"> |
| 1316 | </div> | 1316 | </div> |
| 1317 | <div data-tg-type="aphrodite,koboldcpp,llamacpp" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0"> | 1317 | <div data-tg-type="ooba,aphrodite,koboldcpp,llamacpp" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0"> |
| 1318 | <small> | 1318 | <small> |
| 1319 | <span data-i18n="Top nsigma">Top nsigma</span> | 1319 | <span data-i18n="Top nsigma">Top nsigma</span> |
| 1320 | <div class="fa-solid fa-circle-info opacity50p" title="A sampling method that filters logits based on their statistical properties. It keeps tokens within n standard deviations of the maximum logit value, providing a simpler alternative to top-p/top-k sampling while maintaining sampling stability across different temperatures."></div> | 1320 | <div class="fa-solid fa-circle-info opacity50p" title="A sampling method that filters logits based on their statistical properties. It keeps tokens within n standard deviations of the maximum logit value, providing a simpler alternative to top-p/top-k sampling while maintaining sampling stability across different temperatures."></div> |
| @@ -1499,7 +1499,7 @@ | |||
| 1499 | </div> | 1499 | </div> |
| 1500 | </div> | 1500 | </div> |
| 1501 | </div> | 1501 | </div> |
| 1502 | <div data-tg-type="ooba,infermaticai,koboldcpp,llamacpp,ollama,tabby" id="mirostat_block_ooba" class="wide100p"> | 1502 | <div data-tg-type="ooba,infermaticai,koboldcpp,llamacpp,tabby" id="mirostat_block_ooba" class="wide100p"> |
| 1503 | <h4 class="wide100p textAlignCenter"> | 1503 | <h4 class="wide100p textAlignCenter"> |
| 1504 | <label data-i18n="Mirostat (mode=1 is only for llama.cpp)">Mirostat</label> | 1504 | <label data-i18n="Mirostat (mode=1 is only for llama.cpp)">Mirostat</label> |
| 1505 | <div class=" fa-solid fa-circle-info opacity50p " data-i18n="[title]Mirostat_desc" title="Mirostat is a thermostat for output perplexity. Mirostat matches the output perplexity to that of the input, thus avoiding the repetition trap (where, as the autoregressive inference produces text, the perplexity of the output tends toward zero) and the confusion trap (where the perplexity diverges). For details, see the paper Mirostat: A Neural Text Decoding Algorithm that Directly Controls Perplexity by Basu et al. (2020). Mode chooses the Mirostat version. 0=disable, 1=Mirostat 1.0 (llama.cpp only), 2=Mirostat 2.0."></div> | 1505 | <div class=" fa-solid fa-circle-info opacity50p " data-i18n="[title]Mirostat_desc" title="Mirostat is a thermostat for output perplexity. Mirostat matches the output perplexity to that of the input, thus avoiding the repetition trap (where, as the autoregressive inference produces text, the perplexity of the output tends toward zero) and the confusion trap (where the perplexity diverges). For details, see the paper Mirostat: A Neural Text Decoding Algorithm that Directly Controls Perplexity by Basu et al. (2020). Mode chooses the Mirostat version. 0=disable, 1=Mirostat 1.0 (llama.cpp only), 2=Mirostat 2.0."></div> |
| @@ -1811,6 +1811,7 @@ | |||
| 1811 | <div data-name="temperature" draggable="true"><span>Temperature</span><small></small></div> | 1811 | <div data-name="temperature" draggable="true"><span>Temperature</span><small></small></div> |
| 1812 | <div data-name="dynamic_temperature" draggable="true"><span>Dynamic Temperature</span><small></small></div> | 1812 | <div data-name="dynamic_temperature" draggable="true"><span>Dynamic Temperature</span><small></small></div> |
| 1813 | <div data-name="quadratic_sampling" draggable="true"><span>Quadratic / Smooth Sampling</span><small></small></div> | 1813 | <div data-name="quadratic_sampling" draggable="true"><span>Quadratic / Smooth Sampling</span><small></small></div> |
| 1814 | <div data-name="top_n_sigma" draggable="true"><span>Top Nsigma</span><small></small></div> | ||
| 1814 | <div data-name="top_k" draggable="true"><span>Top K</span><small></small></div> | 1815 | <div data-name="top_k" draggable="true"><span>Top K</span><small></small></div> |
| 1815 | <div data-name="top_p" draggable="true"><span>Top P</span><small></small></div> | 1816 | <div data-name="top_p" draggable="true"><span>Top P</span><small></small></div> |
| 1816 | <div data-name="typical_p" draggable="true"><span>Typical P</span><small></small></div> | 1817 | <div data-name="typical_p" draggable="true"><span>Typical P</span><small></small></div> |
| @@ -1969,7 +1970,7 @@ | |||
| 1969 | </span> | 1970 | </span> |
| 1970 | </div> | 1971 | </div> |
| 1971 | </div> | 1972 | </div> |
| 1972 | <div class="range-block" data-source="makersuite,vertexai,aimlapi,openrouter,claude,xai"> | 1973 | <div class="range-block" data-source="makersuite,vertexai,aimlapi,openrouter,claude,xai,nanogpt"> |
| 1973 | <label for="openai_enable_web_search" class="checkbox_label flexWrap widthFreeExpand"> | 1974 | <label for="openai_enable_web_search" class="checkbox_label flexWrap widthFreeExpand"> |
| 1974 | <input id="openai_enable_web_search" type="checkbox" /> | 1975 | <input id="openai_enable_web_search" type="checkbox" /> |
| 1975 | <span data-i18n="Enable web search">Enable web search</span> | 1976 | <span data-i18n="Enable web search">Enable web search</span> |
| @@ -2078,7 +2079,7 @@ | |||
| 2078 | </span> | 2079 | </span> |
| 2079 | </div> | 2080 | </div> |
| 2080 | </div> | 2081 | </div> |
| 2081 | <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude,xai,makersuite,vertexai,aimlapi,openrouter,pollinations"> | 2082 | <div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude,xai,makersuite,vertexai,aimlapi,openrouter,pollinations,perplexity"> |
| 2082 | <div class="flex-container oneline-dropdown" title="Constrains effort on reasoning for reasoning models. Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response." data-i18n="[title]Constrains effort on reasoning for reasoning models."> | 2083 | <div class="flex-container oneline-dropdown" title="Constrains effort on reasoning for reasoning models. Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response." data-i18n="[title]Constrains effort on reasoning for reasoning models."> |
| 2083 | <label for="openai_reasoning_effort"> | 2084 | <label for="openai_reasoning_effort"> |
| 2084 | <span data-i18n="Reasoning Effort">Reasoning Effort</span> | 2085 | <span data-i18n="Reasoning Effort">Reasoning Effort</span> |
| @@ -2092,7 +2093,7 @@ | |||
| 2092 | <option data-i18n="openai_reasoning_effort_high" value="high">High</option> | 2093 | <option data-i18n="openai_reasoning_effort_high" value="high">High</option> |
| 2093 | <option data-i18n="openai_reasoning_effort_maximum" value="max">Maximum</option> | 2094 | <option data-i18n="openai_reasoning_effort_maximum" value="max">Maximum</option> |
| 2094 | </select> | 2095 | </select> |
| 2095 | <div class="toggle-description justifyLeft marginBot5" data-source="openai,custom,xai,aimlapi,openrouter" data-i18n="OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level."> | 2096 | <div class="toggle-description justifyLeft marginBot5" data-source="openai,custom,xai,aimlapi,openrouter,perplexity" data-i18n="OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level."> |
| 2096 | OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level. | 2097 | OpenAI-style options: low, medium, high. Minimum and maximum are aliased to low and high. Auto does not send an effort level. |
| 2097 | </div> | 2098 | </div> |
| 2098 | <div class="toggle-description justifyLeft marginBot5" data-source="claude" data-i18n="Allocates a portion of the response length for thinking (min: 1024 tokens, low: 10%, medium: 25%, high: 50%, max: 95%), but minimum 1024 tokens. Auto does not request thinking."> | 2099 | <div class="toggle-description justifyLeft marginBot5" data-source="claude" data-i18n="Allocates a portion of the response length for thinking (min: 1024 tokens, low: 10%, medium: 25%, high: 50%, max: 95%), but minimum 1024 tokens. Auto does not request thinking."> |
| @@ -2131,7 +2132,7 @@ | |||
| 2131 | </div> | 2132 | </div> |
| 2132 | </div> | 2133 | </div> |
| 2133 | </div> | 2134 | </div> |
| 2134 | <div class="range-block m-t-1" data-source="openai,aimlapi,openrouter,scale,custom"> | 2135 | <div class="range-block m-t-1" data-source="openai,aimlapi,openrouter,custom"> |
| 2135 | <div id="logit_bias_openai" class="range-block-title openai_restorable" data-i18n="Logit Bias"> | 2136 | <div id="logit_bias_openai" class="range-block-title openai_restorable" data-i18n="Logit Bias"> |
| 2136 | Logit Bias | 2137 | Logit Bias |
| 2137 | </div> | 2138 | </div> |
| @@ -2801,8 +2802,6 @@ | |||
| 2801 | <option value="openrouter">OpenRouter</option> | 2802 | <option value="openrouter">OpenRouter</option> |
| 2802 | <option value="perplexity">Perplexity</option> | 2803 | <option value="perplexity">Perplexity</option> |
| 2803 | <option value="pollinations">Pollinations</option> | 2804 | <option value="pollinations">Pollinations</option> |
| 2804 | <option value="scale">Scale</option> | ||
| 2805 | <option value="windowai">Window AI</option> | ||
| 2806 | <option value="xai">xAI (Grok)</option> | 2805 | <option value="xai">xAI (Grok)</option> |
| 2807 | </optgroup> | 2806 | </optgroup> |
| 2808 | </select> | 2807 | </select> |
| @@ -3022,34 +3021,11 @@ | |||
| 3022 | <option value="claude-3-5-haiku-latest">claude-3-5-haiku-latest</option> | 3021 | <option value="claude-3-5-haiku-latest">claude-3-5-haiku-latest</option> |
| 3023 | <option value="claude-3-5-haiku-20241022">claude-3-5-haiku-20241022</option> | 3022 | <option value="claude-3-5-haiku-20241022">claude-3-5-haiku-20241022</option> |
| 3024 | <option value="claude-3-opus-20240229">claude-3-opus-20240229</option> | 3023 | <option value="claude-3-opus-20240229">claude-3-opus-20240229</option> |
| 3025 | <option value="claude-3-sonnet-20240229">claude-3-sonnet-20240229</option> | ||
| 3026 | <option value="claude-3-haiku-20240307">claude-3-haiku-20240307</option> | 3024 | <option value="claude-3-haiku-20240307">claude-3-haiku-20240307</option> |
| 3027 | <option value="claude-2.1">claude-2.1</option> | ||
| 3028 | <option value="claude-2.0">claude-2.0</option> | ||
| 3029 | </optgroup> | 3025 | </optgroup> |
| 3030 | </select> | 3026 | </select> |
| 3031 | </div> | 3027 | </div> |
| 3032 | </form> | 3028 | </form> |
| 3033 | <form id="windowai_form" data-source="windowai" action="javascript:void(null);" method="post" enctype="multipart/form-data"> | ||
| 3034 | <div> | ||
| 3035 | <h4 data-i18n="Window AI Model">Window AI Model</h4> | ||
| 3036 | <select id="model_windowai_select"> | ||
| 3037 | <option data-i18n="Use extension settings" value="">Use extension settings</option> | ||
| 3038 | <option value="openai/gpt-3.5-turbo">openai/gpt-3.5-turbo</option> | ||
| 3039 | <option value="openai/gpt-3.5-turbo-16k">openai/gpt-3.5-turbo-16k</option> | ||
| 3040 | <option value="openai/gpt-4">openai/gpt-4</option> | ||
| 3041 | <option value="openai/gpt-4-32k">openai/gpt-4-32k</option> | ||
| 3042 | <option value="anthropic/claude-instant-v1">anthropic/claude-instant-v1</option> | ||
| 3043 | <option value="anthropic/claude-instant-v1-100k">anthropic/claude-instant-v1-100k</option> | ||
| 3044 | <option value="anthropic/claude-v1">anthropic/claude-v1</option> | ||
| 3045 | <option value="anthropic/claude-v1-100k">anthropic/claude-v1-100k</option> | ||
| 3046 | <option value="google/palm-2-chat-bison">google/palm-2-chat-bison</option> | ||
| 3047 | <option value="google/palm-2-codechat-bison">google/palm-2-codechat-bison</option> | ||
| 3048 | <option value="togethercomputer/GPT-NeoXT-Chat-Base-20B">togethercomputer/GPT-NeoXT-Chat-Base-20B</option> | ||
| 3049 | <option value="cohere/command-nightly">cohere/command-nightly</option> | ||
| 3050 | </select> | ||
| 3051 | </div> | ||
| 3052 | </form> | ||
| 3053 | <form id="openrouter_form" data-source="openrouter" action="javascript:void(null);" method="post" enctype="multipart/form-data"> | 3029 | <form id="openrouter_form" data-source="openrouter" action="javascript:void(null);" method="post" enctype="multipart/form-data"> |
| 3054 | <h4 data-i18n="OpenRouter API Key">OpenRouter API Key</h4> | 3030 | <h4 data-i18n="OpenRouter API Key">OpenRouter API Key</h4> |
| 3055 | <div> | 3031 | <div> |
| @@ -3119,36 +3095,6 @@ | |||
| 3119 | <span data-i18n="To use instruct formatting, switch to OpenRouter under Text Completion API.">To use instruct formatting, switch to OpenRouter under Text Completion API.</span> | 3095 | <span data-i18n="To use instruct formatting, switch to OpenRouter under Text Completion API.">To use instruct formatting, switch to OpenRouter under Text Completion API.</span> |
| 3120 | </small> | 3096 | </small> |
| 3121 | </form> | 3097 | </form> |
| 3122 | <form id="scale_form" data-source="scale" action="javascript:void(null);" method="post" enctype="multipart/form-data"> | ||
| 3123 | <div id="normal_scale_form"> | ||
| 3124 | <h4 data-i18n="Scale API Key">Scale API Key</h4> | ||
| 3125 | <div class="flex-container"> | ||
| 3126 | <input id="api_key_scale" name="api_key_scale" class="text_pole flex1" value="" autocomplete="off"> | ||
| 3127 | <div title="Manage API keys" data-i18n="[title]Manage API keys" class="menu_button fa-solid fa-key fa-fw manage-api-keys" data-key="api_key_scale"></div> | ||
| 3128 | </div> | ||
| 3129 | <div data-for="api_key_scale" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'."> | ||
| 3130 | For privacy reasons, your API key will be hidden after you click 'Connect'. | ||
| 3131 | </div> | ||
| 3132 | <h4>Scale API URL</h4> | ||
| 3133 | <input id="api_url_scale" name="api_url_scale" class="text_pole" value="" autocomplete="off" placeholder="https://dashboard.scale.com/spellbook/api/v2/deploy/xxxxxxx"> | ||
| 3134 | </div> | ||
| 3135 | <div id="alt_scale_form"> | ||
| 3136 | <h4>Scale Cookie (_jwt)</h4> | ||
| 3137 | <div class="flex-container"> | ||
| 3138 | <input id="scale_cookie" name="scale_cookie" class="text_pole flex1" value="" autocomplete="off"> | ||
| 3139 | <div title="Clear your cookie" data-i18n="[title]Clear your cookie" class="menu_button fa-solid fa-key fa-fw manage-api-keys" data-key="scale_cookie"></div> | ||
| 3140 | </div> | ||
| 3141 | <div data-for="scale_cookie" class="neutral_warning"> | ||
| 3142 | For privacy reasons, your cookie will be hidden after you click 'Connect'. | ||
| 3143 | </div> | ||
| 3144 | </div> | ||
| 3145 | <!-- Its only purpose is to trigger max context size check --> | ||
| 3146 | <select id="model_scale_select" class="displayNone"></select> | ||
| 3147 | <label for="scale-alt" class="checkbox_label"> | ||
| 3148 | <input id="scale-alt" type="checkbox" checked> | ||
| 3149 | <span data-i18n="Alt Method">Alt Method</span> | ||
| 3150 | </label> | ||
| 3151 | </form> | ||
| 3152 | <form id="ai21_form" data-source="ai21" action="javascript:void(null);" method="post" enctype="multipart/form-data"> | 3098 | <form id="ai21_form" data-source="ai21" action="javascript:void(null);" method="post" enctype="multipart/form-data"> |
| 3153 | <h4 data-i18n="AI21 API Key">AI21 API Key</h4> | 3099 | <h4 data-i18n="AI21 API Key">AI21 API Key</h4> |
| 3154 | <div class="flex-container"> | 3100 | <div class="flex-container"> |
| @@ -3165,7 +3111,11 @@ | |||
| 3165 | <option value="jamba-mini">jamba-mini</option> | 3111 | <option value="jamba-mini">jamba-mini</option> |
| 3166 | <option value="jamba-large">jamba-large</option> | 3112 | <option value="jamba-large">jamba-large</option> |
| 3167 | </optgroup> | 3113 | </optgroup> |
| 3168 | <optgroup label="Jamba 1.6"> | 3114 | <optgroup label="Jamba 1.7"> |
| 3115 | <option value="jamba-1.7-mini">jamba-1.7-mini</option> | ||
| 3116 | <option value="jamba-1.7-large">jamba-1.7-large</option> | ||
| 3117 | </optgroup> | ||
| 3118 | <optgroup label="Jamba 1.6 (Deprecated)"> | ||
| 3169 | <option value="jamba-1.6-mini">jamba-1.6-mini</option> | 3119 | <option value="jamba-1.6-mini">jamba-1.6-mini</option> |
| 3170 | <option value="jamba-1.6-large">jamba-1.6-large</option> | 3120 | <option value="jamba-1.6-large">jamba-1.6-large</option> |
| 3171 | </optgroup> | 3121 | </optgroup> |
| @@ -3415,6 +3365,7 @@ | |||
| 3415 | <option value="pixtral-12b-latest">pixtral-12b-latest</option> | 3365 | <option value="pixtral-12b-latest">pixtral-12b-latest</option> |
| 3416 | <option value="pixtral-large-latest">pixtral-large-latest</option> | 3366 | <option value="pixtral-large-latest">pixtral-large-latest</option> |
| 3417 | <option value="devstral-small-latest">devstral-small-latest</option> | 3367 | <option value="devstral-small-latest">devstral-small-latest</option> |
| 3368 | <option value="devstral-medium-latest">devstral-medium-latest</option> | ||
| 3418 | <option value="magistral-small-latest">magistral-small-latest</option> | 3369 | <option value="magistral-small-latest">magistral-small-latest</option> |
| 3419 | <option value="magistral-medium-latest">magistral-medium-latest</option> | 3370 | <option value="magistral-medium-latest">magistral-medium-latest</option> |
| 3420 | </optgroup> | 3371 | </optgroup> |
| @@ -3430,6 +3381,7 @@ | |||
| 3430 | <option value="mistral-small-2409">mistral-small-2409</option> | 3381 | <option value="mistral-small-2409">mistral-small-2409</option> |
| 3431 | <option value="mistral-small-2501">mistral-small-2501</option> | 3382 | <option value="mistral-small-2501">mistral-small-2501</option> |
| 3432 | <option value="mistral-small-2503">mistral-small-2503</option> | 3383 | <option value="mistral-small-2503">mistral-small-2503</option> |
| 3384 | <option value="mistral-small-2506">mistral-small-2506</option> | ||
| 3433 | <option value="mistral-medium-2312">mistral-medium-2312</option> | 3385 | <option value="mistral-medium-2312">mistral-medium-2312</option> |
| 3434 | <option value="mistral-medium-2505">mistral-medium-2505</option> | 3386 | <option value="mistral-medium-2505">mistral-medium-2505</option> |
| 3435 | <option value="mistral-large-2402">mistral-large-2402</option> | 3387 | <option value="mistral-large-2402">mistral-large-2402</option> |
| @@ -3446,8 +3398,12 @@ | |||
| 3446 | <option value="pixtral-12b-2409">pixtral-12b-2409</option> | 3398 | <option value="pixtral-12b-2409">pixtral-12b-2409</option> |
| 3447 | <option value="pixtral-large-2411">pixtral-large-2411</option> | 3399 | <option value="pixtral-large-2411">pixtral-large-2411</option> |
| 3448 | <option value="devstral-small-2505">devstral-small-2505</option> | 3400 | <option value="devstral-small-2505">devstral-small-2505</option> |
| 3401 | <option value="devstral-small-2507">devstral-small-2507</option> | ||
| 3402 | <option value="devstral-medium-2507">devstral-medium-2507</option> | ||
| 3449 | <option value="magistral-small-2506">magistral-small-2506</option> | 3403 | <option value="magistral-small-2506">magistral-small-2506</option> |
| 3450 | <option value="magistral-medium-2506">magistral-medium-2506</option> | 3404 | <option value="magistral-medium-2506">magistral-medium-2506</option> |
| 3405 | <option value="magistral-small-2507">magistral-small-2507</option> | ||
| 3406 | <option value="magistral-medium-2507">magistral-medium-2507</option> | ||
| 3451 | </optgroup> | 3407 | </optgroup> |
| 3452 | <optgroup id="mistralai_other_models" label="Other"></optgroup> | 3408 | <optgroup id="mistralai_other_models" label="Other"></optgroup> |
| 3453 | </select> | 3409 | </select> |
| @@ -3464,35 +3420,17 @@ | |||
| 3464 | </div> | 3420 | </div> |
| 3465 | <h4 data-i18n="Groq Model">Groq Model</h4> | 3421 | <h4 data-i18n="Groq Model">Groq Model</h4> |
| 3466 | <select id="model_groq_select"> | 3422 | <select id="model_groq_select"> |
| 3467 | <optgroup label="Alibaba Cloud"> | 3423 | <option value="qwen/qwen3-32b">qwen/qwen3-32b</option> |
| 3468 | <option value="qwen-2.5-32b">qwen-2.5-32b</option> | 3424 | <option value="deepseek-r1-distill-llama-70b">deepseek-r1-distill-llama-70b</option> |
| 3469 | <option value="qwen-2.5-coder-32b">qwen-2.5-coder-32b</option> | 3425 | <option value="gemma2-9b-it">gemma2-9b-it</option> |
| 3470 | </optgroup> | 3426 | <option value="meta-llama/llama-4-scout-17b-16e-instruct">meta-llama/llama-4-scout-17b-16e-instruct</option> |
| 3471 | <optgroup label="DeepSeek"> | 3427 | <option value="meta-llama/llama-4-maverick-17b-128e-instruct">meta-llama/llama-4-maverick-17b-128e-instruct</option> |
| 3472 | <option value="deepseek-r1-distill-qwen-32b">deepseek-r1-distill-qwen-32b</option> | 3428 | <option value="llama-3.1-8b-instant">llama-3.1-8b-instant</option> |
| 3473 | <option value="deepseek-r1-distill-llama-70b">deepseek-r1-distill-llama-70b</option> | 3429 | <option value="llama-3.3-70b-versatile">llama-3.3-70b-versatile </option> |
| 3474 | </optgroup> | 3430 | <option value="llama-guard-3-8b">llama-guard-3-8b</option> |
| 3475 | <optgroup label="Google"> | 3431 | <option value="llama3-70b-8192">llama3-70b-8192</option> |
| 3476 | <option value="gemma2-9b-it">gemma2-9b-it</option> | 3432 | <option value="llama3-8b-8192">llama3-8b-8192</option> |
| 3477 | </optgroup> | 3433 | <option value="mistral-saba-24b">mistral-saba-24b</option> |
| 3478 | <optgroup label="Meta"> | ||
| 3479 | <option value="meta-llama/llama-4-scout-17b-16e-instruct">meta-llama/llama-4-scout-17b-16e-instruct</option> | ||
| 3480 | <option value="meta-llama/llama-4-maverick-17b-128e-instruct">meta-llama/llama-4-maverick-17b-128e-instruct</option> | ||
| 3481 | <option value="llama-3.1-8b-instant">llama-3.1-8b-instant</option> | ||
| 3482 | <option value="llama-3.2-11b-vision-preview">llama-3.2-11b-vision-preview </option> | ||
| 3483 | <option value="llama-3.2-1b-preview">llama-3.2-1b-preview </option> | ||
| 3484 | <option value="llama-3.2-3b-preview">llama-3.2-3b-preview </option> | ||
| 3485 | <option value="llama-3.2-90b-vision-preview">llama-3.2-90b-vision-preview </option> | ||
| 3486 | <option value="llama-3.3-70b-specdec">llama-3.3-70b-specdec</option> | ||
| 3487 | <option value="llama-3.3-70b-versatile">llama-3.3-70b-versatile </option> | ||
| 3488 | <option value="llama-guard-3-8b">llama-guard-3-8b</option> | ||
| 3489 | <option value="llama3-70b-8192">llama3-70b-8192</option> | ||
| 3490 | <option value="llama3-8b-8192">llama3-8b-8192</option> | ||
| 3491 | </optgroup> | ||
| 3492 | <optgroup label="Mistral AI"> | ||
| 3493 | <option value="mistral-saba-24b">mistral-saba-24b</option> | ||
| 3494 | <option value="mixtral-8x7b-32768">mixtral-8x7b-32768</option> | ||
| 3495 | </optgroup> | ||
| 3496 | </select> | 3434 | </select> |
| 3497 | </div> | 3435 | </div> |
| 3498 | <div id="nanogpt_form" data-source="nanogpt"> | 3436 | <div id="nanogpt_form" data-source="nanogpt"> |
| @@ -3545,6 +3483,7 @@ | |||
| 3545 | <option value="sonar-pro">sonar-pro</option> | 3483 | <option value="sonar-pro">sonar-pro</option> |
| 3546 | <option value="sonar-reasoning">sonar-reasoning</option> | 3484 | <option value="sonar-reasoning">sonar-reasoning</option> |
| 3547 | <option value="sonar-reasoning-pro">sonar-reasoning-pro</option> | 3485 | <option value="sonar-reasoning-pro">sonar-reasoning-pro</option> |
| 3486 | <option value="sonar-deep-research">sonar-deep-research</option> | ||
| 3548 | </optgroup> | 3487 | </optgroup> |
| 3549 | <optgroup label="Offline Models"> | 3488 | <optgroup label="Offline Models"> |
| 3550 | <option value="r1-1776">r1-1776</option> | 3489 | <option value="r1-1776">r1-1776</option> |
| @@ -3659,6 +3598,7 @@ | |||
| 3659 | </div> | 3598 | </div> |
| 3660 | <h4 data-i18n="xAI Model">xAI Model</h4> | 3599 | <h4 data-i18n="xAI Model">xAI Model</h4> |
| 3661 | <select id="model_xai_select"> | 3600 | <select id="model_xai_select"> |
| 3601 | <option value="grok-4-0709">grok-4-0709</option> | ||
| 3662 | <option value="grok-3-beta">grok-3-beta</option> | 3602 | <option value="grok-3-beta">grok-3-beta</option> |
| 3663 | <option value="grok-3-fast-beta">grok-3-fast-beta</option> | 3603 | <option value="grok-3-fast-beta">grok-3-fast-beta</option> |
| 3664 | <option value="grok-3-mini-beta">grok-3-mini-beta</option> | 3604 | <option value="grok-3-mini-beta">grok-3-mini-beta</option> |
| @@ -3728,7 +3668,7 @@ | |||
| 3728 | <div class="api_loading menu_button menu_button_icon" data-i18n="Cancel">Cancel</div> | 3668 | <div class="api_loading menu_button menu_button_icon" data-i18n="Cancel">Cancel</div> |
| 3729 | <div data-source="custom" id="customize_additional_parameters" class="menu_button menu_button_icon" data-i18n="Additional Parameters">Additional Parameters</div> | 3669 | <div data-source="custom" id="customize_additional_parameters" class="menu_button menu_button_icon" data-i18n="Additional Parameters">Additional Parameters</div> |
| 3730 | <div data-source="openrouter" class="menu_button menu_button_icon openrouter_authorize" title="Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai" data-i18n="Authorize;[title]Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai">Authorize</div> | 3670 | <div data-source="openrouter" class="menu_button menu_button_icon openrouter_authorize" title="Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai" data-i18n="Authorize;[title]Get your OpenRouter API token using OAuth flow. You will be redirected to openrouter.ai">Authorize</div> |
| 3731 | <div id="test_api_button" class="menu_button menu_button_icon" title="Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!" data-i18n="[title]Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!"><span data-i18n="Test Message">Test Message</span></div> | 3671 | <div id="test_api_button" class="menu_button menu_button_icon" title="Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!" data-i18n="Test Message;[title]Verifies your API connection by sending a short test message. Be aware that you'll be credited for it!">Test Message</div> |
| 3732 | </div> | 3672 | </div> |
| 3733 | <div class="online_status"> | 3673 | <div class="online_status"> |
| 3734 | <div class="online_status_indicator"></div> | 3674 | <div class="online_status_indicator"></div> |
| @@ -4241,6 +4181,13 @@ | |||
| 4241 | </div> | 4181 | </div> |
| 4242 | <div> | 4182 | <div> |
| 4243 | <h4 class="standoutHeader" data-i18n="Miscellaneous">Miscellaneous</h4> | 4183 | <h4 class="standoutHeader" data-i18n="Miscellaneous">Miscellaneous</h4> |
| 4184 | <div name="bindModelPresetBlock"> | ||
| 4185 | <label for="bind_model_templates" class="checkbox_label"> | ||
| 4186 | <input id="bind_model_templates" type="checkbox" /> | ||
| 4187 | <small data-i18n="Bind Model to Templates">Bind Model to Templates</small> | ||
| 4188 | <span class="fa-solid fa-circle-question" data-i18n="[title]bind_model_templates_desc" title="When connecting to an API or choosing a model, automatically activate the current Instruct and Context templates if the model name or its chat template matches the currently loaded model."></span> | ||
| 4189 | </label> | ||
| 4190 | </div> | ||
| 4244 | <div> | 4191 | <div> |
| 4245 | <small> | 4192 | <small> |
| 4246 | <span data-i18n="Non-markdown strings"> | 4193 | <span data-i18n="Non-markdown strings"> |
| @@ -4307,7 +4254,7 @@ | |||
| 4307 | </span> | 4254 | </span> |
| 4308 | </div> | 4255 | </div> |
| 4309 | <div class="range-block-range"> | 4256 | <div class="range-block-range"> |
| 4310 | <select id="world_info" class="select2_multi_sameline" multiple> | 4257 | <select id="world_info" multiple> |
| 4311 | <option value="" data-i18n="-- World Info not found --">-- World Info not found -- </option> | 4258 | <option value="" data-i18n="-- World Info not found --">-- World Info not found -- </option> |
| 4312 | </select> | 4259 | </select> |
| 4313 | </div> | 4260 | </div> |
| @@ -4800,10 +4747,10 @@ | |||
| 4800 | <input id="never_resize_avatars" type="checkbox" /> | 4747 | <input id="never_resize_avatars" type="checkbox" /> |
| 4801 | <small data-i18n="Never resize avatars">Never resize avatars</small> | 4748 | <small data-i18n="Never resize avatars">Never resize avatars</small> |
| 4802 | </label> | 4749 | </label> |
| 4803 | <label data-i18n="[title]Allow animations for WEBP backgrounds. This is only a change for the selection menu." for="background_thumbnails_animation" class="checkbox_label" title="Allow animations for WEBP backgrounds. This is only a change for the selection menu."> | 4750 | <label data-i18n="[title]Allow animations for WEBP backgrounds. This is only a change for the selection menu." for="background_thumbnails_animation" class="checkbox_label" title="Allow animations for WEBP backgrounds. This is only a change for the selection menu."> |
| 4804 | <input id="background_thumbnails_animation" type="checkbox" /> | 4751 | <input id="background_thumbnails_animation" type="checkbox" /> |
| 4805 | <small data-i18n="Animated background thumbnails">Animated background thumbnails</small> | 4752 | <small data-i18n="Animated background thumbnails">Animated background thumbnails</small> |
| 4806 | </label> | 4753 | </label> |
| 4807 | <label class="checkbox_label" for="show_card_avatar_urls" title="Show actual file names on the disk, in the characters list display only." data-i18n="[title]Show actual file names on the disk, in the characters list display only"> | 4754 | <label class="checkbox_label" for="show_card_avatar_urls" title="Show actual file names on the disk, in the characters list display only." data-i18n="[title]Show actual file names on the disk, in the characters list display only"> |
| 4808 | <input id="show_card_avatar_urls" type="checkbox" /> | 4755 | <input id="show_card_avatar_urls" type="checkbox" /> |
| 4809 | <small data-i18n="Show avatar filenames">Show avatar filenames</small> | 4756 | <small data-i18n="Show avatar filenames">Show avatar filenames</small> |
| @@ -5522,7 +5469,6 @@ | |||
| 5522 | <div id="HotSwapWrapper" class="alignitemscenter flex-container margin0auto wide100p"> | 5469 | <div id="HotSwapWrapper" class="alignitemscenter flex-container margin0auto wide100p"> |
| 5523 | <div class="hotswap avatars_inline scroll-reset-container expander" data-i18n="[no_favs]Favorite characters to add them to HotSwaps" no_favs="Favorite characters to add them to HotSwaps"> | 5470 | <div class="hotswap avatars_inline scroll-reset-container expander" data-i18n="[no_favs]Favorite characters to add them to HotSwaps" no_favs="Favorite characters to add them to HotSwaps"> |
| 5524 | </div> | 5471 | </div> |
| 5525 | <hr class="wide100p"> | ||
| 5526 | </div> | 5472 | </div> |
| 5527 | </div> | 5473 | </div> |
| 5528 | 5474 | ||
| @@ -5638,19 +5584,26 @@ | |||
| 5638 | </div> | 5584 | </div> |
| 5639 | </div> | 5585 | </div> |
| 5640 | </div> | 5586 | </div> |
| 5641 | <hr> | 5587 | <div id="spoiler_free_desc" class="inline-drawer flex-container flexFlowColumn flexNoGap"> |
| 5642 | <div id="spoiler_free_desc" class="flex-container flexFlowColumn flex1 flexNoGap"> | 5588 | <div class="inline-drawer-toggle inline-drawer-header padding0 gap5px standoutHeader"> |
| 5643 | <div id="creators_notes_div" class="title_restorable flexGap5"> | 5589 | <div id="creators_notes_div" class="title_restorable flexGap5 wide100p "> |
| 5644 | <span class="flex1" data-i18n="Creator's Notes">Creator's Notes</span> | 5590 | <span class="flex1" data-i18n="Creator's Notes">Creator's Notes</span> |
| 5645 | <small id="creators_note_desc_hidden" data-i18n="Character details are hidden.">Character details are hidden.</small> | 5591 | <div id="creators_note_styles_button" class="margin0 menu_button fa-solid fa-palette fa-fw" title="Allow / Forbid the use of global styles for this character." data-i18n="[title]Allow / Forbid the use of global styles for this character."></div> |
| 5646 | <div id="creators_note_styles_button" class="margin0 menu_button fa-solid fa-palette fa-fw" title="Allow / Forbid the use of global styles for this character." data-i18n="[title]Allow / Forbid the use of global styles for this character."></div> | 5592 | <div id="spoiler_free_desc_button" class="margin0 menu_button fa-solid fa-eye fa-fw" title="Show / Hide Description and First Message" data-i18n="[title]Show / Hide Description and First Message"></div> |
| 5647 | <div id="spoiler_free_desc_button" class="margin0 menu_button fa-solid fa-eye fa-fw" title="Show / Hide Description and First Message" data-i18n="[title]Show / Hide Description and First Message"></div> | 5593 | </div> |
| 5594 | <div class="flex-container widthFitContent"> | ||
| 5595 | <div class="inline-drawer-icon fa-solid fa-circle-chevron-down down interactable"></div> | ||
| 5596 | </div> | ||
| 5597 | </div> | ||
| 5598 | <div class="inline-drawer-content"> | ||
| 5599 | <div id="creator_notes_spoiler"></div> | ||
| 5600 | <div id="creator_notes_empty" data-i18n="No Creator's Notes provided."> | ||
| 5601 | No Creator's Notes provided. | ||
| 5602 | </div> | ||
| 5648 | </div> | 5603 | </div> |
| 5649 | <div id="creator_notes_spoiler" class="flex1"></div> | ||
| 5650 | <!-- A button to show / hide description_div and description_textarea and first_message_div and firstmessage_textarea--> | ||
| 5651 | </div> | 5604 | </div> |
| 5605 | <small id="creators_note_desc_hidden" data-i18n="Character details are hidden.">Character details are hidden.</small> | ||
| 5652 | <div id="descriptionWrapper" class="flex-container flexFlowColumn flex1"> | 5606 | <div id="descriptionWrapper" class="flex-container flexFlowColumn flex1"> |
| 5653 | <hr> | ||
| 5654 | <div id="description_div" class="title_restorable"> | 5607 | <div id="description_div" class="title_restorable"> |
| 5655 | <div class="flex-container alignitemscenter"> | 5608 | <div class="flex-container alignitemscenter"> |
| 5656 | <span data-i18n="Character Description" class="mdhotkey_location">Description</span> | 5609 | <span data-i18n="Character Description" class="mdhotkey_location">Description</span> |
| @@ -5843,20 +5796,22 @@ | |||
| 5843 | </div> | 5796 | </div> |
| 5844 | <div id="rm_character_import" class="right_menu" style="display: none;"> | 5797 | <div id="rm_character_import" class="right_menu" style="display: none;"> |
| 5845 | <form id="form_import" action="javascript:void(null);" method="post" enctype="multipart/form-data"> | 5798 | <form id="form_import" action="javascript:void(null);" method="post" enctype="multipart/form-data"> |
| 5846 | <input multiple type="file" id="character_import_file" accept=".json, image/png, .yaml, .yml, .charx" name="avatar"> | 5799 | <input multiple type="file" id="character_import_file" accept=".json, image/png, .yaml, .yml, .charx, .byaf" name="avatar"> |
| 5847 | <input id="character_import_file_type" name="file_type" class="text_pole" value="" autocomplete="off"> | 5800 | <input id="character_import_file_type" name="file_type" class="text_pole" value="" autocomplete="off"> |
| 5848 | </form> | 5801 | </form> |
| 5849 | <input type="file" id="character_replace_file" accept=".json, image/png, .yaml, .yml, .charx" name="replace_avatar" hidden> | 5802 | <input type="file" id="character_replace_file" accept=".json, image/png, .yaml, .yml, .charx, .byaf" name="replace_avatar" hidden> |
| 5850 | </div> | 5803 | </div> |
| 5851 | <div name="Character List Panel" id="rm_characters_block" class="right_menu"> | 5804 | <div name="Character List Panel" id="rm_characters_block" class="right_menu"> |
| 5852 | <div id="charListFixedTop"> | 5805 | <div id="charListFixedTop"> |
| 5853 | <form id="form_character_search_form" action="javascript:void(null);"> | 5806 | <div id="rm_button_bar"> |
| 5854 | <div id="rm_button_create" title="Create New Character" data-i18n="[title]Create New Character" class="menu_button fa-solid fa-user-plus "></div> | 5807 | <div id="rm_button_create" title="Create New Character" data-i18n="[title]Create New Character" class="menu_button fa-solid fa-user-plus"></div> |
| 5855 | <div id="character_import_button" title="Import Character from File" data-i18n="[title]Import Character from File" class="menu_button fa-solid fa-file-import faSmallFontSquareFix"></div> | 5808 | <div id="character_import_button" title="Import Character from File" data-i18n="[title]Import Character from File" class="menu_button fa-solid fa-file-import"></div> |
| 5856 | <div id="external_import_button" title="Import content from external URL" data-i18n="[title]Import content from external URL" class="menu_button fa-solid fa-cloud-arrow-down faSmallFontSquareFix"></div> | 5809 | <div id="external_import_button" title="Import content from external URL" data-i18n="[title]Import content from external URL" class="menu_button fa-solid fa-cloud-arrow-down"></div> |
| 5857 | <div id="rm_button_group_chats" title="Create New Chat Group" data-i18n="[title]Create New Chat Group" class="menu_button fa-solid fa-users-gear "></div> | 5810 | <div id="rm_button_group_chats" title="Create New Chat Group" data-i18n="[title]Create New Chat Group" class="menu_button fa-solid fa-users-gear"></div> |
| 5858 | <input id="character_search_bar" class="text_pole width100p" type="search" data-i18n="[placeholder]Search..." placeholder="Search..." /> | 5811 | <div id="rm_buttons_container"> |
| 5859 | <select id="character_sort_order" title="Characters sorting order" data-i18n="[title]Characters sorting order"> | 5812 | <!-- Container for additional buttons added by extensions --> |
| 5813 | </div> | ||
| 5814 | <select id="character_sort_order" class="flex1 text_pole textarea_compact" title="Characters sorting order" data-i18n="[title]Characters sorting order"> | ||
| 5860 | <option data-field="search" data-order="desc" data-i18n="Search" hidden>Search</option> | 5815 | <option data-field="search" data-order="desc" data-i18n="Search" hidden>Search</option> |
| 5861 | <option data-field="name" data-order="asc" data-i18n="A-Z">A-Z</option> | 5816 | <option data-field="name" data-order="asc" data-i18n="A-Z">A-Z</option> |
| 5862 | <option data-field="name" data-order="desc" data-i18n="Z-A">Z-A</option> | 5817 | <option data-field="name" data-order="desc" data-i18n="Z-A">Z-A</option> |
| @@ -5870,12 +5825,15 @@ | |||
| 5870 | <option data-field="data_size" data-order="asc" data-i18n="Least tokens">Least tokens</option> | 5825 | <option data-field="data_size" data-order="asc" data-i18n="Least tokens">Least tokens</option> |
| 5871 | <option data-field="name" data-order="random" data-i18n="Random">Random</option> | 5826 | <option data-field="name" data-order="random" data-i18n="Random">Random</option> |
| 5872 | </select> | 5827 | </select> |
| 5873 | </form> | 5828 | <div id="rm_button_search" class="right_menu_button fa-fw fa-solid fa-search" title="Toggle search bar" data-i18n="[title]Toggle search bar"></div> |
| 5829 | </div> | ||
| 5830 | <div id="form_character_search_form"> | ||
| 5831 | <input id="character_search_bar" class="text_pole textarea_compact width100p" type="search" data-i18n="[placeholder]Search..." placeholder="Search..." /> | ||
| 5832 | </div> | ||
| 5874 | <div class="rm_tag_controls"> | 5833 | <div class="rm_tag_controls"> |
| 5875 | <div class="tags rm_tag_filter"></div> | 5834 | <div class="tags rm_tag_filter"></div> |
| 5876 | <div class="tags rm_tag_bogus_drilldown"></div> | 5835 | <div class="tags rm_tag_bogus_drilldown"></div> |
| 5877 | </div> | 5836 | </div> |
| 5878 | <hr> | ||
| 5879 | </div> | 5837 | </div> |
| 5880 | 5838 | ||
| 5881 | <div id="rm_print_characters_pagination"> | 5839 | <div id="rm_print_characters_pagination"> |
| @@ -5927,6 +5885,7 @@ | |||
| 5927 | <div class="flex-container wide100pLess70px character_select_container"> | 5885 | <div class="flex-container wide100pLess70px character_select_container"> |
| 5928 | <div class="wide100p character_name_block"> | 5886 | <div class="wide100p character_name_block"> |
| 5929 | <span class="ch_name flex1"></span> | 5887 | <span class="ch_name flex1"></span> |
| 5888 | <small class="ch_additional_info"></small> | ||
| 5930 | </div> | 5889 | </div> |
| 5931 | <div class="ch_description"></div> | 5890 | <div class="ch_description"></div> |
| 5932 | <div class="avatar_container_states buttons_block"> | 5891 | <div class="avatar_container_states buttons_block"> |
| @@ -6273,7 +6232,7 @@ | |||
| 6273 | </span> | 6232 | </span> |
| 6274 | </small> | 6233 | </small> |
| 6275 | <small class="textAlignCenter" data-i18n="Primary Keywords">Primary Keywords</small> | 6234 | <small class="textAlignCenter" data-i18n="Primary Keywords">Primary Keywords</small> |
| 6276 | <select class="keyprimaryselect keyselect select2_multi_sameline" name="key" data-i18n="[placeholder]Keywords or Regexes" placeholder="Keywords or Regexes" multiple="multiple"></select> | 6235 | <select class="keyprimaryselect keyselect" name="key" data-i18n="[placeholder]Keywords or Regexes" placeholder="Keywords or Regexes" multiple="multiple"></select> |
| 6277 | <textarea class="text_pole keyprimarytextpole mobile" name="key" rows="1" data-i18n="[placeholder]Comma separated list" placeholder="Comma separated list" style="display: none;"></textarea> | 6236 | <textarea class="text_pole keyprimarytextpole mobile" name="key" rows="1" data-i18n="[placeholder]Comma separated list" placeholder="Comma separated list" style="display: none;"></textarea> |
| 6278 | <button type="button" class="switch_input_type_icon" tabindex="-1" title="Switch to plaintext mode" data-i18n="[title]Switch to plaintext mode" data-icon-on="✨" data-icon-off="⌨️" data-tooltip-on="Switch to fancy mode" data-tooltip-off="Switch to plaintext mode"> | 6237 | <button type="button" class="switch_input_type_icon" tabindex="-1" title="Switch to plaintext mode" data-i18n="[title]Switch to plaintext mode" data-icon-on="✨" data-icon-off="⌨️" data-tooltip-on="Switch to fancy mode" data-tooltip-off="Switch to plaintext mode"> |
| 6279 | ⌨️ | 6238 | ⌨️ |
| @@ -6295,7 +6254,7 @@ | |||
| 6295 | </span> | 6254 | </span> |
| 6296 | </small> | 6255 | </small> |
| 6297 | <small class="textAlignCenter" data-i18n="Optional Filter">Optional Filter</small> | 6256 | <small class="textAlignCenter" data-i18n="Optional Filter">Optional Filter</small> |
| 6298 | <select class="keysecondaryselect keyselect select2_multi_sameline" name="keysecondary" data-i18n="[placeholder]Keywords or Regexes (ignored if empty)" placeholder="Keywords or Regexes (ignored if empty)" multiple="multiple"></select> | 6257 | <select class="keysecondaryselect keyselect" name="keysecondary" data-i18n="[placeholder]Keywords or Regexes (ignored if empty)" placeholder="Keywords or Regexes (ignored if empty)" multiple="multiple"></select> |
| 6299 | <textarea class="text_pole keysecondarytextpole mobile" name="keysecondary" rows="1" data-i18n="[placeholder]Comma separated list (ignored if empty)" placeholder="Comma separated list (ignored if empty)" style="display: none;"></textarea> | 6258 | <textarea class="text_pole keysecondarytextpole mobile" name="keysecondary" rows="1" data-i18n="[placeholder]Comma separated list (ignored if empty)" placeholder="Comma separated list (ignored if empty)" style="display: none;"></textarea> |
| 6300 | <button type="button" class="switch_input_type_icon" tabindex="-1" title="Switch to plaintext mode" data-i18n="[title]Switch to plaintext mode" data-icon-on="✨" data-icon-off="⌨️" data-tooltip-on="Switch to fancy mode" data-tooltip-off="Switch to plaintext mode"> | 6259 | <button type="button" class="switch_input_type_icon" tabindex="-1" title="Switch to plaintext mode" data-i18n="[title]Switch to plaintext mode" data-icon-on="✨" data-icon-off="⌨️" data-tooltip-on="Switch to fancy mode" data-tooltip-off="Switch to plaintext mode"> |
| 6301 | ⌨️ | 6260 | ⌨️ |
| @@ -6482,13 +6441,37 @@ | |||
| 6482 | </label> | 6441 | </label> |
| 6483 | </div> | 6442 | </div> |
| 6484 | <div class="range-block-range"> | 6443 | <div class="range-block-range"> |
| 6485 | <select name="characterFilter" class="select2_multi_sameline" multiple> | 6444 | <select name="characterFilter" multiple> |
| 6486 | <option value="" data-i18n="-- Characters not found --"> | 6445 | <option value="" data-i18n="-- Characters not found --"> |
| 6487 | -- Characters not found -- | 6446 | -- Characters not found -- |
| 6488 | </option> | 6447 | </option> |
| 6489 | </select> | 6448 | </select> |
| 6490 | </div> | 6449 | </div> |
| 6491 | </div> | 6450 | </div> |
| 6451 | <div class="flex4"> | ||
| 6452 | <div class="flex-container justifySpaceBetween"> | ||
| 6453 | <small> | ||
| 6454 | <span data-i18n="Filter to Generation Triggers"> | ||
| 6455 | Filter to Generation Triggers | ||
| 6456 | </span> | ||
| 6457 | </small> | ||
| 6458 | <!-- Not a real control. Used to make label heights even. --> | ||
| 6459 | <label class="checkbox_label" for="__invisible"> | ||
| 6460 | <input type="checkbox" name="__invisible"> | ||
| 6461 | <span><small> </small></span> | ||
| 6462 | </label> | ||
| 6463 | </div> | ||
| 6464 | <div class="range-block-range"> | ||
| 6465 | <select name="triggers" multiple> | ||
| 6466 | <option data-i18n="Normal" value="normal">Normal</option> | ||
| 6467 | <option data-i18n="Continue" value="continue">Continue</option> | ||
| 6468 | <option data-i18n="Impersonate" value="impersonate">Impersonate</option> | ||
| 6469 | <option data-i18n="Swipe" value="swipe">Swipe</option> | ||
| 6470 | <option data-i18n="Regenerate" value="regenerate">Regenerate</option> | ||
| 6471 | <option data-i18n="Quiet" value="quiet">Quiet</option> | ||
| 6472 | </select> | ||
| 6473 | </div> | ||
| 6474 | </div> | ||
| 6492 | </div> | 6475 | </div> |
| 6493 | <div name="WIEntryBottomControls" class="flex-container flex1 justifySpaceBetween world_entry_form_horizontal"> | 6476 | <div name="WIEntryBottomControls" class="flex-container flex1 justifySpaceBetween world_entry_form_horizontal"> |
| 6494 | <div class="flex-container flexFlowColumn flexNoGap wi-enter-footer-text"> | 6477 | <div class="flex-container flexFlowColumn flexNoGap wi-enter-footer-text"> |
| @@ -6719,6 +6702,22 @@ | |||
| 6719 | </select> | 6702 | </select> |
| 6720 | <div class="text_muted" data-i18n="To whom this message will be attributed.">To whom this message will be attributed.</div> | 6703 | <div class="text_muted" data-i18n="To whom this message will be attributed.">To whom this message will be attributed.</div> |
| 6721 | </div> | 6704 | </div> |
| 6705 | <div class="completion_prompt_manager_popup_entry_form_control flex1"> | ||
| 6706 | <label for="completion_prompt_manager_popup_entry_form_injection_trigger"> | ||
| 6707 | <span data-i18n="Triggers">Triggers</span> | ||
| 6708 | </label> | ||
| 6709 | <select id="completion_prompt_manager_popup_entry_form_injection_trigger" class="text_pole" name="injection_trigger" multiple> | ||
| 6710 | <option data-i18n="Normal" value="normal">Normal</option> | ||
| 6711 | <option data-i18n="Continue" value="continue">Continue</option> | ||
| 6712 | <option data-i18n="Impersonate" value="impersonate">Impersonate</option> | ||
| 6713 | <option data-i18n="Swipe" value="swipe">Swipe</option> | ||
| 6714 | <option data-i18n="Regenerate" value="regenerate">Regenerate</option> | ||
| 6715 | <option data-i18n="Quiet" value="quiet">Quiet</option> | ||
| 6716 | </select> | ||
| 6717 | <div class="text_muted" data-i18n="Filter to specific generation types."> | ||
| 6718 | Filter to specific generation types. | ||
| 6719 | </div> | ||
| 6720 | </div> | ||
| 6722 | </div> | 6721 | </div> |
| 6723 | <div class="flex-container gap10px"> | 6722 | <div class="flex-container gap10px"> |
| 6724 | <div class="completion_prompt_manager_popup_entry_form_control flex1"> | 6723 | <div class="completion_prompt_manager_popup_entry_form_control flex1"> |
| @@ -7066,6 +7065,16 @@ | |||
| 7066 | <div class="right_menu_button mes_file_delete fa-solid fa-trash-can" title="Remove the file" data-i18n="[title]Remove the file"></div> | 7065 | <div class="right_menu_button mes_file_delete fa-solid fa-trash-can" title="Remove the file" data-i18n="[title]Remove the file"></div> |
| 7067 | </div> | 7066 | </div> |
| 7068 | </div> | 7067 | </div> |
| 7068 | |||
| 7069 | <div id="message_video_template" class="template_element"> | ||
| 7070 | <div class="mes_video_container"> | ||
| 7071 | <div class="mes_video_controls"> | ||
| 7072 | <div><!-- Placeholder --></div> | ||
| 7073 | <div title="Delete" class="right_menu_button fa-lg fa-solid fa-trash-can mes_video_delete" data-i18n="[title]Delete"></div> | ||
| 7074 | </div> | ||
| 7075 | <video class="mes_video" controls preload="metadata"></video> | ||
| 7076 | </div> | ||
| 7077 | </div> | ||
| 7069 | </div> | 7078 | </div> |
| 7070 | <div id="movingDivs"> | 7079 | <div id="movingDivs"> |
| 7071 | <div id="floatingPrompt" class="drawer-content flexGap5"> | 7080 | <div id="floatingPrompt" class="drawer-content flexGap5"> |
| @@ -21,6 +21,7 @@ import droll from 'droll'; | |||
| 21 | import morphdom from 'morphdom'; | 21 | import morphdom from 'morphdom'; |
| 22 | import { toggle as slideToggle } from 'slidetoggle'; | 22 | import { toggle as slideToggle } from 'slidetoggle'; |
| 23 | import chalk from 'chalk'; | 23 | import chalk from 'chalk'; |
| 24 | import yaml from 'yaml'; | ||
| 24 | 25 | ||
| 25 | /** | 26 | /** |
| 26 | * Expose the libraries to the 'window' object. | 27 | * Expose the libraries to the 'window' object. |
| @@ -98,6 +99,7 @@ export default { | |||
| 98 | morphdom, | 99 | morphdom, |
| 99 | slideToggle, | 100 | slideToggle, |
| 100 | chalk, | 101 | chalk, |
| 102 | yaml, | ||
| 101 | }; | 103 | }; |
| 102 | 104 | ||
| 103 | export { | 105 | export { |
| @@ -121,4 +123,5 @@ export { | |||
| 121 | morphdom, | 123 | morphdom, |
| 122 | slideToggle, | 124 | slideToggle, |
| 123 | chalk, | 125 | chalk, |
| 126 | yaml, | ||
| 124 | }; | 127 | }; |
| @@ -375,9 +375,6 @@ | |||
| 375 | "Group by vendors Description": "ضع نماذج OpenAI في مجموعة واحدة، والنماذج الإنسانية في مجموعة أخرى، وما إلى ذلك. ويمكن دمجها مع الفرز.", | 375 | "Group by vendors Description": "ضع نماذج OpenAI في مجموعة واحدة، والنماذج الإنسانية في مجموعة أخرى، وما إلى ذلك. ويمكن دمجها مع الفرز.", |
| 376 | "Allow fallback routes": "السماح بمسارات الاحتياط", | 376 | "Allow fallback routes": "السماح بمسارات الاحتياط", |
| 377 | "Allow fallback routes Description": "يختار النموذج البديل تلقائيًا إذا كان النموذج المحدد غير قادر على تلبية طلبك.", | 377 | "Allow fallback routes Description": "يختار النموذج البديل تلقائيًا إذا كان النموذج المحدد غير قادر على تلبية طلبك.", |
| 378 | "Scale API Key": "مفتاح API لـ Scale", | ||
| 379 | "Clear your cookie": "امسح ملف تعريف الارتباط الخاص بك", | ||
| 380 | "Alt Method": "طريقة بديلة", | ||
| 381 | "AI21 API Key": "مفتاح API لـ AI21", | 378 | "AI21 API Key": "مفتاح API لـ AI21", |
| 382 | "AI21 Model": "نموذج AI21", | 379 | "AI21 Model": "نموذج AI21", |
| 383 | "Google AI Studio API Key": "مفتاح واجهة برمجة تطبيقات Google AI Studio", | 380 | "Google AI Studio API Key": "مفتاح واجهة برمجة تطبيقات Google AI Studio", |
| @@ -1380,6 +1377,7 @@ | |||
| 1380 | "char_import_7": "للمضيفين المسموح بهم)", | 1377 | "char_import_7": "للمضيفين المسموح بهم)", |
| 1381 | "char_import_8": "شخصية RisuRealm (رابط مباشر)", | 1378 | "char_import_8": "شخصية RisuRealm (رابط مباشر)", |
| 1382 | "char_import_9": "شخصية Soulkyn (رابط مباشر)", | 1379 | "char_import_9": "شخصية Soulkyn (رابط مباشر)", |
| 1380 | "char_import_10": "شخصية Perchance (رابط مباشر أو UUID + .gz)", | ||
| 1383 | "Supports importing multiple characters.": "يدعم استيراد أحرف متعددة.", | 1381 | "Supports importing multiple characters.": "يدعم استيراد أحرف متعددة.", |
| 1384 | "Write each URL or ID into a new line.": "اكتب كل عنوان URL أو معرف في سطر جديد.", | 1382 | "Write each URL or ID into a new line.": "اكتب كل عنوان URL أو معرف في سطر جديد.", |
| 1385 | "Export for character": "تصدير للشخصية", | 1383 | "Export for character": "تصدير للشخصية", |
| @@ -375,9 +375,6 @@ | |||
| 375 | "Group by vendors Description": "Platzieren Sie OpenAI-Modelle in einer Gruppe, anthropogene Modelle in einer anderen Gruppe usw. Kann mit Sortierung kombiniert werden.", | 375 | "Group by vendors Description": "Platzieren Sie OpenAI-Modelle in einer Gruppe, anthropogene Modelle in einer anderen Gruppe usw. Kann mit Sortierung kombiniert werden.", |
| 376 | "Allow fallback routes": "Fallback-Routen zulassen", | 376 | "Allow fallback routes": "Fallback-Routen zulassen", |
| 377 | "Allow fallback routes Description": "Das alternative Modell wird automatisch ausgewählt, wenn das ausgewählte Modell Ihre Anfrage nicht erfüllen kann.", | 377 | "Allow fallback routes Description": "Das alternative Modell wird automatisch ausgewählt, wenn das ausgewählte Modell Ihre Anfrage nicht erfüllen kann.", |
| 378 | "Scale API Key": "Scale API-Schlüssel", | ||
| 379 | "Clear your cookie": "Löschen Sie Ihre Cookies", | ||
| 380 | "Alt Method": "Alternative Methode", | ||
| 381 | "AI21 API Key": "AI21 API-Schlüssel", | 378 | "AI21 API Key": "AI21 API-Schlüssel", |
| 382 | "AI21 Model": "AI21-Modell", | 379 | "AI21 Model": "AI21-Modell", |
| 383 | "Google AI Studio API Key": "Google AI Studio API-Schlüssel", | 380 | "Google AI Studio API Key": "Google AI Studio API-Schlüssel", |
| @@ -1380,6 +1377,7 @@ | |||
| 1380 | "char_import_7": "für erlaubte Hosts)", | 1377 | "char_import_7": "für erlaubte Hosts)", |
| 1381 | "char_import_8": "RisuRealm-Charakter (Direktlink)", | 1378 | "char_import_8": "RisuRealm-Charakter (Direktlink)", |
| 1382 | "char_import_9": "Soulkyn-Charakter (Direktlink)", | 1379 | "char_import_9": "Soulkyn-Charakter (Direktlink)", |
| 1380 | "char_import_10": "Perchance-Charakter (Direktlink oder UUID + .gz)", | ||
| 1383 | "Supports importing multiple characters.": "Unterstützt den Import mehrerer Zeichen.", | 1381 | "Supports importing multiple characters.": "Unterstützt den Import mehrerer Zeichen.", |
| 1384 | "Write each URL or ID into a new line.": "Schreiben Sie jede URL oder ID in eine neue Zeile.", | 1382 | "Write each URL or ID into a new line.": "Schreiben Sie jede URL oder ID in eine neue Zeile.", |
| 1385 | "Export for character": "Export für Zeichen", | 1383 | "Export for character": "Export für Zeichen", |
| @@ -375,9 +375,6 @@ | |||
| 375 | "Group by vendors Description": "Coloque los modelos OpenAI en un grupo, los modelos antrópicos en otro grupo, etc. Se puede combinar con la clasificación.", | 375 | "Group by vendors Description": "Coloque los modelos OpenAI en un grupo, los modelos antrópicos en otro grupo, etc. Se puede combinar con la clasificación.", |
| 376 | "Allow fallback routes": "Permitir rutas de respaldo", | 376 | "Allow fallback routes": "Permitir rutas de respaldo", |
| 377 | "Allow fallback routes Description": "El modelo alternativo se elige automáticamente si el modelo seleccionado no puede cumplir con tu solicitud.", | 377 | "Allow fallback routes Description": "El modelo alternativo se elige automáticamente si el modelo seleccionado no puede cumplir con tu solicitud.", |
| 378 | "Scale API Key": "Clave API de Scale", | ||
| 379 | "Clear your cookie": "Limpia tu cookie", | ||
| 380 | "Alt Method": "Método alternativo", | ||
| 381 | "AI21 API Key": "Clave API de AI21", | 378 | "AI21 API Key": "Clave API de AI21", |
| 382 | "AI21 Model": "Modelo de AI21", | 379 | "AI21 Model": "Modelo de AI21", |
| 383 | "Google AI Studio API Key": "Clave API de Google AI Studio", | 380 | "Google AI Studio API Key": "Clave API de Google AI Studio", |
| @@ -1380,6 +1377,7 @@ | |||
| 1380 | "char_import_7": "para hosts permitidos)", | 1377 | "char_import_7": "para hosts permitidos)", |
| 1381 | "char_import_8": "Personaje RisuRealm (Enlace directo)", | 1378 | "char_import_8": "Personaje RisuRealm (Enlace directo)", |
| 1382 | "char_import_9": "Personaje Soulkyn (Enlace directo)", | 1379 | "char_import_9": "Personaje Soulkyn (Enlace directo)", |
| 1380 | "char_import_10": "Personaje Perchance (enlace directo o UUID + .gz)", | ||
| 1383 | "Supports importing multiple characters.": "Admite la importación de múltiples caracteres.", | 1381 | "Supports importing multiple characters.": "Admite la importación de múltiples caracteres.", |
| 1384 | "Write each URL or ID into a new line.": "Escriba cada URL o ID en una nueva línea.", | 1382 | "Write each URL or ID into a new line.": "Escriba cada URL o ID en una nueva línea.", |
| 1385 | "Export for character": "Exportar para personaje", | 1383 | "Export for character": "Exportar para personaje", |
| @@ -355,9 +355,6 @@ | |||
| 355 | "Group by vendors": "Regrouper par fournisseurs", | 355 | "Group by vendors": "Regrouper par fournisseurs", |
| 356 | "Group by vendors Description": "Placez les modèles OpenAI dans un groupe, les modèles Anthropic dans un autre groupe, etc. Peut être combiné avec le tri.", | 356 | "Group by vendors Description": "Placez les modèles OpenAI dans un groupe, les modèles Anthropic dans un autre groupe, etc. Peut être combiné avec le tri.", |
| 357 | "Allow fallback routes Description": "Le modèle alternatif est automatiquement sélectionné si le modèle choisi ne peut pas répondre à votre demande.", | 357 | "Allow fallback routes Description": "Le modèle alternatif est automatiquement sélectionné si le modèle choisi ne peut pas répondre à votre demande.", |
| 358 | "Scale API Key": "Clé API Scale", | ||
| 359 | "Clear your cookie": "Effacer vos cookies", | ||
| 360 | "Alt Method": "Méthode alternative", | ||
| 361 | "AI21 API Key": "Clé API AI21", | 358 | "AI21 API Key": "Clé API AI21", |
| 362 | "AI21 Model": "Modèle AI21", | 359 | "AI21 Model": "Modèle AI21", |
| 363 | "Google AI Studio API Key": "Clé API Google AI Studio", | 360 | "Google AI Studio API Key": "Clé API Google AI Studio", |
| @@ -1302,6 +1299,7 @@ | |||
| 1302 | "char_import_7": "pour les hôtes autorisés)", | 1299 | "char_import_7": "pour les hôtes autorisés)", |
| 1303 | "char_import_8": "Personnage de RisuRealm (lien direct)", | 1300 | "char_import_8": "Personnage de RisuRealm (lien direct)", |
| 1304 | "char_import_9": "Personnage de Soulkyn (lien direct)", | 1301 | "char_import_9": "Personnage de Soulkyn (lien direct)", |
| 1302 | "char_import_10": "Personnage de Perchance (lien direct ou UUID + .gz)", | ||
| 1305 | "Supports importing multiple characters.": "Prend en charge l'importation de plusieurs caractères.", | 1303 | "Supports importing multiple characters.": "Prend en charge l'importation de plusieurs caractères.", |
| 1306 | "Write each URL or ID into a new line.": "Écrivez chaque URL ou identifiant dans une nouvelle ligne.", | 1304 | "Write each URL or ID into a new line.": "Écrivez chaque URL ou identifiant dans une nouvelle ligne.", |
| 1307 | "Export for character": "Exportation pour le personnage", | 1305 | "Export for character": "Exportation pour le personnage", |
| @@ -375,9 +375,6 @@ | |||
| 375 | "Group by vendors Description": "Setjið OpenAI módel í einn hóp, Anthropic módel í annan hóp osfrv. Hægt að sameina við flokkun.", | 375 | "Group by vendors Description": "Setjið OpenAI módel í einn hóp, Anthropic módel í annan hóp osfrv. Hægt að sameina við flokkun.", |
| 376 | "Allow fallback routes": "Leyfa bakfallssvæði", | 376 | "Allow fallback routes": "Leyfa bakfallssvæði", |
| 377 | "Allow fallback routes Description": "Veldur hlutleysa vélbúnaðarinn við val þinn ef valið módel getur ekki uppfyllt beiðni þína.", | 377 | "Allow fallback routes Description": "Veldur hlutleysa vélbúnaðarinn við val þinn ef valið módel getur ekki uppfyllt beiðni þína.", |
| 378 | "Scale API Key": "Lykill API fyrir Scale", | ||
| 379 | "Clear your cookie": "Hreinsaðu kökuna þína", | ||
| 380 | "Alt Method": "Aðferð Bakmenn", | ||
| 381 | "AI21 API Key": "Lykill API fyrir AI21", | 378 | "AI21 API Key": "Lykill API fyrir AI21", |
| 382 | "AI21 Model": "AI21 Módel", | 379 | "AI21 Model": "AI21 Módel", |
| 383 | "Google AI Studio API Key": "Google AI Studio API lykill", | 380 | "Google AI Studio API Key": "Google AI Studio API lykill", |
| @@ -1380,6 +1377,7 @@ | |||
| 1380 | "char_import_7": "fyrir leyfilega gestgjafa)", | 1377 | "char_import_7": "fyrir leyfilega gestgjafa)", |
| 1381 | "char_import_8": "RisuRealm karakter (beinn hlekkur)", | 1378 | "char_import_8": "RisuRealm karakter (beinn hlekkur)", |
| 1382 | "char_import_9": "Soulkyn karakter (beinn hlekkur)", | 1379 | "char_import_9": "Soulkyn karakter (beinn hlekkur)", |
| 1380 | "char_import_10": "Perchance karakter (beinn hlekkur eða UUID + .gz)", | ||
| 1383 | "Supports importing multiple characters.": "Styður innflutning á mörgum stöfum.", | 1381 | "Supports importing multiple characters.": "Styður innflutning á mörgum stöfum.", |
| 1384 | "Write each URL or ID into a new line.": "Skrifaðu hverja vefslóð eða auðkenni í nýja línu.", | 1382 | "Write each URL or ID into a new line.": "Skrifaðu hverja vefslóð eða auðkenni í nýja línu.", |
| 1385 | "Export for character": "Flytja út fyrir persónu", | 1383 | "Export for character": "Flytja út fyrir persónu", |
| @@ -375,9 +375,6 @@ | |||
| 375 | "Group by vendors Description": "Metti i modelli OpenAI in un gruppo, i modelli antropici in un altro gruppo, ecc. Può essere combinato con l'ordinamento.", | 375 | "Group by vendors Description": "Metti i modelli OpenAI in un gruppo, i modelli antropici in un altro gruppo, ecc. Può essere combinato con l'ordinamento.", |
| 376 | "Allow fallback routes": "Consenti percorsi alternativi", | 376 | "Allow fallback routes": "Consenti percorsi alternativi", |
| 377 | "Allow fallback routes Description": "Il modello alternativo viene automaticamente scelto se il modello selezionato non può soddisfare la tua richiesta.", | 377 | "Allow fallback routes Description": "Il modello alternativo viene automaticamente scelto se il modello selezionato non può soddisfare la tua richiesta.", |
| 378 | "Scale API Key": "Chiave API di Scale", | ||
| 379 | "Clear your cookie": "Cancella il tuo cookie", | ||
| 380 | "Alt Method": "Metodo alternativo", | ||
| 381 | "AI21 API Key": "Chiave API di AI21", | 378 | "AI21 API Key": "Chiave API di AI21", |
| 382 | "AI21 Model": "Modello AI21", | 379 | "AI21 Model": "Modello AI21", |
| 383 | "Google AI Studio API Key": "Chiave API Google AI Studio", | 380 | "Google AI Studio API Key": "Chiave API Google AI Studio", |
| @@ -1380,6 +1377,7 @@ | |||
| 1380 | "char_import_7": "per gli host consentiti)", | 1377 | "char_import_7": "per gli host consentiti)", |
| 1381 | "char_import_8": "Personaggio RisuRealm (collegamento diretto)", | 1378 | "char_import_8": "Personaggio RisuRealm (collegamento diretto)", |
| 1382 | "char_import_9": "Personaggio Soulkyn (collegamento diretto)", | 1379 | "char_import_9": "Personaggio Soulkyn (collegamento diretto)", |
| 1380 | "char_import_10": "Perchance Personaggio (collegamento diretto o UUID + .gz)", | ||
| 1383 | "Supports importing multiple characters.": "Supporta l'importazione di più caratteri.", | 1381 | "Supports importing multiple characters.": "Supporta l'importazione di più caratteri.", |
| 1384 | "Write each URL or ID into a new line.": "Scrivi ogni URL o ID in una nuova riga.", | 1382 | "Write each URL or ID into a new line.": "Scrivi ogni URL o ID in una nuova riga.", |
| 1385 | "Export for character": "Esporta per carattere", | 1383 | "Export for character": "Esporta per carattere", |
| @@ -375,9 +375,6 @@ | |||
| 375 | "Group by vendors Description": "OpenAI モデルを 1 つのグループに、Anthropic モデルを別のグループに配置するなどします。ソートと組み合わせることができます。", | 375 | "Group by vendors Description": "OpenAI モデルを 1 つのグループに、Anthropic モデルを別のグループに配置するなどします。ソートと組み合わせることができます。", |
| 376 | "Allow fallback routes": "フォールバックルートを許可", | 376 | "Allow fallback routes": "フォールバックルートを許可", |
| 377 | "Allow fallback routes Description": "選択したモデルが要求を満たせない場合、代替モデルが自動的に選択されます。", | 377 | "Allow fallback routes Description": "選択したモデルが要求を満たせない場合、代替モデルが自動的に選択されます。", |
| 378 | "Scale API Key": "ScaleのAPIキー", | ||
| 379 | "Clear your cookie": "クッキーを消去する", | ||
| 380 | "Alt Method": "代替手法", | ||
| 381 | "AI21 API Key": "AI21のAPIキー", | 378 | "AI21 API Key": "AI21のAPIキー", |
| 382 | "AI21 Model": "AI21モデル", | 379 | "AI21 Model": "AI21モデル", |
| 383 | "Google AI Studio API Key": "Google AI Studio APIキー", | 380 | "Google AI Studio API Key": "Google AI Studio APIキー", |
| @@ -1382,6 +1379,7 @@ | |||
| 1382 | "char_import_7": "許可されたホストの場合)", | 1379 | "char_import_7": "許可されたホストの場合)", |
| 1383 | "char_import_8": "RisuRealm キャラクター (直接リンク)", | 1380 | "char_import_8": "RisuRealm キャラクター (直接リンク)", |
| 1384 | "char_import_9": "Soulkyn キャラクター (直接リンク)", | 1381 | "char_import_9": "Soulkyn キャラクター (直接リンク)", |
| 1382 | "char_import_10": "Perchance キャラクター (直接リンクまたは UUID + .gz)", | ||
| 1385 | "Supports importing multiple characters.": "複数のキャラクターのインポートをサポートします。", | 1383 | "Supports importing multiple characters.": "複数のキャラクターのインポートをサポートします。", |
| 1386 | "Write each URL or ID into a new line.": "各 URL または ID を新しい行に入力します。", | 1384 | "Write each URL or ID into a new line.": "各 URL または ID を新しい行に入力します。", |
| 1387 | "Export for character": "キャラクターのエクスポート", | 1385 | "Export for character": "キャラクターのエクスポート", |
| @@ -384,9 +384,6 @@ | |||
| 384 | "LEGACY": "유산", | 384 | "LEGACY": "유산", |
| 385 | "Force Instruct Mode formatting": "강제 지시 모드 형식", | 385 | "Force Instruct Mode formatting": "강제 지시 모드 형식", |
| 386 | "Force_Instruct_Mode_formatting_Description": "Instruct Mode와 이 모드가 모두 활성화된 경우 프롬프트는 SillyTavern에 의해 현재 형식을 사용하여 형식화됩니다.\n 고급 형식 설정(시스템 프롬프트 지시 제외) 비활성화된 경우 프롬프트는 OpenRouter에 의해 형식화됩니다.", | 386 | "Force_Instruct_Mode_formatting_Description": "Instruct Mode와 이 모드가 모두 활성화된 경우 프롬프트는 SillyTavern에 의해 현재 형식을 사용하여 형식화됩니다.\n 고급 형식 설정(시스템 프롬프트 지시 제외) 비활성화된 경우 프롬프트는 OpenRouter에 의해 형식화됩니다.", |
| 387 | "Scale API Key": "Scale API 키", | ||
| 388 | "Clear your cookie": "쿠키 지우기", | ||
| 389 | "Alt Method": "대체 방법", | ||
| 390 | "AI21 API Key": "AI21 API 키", | 387 | "AI21 API Key": "AI21 API 키", |
| 391 | "AI21 Model": "AI21 모델", | 388 | "AI21 Model": "AI21 모델", |
| 392 | "Google AI Studio API Key": "Google AI Studio API 키", | 389 | "Google AI Studio API Key": "Google AI Studio API 키", |
| @@ -1399,6 +1396,7 @@ | |||
| 1399 | "char_import_7": "허용된 호스트의 경우)", | 1396 | "char_import_7": "허용된 호스트의 경우)", |
| 1400 | "char_import_8": "RisuRealm 캐릭터 (직접링크)", | 1397 | "char_import_8": "RisuRealm 캐릭터 (직접링크)", |
| 1401 | "char_import_9": "Soulkyn 캐릭터 (직접링크)", | 1398 | "char_import_9": "Soulkyn 캐릭터 (직접링크)", |
| 1399 | "char_import_10": "Perchance 캐릭터 (직접 링크 또는 UUID + .gz)", | ||
| 1402 | "Supports importing multiple characters.": "여러 문자 가져오기를 지원합니다.", | 1400 | "Supports importing multiple characters.": "여러 문자 가져오기를 지원합니다.", |
| 1403 | "Write each URL or ID into a new line.": "각 URL 또는 ID를 새 줄에 작성합니다.", | 1401 | "Write each URL or ID into a new line.": "각 URL 또는 ID를 새 줄에 작성합니다.", |
| 1404 | "Export for character": "캐릭터 내보내기", | 1402 | "Export for character": "캐릭터 내보내기", |
| @@ -375,9 +375,6 @@ | |||
| 375 | "Group by vendors Description": "Plaats OpenAI-modellen in één groep, antropische modellen in een andere groep, enz. Kan worden gecombineerd met sorteren.", | 375 | "Group by vendors Description": "Plaats OpenAI-modellen in één groep, antropische modellen in een andere groep, enz. Kan worden gecombineerd met sorteren.", |
| 376 | "Allow fallback routes": "Fallback-routes toestaan", | 376 | "Allow fallback routes": "Fallback-routes toestaan", |
| 377 | "Allow fallback routes Description": "Het alternatieve model wordt automatisch gekozen als het geselecteerde model niet aan uw verzoek kan voldoen.", | 377 | "Allow fallback routes Description": "Het alternatieve model wordt automatisch gekozen als het geselecteerde model niet aan uw verzoek kan voldoen.", |
| 378 | "Scale API Key": "Scale API-sleutel", | ||
| 379 | "Clear your cookie": "Wis uw cookie", | ||
| 380 | "Alt Method": "Alternatieve methode", | ||
| 381 | "AI21 API Key": "AI21 API-sleutel", | 378 | "AI21 API Key": "AI21 API-sleutel", |
| 382 | "AI21 Model": "AI21-model", | 379 | "AI21 Model": "AI21-model", |
| 383 | "MakerSuite API Key": "MakerSuite API-sleutel", | 380 | "MakerSuite API Key": "MakerSuite API-sleutel", |
| @@ -1380,6 +1377,7 @@ | |||
| 1380 | "char_import_7": "voor toegestane hosts)", | 1377 | "char_import_7": "voor toegestane hosts)", |
| 1381 | "char_import_8": "RisuRealm-personage (directe link)", | 1378 | "char_import_8": "RisuRealm-personage (directe link)", |
| 1382 | "char_import_9": "Soulkyn-personage (directe link)", | 1379 | "char_import_9": "Soulkyn-personage (directe link)", |
| 1380 | "char_import_10": "Perchance-personage (directe link of UUID + .gz)", | ||
| 1383 | "Supports importing multiple characters.": "Ondersteunt het importeren van meerdere tekens.", | 1381 | "Supports importing multiple characters.": "Ondersteunt het importeren van meerdere tekens.", |
| 1384 | "Write each URL or ID into a new line.": "Schrijf elke URL of ID op een nieuwe regel.", | 1382 | "Write each URL or ID into a new line.": "Schrijf elke URL of ID op een nieuwe regel.", |
| 1385 | "Export for character": "Exporteren voor karakter", | 1383 | "Export for character": "Exporteren voor karakter", |
| @@ -375,9 +375,6 @@ | |||
| 375 | "Group by vendors Description": "Coloque os modelos OpenAI em um grupo, os modelos antrópicos em outro grupo, etc.", | 375 | "Group by vendors Description": "Coloque os modelos OpenAI em um grupo, os modelos antrópicos em outro grupo, etc.", |
| 376 | "Allow fallback routes": "Permitir rotas de fallback", | 376 | "Allow fallback routes": "Permitir rotas de fallback", |
| 377 | "Allow fallback routes Description": "O modelo alternativo será escolhido automaticamente se o modelo selecionado não puder atender à sua solicitação.", | 377 | "Allow fallback routes Description": "O modelo alternativo será escolhido automaticamente se o modelo selecionado não puder atender à sua solicitação.", |
| 378 | "Scale API Key": "Chave da API Scale", | ||
| 379 | "Clear your cookie": "Limpe seu cookie", | ||
| 380 | "Alt Method": "Método Alternativo", | ||
| 381 | "AI21 API Key": "Chave da API AI21", | 378 | "AI21 API Key": "Chave da API AI21", |
| 382 | "AI21 Model": "Modelo AI21", | 379 | "AI21 Model": "Modelo AI21", |
| 383 | "Google AI Studio API Key": "Chave API Google AI Studio", | 380 | "Google AI Studio API Key": "Chave API Google AI Studio", |
| @@ -1380,6 +1377,7 @@ | |||
| 1380 | "char_import_7": "para hosts permitidos)", | 1377 | "char_import_7": "para hosts permitidos)", |
| 1381 | "char_import_8": "Personagem RisuRealm (link direto)", | 1378 | "char_import_8": "Personagem RisuRealm (link direto)", |
| 1382 | "char_import_9": "Personagem Soulkyn (link direto)", | 1379 | "char_import_9": "Personagem Soulkyn (link direto)", |
| 1380 | "char_import_10": "Personagem Perchance (Link Direto ou UUID + .gz)", | ||
| 1383 | "Supports importing multiple characters.": "Suporta importação de vários caracteres.", | 1381 | "Supports importing multiple characters.": "Suporta importação de vários caracteres.", |
| 1384 | "Write each URL or ID into a new line.": "Escreva cada URL ou ID em uma nova linha.", | 1382 | "Write each URL or ID into a new line.": "Escreva cada URL ou ID em uma nova linha.", |
| 1385 | "Export for character": "Exportar para personagem", | 1383 | "Export for character": "Exportar para personagem", |
| @@ -142,8 +142,6 @@ | |||
| 142 | "Get your key from": "Получите ключ в", | 142 | "Get your key from": "Получите ключ в", |
| 143 | "Anthropic's developer console": "консоли разработчика Anthropic", | 143 | "Anthropic's developer console": "консоли разработчика Anthropic", |
| 144 | "Claude Model": "Модель Claude", | 144 | "Claude Model": "Модель Claude", |
| 145 | "Scale API Key": "Ключ от Scale API", | ||
| 146 | "Alt Method": "Альтернативный метод", | ||
| 147 | "AI21 API Key": "Ключ от API AI21", | 145 | "AI21 API Key": "Ключ от API AI21", |
| 148 | "AI21 Model": "Модель AI21", | 146 | "AI21 Model": "Модель AI21", |
| 149 | "View API Usage Metrics": "Посмотреть статистику использования API", | 147 | "View API Usage Metrics": "Посмотреть статистику использования API", |
| @@ -966,6 +964,7 @@ | |||
| 966 | "char_import_6": "Прямая ссылка на PNG-файл (список разрешённых хостов находится в", | 964 | "char_import_6": "Прямая ссылка на PNG-файл (список разрешённых хостов находится в", |
| 967 | "char_import_7": ")", | 965 | "char_import_7": ")", |
| 968 | "char_import_9": "Персонаж с Soulkyn (прямая ссылка)", | 966 | "char_import_9": "Персонаж с Soulkyn (прямая ссылка)", |
| 967 | "char_import_10": "Персонаж с Perchance (прямая ссылка или UUID + .gz)", | ||
| 969 | "Grammar String": "Грамматика", | 968 | "Grammar String": "Грамматика", |
| 970 | "GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF или EBNF, зависит от бэкенда. Если вы это используете, то, скорее всего, сами знаете, какой именно.", | 969 | "GBNF or EBNF, depends on the backend in use. If you're using this you should know which.": "GBNF или EBNF, зависит от бэкенда. Если вы это используете, то, скорее всего, сами знаете, какой именно.", |
| 971 | "Account": "Аккаунт", | 970 | "Account": "Аккаунт", |
| @@ -1254,7 +1253,6 @@ | |||
| 1254 | "vLLM Model": "Модель vLLM", | 1253 | "vLLM Model": "Модель vLLM", |
| 1255 | "Aphrodite Model": "Модель Aphrodite", | 1254 | "Aphrodite Model": "Модель Aphrodite", |
| 1256 | "Peek a password": "Посмотреть пароль", | 1255 | "Peek a password": "Посмотреть пароль", |
| 1257 | "Clear your cookie": "Clear your cookie", | ||
| 1258 | "Add Chat Start and Example Separator to a list of stopping strings.": "Использовать Начало чата и Разделитель примеров сообщений в качестве стоп-строк.", | 1256 | "Add Chat Start and Example Separator to a list of stopping strings.": "Использовать Начало чата и Разделитель примеров сообщений в качестве стоп-строк.", |
| 1259 | "Context Order": "Context Order", | 1257 | "Context Order": "Context Order", |
| 1260 | "Summary": "Summary", | 1258 | "Summary": "Summary", |
| @@ -1748,8 +1746,6 @@ | |||
| 1748 | "dot quota_error": "имеется достаточно кредитов.", | 1746 | "dot quota_error": "имеется достаточно кредитов.", |
| 1749 | "If you have sufficient credits, please try again later.": "Если кредитов достаточно, то повторите попытку позднее.", | 1747 | "If you have sufficient credits, please try again later.": "Если кредитов достаточно, то повторите попытку позднее.", |
| 1750 | "Proxy preset '${0}' not found": "Пресет '${0}' не найден", | 1748 | "Proxy preset '${0}' not found": "Пресет '${0}' не найден", |
| 1751 | "Window.ai returned an error": "Window.ai ответил ошибкой", | ||
| 1752 | "Extension is not installed": "Расширение не установлено", | ||
| 1753 | "Update or remove your reverse proxy settings.": "Измените или удалите ваши настройки прокси.", | 1749 | "Update or remove your reverse proxy settings.": "Измените или удалите ваши настройки прокси.", |
| 1754 | "An error occurred while importing prompts. More info available in console.": "В процессе импорта произошла ошибка. Подробную информацию см. в консоли.", | 1750 | "An error occurred while importing prompts. More info available in console.": "В процессе импорта произошла ошибка. Подробную информацию см. в консоли.", |
| 1755 | "Could not import prompts. Export failed validation.": "Не удалось импортировать промпты. Не пройдена валидация при экспорте.", | 1751 | "Could not import prompts. Export failed validation.": "Не удалось импортировать промпты. Не пройдена валидация при экспорте.", |
| @@ -375,9 +375,6 @@ | |||
| 375 | "Group by vendors Description": "Помістіть моделі OpenAI в одну групу, моделі Anthropic в іншу групу тощо. Можна поєднати з сортуванням.", | 375 | "Group by vendors Description": "Помістіть моделі OpenAI в одну групу, моделі Anthropic в іншу групу тощо. Можна поєднати з сортуванням.", |
| 376 | "Allow fallback routes": "Дозволити резервні маршрути", | 376 | "Allow fallback routes": "Дозволити резервні маршрути", |
| 377 | "Allow fallback routes Description": "Автоматично вибирає альтернативну модель, якщо вибрана модель не може задовольнити ваш запит.", | 377 | "Allow fallback routes Description": "Автоматично вибирає альтернативну модель, якщо вибрана модель не може задовольнити ваш запит.", |
| 378 | "Scale API Key": "Ключ API для Scale", | ||
| 379 | "Clear your cookie": "Очистіть файл cookie", | ||
| 380 | "Alt Method": "Альтернативний метод", | ||
| 381 | "AI21 API Key": "Ключ API для AI21", | 378 | "AI21 API Key": "Ключ API для AI21", |
| 382 | "AI21 Model": "Модель AI21", | 379 | "AI21 Model": "Модель AI21", |
| 383 | "Google AI Studio API Key": "Ключ API Google AI Studio", | 380 | "Google AI Studio API Key": "Ключ API Google AI Studio", |
| @@ -1380,6 +1377,7 @@ | |||
| 1380 | "char_import_7": "для дозволених хостів)", | 1377 | "char_import_7": "для дозволених хостів)", |
| 1381 | "char_import_8": "Персонаж RisuRealm (пряме посилання)", | 1378 | "char_import_8": "Персонаж RisuRealm (пряме посилання)", |
| 1382 | "char_import_9": "Персонаж Soulkyn (пряме посилання)", | 1379 | "char_import_9": "Персонаж Soulkyn (пряме посилання)", |
| 1380 | "char_import_10": "Персонаж Perchance (пряме посилання або UUID + .gz)", | ||
| 1383 | "Supports importing multiple characters.": "Підтримується імпорт кількох символів.", | 1381 | "Supports importing multiple characters.": "Підтримується імпорт кількох символів.", |
| 1384 | "Write each URL or ID into a new line.": "Напишіть кожну URL-адресу або ідентифікатор у новому рядку.", | 1382 | "Write each URL or ID into a new line.": "Напишіть кожну URL-адресу або ідентифікатор у новому рядку.", |
| 1385 | "Export for character": "Експорт для персонажа", | 1383 | "Export for character": "Експорт для персонажа", |
| @@ -375,9 +375,6 @@ | |||
| 375 | "Group by vendors Description": "Xếp các mô hình OpenAI vào một nhóm, các mô hình Anthropic vào một nhóm khác, v.v. Có thể kết hợp với việc sắp xếp.", | 375 | "Group by vendors Description": "Xếp các mô hình OpenAI vào một nhóm, các mô hình Anthropic vào một nhóm khác, v.v. Có thể kết hợp với việc sắp xếp.", |
| 376 | "Allow fallback routes": "Cho phép các tuyến đường phụ", | 376 | "Allow fallback routes": "Cho phép các tuyến đường phụ", |
| 377 | "Allow fallback routes Description": "Bot thay thế tự động nếu mô hình được chọn không thể đáp ứng yêu cầu của bạn.", | 377 | "Allow fallback routes Description": "Bot thay thế tự động nếu mô hình được chọn không thể đáp ứng yêu cầu của bạn.", |
| 378 | "Scale API Key": "Scale API Key", | ||
| 379 | "Clear your cookie": "Xóa cookie", | ||
| 380 | "Alt Method": "Phương pháp thay thế", | ||
| 381 | "AI21 API Key": "AI21 API Key", | 378 | "AI21 API Key": "AI21 API Key", |
| 382 | "AI21 Model": "Model của AI21", | 379 | "AI21 Model": "Model của AI21", |
| 383 | "Google AI Studio API Key": "Google AI Studio API Key", | 380 | "Google AI Studio API Key": "Google AI Studio API Key", |
| @@ -1380,6 +1377,7 @@ | |||
| 1380 | "char_import_7": "đối với các máy chủ được phép)", | 1377 | "char_import_7": "đối với các máy chủ được phép)", |
| 1381 | "char_import_8": "RisuRealm (URL trực tiếp)", | 1378 | "char_import_8": "RisuRealm (URL trực tiếp)", |
| 1382 | "char_import_9": "Soulkyn (URL trực tiếp)", | 1379 | "char_import_9": "Soulkyn (URL trực tiếp)", |
| 1380 | "char_import_10": "Perchance (Nhập URL trực tiếp hoặc UUID + .gz)", | ||
| 1383 | "Supports importing multiple characters.": "Hỗ trợ nhập nhiều ký tự.", | 1381 | "Supports importing multiple characters.": "Hỗ trợ nhập nhiều ký tự.", |
| 1384 | "Write each URL or ID into a new line.": "Viết mỗi URL hoặc ID vào một dòng mới.", | 1382 | "Write each URL or ID into a new line.": "Viết mỗi URL hoặc ID vào một dòng mới.", |
| 1385 | "Export for character": "Xuất cho nhân vật", | 1383 | "Export for character": "Xuất cho nhân vật", |
| @@ -429,9 +429,6 @@ | |||
| 429 | "Group by vendors": "按厂商分组", | 429 | "Group by vendors": "按厂商分组", |
| 430 | "Group by vendors Description": "将 OpenAI 模型放在一组,将 Anthropic 模型放在另一组,等等。可以与排序结合。", | 430 | "Group by vendors Description": "将 OpenAI 模型放在一组,将 Anthropic 模型放在另一组,等等。可以与排序结合。", |
| 431 | "To use instruct formatting, switch to OpenRouter under Text Completion API.": "To use instruct formatting, switch to OpenRouter under Text Completion API.", | 431 | "To use instruct formatting, switch to OpenRouter under Text Completion API.": "To use instruct formatting, switch to OpenRouter under Text Completion API.", |
| 432 | "Scale API Key": "Scale API密钥", | ||
| 433 | "Clear your cookie": "清除你的 Cookie", | ||
| 434 | "Alt Method": "备用方法", | ||
| 435 | "AI21 API Key": "AI21 API 密钥", | 432 | "AI21 API Key": "AI21 API 密钥", |
| 436 | "AI21 Model": "AI21 模型", | 433 | "AI21 Model": "AI21 模型", |
| 437 | "Google AI Studio API Key": "Google AI Studio API 密钥", | 434 | "Google AI Studio API Key": "Google AI Studio API 密钥", |
| @@ -1894,6 +1891,7 @@ | |||
| 1894 | "char_import_7": ")", | 1891 | "char_import_7": ")", |
| 1895 | "char_import_8": "RisuRealm 角色(直链)", | 1892 | "char_import_8": "RisuRealm 角色(直链)", |
| 1896 | "char_import_9": "Soulkyn 角色(直链)", | 1893 | "char_import_9": "Soulkyn 角色(直链)", |
| 1894 | "char_import_10": "Perchance 角色(直链或UUID + .gz)", | ||
| 1897 | "Supports importing multiple characters.": "支持导入多个角色。", | 1895 | "Supports importing multiple characters.": "支持导入多个角色。", |
| 1898 | "Write each URL or ID into a new line.": "将每个 URL 或 ID 写入新行。", | 1896 | "Write each URL or ID into a new line.": "将每个 URL 或 ID 写入新行。", |
| 1899 | "Enter the Git URL of the extension to install": "输入扩展程序的 Git URL 以安装", | 1897 | "Enter the Git URL of the extension to install": "输入扩展程序的 Git URL 以安装", |
| @@ -376,9 +376,6 @@ | |||
| 376 | "Group by vendors Description": "將 OpenAI、Anthropic 等模型放各自供應商的群組中。可以與排序功能結合使用。", | 376 | "Group by vendors Description": "將 OpenAI、Anthropic 等模型放各自供應商的群組中。可以與排序功能結合使用。", |
| 377 | "Allow fallback routes": "允許備援路徑", | 377 | "Allow fallback routes": "允許備援路徑", |
| 378 | "Allow fallback routes Description": "如果選擇的模型無法滿足要求,會自動選擇替代模型。", | 378 | "Allow fallback routes Description": "如果選擇的模型無法滿足要求,會自動選擇替代模型。", |
| 379 | "Scale API Key": "Scale API 金鑰", | ||
| 380 | "Clear your cookie": "清除您的 Cookie", | ||
| 381 | "Alt Method": "替代方法", | ||
| 382 | "AI21 API Key": "AI21 API 金鑰", | 379 | "AI21 API Key": "AI21 API 金鑰", |
| 383 | "AI21 Model": "AI21 模型", | 380 | "AI21 Model": "AI21 模型", |
| 384 | "Google AI Studio API Key": "Google AI Studio API 金鑰", | 381 | "Google AI Studio API Key": "Google AI Studio API 金鑰", |
| @@ -1385,6 +1382,7 @@ | |||
| 1385 | "char_import_7": "對於允許的主機)", | 1382 | "char_import_7": "對於允許的主機)", |
| 1386 | "char_import_8": "RisuRealm 角色(直接連結)", | 1383 | "char_import_8": "RisuRealm 角色(直接連結)", |
| 1387 | "char_import_9": "Soulkyn 角色(直接連結)", | 1384 | "char_import_9": "Soulkyn 角色(直接連結)", |
| 1385 | "char_import_10": "Perchance 角色(直接連結或 ID + .gz)", | ||
| 1388 | "Supports importing multiple characters.": "支援匯入多個字元。", | 1386 | "Supports importing multiple characters.": "支援匯入多個字元。", |
| 1389 | "Write each URL or ID into a new line.": "將每個 URL 或 ID 寫入新行。", | 1387 | "Write each URL or ID into a new line.": "將每個 URL 或 ID 寫入新行。", |
| 1390 | "Export for character": "匯出字元", | 1388 | "Export for character": "匯出字元", |
| @@ -2515,106 +2513,6 @@ | |||
| 2515 | "Note: Themes can be made/applied by going to the ": "注意:主題可通過前往以下位置進行創建/應用", | 2513 | "Note: Themes can be made/applied by going to the ": "注意:主題可通過前往以下位置進行創建/應用", |
| 2516 | "Theme Name": "主題名稱", | 2514 | "Theme Name": "主題名稱", |
| 2517 | "An unknown error occurred while counting tokens. Further information may be available in console.": "計算符元時發生未知錯誤。更多資訊可能可在主控台(console)中查看。", | 2515 | "An unknown error occurred while counting tokens. Further information may be available in console.": "計算符元時發生未知錯誤。更多資訊可能可在主控台(console)中查看。", |
| 2518 | "Qvink Memory": "Qvink Memory(進階聊天記憶)", | ||
| 2519 | "Toggle whether memory is enabled for this chat specifically (overrides all settings).": "切換是否為此聊天啟用記憶功能(將覆蓋所有設定)。", | ||
| 2520 | "Toggle Chat Memory": "切換聊天記憶", | ||
| 2521 | "Preview current memory state (the exact text that will be injected into your context).": "預覽目前記憶狀態(包含將嵌入上下文的具體內容)。", | ||
| 2522 | "Copy ALL memories to clipboard (all memories in the entire chat, not just those injected).": "將所有記憶複製到剪貼簿(包含整個聊天的所有記憶,而非僅限於注入的部分)。", | ||
| 2523 | "Just refreshes which memories are included and re-renders the memories under each message, doesn't change summaries. This is done automatically all the time, the button is here just in case.": "不影響摘要,僅更新已包含的聊天記憶,並重新顯示在每則訊息下方。此過程通常會自動執行,按鈕只是備用選項。", | ||
| 2524 | "Active Settings Profile ": "目前設定檔", | ||
| 2525 | "Create, edit, and save configuration profiles for this extension.": "建立、編輯及儲存此擴充功能的設定檔。", | ||
| 2526 | "The currently selected profile": "目前選取的設定檔", | ||
| 2527 | "Save current profile": "儲存此設定檔", | ||
| 2528 | "Rename current profile": "重新命名此設定檔", | ||
| 2529 | "Create new profile": "建立新設定檔", | ||
| 2530 | "Restore current profile": "還原此設定檔", | ||
| 2531 | "Delete current profile": "刪除此設定檔", | ||
| 2532 | "Set as default profile for current character": "設為目前角色的預設設定檔", | ||
| 2533 | "Summarization": "摘要", | ||
| 2534 | "Customize the prompt used to summarize a given message": "自訂用於摘要指定訊息的提示詞", | ||
| 2535 | "Edit the summary prompt": "編輯摘要提示", | ||
| 2536 | "Preview the filled-in summary prompt, using the last message as an example.": "以最後一則訊息為例,預覽填充完成的摘要提示", | ||
| 2537 | "Mass re-summarization. Brings up dialog to choose subsets of messages to summarize or re-summarize.": "批量重新摘要:開啟對話框以選擇訊息子集進行摘要或重新摘要。", | ||
| 2538 | "Stop all summarization immediately.": "立即停止所有摘要。", | ||
| 2539 | "New messages will be automatically summarized if they will be included in short-term memory.": "如果新訊息將被納入短期記憶,將自動進行摘要。", | ||
| 2540 | "Auto Summarize": "自動摘要", | ||
| 2541 | "Auto-summarization will be triggered before a new message is sent instead of after.": "自動摘要將在發送新訊息之前觸發。", | ||
| 2542 | "Auto Summarize Before Generation": "在生成內容前自動摘要", | ||
| 2543 | "Show the progress bar when auto-summarizing more than 1 message.": "在自動摘要多於 1 則訊息時顯示進度條。", | ||
| 2544 | "Auto Summarize Progress Bar": "自動摘要進度條", | ||
| 2545 | "Number of messages to delay summarization (0 = summarize up to the most recent message, 1 = lag behind by one message, etc.)": "延遲摘要的訊息數量(0 = 摘要至最新訊息,1 = 延遲摘要 1 則訊息,以此類推)。", | ||
| 2546 | "Auto Summarize Message Lag": "自動摘要訊息延遲", | ||
| 2547 | "Wait until this many messages before auto-summarizing them all in sequence (1 = summarize every message immediately, 2 = summarize when you have two ready, etc). Still summarizes one at a time.": "在訊息數量達到此設定值後,依序自動摘要(1 = 即時摘要每則訊息,2 = 等待 2 則訊息後再摘要,以此類推)。摘要將逐條執行。", | ||
| 2548 | "Auto Summarize Batch Size": "自動摘要批次大小", | ||
| 2549 | "The maximum number of messages back that auto-summarization will apply (-1 to disable).": "自動摘要可回溯的訊息最大數量(-1 表示禁用此功能)。", | ||
| 2550 | "Auto Summarize Message Limit": "自動摘要訊息上限", | ||
| 2551 | "Time in seconds to wait between summarizations. May be needed if you are using a external API with a rate limit.": "每次摘要的間隔時間(秒)。此設定適用於使用具有請求速率限制的外部 API。", | ||
| 2552 | "Summarization Time Delay": "摘要時間延遲", | ||
| 2553 | "The maximum token length a summary is allowed to be before cutting it off. Use the {{words}} macro in the summarization prompt to get this value.": "摘要在被截斷前允許的最大符元(token)長度。可在摘要提示中使用 {{words}} 巨集以取得此數值。", | ||
| 2554 | "Summary Max Token Length": "摘要允許的最大符元長度", | ||
| 2555 | "Editing a message will automatically trigger a re-summarization if it has already been summarized.": "編輯訊息時,若該訊息已被摘要,將自動觸發重新摘要。", | ||
| 2556 | "Re-summarize on Edit": "編輯後重新摘要", | ||
| 2557 | "Swiping a message will automatically trigger a re-summarization if it has already been summarized.": "滑動訊息後若已進行摘要,將自動觸發重新摘要。", | ||
| 2558 | "Re-summarize on Swipe": "滑動後重新摘要", | ||
| 2559 | "Block chat input while summarizing.": "在摘要進行時暫時禁用聊天訊息輸入。", | ||
| 2560 | "Block Chat": "鎖定訊息輸入", | ||
| 2561 | "Whether to use messages and/or summaries as context for summarization. You must use {{history}} in the summary prompt.": "決定是否在摘要中使用訊息及/或過往摘要作為背景資訊。需於摘要提示詞中,使用 {{history}}。", | ||
| 2562 | "Message History": "訊息歷史", | ||
| 2563 | "Messages": "僅訊息", | ||
| 2564 | "Summaries": "僅摘要", | ||
| 2565 | "Both": "訊息與摘要", | ||
| 2566 | "Preview what the message history will look like": "預覽訊息歷史的顯示效果", | ||
| 2567 | "How many previous messages to include in the summarization prompt as context.": "摘要提示中要包含多少先前訊息作為上下文。", | ||
| 2568 | "Number of Previous Messages": "先前訊息數量", | ||
| 2569 | "When including previous messages, also include user messages.": "包含先前訊息時,也包含使用者訊息。", | ||
| 2570 | "Include Previous User Messages": "包含先前使用者訊息", | ||
| 2571 | "The message to summarize will be inside the system instruct template itself. In unchecked (default), the message will instead be added separately after the prompt. Some models benefit from this, but it is not recommended.": "系統指令模板內將直接包含需要摘要的訊息。若未啟用此選項(預設設定),訊息會在提示後分開添加。儘管某些模型可能更適合此設定,但一般不建議使用。", | ||
| 2572 | "Nest Message in Summary Prompt": "在摘要提示中內嵌訊息", | ||
| 2573 | "WARNING: doesn't work great. Attempts to preserve context-shifting by including all the content that is sent in regular prompts (world info, description, personas, example messages, message history, etc). If your regular prompts are static, this can allow Context Shifting to work between summarizations, but it decreases the accuracy of summarization due to all the extra stuff in the prompt. It also can't be previewed as this injection is handled by ST, not the extension.": "警告:效果不佳。此功能嘗試透過在摘要時包含所有常規提示內容(如世界資訊、描述、角色設定、示範訊息、聊天歷史等)來保留上下文轉換。若您的常規提示為靜態內容,則可在摘要之間維持上下文轉換,但由於提示中包含大量額外資訊,將降低摘要的準確性。此外,此內容注入由 ST 處理,而非此擴充功能,因此無法進行預覽。", | ||
| 2574 | "Include All Context Content": "包含所有上下文內容", | ||
| 2575 | "Short-term Memory Injection": "短期記憶注入", | ||
| 2576 | "Determines which messages are included in the short-term memory injection and where. If you change this and include messages that weren't summarized previously, you can either manually trigger a re-summarization or just wait until automatic summarization triggers.": "設定短期記憶注入中所包含的訊息及其插入位置。若更改此設定並包含先前未摘要的訊息,您可手動觸發重新摘要,或等待自動摘要啟動。", | ||
| 2577 | "Edit the short-term memory prompt": "編輯短期記憶提示", | ||
| 2578 | "Include User Messages": "包含使用者訊息", | ||
| 2579 | "Include System Messages": "包含系統訊息", | ||
| 2580 | "Include Thought Message": "包含思考訊息", | ||
| 2581 | "Message Length Threshold": "訊息長度閾值", | ||
| 2582 | "The minimum token length a message has to be in order to get summarized.": "可被摘要的訊息最小符元長度。", | ||
| 2583 | "The max percent of the context that short-term memory can take up.": "短期記憶可佔用上下文的最大百分比。", | ||
| 2584 | "Short-Term Context %": "短期記憶上下文%", | ||
| 2585 | "Include short-term memory in the World Info Scan": "在世界資訊掃描中包含短期記憶", | ||
| 2586 | "Do not inject": "不注入", | ||
| 2587 | "Before main prompt": "主提示之前", | ||
| 2588 | "After main prompt": "主提示之後", | ||
| 2589 | "In chat at depth": "在對話中位於深度", | ||
| 2590 | "Long-Term Memory Injection": "長期記憶注入", | ||
| 2591 | "Determines where long-term messages are injected.": "決定長期訊息注入的位置。", | ||
| 2592 | "Edit the long-term memory prompt": "編輯長期記憶提示", | ||
| 2593 | "The max percent of the context that long-term memory can take up.": "長期記憶可佔用上下文的最大百分比。", | ||
| 2594 | "Long-Term Context %": "長期記憶上下文%", | ||
| 2595 | "Include long-term memory in the World Info Scan": "在世界資訊掃描中包含長期記憶", | ||
| 2596 | "Misc.": "其他", | ||
| 2597 | "Fill your console with debug messages": "將偵錯訊息填入主控台", | ||
| 2598 | "Debug Mode": "偵錯模式", | ||
| 2599 | "Display summarizations below each message": "在每則訊息下顯示摘要", | ||
| 2600 | "Display Memories": "顯示記憶", | ||
| 2601 | "Enable Memory in New Chats": "在新對話中啟用記憶", | ||
| 2602 | "Limit Message History": "限制訊息歷史", | ||
| 2603 | "Revert Settings": "還原設定", | ||
| 2604 | "Auto-summarize user messages and include summaries in memory.": "自動摘要使用者訊息,並將該摘要納入記憶。", | ||
| 2605 | "Auto-summarize system messages and include summaries in memory.": "自動摘要系統訊息,並將該摘要納入記憶。", | ||
| 2606 | "Auto-summarize thought messages and include summaries in memory (from the Stepped Thinking extension).": "自動摘要思考訊息並將摘要納入記憶(來自 Stepped Thinking 擴充功能)。", | ||
| 2607 | "Revert all settings to default (not the default profile, just the default that comes with the extension). Your other profiles won't be affected.": "將所有設定恢復為預設值(並非恢復至「預設設定檔」,而是擴充功能隨附的原始預設值)。其他設定檔將不受影響。", | ||
| 2608 | "Limit the number of messages to send in regular prompts to this number (-1 for no limit). Message memories will still be sent.": "限制常規提示中傳送的訊息數量至此數值(-1 表示無限制)。訊息記憶仍將一併傳送。", | ||
| 2609 | "Whether memory is enabled by default for new chats.": "是否在新對話中預設啟用記憶。", | ||
| 2610 | "Summarize Chat": "聊天摘要", | ||
| 2611 | "Choose settings for the chat summarization. All message inclusion/exclusion settings from the main config profile are used, in addition to the following options.": "選擇聊天摘要的設定。摘要時將使用主要設定檔中的所有訊息包含/排除規則,並可額外設定以下選項。", | ||
| 2612 | "Currently preparing to summarize:": "目前正在準備摘要:", | ||
| 2613 | "Summarize messages with no existing summary": "摘要尚無摘要的訊息", | ||
| 2614 | "Re-summarize messages with existing short-term memories": "重新摘要具有現有短期記憶的訊息", | ||
| 2615 | "Re-summarize messages with existing long-term memories": "重新摘要具有現有長期記憶的訊息", | ||
| 2616 | "Re-summarize messages with existing memories, but which are currently excluded from short-term and long-term memory": "重新摘要具有現有記憶,但目前被排除在短期和長期記憶之外的訊息", | ||
| 2617 | "Re-summarize messages with existing memories that have been manually edited.": "重新摘要已手動編輯的訊息記憶", | ||
| 2618 | "Type the folder name of the theme you want to apply.": "輸入您想套用的主題資料夾名稱。", | 2516 | "Type the folder name of the theme you want to apply.": "輸入您想套用的主題資料夾名稱。", |
| 2619 | "Place your theme data in a folder.": "請將主題資料存於該資料夾內。", | 2517 | "Place your theme data in a folder.": "請將主題資料存於該資料夾內。", |
| 2620 | "Unsure where to start? Type ": "不確定如何開始?輸入:", | 2518 | "Unsure where to start? Type ": "不確定如何開始?輸入:", |
| @@ -1,12 +1,9 @@ | |||
| 1 | import { | 1 | import { |
| 2 | showdown, | 2 | showdown, |
| 3 | moment, | 3 | moment, |
| 4 | Fuse, | ||
| 5 | DOMPurify, | 4 | DOMPurify, |
| 6 | hljs, | 5 | hljs, |
| 7 | localforage, | ||
| 8 | Handlebars, | 6 | Handlebars, |
| 9 | DiffMatchPatch, | ||
| 10 | SVGInject, | 7 | SVGInject, |
| 11 | Popper, | 8 | Popper, |
| 12 | initLibraryShims, | 9 | initLibraryShims, |
| @@ -19,10 +16,11 @@ import { | |||
| 19 | generateKoboldWithStreaming, | 16 | generateKoboldWithStreaming, |
| 20 | kai_settings, | 17 | kai_settings, |
| 21 | loadKoboldSettings, | 18 | loadKoboldSettings, |
| 22 | formatKoboldUrl, | ||
| 23 | getKoboldGenerationData, | 19 | getKoboldGenerationData, |
| 24 | kai_flags, | 20 | kai_flags, |
| 25 | setKoboldFlags, | 21 | koboldai_settings, |
| 22 | koboldai_setting_names, | ||
| 23 | initKoboldSettings, | ||
| 26 | } from './scripts/kai-settings.js'; | 24 | } from './scripts/kai-settings.js'; |
| 27 | 25 | ||
| 28 | import { | 26 | import { |
| @@ -31,10 +29,9 @@ import { | |||
| 31 | generateTextGenWithStreaming, | 29 | generateTextGenWithStreaming, |
| 32 | getTextGenGenerationData, | 30 | getTextGenGenerationData, |
| 33 | textgen_types, | 31 | textgen_types, |
| 34 | getTextGenServer, | ||
| 35 | validateTextGenUrl, | ||
| 36 | parseTextgenLogprobs, | 32 | parseTextgenLogprobs, |
| 37 | parseTabbyLogprobs, | 33 | parseTabbyLogprobs, |
| 34 | initTextGenSettings, | ||
| 38 | } from './scripts/textgen-settings.js'; | 35 | } from './scripts/textgen-settings.js'; |
| 39 | 36 | ||
| 40 | import { | 37 | import { |
| @@ -91,7 +88,6 @@ import { | |||
| 91 | sortEntitiesList, | 88 | sortEntitiesList, |
| 92 | registerDebugFunction, | 89 | registerDebugFunction, |
| 93 | flushEphemeralStoppingStrings, | 90 | flushEphemeralStoppingStrings, |
| 94 | context_presets, | ||
| 95 | resetMovableStyles, | 91 | resetMovableStyles, |
| 96 | forceCharacterEditorTokenize, | 92 | forceCharacterEditorTokenize, |
| 97 | applyPowerUserSettings, | 93 | applyPowerUserSettings, |
| @@ -120,13 +116,13 @@ import { | |||
| 120 | generateNovelWithStreaming, | 116 | generateNovelWithStreaming, |
| 121 | getNovelGenerationData, | 117 | getNovelGenerationData, |
| 122 | getKayraMaxContextTokens, | 118 | getKayraMaxContextTokens, |
| 123 | getNovelTier, | ||
| 124 | loadNovelPreset, | ||
| 125 | loadNovelSettings, | 119 | loadNovelSettings, |
| 126 | nai_settings, | 120 | nai_settings, |
| 127 | adjustNovelInstructionPrompt, | 121 | adjustNovelInstructionPrompt, |
| 128 | loadNovelSubscriptionData, | ||
| 129 | parseNovelAILogprobs, | 122 | parseNovelAILogprobs, |
| 123 | novelai_settings, | ||
| 124 | novelai_setting_names, | ||
| 125 | initNovelAISettings, | ||
| 130 | } from './scripts/nai-settings.js'; | 126 | } from './scripts/nai-settings.js'; |
| 131 | 127 | ||
| 132 | import { | 128 | import { |
| @@ -139,9 +135,10 @@ import { | |||
| 139 | horde_settings, | 135 | horde_settings, |
| 140 | loadHordeSettings, | 136 | loadHordeSettings, |
| 141 | generateHorde, | 137 | generateHorde, |
| 142 | checkHordeStatus, | 138 | getStatusHorde, |
| 143 | getHordeModels, | 139 | getHordeModels, |
| 144 | adjustHordeGenerationParams, | 140 | adjustHordeGenerationParams, |
| 141 | isHordeGenerationNotAllowed, | ||
| 145 | MIN_LENGTH, | 142 | MIN_LENGTH, |
| 146 | initHorde, | 143 | initHorde, |
| 147 | } from './scripts/horde.js'; | 144 | } from './scripts/horde.js'; |
| @@ -168,7 +165,6 @@ import { | |||
| 168 | isValidUrl, | 165 | isValidUrl, |
| 169 | ensureImageFormatSupported, | 166 | ensureImageFormatSupported, |
| 170 | flashHighlight, | 167 | flashHighlight, |
| 171 | isTrueBoolean, | ||
| 172 | toggleDrawer, | 168 | toggleDrawer, |
| 173 | isElementInViewport, | 169 | isElementInViewport, |
| 174 | copyText, | 170 | copyText, |
| @@ -180,10 +176,10 @@ import { | |||
| 180 | renderPaginationDropdown, | 176 | renderPaginationDropdown, |
| 181 | paginationDropdownChangeHandler, | 177 | paginationDropdownChangeHandler, |
| 182 | } from './scripts/utils.js'; | 178 | } from './scripts/utils.js'; |
| 183 | import { debounce_timeout, IGNORE_SYMBOL } from './scripts/constants.js'; | 179 | import { debounce_timeout, GENERATION_TYPE_TRIGGERS, IGNORE_SYMBOL } from './scripts/constants.js'; |
| 184 | 180 | ||
| 185 | import { cancelDebouncedMetadataSave, doDailyExtensionUpdatesCheck, extension_settings, initExtensions, loadExtensionSettings, runGenerationInterceptors, saveMetadataDebounced } from './scripts/extensions.js'; | 181 | import { cancelDebouncedMetadataSave, doDailyExtensionUpdatesCheck, extension_settings, initExtensions, loadExtensionSettings, runGenerationInterceptors, saveMetadataDebounced } from './scripts/extensions.js'; |
| 186 | import { COMMENT_NAME_DEFAULT, executeSlashCommandsOnChatInput, getSlashCommandsHelp, initDefaultSlashCommands, isExecutingCommandsFromChatInput, pauseScriptExecution, processChatSlashCommands, stopScriptExecution } from './scripts/slash-commands.js'; | 182 | import { COMMENT_NAME_DEFAULT, CONNECT_API_MAP, executeSlashCommandsOnChatInput, initDefaultSlashCommands, isExecutingCommandsFromChatInput, pauseScriptExecution, stopScriptExecution, UNIQUE_APIS } from './scripts/slash-commands.js'; |
| 187 | import { | 183 | import { |
| 188 | tag_map, | 184 | tag_map, |
| 189 | tags, | 185 | tags, |
| @@ -205,15 +201,9 @@ import { | |||
| 205 | applyTagsOnCharacterSelect, | 201 | applyTagsOnCharacterSelect, |
| 206 | applyTagsOnGroupSelect, | 202 | applyTagsOnGroupSelect, |
| 207 | tag_import_setting, | 203 | tag_import_setting, |
| 204 | applyCharacterTagsToMessageDivs, | ||
| 208 | } from './scripts/tags.js'; | 205 | } from './scripts/tags.js'; |
| 209 | import { | 206 | import { initSecrets, readSecretState } from './scripts/secrets.js'; |
| 210 | SECRET_KEYS, | ||
| 211 | initSecrets, | ||
| 212 | readSecretState, | ||
| 213 | secret_state, | ||
| 214 | writeSecret, | ||
| 215 | } from './scripts/secrets.js'; | ||
| 216 | import { EventEmitter } from './lib/eventemitter.js'; | ||
| 217 | import { markdownExclusionExt } from './scripts/showdown-exclusion.js'; | 207 | import { markdownExclusionExt } from './scripts/showdown-exclusion.js'; |
| 218 | import { markdownUnderscoreExt } from './scripts/showdown-underscore.js'; | 208 | import { markdownUnderscoreExt } from './scripts/showdown-underscore.js'; |
| 219 | import { NOTE_MODULE_NAME, initAuthorsNote, metadata_keys, setFloatingPrompt, shouldWIAddPrompt } from './scripts/authors-note.js'; | 209 | import { NOTE_MODULE_NAME, initAuthorsNote, metadata_keys, setFloatingPrompt, shouldWIAddPrompt } from './scripts/authors-note.js'; |
| @@ -228,14 +218,10 @@ import { | |||
| 228 | formatInstructModePrompt, | 218 | formatInstructModePrompt, |
| 229 | formatInstructModeExamples, | 219 | formatInstructModeExamples, |
| 230 | getInstructStoppingSequences, | 220 | getInstructStoppingSequences, |
| 231 | autoSelectInstructPreset, | ||
| 232 | formatInstructModeSystemPrompt, | 221 | formatInstructModeSystemPrompt, |
| 233 | selectInstructPreset, | ||
| 234 | instruct_presets, | ||
| 235 | selectContextPreset, | ||
| 236 | } from './scripts/instruct-mode.js'; | 222 | } from './scripts/instruct-mode.js'; |
| 237 | import { initLocales, t } from './scripts/i18n.js'; | 223 | import { initLocales, t } from './scripts/i18n.js'; |
| 238 | import { getFriendlyTokenizerName, getTokenCount, getTokenCountAsync, initTokenizers, saveTokenCache, TOKENIZER_SUPPORTED_KEY } from './scripts/tokenizers.js'; | 224 | import { getFriendlyTokenizerName, getTokenCount, getTokenCountAsync, initTokenizers, saveTokenCache } from './scripts/tokenizers.js'; |
| 239 | import { | 225 | import { |
| 240 | user_avatar, | 226 | user_avatar, |
| 241 | getUserAvatars, | 227 | getUserAvatars, |
| @@ -249,38 +235,19 @@ import { | |||
| 249 | } from './scripts/personas.js'; | 235 | } from './scripts/personas.js'; |
| 250 | import { getBackgrounds, initBackgrounds, loadBackgroundSettings, background_settings } from './scripts/backgrounds.js'; | 236 | import { getBackgrounds, initBackgrounds, loadBackgroundSettings, background_settings } from './scripts/backgrounds.js'; |
| 251 | import { hideLoader, showLoader } from './scripts/loader.js'; | 237 | import { hideLoader, showLoader } from './scripts/loader.js'; |
| 252 | import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js'; | 238 | import { BulkEditOverlay } from './scripts/BulkEditOverlay.js'; |
| 253 | import { | 239 | import { initTextGenModels } from './scripts/textgen-models.js'; |
| 254 | loadFeatherlessModels, | 240 | import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, preserveNeutralChat, restoreNeutralChat, formatCreatorNotes, initChatUtilities, addDOMPurifyHooks } from './scripts/chats.js'; |
| 255 | loadMancerModels, | ||
| 256 | loadOllamaModels, | ||
| 257 | loadTogetherAIModels, | ||
| 258 | loadInfermaticAIModels, | ||
| 259 | loadOpenRouterModels, | ||
| 260 | loadVllmModels, | ||
| 261 | loadAphroditeModels, | ||
| 262 | loadDreamGenModels, | ||
| 263 | initTextGenModels, | ||
| 264 | loadTabbyModels, | ||
| 265 | loadGenericModels, | ||
| 266 | } from './scripts/textgen-models.js'; | ||
| 267 | import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags, isExternalMediaAllowed, getCurrentEntityId, preserveNeutralChat, restoreNeutralChat, formatCreatorNotes, initChatUtilities } from './scripts/chats.js'; | ||
| 268 | import { getPresetManager, initPresetManager } from './scripts/preset-manager.js'; | 241 | import { getPresetManager, initPresetManager } from './scripts/preset-manager.js'; |
| 269 | import { evaluateMacros, getLastMessageId, initMacros } from './scripts/macros.js'; | 242 | import { evaluateMacros, getLastMessageId, initMacros } from './scripts/macros.js'; |
| 270 | import { currentUser, setUserControls } from './scripts/user.js'; | 243 | import { currentUser, setUserControls } from './scripts/user.js'; |
| 271 | import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup, fixToastrForDialogs } from './scripts/popup.js'; | 244 | import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup, fixToastrForDialogs } from './scripts/popup.js'; |
| 272 | import { renderTemplate, renderTemplateAsync } from './scripts/templates.js'; | 245 | import { renderTemplate, renderTemplateAsync } from './scripts/templates.js'; |
| 273 | import { initScrapers } from './scripts/scrapers.js'; | 246 | import { initScrapers } from './scripts/scrapers.js'; |
| 274 | import { SlashCommandParser } from './scripts/slash-commands/SlashCommandParser.js'; | ||
| 275 | import { SlashCommand } from './scripts/slash-commands/SlashCommand.js'; | ||
| 276 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './scripts/slash-commands/SlashCommandArgument.js'; | ||
| 277 | import { SlashCommandBrowser } from './scripts/slash-commands/SlashCommandBrowser.js'; | ||
| 278 | import { initCustomSelectedSamplers, validateDisabledSamplers } from './scripts/samplerSelect.js'; | 247 | import { initCustomSelectedSamplers, validateDisabledSamplers } from './scripts/samplerSelect.js'; |
| 279 | import { DragAndDropHandler } from './scripts/dragdrop.js'; | 248 | import { DragAndDropHandler } from './scripts/dragdrop.js'; |
| 280 | import { INTERACTABLE_CONTROL_CLASS, initKeyboard } from './scripts/keyboard.js'; | 249 | import { INTERACTABLE_CONTROL_CLASS, initKeyboard } from './scripts/keyboard.js'; |
| 281 | import { initDynamicStyles } from './scripts/dynamic-styles.js'; | 250 | import { initDynamicStyles } from './scripts/dynamic-styles.js'; |
| 282 | import { SlashCommandEnumValue, enumTypes } from './scripts/slash-commands/SlashCommandEnumValue.js'; | ||
| 283 | import { commonEnumProviders, enumIcons } from './scripts/slash-commands/SlashCommandCommonEnumsProvider.js'; | ||
| 284 | import { initInputMarkdown } from './scripts/input-md-formatting.js'; | 251 | import { initInputMarkdown } from './scripts/input-md-formatting.js'; |
| 285 | import { AbortReason } from './scripts/util/AbortReason.js'; | 252 | import { AbortReason } from './scripts/util/AbortReason.js'; |
| 286 | import { initSystemPrompts } from './scripts/sysprompt.js'; | 253 | import { initSystemPrompts } from './scripts/sysprompt.js'; |
| @@ -291,12 +258,14 @@ import { applyBrowserFixes } from './scripts/browser-fixes.js'; | |||
| 291 | import { initServerHistory } from './scripts/server-history.js'; | 258 | import { initServerHistory } from './scripts/server-history.js'; |
| 292 | import { initSettingsSearch } from './scripts/setting-search.js'; | 259 | import { initSettingsSearch } from './scripts/setting-search.js'; |
| 293 | import { initBulkEdit } from './scripts/bulk-edit.js'; | 260 | import { initBulkEdit } from './scripts/bulk-edit.js'; |
| 294 | import { deriveTemplatesFromChatTemplate } from './scripts/chat-templates.js'; | ||
| 295 | import { getContext } from './scripts/st-context.js'; | 261 | import { getContext } from './scripts/st-context.js'; |
| 296 | import { extractReasoningFromData, initReasoning, parseReasoningInSwipes, PromptReasoning, ReasoningHandler, removeReasoningFromString, updateReasoningUI } from './scripts/reasoning.js'; | 262 | import { extractReasoningFromData, initReasoning, parseReasoningInSwipes, PromptReasoning, ReasoningHandler, removeReasoningFromString, updateReasoningUI } from './scripts/reasoning.js'; |
| 297 | import { accountStorage } from './scripts/util/AccountStorage.js'; | 263 | import { accountStorage } from './scripts/util/AccountStorage.js'; |
| 298 | import { initWelcomeScreen, openPermanentAssistantChat, openPermanentAssistantCard, getPermanentAssistantAvatar } from './scripts/welcome-screen.js'; | 264 | import { initWelcomeScreen, openPermanentAssistantChat, openPermanentAssistantCard, getPermanentAssistantAvatar } from './scripts/welcome-screen.js'; |
| 299 | import { initDataMaid } from './scripts/data-maid.js'; | 265 | import { initDataMaid } from './scripts/data-maid.js'; |
| 266 | import { clearItemizedPrompts, deleteItemizedPrompts, findItemizedPromptSet, initItemizedPrompts, itemizedParams, itemizedPrompts, loadItemizedPrompts, promptItemize, replaceItemizedPromptText, saveItemizedPrompts } from './scripts/itemized-prompts.js'; | ||
| 267 | import { getSystemMessageByType, initSystemMessages, SAFETY_CHAT, sendSystemMessage, system_message_types, system_messages } from './scripts/system-messages.js'; | ||
| 268 | import { event_types, eventSource } from './scripts/events.js'; | ||
| 300 | 269 | ||
| 301 | // API OBJECT FOR EXTERNAL WIRING | 270 | // API OBJECT FOR EXTERNAL WIRING |
| 302 | globalThis.SillyTavern = { | 271 | globalThis.SillyTavern = { |
| @@ -304,7 +273,6 @@ globalThis.SillyTavern = { | |||
| 304 | getContext, | 273 | getContext, |
| 305 | }; | 274 | }; |
| 306 | 275 | ||
| 307 | //exporting functions and vars for mods | ||
| 308 | export { | 276 | export { |
| 309 | user_avatar, | 277 | user_avatar, |
| 310 | setUserAvatar, | 278 | setUserAvatar, |
| @@ -314,6 +282,27 @@ export { | |||
| 314 | isOdd, | 282 | isOdd, |
| 315 | countOccurrences, | 283 | countOccurrences, |
| 316 | renderTemplate, | 284 | renderTemplate, |
| 285 | promptItemize, | ||
| 286 | itemizedPrompts, | ||
| 287 | saveItemizedPrompts, | ||
| 288 | loadItemizedPrompts, | ||
| 289 | itemizedParams, | ||
| 290 | clearItemizedPrompts, | ||
| 291 | replaceItemizedPromptText, | ||
| 292 | deleteItemizedPrompts, | ||
| 293 | findItemizedPromptSet, | ||
| 294 | koboldai_settings, | ||
| 295 | koboldai_setting_names, | ||
| 296 | novelai_settings, | ||
| 297 | novelai_setting_names, | ||
| 298 | UNIQUE_APIS, | ||
| 299 | CONNECT_API_MAP, | ||
| 300 | system_messages, | ||
| 301 | system_message_types, | ||
| 302 | sendSystemMessage, | ||
| 303 | getSystemMessageByType, | ||
| 304 | event_types, | ||
| 305 | eventSource, | ||
| 317 | }; | 306 | }; |
| 318 | 307 | ||
| 319 | /** | 308 | /** |
| @@ -351,225 +340,7 @@ toastr.options = { | |||
| 351 | }, | 340 | }, |
| 352 | }; | 341 | }; |
| 353 | 342 | ||
| 354 | // Allow target="_blank" in links | ||
| 355 | DOMPurify.addHook('afterSanitizeAttributes', function (node) { | ||
| 356 | if ('target' in node) { | ||
| 357 | node.setAttribute('target', '_blank'); | ||
| 358 | node.setAttribute('rel', 'noopener'); | ||
| 359 | } | ||
| 360 | }); | ||
| 361 | |||
| 362 | DOMPurify.addHook('uponSanitizeAttribute', (node, data, config) => { | ||
| 363 | if (!config['MESSAGE_SANITIZE']) { | ||
| 364 | return; | ||
| 365 | } | ||
| 366 | |||
| 367 | /* Retain the classes on UI elements of messages that interact with the main UI */ | ||
| 368 | const permittedNodeTypes = ['BUTTON', 'DIV']; | ||
| 369 | if (config['MESSAGE_ALLOW_SYSTEM_UI'] && node.classList.contains('menu_button') && permittedNodeTypes.includes(node.nodeName)) { | ||
| 370 | return; | ||
| 371 | } | ||
| 372 | |||
| 373 | switch (data.attrName) { | ||
| 374 | case 'class': { | ||
| 375 | if (data.attrValue) { | ||
| 376 | data.attrValue = data.attrValue.split(' ').map((v) => { | ||
| 377 | if (v.startsWith('fa-') || v.startsWith('note-') || v === 'monospace') { | ||
| 378 | return v; | ||
| 379 | } | ||
| 380 | |||
| 381 | return 'custom-' + v; | ||
| 382 | }).join(' '); | ||
| 383 | } | ||
| 384 | break; | ||
| 385 | } | ||
| 386 | } | ||
| 387 | }); | ||
| 388 | |||
| 389 | DOMPurify.addHook('uponSanitizeElement', (node, _, config) => { | ||
| 390 | if (!config['MESSAGE_SANITIZE']) { | ||
| 391 | return; | ||
| 392 | } | ||
| 393 | |||
| 394 | // Replace line breaks with <br> in unknown elements | ||
| 395 | if (node instanceof HTMLUnknownElement) { | ||
| 396 | node.innerHTML = node.innerHTML.trim().replaceAll('\n', '<br>'); | ||
| 397 | } | ||
| 398 | |||
| 399 | const isMediaAllowed = isExternalMediaAllowed(); | ||
| 400 | if (isMediaAllowed) { | ||
| 401 | return; | ||
| 402 | } | ||
| 403 | |||
| 404 | if (!(node instanceof Element)) { | ||
| 405 | return; | ||
| 406 | } | ||
| 407 | |||
| 408 | let mediaBlocked = false; | ||
| 409 | |||
| 410 | switch (node.tagName) { | ||
| 411 | case 'AUDIO': | ||
| 412 | case 'VIDEO': | ||
| 413 | case 'SOURCE': | ||
| 414 | case 'TRACK': | ||
| 415 | case 'EMBED': | ||
| 416 | case 'OBJECT': | ||
| 417 | case 'IMG': { | ||
| 418 | const isExternalUrl = (url) => (url.indexOf('://') > 0 || url.indexOf('//') === 0) && !url.startsWith(window.location.origin); | ||
| 419 | const src = node.getAttribute('src'); | ||
| 420 | const data = node.getAttribute('data'); | ||
| 421 | const srcset = node.getAttribute('srcset'); | ||
| 422 | |||
| 423 | if (srcset) { | ||
| 424 | const srcsetUrls = srcset.split(','); | ||
| 425 | |||
| 426 | for (const srcsetUrl of srcsetUrls) { | ||
| 427 | const [url] = srcsetUrl.trim().split(' '); | ||
| 428 | |||
| 429 | if (isExternalUrl(url)) { | ||
| 430 | console.warn('External media blocked', url); | ||
| 431 | node.remove(); | ||
| 432 | mediaBlocked = true; | ||
| 433 | break; | ||
| 434 | } | ||
| 435 | } | ||
| 436 | } | ||
| 437 | |||
| 438 | if (src && isExternalUrl(src)) { | ||
| 439 | console.warn('External media blocked', src); | ||
| 440 | mediaBlocked = true; | ||
| 441 | node.remove(); | ||
| 442 | } | ||
| 443 | |||
| 444 | if (data && isExternalUrl(data)) { | ||
| 445 | console.warn('External media blocked', data); | ||
| 446 | mediaBlocked = true; | ||
| 447 | node.remove(); | ||
| 448 | } | ||
| 449 | |||
| 450 | if (mediaBlocked && (node instanceof HTMLMediaElement)) { | ||
| 451 | node.autoplay = false; | ||
| 452 | node.pause(); | ||
| 453 | } | ||
| 454 | } | ||
| 455 | break; | ||
| 456 | } | ||
| 457 | |||
| 458 | if (mediaBlocked) { | ||
| 459 | const entityId = getCurrentEntityId(); | ||
| 460 | const warningShownKey = `mediaWarningShown:${entityId}`; | ||
| 461 | |||
| 462 | if (accountStorage.getItem(warningShownKey) === null) { | ||
| 463 | const warningToast = toastr.warning( | ||
| 464 | t`Use the 'Ext. Media' button to allow it. Click on this message to dismiss.`, | ||
| 465 | t`External media has been blocked`, | ||
| 466 | { | ||
| 467 | timeOut: 0, | ||
| 468 | preventDuplicates: true, | ||
| 469 | onclick: () => toastr.clear(warningToast), | ||
| 470 | }, | ||
| 471 | ); | ||
| 472 | |||
| 473 | accountStorage.setItem(warningShownKey, 'true'); | ||
| 474 | } | ||
| 475 | } | ||
| 476 | }); | ||
| 477 | |||
| 478 | // Event source init | ||
| 479 | //MARK: event_types | ||
| 480 | export const event_types = { | ||
| 481 | APP_READY: 'app_ready', | ||
| 482 | EXTRAS_CONNECTED: 'extras_connected', | ||
| 483 | MESSAGE_SWIPED: 'message_swiped', | ||
| 484 | MESSAGE_SENT: 'message_sent', | ||
| 485 | MESSAGE_RECEIVED: 'message_received', | ||
| 486 | MESSAGE_EDITED: 'message_edited', | ||
| 487 | MESSAGE_DELETED: 'message_deleted', | ||
| 488 | MESSAGE_UPDATED: 'message_updated', | ||
| 489 | MESSAGE_FILE_EMBEDDED: 'message_file_embedded', | ||
| 490 | MESSAGE_REASONING_EDITED: 'message_reasoning_edited', | ||
| 491 | MESSAGE_REASONING_DELETED: 'message_reasoning_deleted', | ||
| 492 | MORE_MESSAGES_LOADED: 'more_messages_loaded', | ||
| 493 | IMPERSONATE_READY: 'impersonate_ready', | ||
| 494 | CHAT_CHANGED: 'chat_id_changed', | ||
| 495 | GENERATION_AFTER_COMMANDS: 'GENERATION_AFTER_COMMANDS', | ||
| 496 | GENERATION_STARTED: 'generation_started', | ||
| 497 | GENERATION_STOPPED: 'generation_stopped', | ||
| 498 | GENERATION_ENDED: 'generation_ended', | ||
| 499 | SD_PROMPT_PROCESSING: 'sd_prompt_processing', | ||
| 500 | EXTENSIONS_FIRST_LOAD: 'extensions_first_load', | ||
| 501 | EXTENSION_SETTINGS_LOADED: 'extension_settings_loaded', | ||
| 502 | SETTINGS_LOADED: 'settings_loaded', | ||
| 503 | SETTINGS_UPDATED: 'settings_updated', | ||
| 504 | GROUP_UPDATED: 'group_updated', | ||
| 505 | MOVABLE_PANELS_RESET: 'movable_panels_reset', | ||
| 506 | SETTINGS_LOADED_BEFORE: 'settings_loaded_before', | ||
| 507 | SETTINGS_LOADED_AFTER: 'settings_loaded_after', | ||
| 508 | CHATCOMPLETION_SOURCE_CHANGED: 'chatcompletion_source_changed', | ||
| 509 | CHATCOMPLETION_MODEL_CHANGED: 'chatcompletion_model_changed', | ||
| 510 | OAI_PRESET_CHANGED_BEFORE: 'oai_preset_changed_before', | ||
| 511 | OAI_PRESET_CHANGED_AFTER: 'oai_preset_changed_after', | ||
| 512 | OAI_PRESET_EXPORT_READY: 'oai_preset_export_ready', | ||
| 513 | OAI_PRESET_IMPORT_READY: 'oai_preset_import_ready', | ||
| 514 | WORLDINFO_SETTINGS_UPDATED: 'worldinfo_settings_updated', | ||
| 515 | WORLDINFO_UPDATED: 'worldinfo_updated', | ||
| 516 | CHARACTER_EDITED: 'character_edited', | ||
| 517 | CHARACTER_PAGE_LOADED: 'character_page_loaded', | ||
| 518 | CHARACTER_GROUP_OVERLAY_STATE_CHANGE_BEFORE: 'character_group_overlay_state_change_before', | ||
| 519 | CHARACTER_GROUP_OVERLAY_STATE_CHANGE_AFTER: 'character_group_overlay_state_change_after', | ||
| 520 | USER_MESSAGE_RENDERED: 'user_message_rendered', | ||
| 521 | CHARACTER_MESSAGE_RENDERED: 'character_message_rendered', | ||
| 522 | FORCE_SET_BACKGROUND: 'force_set_background', | ||
| 523 | CHAT_DELETED: 'chat_deleted', | ||
| 524 | CHAT_CREATED: 'chat_created', | ||
| 525 | GROUP_CHAT_DELETED: 'group_chat_deleted', | ||
| 526 | GROUP_CHAT_CREATED: 'group_chat_created', | ||
| 527 | GENERATE_BEFORE_COMBINE_PROMPTS: 'generate_before_combine_prompts', | ||
| 528 | GENERATE_AFTER_COMBINE_PROMPTS: 'generate_after_combine_prompts', | ||
| 529 | GENERATE_AFTER_DATA: 'generate_after_data', | ||
| 530 | GROUP_MEMBER_DRAFTED: 'group_member_drafted', | ||
| 531 | GROUP_WRAPPER_STARTED: 'group_wrapper_started', | ||
| 532 | GROUP_WRAPPER_FINISHED: 'group_wrapper_finished', | ||
| 533 | WORLD_INFO_ACTIVATED: 'world_info_activated', | ||
| 534 | TEXT_COMPLETION_SETTINGS_READY: 'text_completion_settings_ready', | ||
| 535 | CHAT_COMPLETION_SETTINGS_READY: 'chat_completion_settings_ready', | ||
| 536 | CHAT_COMPLETION_PROMPT_READY: 'chat_completion_prompt_ready', | ||
| 537 | CHARACTER_FIRST_MESSAGE_SELECTED: 'character_first_message_selected', | ||
| 538 | // TODO: Naming convention is inconsistent with other events | ||
| 539 | CHARACTER_DELETED: 'characterDeleted', | ||
| 540 | CHARACTER_DUPLICATED: 'character_duplicated', | ||
| 541 | CHARACTER_RENAMED: 'character_renamed', | ||
| 542 | CHARACTER_RENAMED_IN_PAST_CHAT: 'character_renamed_in_past_chat', | ||
| 543 | /** @deprecated The event is aliased to STREAM_TOKEN_RECEIVED. */ | ||
| 544 | SMOOTH_STREAM_TOKEN_RECEIVED: 'stream_token_received', | ||
| 545 | STREAM_TOKEN_RECEIVED: 'stream_token_received', | ||
| 546 | STREAM_REASONING_DONE: 'stream_reasoning_done', | ||
| 547 | FILE_ATTACHMENT_DELETED: 'file_attachment_deleted', | ||
| 548 | WORLDINFO_FORCE_ACTIVATE: 'worldinfo_force_activate', | ||
| 549 | OPEN_CHARACTER_LIBRARY: 'open_character_library', | ||
| 550 | ONLINE_STATUS_CHANGED: 'online_status_changed', | ||
| 551 | IMAGE_SWIPED: 'image_swiped', | ||
| 552 | CONNECTION_PROFILE_LOADED: 'connection_profile_loaded', | ||
| 553 | CONNECTION_PROFILE_CREATED: 'connection_profile_created', | ||
| 554 | CONNECTION_PROFILE_DELETED: 'connection_profile_deleted', | ||
| 555 | CONNECTION_PROFILE_UPDATED: 'connection_profile_updated', | ||
| 556 | TOOL_CALLS_PERFORMED: 'tool_calls_performed', | ||
| 557 | TOOL_CALLS_RENDERED: 'tool_calls_rendered', | ||
| 558 | CHARACTER_MANAGEMENT_DROPDOWN: 'charManagementDropdown', | ||
| 559 | SECRET_WRITTEN: 'secret_written', | ||
| 560 | SECRET_DELETED: 'secret_deleted', | ||
| 561 | SECRET_ROTATED: 'secret_rotated', | ||
| 562 | SECRET_EDITED: 'secret_edited', | ||
| 563 | }; | ||
| 564 | |||
| 565 | export const eventSource = new EventEmitter([event_types.APP_READY]); | ||
| 566 | |||
| 567 | eventSource.on(event_types.CHAT_CHANGED, processChatSlashCommands); | ||
| 568 | |||
| 569 | export const characterGroupOverlay = new BulkEditOverlay(); | 343 | export const characterGroupOverlay = new BulkEditOverlay(); |
| 570 | const characterContextMenu = new CharacterContextMenu(characterGroupOverlay); | ||
| 571 | eventSource.on(event_types.CHARACTER_PAGE_LOADED, characterGroupOverlay.onPageLoad); | ||
| 572 | console.debug('Character context menu initialized', characterContextMenu); | ||
| 573 | 344 | ||
| 574 | // Markdown converter | 345 | // Markdown converter |
| 575 | export let mesForShowdownParse; //intended to be used as a context to compare showdown strings against | 346 | export let mesForShowdownParse; //intended to be used as a context to compare showdown strings against |
| @@ -577,9 +348,6 @@ export let mesForShowdownParse; //intended to be used as a context to compare sh | |||
| 577 | export let converter; | 348 | export let converter; |
| 578 | 349 | ||
| 579 | // array for prompt token calculations | 350 | // array for prompt token calculations |
| 580 | console.debug('initializing Prompt Itemization Array on Startup'); | ||
| 581 | const promptStorage = localforage.createInstance({ name: 'SillyTavern_Prompts' }); | ||
| 582 | export let itemizedPrompts = []; | ||
| 583 | 351 | ||
| 584 | export const systemUserName = 'SillyTavern System'; | 352 | export const systemUserName = 'SillyTavern System'; |
| 585 | export const neutralCharacterName = 'Assistant'; | 353 | export const neutralCharacterName = 'Assistant'; |
| @@ -652,25 +420,6 @@ export const saveCharacterDebounced = debounce(() => $('#create_button').trigger | |||
| 652 | export const printCharactersDebounced = debounce(() => { printCharacters(false); }, DEFAULT_PRINT_TIMEOUT); | 420 | export const printCharactersDebounced = debounce(() => { printCharacters(false); }, DEFAULT_PRINT_TIMEOUT); |
| 653 | 421 | ||
| 654 | /** | 422 | /** |
| 655 | * @enum {string} System message types | ||
| 656 | */ | ||
| 657 | export const system_message_types = { | ||
| 658 | HELP: 'help', | ||
| 659 | WELCOME: 'welcome', | ||
| 660 | EMPTY: 'empty', | ||
| 661 | GENERIC: 'generic', | ||
| 662 | NARRATOR: 'narrator', | ||
| 663 | COMMENT: 'comment', | ||
| 664 | SLASH_COMMANDS: 'slash_commands', | ||
| 665 | FORMATTING: 'formatting', | ||
| 666 | HOTKEYS: 'hotkeys', | ||
| 667 | MACROS: 'macros', | ||
| 668 | WELCOME_PROMPT: 'welcome_prompt', | ||
| 669 | ASSISTANT_NOTE: 'assistant_note', | ||
| 670 | ASSISTANT_MESSAGE: 'assistant_message', | ||
| 671 | }; | ||
| 672 | |||
| 673 | /** | ||
| 674 | * @enum {number} Extension prompt types | 423 | * @enum {number} Extension prompt types |
| 675 | */ | 424 | */ |
| 676 | export const extension_prompt_types = { | 425 | export const extension_prompt_types = { |
| @@ -691,106 +440,6 @@ export const extension_prompt_roles = { | |||
| 691 | 440 | ||
| 692 | export const MAX_INJECTION_DEPTH = 10000; | 441 | export const MAX_INJECTION_DEPTH = 10000; |
| 693 | 442 | ||
| 694 | // Initialized in getSystemMessages() | ||
| 695 | const SAFETY_CHAT = []; | ||
| 696 | |||
| 697 | export let system_messages = {}; | ||
| 698 | |||
| 699 | async function getSystemMessages() { | ||
| 700 | system_messages = { | ||
| 701 | help: { | ||
| 702 | name: systemUserName, | ||
| 703 | force_avatar: system_avatar, | ||
| 704 | is_user: false, | ||
| 705 | is_system: true, | ||
| 706 | mes: await renderTemplateAsync('help'), | ||
| 707 | }, | ||
| 708 | slash_commands: { | ||
| 709 | name: systemUserName, | ||
| 710 | force_avatar: system_avatar, | ||
| 711 | is_user: false, | ||
| 712 | is_system: true, | ||
| 713 | mes: '', | ||
| 714 | }, | ||
| 715 | hotkeys: { | ||
| 716 | name: systemUserName, | ||
| 717 | force_avatar: system_avatar, | ||
| 718 | is_user: false, | ||
| 719 | is_system: true, | ||
| 720 | mes: await renderTemplateAsync('hotkeys'), | ||
| 721 | }, | ||
| 722 | formatting: { | ||
| 723 | name: systemUserName, | ||
| 724 | force_avatar: system_avatar, | ||
| 725 | is_user: false, | ||
| 726 | is_system: true, | ||
| 727 | mes: await renderTemplateAsync('formatting'), | ||
| 728 | }, | ||
| 729 | macros: { | ||
| 730 | name: systemUserName, | ||
| 731 | force_avatar: system_avatar, | ||
| 732 | is_user: false, | ||
| 733 | is_system: true, | ||
| 734 | mes: await renderTemplateAsync('macros'), | ||
| 735 | }, | ||
| 736 | welcome: | ||
| 737 | { | ||
| 738 | name: systemUserName, | ||
| 739 | force_avatar: system_avatar, | ||
| 740 | is_user: false, | ||
| 741 | is_system: true, | ||
| 742 | uses_system_ui: true, | ||
| 743 | mes: await renderTemplateAsync('welcome', { displayVersion }), | ||
| 744 | }, | ||
| 745 | empty: { | ||
| 746 | name: systemUserName, | ||
| 747 | force_avatar: system_avatar, | ||
| 748 | is_user: false, | ||
| 749 | is_system: true, | ||
| 750 | mes: 'No one hears you. <b>Hint:</b> add more members to the group!', | ||
| 751 | }, | ||
| 752 | generic: { | ||
| 753 | name: systemUserName, | ||
| 754 | force_avatar: system_avatar, | ||
| 755 | is_user: false, | ||
| 756 | is_system: true, | ||
| 757 | mes: 'Generic system message. User `text` parameter to override the contents', | ||
| 758 | }, | ||
| 759 | welcome_prompt: { | ||
| 760 | name: systemUserName, | ||
| 761 | force_avatar: system_avatar, | ||
| 762 | is_user: false, | ||
| 763 | is_system: true, | ||
| 764 | uses_system_ui: true, | ||
| 765 | mes: await renderTemplateAsync('welcomePrompt'), | ||
| 766 | extra: { | ||
| 767 | isSmallSys: true, | ||
| 768 | }, | ||
| 769 | }, | ||
| 770 | assistant_note: { | ||
| 771 | name: systemUserName, | ||
| 772 | force_avatar: system_avatar, | ||
| 773 | is_user: false, | ||
| 774 | is_system: true, | ||
| 775 | mes: await renderTemplateAsync('assistantNote'), | ||
| 776 | uses_system_ui: true, | ||
| 777 | extra: { | ||
| 778 | isSmallSys: true, | ||
| 779 | }, | ||
| 780 | }, | ||
| 781 | }; | ||
| 782 | |||
| 783 | const safetyMessage = { | ||
| 784 | name: systemUserName, | ||
| 785 | force_avatar: system_avatar, | ||
| 786 | is_system: true, | ||
| 787 | is_user: false, | ||
| 788 | create_date: 0, | ||
| 789 | mes: t`You deleted a character/chat and arrived back here for safety reasons! Pick another character!`, | ||
| 790 | }; | ||
| 791 | SAFETY_CHAT.splice(0, SAFETY_CHAT.length, safetyMessage); | ||
| 792 | } | ||
| 793 | |||
| 794 | async function getClientVersion() { | 443 | async function getClientVersion() { |
| 795 | try { | 444 | try { |
| 796 | const response = await fetch('/version'); | 445 | const response = await fetch('/version'); |
| @@ -882,6 +531,7 @@ export let create_save = { | |||
| 882 | depth_prompt_depth: depth_prompt_depth_default, | 531 | depth_prompt_depth: depth_prompt_depth_default, |
| 883 | depth_prompt_role: depth_prompt_role_default, | 532 | depth_prompt_role: depth_prompt_role_default, |
| 884 | extensions: {}, | 533 | extensions: {}, |
| 534 | extra_books: [], | ||
| 885 | }; | 535 | }; |
| 886 | 536 | ||
| 887 | //animation right menu | 537 | //animation right menu |
| @@ -892,8 +542,6 @@ let popup_type = ''; | |||
| 892 | let chat_file_for_del = ''; | 542 | let chat_file_for_del = ''; |
| 893 | export let online_status = 'no_connection'; | 543 | export let online_status = 'no_connection'; |
| 894 | 544 | ||
| 895 | export let api_server = ''; | ||
| 896 | |||
| 897 | export let is_send_press = false; //Send generation | 545 | export let is_send_press = false; //Send generation |
| 898 | 546 | ||
| 899 | let this_del_mes = -1; | 547 | let this_del_mes = -1; |
| @@ -904,9 +552,6 @@ var this_edit_mes_id; | |||
| 904 | 552 | ||
| 905 | //settings | 553 | //settings |
| 906 | export let settings; | 554 | export let settings; |
| 907 | export let koboldai_settings; | ||
| 908 | export let koboldai_setting_names; | ||
| 909 | var preset_settings = 'gui'; | ||
| 910 | export let amount_gen = 80; //default max length of AI generated responses | 555 | export let amount_gen = 80; //default max length of AI generated responses |
| 911 | export let max_context = 2048; | 556 | export let max_context = 2048; |
| 912 | 557 | ||
| @@ -914,9 +559,6 @@ var swipes = true; | |||
| 914 | export let extension_prompts = {}; | 559 | export let extension_prompts = {}; |
| 915 | 560 | ||
| 916 | export let main_api;// = "kobold"; | 561 | export let main_api;// = "kobold"; |
| 917 | //novel settings | ||
| 918 | export let novelai_settings; | ||
| 919 | export let novelai_setting_names; | ||
| 920 | /** @type {AbortController} */ | 562 | /** @type {AbortController} */ |
| 921 | let abortController; | 563 | let abortController; |
| 922 | 564 | ||
| @@ -927,8 +569,6 @@ var kobold_horde_model = ''; | |||
| 927 | 569 | ||
| 928 | export let token; | 570 | export let token; |
| 929 | 571 | ||
| 930 | var PromptArrayItemForRawPromptDisplay; | ||
| 931 | var priorPromptArrayItemForRawPromptDisplay; | ||
| 932 | 572 | ||
| 933 | /** The tag of the active character. (NOT the id) */ | 573 | /** The tag of the active character. (NOT the id) */ |
| 934 | export let active_character = ''; | 574 | export let active_character = ''; |
| @@ -937,11 +577,17 @@ export let active_group = ''; | |||
| 937 | 577 | ||
| 938 | export const entitiesFilter = new FilterHelper(printCharactersDebounced); | 578 | export const entitiesFilter = new FilterHelper(printCharactersDebounced); |
| 939 | 579 | ||
| 940 | export function getRequestHeaders() { | 580 | export function getRequestHeaders({ omitContentType = false } = {}) { |
| 941 | return { | 581 | const headers = { |
| 942 | 'Content-Type': 'application/json', | 582 | 'Content-Type': 'application/json', |
| 943 | 'X-CSRF-Token': token, | 583 | 'X-CSRF-Token': token, |
| 944 | }; | 584 | }; |
| 585 | |||
| 586 | if (omitContentType) { | ||
| 587 | delete headers['Content-Type']; | ||
| 588 | } | ||
| 589 | |||
| 590 | return headers; | ||
| 945 | } | 591 | } |
| 946 | 592 | ||
| 947 | export function getSlideToggleOptions() { | 593 | export function getSlideToggleOptions() { |
| @@ -993,6 +639,7 @@ async function firstLoadInit() { | |||
| 993 | initStandaloneMode(); | 639 | initStandaloneMode(); |
| 994 | initLibraryShims(); | 640 | initLibraryShims(); |
| 995 | addShowdownPatch(showdown); | 641 | addShowdownPatch(showdown); |
| 642 | addDOMPurifyHooks(); | ||
| 996 | reloadMarkdownProcessor(); | 643 | reloadMarkdownProcessor(); |
| 997 | applyBrowserFixes(); | 644 | applyBrowserFixes(); |
| 998 | await getClientVersion(); | 645 | await getClientVersion(); |
| @@ -1003,12 +650,15 @@ async function firstLoadInit() { | |||
| 1003 | initDefaultSlashCommands(); | 650 | initDefaultSlashCommands(); |
| 1004 | initTextGenModels(); | 651 | initTextGenModels(); |
| 1005 | initOpenAI(); | 652 | initOpenAI(); |
| 653 | initTextGenSettings(); | ||
| 654 | initKoboldSettings(); | ||
| 655 | initNovelAISettings(); | ||
| 1006 | initSystemPrompts(); | 656 | initSystemPrompts(); |
| 1007 | initExtensions(); | 657 | initExtensions(); |
| 1008 | initExtensionSlashCommands(); | 658 | initExtensionSlashCommands(); |
| 1009 | ToolManager.initToolSlashCommands(); | 659 | ToolManager.initToolSlashCommands(); |
| 1010 | await initPresetManager(); | 660 | await initPresetManager(); |
| 1011 | await getSystemMessages(); | 661 | await initSystemMessages(); |
| 1012 | await getSettings(); | 662 | await getSettings(); |
| 1013 | initKeyboard(); | 663 | initKeyboard(); |
| 1014 | initDynamicStyles(); | 664 | initDynamicStyles(); |
| @@ -1037,6 +687,7 @@ async function firstLoadInit() { | |||
| 1037 | await initScrapers(); | 687 | await initScrapers(); |
| 1038 | initCustomSelectedSamplers(); | 688 | initCustomSelectedSamplers(); |
| 1039 | initDataMaid(); | 689 | initDataMaid(); |
| 690 | initItemizedPrompts(); | ||
| 1040 | addDebugFunctions(); | 691 | addDebugFunctions(); |
| 1041 | doDailyExtensionUpdatesCheck(); | 692 | doDailyExtensionUpdatesCheck(); |
| 1042 | await hideLoader(); | 693 | await hideLoader(); |
| @@ -1101,303 +752,6 @@ export function setActiveGroup(entityOrKey) { | |||
| 1101 | if (active_group) active_character = null; | 752 | if (active_group) active_character = null; |
| 1102 | } | 753 | } |
| 1103 | 754 | ||
| 1104 | /** | ||
| 1105 | * Gets the itemized prompts for a chat. | ||
| 1106 | * @param {string} chatId Chat ID to load | ||
| 1107 | */ | ||
| 1108 | export async function loadItemizedPrompts(chatId) { | ||
| 1109 | try { | ||
| 1110 | if (!chatId) { | ||
| 1111 | itemizedPrompts = []; | ||
| 1112 | return; | ||
| 1113 | } | ||
| 1114 | |||
| 1115 | itemizedPrompts = await promptStorage.getItem(chatId); | ||
| 1116 | |||
| 1117 | if (!itemizedPrompts) { | ||
| 1118 | itemizedPrompts = []; | ||
| 1119 | } | ||
| 1120 | } catch { | ||
| 1121 | console.log('Error loading itemized prompts for chat', chatId); | ||
| 1122 | itemizedPrompts = []; | ||
| 1123 | } | ||
| 1124 | } | ||
| 1125 | |||
| 1126 | /** | ||
| 1127 | * Saves the itemized prompts for a chat. | ||
| 1128 | * @param {string} chatId Chat ID to save itemized prompts for | ||
| 1129 | */ | ||
| 1130 | export async function saveItemizedPrompts(chatId) { | ||
| 1131 | try { | ||
| 1132 | if (!chatId) { | ||
| 1133 | return; | ||
| 1134 | } | ||
| 1135 | |||
| 1136 | await promptStorage.setItem(chatId, itemizedPrompts); | ||
| 1137 | } catch { | ||
| 1138 | console.log('Error saving itemized prompts for chat', chatId); | ||
| 1139 | } | ||
| 1140 | } | ||
| 1141 | |||
| 1142 | /** | ||
| 1143 | * Replaces the itemized prompt text for a message. | ||
| 1144 | * @param {number} mesId Message ID to get itemized prompt for | ||
| 1145 | * @param {string} promptText New raw prompt text | ||
| 1146 | * @returns | ||
| 1147 | */ | ||
| 1148 | export async function replaceItemizedPromptText(mesId, promptText) { | ||
| 1149 | if (!Array.isArray(itemizedPrompts)) { | ||
| 1150 | itemizedPrompts = []; | ||
| 1151 | } | ||
| 1152 | |||
| 1153 | const itemizedPrompt = itemizedPrompts.find(x => x.mesId === mesId); | ||
| 1154 | |||
| 1155 | if (!itemizedPrompt) { | ||
| 1156 | return; | ||
| 1157 | } | ||
| 1158 | |||
| 1159 | itemizedPrompt.rawPrompt = promptText; | ||
| 1160 | } | ||
| 1161 | |||
| 1162 | /** | ||
| 1163 | * Deletes the itemized prompts for a chat. | ||
| 1164 | * @param {string} chatId Chat ID to delete itemized prompts for | ||
| 1165 | */ | ||
| 1166 | export async function deleteItemizedPrompts(chatId) { | ||
| 1167 | try { | ||
| 1168 | if (!chatId) { | ||
| 1169 | return; | ||
| 1170 | } | ||
| 1171 | |||
| 1172 | await promptStorage.removeItem(chatId); | ||
| 1173 | } catch { | ||
| 1174 | console.log('Error deleting itemized prompts for chat', chatId); | ||
| 1175 | } | ||
| 1176 | } | ||
| 1177 | |||
| 1178 | /** | ||
| 1179 | * Empties the itemized prompts array and caches. | ||
| 1180 | */ | ||
| 1181 | export async function clearItemizedPrompts() { | ||
| 1182 | try { | ||
| 1183 | await promptStorage.clear(); | ||
| 1184 | itemizedPrompts = []; | ||
| 1185 | } catch { | ||
| 1186 | console.log('Error clearing itemized prompts'); | ||
| 1187 | } | ||
| 1188 | } | ||
| 1189 | |||
| 1190 | async function getStatusHorde() { | ||
| 1191 | try { | ||
| 1192 | const hordeStatus = await checkHordeStatus(); | ||
| 1193 | setOnlineStatus(hordeStatus ? t`Connected` : 'no_connection'); | ||
| 1194 | } | ||
| 1195 | catch { | ||
| 1196 | setOnlineStatus('no_connection'); | ||
| 1197 | } | ||
| 1198 | |||
| 1199 | return resultCheckStatus(); | ||
| 1200 | } | ||
| 1201 | |||
| 1202 | async function getStatusKobold() { | ||
| 1203 | let endpoint = api_server; | ||
| 1204 | |||
| 1205 | if (!endpoint) { | ||
| 1206 | console.warn('No endpoint for status check'); | ||
| 1207 | setOnlineStatus('no_connection'); | ||
| 1208 | return resultCheckStatus(); | ||
| 1209 | } | ||
| 1210 | |||
| 1211 | try { | ||
| 1212 | const response = await fetch('/api/backends/kobold/status', { | ||
| 1213 | method: 'POST', | ||
| 1214 | headers: getRequestHeaders(), | ||
| 1215 | body: JSON.stringify({ | ||
| 1216 | main_api, | ||
| 1217 | api_server: endpoint, | ||
| 1218 | }), | ||
| 1219 | signal: abortStatusCheck.signal, | ||
| 1220 | }); | ||
| 1221 | |||
| 1222 | const data = await response.json(); | ||
| 1223 | |||
| 1224 | setOnlineStatus(data?.model ?? 'no_connection'); | ||
| 1225 | |||
| 1226 | if (!data.koboldUnitedVersion) { | ||
| 1227 | throw new Error('Missing mandatory Kobold version in data:', data); | ||
| 1228 | } | ||
| 1229 | |||
| 1230 | // Determine instruct mode preset | ||
| 1231 | autoSelectInstructPreset(online_status); | ||
| 1232 | |||
| 1233 | // determine if we can use stop sequence and streaming | ||
| 1234 | setKoboldFlags(data.koboldUnitedVersion, data.koboldCppVersion); | ||
| 1235 | |||
| 1236 | // We didn't get a 200 status code, but the endpoint has an explanation. Which means it DID connect, but I digress. | ||
| 1237 | if (online_status === 'no_connection' && data.response) { | ||
| 1238 | toastr.error(data.response, t`API Error`, { timeOut: 5000, preventDuplicates: true }); | ||
| 1239 | } | ||
| 1240 | } catch (err) { | ||
| 1241 | console.error('Error getting status', err); | ||
| 1242 | setOnlineStatus('no_connection'); | ||
| 1243 | } | ||
| 1244 | |||
| 1245 | return resultCheckStatus(); | ||
| 1246 | } | ||
| 1247 | |||
| 1248 | async function getStatusTextgen() { | ||
| 1249 | const url = '/api/backends/text-completions/status'; | ||
| 1250 | |||
| 1251 | const endpoint = getTextGenServer(); | ||
| 1252 | |||
| 1253 | if (!endpoint) { | ||
| 1254 | console.warn('No endpoint for status check'); | ||
| 1255 | setOnlineStatus('no_connection'); | ||
| 1256 | return resultCheckStatus(); | ||
| 1257 | } | ||
| 1258 | |||
| 1259 | if ([textgen_types.GENERIC, textgen_types.OOBA].includes(textgen_settings.type) && textgen_settings.bypass_status_check) { | ||
| 1260 | setOnlineStatus(t`Status check bypassed`); | ||
| 1261 | return resultCheckStatus(); | ||
| 1262 | } | ||
| 1263 | |||
| 1264 | try { | ||
| 1265 | const response = await fetch(url, { | ||
| 1266 | method: 'POST', | ||
| 1267 | headers: getRequestHeaders(), | ||
| 1268 | body: JSON.stringify({ | ||
| 1269 | api_server: endpoint, | ||
| 1270 | api_type: textgen_settings.type, | ||
| 1271 | }), | ||
| 1272 | signal: abortStatusCheck.signal, | ||
| 1273 | }); | ||
| 1274 | |||
| 1275 | const data = await response.json(); | ||
| 1276 | |||
| 1277 | if (textgen_settings.type === textgen_types.MANCER) { | ||
| 1278 | loadMancerModels(data?.data); | ||
| 1279 | setOnlineStatus(textgen_settings.mancer_model); | ||
| 1280 | } else if (textgen_settings.type === textgen_types.TOGETHERAI) { | ||
| 1281 | loadTogetherAIModels(data?.data); | ||
| 1282 | setOnlineStatus(textgen_settings.togetherai_model); | ||
| 1283 | } else if (textgen_settings.type === textgen_types.OLLAMA) { | ||
| 1284 | loadOllamaModels(data?.data); | ||
| 1285 | setOnlineStatus(textgen_settings.ollama_model || t`Connected`); | ||
| 1286 | } else if (textgen_settings.type === textgen_types.INFERMATICAI) { | ||
| 1287 | loadInfermaticAIModels(data?.data); | ||
| 1288 | setOnlineStatus(textgen_settings.infermaticai_model); | ||
| 1289 | } else if (textgen_settings.type === textgen_types.DREAMGEN) { | ||
| 1290 | loadDreamGenModels(data?.data); | ||
| 1291 | setOnlineStatus(textgen_settings.dreamgen_model); | ||
| 1292 | } else if (textgen_settings.type === textgen_types.OPENROUTER) { | ||
| 1293 | loadOpenRouterModels(data?.data); | ||
| 1294 | setOnlineStatus(textgen_settings.openrouter_model); | ||
| 1295 | } else if (textgen_settings.type === textgen_types.VLLM) { | ||
| 1296 | loadVllmModels(data?.data); | ||
| 1297 | setOnlineStatus(textgen_settings.vllm_model); | ||
| 1298 | } else if (textgen_settings.type === textgen_types.APHRODITE) { | ||
| 1299 | loadAphroditeModels(data?.data); | ||
| 1300 | setOnlineStatus(textgen_settings.aphrodite_model); | ||
| 1301 | } else if (textgen_settings.type === textgen_types.FEATHERLESS) { | ||
| 1302 | loadFeatherlessModels(data?.data); | ||
| 1303 | setOnlineStatus(textgen_settings.featherless_model); | ||
| 1304 | } else if (textgen_settings.type === textgen_types.TABBY) { | ||
| 1305 | loadTabbyModels(data?.data); | ||
| 1306 | setOnlineStatus(textgen_settings.tabby_model || data?.result); | ||
| 1307 | } else if (textgen_settings.type === textgen_types.GENERIC) { | ||
| 1308 | loadGenericModels(data?.data); | ||
| 1309 | setOnlineStatus(textgen_settings.generic_model || data?.result || t`Connected`); | ||
| 1310 | } else { | ||
| 1311 | setOnlineStatus(data?.result); | ||
| 1312 | } | ||
| 1313 | |||
| 1314 | if (!online_status) { | ||
| 1315 | setOnlineStatus('no_connection'); | ||
| 1316 | } | ||
| 1317 | |||
| 1318 | // Determine instruct mode preset | ||
| 1319 | autoSelectInstructPreset(online_status); | ||
| 1320 | |||
| 1321 | const supportsTokenization = response.headers.get('x-supports-tokenization') === 'true'; | ||
| 1322 | supportsTokenization ? sessionStorage.setItem(TOKENIZER_SUPPORTED_KEY, 'true') : sessionStorage.removeItem(TOKENIZER_SUPPORTED_KEY); | ||
| 1323 | |||
| 1324 | const wantsInstructDerivation = (power_user.instruct.enabled && power_user.instruct.derived); | ||
| 1325 | const wantsContextDerivation = power_user.context_derived; | ||
| 1326 | const wantsContextSize = power_user.context_size_derived; | ||
| 1327 | const supportsChatTemplate = [textgen_types.KOBOLDCPP, textgen_types.LLAMACPP].includes(textgen_settings.type); | ||
| 1328 | if (supportsChatTemplate && (wantsInstructDerivation || wantsContextDerivation || wantsContextSize)) { | ||
| 1329 | const response = await fetch('/api/backends/text-completions/props', { | ||
| 1330 | method: 'POST', | ||
| 1331 | headers: getRequestHeaders(), | ||
| 1332 | body: JSON.stringify({ | ||
| 1333 | api_server: endpoint, | ||
| 1334 | api_type: textgen_settings.type, | ||
| 1335 | }), | ||
| 1336 | }); | ||
| 1337 | |||
| 1338 | if (response.ok) { | ||
| 1339 | const data = await response.json(); | ||
| 1340 | if (data) { | ||
| 1341 | const { chat_template, chat_template_hash } = data; | ||
| 1342 | if (wantsContextSize && 'default_generation_settings' in data) { | ||
| 1343 | const backend_max_context = data['default_generation_settings']['n_ctx']; | ||
| 1344 | const old_value = max_context; | ||
| 1345 | if (max_context !== backend_max_context) { | ||
| 1346 | setGenerationParamsFromPreset({ max_length: backend_max_context }); | ||
| 1347 | } | ||
| 1348 | if (old_value !== max_context) { | ||
| 1349 | console.log(`Auto-switched max context from ${old_value} to ${max_context}`); | ||
| 1350 | toastr.info(`${old_value} ⇒ ${max_context}`, 'Context Size Changed'); | ||
| 1351 | } | ||
| 1352 | } | ||
| 1353 | console.log(`We have chat template ${chat_template.split('\n')[0]}...`); | ||
| 1354 | const templates = await deriveTemplatesFromChatTemplate(chat_template, chat_template_hash); | ||
| 1355 | if (templates) { | ||
| 1356 | const { context, instruct } = templates; | ||
| 1357 | if (wantsContextDerivation) { | ||
| 1358 | selectContextPreset(context, { isAuto: true }); | ||
| 1359 | } | ||
| 1360 | if (wantsInstructDerivation) { | ||
| 1361 | selectInstructPreset(instruct, { isAuto: true }); | ||
| 1362 | } | ||
| 1363 | } | ||
| 1364 | } | ||
| 1365 | } | ||
| 1366 | } | ||
| 1367 | |||
| 1368 | // We didn't get a 200 status code, but the endpoint has an explanation. Which means it DID connect, but I digress. | ||
| 1369 | if (online_status === 'no_connection' && data.response) { | ||
| 1370 | toastr.error(data.response, t`API Error`, { timeOut: 5000, preventDuplicates: true }); | ||
| 1371 | } | ||
| 1372 | } catch (err) { | ||
| 1373 | if (err instanceof AbortReason) { | ||
| 1374 | console.info('Status check aborted.', err.reason); | ||
| 1375 | } else { | ||
| 1376 | console.error('Error getting status', err); | ||
| 1377 | |||
| 1378 | } | ||
| 1379 | setOnlineStatus('no_connection'); | ||
| 1380 | } | ||
| 1381 | |||
| 1382 | return resultCheckStatus(); | ||
| 1383 | } | ||
| 1384 | |||
| 1385 | async function getStatusNovel() { | ||
| 1386 | try { | ||
| 1387 | const result = await loadNovelSubscriptionData(); | ||
| 1388 | |||
| 1389 | if (!result) { | ||
| 1390 | throw new Error('Could not load subscription data'); | ||
| 1391 | } | ||
| 1392 | |||
| 1393 | setOnlineStatus(getNovelTier()); | ||
| 1394 | } catch { | ||
| 1395 | setOnlineStatus('no_connection'); | ||
| 1396 | } | ||
| 1397 | |||
| 1398 | resultCheckStatus(); | ||
| 1399 | } | ||
| 1400 | |||
| 1401 | export function startStatusLoading() { | 755 | export function startStatusLoading() { |
| 1402 | $('.api_loading').show(); | 756 | $('.api_loading').show(); |
| 1403 | $('.api_button').addClass('disabled'); | 757 | $('.api_button').addClass('disabled'); |
| @@ -1836,6 +1190,12 @@ function getCharacterSource(chId = this_chid) { | |||
| 1836 | return `https://realm.risuai.net/character/${realmId}`; | 1190 | return `https://realm.risuai.net/character/${realmId}`; |
| 1837 | } | 1191 | } |
| 1838 | 1192 | ||
| 1193 | const perchanceSlug = characters[chId]?.data?.extensions?.perchance_data?.slug; | ||
| 1194 | |||
| 1195 | if (perchanceSlug) { | ||
| 1196 | return `https://perchance.org/ai-character-chat?data=${perchanceSlug}`; | ||
| 1197 | } | ||
| 1198 | |||
| 1839 | return ''; | 1199 | return ''; |
| 1840 | } | 1200 | } |
| 1841 | 1201 | ||
| @@ -1845,7 +1205,7 @@ export async function getCharacters() { | |||
| 1845 | headers: getRequestHeaders(), | 1205 | headers: getRequestHeaders(), |
| 1846 | body: JSON.stringify({}), | 1206 | body: JSON.stringify({}), |
| 1847 | }); | 1207 | }); |
| 1848 | if (response.ok === true) { | 1208 | if (response.ok) { |
| 1849 | const previousAvatar = this_chid !== undefined ? characters[this_chid]?.avatar : null; | 1209 | const previousAvatar = this_chid !== undefined ? characters[this_chid]?.avatar : null; |
| 1850 | characters.splice(0, characters.length); | 1210 | characters.splice(0, characters.length); |
| 1851 | const getData = await response.json(); | 1211 | const getData = await response.json(); |
| @@ -1874,6 +1234,12 @@ export async function getCharacters() { | |||
| 1874 | 1234 | ||
| 1875 | await getGroups(); | 1235 | await getGroups(); |
| 1876 | await printCharacters(true); | 1236 | await printCharacters(true); |
| 1237 | } else { | ||
| 1238 | console.error('Failed to fetch characters:', response.statusText); | ||
| 1239 | const errorData = await response.json(); | ||
| 1240 | if (errorData?.overflow) { | ||
| 1241 | await Popup.show.text(t`Character data length limit reached`, t`To resolve this, set "performance.lazyLoadCharacters" to "true" in config.yaml and restart the server.`); | ||
| 1242 | } | ||
| 1877 | } | 1243 | } |
| 1878 | } | 1244 | } |
| 1879 | 1245 | ||
| @@ -2080,7 +1446,7 @@ export async function clearChat() { | |||
| 2080 | } else { console.debug('saw no avatars'); } | 1446 | } else { console.debug('saw no avatars'); } |
| 2081 | 1447 | ||
| 2082 | await saveItemizedPrompts(getCurrentChatId()); | 1448 | await saveItemizedPrompts(getCurrentChatId()); |
| 2083 | itemizedPrompts = []; | 1449 | itemizedPrompts.length = 0; |
| 2084 | } | 1450 | } |
| 2085 | 1451 | ||
| 2086 | export async function deleteLastMessage() { | 1452 | export async function deleteLastMessage() { |
| @@ -2496,20 +1862,20 @@ export function appendMediaToMessage(mes, messageElement, adjustScroll = true) { | |||
| 2496 | eventSource.emit(event_types.IMAGE_SWIPED, { message: mes, element: messageElement, direction: 'right' }); | 1862 | eventSource.emit(event_types.IMAGE_SWIPED, { message: mes, element: messageElement, direction: 'right' }); |
| 2497 | }); | 1863 | }); |
| 2498 | } | 1864 | } |
| 1865 | } else { | ||
| 1866 | const container = messageElement.find('.mes_img_container'); | ||
| 1867 | container.removeClass('img_extra img_swipes'); | ||
| 1868 | const text = messageElement.find('.mes_text'); | ||
| 1869 | text.removeClass('displayNone'); | ||
| 2499 | } | 1870 | } |
| 2500 | 1871 | ||
| 2501 | // Add video to message | 1872 | // Add video to message |
| 2502 | if (mes.extra?.video) { | 1873 | if (mes.extra?.video) { |
| 2503 | const container = messageElement.find('.mes_block'); | 1874 | const container = $('#message_video_template .mes_video_container').clone(); |
| 1875 | messageElement.find('.mes_video_container').remove(); | ||
| 1876 | messageElement.find('.mes_block').append(container); | ||
| 2504 | const chatHeight = $('#chat').prop('scrollHeight'); | 1877 | const chatHeight = $('#chat').prop('scrollHeight'); |
| 2505 | 1878 | const video = container.find('.mes_video'); | |
| 2506 | // Create video element if it doesn't exist | ||
| 2507 | let video = messageElement.find('.mes_video'); | ||
| 2508 | if (video.length === 0) { | ||
| 2509 | video = $('<video class="mes_video" controls preload="metadata"></video>'); | ||
| 2510 | container.append(video); | ||
| 2511 | } | ||
| 2512 | |||
| 2513 | video.off('loadedmetadata').on('loadedmetadata', function () { | 1879 | video.off('loadedmetadata').on('loadedmetadata', function () { |
| 2514 | if (!adjustScroll) { | 1880 | if (!adjustScroll) { |
| 2515 | return; | 1881 | return; |
| @@ -2521,6 +1887,8 @@ export function appendMediaToMessage(mes, messageElement, adjustScroll = true) { | |||
| 2521 | }); | 1887 | }); |
| 2522 | 1888 | ||
| 2523 | video.attr('src', mes.extra?.video); | 1889 | video.attr('src', mes.extra?.video); |
| 1890 | } else { | ||
| 1891 | messageElement.find('.mes_video_container').remove(); | ||
| 2524 | } | 1892 | } |
| 2525 | 1893 | ||
| 2526 | // Add file to message | 1894 | // Add file to message |
| @@ -2553,6 +1921,9 @@ export function addCopyToCodeBlocks(messageElement) { | |||
| 2553 | copyButton.classList.add('fa-solid', 'fa-copy', 'code-copy', 'interactable'); | 1921 | copyButton.classList.add('fa-solid', 'fa-copy', 'code-copy', 'interactable'); |
| 2554 | copyButton.title = 'Copy code'; | 1922 | copyButton.title = 'Copy code'; |
| 2555 | codeBlocks.get(i).appendChild(copyButton); | 1923 | codeBlocks.get(i).appendChild(copyButton); |
| 1924 | copyButton.addEventListener('click', function (e) { | ||
| 1925 | e.stopPropagation(); | ||
| 1926 | }); | ||
| 2556 | copyButton.addEventListener('pointerup', async function () { | 1927 | copyButton.addEventListener('pointerup', async function () { |
| 2557 | const text = codeBlocks.get(i).innerText; | 1928 | const text = codeBlocks.get(i).innerText; |
| 2558 | await copyText(text); | 1929 | await copyText(text); |
| @@ -2590,7 +1961,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll | |||
| 2590 | mes.swipes = [mes.mes]; | 1961 | mes.swipes = [mes.mes]; |
| 2591 | } | 1962 | } |
| 2592 | 1963 | ||
| 2593 | let avatarImg = getUserAvatar(user_avatar); | 1964 | let avatarImg = getThumbnailUrl('persona', user_avatar); |
| 2594 | const isSystem = mes.is_system; | 1965 | const isSystem = mes.is_system; |
| 2595 | const title = mes.title; | 1966 | const title = mes.title; |
| 2596 | generatedPromptCache = ''; | 1967 | generatedPromptCache = ''; |
| @@ -2740,6 +2111,8 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll | |||
| 2740 | if (!insertAfter && !insertBefore && scroll) { | 2111 | if (!insertAfter && !insertBefore && scroll) { |
| 2741 | scrollChatToBottom(); | 2112 | scrollChatToBottom(); |
| 2742 | } | 2113 | } |
| 2114 | |||
| 2115 | applyCharacterTagsToMessageDivs({ mesIds: newMessageId }); | ||
| 2743 | } | 2116 | } |
| 2744 | 2117 | ||
| 2745 | /** | 2118 | /** |
| @@ -2963,35 +2336,43 @@ export function getStoppingStrings(isImpersonate, isContinue) { | |||
| 2963 | 2336 | ||
| 2964 | /** | 2337 | /** |
| 2965 | * Background generation based on the provided prompt. | 2338 | * Background generation based on the provided prompt. |
| 2966 | * @param {string} quiet_prompt Instruction prompt for the AI | 2339 | * @typedef {object} GenerateQuietPromptParams |
| 2967 | * @param {boolean} quietToLoud Whether the message should be sent in a foreground (loud) or background (quiet) mode | 2340 | * @prop {string} [quietPrompt] Instruction prompt for the AI |
| 2968 | * @param {boolean} skipWIAN whether to skip addition of World Info and Author's Note into the prompt | 2341 | * @prop {boolean} [quietToLoud] Whether the message should be sent in a foreground (loud) or background (quiet) mode |
| 2969 | * @param {string} quietImage Image to use for the quiet prompt | 2342 | * @prop {boolean} [skipWIAN] Whether to skip addition of World Info and Author's Note into the prompt |
| 2970 | * @param {string} quietName Name to use for the quiet prompt (defaults to "System:") | 2343 | * @prop {string} [quietImage] Image to use for the quiet prompt |
| 2971 | * @param {number} [responseLength] Maximum response length. If unset, the global default value is used. | 2344 | * @prop {string} [quietName] Name to use for the quiet prompt (defaults to "System:") |
| 2972 | * @param {number} force_chid Character ID to use for this generation run. Works in groups only. | 2345 | * @prop {number} [responseLength] Maximum response length. If unset, the global default value is used. |
| 2973 | * @returns | 2346 | * @prop {number} [forceChId] Character ID to use for this generation run. Works in groups only. |
| 2347 | * @prop {object} [jsonSchema] JSON schema to use for the structured generation. Usually requires a special instruction. | ||
| 2348 | * @param {GenerateQuietPromptParams} params Parameters for the quiet prompt generation | ||
| 2349 | * @returns {Promise<string>} Generated text. If using structured output, will contain a serialized JSON object. | ||
| 2974 | */ | 2350 | */ |
| 2975 | export async function generateQuietPrompt(quiet_prompt, quietToLoud, skipWIAN, quietImage = null, quietName = null, responseLength = null, force_chid = null) { | 2351 | export async function generateQuietPrompt({ quietPrompt = '', quietToLoud = false, skipWIAN = false, quietImage = null, quietName = null, responseLength = null, forceChId = null, jsonSchema = null } = {}) { |
| 2976 | console.log('got into genQuietPrompt'); | 2352 | if (arguments.length > 0 && typeof arguments[0] !== 'object') { |
| 2353 | console.trace('generateQuietPrompt called with positional arguments. Please use an object instead.'); | ||
| 2354 | [quietPrompt, quietToLoud, skipWIAN, quietImage, quietName, responseLength, forceChId, jsonSchema] = arguments; | ||
| 2355 | } | ||
| 2356 | |||
| 2977 | const responseLengthCustomized = typeof responseLength === 'number' && responseLength > 0; | 2357 | const responseLengthCustomized = typeof responseLength === 'number' && responseLength > 0; |
| 2978 | let eventHook = () => { }; | 2358 | let eventHook = () => { }; |
| 2979 | try { | 2359 | try { |
| 2980 | /** @type {GenerateOptions} */ | 2360 | /** @type {GenerateOptions} */ |
| 2981 | const options = { | 2361 | const generateOptions = { |
| 2982 | quiet_prompt, | 2362 | quiet_prompt: quietPrompt ?? '', |
| 2983 | quietToLoud, | 2363 | quietToLoud: quietToLoud ?? false, |
| 2984 | skipWIAN: skipWIAN, | 2364 | skipWIAN: skipWIAN ?? false, |
| 2985 | force_name2: true, | 2365 | force_name2: true, |
| 2986 | quietImage: quietImage, | 2366 | quietImage: quietImage ?? null, |
| 2987 | quietName: quietName, | 2367 | quietName: quietName ?? null, |
| 2988 | force_chid: force_chid, | 2368 | force_chid: forceChId ?? null, |
| 2369 | jsonSchema: jsonSchema ?? null, | ||
| 2989 | }; | 2370 | }; |
| 2990 | if (responseLengthCustomized) { | 2371 | if (responseLengthCustomized) { |
| 2991 | TempResponseLength.save(main_api, responseLength); | 2372 | TempResponseLength.save(main_api, responseLength); |
| 2992 | eventHook = TempResponseLength.setupEventHook(main_api); | 2373 | eventHook = TempResponseLength.setupEventHook(main_api); |
| 2993 | } | 2374 | } |
| 2994 | const result = await Generate('quiet', options); | 2375 | const result = await Generate('quiet', generateOptions); |
| 2995 | return removeReasoningFromString(result); | 2376 | return removeReasoningFromString(result); |
| 2996 | } finally { | 2377 | } finally { |
| 2997 | if (responseLengthCustomized && TempResponseLength.isCustomized()) { | 2378 | if (responseLengthCustomized && TempResponseLength.isCustomized()) { |
| @@ -3017,67 +2398,13 @@ export async function processCommands(message) { | |||
| 3017 | } | 2398 | } |
| 3018 | 2399 | ||
| 3019 | /** | 2400 | /** |
| 3020 | * Gets a system message by type. | 2401 | * Extracts the contents of bias macros from a message. |
| 3021 | * @param {string} type Type of system message | 2402 | * @param {string} message Message text |
| 3022 | * @param {string} [text] Text to be sent | 2403 | * @returns {string} Message bias extracted from the message (or an empty string if not found) |
| 3023 | * @param {object} [extra] Additional data to be added to the message | ||
| 3024 | * @returns {object} System message object | ||
| 3025 | */ | 2404 | */ |
| 3026 | export function getSystemMessageByType(type, text, extra = {}) { | 2405 | export function extractMessageBias(message) { |
| 3027 | const systemMessage = system_messages[type]; | 2406 | if (!message) { |
| 3028 | 2407 | return ''; | |
| 3029 | if (!systemMessage) { | ||
| 3030 | return; | ||
| 3031 | } | ||
| 3032 | |||
| 3033 | const newMessage = { ...systemMessage, send_date: getMessageTimeStamp() }; | ||
| 3034 | |||
| 3035 | if (text) { | ||
| 3036 | newMessage.mes = text; | ||
| 3037 | } | ||
| 3038 | |||
| 3039 | if (type === system_message_types.SLASH_COMMANDS) { | ||
| 3040 | newMessage.mes = getSlashCommandsHelp(); | ||
| 3041 | } | ||
| 3042 | |||
| 3043 | if (!newMessage.extra) { | ||
| 3044 | newMessage.extra = {}; | ||
| 3045 | } | ||
| 3046 | |||
| 3047 | newMessage.extra = Object.assign(newMessage.extra, extra); | ||
| 3048 | newMessage.extra.type = type; | ||
| 3049 | return newMessage; | ||
| 3050 | } | ||
| 3051 | |||
| 3052 | /** | ||
| 3053 | * Sends a system message to the chat. | ||
| 3054 | * @param {string} type Type of system message | ||
| 3055 | * @param {string} [text] Text to be sent | ||
| 3056 | * @param {object} [extra] Additional data to be added to the message | ||
| 3057 | */ | ||
| 3058 | export function sendSystemMessage(type, text, extra = {}) { | ||
| 3059 | const newMessage = getSystemMessageByType(type, text, extra); | ||
| 3060 | chat.push(newMessage); | ||
| 3061 | addOneMessage(newMessage); | ||
| 3062 | is_send_press = false; | ||
| 3063 | if (type === system_message_types.SLASH_COMMANDS) { | ||
| 3064 | const browser = new SlashCommandBrowser(); | ||
| 3065 | const spinner = document.querySelector('#chat .last_mes .custom-slashHelp'); | ||
| 3066 | const parent = spinner.parentElement; | ||
| 3067 | spinner.remove(); | ||
| 3068 | browser.renderInto(parent); | ||
| 3069 | browser.search.focus(); | ||
| 3070 | } | ||
| 3071 | } | ||
| 3072 | |||
| 3073 | /** | ||
| 3074 | * Extracts the contents of bias macros from a message. | ||
| 3075 | * @param {string} message Message text | ||
| 3076 | * @returns {string} Message bias extracted from the message (or an empty string if not found) | ||
| 3077 | */ | ||
| 3078 | export function extractMessageBias(message) { | ||
| 3079 | if (!message) { | ||
| 3080 | return ''; | ||
| 3081 | } | 2408 | } |
| 3082 | 2409 | ||
| 3083 | try { | 2410 | try { |
| @@ -3368,11 +2695,9 @@ export function parseMesExamples(examplesStr, isInstruct) { | |||
| 3368 | } | 2695 | } |
| 3369 | 2696 | ||
| 3370 | export function isStreamingEnabled() { | 2697 | export function isStreamingEnabled() { |
| 3371 | const noStreamSources = [chat_completion_sources.SCALE]; | ||
| 3372 | return ( | 2698 | return ( |
| 3373 | (main_api == 'openai' && | 2699 | (main_api == 'openai' && |
| 3374 | oai_settings.stream_openai && | 2700 | oai_settings.stream_openai && |
| 3375 | !noStreamSources.includes(oai_settings.chat_completion_source) && | ||
| 3376 | !(oai_settings.chat_completion_source == chat_completion_sources.OPENAI && ['o1-2024-12-17', 'o1'].includes(oai_settings.openai_model)) | 2701 | !(oai_settings.chat_completion_source == chat_completion_sources.OPENAI && ['o1-2024-12-17', 'o1'].includes(oai_settings.openai_model)) |
| 3377 | ) | 2702 | ) |
| 3378 | || (main_api == 'kobold' && kai_settings.streaming_kobold && kai_flags.can_use_streaming) | 2703 | || (main_api == 'kobold' && kai_settings.streaming_kobold && kai_flags.can_use_streaming) |
| @@ -3547,7 +2872,7 @@ class StreamingProcessor { | |||
| 3547 | 2872 | ||
| 3548 | // Token count update. | 2873 | // Token count update. |
| 3549 | const tokenCountText = this.reasoningHandler.reasoning + processedText; | 2874 | const tokenCountText = this.reasoningHandler.reasoning + processedText; |
| 3550 | const currentTokenCount = isFinal && power_user.message_token_count_enabled ? getTokenCount(tokenCountText, 0) : 0; | 2875 | const currentTokenCount = isFinal && power_user.message_token_count_enabled ? await getTokenCountAsync(tokenCountText, 0) : 0; |
| 3551 | if (currentTokenCount) { | 2876 | if (currentTokenCount) { |
| 3552 | chat[messageId]['extra']['token_count'] = currentTokenCount; | 2877 | chat[messageId]['extra']['token_count'] = currentTokenCount; |
| 3553 | if (this.messageTokenCounterDom instanceof HTMLElement) { | 2878 | if (this.messageTokenCounterDom instanceof HTMLElement) { |
| @@ -3561,7 +2886,7 @@ class StreamingProcessor { | |||
| 3561 | 'send_date': chat[messageId]['send_date'], | 2886 | 'send_date': chat[messageId]['send_date'], |
| 3562 | 'gen_started': chat[messageId]['gen_started'], | 2887 | 'gen_started': chat[messageId]['gen_started'], |
| 3563 | 'gen_finished': chat[messageId]['gen_finished'], | 2888 | 'gen_finished': chat[messageId]['gen_finished'], |
| 3564 | 'extra': JSON.parse(JSON.stringify(chat[messageId]['extra'])), | 2889 | 'extra': structuredClone(chat[messageId]['extra']), |
| 3565 | }; | 2890 | }; |
| 3566 | } | 2891 | } |
| 3567 | 2892 | ||
| @@ -3666,7 +2991,7 @@ class StreamingProcessor { | |||
| 3666 | 'send_date': chat[messageId]['send_date'], | 2991 | 'send_date': chat[messageId]['send_date'], |
| 3667 | 'gen_started': chat[messageId]['gen_started'], | 2992 | 'gen_started': chat[messageId]['gen_started'], |
| 3668 | 'gen_finished': chat[messageId]['gen_finished'], | 2993 | 'gen_finished': chat[messageId]['gen_finished'], |
| 3669 | 'extra': JSON.parse(JSON.stringify(chat[messageId]['extra'])), | 2994 | 'extra': structuredClone(chat[messageId]['extra']), |
| 3670 | }; | 2995 | }; |
| 3671 | } | 2996 | } |
| 3672 | } | 2997 | } |
| @@ -3740,37 +3065,101 @@ class StreamingProcessor { | |||
| 3740 | } | 3065 | } |
| 3741 | 3066 | ||
| 3742 | /** | 3067 | /** |
| 3743 | * Generates a message using the provided prompt. | 3068 | * Constructs a prompt to be used for either Text Completion or Chat Completion. Input is format-agnostic. |
| 3744 | * @param {string} prompt Prompt to generate a message from | 3069 | * @param {string | object[]} prompt Input prompt. Can be a string or an array of chat-style messages, i.e. [{role: '', content: ''}, ...] |
| 3745 | * @param {string} api API to use. Main API is used if not specified. | 3070 | * @param {string} api API to use. |
| 3746 | * @param {boolean} instructOverride true to override instruct mode, false to use the default value | 3071 | * @param {boolean} instructOverride true to override instruct mode, false to use the default value |
| 3747 | * @param {boolean} quietToLoud true to generate a message in system mode, false to generate a message in character mode | 3072 | * @param {boolean} quietToLoud true to generate a message in system mode, false to generate a message in character mode |
| 3748 | * @param {string} [systemPrompt] System prompt to use. Only Instruct mode or OpenAI. | 3073 | * @param {string} [systemPrompt] System prompt to use. |
| 3749 | * @param {number} [responseLength] Maximum response length. If unset, the global default value is used. | 3074 | * @param {string} [prefill] Prefill for the prompt. |
| 3750 | * @param {boolean} [trimNames] Whether to allow trimming "{{user}}:" and "{{char}}:" from the response. | 3075 | * @returns {string | object[]} Prompt ready for use in generation. If using TC, this will be a string. If using CC, this will be an array of chat-style messages. |
| 3076 | */ | ||
| 3077 | export function createRawPrompt(prompt, api, instructOverride, quietToLoud, systemPrompt, prefill) { | ||
| 3078 | const isInstruct = power_user.instruct.enabled && api !== 'openai' && api !== 'novel' && !instructOverride; | ||
| 3079 | |||
| 3080 | // If the prompt was given as a string, convert to a message-style object assuming user role | ||
| 3081 | if (typeof prompt === 'string') { | ||
| 3082 | const message = api === 'openai' | ||
| 3083 | ? { role: 'user', content: prompt.trim() } | ||
| 3084 | : { role: 'system', content: prompt }; | ||
| 3085 | prompt = [message]; | ||
| 3086 | } else { // checks for message-style object | ||
| 3087 | if (prompt.length === 0 && !systemPrompt) throw Error('No messages provided'); | ||
| 3088 | } | ||
| 3089 | |||
| 3090 | // Substitute the prefill if provided | ||
| 3091 | prefill = substituteParams(prefill ?? ''); | ||
| 3092 | |||
| 3093 | // Format each message in the prompt, accounting for the provided roles | ||
| 3094 | for (const message of prompt) { | ||
| 3095 | let name = ''; | ||
| 3096 | if (message.role === 'user') name = message.name ?? name1; | ||
| 3097 | if (message.role === 'assistant') name = message.name ?? name2; | ||
| 3098 | if (message.role === 'system') name = message.name ?? ''; | ||
| 3099 | const prefix = isInstruct || api === 'openai' ? '' : (name ? `${name}: ` : ''); | ||
| 3100 | message.content = prefix + substituteParams(message.content ?? ''); | ||
| 3101 | if (isInstruct) { // instruct formatting for text completion | ||
| 3102 | const isUser = message.role === 'user'; | ||
| 3103 | const isNarrator = message.role === 'system'; | ||
| 3104 | message.content = formatInstructModeChat(name, message.content, isUser, isNarrator, '', name1, name2, false); | ||
| 3105 | } | ||
| 3106 | } | ||
| 3107 | |||
| 3108 | // prepend system prompt, if provided | ||
| 3109 | if (systemPrompt) { | ||
| 3110 | systemPrompt = substituteParams(systemPrompt); | ||
| 3111 | systemPrompt = isInstruct ? (formatInstructModeSystemPrompt(systemPrompt) + '\n') : systemPrompt.trim(); | ||
| 3112 | prompt.unshift({ role: 'system', content: systemPrompt }); | ||
| 3113 | } | ||
| 3114 | |||
| 3115 | // with Chat Completion, the prefill is an additional assistant message at the end. | ||
| 3116 | if (api === 'openai' && prefill) { | ||
| 3117 | prompt.push({ role: 'assistant', content: prefill }); | ||
| 3118 | } | ||
| 3119 | |||
| 3120 | // if text completion, convert to text prompt by concatenating all message contents and adding the prefill as a promptBias. | ||
| 3121 | if (api !== 'openai') { | ||
| 3122 | const joiner = isInstruct ? '' : '\n'; | ||
| 3123 | prompt = prompt.map(message => message.content).join(joiner); | ||
| 3124 | prompt = api === 'novel' ? adjustNovelInstructionPrompt(prompt) : prompt; | ||
| 3125 | prompt = prompt + (isInstruct ? formatInstructModePrompt(name2, false, prefill, name1, name2, true, quietToLoud) : `\n${prefill}`); // add last line | ||
| 3126 | } | ||
| 3127 | |||
| 3128 | return prompt; | ||
| 3129 | } | ||
| 3130 | |||
| 3131 | /** | ||
| 3132 | * Generates a message using the provided prompt. | ||
| 3133 | * If the prompt is an array of chat-style messages and not using chat completion, it will be converted to a text prompt. | ||
| 3134 | * @typedef {object} GenerateRawParams | ||
| 3135 | * @prop {string | object[]} [prompt] Prompt to generate a message from. Can be a string or an array of chat-style messages, i.e. [{role: '', content: ''}, ...] | ||
| 3136 | * @prop {string} [api] API to use. Main API is used if not specified. | ||
| 3137 | * @prop {boolean} [instructOverride] true to override instruct mode, false to use the default value | ||
| 3138 | * @prop {boolean} [quietToLoud] true to generate a message in system mode, false to generate a message in character mode | ||
| 3139 | * @prop {string} [systemPrompt] System prompt to use. | ||
| 3140 | * @prop {number} [responseLength] Maximum response length. If unset, the global default value is used. | ||
| 3141 | * @prop {boolean} [trimNames] Whether to allow trimming "{{user}}:" and "{{char}}:" from the response. | ||
| 3142 | * @prop {string} [prefill] An optional prefill for the prompt. | ||
| 3143 | * @prop {object} [jsonSchema] JSON schema to use for the structured generation. Usually requires a special instruction. | ||
| 3144 | * @param {GenerateRawParams} params Parameters for generating a message | ||
| 3751 | * @returns {Promise<string>} Generated message | 3145 | * @returns {Promise<string>} Generated message |
| 3752 | */ | 3146 | */ |
| 3753 | export async function generateRaw(prompt, api, instructOverride, quietToLoud, systemPrompt, responseLength, trimNames = true) { | 3147 | export async function generateRaw({ prompt = '', api = null, instructOverride = false, quietToLoud = false, systemPrompt = '', responseLength = null, trimNames = true, prefill = '', jsonSchema = null } = {}) { |
| 3148 | if (arguments.length > 0 && typeof arguments[0] !== 'object') { | ||
| 3149 | console.trace('generateRaw called with positional arguments. Please use an object instead.'); | ||
| 3150 | [prompt, api, instructOverride, quietToLoud, systemPrompt, responseLength, trimNames, prefill, jsonSchema] = arguments; | ||
| 3151 | } | ||
| 3152 | |||
| 3754 | if (!api) { | 3153 | if (!api) { |
| 3755 | api = main_api; | 3154 | api = main_api; |
| 3756 | } | 3155 | } |
| 3757 | 3156 | ||
| 3758 | const abortController = new AbortController(); | 3157 | const abortController = new AbortController(); |
| 3759 | const responseLengthCustomized = typeof responseLength === 'number' && responseLength > 0; | 3158 | const responseLengthCustomized = typeof responseLength === 'number' && responseLength > 0; |
| 3760 | const isInstruct = power_user.instruct.enabled && api !== 'openai' && api !== 'novel' && !instructOverride; | ||
| 3761 | const isQuiet = true; | ||
| 3762 | let eventHook = () => { }; | 3159 | let eventHook = () => { }; |
| 3763 | 3160 | ||
| 3764 | if (systemPrompt) { | 3161 | // construct final prompt from the input. Can either be a string or an array of chat-style messages. |
| 3765 | systemPrompt = substituteParams(systemPrompt); | 3162 | prompt = createRawPrompt(prompt, api, instructOverride, quietToLoud, systemPrompt, prefill); |
| 3766 | systemPrompt = isInstruct ? formatInstructModeSystemPrompt(systemPrompt) : systemPrompt; | ||
| 3767 | prompt = api === 'openai' ? prompt : `${systemPrompt}\n${prompt}`; | ||
| 3768 | } | ||
| 3769 | |||
| 3770 | prompt = substituteParams(prompt); | ||
| 3771 | prompt = api == 'novel' ? adjustNovelInstructionPrompt(prompt) : prompt; | ||
| 3772 | prompt = isInstruct ? formatInstructModeChat(name1, prompt, false, true, '', name1, name2, false) : prompt; | ||
| 3773 | prompt = isInstruct ? (prompt + formatInstructModePrompt(name2, false, '', name1, name2, isQuiet, quietToLoud)) : (prompt + '\n'); | ||
| 3774 | 3163 | ||
| 3775 | try { | 3164 | try { |
| 3776 | if (responseLengthCustomized) { | 3165 | if (responseLengthCustomized) { |
| @@ -3782,12 +3171,12 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy | |||
| 3782 | switch (api) { | 3171 | switch (api) { |
| 3783 | case 'kobold': | 3172 | case 'kobold': |
| 3784 | case 'koboldhorde': | 3173 | case 'koboldhorde': |
| 3785 | if (preset_settings === 'gui') { | 3174 | if (kai_settings.preset_settings === 'gui') { |
| 3786 | generateData = { prompt: prompt, gui_settings: true, max_length: amount_gen, max_context_length: max_context, api_server }; | 3175 | generateData = { prompt: prompt, gui_settings: true, max_length: amount_gen, max_context_length: max_context, api_server: kai_settings.api_server }; |
| 3787 | } else { | 3176 | } else { |
| 3788 | const isHorde = api === 'koboldhorde'; | 3177 | const isHorde = api === 'koboldhorde'; |
| 3789 | const koboldSettings = koboldai_settings[koboldai_setting_names[preset_settings]]; | 3178 | const koboldSettings = koboldai_settings[koboldai_setting_names[kai_settings.preset_settings]]; |
| 3790 | generateData = getKoboldGenerationData(prompt, koboldSettings, amount_gen, max_context, isHorde, 'quiet'); | 3179 | generateData = getKoboldGenerationData(prompt.toString(), koboldSettings, amount_gen, max_context, isHorde, 'quiet'); |
| 3791 | } | 3180 | } |
| 3792 | TempResponseLength.restore(api); | 3181 | TempResponseLength.restore(api); |
| 3793 | break; | 3182 | break; |
| @@ -3802,10 +3191,7 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy | |||
| 3802 | TempResponseLength.restore(api); | 3191 | TempResponseLength.restore(api); |
| 3803 | break; | 3192 | break; |
| 3804 | case 'openai': { | 3193 | case 'openai': { |
| 3805 | generateData = [{ role: 'user', content: prompt.trim() }]; | 3194 | generateData = prompt; // generateData is just the chat message object |
| 3806 | if (systemPrompt) { | ||
| 3807 | generateData.unshift({ role: 'system', content: systemPrompt.trim() }); | ||
| 3808 | } | ||
| 3809 | eventHook = TempResponseLength.setupEventHook(api); | 3195 | eventHook = TempResponseLength.setupEventHook(api); |
| 3810 | } break; | 3196 | } break; |
| 3811 | } | 3197 | } |
| @@ -3813,9 +3199,9 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy | |||
| 3813 | let data = {}; | 3199 | let data = {}; |
| 3814 | 3200 | ||
| 3815 | if (api === 'koboldhorde') { | 3201 | if (api === 'koboldhorde') { |
| 3816 | data = await generateHorde(prompt, generateData, abortController.signal, false); | 3202 | data = await generateHorde(prompt.toString(), generateData, abortController.signal, false); |
| 3817 | } else if (api === 'openai') { | 3203 | } else if (api === 'openai') { |
| 3818 | data = await sendOpenAIRequest('quiet', generateData, abortController.signal); | 3204 | data = await sendOpenAIRequest('quiet', generateData, abortController.signal, { jsonSchema }); |
| 3819 | } else { | 3205 | } else { |
| 3820 | const generateUrl = getGenerateUrl(api); | 3206 | const generateUrl = getGenerateUrl(api); |
| 3821 | const response = await fetch(generateUrl, { | 3207 | const response = await fetch(generateUrl, { |
| @@ -3840,6 +3226,10 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy | |||
| 3840 | throw new Error(data.response); | 3226 | throw new Error(data.response); |
| 3841 | } | 3227 | } |
| 3842 | 3228 | ||
| 3229 | if (jsonSchema) { | ||
| 3230 | return extractJsonFromData(data, { mainApi: api }); | ||
| 3231 | } | ||
| 3232 | |||
| 3843 | // format result, exclude user prompt bias | 3233 | // format result, exclude user prompt bias |
| 3844 | const message = cleanUpMessage({ | 3234 | const message = cleanUpMessage({ |
| 3845 | getMessage: extractMessageFromData(data), | 3235 | getMessage: extractMessageFromData(data), |
| @@ -3972,15 +3362,35 @@ function removeLastMessage() { | |||
| 3972 | } | 3362 | } |
| 3973 | 3363 | ||
| 3974 | /** | 3364 | /** |
| 3365 | * @typedef {object} JsonSchema | ||
| 3366 | * @property {string} name Name of the schema. | ||
| 3367 | * @property {object} value JSON schema value. | ||
| 3368 | * @property {string} [description] Description of the schema. | ||
| 3369 | * @property {boolean} [strict] If true, the schema will be used in strict mode, meaning that only the fields defined in the schema will be allowed. | ||
| 3370 | * | ||
| 3371 | * @typedef {object} GenerateOptions | ||
| 3372 | * @property {boolean} [automatic_trigger] If the generation was triggered automatically (e.g. group auto mode). | ||
| 3373 | * @property {boolean} [force_name2] If a char name should be forced to add to the prompt's last line (Text Completion, non-Instruct only). | ||
| 3374 | * @property {string} [quiet_prompt] A system instruction to use for the quiet prompt. | ||
| 3375 | * @property {boolean} [quietToLoud] Whether the system instruction should be sent in background (quiet) or a foreground (loud) mode. | ||
| 3376 | * @property {boolean} [skipWIAN] Skip adding World Info and Author's Note to the prompt. | ||
| 3377 | * @property {number} [force_chid] Force character ID to use for the generation. Only works in groups. | ||
| 3378 | * @property {AbortSignal} [signal] Abort signal to cancel the generation. If not provided, will create a new AbortController. | ||
| 3379 | * @property {string} [quietImage] Image URL to use for the quiet prompt (defaults to empty string) | ||
| 3380 | * @property {string} [quietName] Name to use for the quiet prompt (defaults to "System:") | ||
| 3381 | * @property {number} [depth] Recursion depth for the generation. Used to prevent infinite loops in tool calls. | ||
| 3382 | * @property {JsonSchema} [jsonSchema] JSON schema to use for the structured generation. Usually requires a special instruction. | ||
| 3383 | */ | ||
| 3384 | |||
| 3385 | /** | ||
| 3975 | * MARK:Generate() | 3386 | * MARK:Generate() |
| 3976 | * Runs a generation using the current chat context. | 3387 | * Runs a generation using the current chat context. |
| 3977 | * @param {string} type Generation type | 3388 | * @param {string} type Generation type |
| 3978 | * @param {GenerateOptions} options Generation options | 3389 | * @param {GenerateOptions} options Generation options |
| 3979 | * @param {boolean} dryRun Whether to actually generate a message or just assemble the prompt | 3390 | * @param {boolean} dryRun Whether to actually generate a message or just assemble the prompt |
| 3980 | * @returns {Promise<any>} Returns a promise that resolves when the text is done generating. | 3391 | * @returns {Promise<any>} Returns a promise that resolves when the text is done generating. |
| 3981 | * @typedef {{automatic_trigger?: boolean, force_name2?: boolean, quiet_prompt?: string, quietToLoud?: boolean, skipWIAN?: boolean, force_chid?: number, signal?: AbortSignal, quietImage?: string, quietName?: string, depth?: number }} GenerateOptions | ||
| 3982 | */ | 3392 | */ |
| 3983 | export async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage, quietName, depth = 0 } = {}, dryRun = false) { | 3393 | export async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage, quietName, jsonSchema = null, depth = 0 } = {}, dryRun = false) { |
| 3984 | console.log('Generate entered'); | 3394 | console.log('Generate entered'); |
| 3985 | setGenerationProgress(0); | 3395 | setGenerationProgress(0); |
| 3986 | generation_started = new Date(); | 3396 | generation_started = new Date(); |
| @@ -4134,7 +3544,6 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 4134 | 'impersonate', | 3544 | 'impersonate', |
| 4135 | 'quiet', | 3545 | 'quiet', |
| 4136 | 'continue', | 3546 | 'continue', |
| 4137 | 'ask_command', | ||
| 4138 | ]; | 3547 | ]; |
| 4139 | //for normal messages sent from user.. | 3548 | //for normal messages sent from user.. |
| 4140 | if ((textareaText != '' || (hasPendingFileAttachment() && !noAttachTypes.includes(type))) && !automatic_trigger && type !== 'quiet' && !dryRun) { | 3549 | if ((textareaText != '' || (hasPendingFileAttachment() && !noAttachTypes.includes(type))) && !automatic_trigger && type !== 'quiet' && !dryRun) { |
| @@ -4316,6 +3725,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 4316 | // Make quiet prompt available for WIAN | 3725 | // Make quiet prompt available for WIAN |
| 4317 | setExtensionPrompt('QUIET_PROMPT', quiet_prompt || '', extension_prompt_types.IN_PROMPT, 0, true); | 3726 | setExtensionPrompt('QUIET_PROMPT', quiet_prompt || '', extension_prompt_types.IN_PROMPT, 0, true); |
| 4318 | const chatForWI = coreChat.map(x => world_info_include_names ? `${x.name}: ${x.mes}` : x.mes).reverse(); | 3727 | const chatForWI = coreChat.map(x => world_info_include_names ? `${x.name}: ${x.mes}` : x.mes).reverse(); |
| 3728 | /** @type {import('./scripts/world-info.js').WIGlobalScanData} */ | ||
| 4319 | const globalScanData = { | 3729 | const globalScanData = { |
| 4320 | personaDescription: persona, | 3730 | personaDescription: persona, |
| 4321 | characterDescription: description, | 3731 | characterDescription: description, |
| @@ -4323,6 +3733,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 4323 | characterDepthPrompt: charDepthPrompt, | 3733 | characterDepthPrompt: charDepthPrompt, |
| 4324 | scenario: scenario, | 3734 | scenario: scenario, |
| 4325 | creatorNotes: creatorNotes, | 3735 | creatorNotes: creatorNotes, |
| 3736 | trigger: GENERATION_TYPE_TRIGGERS.includes(type) ? type : 'normal', | ||
| 4326 | }; | 3737 | }; |
| 4327 | const { worldInfoString, worldInfoBefore, worldInfoAfter, worldInfoExamples, worldInfoDepth } = await getWorldInfoPrompt(chatForWI, this_max_context, dryRun, globalScanData); | 3738 | const { worldInfoString, worldInfoBefore, worldInfoAfter, worldInfoExamples, worldInfoDepth } = await getWorldInfoPrompt(chatForWI, this_max_context, dryRun, globalScanData); |
| 4328 | setExtensionPrompt('QUIET_PROMPT', '', extension_prompt_types.IN_PROMPT, 0, true); | 3739 | setExtensionPrompt('QUIET_PROMPT', '', extension_prompt_types.IN_PROMPT, 0, true); |
| @@ -4937,12 +4348,12 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 4937 | gui_settings: true, | 4348 | gui_settings: true, |
| 4938 | max_length: maxLength, | 4349 | max_length: maxLength, |
| 4939 | max_context_length: max_context, | 4350 | max_context_length: max_context, |
| 4940 | api_server, | 4351 | api_server: kai_settings.api_server, |
| 4941 | }; | 4352 | }; |
| 4942 | 4353 | ||
| 4943 | if (preset_settings != 'gui') { | 4354 | if (kai_settings.preset_settings != 'gui') { |
| 4944 | const isHorde = main_api == 'koboldhorde'; | 4355 | const isHorde = main_api == 'koboldhorde'; |
| 4945 | const presetSettings = koboldai_settings[koboldai_setting_names[preset_settings]]; | 4356 | const presetSettings = koboldai_settings[koboldai_setting_names[kai_settings.preset_settings]]; |
| 4946 | const maxContext = (adjustedParams && horde_settings.auto_adjust_context_length) ? adjustedParams.maxContextLength : max_context; | 4357 | const maxContext = (adjustedParams && horde_settings.auto_adjust_context_length) ? adjustedParams.maxContextLength : max_context; |
| 4947 | generate_data = getKoboldGenerationData(finalPrompt, presetSettings, maxLength, maxContext, isHorde, type); | 4358 | generate_data = getKoboldGenerationData(finalPrompt, presetSettings, maxLength, maxContext, isHorde, type); |
| 4948 | } | 4359 | } |
| @@ -4974,7 +4385,6 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 4974 | cyclePrompt: cyclePrompt, | 4385 | cyclePrompt: cyclePrompt, |
| 4975 | systemPromptOverride: system, | 4386 | systemPromptOverride: system, |
| 4976 | jailbreakPromptOverride: jailbreak, | 4387 | jailbreakPromptOverride: jailbreak, |
| 4977 | personaDescription: persona, | ||
| 4978 | messages: oaiMessages, | 4388 | messages: oaiMessages, |
| 4979 | messageExamples: oaiMessageExamples, | 4389 | messageExamples: oaiMessageExamples, |
| 4980 | }, dryRun); | 4390 | }, dryRun); |
| @@ -5122,7 +4532,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 5122 | }); | 4532 | }); |
| 5123 | } | 4533 | } |
| 5124 | } else { | 4534 | } else { |
| 5125 | return await sendGenerationRequest(type, generate_data); | 4535 | return await sendGenerationRequest(type, generate_data, { jsonSchema }); |
| 5126 | } | 4536 | } |
| 5127 | } | 4537 | } |
| 5128 | 4538 | ||
| @@ -5154,6 +4564,12 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 5154 | throw new Error(data?.response); | 4564 | throw new Error(data?.response); |
| 5155 | } | 4565 | } |
| 5156 | 4566 | ||
| 4567 | if (jsonSchema) { | ||
| 4568 | unblockGeneration(type); | ||
| 4569 | generatedPromptCache = ''; | ||
| 4570 | return extractJsonFromData(data); | ||
| 4571 | } | ||
| 4572 | |||
| 5157 | //const getData = await response.json(); | 4573 | //const getData = await response.json(); |
| 5158 | let getMessage = extractMessageFromData(data); | 4574 | let getMessage = extractMessageFromData(data); |
| 5159 | let title = extractTitleFromData(data); | 4575 | let title = extractTitleFromData(data); |
| @@ -5567,7 +4983,7 @@ export async function sendMessageAsUser(messageText, messageBias, insertAt = nul | |||
| 5567 | 4983 | ||
| 5568 | // Lock user avatar to a persona. | 4984 | // Lock user avatar to a persona. |
| 5569 | if (avatar in power_user.personas) { | 4985 | if (avatar in power_user.personas) { |
| 5570 | message.force_avatar = getUserAvatar(avatar); | 4986 | message.force_avatar = getThumbnailUrl('persona', avatar); |
| 5571 | } | 4987 | } |
| 5572 | 4988 | ||
| 5573 | if (messageBias) { | 4989 | if (messageBias) { |
| @@ -5680,7 +5096,7 @@ function addChatsSeparator(mesSendString) { | |||
| 5680 | } | 5096 | } |
| 5681 | } | 5097 | } |
| 5682 | 5098 | ||
| 5683 | async function duplicateCharacter() { | 5099 | export async function duplicateCharacter() { |
| 5684 | if (this_chid === undefined || !characters[this_chid]) { | 5100 | if (this_chid === undefined || !characters[this_chid]) { |
| 5685 | toastr.warning(t`You must first select a character to duplicate!`); | 5101 | toastr.warning(t`You must first select a character to duplicate!`); |
| 5686 | return ''; | 5102 | return ''; |
| @@ -5710,231 +5126,6 @@ async function duplicateCharacter() { | |||
| 5710 | return ''; | 5126 | return ''; |
| 5711 | } | 5127 | } |
| 5712 | 5128 | ||
| 5713 | export async function itemizedParams(itemizedPrompts, thisPromptSet, incomingMesId) { | ||
| 5714 | const params = { | ||
| 5715 | charDescriptionTokens: await getTokenCountAsync(itemizedPrompts[thisPromptSet].charDescription), | ||
| 5716 | charPersonalityTokens: await getTokenCountAsync(itemizedPrompts[thisPromptSet].charPersonality), | ||
| 5717 | scenarioTextTokens: await getTokenCountAsync(itemizedPrompts[thisPromptSet].scenarioText), | ||
| 5718 | userPersonaStringTokens: await getTokenCountAsync(itemizedPrompts[thisPromptSet].userPersona), | ||
| 5719 | worldInfoStringTokens: await getTokenCountAsync(itemizedPrompts[thisPromptSet].worldInfoString), | ||
| 5720 | allAnchorsTokens: await getTokenCountAsync(itemizedPrompts[thisPromptSet].allAnchors), | ||
| 5721 | summarizeStringTokens: await getTokenCountAsync(itemizedPrompts[thisPromptSet].summarizeString), | ||
| 5722 | authorsNoteStringTokens: await getTokenCountAsync(itemizedPrompts[thisPromptSet].authorsNoteString), | ||
| 5723 | smartContextStringTokens: await getTokenCountAsync(itemizedPrompts[thisPromptSet].smartContextString), | ||
| 5724 | beforeScenarioAnchorTokens: await getTokenCountAsync(itemizedPrompts[thisPromptSet].beforeScenarioAnchor), | ||
| 5725 | afterScenarioAnchorTokens: await getTokenCountAsync(itemizedPrompts[thisPromptSet].afterScenarioAnchor), | ||
| 5726 | zeroDepthAnchorTokens: await getTokenCountAsync(itemizedPrompts[thisPromptSet].zeroDepthAnchor), // TODO: unused | ||
| 5727 | thisPrompt_padding: itemizedPrompts[thisPromptSet].padding, | ||
| 5728 | this_main_api: itemizedPrompts[thisPromptSet].main_api, | ||
| 5729 | chatInjects: await getTokenCountAsync(itemizedPrompts[thisPromptSet].chatInjects), | ||
| 5730 | chatVectorsStringTokens: await getTokenCountAsync(itemizedPrompts[thisPromptSet].chatVectorsString), | ||
| 5731 | dataBankVectorsStringTokens: await getTokenCountAsync(itemizedPrompts[thisPromptSet].dataBankVectorsString), | ||
| 5732 | modelUsed: chat[incomingMesId]?.extra?.model, | ||
| 5733 | apiUsed: chat[incomingMesId]?.extra?.api, | ||
| 5734 | presetName: itemizedPrompts[thisPromptSet].presetName || t`(Unknown)`, | ||
| 5735 | messagesCount: String(itemizedPrompts[thisPromptSet].messagesCount ?? ''), | ||
| 5736 | examplesCount: String(itemizedPrompts[thisPromptSet].examplesCount ?? ''), | ||
| 5737 | }; | ||
| 5738 | |||
| 5739 | const getFriendlyName = (value) => $(`#rm_api_block select option[value="${value}"]`).first().text() || value; | ||
| 5740 | |||
| 5741 | if (params.apiUsed) { | ||
| 5742 | params.apiUsed = getFriendlyName(params.apiUsed); | ||
| 5743 | } | ||
| 5744 | |||
| 5745 | if (params.this_main_api) { | ||
| 5746 | params.mainApiFriendlyName = getFriendlyName(params.this_main_api); | ||
| 5747 | } | ||
| 5748 | |||
| 5749 | if (params.chatInjects) { | ||
| 5750 | params.ActualChatHistoryTokens = params.ActualChatHistoryTokens - params.chatInjects; | ||
| 5751 | } | ||
| 5752 | |||
| 5753 | if (params.this_main_api == 'openai') { | ||
| 5754 | //for OAI API | ||
| 5755 | //console.log('-- Counting OAI Tokens'); | ||
| 5756 | |||
| 5757 | //params.finalPromptTokens = itemizedPrompts[thisPromptSet].oaiTotalTokens; | ||
| 5758 | params.oaiMainTokens = itemizedPrompts[thisPromptSet].oaiMainTokens; | ||
| 5759 | params.oaiStartTokens = itemizedPrompts[thisPromptSet].oaiStartTokens; | ||
| 5760 | params.ActualChatHistoryTokens = itemizedPrompts[thisPromptSet].oaiConversationTokens; | ||
| 5761 | params.examplesStringTokens = itemizedPrompts[thisPromptSet].oaiExamplesTokens; | ||
| 5762 | params.oaiPromptTokens = itemizedPrompts[thisPromptSet].oaiPromptTokens - (params.afterScenarioAnchorTokens + params.beforeScenarioAnchorTokens) + params.examplesStringTokens; | ||
| 5763 | params.oaiBiasTokens = itemizedPrompts[thisPromptSet].oaiBiasTokens; | ||
| 5764 | params.oaiJailbreakTokens = itemizedPrompts[thisPromptSet].oaiJailbreakTokens; | ||
| 5765 | params.oaiNudgeTokens = itemizedPrompts[thisPromptSet].oaiNudgeTokens; | ||
| 5766 | params.oaiImpersonateTokens = itemizedPrompts[thisPromptSet].oaiImpersonateTokens; | ||
| 5767 | params.oaiNsfwTokens = itemizedPrompts[thisPromptSet].oaiNsfwTokens; | ||
| 5768 | params.finalPromptTokens = | ||
| 5769 | params.oaiStartTokens + | ||
| 5770 | params.oaiPromptTokens + | ||
| 5771 | params.oaiMainTokens + | ||
| 5772 | params.oaiNsfwTokens + | ||
| 5773 | params.oaiBiasTokens + | ||
| 5774 | params.oaiImpersonateTokens + | ||
| 5775 | params.oaiJailbreakTokens + | ||
| 5776 | params.oaiNudgeTokens + | ||
| 5777 | params.ActualChatHistoryTokens + | ||
| 5778 | //charDescriptionTokens + | ||
| 5779 | //charPersonalityTokens + | ||
| 5780 | //allAnchorsTokens + | ||
| 5781 | params.worldInfoStringTokens + | ||
| 5782 | params.beforeScenarioAnchorTokens + | ||
| 5783 | params.afterScenarioAnchorTokens; | ||
| 5784 | // Max context size - max completion tokens | ||
| 5785 | params.thisPrompt_max_context = (oai_settings.openai_max_context - oai_settings.openai_max_tokens); | ||
| 5786 | |||
| 5787 | //console.log('-- applying % on OAI tokens'); | ||
| 5788 | params.oaiStartTokensPercentage = ((params.oaiStartTokens / (params.finalPromptTokens)) * 100).toFixed(2); | ||
| 5789 | params.storyStringTokensPercentage = (((params.afterScenarioAnchorTokens + params.beforeScenarioAnchorTokens + params.oaiPromptTokens) / (params.finalPromptTokens)) * 100).toFixed(2); | ||
| 5790 | params.ActualChatHistoryTokensPercentage = ((params.ActualChatHistoryTokens / (params.finalPromptTokens)) * 100).toFixed(2); | ||
| 5791 | params.promptBiasTokensPercentage = ((params.oaiBiasTokens / (params.finalPromptTokens)) * 100).toFixed(2); | ||
| 5792 | params.worldInfoStringTokensPercentage = ((params.worldInfoStringTokens / (params.finalPromptTokens)) * 100).toFixed(2); | ||
| 5793 | params.allAnchorsTokensPercentage = ((params.allAnchorsTokens / (params.finalPromptTokens)) * 100).toFixed(2); | ||
| 5794 | params.selectedTokenizer = getFriendlyTokenizerName(params.this_main_api).tokenizerName; | ||
| 5795 | params.oaiSystemTokens = params.oaiImpersonateTokens + params.oaiJailbreakTokens + params.oaiNudgeTokens + params.oaiStartTokens + params.oaiNsfwTokens + params.oaiMainTokens; | ||
| 5796 | params.oaiSystemTokensPercentage = ((params.oaiSystemTokens / (params.finalPromptTokens)) * 100).toFixed(2); | ||
| 5797 | } else { | ||
| 5798 | //for non-OAI APIs | ||
| 5799 | //console.log('-- Counting non-OAI Tokens'); | ||
| 5800 | params.finalPromptTokens = await getTokenCountAsync(itemizedPrompts[thisPromptSet].finalPrompt); | ||
| 5801 | params.storyStringTokens = await getTokenCountAsync(itemizedPrompts[thisPromptSet].storyString) - params.worldInfoStringTokens; | ||
| 5802 | params.examplesStringTokens = await getTokenCountAsync(itemizedPrompts[thisPromptSet].examplesString); | ||
| 5803 | params.mesSendStringTokens = await getTokenCountAsync(itemizedPrompts[thisPromptSet].mesSendString); | ||
| 5804 | params.ActualChatHistoryTokens = params.mesSendStringTokens - (params.allAnchorsTokens - (params.beforeScenarioAnchorTokens + params.afterScenarioAnchorTokens)) + power_user.token_padding; | ||
| 5805 | params.instructionTokens = await getTokenCountAsync(itemizedPrompts[thisPromptSet].instruction); | ||
| 5806 | params.promptBiasTokens = await getTokenCountAsync(itemizedPrompts[thisPromptSet].promptBias); | ||
| 5807 | |||
| 5808 | params.totalTokensInPrompt = | ||
| 5809 | params.storyStringTokens + //chardefs total | ||
| 5810 | params.worldInfoStringTokens + | ||
| 5811 | params.examplesStringTokens + // example messages | ||
| 5812 | params.ActualChatHistoryTokens + //chat history | ||
| 5813 | params.allAnchorsTokens + // AN and/or legacy anchors | ||
| 5814 | //afterScenarioAnchorTokens + //only counts if AN is set to 'after scenario' | ||
| 5815 | //zeroDepthAnchorTokens + //same as above, even if AN not on 0 depth | ||
| 5816 | params.promptBiasTokens; //{{}} | ||
| 5817 | //- thisPrompt_padding; //not sure this way of calculating is correct, but the math results in same value as 'finalPrompt' | ||
| 5818 | params.thisPrompt_max_context = itemizedPrompts[thisPromptSet].this_max_context; | ||
| 5819 | params.thisPrompt_actual = params.thisPrompt_max_context - params.thisPrompt_padding; | ||
| 5820 | |||
| 5821 | //console.log('-- applying % on non-OAI tokens'); | ||
| 5822 | params.storyStringTokensPercentage = ((params.storyStringTokens / (params.totalTokensInPrompt)) * 100).toFixed(2); | ||
| 5823 | params.ActualChatHistoryTokensPercentage = ((params.ActualChatHistoryTokens / (params.totalTokensInPrompt)) * 100).toFixed(2); | ||
| 5824 | params.promptBiasTokensPercentage = ((params.promptBiasTokens / (params.totalTokensInPrompt)) * 100).toFixed(2); | ||
| 5825 | params.worldInfoStringTokensPercentage = ((params.worldInfoStringTokens / (params.totalTokensInPrompt)) * 100).toFixed(2); | ||
| 5826 | params.allAnchorsTokensPercentage = ((params.allAnchorsTokens / (params.totalTokensInPrompt)) * 100).toFixed(2); | ||
| 5827 | params.selectedTokenizer = itemizedPrompts[thisPromptSet]?.tokenizer || getFriendlyTokenizerName(params.this_main_api).tokenizerName; | ||
| 5828 | } | ||
| 5829 | return params; | ||
| 5830 | } | ||
| 5831 | |||
| 5832 | export function findItemizedPromptSet(itemizedPrompts, incomingMesId) { | ||
| 5833 | var thisPromptSet = undefined; | ||
| 5834 | |||
| 5835 | for (var i = 0; i < itemizedPrompts.length; i++) { | ||
| 5836 | console.log(`looking for ${incomingMesId} vs ${itemizedPrompts[i].mesId}`); | ||
| 5837 | if (itemizedPrompts[i].mesId === incomingMesId) { | ||
| 5838 | console.log(`found matching mesID ${i}`); | ||
| 5839 | thisPromptSet = i; | ||
| 5840 | PromptArrayItemForRawPromptDisplay = i; | ||
| 5841 | console.log(`wanting to raw display of ArrayItem: ${PromptArrayItemForRawPromptDisplay} which is mesID ${incomingMesId}`); | ||
| 5842 | console.log(itemizedPrompts[thisPromptSet]); | ||
| 5843 | break; | ||
| 5844 | } else if (itemizedPrompts[i].rawPrompt) { | ||
| 5845 | priorPromptArrayItemForRawPromptDisplay = i; | ||
| 5846 | } | ||
| 5847 | } | ||
| 5848 | return thisPromptSet; | ||
| 5849 | } | ||
| 5850 | |||
| 5851 | async function promptItemize(itemizedPrompts, requestedMesId) { | ||
| 5852 | console.log('PROMPT ITEMIZE ENTERED'); | ||
| 5853 | var incomingMesId = Number(requestedMesId); | ||
| 5854 | console.debug(`looking for MesId ${incomingMesId}`); | ||
| 5855 | var thisPromptSet = findItemizedPromptSet(itemizedPrompts, incomingMesId); | ||
| 5856 | |||
| 5857 | if (thisPromptSet === undefined) { | ||
| 5858 | console.log(`couldnt find the right mesId. looked for ${incomingMesId}`); | ||
| 5859 | console.log(itemizedPrompts); | ||
| 5860 | return null; | ||
| 5861 | } | ||
| 5862 | |||
| 5863 | const params = await itemizedParams(itemizedPrompts, thisPromptSet, incomingMesId); | ||
| 5864 | const flatten = (rawPrompt) => Array.isArray(rawPrompt) ? rawPrompt.map(x => x.content).join('\n') : rawPrompt; | ||
| 5865 | |||
| 5866 | const template = params.this_main_api == 'openai' | ||
| 5867 | ? await renderTemplateAsync('itemizationChat', params) | ||
| 5868 | : await renderTemplateAsync('itemizationText', params); | ||
| 5869 | |||
| 5870 | const popup = new Popup(template, POPUP_TYPE.TEXT); | ||
| 5871 | |||
| 5872 | /** @type {HTMLElement} */ | ||
| 5873 | const diffPrevPrompt = popup.dlg.querySelector('#diffPrevPrompt'); | ||
| 5874 | if (priorPromptArrayItemForRawPromptDisplay) { | ||
| 5875 | diffPrevPrompt.style.display = ''; | ||
| 5876 | diffPrevPrompt.addEventListener('click', function () { | ||
| 5877 | const dmp = new DiffMatchPatch(); | ||
| 5878 | const text1 = flatten(itemizedPrompts[priorPromptArrayItemForRawPromptDisplay].rawPrompt); | ||
| 5879 | const text2 = flatten(itemizedPrompts[PromptArrayItemForRawPromptDisplay].rawPrompt); | ||
| 5880 | |||
| 5881 | dmp.Diff_Timeout = 2.0; | ||
| 5882 | |||
| 5883 | const d = dmp.diff_main(text1, text2); | ||
| 5884 | let ds = dmp.diff_prettyHtml(d); | ||
| 5885 | // make it readable | ||
| 5886 | ds = ds.replaceAll('background:#e6ffe6;', 'background:#b9f3b9; color:black;'); | ||
| 5887 | ds = ds.replaceAll('background:#ffe6e6;', 'background:#f5b4b4; color:black;'); | ||
| 5888 | ds = ds.replaceAll('¶', ''); | ||
| 5889 | const container = document.createElement('div'); | ||
| 5890 | container.innerHTML = DOMPurify.sanitize(ds); | ||
| 5891 | const rawPromptWrapper = document.getElementById('rawPromptWrapper'); | ||
| 5892 | rawPromptWrapper.replaceChildren(container); | ||
| 5893 | $('#rawPromptPopup').slideToggle(); | ||
| 5894 | }); | ||
| 5895 | } else { | ||
| 5896 | diffPrevPrompt.style.display = 'none'; | ||
| 5897 | } | ||
| 5898 | popup.dlg.querySelector('#copyPromptToClipboard').addEventListener('pointerup', async function () { | ||
| 5899 | let rawPrompt = itemizedPrompts[PromptArrayItemForRawPromptDisplay].rawPrompt; | ||
| 5900 | let rawPromptValues = rawPrompt; | ||
| 5901 | |||
| 5902 | if (Array.isArray(rawPrompt)) { | ||
| 5903 | rawPromptValues = rawPrompt.map(x => x.content).join('\n'); | ||
| 5904 | } | ||
| 5905 | |||
| 5906 | await copyText(rawPromptValues); | ||
| 5907 | toastr.info(t`Copied!`); | ||
| 5908 | }); | ||
| 5909 | |||
| 5910 | popup.dlg.querySelector('#showRawPrompt').addEventListener('click', async function () { | ||
| 5911 | //console.log(itemizedPrompts[PromptArrayItemForRawPromptDisplay].rawPrompt); | ||
| 5912 | console.log(PromptArrayItemForRawPromptDisplay); | ||
| 5913 | console.log(itemizedPrompts); | ||
| 5914 | console.log(itemizedPrompts[PromptArrayItemForRawPromptDisplay].rawPrompt); | ||
| 5915 | |||
| 5916 | const rawPrompt = flatten(itemizedPrompts[PromptArrayItemForRawPromptDisplay].rawPrompt); | ||
| 5917 | |||
| 5918 | // Mobile needs special handholding. The side-view on the popup wouldn't work, | ||
| 5919 | // so we just show an additional popup for this. | ||
| 5920 | if (isMobile()) { | ||
| 5921 | const content = document.createElement('div'); | ||
| 5922 | content.classList.add('tokenItemizingMaintext'); | ||
| 5923 | content.innerText = rawPrompt; | ||
| 5924 | const popup = new Popup(content, POPUP_TYPE.TEXT, null, { allowVerticalScrolling: true, leftAlign: true }); | ||
| 5925 | await popup.show(); | ||
| 5926 | return; | ||
| 5927 | } | ||
| 5928 | |||
| 5929 | //let DisplayStringifiedPrompt = JSON.stringify(itemizedPrompts[PromptArrayItemForRawPromptDisplay].rawPrompt).replace(/\n+/g, '<br>'); | ||
| 5930 | const rawPromptWrapper = document.getElementById('rawPromptWrapper'); | ||
| 5931 | rawPromptWrapper.innerText = rawPrompt; | ||
| 5932 | $('#rawPromptPopup').slideToggle(); | ||
| 5933 | }); | ||
| 5934 | |||
| 5935 | await popup.show(); | ||
| 5936 | } | ||
| 5937 | |||
| 5938 | function setInContextMessages(msgInContextCount, type) { | 5129 | function setInContextMessages(msgInContextCount, type) { |
| 5939 | $('#chat .mes').removeClass('lastInContext'); | 5130 | $('#chat .mes').removeClass('lastInContext'); |
| 5940 | 5131 | ||
| @@ -5956,15 +5147,21 @@ function setInContextMessages(msgInContextCount, type) { | |||
| 5956 | } | 5147 | } |
| 5957 | 5148 | ||
| 5958 | /** | 5149 | /** |
| 5150 | * @typedef {object} AdditionalRequestOptions | ||
| 5151 | * @property {JsonSchema} [jsonSchema] | ||
| 5152 | */ | ||
| 5153 | |||
| 5154 | /** | ||
| 5959 | * Sends a non-streaming request to the API. | 5155 | * Sends a non-streaming request to the API. |
| 5960 | * @param {string} type Generation type | 5156 | * @param {string} type Generation type |
| 5961 | * @param {object} data Generation data | 5157 | * @param {object} data Generation data |
| 5158 | * @param {AdditionalRequestOptions} [options] Additional options for the generation request | ||
| 5962 | * @returns {Promise<object>} Response data from the API | 5159 | * @returns {Promise<object>} Response data from the API |
| 5963 | * @throws {Error|object} | 5160 | * @throws {Error|object} |
| 5964 | */ | 5161 | */ |
| 5965 | export async function sendGenerationRequest(type, data) { | 5162 | export async function sendGenerationRequest(type, data, options = {}) { |
| 5966 | if (main_api === 'openai') { | 5163 | if (main_api === 'openai') { |
| 5967 | return await sendOpenAIRequest(type, data.prompt, abortController.signal); | 5164 | return await sendOpenAIRequest(type, data.prompt, abortController.signal, options); |
| 5968 | } | 5165 | } |
| 5969 | 5166 | ||
| 5970 | if (main_api === 'koboldhorde') { | 5167 | if (main_api === 'koboldhorde') { |
| @@ -5990,16 +5187,17 @@ export async function sendGenerationRequest(type, data) { | |||
| 5990 | * Sends a streaming request to the API. | 5187 | * Sends a streaming request to the API. |
| 5991 | * @param {string} type Generation type | 5188 | * @param {string} type Generation type |
| 5992 | * @param {object} data Generation data | 5189 | * @param {object} data Generation data |
| 5190 | * @param {AdditionalRequestOptions} [options] Additional options for the generation request | ||
| 5993 | * @returns {Promise<any>} Streaming generator | 5191 | * @returns {Promise<any>} Streaming generator |
| 5994 | */ | 5192 | */ |
| 5995 | export async function sendStreamingRequest(type, data) { | 5193 | export async function sendStreamingRequest(type, data, options = {}) { |
| 5996 | if (abortController?.signal?.aborted) { | 5194 | if (abortController?.signal?.aborted) { |
| 5997 | throw new Error('Generation was aborted.'); | 5195 | throw new Error('Generation was aborted.'); |
| 5998 | } | 5196 | } |
| 5999 | 5197 | ||
| 6000 | switch (main_api) { | 5198 | switch (main_api) { |
| 6001 | case 'openai': | 5199 | case 'openai': |
| 6002 | return await sendOpenAIRequest(type, data.prompt, streamingProcessor.abortController.signal); | 5200 | return await sendOpenAIRequest(type, data.prompt, streamingProcessor.abortController.signal, options); |
| 6003 | case 'textgenerationwebui': | 5201 | case 'textgenerationwebui': |
| 6004 | return await generateTextGenWithStreaming(data, streamingProcessor.abortController.signal); | 5202 | return await generateTextGenWithStreaming(data, streamingProcessor.abortController.signal); |
| 6005 | case 'novel': | 5203 | case 'novel': |
| @@ -6141,6 +5339,58 @@ export function extractMessageFromData(data, activeApi = null) { | |||
| 6141 | } | 5339 | } |
| 6142 | 5340 | ||
| 6143 | /** | 5341 | /** |
| 5342 | * Extracts JSON from the response data. | ||
| 5343 | * @param {object} data Response data | ||
| 5344 | * @returns {string} Extracted JSON string from the response data | ||
| 5345 | */ | ||
| 5346 | export function extractJsonFromData(data, { mainApi = null, chatCompletionSource = null } = {}) { | ||
| 5347 | mainApi = mainApi ?? main_api; | ||
| 5348 | chatCompletionSource = chatCompletionSource ?? oai_settings.chat_completion_source; | ||
| 5349 | |||
| 5350 | const tryParse = (/** @type {string} */ value) => { | ||
| 5351 | try { | ||
| 5352 | return JSON.parse(value); | ||
| 5353 | } catch (e) { | ||
| 5354 | console.debug('Failed to parse content as JSON.', e); | ||
| 5355 | } | ||
| 5356 | }; | ||
| 5357 | |||
| 5358 | let result = {}; | ||
| 5359 | |||
| 5360 | switch (mainApi) { | ||
| 5361 | case 'openai': { | ||
| 5362 | const text = extractMessageFromData(data, mainApi); | ||
| 5363 | switch (chatCompletionSource) { | ||
| 5364 | case chat_completion_sources.CLAUDE: | ||
| 5365 | result = data?.content?.find(x => x.type === 'tool_use')?.input; | ||
| 5366 | break; | ||
| 5367 | case chat_completion_sources.PERPLEXITY: | ||
| 5368 | result = tryParse(removeReasoningFromString(text)); | ||
| 5369 | break; | ||
| 5370 | case chat_completion_sources.VERTEXAI: | ||
| 5371 | case chat_completion_sources.MAKERSUITE: | ||
| 5372 | case chat_completion_sources.DEEPSEEK: | ||
| 5373 | case chat_completion_sources.AI21: | ||
| 5374 | case chat_completion_sources.GROQ: | ||
| 5375 | case chat_completion_sources.POLLINATIONS: | ||
| 5376 | case chat_completion_sources.AIMLAPI: | ||
| 5377 | case chat_completion_sources.OPENAI: | ||
| 5378 | case chat_completion_sources.OPENROUTER: | ||
| 5379 | case chat_completion_sources.MISTRALAI: | ||
| 5380 | case chat_completion_sources.CUSTOM: | ||
| 5381 | case chat_completion_sources.COHERE: | ||
| 5382 | case chat_completion_sources.XAI: | ||
| 5383 | default: | ||
| 5384 | result = tryParse(text); | ||
| 5385 | break; | ||
| 5386 | } | ||
| 5387 | } break; | ||
| 5388 | } | ||
| 5389 | |||
| 5390 | return JSON.stringify(result ?? {}); | ||
| 5391 | } | ||
| 5392 | |||
| 5393 | /** | ||
| 6144 | * Extracts multiswipe swipes from the response data. | 5394 | * Extracts multiswipe swipes from the response data. |
| 6145 | * @param {Object} data Response data | 5395 | * @param {Object} data Response data |
| 6146 | * @param {string} type Type of generation | 5396 | * @param {string} type Type of generation |
| @@ -6551,7 +5801,7 @@ export async function saveReply({ type, getMessage, fromStreaming = false, title | |||
| 6551 | send_date: item['send_date'], | 5801 | send_date: item['send_date'], |
| 6552 | gen_started: item['gen_started'], | 5802 | gen_started: item['gen_started'], |
| 6553 | gen_finished: item['gen_finished'], | 5803 | gen_finished: item['gen_finished'], |
| 6554 | extra: JSON.parse(JSON.stringify(item['extra'])), | 5804 | extra: structuredClone(item['extra']), |
| 6555 | }; | 5805 | }; |
| 6556 | } else { | 5806 | } else { |
| 6557 | item['swipe_id'] = 0; | 5807 | item['swipe_id'] = 0; |
| @@ -6561,7 +5811,7 @@ export async function saveReply({ type, getMessage, fromStreaming = false, title | |||
| 6561 | send_date: chat[chat.length - 1]['send_date'], | 5811 | send_date: chat[chat.length - 1]['send_date'], |
| 6562 | gen_started: chat[chat.length - 1]['gen_started'], | 5812 | gen_started: chat[chat.length - 1]['gen_started'], |
| 6563 | gen_finished: chat[chat.length - 1]['gen_finished'], | 5813 | gen_finished: chat[chat.length - 1]['gen_finished'], |
| 6564 | extra: JSON.parse(JSON.stringify(chat[chat.length - 1]['extra'])), | 5814 | extra: structuredClone(chat[chat.length - 1]['extra']), |
| 6565 | }; | 5815 | }; |
| 6566 | } | 5816 | } |
| 6567 | 5817 | ||
| @@ -7196,7 +6446,7 @@ async function read_avatar_load(input) { | |||
| 7196 | await delay(DEFAULT_SAVE_EDIT_TIMEOUT); | 6446 | await delay(DEFAULT_SAVE_EDIT_TIMEOUT); |
| 7197 | 6447 | ||
| 7198 | const formData = new FormData(/** @type {HTMLFormElement} */($('#form_create').get(0))); | 6448 | const formData = new FormData(/** @type {HTMLFormElement} */($('#form_create').get(0))); |
| 7199 | await fetch(getThumbnailUrl('avatar', formData.get('avatar_url')), { | 6449 | await fetch(getThumbnailUrl('avatar', formData.get('avatar_url').toString()), { |
| 7200 | method: 'GET', | 6450 | method: 'GET', |
| 7201 | cache: 'no-cache', | 6451 | cache: 'no-cache', |
| 7202 | headers: { | 6452 | headers: { |
| @@ -7225,8 +6475,15 @@ async function read_avatar_load(input) { | |||
| 7225 | } | 6475 | } |
| 7226 | } | 6476 | } |
| 7227 | 6477 | ||
| 7228 | export function getThumbnailUrl(type, file) { | 6478 | /** |
| 7229 | return `/thumbnail?type=${type}&file=${encodeURIComponent(file)}`; | 6479 | * Gets the URL for a thumbnail of a specific type and file. |
| 6480 | * @param {import('../src/endpoints/thumbnails.js').ThumbnailType} type The type of the thumbnail to get | ||
| 6481 | * @param {string} file The file name or path for which to get the thumbnail URL | ||
| 6482 | * @param {boolean} [t=false] Whether to add a cache-busting timestamp to the URL | ||
| 6483 | * @returns {string} The URL for the thumbnail | ||
| 6484 | */ | ||
| 6485 | export function getThumbnailUrl(type, file, t = false) { | ||
| 6486 | return `/thumbnail?type=${type}&file=${encodeURIComponent(file)}${t ? `&t=${Date.now()}` : ''}`; | ||
| 7230 | } | 6487 | } |
| 7231 | 6488 | ||
| 7232 | export function buildAvatarList(block, entities, { templateId = 'inline_avatar_template', empty = true, interactable = false, highlightFavs = true } = {}) { | 6489 | export function buildAvatarList(block, entities, { templateId = 'inline_avatar_template', empty = true, interactable = false, highlightFavs = true } = {}) { |
| @@ -7266,7 +6523,7 @@ export function buildAvatarList(block, entities, { templateId = 'inline_avatar_t | |||
| 7266 | } | 6523 | } |
| 7267 | else if (entity.type === 'persona') { | 6524 | else if (entity.type === 'persona') { |
| 7268 | avatarTemplate.attr({ 'data-pid': id, 'data-chid': null }); | 6525 | avatarTemplate.attr({ 'data-pid': id, 'data-chid': null }); |
| 7269 | avatarTemplate.find('img').attr('src', getUserAvatar(entity.item.avatar)); | 6526 | avatarTemplate.find('img').attr('src', getThumbnailUrl('persona', entity.item.avatar)); |
| 7270 | avatarTemplate.attr('title', `[Persona] ${entity.item.name}\nFile: ${entity.item.avatar}`); | 6527 | avatarTemplate.attr('title', `[Persona] ${entity.item.name}\nFile: ${entity.item.avatar}`); |
| 7271 | } | 6528 | } |
| 7272 | 6529 | ||
| @@ -7518,6 +6775,8 @@ export function changeMainAPI() { | |||
| 7518 | $('#ai_module_block_novel').css('display', 'none'); | 6775 | $('#ai_module_block_novel').css('display', 'none'); |
| 7519 | } | 6776 | } |
| 7520 | 6777 | ||
| 6778 | $('#prompt_cost_block').toggle(selectedVal === 'textgenerationwebui'); | ||
| 6779 | |||
| 7521 | // Hide common settings for OpenAI | 6780 | // Hide common settings for OpenAI |
| 7522 | console.debug('value?', selectedVal); | 6781 | console.debug('value?', selectedVal); |
| 7523 | if (selectedVal == 'openai') { | 6782 | if (selectedVal == 'openai') { |
| @@ -7530,10 +6789,6 @@ export function changeMainAPI() { | |||
| 7530 | main_api = selectedVal; | 6789 | main_api = selectedVal; |
| 7531 | setOnlineStatus('no_connection'); | 6790 | setOnlineStatus('no_connection'); |
| 7532 | 6791 | ||
| 7533 | if (main_api == 'openai' && oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI) { | ||
| 7534 | $('#api_button_openai').trigger('click'); | ||
| 7535 | } | ||
| 7536 | |||
| 7537 | if (main_api == 'koboldhorde') { | 6792 | if (main_api == 'koboldhorde') { |
| 7538 | getStatusHorde(); | 6793 | getStatusHorde(); |
| 7539 | getHordeModels(true); | 6794 | getHordeModels(true); |
| @@ -7611,59 +6866,7 @@ export async function getSettings() { | |||
| 7611 | // Allow subscribers to mutate settings | 6866 | // Allow subscribers to mutate settings |
| 7612 | await eventSource.emit(event_types.SETTINGS_LOADED_BEFORE, settings); | 6867 | await eventSource.emit(event_types.SETTINGS_LOADED_BEFORE, settings); |
| 7613 | 6868 | ||
| 7614 | //Load KoboldAI settings | ||
| 7615 | koboldai_setting_names = data.koboldai_setting_names; | ||
| 7616 | koboldai_settings = data.koboldai_settings; | ||
| 7617 | koboldai_settings.forEach(function (item, i, arr) { | ||
| 7618 | koboldai_settings[i] = JSON.parse(item); | ||
| 7619 | }); | ||
| 7620 | |||
| 7621 | let arr_holder = {}; | ||
| 7622 | |||
| 7623 | $('#settings_preset').empty(); | ||
| 7624 | $('#settings_preset').append( | ||
| 7625 | '<option value="gui">GUI KoboldAI Settings</option>', | ||
| 7626 | ); //adding in the GUI settings, since it is not loaded dynamically | ||
| 7627 | |||
| 7628 | koboldai_setting_names.forEach(function (item, i, arr) { | ||
| 7629 | arr_holder[item] = i; | ||
| 7630 | $('#settings_preset').append(`<option value=${i}>${item}</option>`); | ||
| 7631 | //console.log('loading preset #'+i+' -- '+item); | ||
| 7632 | }); | ||
| 7633 | koboldai_setting_names = {}; | ||
| 7634 | koboldai_setting_names = arr_holder; | ||
| 7635 | preset_settings = settings.preset_settings; | ||
| 7636 | |||
| 7637 | if (preset_settings == 'gui') { | ||
| 7638 | selectKoboldGuiPreset(); | ||
| 7639 | } else { | ||
| 7640 | if (typeof koboldai_setting_names[preset_settings] !== 'undefined') { | ||
| 7641 | $(`#settings_preset option[value=${koboldai_setting_names[preset_settings]}]`) | ||
| 7642 | .attr('selected', 'true'); | ||
| 7643 | } else { | ||
| 7644 | preset_settings = 'gui'; | ||
| 7645 | selectKoboldGuiPreset(); | ||
| 7646 | } | ||
| 7647 | } | ||
| 7648 | |||
| 7649 | novelai_setting_names = data.novelai_setting_names; | ||
| 7650 | novelai_settings = data.novelai_settings; | ||
| 7651 | novelai_settings.forEach(function (item, i, arr) { | ||
| 7652 | novelai_settings[i] = JSON.parse(item); | ||
| 7653 | }); | ||
| 7654 | arr_holder = {}; | ||
| 7655 | |||
| 7656 | $('#settings_preset_novel').empty(); | ||
| 7657 | |||
| 7658 | novelai_setting_names.forEach(function (item, i, arr) { | ||
| 7659 | arr_holder[item] = i; | ||
| 7660 | $('#settings_preset_novel').append(`<option value=${i}>${item}</option>`); | ||
| 7661 | }); | ||
| 7662 | novelai_setting_names = {}; | ||
| 7663 | novelai_setting_names = arr_holder; | ||
| 7664 | |||
| 7665 | //Load AI model config settings | 6869 | //Load AI model config settings |
| 7666 | |||
| 7667 | amount_gen = settings.amount_gen; | 6870 | amount_gen = settings.amount_gen; |
| 7668 | if (settings.max_context !== undefined) | 6871 | if (settings.max_context !== undefined) |
| 7669 | max_context = parseInt(settings.max_context); | 6872 | max_context = parseInt(settings.max_context); |
| @@ -7674,16 +6877,14 @@ export async function getSettings() { | |||
| 7674 | showSwipeButtons(); | 6877 | showSwipeButtons(); |
| 7675 | 6878 | ||
| 7676 | // Kobold | 6879 | // Kobold |
| 7677 | loadKoboldSettings(settings.kai_settings ?? settings); | 6880 | loadKoboldSettings(data, settings.kai_settings ?? settings, settings); |
| 7678 | 6881 | ||
| 7679 | // Novel | 6882 | // Novel |
| 7680 | loadNovelSettings(settings.nai_settings ?? settings); | 6883 | loadNovelSettings(data, settings.nai_settings ?? settings); |
| 7681 | $(`#settings_preset_novel option[value=${novelai_setting_names[nai_settings.preset_settings_novel]}]`).attr('selected', 'true'); | ||
| 7682 | 6884 | ||
| 7683 | // TextGen | 6885 | // TextGen |
| 7684 | loadTextGenSettings(data, settings); | 6886 | loadTextGenSettings(data, settings); |
| 7685 | 6887 | ||
| 7686 | |||
| 7687 | // OpenAI | 6888 | // OpenAI |
| 7688 | loadOpenAISettings(data, settings.oai_settings ?? settings); | 6889 | loadOpenAISettings(data, settings.oai_settings ?? settings); |
| 7689 | 6890 | ||
| @@ -7726,13 +6927,9 @@ export async function getSettings() { | |||
| 7726 | 6927 | ||
| 7727 | main_api = settings.main_api; | 6928 | main_api = settings.main_api; |
| 7728 | $('#main_api').val(main_api); | 6929 | $('#main_api').val(main_api); |
| 7729 | $('#main_api option[value=' + main_api + ']').attr( | 6930 | $(`#main_api option[value=${main_api}]`).attr('selected', 'true'); |
| 7730 | 'selected', | ||
| 7731 | 'true', | ||
| 7732 | ); | ||
| 7733 | changeMainAPI(); | 6931 | changeMainAPI(); |
| 7734 | 6932 | ||
| 7735 | |||
| 7736 | //Load User's Name and Avatar | 6933 | //Load User's Name and Avatar |
| 7737 | initUserAvatar(settings.user_avatar); | 6934 | initUserAvatar(settings.user_avatar); |
| 7738 | setPersonaDescription(); | 6935 | setPersonaDescription(); |
| @@ -7741,10 +6938,6 @@ export async function getSettings() { | |||
| 7741 | active_character = settings.active_character; | 6938 | active_character = settings.active_character; |
| 7742 | active_group = settings.active_group; | 6939 | active_group = settings.active_group; |
| 7743 | 6940 | ||
| 7744 | //Load the API server URL from settings | ||
| 7745 | api_server = settings.api_server; | ||
| 7746 | $('#api_url_text').val(api_server); | ||
| 7747 | |||
| 7748 | setWorldInfoSettings(settings.world_info_settings ?? settings, data); | 6941 | setWorldInfoSettings(settings.world_info_settings ?? settings, data); |
| 7749 | 6942 | ||
| 7750 | selected_button = settings.selected_button; | 6943 | selected_button = settings.selected_button; |
| @@ -7769,12 +6962,6 @@ export async function getSettings() { | |||
| 7769 | await eventSource.emit(event_types.SETTINGS_LOADED); | 6962 | await eventSource.emit(event_types.SETTINGS_LOADED); |
| 7770 | } | 6963 | } |
| 7771 | 6964 | ||
| 7772 | function selectKoboldGuiPreset() { | ||
| 7773 | $('#settings_preset option[value=gui]') | ||
| 7774 | .attr('selected', 'true') | ||
| 7775 | .trigger('change'); | ||
| 7776 | } | ||
| 7777 | |||
| 7778 | //MARK: saveSettings() | 6965 | //MARK: saveSettings() |
| 7779 | export async function saveSettings(loopCounter = 0) { | 6966 | export async function saveSettings(loopCounter = 0) { |
| 7780 | if (!settingsReady) { | 6967 | if (!settingsReady) { |
| @@ -7794,52 +6981,51 @@ export async function saveSettings(loopCounter = 0) { | |||
| 7794 | TempResponseLength.restore(null); | 6981 | TempResponseLength.restore(null); |
| 7795 | } | 6982 | } |
| 7796 | 6983 | ||
| 7797 | //console.log('Entering settings with name1 = '+name1); | 6984 | const payload = { |
| 7798 | return jQuery.ajax({ | 6985 | firstRun: firstRun, |
| 7799 | type: 'POST', | 6986 | accountStorage: accountStorage.getState(), |
| 7800 | url: '/api/settings/save', | 6987 | currentVersion: currentVersion, |
| 7801 | data: JSON.stringify({ | 6988 | username: name1, |
| 7802 | firstRun: firstRun, | 6989 | active_character: active_character, |
| 7803 | accountStorage: accountStorage.getState(), | 6990 | active_group: active_group, |
| 7804 | currentVersion: currentVersion, | 6991 | user_avatar: user_avatar, |
| 7805 | username: name1, | 6992 | amount_gen: amount_gen, |
| 7806 | active_character: active_character, | 6993 | max_context: max_context, |
| 7807 | active_group: active_group, | 6994 | main_api: main_api, |
| 7808 | api_server: api_server, | 6995 | world_info_settings: getWorldInfoSettings(), |
| 7809 | preset_settings: preset_settings, | 6996 | textgenerationwebui_settings: textgen_settings, |
| 7810 | user_avatar: user_avatar, | 6997 | swipes: swipes, |
| 7811 | amount_gen: amount_gen, | 6998 | horde_settings: horde_settings, |
| 7812 | max_context: max_context, | 6999 | power_user: power_user, |
| 7813 | main_api: main_api, | 7000 | extension_settings: extension_settings, |
| 7814 | world_info_settings: getWorldInfoSettings(), | 7001 | tags: tags, |
| 7815 | textgenerationwebui_settings: textgen_settings, | 7002 | tag_map: tag_map, |
| 7816 | swipes: swipes, | 7003 | nai_settings: nai_settings, |
| 7817 | horde_settings: horde_settings, | 7004 | kai_settings: kai_settings, |
| 7818 | power_user: power_user, | 7005 | oai_settings: oai_settings, |
| 7819 | extension_settings: extension_settings, | 7006 | background: background_settings, |
| 7820 | tags: tags, | 7007 | proxies: proxies, |
| 7821 | tag_map: tag_map, | 7008 | selected_proxy: selected_proxy, |
| 7822 | nai_settings: nai_settings, | 7009 | }; |
| 7823 | kai_settings: kai_settings, | 7010 | |
| 7824 | oai_settings: oai_settings, | 7011 | try { |
| 7825 | background: background_settings, | 7012 | const result = await fetch('/api/settings/save', { |
| 7826 | proxies: proxies, | 7013 | method: 'POST', |
| 7827 | selected_proxy: selected_proxy, | 7014 | headers: getRequestHeaders(), |
| 7828 | }, null, 4), | 7015 | body: JSON.stringify(payload), |
| 7829 | beforeSend: function () { }, | 7016 | cache: 'no-cache', |
| 7830 | cache: false, | 7017 | }); |
| 7831 | dataType: 'json', | 7018 | |
| 7832 | contentType: 'application/json', | 7019 | if (!result.ok) { |
| 7833 | //processData: false, | 7020 | throw new Error(`Failed to save settings: ${result.statusText}`); |
| 7834 | success: async function (data) { | 7021 | } |
| 7835 | eventSource.emit(event_types.SETTINGS_UPDATED); | 7022 | |
| 7836 | }, | 7023 | settings = payload; |
| 7837 | error: function (jqXHR, exception) { | 7024 | await eventSource.emit(event_types.SETTINGS_UPDATED); |
| 7838 | toastr.error(t`Check the server connection and reload the page to prevent data loss.`, t`Settings could not be saved`); | 7025 | } catch (error) { |
| 7839 | console.log(exception); | 7026 | console.error('Error saving settings:', error); |
| 7840 | console.log(jqXHR); | 7027 | toastr.error(t`Check the server connection and reload the page to prevent data loss.`, t`Settings could not be saved`); |
| 7841 | }, | 7028 | } |
| 7842 | }); | ||
| 7843 | } | 7029 | } |
| 7844 | 7030 | ||
| 7845 | /** | 7031 | /** |
| @@ -8094,7 +7280,7 @@ export async function getPastCharacterChats(characterId = null) { | |||
| 8094 | /** | 7280 | /** |
| 8095 | * Helper for `displayPastChats`, to make the same info consistently available for other functions | 7281 | * Helper for `displayPastChats`, to make the same info consistently available for other functions |
| 8096 | */ | 7282 | */ |
| 8097 | function getCurrentChatDetails() { | 7283 | export function getCurrentChatDetails() { |
| 8098 | if (!characters[this_chid] && !selected_group) { | 7284 | if (!characters[this_chid] && !selected_group) { |
| 8099 | return { sessionName: '', group: null, characterName: '', avatarImgURL: '' }; | 7285 | return { sessionName: '', group: null, characterName: '', avatarImgURL: '' }; |
| 8100 | } | 7286 | } |
| @@ -8649,7 +7835,6 @@ export function showSwipeButtons() { | |||
| 8649 | !swipes || | 7835 | !swipes || |
| 8650 | Number($('.mes:last').attr('mesid')) < 0 || | 7836 | Number($('.mes:last').attr('mesid')) < 0 || |
| 8651 | chat[chat.length - 1].is_user || | 7837 | chat[chat.length - 1].is_user || |
| 8652 | chat[chat.length - 1].extra?.image || | ||
| 8653 | (selected_group && is_group_generating) | 7838 | (selected_group && is_group_generating) |
| 8654 | ) { return; } | 7839 | ) { return; } |
| 8655 | 7840 | ||
| @@ -8665,6 +7850,13 @@ export function showSwipeButtons() { | |||
| 8665 | chat[chat.length - 1]['swipe_id'] = 0; // set it to id 0 | 7850 | chat[chat.length - 1]['swipe_id'] = 0; // set it to id 0 |
| 8666 | chat[chat.length - 1]['swipes'] = []; // empty the array | 7851 | chat[chat.length - 1]['swipes'] = []; // empty the array |
| 8667 | chat[chat.length - 1]['swipes'][0] = chat[chat.length - 1]['mes']; //assign swipe array with last message from chat | 7852 | chat[chat.length - 1]['swipes'][0] = chat[chat.length - 1]['mes']; //assign swipe array with last message from chat |
| 7853 | chat[chat.length - 1]['swipe_info'] = []; | ||
| 7854 | chat[chat.length - 1]['swipe_info'][0] = { | ||
| 7855 | 'send_date': chat[chat.length - 1]['send_date'], | ||
| 7856 | 'gen_started': chat[chat.length - 1]['gen_started'], | ||
| 7857 | 'gen_finished': chat[chat.length - 1]['gen_finished'], | ||
| 7858 | 'extra': structuredClone(chat[chat.length - 1]['extra']), | ||
| 7859 | }; | ||
| 8668 | } | 7860 | } |
| 8669 | 7861 | ||
| 8670 | const currentMessage = $('#chat').children().filter(`[mesid="${chat.length - 1}"]`); | 7862 | const currentMessage = $('#chat').children().filter(`[mesid="${chat.length - 1}"]`); |
| @@ -8791,12 +7983,10 @@ export async function saveChatConditional() { | |||
| 8791 | * @param {EventTarget} eventTarget Event target to trigger the event on. | 7983 | * @param {EventTarget} eventTarget Event target to trigger the event on. |
| 8792 | */ | 7984 | */ |
| 8793 | async function importCharacterChat(formData, eventTarget) { | 7985 | async function importCharacterChat(formData, eventTarget) { |
| 8794 | const headers = getRequestHeaders(); | ||
| 8795 | delete headers['Content-Type']; | ||
| 8796 | const fetchResult = await fetch('/api/chats/import', { | 7986 | const fetchResult = await fetch('/api/chats/import', { |
| 8797 | method: 'POST', | 7987 | method: 'POST', |
| 8798 | body: formData, | 7988 | body: formData, |
| 8799 | headers: headers, | 7989 | headers: getRequestHeaders({ omitContentType: true }), |
| 8800 | cache: 'no-cache', | 7990 | cache: 'no-cache', |
| 8801 | }); | 7991 | }); |
| 8802 | 7992 | ||
| @@ -8884,15 +8074,6 @@ export function setGenerationProgress(progress) { | |||
| 8884 | } | 8074 | } |
| 8885 | } | 8075 | } |
| 8886 | 8076 | ||
| 8887 | function isHordeGenerationNotAllowed() { | ||
| 8888 | if (main_api == 'koboldhorde' && preset_settings == 'gui') { | ||
| 8889 | toastr.error(t`GUI Settings preset is not supported for Horde. Please select another preset.`); | ||
| 8890 | return true; | ||
| 8891 | } | ||
| 8892 | |||
| 8893 | return false; | ||
| 8894 | } | ||
| 8895 | |||
| 8896 | export function cancelTtsPlay() { | 8077 | export function cancelTtsPlay() { |
| 8897 | if ('speechSynthesis' in window) { | 8078 | if ('speechSynthesis' in window) { |
| 8898 | speechSynthesis.cancel(); | 8079 | speechSynthesis.cancel(); |
| @@ -8904,19 +8085,25 @@ function updateAlternateGreetingsHintVisibility(root) { | |||
| 8904 | $(root).find('.alternate_grettings_hint').toggle(numberOfGreetings == 0); | 8085 | $(root).find('.alternate_grettings_hint').toggle(numberOfGreetings == 0); |
| 8905 | } | 8086 | } |
| 8906 | 8087 | ||
| 8907 | function openCharacterWorldPopup() { | 8088 | async function openCharacterWorldPopup() { |
| 8908 | const chid = $('#set_character_world').data('chid'); | 8089 | const chid = $('#set_character_world').data('chid'); |
| 8909 | |||
| 8910 | if (menu_type != 'create' && chid === undefined) { | 8090 | if (menu_type != 'create' && chid === undefined) { |
| 8911 | toastr.error('Does not have an Id for this character in world select menu.'); | 8091 | toastr.error('Does not have an Id for this character in world select menu.'); |
| 8912 | return; | 8092 | return; |
| 8913 | } | 8093 | } |
| 8914 | 8094 | ||
| 8915 | async function onSelectCharacterWorld() { | 8095 | // TODO: Maybe make this utility function not use the window context? |
| 8916 | const value = $('.character_world_info_selector').find('option:selected').val(); | 8096 | const fileName = getCharaFilename(chid); |
| 8917 | const worldIndex = value !== '' ? Number(value) : NaN; | 8097 | const charName = (menu_type == 'create' ? create_save.name : characters[chid]?.data?.name) || 'Nameless'; |
| 8918 | const name = !isNaN(worldIndex) ? world_names[worldIndex] : ''; | 8098 | const worldId = (menu_type == 'create' ? create_save.world : characters[chid]?.data?.extensions?.world) || ''; |
| 8099 | const template = $('#character_world_template .character_world').clone(); | ||
| 8100 | template.find('.character_name').text(charName); | ||
| 8919 | 8101 | ||
| 8102 | // --- Event Handlers --- | ||
| 8103 | async function handlePrimaryWorldSelect() { | ||
| 8104 | const selectedValue = $(this).val(); | ||
| 8105 | const worldIndex = selectedValue !== '' ? Number(selectedValue) : NaN; | ||
| 8106 | const name = !isNaN(worldIndex) ? world_names[worldIndex] : ''; | ||
| 8920 | const previousValue = $('#character_world').val(); | 8107 | const previousValue = $('#character_world').val(); |
| 8921 | $('#character_world').val(name); | 8108 | $('#character_world').val(name); |
| 8922 | 8109 | ||
| @@ -8944,26 +8131,26 @@ function openCharacterWorldPopup() { | |||
| 8944 | await createOrEditCharacter(); | 8131 | await createOrEditCharacter(); |
| 8945 | } | 8132 | } |
| 8946 | 8133 | ||
| 8947 | setWorldInfoButtonClass(undefined, !!value); | 8134 | setWorldInfoButtonClass(undefined, !!name); |
| 8948 | } | 8135 | } |
| 8949 | 8136 | ||
| 8950 | function onExtraWorldInfoChanged() { | 8137 | function handleExtrasWorldSelect() { |
| 8951 | const selectorFieldValue = $('.character_extra_world_info_selector').val(); | 8138 | const selectedValues = $(this).val(); |
| 8952 | const selectedWorlds = Array.isArray(selectorFieldValue) ? selectorFieldValue : []; | 8139 | const selectedWorlds = Array.isArray(selectedValues) ? selectedValues : []; |
| 8953 | let charLore = world_info.charLore ?? []; | 8140 | let charLore = world_info.charLore ?? []; |
| 8141 | const tempExtraBooks = selectedWorlds.map((index) => world_names[index]).filter(Boolean); | ||
| 8142 | const existingCharIndex = charLore.findIndex((e) => e.name === fileName); | ||
| 8954 | 8143 | ||
| 8955 | // TODO: Maybe make this utility function not use the window context? | 8144 | if (menu_type == 'create') { |
| 8956 | const fileName = getCharaFilename(chid); | 8145 | create_save.extra_books = tempExtraBooks; |
| 8957 | const tempExtraBooks = selectedWorlds.map((index) => world_names[index]).filter((e) => e !== undefined); | 8146 | return; |
| 8147 | } | ||
| 8958 | 8148 | ||
| 8959 | const existingCharIndex = charLore.findIndex((e) => e.name === fileName); | ||
| 8960 | if (existingCharIndex === -1) { | 8149 | if (existingCharIndex === -1) { |
| 8961 | const newCharLoreEntry = { | 8150 | // Add record only if at least 1 lorebook is selected. |
| 8962 | name: fileName, | 8151 | if (tempExtraBooks.length > 0) { |
| 8963 | extraBooks: tempExtraBooks, | 8152 | charLore.push({ name: fileName, extraBooks: tempExtraBooks }); |
| 8964 | }; | 8153 | } |
| 8965 | |||
| 8966 | charLore.push(newCharLoreEntry); | ||
| 8967 | } else if (tempExtraBooks.length === 0) { | 8154 | } else if (tempExtraBooks.length === 0) { |
| 8968 | charLore.splice(existingCharIndex, 1); | 8155 | charLore.splice(existingCharIndex, 1); |
| 8969 | } else { | 8156 | } else { |
| @@ -8974,62 +8161,43 @@ function openCharacterWorldPopup() { | |||
| 8974 | saveSettingsDebounced(); | 8161 | saveSettingsDebounced(); |
| 8975 | } | 8162 | } |
| 8976 | 8163 | ||
| 8977 | const template = $('#character_world_template .character_world').clone(); | 8164 | // --- Populate Dropdowns --- |
| 8978 | const select = template.find('.character_world_info_selector'); | 8165 | // Append to primary dropdown. |
| 8979 | const extraSelect = template.find('.character_extra_world_info_selector'); | 8166 | const primarySelect = template.find('.character_world_info_selector'); |
| 8980 | const name = (menu_type == 'create' ? create_save.name : characters[chid]?.data?.name) || 'Nameless'; | ||
| 8981 | const worldId = (menu_type == 'create' ? create_save.world : characters[chid]?.data?.extensions?.world) || ''; | ||
| 8982 | template.find('.character_name').text(name); | ||
| 8983 | |||
| 8984 | // Not needed on mobile | ||
| 8985 | if (!isMobile()) { | ||
| 8986 | $(extraSelect).select2({ | ||
| 8987 | width: '100%', | ||
| 8988 | placeholder: t`No auxillary Lorebooks set. Click here to select.`, | ||
| 8989 | allowClear: true, | ||
| 8990 | closeOnSelect: false, | ||
| 8991 | }); | ||
| 8992 | } | ||
| 8993 | |||
| 8994 | // Apped to base dropdown | ||
| 8995 | world_names.forEach((item, i) => { | 8167 | world_names.forEach((item, i) => { |
| 8996 | const option = document.createElement('option'); | 8168 | primarySelect.append(new Option(item, String(i), item === worldId, item === worldId)); |
| 8997 | option.value = String(i); | ||
| 8998 | option.innerText = item; | ||
| 8999 | option.selected = item === worldId; | ||
| 9000 | select.append(option); | ||
| 9001 | }); | 8169 | }); |
| 9002 | 8170 | ||
| 9003 | // Append to extras dropdown | 8171 | // Append to extras dropdown. |
| 9004 | if (world_names.length > 0) { | 8172 | const extrasSelect = template.find('.character_extra_world_info_selector'); |
| 9005 | extraSelect.empty(); | 8173 | const existingCharLore = world_info.charLore?.find((e) => e.name === fileName); |
| 9006 | } | ||
| 9007 | world_names.forEach((item, i) => { | 8174 | world_names.forEach((item, i) => { |
| 9008 | const option = document.createElement('option'); | 8175 | const array = (menu_type == 'create' ? create_save.extra_books : existingCharLore?.extraBooks); |
| 9009 | option.value = String(i); | 8176 | const isSelected = !!array?.includes(item); |
| 9010 | option.innerText = item; | 8177 | extrasSelect.append(new Option(item, String(i), isSelected, isSelected)); |
| 9011 | |||
| 9012 | const existingCharLore = world_info.charLore?.find((e) => e.name === getCharaFilename()); | ||
| 9013 | if (existingCharLore) { | ||
| 9014 | option.selected = existingCharLore.extraBooks.includes(item); | ||
| 9015 | } else { | ||
| 9016 | option.selected = false; | ||
| 9017 | } | ||
| 9018 | extraSelect.append(option); | ||
| 9019 | }); | 8178 | }); |
| 9020 | 8179 | ||
| 9021 | select.on('change', onSelectCharacterWorld); | 8180 | const popup = new Popup(template, POPUP_TYPE.TEXT, '', { |
| 9022 | extraSelect.on('mousedown change', async function (e) { | 8181 | onOpen: function (popup) { |
| 9023 | // If there's no world names, don't do anything | 8182 | const popupDialog = $(popup.dlg); |
| 9024 | if (world_names.length === 0) { | 8183 | |
| 9025 | e.preventDefault(); | 8184 | primarySelect.on('change', handlePrimaryWorldSelect); |
| 9026 | return; | 8185 | extrasSelect.on('change', handleExtrasWorldSelect); |
| 9027 | } | 8186 | |
| 9028 | 8187 | // Not needed on mobile. | |
| 9029 | onExtraWorldInfoChanged(); | 8188 | if (!isMobile()) { |
| 8189 | extrasSelect.select2({ | ||
| 8190 | width: '100%', | ||
| 8191 | placeholder: t`No auxiliary Lorebooks set. Click here to select.`, | ||
| 8192 | allowClear: true, | ||
| 8193 | closeOnSelect: false, | ||
| 8194 | dropdownParent: popupDialog, | ||
| 8195 | }); | ||
| 8196 | } | ||
| 8197 | }, | ||
| 9030 | }); | 8198 | }); |
| 9031 | 8199 | ||
| 9032 | callPopup(template, 'text'); | 8200 | await popup.show(); |
| 9033 | } | 8201 | } |
| 9034 | 8202 | ||
| 9035 | function openAlternateGreetings() { | 8203 | function openAlternateGreetings() { |
| @@ -9122,8 +8290,7 @@ async function createOrEditCharacter(e) { | |||
| 9122 | formData.set('avatar', convertedFile); | 8290 | formData.set('avatar', convertedFile); |
| 9123 | } | 8291 | } |
| 9124 | 8292 | ||
| 9125 | const headers = getRequestHeaders(); | 8293 | const headers = getRequestHeaders({ omitContentType: true }); |
| 9126 | delete headers['Content-Type']; | ||
| 9127 | 8294 | ||
| 9128 | if ($('#form_create').attr('actiontype') == 'createcharacter') { | 8295 | if ($('#form_create').attr('actiontype') == 'createcharacter') { |
| 9129 | if (String($('#character_name_pole').val()).length === 0) { | 8296 | if (String($('#character_name_pole').val()).length === 0) { |
| @@ -9192,6 +8359,15 @@ async function createOrEditCharacter(e) { | |||
| 9192 | field.callback && field.callback(fieldValue); | 8359 | field.callback && field.callback(fieldValue); |
| 9193 | }); | 8360 | }); |
| 9194 | 8361 | ||
| 8362 | if (Array.isArray(create_save.extra_books) && create_save.extra_books.length > 0) { | ||
| 8363 | const fileName = getCharaFilename(null, { manualAvatarKey: avatarId }); | ||
| 8364 | const charLore = world_info.charLore ?? []; | ||
| 8365 | charLore.push({ name: fileName, extraBooks: create_save.extra_books }); | ||
| 8366 | Object.assign(world_info, { charLore: charLore }); | ||
| 8367 | saveSettingsDebounced(); | ||
| 8368 | } | ||
| 8369 | create_save.extra_books = []; | ||
| 8370 | |||
| 9195 | $('#character_popup-button-h3').text('Create character'); | 8371 | $('#character_popup-button-h3').text('Create character'); |
| 9196 | 8372 | ||
| 9197 | create_save.avatar = null; | 8373 | create_save.avatar = null; |
| @@ -9339,7 +8515,7 @@ export function swipe_left(_event, { source, repeated } = {}) { | |||
| 9339 | const this_mes_block_height = this_mes_block[0].scrollHeight; | 8515 | const this_mes_block_height = this_mes_block[0].scrollHeight; |
| 9340 | chat[chat.length - 1]['mes'] = chat[chat.length - 1]['swipes'][chat[chat.length - 1]['swipe_id']]; | 8516 | chat[chat.length - 1]['mes'] = chat[chat.length - 1]['swipes'][chat[chat.length - 1]['swipe_id']]; |
| 9341 | chat[chat.length - 1]['send_date'] = chat[chat.length - 1].swipe_info[chat[chat.length - 1]['swipe_id']]?.send_date || chat[chat.length - 1].send_date; //load the last mes box with the latest generation | 8517 | chat[chat.length - 1]['send_date'] = chat[chat.length - 1].swipe_info[chat[chat.length - 1]['swipe_id']]?.send_date || chat[chat.length - 1].send_date; //load the last mes box with the latest generation |
| 9342 | chat[chat.length - 1]['extra'] = JSON.parse(JSON.stringify(chat[chat.length - 1].swipe_info[chat[chat.length - 1]['swipe_id']]?.extra || chat[chat.length - 1].extra)); | 8518 | chat[chat.length - 1]['extra'] = structuredClone(chat[chat.length - 1].swipe_info[chat[chat.length - 1]['swipe_id']]?.extra || chat[chat.length - 1].extra); |
| 9343 | 8519 | ||
| 9344 | if (chat[chat.length - 1].extra) { | 8520 | if (chat[chat.length - 1].extra) { |
| 9345 | // if message has memory attached - remove it to allow regen | 8521 | // if message has memory attached - remove it to allow regen |
| @@ -9401,6 +8577,7 @@ export function swipe_left(_event, { source, repeated } = {}) { | |||
| 9401 | easing: animation_easing, | 8577 | easing: animation_easing, |
| 9402 | queue: false, | 8578 | queue: false, |
| 9403 | complete: async function () { | 8579 | complete: async function () { |
| 8580 | appendMediaToMessage(chat[chat.length - 1], $(this).parent().children('.mes_block')); | ||
| 9404 | await eventSource.emit(event_types.MESSAGE_SWIPED, (chat.length - 1)); | 8581 | await eventSource.emit(event_types.MESSAGE_SWIPED, (chat.length - 1)); |
| 9405 | saveChatDebounced(); | 8582 | saveChatDebounced(); |
| 9406 | }, | 8583 | }, |
| @@ -9476,7 +8653,7 @@ export function swipe_right(_event = null, { source, repeated } = {}) { | |||
| 9476 | 'send_date': chat[chat.length - 1]['send_date'], | 8653 | 'send_date': chat[chat.length - 1]['send_date'], |
| 9477 | 'gen_started': chat[chat.length - 1]['gen_started'], | 8654 | 'gen_started': chat[chat.length - 1]['gen_started'], |
| 9478 | 'gen_finished': chat[chat.length - 1]['gen_finished'], | 8655 | 'gen_finished': chat[chat.length - 1]['gen_finished'], |
| 9479 | 'extra': JSON.parse(JSON.stringify(chat[chat.length - 1]['extra'])), | 8656 | 'extra': structuredClone(chat[chat.length - 1]['extra']), |
| 9480 | }; | 8657 | }; |
| 9481 | //assign swipe info array with last message from chat | 8658 | //assign swipe info array with last message from chat |
| 9482 | } | 8659 | } |
| @@ -9500,6 +8677,11 @@ export function swipe_right(_event = null, { source, repeated } = {}) { | |||
| 9500 | if (chat[chat.length - 1].extra.display_text) { | 8677 | if (chat[chat.length - 1].extra.display_text) { |
| 9501 | delete chat[chat.length - 1].extra.display_text; | 8678 | delete chat[chat.length - 1].extra.display_text; |
| 9502 | } | 8679 | } |
| 8680 | |||
| 8681 | delete chat[chat.length - 1].extra.image; | ||
| 8682 | delete chat[chat.length - 1].extra.image_swipes; | ||
| 8683 | delete chat[chat.length - 1].extra.video; | ||
| 8684 | delete chat[chat.length - 1].extra.inline_image; | ||
| 9503 | } | 8685 | } |
| 9504 | if (!Array.isArray(chat[chat.length - 1]['swipe_info'])) { | 8686 | if (!Array.isArray(chat[chat.length - 1]['swipe_info'])) { |
| 9505 | chat[chat.length - 1]['swipe_info'] = []; | 8687 | chat[chat.length - 1]['swipe_info'] = []; |
| @@ -9512,7 +8694,7 @@ export function swipe_right(_event = null, { source, repeated } = {}) { | |||
| 9512 | } else if (parseInt(chat[chat.length - 1]['swipe_id']) < chat[chat.length - 1]['swipes'].length) { //otherwise, if the id is less than the number of swipes | 8694 | } else if (parseInt(chat[chat.length - 1]['swipe_id']) < chat[chat.length - 1]['swipes'].length) { //otherwise, if the id is less than the number of swipes |
| 9513 | chat[chat.length - 1]['mes'] = chat[chat.length - 1]['swipes'][chat[chat.length - 1]['swipe_id']]; //load the last mes box with the latest generation | 8695 | chat[chat.length - 1]['mes'] = chat[chat.length - 1]['swipes'][chat[chat.length - 1]['swipe_id']]; //load the last mes box with the latest generation |
| 9514 | chat[chat.length - 1]['send_date'] = chat[chat.length - 1]?.swipe_info[chat[chat.length - 1]['swipe_id']]?.send_date || chat[chat.length - 1]['send_date']; //update send date | 8696 | chat[chat.length - 1]['send_date'] = chat[chat.length - 1]?.swipe_info[chat[chat.length - 1]['swipe_id']]?.send_date || chat[chat.length - 1]['send_date']; //update send date |
| 9515 | chat[chat.length - 1]['extra'] = JSON.parse(JSON.stringify(chat[chat.length - 1].swipe_info[chat[chat.length - 1]['swipe_id']]?.extra || chat[chat.length - 1].extra || [])); | 8697 | chat[chat.length - 1]['extra'] = structuredClone(chat[chat.length - 1].swipe_info[chat[chat.length - 1]['swipe_id']]?.extra || chat[chat.length - 1].extra || []); |
| 9516 | run_swipe_right = true; //then prepare to do normal right swipe to show next message | 8698 | run_swipe_right = true; //then prepare to do normal right swipe to show next message |
| 9517 | } | 8699 | } |
| 9518 | 8700 | ||
| @@ -9595,6 +8777,7 @@ export function swipe_right(_event = null, { source, repeated } = {}) { | |||
| 9595 | easing: animation_easing, | 8777 | easing: animation_easing, |
| 9596 | queue: false, | 8778 | queue: false, |
| 9597 | complete: async function () { | 8779 | complete: async function () { |
| 8780 | appendMediaToMessage(chat[chat.length - 1], swipeMessage); | ||
| 9598 | await eventSource.emit(event_types.MESSAGE_SWIPED, (chat.length - 1)); | 8781 | await eventSource.emit(event_types.MESSAGE_SWIPED, (chat.length - 1)); |
| 9599 | if (run_generate && !is_send_press && parseInt(chat[chat.length - 1]['swipe_id']) === chat[chat.length - 1]['swipes'].length) { | 8782 | if (run_generate && !is_send_press && parseInt(chat[chat.length - 1]['swipe_id']) === chat[chat.length - 1]['swipes'].length) { |
| 9600 | console.debug('caught here 2'); | 8783 | console.debug('caught here 2'); |
| @@ -9640,219 +8823,6 @@ export function swipe_right(_event = null, { source, repeated } = {}) { | |||
| 9640 | } | 8823 | } |
| 9641 | 8824 | ||
| 9642 | /** | 8825 | /** |
| 9643 | * @typedef {object} ConnectAPIMap | ||
| 9644 | * @property {string} selected - API name (e.g. "textgenerationwebui", "openai") | ||
| 9645 | * @property {string?} [button] - CSS selector for the API button | ||
| 9646 | * @property {string?} [type] - API type, mostly used by text completion. (e.g. "openrouter") | ||
| 9647 | * @property {string?} [source] - API source, mostly used by chat completion. (e.g. "openai") | ||
| 9648 | */ | ||
| 9649 | |||
| 9650 | /** | ||
| 9651 | * @type {Record<string, ConnectAPIMap>} | ||
| 9652 | */ | ||
| 9653 | export const CONNECT_API_MAP = { | ||
| 9654 | // Default APIs not contined inside text gen / chat gen | ||
| 9655 | 'kobold': { | ||
| 9656 | selected: 'kobold', | ||
| 9657 | button: '#api_button', | ||
| 9658 | }, | ||
| 9659 | 'horde': { | ||
| 9660 | selected: 'koboldhorde', | ||
| 9661 | }, | ||
| 9662 | 'novel': { | ||
| 9663 | selected: 'novel', | ||
| 9664 | button: '#api_button_novel', | ||
| 9665 | }, | ||
| 9666 | 'koboldcpp': { | ||
| 9667 | selected: 'textgenerationwebui', | ||
| 9668 | button: '#api_button_textgenerationwebui', | ||
| 9669 | type: textgen_types.KOBOLDCPP, | ||
| 9670 | }, | ||
| 9671 | // KoboldCpp alias | ||
| 9672 | 'kcpp': { | ||
| 9673 | selected: 'textgenerationwebui', | ||
| 9674 | button: '#api_button_textgenerationwebui', | ||
| 9675 | type: textgen_types.KOBOLDCPP, | ||
| 9676 | }, | ||
| 9677 | 'openai': { | ||
| 9678 | selected: 'openai', | ||
| 9679 | button: '#api_button_openai', | ||
| 9680 | source: chat_completion_sources.OPENAI, | ||
| 9681 | }, | ||
| 9682 | // OpenAI alias | ||
| 9683 | 'oai': { | ||
| 9684 | selected: 'openai', | ||
| 9685 | button: '#api_button_openai', | ||
| 9686 | source: chat_completion_sources.OPENAI, | ||
| 9687 | }, | ||
| 9688 | // Google alias | ||
| 9689 | 'google': { | ||
| 9690 | selected: 'openai', | ||
| 9691 | button: '#api_button_openai', | ||
| 9692 | source: chat_completion_sources.MAKERSUITE, | ||
| 9693 | }, | ||
| 9694 | // OpenRouter special naming, to differentiate between chat comp and text comp | ||
| 9695 | 'openrouter': { | ||
| 9696 | selected: 'openai', | ||
| 9697 | button: '#api_button_openai', | ||
| 9698 | source: chat_completion_sources.OPENROUTER, | ||
| 9699 | }, | ||
| 9700 | 'openrouter-text': { | ||
| 9701 | selected: 'textgenerationwebui', | ||
| 9702 | button: '#api_button_textgenerationwebui', | ||
| 9703 | type: textgen_types.OPENROUTER, | ||
| 9704 | }, | ||
| 9705 | }; | ||
| 9706 | |||
| 9707 | // Collect all unique API names in an array | ||
| 9708 | export const UNIQUE_APIS = [...new Set(Object.values(CONNECT_API_MAP).map(x => x.selected))]; | ||
| 9709 | |||
| 9710 | // Fill connections map from textgen_types and chat_completion_sources | ||
| 9711 | for (const textGenType of Object.values(textgen_types)) { | ||
| 9712 | if (CONNECT_API_MAP[textGenType]) continue; | ||
| 9713 | CONNECT_API_MAP[textGenType] = { | ||
| 9714 | selected: 'textgenerationwebui', | ||
| 9715 | button: '#api_button_textgenerationwebui', | ||
| 9716 | type: textGenType, | ||
| 9717 | }; | ||
| 9718 | } | ||
| 9719 | for (const chatCompletionSource of Object.values(chat_completion_sources)) { | ||
| 9720 | if (CONNECT_API_MAP[chatCompletionSource]) continue; | ||
| 9721 | CONNECT_API_MAP[chatCompletionSource] = { | ||
| 9722 | selected: 'openai', | ||
| 9723 | button: '#api_button_openai', | ||
| 9724 | source: chatCompletionSource, | ||
| 9725 | }; | ||
| 9726 | } | ||
| 9727 | |||
| 9728 | async function selectContextCallback(args, name) { | ||
| 9729 | if (!name) { | ||
| 9730 | return power_user.context.preset; | ||
| 9731 | } | ||
| 9732 | |||
| 9733 | const quiet = isTrueBoolean(args?.quiet); | ||
| 9734 | const contextNames = context_presets.map(preset => preset.name); | ||
| 9735 | const fuse = new Fuse(contextNames); | ||
| 9736 | const result = fuse.search(name); | ||
| 9737 | |||
| 9738 | if (result.length === 0) { | ||
| 9739 | !quiet && toastr.warning(t`Context template '${name}' not found`); | ||
| 9740 | return ''; | ||
| 9741 | } | ||
| 9742 | |||
| 9743 | const foundName = result[0].item; | ||
| 9744 | selectContextPreset(foundName, { quiet: quiet }); | ||
| 9745 | return foundName; | ||
| 9746 | } | ||
| 9747 | |||
| 9748 | async function selectInstructCallback(args, name) { | ||
| 9749 | if (!name) { | ||
| 9750 | return power_user.instruct.enabled || isTrueBoolean(args?.forceGet) ? power_user.instruct.preset : ''; | ||
| 9751 | } | ||
| 9752 | |||
| 9753 | const quiet = isTrueBoolean(args?.quiet); | ||
| 9754 | const instructNames = instruct_presets.map(preset => preset.name); | ||
| 9755 | const fuse = new Fuse(instructNames); | ||
| 9756 | const result = fuse.search(name); | ||
| 9757 | |||
| 9758 | if (result.length === 0) { | ||
| 9759 | !quiet && toastr.warning(t`Instruct template '${name}' not found`); | ||
| 9760 | return ''; | ||
| 9761 | } | ||
| 9762 | |||
| 9763 | const foundName = result[0].item; | ||
| 9764 | selectInstructPreset(foundName, { quiet: quiet }); | ||
| 9765 | return foundName; | ||
| 9766 | } | ||
| 9767 | |||
| 9768 | async function enableInstructCallback() { | ||
| 9769 | $('#instruct_enabled').prop('checked', true).trigger('input').trigger('change'); | ||
| 9770 | return ''; | ||
| 9771 | } | ||
| 9772 | |||
| 9773 | async function disableInstructCallback() { | ||
| 9774 | $('#instruct_enabled').prop('checked', false).trigger('input').trigger('change'); | ||
| 9775 | return ''; | ||
| 9776 | } | ||
| 9777 | |||
| 9778 | /** | ||
| 9779 | * @param {string} text API name | ||
| 9780 | */ | ||
| 9781 | async function connectAPISlash(args, text) { | ||
| 9782 | if (!text.trim()) { | ||
| 9783 | for (const [key, config] of Object.entries(CONNECT_API_MAP)) { | ||
| 9784 | if (config.selected !== main_api) continue; | ||
| 9785 | |||
| 9786 | if (config.source) { | ||
| 9787 | if (oai_settings.chat_completion_source === config.source) { | ||
| 9788 | return key; | ||
| 9789 | } else { | ||
| 9790 | continue; | ||
| 9791 | } | ||
| 9792 | } | ||
| 9793 | |||
| 9794 | if (config.type) { | ||
| 9795 | if (textgen_settings.type === config.type) { | ||
| 9796 | return key; | ||
| 9797 | } else { | ||
| 9798 | continue; | ||
| 9799 | } | ||
| 9800 | } | ||
| 9801 | |||
| 9802 | return key; | ||
| 9803 | } | ||
| 9804 | |||
| 9805 | console.error('FIXME: The current API is not in the API map'); | ||
| 9806 | return ''; | ||
| 9807 | } | ||
| 9808 | |||
| 9809 | const apiConfig = CONNECT_API_MAP[text.toLowerCase()]; | ||
| 9810 | if (!apiConfig) { | ||
| 9811 | toastr.error(t`Error: ${text} is not a valid API`); | ||
| 9812 | return ''; | ||
| 9813 | } | ||
| 9814 | |||
| 9815 | let connectionRequired = false; | ||
| 9816 | |||
| 9817 | if (main_api !== apiConfig.selected) { | ||
| 9818 | $(`#main_api option[value='${apiConfig.selected || text}']`).prop('selected', true); | ||
| 9819 | $('#main_api').trigger('change'); | ||
| 9820 | connectionRequired = true; | ||
| 9821 | } | ||
| 9822 | |||
| 9823 | if (apiConfig.source && oai_settings.chat_completion_source !== apiConfig.source) { | ||
| 9824 | $(`#chat_completion_source option[value='${apiConfig.source}']`).prop('selected', true); | ||
| 9825 | $('#chat_completion_source').trigger('change'); | ||
| 9826 | connectionRequired = true; | ||
| 9827 | } | ||
| 9828 | |||
| 9829 | if (apiConfig.type && textgen_settings.type !== apiConfig.type) { | ||
| 9830 | $(`#textgen_type option[value='${apiConfig.type}']`).prop('selected', true); | ||
| 9831 | $('#textgen_type').trigger('change'); | ||
| 9832 | connectionRequired = true; | ||
| 9833 | } | ||
| 9834 | |||
| 9835 | if (connectionRequired && apiConfig.button) { | ||
| 9836 | $(apiConfig.button).trigger('click'); | ||
| 9837 | } | ||
| 9838 | |||
| 9839 | const quiet = isTrueBoolean(args?.quiet); | ||
| 9840 | const toast = quiet ? jQuery() : toastr.info(t`API set to ${text}, trying to connect..`); | ||
| 9841 | |||
| 9842 | try { | ||
| 9843 | if (connectionRequired) { | ||
| 9844 | await waitUntilCondition(() => online_status !== 'no_connection', 5000, 100); | ||
| 9845 | } | ||
| 9846 | console.log('Connection successful'); | ||
| 9847 | } catch { | ||
| 9848 | console.log('Could not connect after 5 seconds, skipping.'); | ||
| 9849 | } | ||
| 9850 | |||
| 9851 | toastr.clear(toast); | ||
| 9852 | return text; | ||
| 9853 | } | ||
| 9854 | |||
| 9855 | /** | ||
| 9856 | * Imports supported files dropped into the app window. | 8826 | * Imports supported files dropped into the app window. |
| 9857 | * @param {File[]} files Array of files to process | 8827 | * @param {File[]} files Array of files to process |
| 9858 | * @param {Map<File, string>} [data] Extra data to pass to the import function | 8828 | * @param {Map<File, string>} [data] Extra data to pass to the import function |
| @@ -9870,6 +8840,7 @@ export async function processDroppedFiles(files, data = new Map()) { | |||
| 9870 | 8840 | ||
| 9871 | const allowedExtensions = [ | 8841 | const allowedExtensions = [ |
| 9872 | 'charx', | 8842 | 'charx', |
| 8843 | 'byaf', | ||
| 9873 | ]; | 8844 | ]; |
| 9874 | 8845 | ||
| 9875 | const avatarFileNames = []; | 8846 | const avatarFileNames = []; |
| @@ -9933,7 +8904,7 @@ async function importCharacter(file, { preserveFileName = '', importTags = false | |||
| 9933 | } | 8904 | } |
| 9934 | 8905 | ||
| 9935 | const ext = file.name.match(/\.(\w+)$/); | 8906 | const ext = file.name.match(/\.(\w+)$/); |
| 9936 | if (!ext || !(['json', 'png', 'yaml', 'yml', 'charx'].includes(ext[1].toLowerCase()))) { | 8907 | if (!ext || !(['json', 'png', 'yaml', 'yml', 'charx', 'byaf'].includes(ext[1].toLowerCase()))) { |
| 9937 | return; | 8908 | return; |
| 9938 | } | 8909 | } |
| 9939 | 8910 | ||
| @@ -9944,32 +8915,38 @@ async function importCharacter(file, { preserveFileName = '', importTags = false | |||
| 9944 | formData.append('file_type', format); | 8915 | formData.append('file_type', format); |
| 9945 | if (preserveFileName) formData.append('preserved_name', preserveFileName); | 8916 | if (preserveFileName) formData.append('preserved_name', preserveFileName); |
| 9946 | 8917 | ||
| 9947 | const data = await jQuery.ajax({ | 8918 | try { |
| 9948 | type: 'POST', | 8919 | const result = await fetch('/api/characters/import', { |
| 9949 | url: '/api/characters/import', | 8920 | method: 'POST', |
| 9950 | data: formData, | 8921 | body: formData, |
| 9951 | async: true, | 8922 | headers: getRequestHeaders({ omitContentType: true }), |
| 9952 | cache: false, | 8923 | cache: 'no-cache', |
| 9953 | contentType: false, | 8924 | }); |
| 9954 | processData: false, | ||
| 9955 | }); | ||
| 9956 | 8925 | ||
| 9957 | if (data.error) { | 8926 | if (!result.ok) { |
| 9958 | toastr.error(t`The file is likely invalid or corrupted.`, t`Could not import character`); | 8927 | throw new Error(`Failed to import character: ${result.statusText}`); |
| 9959 | return; | 8928 | } |
| 9960 | } | ||
| 9961 | 8929 | ||
| 9962 | if (data.file_name !== undefined) { | 8930 | const data = await result.json(); |
| 9963 | $('#character_search_bar').val('').trigger('input'); | 8931 | |
| 8932 | if (data.error) { | ||
| 8933 | throw new Error(`Server returned an error: ${data.error}`); | ||
| 8934 | } | ||
| 9964 | 8935 | ||
| 9965 | toastr.success(t`Character Created: ${String(data.file_name).replace('.png', '')}`); | 8936 | if (data.file_name !== undefined) { |
| 9966 | let avatarFileName = `${data.file_name}.png`; | 8937 | $('#character_search_bar').val('').trigger('input'); |
| 9967 | if (importTags) { | ||
| 9968 | await importCharactersTags([avatarFileName]); | ||
| 9969 | 8938 | ||
| 9970 | selectImportedChar(data.file_name); | 8939 | toastr.success(t`Character Created: ${String(data.file_name).replace('.png', '')}`); |
| 8940 | let avatarFileName = `${data.file_name}.png`; | ||
| 8941 | if (importTags) { | ||
| 8942 | await importCharactersTags([avatarFileName]); | ||
| 8943 | selectImportedChar(data.file_name); | ||
| 8944 | } | ||
| 8945 | return avatarFileName; | ||
| 9971 | } | 8946 | } |
| 9972 | return avatarFileName; | 8947 | } catch (error) { |
| 8948 | console.error('Error importing character', error); | ||
| 8949 | toastr.error(t`The file is likely invalid or corrupted.`, t`Could not import character`); | ||
| 9973 | } | 8950 | } |
| 9974 | } | 8951 | } |
| 9975 | 8952 | ||
| @@ -9995,32 +8972,6 @@ async function importFromURL(items, files) { | |||
| 9995 | } | 8972 | } |
| 9996 | } | 8973 | } |
| 9997 | 8974 | ||
| 9998 | async function doImpersonate(args, prompt) { | ||
| 9999 | const options = prompt?.trim() ? { quiet_prompt: prompt.trim(), quietToLoud: true } : {}; | ||
| 10000 | const shouldAwait = isTrueBoolean(args?.await); | ||
| 10001 | const outerPromise = new Promise((outerResolve) => setTimeout(async () => { | ||
| 10002 | try { | ||
| 10003 | await waitUntilCondition(() => !is_send_press && !is_group_generating, 10000, 100); | ||
| 10004 | } catch { | ||
| 10005 | console.warn('Timeout waiting for generation unlock'); | ||
| 10006 | toastr.warning(t`Cannot run /impersonate command while the reply is being generated.`); | ||
| 10007 | return ''; | ||
| 10008 | } | ||
| 10009 | |||
| 10010 | // Prevent generate recursion | ||
| 10011 | $('#send_textarea').val('')[0].dispatchEvent(new Event('input', { bubbles: true })); | ||
| 10012 | |||
| 10013 | outerResolve(new Promise(innerResolve => setTimeout(() => innerResolve(Generate('impersonate', options)), 1))); | ||
| 10014 | }, 1)); | ||
| 10015 | |||
| 10016 | if (shouldAwait) { | ||
| 10017 | const innerPromise = await outerPromise; | ||
| 10018 | await innerPromise; | ||
| 10019 | } | ||
| 10020 | |||
| 10021 | return ''; | ||
| 10022 | } | ||
| 10023 | |||
| 10024 | export async function doNewChat({ deleteCurrentChat = false } = {}) { | 8975 | export async function doNewChat({ deleteCurrentChat = false } = {}) { |
| 10025 | //Make a new chat for selected character | 8976 | //Make a new chat for selected character |
| 10026 | if ((!selected_group && this_chid == undefined) || menu_type == 'create') { | 8977 | if ((!selected_group && this_chid == undefined) || menu_type == 'create') { |
| @@ -10055,33 +9006,6 @@ export async function doNewChat({ deleteCurrentChat = false } = {}) { | |||
| 10055 | 9006 | ||
| 10056 | } | 9007 | } |
| 10057 | 9008 | ||
| 10058 | async function doDeleteChat() { | ||
| 10059 | await displayPastChats(); | ||
| 10060 | let currentChatDeleteButton = $('.select_chat_block[highlight=\'true\']').parent().find('.PastChat_cross'); | ||
| 10061 | $(currentChatDeleteButton).trigger('click'); | ||
| 10062 | await delay(1); | ||
| 10063 | $('#dialogue_popup_ok').trigger('click', { fromSlashCommand: true }); | ||
| 10064 | return ''; | ||
| 10065 | } | ||
| 10066 | |||
| 10067 | async function doRenameChat(_, chatName) { | ||
| 10068 | if (!chatName) { | ||
| 10069 | toastr.warning(t`Name must be provided as an argument to rename this chat.`); | ||
| 10070 | return ''; | ||
| 10071 | } | ||
| 10072 | |||
| 10073 | const currentChatName = getCurrentChatId(); | ||
| 10074 | if (!currentChatName) { | ||
| 10075 | toastr.warning(t`No chat selected that can be renamed.`); | ||
| 10076 | return ''; | ||
| 10077 | } | ||
| 10078 | |||
| 10079 | await renameChat(currentChatName, chatName); | ||
| 10080 | |||
| 10081 | toastr.success(t`Successfully renamed chat to: ${chatName}`); | ||
| 10082 | return ''; | ||
| 10083 | } | ||
| 10084 | |||
| 10085 | /** | 9009 | /** |
| 10086 | * Renames a group or character chat. | 9010 | * Renames a group or character chat. |
| 10087 | * @param {object} param Parameters for renaming chat | 9011 | * @param {object} param Parameters for renaming chat |
| @@ -10195,12 +9119,6 @@ export async function updateRemoteChatName(characterId, newName) { | |||
| 10195 | } | 9119 | } |
| 10196 | } | 9120 | } |
| 10197 | 9121 | ||
| 10198 | /** | ||
| 10199 | * /getchatname` slash command | ||
| 10200 | */ | ||
| 10201 | async function doGetChatName() { | ||
| 10202 | return getCurrentChatDetails().sessionName; | ||
| 10203 | } | ||
| 10204 | 9122 | ||
| 10205 | function doCharListDisplaySwitch() { | 9123 | function doCharListDisplaySwitch() { |
| 10206 | power_user.charListGrid = !power_user.charListGrid; | 9124 | power_user.charListGrid = !power_user.charListGrid; |
| @@ -10208,11 +9126,6 @@ function doCharListDisplaySwitch() { | |||
| 10208 | saveSettingsDebounced(); | 9126 | saveSettingsDebounced(); |
| 10209 | } | 9127 | } |
| 10210 | 9128 | ||
| 10211 | function doCloseChat() { | ||
| 10212 | $('#option_close_chat').trigger('click'); | ||
| 10213 | return ''; | ||
| 10214 | } | ||
| 10215 | |||
| 10216 | /** | 9129 | /** |
| 10217 | * Function to handle the deletion of a character, given a specific popup type and character ID. | 9130 | * Function to handle the deletion of a character, given a specific popup type and character ID. |
| 10218 | * If popup type equals "del_ch", it will proceed with deletion otherwise it will exit the function. | 9131 | * If popup type equals "del_ch", it will proceed with deletion otherwise it will exit the function. |
| @@ -10323,11 +9236,6 @@ export async function newAssistantChat({ temporary = false } = {}) { | |||
| 10323 | sendSystemMessage(system_message_types.ASSISTANT_NOTE); | 9236 | sendSystemMessage(system_message_types.ASSISTANT_NOTE); |
| 10324 | } | 9237 | } |
| 10325 | 9238 | ||
| 10326 | function doTogglePanels() { | ||
| 10327 | $('#option_settings').trigger('click'); | ||
| 10328 | return ''; | ||
| 10329 | } | ||
| 10330 | |||
| 10331 | /** | 9239 | /** |
| 10332 | * Event handler to open a navbar drawer when a drawer open button is clicked. | 9240 | * Event handler to open a navbar drawer when a drawer open button is clicked. |
| 10333 | * Handles click events on .drawer-opener elements. | 9241 | * Handles click events on .drawer-opener elements. |
| @@ -10363,7 +9271,7 @@ export async function doNavbarIconClick() { | |||
| 10363 | for (const el of $openDrawers) { | 9271 | for (const el of $openDrawers) { |
| 10364 | $(el).toggleClass('closedDrawer openDrawer'); | 9272 | $(el).toggleClass('closedDrawer openDrawer'); |
| 10365 | } | 9273 | } |
| 10366 | if ($openDrawers.length) { | 9274 | if ($openDrawers.length && animation_duration) { |
| 10367 | await delay(animation_duration); | 9275 | await delay(animation_duration); |
| 10368 | } | 9276 | } |
| 10369 | icon.toggleClass('openIcon closedIcon'); | 9277 | icon.toggleClass('openIcon closedIcon'); |
| @@ -10421,18 +9329,9 @@ function addDebugFunctions() { | |||
| 10421 | registerDebugFunction('generationTest', 'Send a generation request', 'Generates text using the currently selected API.', async () => { | 9329 | registerDebugFunction('generationTest', 'Send a generation request', 'Generates text using the currently selected API.', async () => { |
| 10422 | const text = prompt('Input text:', 'Hello'); | 9330 | const text = prompt('Input text:', 'Hello'); |
| 10423 | toastr.info('Working on it...'); | 9331 | toastr.info('Working on it...'); |
| 10424 | const message = await generateRaw(text, null, false, false); | 9332 | const message = await generateRaw({ prompt: text }); |
| 10425 | alert(message); | 9333 | alert(message); |
| 10426 | }); | 9334 | }); |
| 10427 | |||
| 10428 | registerDebugFunction('clearPrompts', 'Delete itemized prompts', 'Deletes all itemized prompts from the local storage.', async () => { | ||
| 10429 | await clearItemizedPrompts(); | ||
| 10430 | toastr.info('Itemized prompts deleted.'); | ||
| 10431 | if (getCurrentChatId()) { | ||
| 10432 | await reloadCurrentChat(); | ||
| 10433 | } | ||
| 10434 | }); | ||
| 10435 | |||
| 10436 | registerDebugFunction('toggleEventTracing', 'Toggle event tracing', 'Useful to see what triggered a certain event.', () => { | 9335 | registerDebugFunction('toggleEventTracing', 'Toggle event tracing', 'Useful to see what triggered a certain event.', () => { |
| 10437 | localStorage.setItem('eventTracing', localStorage.getItem('eventTracing') === 'true' ? 'false' : 'true'); | 9336 | localStorage.setItem('eventTracing', localStorage.getItem('eventTracing') === 'true' ? 'false' : 'true'); |
| 10438 | toastr.info('Event tracing is now ' + (localStorage.getItem('eventTracing') === 'true' ? 'enabled' : 'disabled')); | 9337 | toastr.info('Event tracing is now ' + (localStorage.getItem('eventTracing') === 'true' ? 'enabled' : 'disabled')); |
| @@ -10471,252 +9370,41 @@ API Settings: ${JSON.stringify(getSettingsContents[getSettingsContents.main_api | |||
| 10471 | }); | 9370 | }); |
| 10472 | } | 9371 | } |
| 10473 | 9372 | ||
| 9373 | function initCharacterSearch() { | ||
| 9374 | const debouncedCharacterSearch = debounce((searchQuery) => { | ||
| 9375 | entitiesFilter.setFilterData(FILTER_TYPES.SEARCH, searchQuery); | ||
| 9376 | }); | ||
| 10474 | 9377 | ||
| 10475 | // MARK: DOM Handlers Start | 9378 | const searchForm = $('#form_character_search_form'); |
| 10476 | jQuery(async function () { | 9379 | const searchInput = $('#character_search_bar'); |
| 10477 | async function doForceSave() { | 9380 | const searchButton = $('#rm_button_search'); |
| 10478 | await saveSettings(); | ||
| 10479 | await saveChatConditional(); | ||
| 10480 | toastr.success('Chat and settings saved.'); | ||
| 10481 | return ''; | ||
| 10482 | } | ||
| 10483 | 9381 | ||
| 10484 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | 9382 | const storageKey = 'characterSearchFormVisible'; |
| 10485 | name: 'dupe', | ||
| 10486 | callback: duplicateCharacter, | ||
| 10487 | helpString: 'Duplicates the currently selected character.', | ||
| 10488 | })); | ||
| 10489 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 10490 | name: 'api', | ||
| 10491 | callback: connectAPISlash, | ||
| 10492 | returns: 'the current API', | ||
| 10493 | namedArgumentList: [ | ||
| 10494 | SlashCommandNamedArgument.fromProps({ | ||
| 10495 | name: 'quiet', | ||
| 10496 | description: 'Suppress the toast message on connection', | ||
| 10497 | typeList: [ARGUMENT_TYPE.BOOLEAN], | ||
| 10498 | defaultValue: 'false', | ||
| 10499 | enumList: commonEnumProviders.boolean('trueFalse')(), | ||
| 10500 | }), | ||
| 10501 | ], | ||
| 10502 | unnamedArgumentList: [ | ||
| 10503 | SlashCommandArgument.fromProps({ | ||
| 10504 | description: 'API to connect to', | ||
| 10505 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 10506 | enumList: Object.entries(CONNECT_API_MAP).map(([api, { selected }]) => | ||
| 10507 | new SlashCommandEnumValue(api, selected, enumTypes.getBasedOnIndex(UNIQUE_APIS.findIndex(x => x === selected)), | ||
| 10508 | selected[0].toUpperCase() ?? enumIcons.default)), | ||
| 10509 | }), | ||
| 10510 | ], | ||
| 10511 | helpString: ` | ||
| 10512 | <div> | ||
| 10513 | Connect to an API. If no argument is provided, it will return the currently connected API. | ||
| 10514 | </div> | ||
| 10515 | <div> | ||
| 10516 | <strong>Available APIs:</strong> | ||
| 10517 | <pre><code>${Object.keys(CONNECT_API_MAP).join(', ')}</code></pre> | ||
| 10518 | </div> | ||
| 10519 | `, | ||
| 10520 | })); | ||
| 10521 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 10522 | name: 'impersonate', | ||
| 10523 | callback: doImpersonate, | ||
| 10524 | aliases: ['imp'], | ||
| 10525 | namedArgumentList: [ | ||
| 10526 | new SlashCommandNamedArgument( | ||
| 10527 | 'await', | ||
| 10528 | 'Whether to await for the triggered generation before continuing', | ||
| 10529 | [ARGUMENT_TYPE.BOOLEAN], | ||
| 10530 | false, | ||
| 10531 | false, | ||
| 10532 | 'false', | ||
| 10533 | ), | ||
| 10534 | ], | ||
| 10535 | unnamedArgumentList: [ | ||
| 10536 | new SlashCommandArgument( | ||
| 10537 | 'prompt', [ARGUMENT_TYPE.STRING], false, | ||
| 10538 | ), | ||
| 10539 | ], | ||
| 10540 | helpString: ` | ||
| 10541 | <div> | ||
| 10542 | Calls an impersonation response, with an optional additional prompt. | ||
| 10543 | </div> | ||
| 10544 | <div> | ||
| 10545 | If <code>await=true</code> named argument is passed, the command will wait for the impersonation to end before continuing. | ||
| 10546 | </div> | ||
| 10547 | <div> | ||
| 10548 | <strong>Example:</strong> | ||
| 10549 | <ul> | ||
| 10550 | <li> | ||
| 10551 | <pre><code class="language-stscript">/impersonate What is the meaning of life?</code></pre> | ||
| 10552 | </li> | ||
| 10553 | </ul> | ||
| 10554 | </div> | ||
| 10555 | `, | ||
| 10556 | })); | ||
| 10557 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 10558 | name: 'delchat', | ||
| 10559 | callback: doDeleteChat, | ||
| 10560 | helpString: 'Deletes the current chat.', | ||
| 10561 | })); | ||
| 10562 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 10563 | name: 'renamechat', | ||
| 10564 | callback: doRenameChat, | ||
| 10565 | unnamedArgumentList: [ | ||
| 10566 | new SlashCommandArgument( | ||
| 10567 | 'new chat name', [ARGUMENT_TYPE.STRING], true, | ||
| 10568 | ), | ||
| 10569 | ], | ||
| 10570 | helpString: 'Renames the current chat.', | ||
| 10571 | })); | ||
| 10572 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 10573 | name: 'getchatname', | ||
| 10574 | callback: doGetChatName, | ||
| 10575 | returns: 'chat file name', | ||
| 10576 | helpString: 'Returns the name of the current chat file into the pipe.', | ||
| 10577 | })); | ||
| 10578 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 10579 | name: 'closechat', | ||
| 10580 | callback: doCloseChat, | ||
| 10581 | helpString: 'Closes the current chat.', | ||
| 10582 | })); | ||
| 10583 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 10584 | name: 'tempchat', | ||
| 10585 | callback: () => { | ||
| 10586 | return new Promise((resolve, reject) => { | ||
| 10587 | const eventCallback = async (chatId) => { | ||
| 10588 | if (chatId) { | ||
| 10589 | return reject('Not in a temporary chat'); | ||
| 10590 | } | ||
| 10591 | await newAssistantChat({ temporary: true }); | ||
| 10592 | return resolve(''); | ||
| 10593 | }; | ||
| 10594 | eventSource.once(event_types.CHAT_CHANGED, eventCallback); | ||
| 10595 | doCloseChat(); | ||
| 10596 | setTimeout(() => { | ||
| 10597 | reject('Failed to open temporary chat'); | ||
| 10598 | eventSource.removeListener(event_types.CHAT_CHANGED, eventCallback); | ||
| 10599 | }, debounce_timeout.relaxed); | ||
| 10600 | }); | ||
| 10601 | }, | ||
| 10602 | helpString: 'Opens a temporary chat with Assistant.', | ||
| 10603 | })); | ||
| 10604 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 10605 | name: 'panels', | ||
| 10606 | callback: doTogglePanels, | ||
| 10607 | aliases: ['togglepanels'], | ||
| 10608 | helpString: 'Toggle UI panels on/off', | ||
| 10609 | })); | ||
| 10610 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 10611 | name: 'forcesave', | ||
| 10612 | callback: doForceSave, | ||
| 10613 | helpString: 'Forces a save of the current chat and settings', | ||
| 10614 | })); | ||
| 10615 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 10616 | name: 'instruct', | ||
| 10617 | callback: selectInstructCallback, | ||
| 10618 | returns: 'current template', | ||
| 10619 | namedArgumentList: [ | ||
| 10620 | SlashCommandNamedArgument.fromProps({ | ||
| 10621 | name: 'quiet', | ||
| 10622 | description: 'Suppress the toast message on template change', | ||
| 10623 | typeList: [ARGUMENT_TYPE.BOOLEAN], | ||
| 10624 | defaultValue: 'false', | ||
| 10625 | enumList: commonEnumProviders.boolean('trueFalse')(), | ||
| 10626 | }), | ||
| 10627 | SlashCommandNamedArgument.fromProps({ | ||
| 10628 | name: 'forceGet', | ||
| 10629 | description: 'Force getting a name even if instruct mode is disabled', | ||
| 10630 | typeList: [ARGUMENT_TYPE.BOOLEAN], | ||
| 10631 | defaultValue: 'false', | ||
| 10632 | enumList: commonEnumProviders.boolean('trueFalse')(), | ||
| 10633 | }), | ||
| 10634 | ], | ||
| 10635 | unnamedArgumentList: [ | ||
| 10636 | SlashCommandArgument.fromProps({ | ||
| 10637 | description: 'instruct template name', | ||
| 10638 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 10639 | enumProvider: () => instruct_presets.map(preset => new SlashCommandEnumValue(preset.name, null, enumTypes.enum, enumIcons.preset)), | ||
| 10640 | }), | ||
| 10641 | ], | ||
| 10642 | helpString: ` | ||
| 10643 | <div> | ||
| 10644 | Selects instruct mode template by name. Enables instruct mode if not already enabled. | ||
| 10645 | Gets the current instruct template if no name is provided and instruct mode is enabled or <code>forceGet=true</code> is passed. | ||
| 10646 | </div> | ||
| 10647 | <div> | ||
| 10648 | <strong>Example:</strong> | ||
| 10649 | <ul> | ||
| 10650 | <li> | ||
| 10651 | <pre><code class="language-stscript">/instruct creative</code></pre> | ||
| 10652 | </li> | ||
| 10653 | </ul> | ||
| 10654 | </div> | ||
| 10655 | `, | ||
| 10656 | })); | ||
| 10657 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 10658 | name: 'instruct-on', | ||
| 10659 | callback: enableInstructCallback, | ||
| 10660 | helpString: 'Enables instruct mode.', | ||
| 10661 | })); | ||
| 10662 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 10663 | name: 'instruct-off', | ||
| 10664 | callback: disableInstructCallback, | ||
| 10665 | helpString: 'Disables instruct mode', | ||
| 10666 | })); | ||
| 10667 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 10668 | name: 'instruct-state', | ||
| 10669 | aliases: ['instruct-toggle'], | ||
| 10670 | helpString: 'Gets the current instruct mode state. If an argument is provided, it will set the instruct mode state.', | ||
| 10671 | unnamedArgumentList: [ | ||
| 10672 | SlashCommandArgument.fromProps({ | ||
| 10673 | description: 'instruct mode state', | ||
| 10674 | typeList: [ARGUMENT_TYPE.BOOLEAN], | ||
| 10675 | enumList: commonEnumProviders.boolean('trueFalse')(), | ||
| 10676 | }), | ||
| 10677 | ], | ||
| 10678 | callback: async (_args, state) => { | ||
| 10679 | if (!state || typeof state !== 'string') { | ||
| 10680 | return String(power_user.instruct.enabled); | ||
| 10681 | } | ||
| 10682 | 9383 | ||
| 10683 | const newState = isTrueBoolean(state); | 9384 | searchInput.on('input', function () { |
| 10684 | newState ? enableInstructCallback() : disableInstructCallback(); | 9385 | const searchQuery = String($(this).val()); |
| 10685 | return String(power_user.instruct.enabled); | 9386 | debouncedCharacterSearch(searchQuery); |
| 10686 | }, | 9387 | }); |
| 10687 | })); | ||
| 10688 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 10689 | name: 'context', | ||
| 10690 | callback: selectContextCallback, | ||
| 10691 | returns: 'template name', | ||
| 10692 | namedArgumentList: [ | ||
| 10693 | SlashCommandNamedArgument.fromProps({ | ||
| 10694 | name: 'quiet', | ||
| 10695 | description: 'Suppress the toast message on template change', | ||
| 10696 | typeList: [ARGUMENT_TYPE.BOOLEAN], | ||
| 10697 | defaultValue: 'false', | ||
| 10698 | enumList: commonEnumProviders.boolean('trueFalse')(), | ||
| 10699 | }), | ||
| 10700 | ], | ||
| 10701 | unnamedArgumentList: [ | ||
| 10702 | SlashCommandArgument.fromProps({ | ||
| 10703 | description: 'context template name', | ||
| 10704 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 10705 | enumProvider: () => context_presets.map(preset => new SlashCommandEnumValue(preset.name, null, enumTypes.enum, enumIcons.preset)), | ||
| 10706 | }), | ||
| 10707 | ], | ||
| 10708 | helpString: 'Selects context template by name. Gets the current template if no name is provided', | ||
| 10709 | })); | ||
| 10710 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 10711 | name: 'chat-manager', | ||
| 10712 | callback: () => { | ||
| 10713 | $('#option_select_chat').trigger('click'); | ||
| 10714 | return ''; | ||
| 10715 | }, | ||
| 10716 | aliases: ['chat-history', 'manage-chats'], | ||
| 10717 | helpString: 'Opens the chat manager for the current character/group.', | ||
| 10718 | })); | ||
| 10719 | 9388 | ||
| 9389 | searchButton.on('click', function () { | ||
| 9390 | const newVisibility = !searchForm.is(':visible'); | ||
| 9391 | searchForm.toggle(newVisibility); | ||
| 9392 | searchButton.toggleClass('active', newVisibility); | ||
| 9393 | accountStorage.setItem(storageKey, String(newVisibility)); | ||
| 9394 | if (newVisibility) { | ||
| 9395 | searchInput.trigger('focus'); | ||
| 9396 | } | ||
| 9397 | }); | ||
| 9398 | |||
| 9399 | eventSource.on(event_types.APP_READY, () => { | ||
| 9400 | const isVisible = accountStorage.getItem(storageKey) === 'true'; | ||
| 9401 | searchForm.toggle(isVisible); | ||
| 9402 | searchButton.toggleClass('active', isVisible); | ||
| 9403 | }); | ||
| 9404 | } | ||
| 9405 | |||
| 9406 | // MARK: DOM Handlers Start | ||
| 9407 | jQuery(async function () { | ||
| 10720 | setTimeout(function () { | 9408 | setTimeout(function () { |
| 10721 | $('#groupControlsToggle').trigger('click'); | 9409 | $('#groupControlsToggle').trigger('click'); |
| 10722 | $('#groupCurrentMemberListToggle .inline-drawer-icon').trigger('click'); | 9410 | $('#groupCurrentMemberListToggle .inline-drawer-icon').trigger('click'); |
| @@ -10731,10 +9419,10 @@ jQuery(async function () { | |||
| 10731 | }); | 9419 | }); |
| 10732 | $('#send_but, #option_regenerate, #option_continue, #mes_continue, #mes_impersonate').on('click', () => { | 9420 | $('#send_but, #option_regenerate, #option_continue, #mes_continue, #mes_impersonate').on('click', () => { |
| 10733 | if (S_TAPreviouslyFocused) { | 9421 | if (S_TAPreviouslyFocused) { |
| 10734 | $('#send_textarea').focus(); | 9422 | $('#send_textarea').trigger('focus'); |
| 10735 | } | 9423 | } |
| 10736 | }); | 9424 | }); |
| 10737 | $(document).click(event => { | 9425 | $(document).on('click', event => { |
| 10738 | if ($(':focus').attr('id') !== 'send_textarea') { | 9426 | if ($(':focus').attr('id') !== 'send_textarea') { |
| 10739 | var validIDs = ['options_button', 'send_but', 'mes_impersonate', 'mes_continue', 'send_textarea', 'option_regenerate', 'option_continue']; | 9427 | var validIDs = ['options_button', 'send_but', 'mes_impersonate', 'mes_continue', 'send_textarea', 'option_regenerate', 'option_continue']; |
| 10740 | if (!validIDs.includes($(event.target).attr('id'))) { | 9428 | if (!validIDs.includes($(event.target).attr('id'))) { |
| @@ -10747,7 +9435,7 @@ jQuery(async function () { | |||
| 10747 | 9435 | ||
| 10748 | ///////////////// | 9436 | ///////////////// |
| 10749 | 9437 | ||
| 10750 | $('#swipes-checkbox').change(function () { | 9438 | $('#swipes-checkbox').on('change', function () { |
| 10751 | swipes = !!$('#swipes-checkbox').prop('checked'); | 9439 | swipes = !!$('#swipes-checkbox').prop('checked'); |
| 10752 | if (swipes) { | 9440 | if (swipes) { |
| 10753 | //console.log('toggle change calling showswipebtns'); | 9441 | //console.log('toggle change calling showswipebtns'); |
| @@ -10764,13 +9452,7 @@ jQuery(async function () { | |||
| 10764 | $(document).on('click', '.last_mes .swipe_right', swipe_right); | 9452 | $(document).on('click', '.last_mes .swipe_right', swipe_right); |
| 10765 | $(document).on('click', '.last_mes .swipe_left', swipe_left); | 9453 | $(document).on('click', '.last_mes .swipe_left', swipe_left); |
| 10766 | 9454 | ||
| 10767 | const debouncedCharacterSearch = debounce((searchQuery) => { | 9455 | initCharacterSearch(); |
| 10768 | entitiesFilter.setFilterData(FILTER_TYPES.SEARCH, searchQuery); | ||
| 10769 | }); | ||
| 10770 | $('#character_search_bar').on('input', function () { | ||
| 10771 | const searchQuery = String($(this).val()); | ||
| 10772 | debouncedCharacterSearch(searchQuery); | ||
| 10773 | }); | ||
| 10774 | 9456 | ||
| 10775 | $('#mes_impersonate').on('click', function () { | 9457 | $('#mes_impersonate').on('click', function () { |
| 10776 | $('#option_impersonate').trigger('click'); | 9458 | $('#option_impersonate').trigger('click'); |
| @@ -10890,11 +9572,51 @@ jQuery(async function () { | |||
| 10890 | } | 9572 | } |
| 10891 | }); | 9573 | }); |
| 10892 | 9574 | ||
| 10893 | $(document).on('click', '.PastChat_cross', function (e) { | 9575 | /** |
| 9576 | * Handles the deletion of a chat file, including group chats. | ||
| 9577 | * | ||
| 9578 | * @param {string} chatFile - The name of the chat file to delete. | ||
| 9579 | * @param {object} group - The group object if the chat is part of a group. | ||
| 9580 | * @param {boolean} [fromSlashCommand=false] - Whether the deletion was triggered from a slash command. | ||
| 9581 | * @returns {Promise<void>} | ||
| 9582 | */ | ||
| 9583 | async function handleDeleteChat(chatFile, group, fromSlashCommand = false) { | ||
| 9584 | // Close past chat popup. | ||
| 9585 | $('#select_chat_cross').trigger('click'); | ||
| 9586 | showLoader(); | ||
| 9587 | if (group) { | ||
| 9588 | await deleteGroupChat(group, chatFile); | ||
| 9589 | } else { | ||
| 9590 | await delChat(chatFile); | ||
| 9591 | } | ||
| 9592 | |||
| 9593 | if (fromSlashCommand) { // When called from `/delchat` command, don't re-open the history view. | ||
| 9594 | $('#options').hide(); // Hide option popup menu. | ||
| 9595 | hideLoader(); | ||
| 9596 | } else { // Open the history view again after 2 seconds (delay to avoid edge cases for deleting last chat). | ||
| 9597 | setTimeout(function () { | ||
| 9598 | $('#option_select_chat').trigger('click'); | ||
| 9599 | $('#options').hide(); // Hide option popup menu. | ||
| 9600 | hideLoader(); | ||
| 9601 | }, 2000); | ||
| 9602 | } | ||
| 9603 | } | ||
| 9604 | |||
| 9605 | $(document).on('click', '.PastChat_cross', async function (e, { fromSlashCommand = false } = {}) { | ||
| 10894 | e.stopPropagation(); | 9606 | e.stopPropagation(); |
| 10895 | chat_file_for_del = $(this).attr('file_name'); | 9607 | chat_file_for_del = $(this).attr('file_name'); |
| 10896 | console.debug('detected cross click for' + chat_file_for_del); | 9608 | console.debug('detected cross click for' + chat_file_for_del); |
| 10897 | callPopup('<h3>' + t`Delete the Chat File?` + '</h3>', 'del_chat'); | 9609 | |
| 9610 | // Skip confirmation if called from a slash command. | ||
| 9611 | if (fromSlashCommand) { | ||
| 9612 | await handleDeleteChat(chat_file_for_del, selected_group, true); | ||
| 9613 | return; | ||
| 9614 | } | ||
| 9615 | |||
| 9616 | const result = await callGenericPopup('<h3>' + t`Delete the Chat File?` + '</h3>', POPUP_TYPE.CONFIRM); | ||
| 9617 | if (result === POPUP_RESULT.AFFIRMATIVE) { | ||
| 9618 | await handleDeleteChat(chat_file_for_del, selected_group, false); | ||
| 9619 | } | ||
| 10898 | }); | 9620 | }); |
| 10899 | 9621 | ||
| 10900 | $('#advanced_div').on('click', function () { | 9622 | $('#advanced_div').on('click', function () { |
| @@ -10943,25 +9665,7 @@ jQuery(async function () { | |||
| 10943 | }, animation_duration); | 9665 | }, animation_duration); |
| 10944 | 9666 | ||
| 10945 | if (popup_type == 'del_chat') { | 9667 | if (popup_type == 'del_chat') { |
| 10946 | //close past chat popup | 9668 | await handleDeleteChat(chat_file_for_del, selected_group, fromSlashCommand); |
| 10947 | $('#select_chat_cross').trigger('click'); | ||
| 10948 | showLoader(); | ||
| 10949 | if (selected_group) { | ||
| 10950 | await deleteGroupChat(selected_group, chat_file_for_del); | ||
| 10951 | } else { | ||
| 10952 | await delChat(chat_file_for_del); | ||
| 10953 | } | ||
| 10954 | |||
| 10955 | if (fromSlashCommand) { // When called from `/delchat` command, don't re-open the history view. | ||
| 10956 | $('#options').hide(); // hide option popup menu | ||
| 10957 | hideLoader(); | ||
| 10958 | } else { // Open the history view again after 2 seconds (delay to avoid edge cases for deleting last chat). | ||
| 10959 | setTimeout(function () { | ||
| 10960 | $('#option_select_chat').trigger('click'); | ||
| 10961 | $('#options').hide(); // hide option popup menu | ||
| 10962 | hideLoader(); | ||
| 10963 | }, 2000); | ||
| 10964 | } | ||
| 10965 | } | 9669 | } |
| 10966 | 9670 | ||
| 10967 | if (dialogueResolve) { | 9671 | if (dialogueResolve) { |
| @@ -11133,76 +9837,6 @@ jQuery(async function () { | |||
| 11133 | } | 9837 | } |
| 11134 | }); | 9838 | }); |
| 11135 | 9839 | ||
| 11136 | /////////////////////////////////////////////////////////////////////////////////// | ||
| 11137 | |||
| 11138 | $('#api_button').on('click', function (e) { | ||
| 11139 | if ($('#api_url_text').val() != '') { | ||
| 11140 | let value = formatKoboldUrl(String($('#api_url_text').val()).trim()); | ||
| 11141 | |||
| 11142 | if (!value) { | ||
| 11143 | toastr.error('Please enter a valid URL.'); | ||
| 11144 | return; | ||
| 11145 | } | ||
| 11146 | |||
| 11147 | $('#api_url_text').val(value); | ||
| 11148 | api_server = value; | ||
| 11149 | startStatusLoading(); | ||
| 11150 | |||
| 11151 | main_api = 'kobold'; | ||
| 11152 | saveSettingsDebounced(); | ||
| 11153 | getStatusKobold(); | ||
| 11154 | } | ||
| 11155 | }); | ||
| 11156 | |||
| 11157 | $('#api_button_textgenerationwebui').on('click', async function (e) { | ||
| 11158 | const keys = [ | ||
| 11159 | { id: 'api_key_mancer', secret: SECRET_KEYS.MANCER }, | ||
| 11160 | { id: 'api_key_vllm', secret: SECRET_KEYS.VLLM }, | ||
| 11161 | { id: 'api_key_aphrodite', secret: SECRET_KEYS.APHRODITE }, | ||
| 11162 | { id: 'api_key_tabby', secret: SECRET_KEYS.TABBY }, | ||
| 11163 | { id: 'api_key_togetherai', secret: SECRET_KEYS.TOGETHERAI }, | ||
| 11164 | { id: 'api_key_ooba', secret: SECRET_KEYS.OOBA }, | ||
| 11165 | { id: 'api_key_infermaticai', secret: SECRET_KEYS.INFERMATICAI }, | ||
| 11166 | { id: 'api_key_dreamgen', secret: SECRET_KEYS.DREAMGEN }, | ||
| 11167 | { id: 'api_key_openrouter-tg', secret: SECRET_KEYS.OPENROUTER }, | ||
| 11168 | { id: 'api_key_koboldcpp', secret: SECRET_KEYS.KOBOLDCPP }, | ||
| 11169 | { id: 'api_key_llamacpp', secret: SECRET_KEYS.LLAMACPP }, | ||
| 11170 | { id: 'api_key_featherless', secret: SECRET_KEYS.FEATHERLESS }, | ||
| 11171 | { id: 'api_key_huggingface', secret: SECRET_KEYS.HUGGINGFACE }, | ||
| 11172 | { id: 'api_key_generic', secret: SECRET_KEYS.GENERIC }, | ||
| 11173 | ]; | ||
| 11174 | |||
| 11175 | for (const key of keys) { | ||
| 11176 | const keyValue = String($(`#${key.id}`).val()).trim(); | ||
| 11177 | if (keyValue.length) { | ||
| 11178 | await writeSecret(key.secret, keyValue); | ||
| 11179 | } | ||
| 11180 | } | ||
| 11181 | |||
| 11182 | validateTextGenUrl(); | ||
| 11183 | startStatusLoading(); | ||
| 11184 | main_api = 'textgenerationwebui'; | ||
| 11185 | saveSettingsDebounced(); | ||
| 11186 | getStatusTextgen(); | ||
| 11187 | }); | ||
| 11188 | |||
| 11189 | $('#api_button_novel').on('click', async function (e) { | ||
| 11190 | e.stopPropagation(); | ||
| 11191 | const api_key_novel = String($('#api_key_novel').val()).trim(); | ||
| 11192 | |||
| 11193 | if (api_key_novel.length) { | ||
| 11194 | await writeSecret(SECRET_KEYS.NOVEL, api_key_novel); | ||
| 11195 | } | ||
| 11196 | |||
| 11197 | if (!secret_state[SECRET_KEYS.NOVEL]) { | ||
| 11198 | console.log('No secret key saved for NovelAI'); | ||
| 11199 | return; | ||
| 11200 | } | ||
| 11201 | |||
| 11202 | startStatusLoading(); | ||
| 11203 | // Check near immediately rather than waiting for up to 90s | ||
| 11204 | await getStatusNovel(); | ||
| 11205 | }); | ||
| 11206 | 9840 | ||
| 11207 | const button = $('#options_button'); | 9841 | const button = $('#options_button'); |
| 11208 | const menu = $('#options'); | 9842 | const menu = $('#options'); |
| @@ -11285,7 +9919,8 @@ jQuery(async function () { | |||
| 11285 | await doNewChat({ deleteCurrentChat: deleteCurrentChat }); | 9919 | await doNewChat({ deleteCurrentChat: deleteCurrentChat }); |
| 11286 | } | 9920 | } |
| 11287 | if (!selected_group && this_chid === undefined && !is_send_press) { | 9921 | if (!selected_group && this_chid === undefined && !is_send_press) { |
| 11288 | await newAssistantChat({ temporary: true }); | 9922 | const alreadyInTempChat = this_chid === undefined && name2 === neutralCharacterName; |
| 9923 | await newAssistantChat({ temporary: alreadyInTempChat }); | ||
| 11289 | } | 9924 | } |
| 11290 | } | 9925 | } |
| 11291 | 9926 | ||
| @@ -11424,48 +10059,11 @@ jQuery(async function () { | |||
| 11424 | is_delete_mode = false; | 10059 | is_delete_mode = false; |
| 11425 | }); | 10060 | }); |
| 11426 | 10061 | ||
| 11427 | $('#settings_preset').change(function () { | 10062 | $('#main_api').on('change', async function () { |
| 11428 | if ($('#settings_preset').find(':selected').val() != 'gui') { | ||
| 11429 | preset_settings = $('#settings_preset').find(':selected').text(); | ||
| 11430 | const preset = koboldai_settings[koboldai_setting_names[preset_settings]]; | ||
| 11431 | loadKoboldSettings(preset); | ||
| 11432 | setGenerationParamsFromPreset(preset); | ||
| 11433 | $('#kobold_api-settings').find('input').prop('disabled', false); | ||
| 11434 | $('#kobold_api-settings').css('opacity', 1.0); | ||
| 11435 | $('#kobold_order') | ||
| 11436 | .css('opacity', 1) | ||
| 11437 | .sortable('enable'); | ||
| 11438 | } else { | ||
| 11439 | //$('.button').disableSelection(); | ||
| 11440 | preset_settings = 'gui'; | ||
| 11441 | |||
| 11442 | $('#kobold_api-settings').find('input').prop('disabled', true); | ||
| 11443 | $('#kobold_api-settings').css('opacity', 0.5); | ||
| 11444 | |||
| 11445 | $('#kobold_order') | ||
| 11446 | .css('opacity', 0.5) | ||
| 11447 | .sortable('disable'); | ||
| 11448 | } | ||
| 11449 | saveSettingsDebounced(); | ||
| 11450 | }); | ||
| 11451 | |||
| 11452 | $('#settings_preset_novel').change(function () { | ||
| 11453 | nai_settings.preset_settings_novel = $('#settings_preset_novel') | ||
| 11454 | .find(':selected') | ||
| 11455 | .text(); | ||
| 11456 | |||
| 11457 | const preset = novelai_settings[novelai_setting_names[nai_settings.preset_settings_novel]]; | ||
| 11458 | loadNovelPreset(preset); | ||
| 11459 | amount_gen = Number($('#amount_gen').val()); | ||
| 11460 | max_context = Number($('#max_context').val()); | ||
| 11461 | |||
| 11462 | saveSettingsDebounced(); | ||
| 11463 | }); | ||
| 11464 | |||
| 11465 | $('#main_api').change(function () { | ||
| 11466 | cancelStatusCheck('Canceled because main api changed'); | 10063 | cancelStatusCheck('Canceled because main api changed'); |
| 11467 | changeMainAPI(); | 10064 | changeMainAPI(); |
| 11468 | saveSettingsDebounced(); | 10065 | saveSettingsDebounced(); |
| 10066 | await eventSource.emit(event_types.MAIN_API_CHANGED, { apiId: main_api }); | ||
| 11469 | }); | 10067 | }); |
| 11470 | 10068 | ||
| 11471 | ////////////////// OPTIMIZED RANGE SLIDER LISTENERS//////////////// | 10069 | ////////////////// OPTIMIZED RANGE SLIDER LISTENERS//////////////// |
| @@ -11542,17 +10140,12 @@ jQuery(async function () { | |||
| 11542 | } | 10140 | } |
| 11543 | }); | 10141 | }); |
| 11544 | 10142 | ||
| 11545 | $(document).on('pointerup', '.mes_prompt', async function () { | ||
| 11546 | let mesIdForItemization = $(this).closest('.mes').attr('mesId'); | ||
| 11547 | console.log(`looking for mesID: ${mesIdForItemization}`); | ||
| 11548 | if (itemizedPrompts.length !== undefined && itemizedPrompts.length !== 0) { | ||
| 11549 | await promptItemize(itemizedPrompts, mesIdForItemization); | ||
| 11550 | } | ||
| 11551 | }); | ||
| 11552 | |||
| 11553 | //******************** | 10143 | //******************** |
| 11554 | //***Message Editor*** | 10144 | //***Message Editor*** |
| 11555 | $(document).on('click', '.mes_edit', async function () { | 10145 | $(document).on('click', '.mes_edit', async function () { |
| 10146 | if (is_delete_mode) { | ||
| 10147 | return; | ||
| 10148 | } | ||
| 11556 | if (this_chid !== undefined || selected_group || name2 === neutralCharacterName) { | 10149 | if (this_chid !== undefined || selected_group || name2 === neutralCharacterName) { |
| 11557 | // Previously system messages we're allowed to be edited | 10150 | // Previously system messages we're allowed to be edited |
| 11558 | /*const message = $(this).closest(".mes"); | 10151 | /*const message = $(this).closest(".mes"); |
| @@ -11896,6 +10489,9 @@ jQuery(async function () { | |||
| 11896 | await importCharactersTags(avatarFileNames); | 10489 | await importCharactersTags(avatarFileNames); |
| 11897 | selectImportedChar(avatarFileNames[avatarFileNames.length - 1]); | 10490 | selectImportedChar(avatarFileNames[avatarFileNames.length - 1]); |
| 11898 | } | 10491 | } |
| 10492 | |||
| 10493 | // Clear the file input value to allow re-uploading the same file | ||
| 10494 | e.target.value = ''; | ||
| 11899 | }); | 10495 | }); |
| 11900 | 10496 | ||
| 11901 | $('#export_button').on('click', function () { | 10497 | $('#export_button').on('click', function () { |
| @@ -11990,7 +10586,7 @@ jQuery(async function () { | |||
| 11990 | select_rm_characters(); | 10586 | select_rm_characters(); |
| 11991 | }); | 10587 | }); |
| 11992 | 10588 | ||
| 11993 | $('#dupe_button').click(async function () { | 10589 | $('#dupe_button').on('click', async function () { |
| 11994 | await duplicateCharacter(); | 10590 | await duplicateCharacter(); |
| 11995 | }); | 10591 | }); |
| 11996 | 10592 | ||
| @@ -12126,9 +10722,17 @@ jQuery(async function () { | |||
| 12126 | $('body').append(newElement); | 10722 | $('body').append(newElement); |
| 12127 | newElement.fadeIn(animation_duration); | 10723 | newElement.fadeIn(animation_duration); |
| 12128 | const zoomedAvatarImgElement = $(`.zoomed_avatar[forChar="${charname}"] img`); | 10724 | const zoomedAvatarImgElement = $(`.zoomed_avatar[forChar="${charname}"] img`); |
| 12129 | if (messageElement.attr('is_user') == 'true' || (messageElement.attr('is_system') == 'true' && !isValidCharacter)) { //handle user and system avatars | 10725 | if (messageElement.attr('is_user') == 'true' || (messageElement.attr('is_system') == 'true' && !isValidCharacter)) { |
| 12130 | zoomedAvatarImgElement.attr('src', thumbURL); | 10726 | //handle user and system avatars |
| 12131 | zoomedAvatarImgElement.attr('data-izoomify-url', thumbURL); | 10727 | const isValidPersona = decodeURIComponent(targetAvatarImg) in power_user.personas; |
| 10728 | if (isValidPersona) { | ||
| 10729 | const personaSrc = getUserAvatar(targetAvatarImg); | ||
| 10730 | zoomedAvatarImgElement.attr('src', personaSrc); | ||
| 10731 | zoomedAvatarImgElement.attr('data-izoomify-url', personaSrc); | ||
| 10732 | } else { | ||
| 10733 | zoomedAvatarImgElement.attr('src', thumbURL); | ||
| 10734 | zoomedAvatarImgElement.attr('data-izoomify-url', thumbURL); | ||
| 10735 | } | ||
| 12132 | } else if (messageElement.attr('is_user') == 'false') { //handle char avatars | 10736 | } else if (messageElement.attr('is_user') == 'false') { //handle char avatars |
| 12133 | zoomedAvatarImgElement.attr('src', avatarSrc); | 10737 | zoomedAvatarImgElement.attr('src', avatarSrc); |
| 12134 | zoomedAvatarImgElement.attr('data-izoomify-url', avatarSrc); | 10738 | zoomedAvatarImgElement.attr('data-izoomify-url', avatarSrc); |
| @@ -12182,18 +10786,18 @@ jQuery(async function () { | |||
| 12182 | } | 10786 | } |
| 12183 | }); | 10787 | }); |
| 12184 | 10788 | ||
| 12185 | $(document).keyup(function (e) { | 10789 | $(document).on('keyup', function (e) { |
| 12186 | if (e.key === 'Escape') { | 10790 | if (e.key === 'Escape') { |
| 12187 | const isEditVisible = $('#curEditTextarea').is(':visible') || $('.reasoning_edit_textarea').length > 0; | 10791 | const isEditVisible = $('#curEditTextarea').is(':visible') || $('.reasoning_edit_textarea').length > 0; |
| 12188 | if (isEditVisible && power_user.auto_save_msg_edits === false) { | 10792 | if (isEditVisible && power_user.auto_save_msg_edits === false) { |
| 12189 | closeMessageEditor('all'); | 10793 | closeMessageEditor('all'); |
| 12190 | $('#send_textarea').focus(); | 10794 | $('#send_textarea').trigger('focus'); |
| 12191 | return; | 10795 | return; |
| 12192 | } | 10796 | } |
| 12193 | if (isEditVisible && power_user.auto_save_msg_edits === true) { | 10797 | if (isEditVisible && power_user.auto_save_msg_edits === true) { |
| 12194 | $(`#chat .mes[mesid="${this_edit_mes_id}"] .mes_edit_done`).trigger('click'); | 10798 | $(`#chat .mes[mesid="${this_edit_mes_id}"] .mes_edit_done`).trigger('click'); |
| 12195 | closeMessageEditor('reasoning'); | 10799 | closeMessageEditor('reasoning'); |
| 12196 | $('#send_textarea').focus(); | 10800 | $('#send_textarea').trigger('focus'); |
| 12197 | return; | 10801 | return; |
| 12198 | } | 10802 | } |
| 12199 | if (!this_edit_mes_id && $('#mes_stop').is(':visible')) { | 10803 | if (!this_edit_mes_id && $('#mes_stop').is(':visible')) { |
| @@ -12210,13 +10814,13 @@ jQuery(async function () { | |||
| 12210 | const target = $(targetElement.selectedOptions).attr('id'); | 10814 | const target = $(targetElement.selectedOptions).attr('id'); |
| 12211 | switch (target) { | 10815 | switch (target) { |
| 12212 | case 'set_character_world': | 10816 | case 'set_character_world': |
| 12213 | openCharacterWorldPopup(); | 10817 | await openCharacterWorldPopup(); |
| 12214 | break; | 10818 | break; |
| 12215 | case 'set_chat_scenario': | 10819 | case 'set_chat_scenario': |
| 12216 | await setScenarioOverride(); | 10820 | await setScenarioOverride(); |
| 12217 | break; | 10821 | break; |
| 12218 | case 'renameCharButton': | 10822 | case 'renameCharButton': |
| 12219 | renameCharacter(); | 10823 | await renameCharacter(); |
| 12220 | break; | 10824 | break; |
| 12221 | case 'import_character_info': | 10825 | case 'import_character_info': |
| 12222 | await importEmbeddedWorldInfo(); | 10826 | await importEmbeddedWorldInfo(); |
| @@ -12294,6 +10898,9 @@ jQuery(async function () { | |||
| 12294 | }); | 10898 | }); |
| 12295 | 10899 | ||
| 12296 | $(document).on('change', '.range-block-counter input, .neo-range-input', function (e) { | 10900 | $(document).on('change', '.range-block-counter input, .neo-range-input', function (e) { |
| 10901 | if (!(e.target instanceof HTMLElement)) { | ||
| 10902 | return; | ||
| 10903 | } | ||
| 12297 | e.target.focus(); | 10904 | e.target.focus(); |
| 12298 | e.target.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true })); | 10905 | e.target.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true })); |
| 12299 | }); | 10906 | }); |
| @@ -12314,7 +10921,6 @@ jQuery(async function () { | |||
| 12314 | } else { | 10921 | } else { |
| 12315 | //if value not ok, warn and reset to last known valid value | 10922 | //if value not ok, warn and reset to last known valid value |
| 12316 | toastr.warning(`Invalid value. Must be between ${$(this).attr('min')} and ${$(this).attr('max')}`); | 10923 | toastr.warning(`Invalid value. Must be between ${$(this).attr('min')} and ${$(this).attr('max')}`); |
| 12317 | console.log(valueBeforeManualInput); | ||
| 12318 | //newSlider.val(valueBeforeManualInput) | 10924 | //newSlider.val(valueBeforeManualInput) |
| 12319 | $(this).val(valueBeforeManualInput); | 10925 | $(this).val(valueBeforeManualInput); |
| 12320 | } | 10926 | } |
| @@ -12324,7 +10930,6 @@ jQuery(async function () { | |||
| 12324 | 10930 | ||
| 12325 | $(document).on('keyup', '.range-block-counter input, .neo-range-input', function () { | 10931 | $(document).on('keyup', '.range-block-counter input, .neo-range-input', function () { |
| 12326 | valueBeforeManualInput = $(this).val(); | 10932 | valueBeforeManualInput = $(this).val(); |
| 12327 | console.log(valueBeforeManualInput); | ||
| 12328 | isManualInput = true; | 10933 | isManualInput = true; |
| 12329 | }); | 10934 | }); |
| 12330 | 10935 | ||
| @@ -12342,7 +10947,6 @@ jQuery(async function () { | |||
| 12342 | } else { | 10947 | } else { |
| 12343 | //if value not ok, warn and reset to last known valid value | 10948 | //if value not ok, warn and reset to last known valid value |
| 12344 | toastr.warning(`Invalid value. Must be between ${$(this).attr('min')} and ${$(this).attr('max')}`); | 10949 | toastr.warning(`Invalid value. Must be between ${$(this).attr('min')} and ${$(this).attr('max')}`); |
| 12345 | console.log(valueBeforeManualInput); | ||
| 12346 | $(this).val(valueBeforeManualInput); | 10950 | $(this).val(valueBeforeManualInput); |
| 12347 | } | 10951 | } |
| 12348 | } | 10952 | } |
| @@ -12437,13 +11041,6 @@ jQuery(async function () { | |||
| 12437 | // Added here to prevent execution before script.js is loaded and get rid of quirky timeouts | 11041 | // Added here to prevent execution before script.js is loaded and get rid of quirky timeouts |
| 12438 | await firstLoadInit(); | 11042 | await firstLoadInit(); |
| 12439 | 11043 | ||
| 12440 | eventSource.on(event_types.CHAT_DELETED, async (name) => { | ||
| 12441 | await deleteItemizedPrompts(name); | ||
| 12442 | }); | ||
| 12443 | eventSource.on(event_types.GROUP_CHAT_DELETED, async (name) => { | ||
| 12444 | await deleteItemizedPrompts(name); | ||
| 12445 | }); | ||
| 12446 | |||
| 12447 | window.addEventListener('beforeunload', (e) => { | 11044 | window.addEventListener('beforeunload', (e) => { |
| 12448 | if (isChatSaving) { | 11045 | if (isChatSaving) { |
| 12449 | e.preventDefault(); | 11046 | e.preventDefault(); |
| @@ -6,7 +6,7 @@ import { event_types, eventSource, is_send_press, main_api, substituteParams } f | |||
| 6 | import { is_group_generating } from './group-chats.js'; | 6 | import { is_group_generating } from './group-chats.js'; |
| 7 | import { Message, MessageCollection, TokenHandler } from './openai.js'; | 7 | import { Message, MessageCollection, TokenHandler } from './openai.js'; |
| 8 | import { power_user } from './power-user.js'; | 8 | import { power_user } from './power-user.js'; |
| 9 | import { debounce, waitUntilCondition, escapeHtml } from './utils.js'; | 9 | import { debounce, waitUntilCondition, escapeHtml, uuidv4 } from './utils.js'; |
| 10 | import { debounce_timeout } from './constants.js'; | 10 | import { debounce_timeout } from './constants.js'; |
| 11 | import { renderTemplateAsync } from './templates.js'; | 11 | import { renderTemplateAsync } from './templates.js'; |
| 12 | import { Popup } from './popup.js'; | 12 | import { Popup } from './popup.js'; |
| @@ -29,6 +29,7 @@ function debouncePromise(func, delay) { | |||
| 29 | } | 29 | } |
| 30 | 30 | ||
| 31 | const DEFAULT_DEPTH = 4; | 31 | const DEFAULT_DEPTH = 4; |
| 32 | const DEFAULT_ORDER = 100; | ||
| 32 | 33 | ||
| 33 | /** | 34 | /** |
| 34 | * @enum {number} | 35 | * @enum {number} |
| @@ -77,25 +78,108 @@ const registerPromptManagerMigration = () => { | |||
| 77 | * Represents a prompt. | 78 | * Represents a prompt. |
| 78 | */ | 79 | */ |
| 79 | class Prompt { | 80 | class Prompt { |
| 80 | identifier; role; content; name; system_prompt; position; injection_position; injection_depth; injection_order; forbid_overrides; extension; | 81 | /** |
| 82 | * Indicates if the prompt is enabled. | ||
| 83 | * @type {boolean} | ||
| 84 | */ | ||
| 85 | enabled; | ||
| 86 | |||
| 87 | /** | ||
| 88 | * Unique identifier for the prompt. | ||
| 89 | * @type {string} | ||
| 90 | */ | ||
| 91 | identifier; | ||
| 92 | |||
| 93 | /** | ||
| 94 | * Role of the prompt, e.g., 'system', 'user', etc. | ||
| 95 | * @type {string} | ||
| 96 | */ | ||
| 97 | role; | ||
| 98 | |||
| 99 | /** | ||
| 100 | * Content of the prompt. | ||
| 101 | * @type {string} | ||
| 102 | */ | ||
| 103 | content; | ||
| 104 | |||
| 105 | /** | ||
| 106 | * Display name of the prompt. | ||
| 107 | * @type {string} | ||
| 108 | */ | ||
| 109 | name; | ||
| 110 | |||
| 111 | /** | ||
| 112 | * Indicates if the prompt is a system prompt. | ||
| 113 | * @type {boolean} | ||
| 114 | */ | ||
| 115 | system_prompt; | ||
| 116 | |||
| 117 | /** | ||
| 118 | * Position of the prompt in the prompt list. | ||
| 119 | * @type {string|number} | ||
| 120 | */ | ||
| 121 | position; | ||
| 122 | |||
| 123 | /** | ||
| 124 | * Inject position of the prompt (relative = 0 or in-chat = 1) | ||
| 125 | * @type {number} | ||
| 126 | */ | ||
| 127 | injection_position; | ||
| 128 | |||
| 129 | /** | ||
| 130 | * Depth of the prompt in the chat. | ||
| 131 | * @type {number} | ||
| 132 | */ | ||
| 133 | injection_depth; | ||
| 134 | |||
| 135 | /** | ||
| 136 | * Order of the prompt in the chat. | ||
| 137 | * @type {number} | ||
| 138 | */ | ||
| 139 | injection_order; | ||
| 140 | |||
| 141 | /** | ||
| 142 | * Indicates if the prompt should not be overridden. | ||
| 143 | * @type {boolean} | ||
| 144 | */ | ||
| 145 | forbid_overrides; | ||
| 146 | |||
| 147 | /** | ||
| 148 | * Prompt is added by an extension. | ||
| 149 | * @type {boolean} | ||
| 150 | */ | ||
| 151 | extension; | ||
| 152 | |||
| 153 | /** | ||
| 154 | * A list of generation type triggers for the prompt injection. | ||
| 155 | * @type {string[]} | ||
| 156 | */ | ||
| 157 | injection_trigger; | ||
| 158 | |||
| 159 | /** | ||
| 160 | * Indicates if the prompt is a marker prompt. | ||
| 161 | * @type {boolean} | ||
| 162 | */ | ||
| 163 | marker; | ||
| 81 | 164 | ||
| 82 | /** | 165 | /** |
| 83 | * Create a new Prompt instance. | 166 | * Create a new Prompt instance. |
| 84 | * | 167 | * |
| 85 | * @param {Object} param0 - Object containing the properties of the prompt. | 168 | * @param {Object} [param0] - Object containing the properties of the prompt. |
| 86 | * @param {string} param0.identifier - The unique identifier of the prompt. | 169 | * @param {string} [param0.identifier] - The unique identifier of the prompt. |
| 87 | * @param {string} param0.role - The role associated with the prompt. | 170 | * @param {string} [param0.role] - The role associated with the prompt. |
| 88 | * @param {string} param0.content - The content of the prompt. | 171 | * @param {string} [param0.content] - The content of the prompt. |
| 89 | * @param {string} [param0.name] - The name of the prompt. | 172 | * @param {string} [param0.name] - The name of the prompt. |
| 90 | * @param {boolean} [param0.system_prompt] - Indicates if the prompt is a system prompt. | 173 | * @param {boolean} [param0.system_prompt] - Indicates if the prompt is a system prompt. |
| 91 | * @param {string} [param0.position] - The position of the prompt in the prompt list. | 174 | * @param {string|number} [param0.position] - The position of the prompt in the prompt list. |
| 92 | * @param {number} [param0.injection_position] - The insert position of the prompt. | 175 | * @param {number} [param0.injection_position] - The insert position of the prompt. |
| 93 | * @param {number} [param0.injection_depth] - The depth of the prompt in the chat. | 176 | * @param {number} [param0.injection_depth] - The depth of the prompt in the chat. |
| 94 | * @param {number} [param0.injection_order] - The order of the prompt in the chat. | 177 | * @param {number} [param0.injection_order] - The order of the prompt in the chat. |
| 178 | * @param {string[]} [param0.injection_trigger] - The generation type trigger for the prompt injection. | ||
| 95 | * @param {boolean} [param0.forbid_overrides] - Indicates if the prompt should not be overridden. | 179 | * @param {boolean} [param0.forbid_overrides] - Indicates if the prompt should not be overridden. |
| 96 | * @param {boolean} [param0.extension] - Prompt is added by an extension. | 180 | * @param {boolean} [param0.extension] - Prompt is added by an extension. |
| 97 | */ | 181 | */ |
| 98 | constructor({ identifier, role, content, name, system_prompt, position, injection_depth, injection_position, forbid_overrides, extension, injection_order } = {}) { | 182 | constructor({ identifier, role, content, name, system_prompt, position, injection_depth, injection_position, forbid_overrides, extension, injection_order, injection_trigger } = {}) { |
| 99 | this.identifier = identifier; | 183 | this.identifier = identifier; |
| 100 | this.role = role; | 184 | this.role = role; |
| 101 | this.content = content; | 185 | this.content = content; |
| @@ -106,7 +190,8 @@ class Prompt { | |||
| 106 | this.injection_position = injection_position; | 190 | this.injection_position = injection_position; |
| 107 | this.forbid_overrides = forbid_overrides; | 191 | this.forbid_overrides = forbid_overrides; |
| 108 | this.extension = extension ?? false; | 192 | this.extension = extension ?? false; |
| 109 | this.injection_order = injection_order ?? 100; | 193 | this.injection_order = injection_order ?? DEFAULT_ORDER; |
| 194 | this.injection_trigger = injection_trigger ?? []; | ||
| 110 | } | 195 | } |
| 111 | } | 196 | } |
| 112 | 197 | ||
| @@ -114,7 +199,16 @@ class Prompt { | |||
| 114 | * Representing a collection of prompts. | 199 | * Representing a collection of prompts. |
| 115 | */ | 200 | */ |
| 116 | export class PromptCollection { | 201 | export class PromptCollection { |
| 202 | /** | ||
| 203 | * List of Prompts in the collection. | ||
| 204 | * @type {Prompt[]} | ||
| 205 | */ | ||
| 117 | collection = []; | 206 | collection = []; |
| 207 | |||
| 208 | /** | ||
| 209 | * List of identifiers of prompts that have been overridden. | ||
| 210 | * @type {string[]} | ||
| 211 | */ | ||
| 118 | overriddenPrompts = []; | 212 | overriddenPrompts = []; |
| 119 | 213 | ||
| 120 | /** | 214 | /** |
| @@ -129,7 +223,7 @@ export class PromptCollection { | |||
| 129 | /** | 223 | /** |
| 130 | * Checks if the provided instances are of the Prompt class. | 224 | * Checks if the provided instances are of the Prompt class. |
| 131 | * | 225 | * |
| 132 | * @param {...any} prompts - Instances to check. | 226 | * @param {...Prompt} prompts - Instances to check. |
| 133 | * @throws Will throw an error if one or more instances are not of the Prompt class. | 227 | * @throws Will throw an error if one or more instances are not of the Prompt class. |
| 134 | */ | 228 | */ |
| 135 | checkPromptInstance(...prompts) { | 229 | checkPromptInstance(...prompts) { |
| @@ -191,6 +285,12 @@ export class PromptCollection { | |||
| 191 | return this.index(identifier) !== -1; | 285 | return this.index(identifier) !== -1; |
| 192 | } | 286 | } |
| 193 | 287 | ||
| 288 | /** | ||
| 289 | * Overrides a prompt at a specific position in the collection. | ||
| 290 | * | ||
| 291 | * @param {Prompt} prompt - The Prompt instance to override. | ||
| 292 | * @param {number} position - The position in the collection to override the Prompt instance. | ||
| 293 | */ | ||
| 194 | override(prompt, position) { | 294 | override(prompt, position) { |
| 195 | this.set(prompt, position); | 295 | this.set(prompt, position); |
| 196 | this.overriddenPrompts.push(prompt.identifier); | 296 | this.overriddenPrompts.push(prompt.identifier); |
| @@ -274,7 +374,7 @@ class PromptManager { | |||
| 274 | this.tryGenerate = async () => { }; | 374 | this.tryGenerate = async () => { }; |
| 275 | 375 | ||
| 276 | /** Called to persist the configuration, must return a promise */ | 376 | /** Called to persist the configuration, must return a promise */ |
| 277 | this.saveServiceSettings = () => { }; | 377 | this.saveServiceSettings = () => { return Promise.resolve(); }; |
| 278 | 378 | ||
| 279 | /** Toggle prompt button click */ | 379 | /** Toggle prompt button click */ |
| 280 | this.handleToggle = () => { }; | 380 | this.handleToggle = () => { }; |
| @@ -444,32 +544,51 @@ class PromptManager { | |||
| 444 | break; | 544 | break; |
| 445 | } | 545 | } |
| 446 | 546 | ||
| 447 | document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_name').value = prompt.name; | 547 | const nameField = /** @type {HTMLInputElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_name')); |
| 448 | document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_role').value = 'system'; | 548 | const roleField = /** @type {HTMLSelectElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_role')); |
| 449 | document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt').value = prompt.content ?? ''; | 549 | const promptField = /** @type {HTMLTextAreaElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt')); |
| 450 | document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position').value = prompt.injection_position ?? 0; | 550 | const injectionPositionField = /** @type {HTMLSelectElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position')); |
| 451 | document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_depth').value = prompt.injection_depth ?? DEFAULT_DEPTH; | 551 | const injectionDepthField = /** @type {HTMLInputElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_depth')); |
| 452 | document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_order').value = prompt.injection_order ?? 100; | 552 | const injectionOrderField = /** @type {HTMLInputElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_order')); |
| 453 | document.getElementById(this.configuration.prefix + 'prompt_manager_depth_block').style.visibility = prompt.injection_position === INJECTION_POSITION.ABSOLUTE ? 'visible' : 'hidden'; | 553 | const injectionTriggerField = /** @type {HTMLSelectElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_trigger')); |
| 454 | document.getElementById(this.configuration.prefix + 'prompt_manager_order_block').style.visibility = prompt.injection_position === INJECTION_POSITION.ABSOLUTE ? 'visible' : 'hidden'; | 554 | const depthBlock = /** @type {HTMLDivElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_depth_block')); |
| 455 | document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_forbid_overrides').checked = prompt.forbid_overrides ?? false; | 555 | const orderBlock = /** @type {HTMLDivElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_order_block')); |
| 456 | document.getElementById(this.configuration.prefix + 'prompt_manager_forbid_overrides_block').style.visibility = this.overridablePrompts.includes(prompt.identifier) ? 'visible' : 'hidden'; | 556 | const forbidOverridesField = /** @type {HTMLInputElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_forbid_overrides')); |
| 457 | document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt').disabled = prompt.marker ?? false; | 557 | const forbidOverridesBlock = /** @type {HTMLDivElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_forbid_overrides_block')); |
| 458 | document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_source_block').style.display = isPulledPrompt ? '' : 'none'; | 558 | const entrySourceBlock = /** @type {HTMLDivElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_source_block')); |
| 559 | const entrySource = /** @type {HTMLSpanElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_source')); | ||
| 560 | |||
| 561 | nameField.value = prompt.name; | ||
| 562 | roleField.value = 'system'; | ||
| 563 | promptField.value = prompt.content ?? ''; | ||
| 564 | injectionPositionField.value = (prompt.injection_position ?? 0).toString(); | ||
| 565 | injectionDepthField.value = (prompt.injection_depth ?? DEFAULT_DEPTH).toString(); | ||
| 566 | injectionOrderField.value = (prompt.injection_order ?? DEFAULT_ORDER).toString(); | ||
| 567 | Array.from(injectionTriggerField.options).forEach(option => { | ||
| 568 | option.selected = false; | ||
| 569 | }); | ||
| 570 | injectionTriggerField.dispatchEvent(new Event('change', { bubbles: true })); | ||
| 571 | depthBlock.style.visibility = prompt.injection_position === INJECTION_POSITION.ABSOLUTE ? 'visible' : 'hidden'; | ||
| 572 | orderBlock.style.visibility = prompt.injection_position === INJECTION_POSITION.ABSOLUTE ? 'visible' : 'hidden'; | ||
| 573 | forbidOverridesField.checked = prompt.forbid_overrides ?? false; | ||
| 574 | forbidOverridesBlock.style.visibility = this.overridablePrompts.includes(prompt.identifier) ? 'visible' : 'hidden'; | ||
| 575 | promptField.disabled = prompt.marker ?? false; | ||
| 576 | entrySourceBlock.style.display = isPulledPrompt ? '' : 'none'; | ||
| 459 | 577 | ||
| 460 | if (isPulledPrompt) { | 578 | if (isPulledPrompt) { |
| 461 | const sourceName = this.promptSources[promptId]; | 579 | const sourceName = this.promptSources[promptId]; |
| 462 | document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_source').textContent = sourceName; | 580 | entrySource.textContent = sourceName; |
| 463 | } | 581 | } |
| 464 | 582 | ||
| 465 | if (!this.systemPrompts.includes(promptId)) { | 583 | if (!this.systemPrompts.includes(promptId)) { |
| 466 | document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position').removeAttribute('disabled'); | 584 | injectionPositionField.removeAttribute('disabled'); |
| 467 | } | 585 | } |
| 468 | }; | 586 | }; |
| 469 | 587 | ||
| 470 | // Append prompt to selected character | 588 | // Append prompt to selected character |
| 471 | this.handleAppendPrompt = (event) => { | 589 | this.handleAppendPrompt = (event) => { |
| 472 | const promptID = document.getElementById(this.configuration.prefix + 'prompt_manager_footer_append_prompt').value; | 590 | const appendPromptFooter = /** @type {HTMLSelectElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_footer_append_prompt')); |
| 591 | const promptID = appendPromptFooter.value; | ||
| 473 | const prompt = this.getPromptById(promptID); | 592 | const prompt = this.getPromptById(promptID); |
| 474 | 593 | ||
| 475 | if (prompt) { | 594 | if (prompt) { |
| @@ -483,7 +602,8 @@ class PromptManager { | |||
| 483 | this.handleDeletePrompt = async (event) => { | 602 | this.handleDeletePrompt = async (event) => { |
| 484 | Popup.show.confirm(t`Are you sure you want to delete this prompt?`, null).then((userChoice) => { | 603 | Popup.show.confirm(t`Are you sure you want to delete this prompt?`, null).then((userChoice) => { |
| 485 | if (!userChoice) return; | 604 | if (!userChoice) return; |
| 486 | const promptID = document.getElementById(this.configuration.prefix + 'prompt_manager_footer_append_prompt').value; | 605 | const appendPromptFooter = /** @type {HTMLSelectElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_footer_append_prompt')); |
| 606 | const promptID = appendPromptFooter.value; | ||
| 487 | const prompt = this.getPromptById(promptID); | 607 | const prompt = this.getPromptById(promptID); |
| 488 | 608 | ||
| 489 | if (prompt && true === this.isPromptDeletionAllowed(prompt)) { | 609 | if (prompt && true === this.isPromptDeletionAllowed(prompt)) { |
| @@ -566,6 +686,7 @@ class PromptManager { | |||
| 566 | fileOpener.accept = '.json'; | 686 | fileOpener.accept = '.json'; |
| 567 | 687 | ||
| 568 | fileOpener.addEventListener('change', (event) => { | 688 | fileOpener.addEventListener('change', (event) => { |
| 689 | if (!(event.target instanceof HTMLInputElement)) return; | ||
| 569 | const file = event.target.files[0]; | 690 | const file = event.target.files[0]; |
| 570 | if (!file) return; | 691 | if (!file) return; |
| 571 | 692 | ||
| @@ -575,7 +696,7 @@ class PromptManager { | |||
| 575 | const fileContent = event.target.result; | 696 | const fileContent = event.target.result; |
| 576 | 697 | ||
| 577 | try { | 698 | try { |
| 578 | const data = JSON.parse(fileContent); | 699 | const data = JSON.parse(fileContent.toString()); |
| 579 | this.import(data); | 700 | this.import(data); |
| 580 | } catch (err) { | 701 | } catch (err) { |
| 581 | toastr.error(t`An error occurred while importing prompts. More info available in console.`); | 702 | toastr.error(t`An error occurred while importing prompts. More info available in console.`); |
| @@ -615,7 +736,7 @@ class PromptManager { | |||
| 615 | 736 | ||
| 616 | // Update edit form if present | 737 | // Update edit form if present |
| 617 | // @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent | 738 | // @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent |
| 618 | const popupEditFormPrompt = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt'); | 739 | const popupEditFormPrompt = /** @type {HTMLTextAreaElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt')); |
| 619 | if (popupEditFormPrompt.offsetParent) { | 740 | if (popupEditFormPrompt.offsetParent) { |
| 620 | popupEditFormPrompt.value = prompt.content; | 741 | popupEditFormPrompt.value = prompt.content; |
| 621 | } | 742 | } |
| @@ -673,6 +794,7 @@ class PromptManager { | |||
| 673 | 794 | ||
| 674 | // Trigger re-render when token settings are changed | 795 | // Trigger re-render when token settings are changed |
| 675 | document.getElementById('openai_max_context').addEventListener('change', (event) => { | 796 | document.getElementById('openai_max_context').addEventListener('change', (event) => { |
| 797 | if (!(event.target instanceof HTMLInputElement)) return; | ||
| 676 | this.serviceSettings.openai_max_context = event.target.value; | 798 | this.serviceSettings.openai_max_context = event.target.value; |
| 677 | if (this.activeCharacter) this.renderDebounced(); | 799 | if (this.activeCharacter) this.renderDebounced(); |
| 678 | }); | 800 | }); |
| @@ -778,23 +900,33 @@ class PromptManager { | |||
| 778 | 900 | ||
| 779 | /** | 901 | /** |
| 780 | * Update a prompt with the values from the HTML form. | 902 | * Update a prompt with the values from the HTML form. |
| 781 | * @param {object} prompt - The prompt to be updated. | 903 | * @param {Partial<Prompt>} prompt - The prompt to be updated. |
| 782 | * @returns {void} | 904 | * @returns {void} |
| 783 | */ | 905 | */ |
| 784 | updatePromptWithPromptEditForm(prompt) { | 906 | updatePromptWithPromptEditForm(prompt) { |
| 785 | prompt.name = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_name').value; | 907 | const nameField = /** @type {HTMLInputElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_name')); |
| 786 | prompt.role = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_role').value; | 908 | const roleField = /** @type {HTMLSelectElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_role')); |
| 787 | prompt.content = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt').value; | 909 | const promptField = /** @type {HTMLTextAreaElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt')); |
| 788 | prompt.injection_position = Number(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position').value); | 910 | const injectionPositionField = /** @type {HTMLSelectElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position')); |
| 789 | prompt.injection_depth = Number(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_depth').value); | 911 | const injectionDepthField = /** @type {HTMLInputElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_depth')); |
| 790 | prompt.injection_order = Number(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_order').value); | 912 | const injectionOrderField = /** @type {HTMLInputElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_order')); |
| 791 | prompt.forbid_overrides = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_forbid_overrides').checked; | 913 | const injectionTriggerField = /** @type {HTMLSelectElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_trigger')); |
| 914 | const forbidOverridesField = /** @type {HTMLInputElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_forbid_overrides')); | ||
| 915 | |||
| 916 | prompt.name = nameField.value; | ||
| 917 | prompt.role = roleField.value; | ||
| 918 | prompt.content = promptField.value; | ||
| 919 | prompt.injection_position = Number(injectionPositionField.value); | ||
| 920 | prompt.injection_depth = Number(injectionDepthField.value); | ||
| 921 | prompt.injection_order = Number(injectionOrderField.value); | ||
| 922 | prompt.injection_trigger = Array.from(injectionTriggerField.selectedOptions).map(option => option.value); | ||
| 923 | prompt.forbid_overrides = forbidOverridesField.checked; | ||
| 792 | } | 924 | } |
| 793 | 925 | ||
| 794 | /** | 926 | /** |
| 795 | * Find a prompt by its identifier and update it with the provided object. | 927 | * Find a prompt by its identifier and update it with the provided object. |
| 796 | * @param {string} identifier - The identifier of the prompt. | 928 | * @param {string} identifier - The identifier of the prompt. |
| 797 | * @param {object} updatePrompt - An object with properties to be updated in the prompt. | 929 | * @param {Prompt} updatePrompt - An object with properties to be updated in the prompt. |
| 798 | * @returns {void} | 930 | * @returns {void} |
| 799 | */ | 931 | */ |
| 800 | updatePromptByIdentifier(identifier, updatePrompt) { | 932 | updatePromptByIdentifier(identifier, updatePrompt) { |
| @@ -804,7 +936,7 @@ class PromptManager { | |||
| 804 | 936 | ||
| 805 | /** | 937 | /** |
| 806 | * Iterate over an array of prompts, find each one by its identifier, and update them with the provided data. | 938 | * Iterate over an array of prompts, find each one by its identifier, and update them with the provided data. |
| 807 | * @param {object[]} prompts - An array of prompt updates. | 939 | * @param {Prompt[]} prompts - An array of prompt updates. |
| 808 | * @returns {void} | 940 | * @returns {void} |
| 809 | */ | 941 | */ |
| 810 | updatePrompts(prompts) { | 942 | updatePrompts(prompts) { |
| @@ -826,7 +958,7 @@ class PromptManager { | |||
| 826 | 958 | ||
| 827 | /** | 959 | /** |
| 828 | * Add a prompt to the current character's prompt list. | 960 | * Add a prompt to the current character's prompt list. |
| 829 | * @param {object} prompt - The prompt to be added. | 961 | * @param {Prompt} prompt - The prompt to be added. |
| 830 | * @param {object} character - The character whose prompt list will be updated. | 962 | * @param {object} character - The character whose prompt list will be updated. |
| 831 | * @returns {void} | 963 | * @returns {void} |
| 832 | */ | 964 | */ |
| @@ -839,7 +971,7 @@ class PromptManager { | |||
| 839 | 971 | ||
| 840 | /** | 972 | /** |
| 841 | * Remove a prompt from the current character's prompt list. | 973 | * Remove a prompt from the current character's prompt list. |
| 842 | * @param {object} prompt - The prompt to be removed. | 974 | * @param {Prompt} prompt - The prompt to be removed. |
| 843 | * @param {object} character - The character whose prompt list will be updated. | 975 | * @param {object} character - The character whose prompt list will be updated. |
| 844 | * @returns {void} | 976 | * @returns {void} |
| 845 | */ | 977 | */ |
| @@ -853,7 +985,7 @@ class PromptManager { | |||
| 853 | 985 | ||
| 854 | /** | 986 | /** |
| 855 | * Create a new prompt and add it to the list of prompts. | 987 | * Create a new prompt and add it to the list of prompts. |
| 856 | * @param {object} prompt - The prompt to be added. | 988 | * @param {Partial<Prompt>} prompt - The prompt to be added. |
| 857 | * @param {string} identifier - The identifier for the new prompt. | 989 | * @param {string} identifier - The identifier for the new prompt. |
| 858 | * @returns {void} | 990 | * @returns {void} |
| 859 | */ | 991 | */ |
| @@ -931,7 +1063,7 @@ class PromptManager { | |||
| 931 | 1063 | ||
| 932 | /** | 1064 | /** |
| 933 | * Check whether a prompt can be inspected. | 1065 | * Check whether a prompt can be inspected. |
| 934 | * @param {object} prompt - The prompt to check. | 1066 | * @param {Prompt} prompt - The prompt to check. |
| 935 | * @returns {boolean} True if the prompt is a marker, false otherwise. | 1067 | * @returns {boolean} True if the prompt is a marker, false otherwise. |
| 936 | */ | 1068 | */ |
| 937 | isPromptInspectionAllowed(prompt) { | 1069 | isPromptInspectionAllowed(prompt) { |
| @@ -940,7 +1072,7 @@ class PromptManager { | |||
| 940 | 1072 | ||
| 941 | /** | 1073 | /** |
| 942 | * Check whether a prompt can be deleted. System prompts cannot be deleted. | 1074 | * Check whether a prompt can be deleted. System prompts cannot be deleted. |
| 943 | * @param {object} prompt - The prompt to check. | 1075 | * @param {Prompt} prompt - The prompt to check. |
| 944 | * @returns {boolean} True if the prompt can be deleted, false otherwise. | 1076 | * @returns {boolean} True if the prompt can be deleted, false otherwise. |
| 945 | */ | 1077 | */ |
| 946 | isPromptDeletionAllowed(prompt) { | 1078 | isPromptDeletionAllowed(prompt) { |
| @@ -949,7 +1081,7 @@ class PromptManager { | |||
| 949 | 1081 | ||
| 950 | /** | 1082 | /** |
| 951 | * Check whether a prompt can be edited. | 1083 | * Check whether a prompt can be edited. |
| 952 | * @param {object} prompt - The prompt to check. | 1084 | * @param {Prompt} prompt - The prompt to check. |
| 953 | * @returns {boolean} True if the prompt can be edited, false otherwise. | 1085 | * @returns {boolean} True if the prompt can be edited, false otherwise. |
| 954 | */ | 1086 | */ |
| 955 | isPromptEditAllowed(prompt) { | 1087 | isPromptEditAllowed(prompt) { |
| @@ -966,7 +1098,7 @@ class PromptManager { | |||
| 966 | 1098 | ||
| 967 | /** | 1099 | /** |
| 968 | * Check whether a prompt can be toggled on or off. | 1100 | * Check whether a prompt can be toggled on or off. |
| 969 | * @param {object} prompt - The prompt to check. | 1101 | * @param {Prompt} prompt - The prompt to check. |
| 970 | * @returns {boolean} True if the prompt can be deleted, false otherwise. | 1102 | * @returns {boolean} True if the prompt can be deleted, false otherwise. |
| 971 | */ | 1103 | */ |
| 972 | isPromptToggleAllowed(prompt) { | 1104 | isPromptToggleAllowed(prompt) { |
| @@ -1062,7 +1194,7 @@ class PromptManager { | |||
| 1062 | 1194 | ||
| 1063 | /** | 1195 | /** |
| 1064 | * Get the prompts for a specific character. Can be filtered to only include enabled prompts. | 1196 | * Get the prompts for a specific character. Can be filtered to only include enabled prompts. |
| 1065 | * @returns {object[]} The prompts for the character. | 1197 | * @returns {Prompt[]} The prompts for the character. |
| 1066 | * @param character | 1198 | * @param character |
| 1067 | * @param onlyEnabled | 1199 | * @param onlyEnabled |
| 1068 | */ | 1200 | */ |
| @@ -1075,7 +1207,7 @@ class PromptManager { | |||
| 1075 | /** | 1207 | /** |
| 1076 | * Get the order of prompts for a specific character. If no character is specified or the character doesn't have a prompt list, an empty array is returned. | 1208 | * Get the order of prompts for a specific character. If no character is specified or the character doesn't have a prompt list, an empty array is returned. |
| 1077 | * @param {object|null} character - The character to get the prompt list for. | 1209 | * @param {object|null} character - The character to get the prompt list for. |
| 1078 | * @returns {object[]} The prompt list for the character, or an empty array. | 1210 | * @returns {Partial<Prompt>[]} The prompt list for the character, or an empty array. |
| 1079 | */ | 1211 | */ |
| 1080 | getPromptOrderForCharacter(character) { | 1212 | getPromptOrderForCharacter(character) { |
| 1081 | return !character ? [] : (this.serviceSettings.prompt_order.find(list => String(list.character_id) === String(character.id))?.order ?? []); | 1213 | return !character ? [] : (this.serviceSettings.prompt_order.find(list => String(list.character_id) === String(character.id))?.order ?? []); |
| @@ -1083,7 +1215,7 @@ class PromptManager { | |||
| 1083 | 1215 | ||
| 1084 | /** | 1216 | /** |
| 1085 | * Set the prompts for the manager. | 1217 | * Set the prompts for the manager. |
| 1086 | * @param {object[]} prompts - The prompts to be set. | 1218 | * @param {Partial<Prompt>[]} prompts - The prompts to be set. |
| 1087 | * @returns {void} | 1219 | * @returns {void} |
| 1088 | */ | 1220 | */ |
| 1089 | setPrompts(prompts) { | 1221 | setPrompts(prompts) { |
| @@ -1125,7 +1257,7 @@ class PromptManager { | |||
| 1125 | /** | 1257 | /** |
| 1126 | * Finds and returns a prompt by its identifier. | 1258 | * Finds and returns a prompt by its identifier. |
| 1127 | * @param {string} identifier - Identifier of the prompt | 1259 | * @param {string} identifier - Identifier of the prompt |
| 1128 | * @returns {Object|null} The prompt object, or null if not found | 1260 | * @returns {Prompt|null} The prompt object, or null if not found |
| 1129 | */ | 1261 | */ |
| 1130 | getPromptById(identifier) { | 1262 | getPromptById(identifier) { |
| 1131 | return this.serviceSettings.prompts.find(item => item && item.identifier === identifier) ?? null; | 1263 | return this.serviceSettings.prompts.find(item => item && item.identifier === identifier) ?? null; |
| @@ -1143,9 +1275,9 @@ class PromptManager { | |||
| 1143 | /** | 1275 | /** |
| 1144 | * Enriches a generic object, creating a new prompt object in the process | 1276 | * Enriches a generic object, creating a new prompt object in the process |
| 1145 | * | 1277 | * |
| 1146 | * @param {Object} prompt - Prompt object | 1278 | * @param {Partial<Prompt>} prompt - Prompt object |
| 1147 | * @param original | 1279 | * @param original |
| 1148 | * @returns {Object} An object with "role" and "content" properties | 1280 | * @returns {Prompt} An object with "role" and "content" properties |
| 1149 | */ | 1281 | */ |
| 1150 | preparePrompt(prompt, original = null) { | 1282 | preparePrompt(prompt, original = null) { |
| 1151 | const groupMembers = this.getActiveGroupCharacters(); | 1283 | const groupMembers = this.getActiveGroupCharacters(); |
| @@ -1184,7 +1316,7 @@ class PromptManager { | |||
| 1184 | 1316 | ||
| 1185 | const debouncedSaveServiceSettings = debouncePromise(() => this.saveServiceSettings(), 300); | 1317 | const debouncedSaveServiceSettings = debouncePromise(() => this.saveServiceSettings(), 300); |
| 1186 | 1318 | ||
| 1187 | const textarea = document.getElementById(textareaIdentifier); | 1319 | const textarea = /** @type {HTMLTextAreaElement} */(document.getElementById(textareaIdentifier)); |
| 1188 | textarea.addEventListener('blur', () => { | 1320 | textarea.addEventListener('blur', () => { |
| 1189 | prompt.content = textarea.value; | 1321 | prompt.content = textarea.value; |
| 1190 | this.updatePromptByIdentifier(identifier, prompt); | 1322 | this.updatePromptByIdentifier(identifier, prompt); |
| @@ -1193,9 +1325,15 @@ class PromptManager { | |||
| 1193 | 1325 | ||
| 1194 | } | 1326 | } |
| 1195 | 1327 | ||
| 1328 | /** | ||
| 1329 | * Updates the quick edit textarea for a specific prompt. | ||
| 1330 | * @param {string} identifier - The identifier of the prompt. | ||
| 1331 | * @param {Prompt} prompt - The updated prompt object. | ||
| 1332 | * @returns {string} The ID of the updated textarea element. | ||
| 1333 | */ | ||
| 1196 | updateQuickEdit(identifier, prompt) { | 1334 | updateQuickEdit(identifier, prompt) { |
| 1197 | const elementId = `${identifier}_prompt_quick_edit_textarea`; | 1335 | const elementId = `${identifier}_prompt_quick_edit_textarea`; |
| 1198 | const textarea = document.getElementById(elementId); | 1336 | const textarea = /** @type {HTMLTextAreaElement} */(document.getElementById(elementId)); |
| 1199 | textarea.value = prompt.content; | 1337 | textarea.value = prompt.content; |
| 1200 | 1338 | ||
| 1201 | return elementId; | 1339 | return elementId; |
| @@ -1220,30 +1358,35 @@ class PromptManager { | |||
| 1220 | 1358 | ||
| 1221 | /** | 1359 | /** |
| 1222 | * Loads a given prompt into the edit form fields. | 1360 | * Loads a given prompt into the edit form fields. |
| 1223 | * @param {Object} prompt - Prompt object with properties 'name', 'role', 'content', and 'system_prompt' | 1361 | * @param {Partial<Prompt>} prompt - Prompt object with properties 'name', 'role', 'content', and 'system_prompt' |
| 1224 | */ | 1362 | */ |
| 1225 | loadPromptIntoEditForm(prompt) { | 1363 | loadPromptIntoEditForm(prompt) { |
| 1226 | const nameField = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_name'); | 1364 | const nameField = /** @type {HTMLInputElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_name')); |
| 1227 | const roleField = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_role'); | 1365 | const roleField = /** @type {HTMLSelectElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_role')); |
| 1228 | const promptField = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt'); | 1366 | const promptField = /** @type {HTMLTextAreaElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt')); |
| 1229 | const injectionPositionField = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position'); | 1367 | const injectionPositionField = /** @type {HTMLSelectElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position')); |
| 1230 | const injectionDepthField = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_depth'); | 1368 | const injectionDepthField = /** @type {HTMLInputElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_depth')); |
| 1231 | const injectionOrderField = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_order'); | 1369 | const injectionOrderField = /** @type {HTMLInputElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_order')); |
| 1232 | const injectionDepthBlock = document.getElementById(this.configuration.prefix + 'prompt_manager_depth_block'); | 1370 | const injectionTriggerField = /** @type {HTMLSelectElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_trigger')); |
| 1233 | const injectionOrderBlock = document.getElementById(this.configuration.prefix + 'prompt_manager_order_block'); | 1371 | const injectionDepthBlock = /** @type {HTMLDivElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_depth_block')); |
| 1234 | const forbidOverridesField = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_forbid_overrides'); | 1372 | const injectionOrderBlock = /** @type {HTMLDivElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_order_block')); |
| 1235 | const forbidOverridesBlock = document.getElementById(this.configuration.prefix + 'prompt_manager_forbid_overrides_block'); | 1373 | const forbidOverridesField = /** @type {HTMLInputElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_forbid_overrides')); |
| 1236 | const entrySourceBlock = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_source_block'); | 1374 | const forbidOverridesBlock = /** @type {HTMLDivElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_forbid_overrides_block')); |
| 1237 | const entrySource = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_source'); | 1375 | const entrySourceBlock = /** @type {HTMLDivElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_source_block')); |
| 1376 | const entrySource = /** @type {HTMLSpanElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_source')); | ||
| 1238 | const isPulledPrompt = Object.keys(this.promptSources).includes(prompt.identifier); | 1377 | const isPulledPrompt = Object.keys(this.promptSources).includes(prompt.identifier); |
| 1239 | 1378 | ||
| 1240 | nameField.value = prompt.name ?? ''; | 1379 | nameField.value = prompt.name ?? ''; |
| 1241 | roleField.value = prompt.role || 'system'; | 1380 | roleField.value = prompt.role || 'system'; |
| 1242 | promptField.value = prompt.content ?? ''; | 1381 | promptField.value = prompt.content ?? ''; |
| 1243 | promptField.disabled = prompt.marker ?? false; | 1382 | promptField.disabled = prompt.marker ?? false; |
| 1244 | injectionPositionField.value = prompt.injection_position ?? INJECTION_POSITION.RELATIVE; | 1383 | injectionPositionField.value = (prompt.injection_position ?? INJECTION_POSITION.RELATIVE).toString(); |
| 1245 | injectionDepthField.value = prompt.injection_depth ?? DEFAULT_DEPTH; | 1384 | injectionDepthField.value = (prompt.injection_depth ?? DEFAULT_DEPTH).toString(); |
| 1246 | injectionOrderField.value = prompt.injection_order ?? 100; | 1385 | injectionOrderField.value = (prompt.injection_order ?? DEFAULT_ORDER).toString(); |
| 1386 | Array.from(injectionTriggerField.options).forEach(option => { | ||
| 1387 | option.selected = Array.isArray(prompt.injection_trigger) && prompt.injection_trigger.includes(option.value); | ||
| 1388 | }); | ||
| 1389 | injectionTriggerField.dispatchEvent(new Event('change', { bubbles: true })); | ||
| 1247 | injectionDepthBlock.style.visibility = prompt.injection_position === INJECTION_POSITION.ABSOLUTE ? 'visible' : 'hidden'; | 1390 | injectionDepthBlock.style.visibility = prompt.injection_position === INJECTION_POSITION.ABSOLUTE ? 'visible' : 'hidden'; |
| 1248 | injectionOrderBlock.style.visibility = prompt.injection_position === INJECTION_POSITION.ABSOLUTE ? 'visible' : 'hidden'; | 1391 | injectionOrderBlock.style.visibility = prompt.injection_position === INJECTION_POSITION.ABSOLUTE ? 'visible' : 'hidden'; |
| 1249 | injectionPositionField.removeAttribute('disabled'); | 1392 | injectionPositionField.removeAttribute('disabled'); |
| @@ -1335,17 +1478,19 @@ class PromptManager { | |||
| 1335 | const editArea = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_edit'); | 1478 | const editArea = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_edit'); |
| 1336 | editArea.style.display = 'none'; | 1479 | editArea.style.display = 'none'; |
| 1337 | 1480 | ||
| 1338 | const nameField = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_name'); | 1481 | const nameField = /** @type {HTMLInputElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_name')); |
| 1339 | const roleField = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_role'); | 1482 | const roleField = /** @type {HTMLSelectElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_role')); |
| 1340 | const promptField = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt'); | 1483 | const promptField = /** @type {HTMLTextAreaElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt')); |
| 1341 | const injectionPositionField = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position'); | 1484 | const injectionPositionField = /** @type {HTMLSelectElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position')); |
| 1342 | const injectionDepthField = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_depth'); | 1485 | const injectionDepthField = /** @type {HTMLInputElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_depth')); |
| 1343 | const injectionDepthBlock = document.getElementById(this.configuration.prefix + 'prompt_manager_depth_block'); | 1486 | const injectionDepthBlock = /** @type {HTMLDivElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_depth_block')); |
| 1344 | const injectionOrderBlock = document.getElementById(this.configuration.prefix + 'prompt_manager_order_block'); | 1487 | const injectionOrderBlock = /** @type {HTMLDivElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_order_block')); |
| 1345 | const forbidOverridesField = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_forbid_overrides'); | 1488 | const injectionOrderField = /** @type {HTMLInputElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_order')); |
| 1346 | const forbidOverridesBlock = document.getElementById(this.configuration.prefix + 'prompt_manager_forbid_overrides_block'); | 1489 | const injectionTriggerField = /** @type {HTMLSelectElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_trigger')); |
| 1347 | const entrySourceBlock = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_source_block'); | 1490 | const forbidOverridesField = /** @type {HTMLInputElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_forbid_overrides')); |
| 1348 | const entrySource = document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_source'); | 1491 | const forbidOverridesBlock = /** @type {HTMLDivElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_forbid_overrides_block')); |
| 1492 | const entrySourceBlock = /** @type {HTMLDivElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_source_block')); | ||
| 1493 | const entrySource = /** @type {HTMLSpanElement} */(document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_source')); | ||
| 1349 | 1494 | ||
| 1350 | nameField.value = ''; | 1495 | nameField.value = ''; |
| 1351 | roleField.selectedIndex = 0; | 1496 | roleField.selectedIndex = 0; |
| @@ -1353,7 +1498,9 @@ class PromptManager { | |||
| 1353 | promptField.disabled = false; | 1498 | promptField.disabled = false; |
| 1354 | injectionPositionField.selectedIndex = 0; | 1499 | injectionPositionField.selectedIndex = 0; |
| 1355 | injectionPositionField.removeAttribute('disabled'); | 1500 | injectionPositionField.removeAttribute('disabled'); |
| 1356 | injectionDepthField.value = DEFAULT_DEPTH; | 1501 | injectionDepthField.value = DEFAULT_DEPTH.toString(); |
| 1502 | injectionOrderField.value = DEFAULT_ORDER.toString(); | ||
| 1503 | injectionTriggerField.value = ''; | ||
| 1357 | injectionDepthBlock.style.visibility = 'unset'; | 1504 | injectionDepthBlock.style.visibility = 'unset'; |
| 1358 | injectionOrderBlock.style.visibility = 'unset'; | 1505 | injectionOrderBlock.style.visibility = 'unset'; |
| 1359 | forbidOverridesBlock.style.visibility = 'unset'; | 1506 | forbidOverridesBlock.style.visibility = 'unset'; |
| @@ -1373,22 +1520,30 @@ class PromptManager { | |||
| 1373 | 1520 | ||
| 1374 | /** | 1521 | /** |
| 1375 | * Returns a full list of prompts whose content markers have been substituted. | 1522 | * Returns a full list of prompts whose content markers have been substituted. |
| 1523 | * @param {string} generationType - The type of generation, e.g., 'continue' or 'quiet'. | ||
| 1376 | * @returns {PromptCollection} A PromptCollection object | 1524 | * @returns {PromptCollection} A PromptCollection object |
| 1377 | */ | 1525 | */ |
| 1378 | getPromptCollection() { | 1526 | getPromptCollection(generationType) { |
| 1527 | generationType = String(generationType || 'normal').toLowerCase().trim(); | ||
| 1528 | const promptCollection = new PromptCollection(); | ||
| 1379 | const promptOrder = this.getPromptOrderForCharacter(this.activeCharacter); | 1529 | const promptOrder = this.getPromptOrderForCharacter(this.activeCharacter); |
| 1380 | 1530 | ||
| 1381 | const promptCollection = new PromptCollection(); | ||
| 1382 | promptOrder.forEach(entry => { | 1531 | promptOrder.forEach(entry => { |
| 1383 | if (true === entry.enabled) { | 1532 | const prompt = this.getPromptById(entry.identifier); |
| 1384 | const prompt = this.getPromptById(entry.identifier); | 1533 | const allowedTrigger = entry.enabled && this.shouldTrigger(prompt, generationType); |
| 1385 | if (prompt) promptCollection.add(this.preparePrompt(prompt)); | 1534 | |
| 1386 | } else if (!entry.enabled && entry.identifier === 'main') { | 1535 | if (!prompt) { |
| 1536 | return; | ||
| 1537 | } | ||
| 1538 | |||
| 1539 | if (allowedTrigger) { | ||
| 1540 | promptCollection.add(this.preparePrompt(prompt)); | ||
| 1541 | } else if (entry.identifier === 'main') { | ||
| 1387 | // Some extensions require main prompt to be present for relative inserts. | 1542 | // Some extensions require main prompt to be present for relative inserts. |
| 1388 | // So we make a GMO-free vegan replacement. | 1543 | // So we make a GMO-free vegan replacement. |
| 1389 | const prompt = structuredClone(this.getPromptById(entry.identifier)); | 1544 | const replacementPrompt = structuredClone(prompt); |
| 1390 | prompt.content = ''; | 1545 | replacementPrompt.content = ''; |
| 1391 | if (prompt) promptCollection.add(this.preparePrompt(prompt)); | 1546 | promptCollection.add(this.preparePrompt(replacementPrompt)); |
| 1392 | } | 1547 | } |
| 1393 | }); | 1548 | }); |
| 1394 | 1549 | ||
| @@ -1396,6 +1551,18 @@ class PromptManager { | |||
| 1396 | } | 1551 | } |
| 1397 | 1552 | ||
| 1398 | /** | 1553 | /** |
| 1554 | * Checks if a prompt should be triggered based on its injection triggers. | ||
| 1555 | * @param {Prompt} prompt - The prompt to check. | ||
| 1556 | * @param {string} generationType - The type of generation to check against. | ||
| 1557 | * @returns {boolean} True if the prompt should be triggered, false otherwise. | ||
| 1558 | */ | ||
| 1559 | shouldTrigger(prompt, generationType) { | ||
| 1560 | if (!Array.isArray(prompt?.injection_trigger)) return true; | ||
| 1561 | if (!prompt.injection_trigger.length) return true; | ||
| 1562 | return prompt.injection_trigger.includes(generationType); | ||
| 1563 | } | ||
| 1564 | |||
| 1565 | /** | ||
| 1399 | * Setter for messages property | 1566 | * Setter for messages property |
| 1400 | * | 1567 | * |
| 1401 | * @param {import('./openai.js').MessageCollection} messages | 1568 | * @param {import('./openai.js').MessageCollection} messages |
| @@ -1807,11 +1974,7 @@ class PromptManager { | |||
| 1807 | * @returns {string} A string representation of an uuid4 | 1974 | * @returns {string} A string representation of an uuid4 |
| 1808 | */ | 1975 | */ |
| 1809 | getUuidv4() { | 1976 | getUuidv4() { |
| 1810 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { | 1977 | return uuidv4(); |
| 1811 | let r = Math.random() * 16 | 0, | ||
| 1812 | v = c === 'x' ? r : (r & 0x3 | 0x8); | ||
| 1813 | return v.toString(16); | ||
| 1814 | }); | ||
| 1815 | } | 1978 | } |
| 1816 | 1979 | ||
| 1817 | /** | 1980 | /** |
| @@ -4,7 +4,6 @@ import { | |||
| 4 | characters, | 4 | characters, |
| 5 | online_status, | 5 | online_status, |
| 6 | main_api, | 6 | main_api, |
| 7 | api_server, | ||
| 8 | is_send_press, | 7 | is_send_press, |
| 9 | max_context, | 8 | max_context, |
| 10 | saveSettingsDebounced, | 9 | saveSettingsDebounced, |
| @@ -42,6 +41,7 @@ import { debounce_timeout } from './constants.js'; | |||
| 42 | import { Popup } from './popup.js'; | 41 | import { Popup } from './popup.js'; |
| 43 | import { accountStorage } from './util/AccountStorage.js'; | 42 | import { accountStorage } from './util/AccountStorage.js'; |
| 44 | import { getCurrentUserHandle } from './user.js'; | 43 | import { getCurrentUserHandle } from './user.js'; |
| 44 | import { kai_settings } from './kai-settings.js'; | ||
| 45 | 45 | ||
| 46 | var RPanelPin = document.getElementById('rm_button_panel_pin'); | 46 | var RPanelPin = document.getElementById('rm_button_panel_pin'); |
| 47 | var LPanelPin = document.getElementById('lm_button_panel_pin'); | 47 | var LPanelPin = document.getElementById('lm_button_panel_pin'); |
| @@ -371,7 +371,7 @@ function RA_autoconnect(PrevApi) { | |||
| 371 | if (online_status === 'no_connection' && power_user.auto_connect) { | 371 | if (online_status === 'no_connection' && power_user.auto_connect) { |
| 372 | switch (main_api) { | 372 | switch (main_api) { |
| 373 | case 'kobold': | 373 | case 'kobold': |
| 374 | if (api_server && isValidUrl(api_server)) { | 374 | if (kai_settings.api_server && isValidUrl(kai_settings.api_server)) { |
| 375 | $('#api_button').trigger('click'); | 375 | $('#api_button').trigger('click'); |
| 376 | } | 376 | } |
| 377 | break; | 377 | break; |
| @@ -397,8 +397,6 @@ function RA_autoconnect(PrevApi) { | |||
| 397 | case 'openai': | 397 | case 'openai': |
| 398 | if (((secret_state[SECRET_KEYS.OPENAI] || oai_settings.reverse_proxy) && oai_settings.chat_completion_source == chat_completion_sources.OPENAI) | 398 | if (((secret_state[SECRET_KEYS.OPENAI] || oai_settings.reverse_proxy) && oai_settings.chat_completion_source == chat_completion_sources.OPENAI) |
| 399 | || ((secret_state[SECRET_KEYS.CLAUDE] || oai_settings.reverse_proxy) && oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) | 399 | || ((secret_state[SECRET_KEYS.CLAUDE] || oai_settings.reverse_proxy) && oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) |
| 400 | || ((secret_state[SECRET_KEYS.SCALE] || secret_state[SECRET_KEYS.SCALE_COOKIE]) && oai_settings.chat_completion_source == chat_completion_sources.SCALE) | ||
| 401 | || (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI) | ||
| 402 | || (secret_state[SECRET_KEYS.OPENROUTER] && oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER) | 400 | || (secret_state[SECRET_KEYS.OPENROUTER] && oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER) |
| 403 | || (secret_state[SECRET_KEYS.AI21] && oai_settings.chat_completion_source == chat_completion_sources.AI21) | 401 | || (secret_state[SECRET_KEYS.AI21] && oai_settings.chat_completion_source == chat_completion_sources.AI21) |
| 404 | || (secret_state[SECRET_KEYS.MAKERSUITE] && oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) | 402 | || (secret_state[SECRET_KEYS.MAKERSUITE] && oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) |
| @@ -434,19 +432,19 @@ function OpenNavPanels() { | |||
| 434 | //auto-open R nav if locked and previously open | 432 | //auto-open R nav if locked and previously open |
| 435 | if (accountStorage.getItem('NavLockOn') == 'true' && accountStorage.getItem('NavOpened') == 'true') { | 433 | if (accountStorage.getItem('NavLockOn') == 'true' && accountStorage.getItem('NavOpened') == 'true') { |
| 436 | //console.log("RA -- clicking right nav to open"); | 434 | //console.log("RA -- clicking right nav to open"); |
| 437 | $('#rightNavDrawerIcon').click(); | 435 | $('#rightNavDrawerIcon').trigger('click'); |
| 438 | } | 436 | } |
| 439 | 437 | ||
| 440 | //auto-open L nav if locked and previously open | 438 | //auto-open L nav if locked and previously open |
| 441 | if (accountStorage.getItem('LNavLockOn') == 'true' && accountStorage.getItem('LNavOpened') == 'true') { | 439 | if (accountStorage.getItem('LNavLockOn') == 'true' && accountStorage.getItem('LNavOpened') == 'true') { |
| 442 | console.debug('RA -- clicking left nav to open'); | 440 | console.debug('RA -- clicking left nav to open'); |
| 443 | $('#leftNavDrawerIcon').click(); | 441 | $('#leftNavDrawerIcon').trigger('click'); |
| 444 | } | 442 | } |
| 445 | 443 | ||
| 446 | //auto-open WI if locked and previously open | 444 | //auto-open WI if locked and previously open |
| 447 | if (accountStorage.getItem('WINavLockOn') == 'true' && accountStorage.getItem('WINavOpened') == 'true') { | 445 | if (accountStorage.getItem('WINavLockOn') == 'true' && accountStorage.getItem('WINavOpened') == 'true') { |
| 448 | console.debug('RA -- clicking WI to open'); | 446 | console.debug('RA -- clicking WI to open'); |
| 449 | $('#WIDrawerIcon').click(); | 447 | $('#WIDrawerIcon').trigger('click'); |
| 450 | } | 448 | } |
| 451 | } | 449 | } |
| 452 | } | 450 | } |
| @@ -711,7 +709,7 @@ export function initRossMods() { | |||
| 711 | RA_autoconnect(); | 709 | RA_autoconnect(); |
| 712 | } | 710 | } |
| 713 | 711 | ||
| 714 | $('#main_api').change(function () { | 712 | $('#main_api').on('change', function () { |
| 715 | var PrevAPI = main_api; | 713 | var PrevAPI = main_api; |
| 716 | setTimeout(() => RA_autoconnect(PrevAPI), 100); | 714 | setTimeout(() => RA_autoconnect(PrevAPI), 100); |
| 717 | }); | 715 | }); |
| @@ -834,11 +832,11 @@ export function initRossMods() { | |||
| 834 | }); | 832 | }); |
| 835 | 833 | ||
| 836 | var chatbarInFocus = false; | 834 | var chatbarInFocus = false; |
| 837 | $('#send_textarea').focus(function () { | 835 | $('#send_textarea').on('focus', function () { |
| 838 | chatbarInFocus = true; | 836 | chatbarInFocus = true; |
| 839 | }); | 837 | }); |
| 840 | 838 | ||
| 841 | $('#send_textarea').blur(function () { | 839 | $('#send_textarea').on('blur', function () { |
| 842 | chatbarInFocus = false; | 840 | chatbarInFocus = false; |
| 843 | }); | 841 | }); |
| 844 | 842 | ||
| @@ -846,8 +844,8 @@ export function initRossMods() { | |||
| 846 | OpenNavPanels(); | 844 | OpenNavPanels(); |
| 847 | }, 300); | 845 | }, 300); |
| 848 | 846 | ||
| 849 | $(SelectedCharacterTab).click(function () { accountStorage.setItem('SelectedNavTab', 'rm_button_selected_ch'); }); | 847 | $(SelectedCharacterTab).on('click', function () { accountStorage.setItem('SelectedNavTab', 'rm_button_selected_ch'); }); |
| 850 | $('#rm_button_characters').click(function () { accountStorage.setItem('SelectedNavTab', 'rm_button_characters'); }); | 848 | $('#rm_button_characters').on('click', function () { accountStorage.setItem('SelectedNavTab', 'rm_button_characters'); }); |
| 851 | 849 | ||
| 852 | // when a char is selected from the list, save them as the auto-load character for next page load | 850 | // when a char is selected from the list, save them as the auto-load character for next page load |
| 853 | 851 | ||
| @@ -932,7 +930,7 @@ export function initRossMods() { | |||
| 932 | var SwipeTargetMesClassParent = $(e.target).closest('.last_mes'); | 930 | var SwipeTargetMesClassParent = $(e.target).closest('.last_mes'); |
| 933 | if (SwipeTargetMesClassParent !== null) { | 931 | if (SwipeTargetMesClassParent !== null) { |
| 934 | if (SwipeButR.css('display') === 'flex') { | 932 | if (SwipeButR.css('display') === 'flex') { |
| 935 | SwipeButR.click(); | 933 | SwipeButR.trigger('click'); |
| 936 | } | 934 | } |
| 937 | } | 935 | } |
| 938 | }); | 936 | }); |
| @@ -956,7 +954,7 @@ export function initRossMods() { | |||
| 956 | var SwipeTargetMesClassParent = $(e.target).closest('.last_mes'); | 954 | var SwipeTargetMesClassParent = $(e.target).closest('.last_mes'); |
| 957 | if (SwipeTargetMesClassParent !== null) { | 955 | if (SwipeTargetMesClassParent !== null) { |
| 958 | if (SwipeButL.css('display') === 'flex') { | 956 | if (SwipeButL.css('display') === 'flex') { |
| 959 | SwipeButL.click(); | 957 | SwipeButL.trigger('click'); |
| 960 | } | 958 | } |
| 961 | } | 959 | } |
| 962 | }); | 960 | }); |
| @@ -1159,7 +1157,7 @@ export function initRossMods() { | |||
| 1159 | const lastMes = document.querySelector('.last_mes'); | 1157 | const lastMes = document.querySelector('.last_mes'); |
| 1160 | const editMes = lastMes.querySelector('.mes_block .mes_edit'); | 1158 | const editMes = lastMes.querySelector('.mes_block .mes_edit'); |
| 1161 | if (editMes !== null) { | 1159 | if (editMes !== null) { |
| 1162 | $(editMes).click(); | 1160 | $(editMes).trigger('click'); |
| 1163 | return; | 1161 | return; |
| 1164 | } | 1162 | } |
| 1165 | } | 1163 | } |
| @@ -421,7 +421,7 @@ async function autoBackgroundCommand() { | |||
| 421 | 421 | ||
| 422 | const list = options.map(option => `- ${option.text}`).join('\n'); | 422 | const list = options.map(option => `- ${option.text}`).join('\n'); |
| 423 | const prompt = stringFormat(autoBgPrompt, list); | 423 | const prompt = stringFormat(autoBgPrompt, list); |
| 424 | const reply = await generateQuietPrompt(prompt, false, false); | 424 | const reply = await generateQuietPrompt({ quietPrompt: prompt }); |
| 425 | const fuse = new Fuse(options, { keys: ['text'] }); | 425 | const fuse = new Fuse(options, { keys: ['text'] }); |
| 426 | const bestMatch = fuse.search(reply, { limit: 1 }); | 426 | const bestMatch = fuse.search(reply, { limit: 1 }); |
| 427 | 427 | ||
| @@ -524,7 +524,7 @@ async function resolveImageUrl(bg, isCustom) { | |||
| 524 | ? bg | 524 | ? bg |
| 525 | : getThumbnailUrl('bg', bg); | 525 | : getThumbnailUrl('bg', bg); |
| 526 | 526 | ||
| 527 | return `url('${thumbnailUrl}')`; | 527 | return `url("${thumbnailUrl}")`; |
| 528 | } | 528 | } |
| 529 | 529 | ||
| 530 | /** | 530 | /** |
| @@ -636,12 +636,9 @@ async function uploadBackground(formData) { | |||
| 636 | return; | 636 | return; |
| 637 | } | 637 | } |
| 638 | 638 | ||
| 639 | const headers = getRequestHeaders(); | ||
| 640 | delete headers['Content-Type']; | ||
| 641 | |||
| 642 | const response = await fetch('/api/backgrounds/upload', { | 639 | const response = await fetch('/api/backgrounds/upload', { |
| 643 | method: 'POST', | 640 | method: 'POST', |
| 644 | headers: headers, | 641 | headers: getRequestHeaders({ omitContentType: true }), |
| 645 | body: formData, | 642 | body: formData, |
| 646 | cache: 'no-cache', | 643 | cache: 'no-cache', |
| 647 | }); | 644 | }); |
| @@ -1,6 +1,6 @@ | |||
| 1 | import { characterGroupOverlay } from '../script.js'; | 1 | import { characterGroupOverlay } from '../script.js'; |
| 2 | import { BulkEditOverlay, BulkEditOverlayState } from './BulkEditOverlay.js'; | 2 | import { BulkEditOverlay, BulkEditOverlayState, CharacterContextMenu } from './BulkEditOverlay.js'; |
| 3 | 3 | import { event_types, eventSource } from './events.js'; | |
| 4 | 4 | ||
| 5 | let is_bulk_edit = false; | 5 | let is_bulk_edit = false; |
| 6 | 6 | ||
| @@ -121,4 +121,8 @@ export function initBulkEdit() { | |||
| 121 | $('#bulkEditButton').on('click', onEditButtonClick); | 121 | $('#bulkEditButton').on('click', onEditButtonClick); |
| 122 | $('#bulkSelectAllButton').on('click', onSelectAllButtonClick); | 122 | $('#bulkSelectAllButton').on('click', onSelectAllButtonClick); |
| 123 | $('#bulkDeleteButton').on('click', onDeleteButtonClick); | 123 | $('#bulkDeleteButton').on('click', onDeleteButtonClick); |
| 124 | |||
| 125 | const characterContextMenu = new CharacterContextMenu(characterGroupOverlay); | ||
| 126 | eventSource.on(event_types.CHARACTER_PAGE_LOADED, characterGroupOverlay.onPageLoad); | ||
| 127 | console.debug('Character context menu initialized', characterContextMenu); | ||
| 124 | } | 128 | } |
| @@ -135,7 +135,7 @@ function onCfgMenuItemClick() { | |||
| 135 | .siblings('.inline-drawer-content') | 135 | .siblings('.inline-drawer-content') |
| 136 | .css('display') !== 'block') { | 136 | .css('display') !== 'block') { |
| 137 | $('#floatingPrompt').addClass('resizing'); | 137 | $('#floatingPrompt').addClass('resizing'); |
| 138 | $('#CFGBlockToggle').click(); | 138 | $('#CFGBlockToggle').trigger('click'); |
| 139 | } | 139 | } |
| 140 | } else { | 140 | } else { |
| 141 | //hide AN if it's already displayed | 141 | //hide AN if it's already displayed |
| @@ -1,3 +1,5 @@ | |||
| 1 | import { t } from './i18n.js'; | ||
| 2 | |||
| 1 | // the hash can be obtained from command line e.g. via: MODEL=path_to_model; python -c "import json, hashlib, sys; print(hashlib.sha256(json.load(open('"$MODEL"/tokenizer_config.json'))['chat_template'].encode()).hexdigest())" | 3 | // the hash can be obtained from command line e.g. via: MODEL=path_to_model; python -c "import json, hashlib, sys; print(hashlib.sha256(json.load(open('"$MODEL"/tokenizer_config.json'))['chat_template'].encode()).hexdigest())" |
| 2 | // note that chat templates must be trimmed to match the llama.cpp metadata value | 4 | // note that chat templates must be trimmed to match the llama.cpp metadata value |
| 3 | const hash_derivations = { | 5 | const hash_derivations = { |
| @@ -83,21 +85,31 @@ const hash_derivations = { | |||
| 83 | '854b703e44ca06bdb196cc471c728d15dbab61e744fe6cdce980086b61646ed1': | 85 | '854b703e44ca06bdb196cc471c728d15dbab61e744fe6cdce980086b61646ed1': |
| 84 | 'GLM-4' | 86 | 'GLM-4' |
| 85 | , | 87 | , |
| 86 | }; | ||
| 87 | 88 | ||
| 88 | const substr_derivations = { | 89 | // Kimi K2, ... |
| 89 | '<|im_start|>': 'ChatML', // qwen2.5, ... | 90 | 'aab20feb9bc6881f941ea649356130ffbc4943b3c2577c0991e1fba90de5a0fc': |
| 91 | 'Moonshot AI' | ||
| 92 | , | ||
| 90 | }; | 93 | }; |
| 91 | 94 | ||
| 95 | const substr_derivations = [ | ||
| 96 | ['Moonshot AI', ['<|im_user|>user<|im_middle|>', '<|im_assistant|>assistant<|im_middle|>', '<|im_end|>']], | ||
| 97 | |||
| 98 | // Generic cases | ||
| 99 | ['ChatML', ['<|im_start|>user', '<|im_start|>assistant', '<|im_end|>']], | ||
| 100 | ]; | ||
| 101 | |||
| 92 | const parse_derivation = derivation => (typeof derivation === 'string') ? { | 102 | const parse_derivation = derivation => (typeof derivation === 'string') ? { |
| 93 | 'context': derivation, | 103 | 'context': derivation, |
| 94 | 'instruct': derivation, | 104 | 'instruct': derivation, |
| 95 | } : derivation; | 105 | } : derivation; |
| 96 | 106 | ||
| 107 | const not_found = { context: null, instruct: null }; | ||
| 108 | |||
| 97 | export async function deriveTemplatesFromChatTemplate(chat_template, hash) { | 109 | export async function deriveTemplatesFromChatTemplate(chat_template, hash) { |
| 98 | if (chat_template.trim() === '') { | 110 | if (chat_template.trim() === '') { |
| 99 | console.log('Missing chat template.'); | 111 | console.log('Missing chat template.'); |
| 100 | return null; | 112 | return not_found; |
| 101 | } | 113 | } |
| 102 | 114 | ||
| 103 | if (hash in hash_derivations) { | 115 | if (hash in hash_derivations) { |
| @@ -105,12 +117,71 @@ export async function deriveTemplatesFromChatTemplate(chat_template, hash) { | |||
| 105 | } | 117 | } |
| 106 | 118 | ||
| 107 | // heuristics | 119 | // heuristics |
| 108 | for (const [substr, derivation] of Object.entries(substr_derivations) ) { | 120 | for (const [derivation, substr] of substr_derivations) { |
| 109 | if (chat_template.includes(substr)) { | 121 | if ([substr].flat().every(str => chat_template.includes(str))) { |
| 110 | return parse_derivation(derivation); | 122 | return parse_derivation(derivation); |
| 111 | } | 123 | } |
| 112 | } | 124 | } |
| 113 | 125 | ||
| 114 | console.warn(`Unknown chat template hash: ${hash} for [${chat_template}]`); | 126 | console.warn(`Unknown chat template hash: ${hash} for [${chat_template}]`); |
| 115 | return null; | 127 | return not_found; |
| 128 | } | ||
| 129 | |||
| 130 | export async function bindModelTemplates(power_user, online_status) { | ||
| 131 | if (online_status === 'no_connection') { | ||
| 132 | return false; | ||
| 133 | } | ||
| 134 | |||
| 135 | const chatTemplateHash = power_user.chat_template_hash; | ||
| 136 | const bindModelTemplates = power_user.model_templates_mappings[online_status] | ||
| 137 | ?? power_user.model_templates_mappings[chatTemplateHash] | ||
| 138 | ?? {}; | ||
| 139 | const bindingsMatch = bindModelTemplates | ||
| 140 | && power_user.context.preset == bindModelTemplates['context'] | ||
| 141 | && (!power_user.instruct.enabled || power_user.instruct.preset === bindModelTemplates['instruct']); | ||
| 142 | |||
| 143 | const bound = []; | ||
| 144 | |||
| 145 | if (bindingsMatch) { | ||
| 146 | // unmap current preset | ||
| 147 | delete power_user.model_templates_mappings[chatTemplateHash]; | ||
| 148 | delete power_user.model_templates_mappings[online_status]; | ||
| 149 | toastr.info(t`Context preset for ${online_status} will use defaults when loaded the next time.`); | ||
| 150 | } else { | ||
| 151 | if (power_user.context_derived) { | ||
| 152 | if (power_user.context.preset !== bindModelTemplates['context']) { | ||
| 153 | bound.push(`${power_user.context.preset} context preset`); | ||
| 154 | // toastr.info(`Bound ${power_user.context.preset} preset to currently loaded model and all models that share its chat template.`); | ||
| 155 | |||
| 156 | // map current preset to current chat template hash | ||
| 157 | bindModelTemplates['context'] = power_user.context.preset; | ||
| 158 | } | ||
| 159 | } else { | ||
| 160 | toastr.warning(t`Note: Context derivation is disabled. Not including context preset.`); | ||
| 161 | } | ||
| 162 | if (power_user.instruct.enabled) { | ||
| 163 | if (power_user.instruct_derived) { | ||
| 164 | if (power_user.instruct.preset !== bindModelTemplates['instruct']) { | ||
| 165 | bound.push(`${power_user.instruct.preset} instruct preset`); | ||
| 166 | bindModelTemplates['instruct'] = power_user.instruct.preset; | ||
| 167 | } | ||
| 168 | } else { | ||
| 169 | toastr.warning(t`Note: Instruct derivation is disabled. Not including instruct preset.`); | ||
| 170 | } | ||
| 171 | } | ||
| 172 | if (bound.length == 0) { | ||
| 173 | toastr.warning(t`No applicable presets available.`); | ||
| 174 | return false; | ||
| 175 | } | ||
| 176 | |||
| 177 | toastr.info(t`Bound ${online_status} to ${bound.join(', ')}.`); | ||
| 178 | if (!online_status.startsWith('koboldcpp/ggml-model-')) { | ||
| 179 | power_user.model_templates_mappings[online_status] = bindModelTemplates; | ||
| 180 | } | ||
| 181 | if (chatTemplateHash !== '') { | ||
| 182 | power_user.model_templates_mappings[chatTemplateHash] = bindModelTemplates; | ||
| 183 | } | ||
| 184 | } | ||
| 185 | |||
| 186 | return true; | ||
| 116 | } | 187 | } |
| @@ -43,6 +43,8 @@ import { | |||
| 43 | extractTextFromOffice, | 43 | extractTextFromOffice, |
| 44 | download, | 44 | download, |
| 45 | getFileText, | 45 | getFileText, |
| 46 | getFileExtension, | ||
| 47 | convertTextToBase64, | ||
| 46 | } from './utils.js'; | 48 | } from './utils.js'; |
| 47 | import { extension_settings, renderExtensionTemplateAsync, saveMetadataDebounced } from './extensions.js'; | 49 | import { extension_settings, renderExtensionTemplateAsync, saveMetadataDebounced } from './extensions.js'; |
| 48 | import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; | 50 | import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js'; |
| @@ -68,7 +70,7 @@ import { accountStorage } from './util/AccountStorage.js'; | |||
| 68 | * @returns {Promise<string>} Converted file text | 70 | * @returns {Promise<string>} Converted file text |
| 69 | */ | 71 | */ |
| 70 | 72 | ||
| 71 | const fileSizeLimit = 1024 * 1024 * 100; // 100 MB | 73 | const fileSizeLimit = 1024 * 1024 * 350; // 350 MB |
| 72 | const ATTACHMENT_SOURCE = { | 74 | const ATTACHMENT_SOURCE = { |
| 73 | GLOBAL: 'global', | 75 | GLOBAL: 'global', |
| 74 | CHARACTER: 'character', | 76 | CHARACTER: 'character', |
| @@ -204,17 +206,16 @@ export async function populateFileAttachment(message, inputId = 'file_form_input | |||
| 204 | const fileNamePrefix = `${Date.now()}_${slug}`; | 206 | const fileNamePrefix = `${Date.now()}_${slug}`; |
| 205 | const fileBase64 = await getBase64Async(file); | 207 | const fileBase64 = await getBase64Async(file); |
| 206 | let base64Data = fileBase64.split(',')[1]; | 208 | let base64Data = fileBase64.split(',')[1]; |
| 209 | const extension = getFileExtension(file); | ||
| 207 | 210 | ||
| 208 | // If file is image | 211 | // If file is image |
| 209 | if (file.type.startsWith('image/')) { | 212 | if (file.type.startsWith('image/')) { |
| 210 | const extension = file.type.split('/')[1]; | ||
| 211 | const imageUrl = await saveBase64AsFile(base64Data, name2, fileNamePrefix, extension); | 213 | const imageUrl = await saveBase64AsFile(base64Data, name2, fileNamePrefix, extension); |
| 212 | message.extra.image = imageUrl; | 214 | message.extra.image = imageUrl; |
| 213 | message.extra.inline_image = true; | 215 | message.extra.inline_image = true; |
| 214 | } | 216 | } |
| 215 | // If file is video | 217 | // If file is video |
| 216 | else if (file.type.startsWith('video/')) { | 218 | else if (file.type.startsWith('video/')) { |
| 217 | const extension = file.type.split('/')[1]; | ||
| 218 | const videoUrl = await saveBase64AsFile(base64Data, name2, fileNamePrefix, extension); | 219 | const videoUrl = await saveBase64AsFile(base64Data, name2, fileNamePrefix, extension); |
| 219 | message.extra.video = videoUrl; | 220 | message.extra.video = videoUrl; |
| 220 | } else { | 221 | } else { |
| @@ -224,7 +225,7 @@ export async function populateFileAttachment(message, inputId = 'file_form_input | |||
| 224 | try { | 225 | try { |
| 225 | const converter = getConverter(file.type); | 226 | const converter = getConverter(file.type); |
| 226 | const fileText = await converter(file); | 227 | const fileText = await converter(file); |
| 227 | base64Data = window.btoa(unescape(encodeURIComponent(fileText))); | 228 | base64Data = convertTextToBase64(fileText); |
| 228 | } catch (error) { | 229 | } catch (error) { |
| 229 | toastr.error(String(error), t`Could not convert file`); | 230 | toastr.error(String(error), t`Could not convert file`); |
| 230 | console.error('Could not convert file', error); | 231 | console.error('Could not convert file', error); |
| @@ -247,6 +248,7 @@ export async function populateFileAttachment(message, inputId = 'file_form_input | |||
| 247 | 248 | ||
| 248 | } catch (error) { | 249 | } catch (error) { |
| 249 | console.error('Could not upload file', error); | 250 | console.error('Could not upload file', error); |
| 251 | toastr.error(t`Either the file is corrupted or its format is not supported.`, t`Could not upload the file`); | ||
| 250 | } finally { | 252 | } finally { |
| 251 | $('#file_form').trigger('reset'); | 253 | $('#file_form').trigger('reset'); |
| 252 | } | 254 | } |
| @@ -476,7 +478,7 @@ export async function appendFileContent(message, messageText) { | |||
| 476 | export function encodeStyleTags(text) { | 478 | export function encodeStyleTags(text) { |
| 477 | const styleRegex = /<style>(.+?)<\/style>/gims; | 479 | const styleRegex = /<style>(.+?)<\/style>/gims; |
| 478 | return text.replaceAll(styleRegex, (_, match) => { | 480 | return text.replaceAll(styleRegex, (_, match) => { |
| 479 | return `<custom-style>${escape(match)}</custom-style>`; | 481 | return `<custom-style>${encodeURIComponent(match)}</custom-style>`; |
| 480 | }); | 482 | }); |
| 481 | } | 483 | } |
| 482 | 484 | ||
| @@ -497,14 +499,7 @@ export function decodeStyleTags(text, { prefix } = { prefix: '.mes_text ' }) { | |||
| 497 | for (let i = 0; i < rule.selectors.length; i++) { | 499 | for (let i = 0; i < rule.selectors.length; i++) { |
| 498 | const selector = rule.selectors[i]; | 500 | const selector = rule.selectors[i]; |
| 499 | if (selector) { | 501 | if (selector) { |
| 500 | const selectors = (selector.split(' ') ?? []).map((v) => { | 502 | rule.selectors[i] = prefix + sanitizeSelector(selector); |
| 501 | if (v.startsWith('.')) { | ||
| 502 | return '.custom-' + v.substring(1); | ||
| 503 | } | ||
| 504 | return v; | ||
| 505 | }).join(' '); | ||
| 506 | |||
| 507 | rule.selectors[i] = prefix + selectors; | ||
| 508 | } | 503 | } |
| 509 | } | 504 | } |
| 510 | } | 505 | } |
| @@ -513,6 +508,36 @@ export function decodeStyleTags(text, { prefix } = { prefix: '.mes_text ' }) { | |||
| 513 | } | 508 | } |
| 514 | } | 509 | } |
| 515 | 510 | ||
| 511 | function sanitizeSelector(selector) { | ||
| 512 | // Handle pseudo-classes that can contain nested selectors | ||
| 513 | const pseudoClasses = ['has', 'not', 'where', 'is', 'matches', 'any']; | ||
| 514 | const pseudoRegex = new RegExp(`:(${pseudoClasses.join('|')})\\(([^)]+)\\)`, 'g'); | ||
| 515 | |||
| 516 | // First, sanitize any nested selectors within pseudo-classes | ||
| 517 | selector = selector.replace(pseudoRegex, (match, pseudoClass, content) => { | ||
| 518 | // Recursively sanitize the content within the pseudo-class | ||
| 519 | const sanitizedContent = sanitizeSimpleSelector(content); | ||
| 520 | return `:${pseudoClass}(${sanitizedContent})`; | ||
| 521 | }); | ||
| 522 | |||
| 523 | // Then sanitize the main selector parts | ||
| 524 | return sanitizeSimpleSelector(selector); | ||
| 525 | } | ||
| 526 | |||
| 527 | function sanitizeSimpleSelector(selector) { | ||
| 528 | // Split by spaces but preserve complex selectors | ||
| 529 | return selector.split(/\s+/).map((part) => { | ||
| 530 | // Handle class selectors, but preserve pseudo-classes and other complex parts | ||
| 531 | return part.replace(/\.([\w-]+)/g, (match, className) => { | ||
| 532 | // Don't modify if it's already prefixed with 'custom-' | ||
| 533 | if (className.startsWith('custom-')) { | ||
| 534 | return match; | ||
| 535 | } | ||
| 536 | return `.custom-${className}`; | ||
| 537 | }); | ||
| 538 | }).join(' '); | ||
| 539 | } | ||
| 540 | |||
| 516 | function sanitizeRuleSet(ruleSet) { | 541 | function sanitizeRuleSet(ruleSet) { |
| 517 | if (Array.isArray(ruleSet.selectors) || Array.isArray(ruleSet.declarations)) { | 542 | if (Array.isArray(ruleSet.selectors) || Array.isArray(ruleSet.declarations)) { |
| 518 | sanitizeRule(ruleSet); | 543 | sanitizeRule(ruleSet); |
| @@ -529,7 +554,7 @@ export function decodeStyleTags(text, { prefix } = { prefix: '.mes_text ' }) { | |||
| 529 | 554 | ||
| 530 | return text.replaceAll(styleDecodeRegex, (_, style) => { | 555 | return text.replaceAll(styleDecodeRegex, (_, style) => { |
| 531 | try { | 556 | try { |
| 532 | let styleCleaned = unescape(style).replaceAll(/<br\/>/g, ''); | 557 | let styleCleaned = decodeURIComponent(style).replaceAll(/<br\/>/g, ''); |
| 533 | const ast = css.parse(styleCleaned); | 558 | const ast = css.parse(styleCleaned); |
| 534 | const sheet = ast?.stylesheet; | 559 | const sheet = ast?.stylesheet; |
| 535 | if (sheet) { | 560 | if (sheet) { |
| @@ -893,6 +918,27 @@ async function deleteMessageImage() { | |||
| 893 | await saveChatConditional(); | 918 | await saveChatConditional(); |
| 894 | } | 919 | } |
| 895 | 920 | ||
| 921 | async function deleteMessageVideo() { | ||
| 922 | const confirm = await Popup.show.confirm(t`Delete video from message?`, t`This action can't be undone.`); | ||
| 923 | if (!confirm) { | ||
| 924 | return; | ||
| 925 | } | ||
| 926 | |||
| 927 | const mesBlock = $(this).closest('.mes'); | ||
| 928 | const mesId = mesBlock.attr('mesid'); | ||
| 929 | const message = chat[mesId]; | ||
| 930 | |||
| 931 | if (!message?.extra?.video) { | ||
| 932 | console.warn('Message has no video or it is empty'); | ||
| 933 | return; | ||
| 934 | } | ||
| 935 | |||
| 936 | delete message.extra.video; | ||
| 937 | mesBlock.find('.mes_video_container').remove(); | ||
| 938 | |||
| 939 | await saveChatConditional(); | ||
| 940 | } | ||
| 941 | |||
| 896 | /** | 942 | /** |
| 897 | * Deletes file from the server. | 943 | * Deletes file from the server. |
| 898 | * @param {string} url Path to the file on the server | 944 | * @param {string} url Path to the file on the server |
| @@ -1475,14 +1521,14 @@ export async function uploadFileAttachmentToServer(file, target) { | |||
| 1475 | try { | 1521 | try { |
| 1476 | const converter = getConverter(file.type); | 1522 | const converter = getConverter(file.type); |
| 1477 | const fileText = await converter(file); | 1523 | const fileText = await converter(file); |
| 1478 | base64Data = window.btoa(unescape(encodeURIComponent(fileText))); | 1524 | base64Data = convertTextToBase64(fileText); |
| 1479 | } catch (error) { | 1525 | } catch (error) { |
| 1480 | toastr.error(String(error), t`Could not convert file`); | 1526 | toastr.error(String(error), t`Could not convert file`); |
| 1481 | console.error('Could not convert file', error); | 1527 | console.error('Could not convert file', error); |
| 1482 | } | 1528 | } |
| 1483 | } else { | 1529 | } else { |
| 1484 | const fileText = await file.text(); | 1530 | const fileText = await file.text(); |
| 1485 | base64Data = window.btoa(unescape(encodeURIComponent(fileText))); | 1531 | base64Data = convertTextToBase64(fileText); |
| 1486 | } | 1532 | } |
| 1487 | 1533 | ||
| 1488 | const fileUrl = await uploadFileAttachment(uniqueFileName, base64Data); | 1534 | const fileUrl = await uploadFileAttachment(uniqueFileName, base64Data); |
| @@ -1670,6 +1716,132 @@ export function registerFileConverter(mimeType, converter) { | |||
| 1670 | converters[mimeType] = converter; | 1716 | converters[mimeType] = converter; |
| 1671 | } | 1717 | } |
| 1672 | 1718 | ||
| 1719 | export function addDOMPurifyHooks() { | ||
| 1720 | // Allow target="_blank" in links | ||
| 1721 | DOMPurify.addHook('afterSanitizeAttributes', function (node) { | ||
| 1722 | if ('target' in node) { | ||
| 1723 | node.setAttribute('target', '_blank'); | ||
| 1724 | node.setAttribute('rel', 'noopener'); | ||
| 1725 | } | ||
| 1726 | }); | ||
| 1727 | |||
| 1728 | DOMPurify.addHook('uponSanitizeAttribute', (node, data, config) => { | ||
| 1729 | if (!config['MESSAGE_SANITIZE']) { | ||
| 1730 | return; | ||
| 1731 | } | ||
| 1732 | |||
| 1733 | /* Retain the classes on UI elements of messages that interact with the main UI */ | ||
| 1734 | const permittedNodeTypes = ['BUTTON', 'DIV']; | ||
| 1735 | if (config['MESSAGE_ALLOW_SYSTEM_UI'] && node.classList.contains('menu_button') && permittedNodeTypes.includes(node.nodeName)) { | ||
| 1736 | return; | ||
| 1737 | } | ||
| 1738 | |||
| 1739 | switch (data.attrName) { | ||
| 1740 | case 'class': { | ||
| 1741 | if (data.attrValue) { | ||
| 1742 | data.attrValue = data.attrValue.split(' ').map((v) => { | ||
| 1743 | if (v.startsWith('fa-') || v.startsWith('note-') || v === 'monospace') { | ||
| 1744 | return v; | ||
| 1745 | } | ||
| 1746 | |||
| 1747 | return 'custom-' + v; | ||
| 1748 | }).join(' '); | ||
| 1749 | } | ||
| 1750 | break; | ||
| 1751 | } | ||
| 1752 | } | ||
| 1753 | }); | ||
| 1754 | |||
| 1755 | DOMPurify.addHook('uponSanitizeElement', (node, _, config) => { | ||
| 1756 | if (!config['MESSAGE_SANITIZE']) { | ||
| 1757 | return; | ||
| 1758 | } | ||
| 1759 | |||
| 1760 | // Replace line breaks with <br> in unknown elements | ||
| 1761 | if (node instanceof HTMLUnknownElement) { | ||
| 1762 | node.innerHTML = node.innerHTML.trim().replaceAll('\n', '<br>'); | ||
| 1763 | } | ||
| 1764 | |||
| 1765 | const isMediaAllowed = isExternalMediaAllowed(); | ||
| 1766 | if (isMediaAllowed) { | ||
| 1767 | return; | ||
| 1768 | } | ||
| 1769 | |||
| 1770 | if (!(node instanceof Element)) { | ||
| 1771 | return; | ||
| 1772 | } | ||
| 1773 | |||
| 1774 | let mediaBlocked = false; | ||
| 1775 | |||
| 1776 | switch (node.tagName) { | ||
| 1777 | case 'AUDIO': | ||
| 1778 | case 'VIDEO': | ||
| 1779 | case 'SOURCE': | ||
| 1780 | case 'TRACK': | ||
| 1781 | case 'EMBED': | ||
| 1782 | case 'OBJECT': | ||
| 1783 | case 'IMG': { | ||
| 1784 | const isExternalUrl = (url) => (url.indexOf('://') > 0 || url.indexOf('//') === 0) && !url.startsWith(window.location.origin); | ||
| 1785 | const src = node.getAttribute('src'); | ||
| 1786 | const data = node.getAttribute('data'); | ||
| 1787 | const srcset = node.getAttribute('srcset'); | ||
| 1788 | |||
| 1789 | if (srcset) { | ||
| 1790 | const srcsetUrls = srcset.split(','); | ||
| 1791 | |||
| 1792 | for (const srcsetUrl of srcsetUrls) { | ||
| 1793 | const [url] = srcsetUrl.trim().split(' '); | ||
| 1794 | |||
| 1795 | if (isExternalUrl(url)) { | ||
| 1796 | console.warn('External media blocked', url); | ||
| 1797 | node.remove(); | ||
| 1798 | mediaBlocked = true; | ||
| 1799 | break; | ||
| 1800 | } | ||
| 1801 | } | ||
| 1802 | } | ||
| 1803 | |||
| 1804 | if (src && isExternalUrl(src)) { | ||
| 1805 | console.warn('External media blocked', src); | ||
| 1806 | mediaBlocked = true; | ||
| 1807 | node.remove(); | ||
| 1808 | } | ||
| 1809 | |||
| 1810 | if (data && isExternalUrl(data)) { | ||
| 1811 | console.warn('External media blocked', data); | ||
| 1812 | mediaBlocked = true; | ||
| 1813 | node.remove(); | ||
| 1814 | } | ||
| 1815 | |||
| 1816 | if (mediaBlocked && (node instanceof HTMLMediaElement)) { | ||
| 1817 | node.autoplay = false; | ||
| 1818 | node.pause(); | ||
| 1819 | } | ||
| 1820 | } | ||
| 1821 | break; | ||
| 1822 | } | ||
| 1823 | |||
| 1824 | if (mediaBlocked) { | ||
| 1825 | const entityId = getCurrentEntityId(); | ||
| 1826 | const warningShownKey = `mediaWarningShown:${entityId}`; | ||
| 1827 | |||
| 1828 | if (accountStorage.getItem(warningShownKey) === null) { | ||
| 1829 | const warningToast = toastr.warning( | ||
| 1830 | t`Use the 'Ext. Media' button to allow it. Click on this message to dismiss.`, | ||
| 1831 | t`External media has been blocked`, | ||
| 1832 | { | ||
| 1833 | timeOut: 0, | ||
| 1834 | preventDuplicates: true, | ||
| 1835 | onclick: () => toastr.clear(warningToast), | ||
| 1836 | }, | ||
| 1837 | ); | ||
| 1838 | |||
| 1839 | accountStorage.setItem(warningShownKey, 'true'); | ||
| 1840 | } | ||
| 1841 | } | ||
| 1842 | }); | ||
| 1843 | } | ||
| 1844 | |||
| 1673 | export function initChatUtilities() { | 1845 | export function initChatUtilities() { |
| 1674 | $(document).on('click', '.mes_hide', async function () { | 1846 | $(document).on('click', '.mes_hide', async function () { |
| 1675 | const messageBlock = $(this).closest('.mes'); | 1847 | const messageBlock = $(this).closest('.mes'); |
| @@ -1849,13 +2021,15 @@ export function initChatUtilities() { | |||
| 1849 | reloadCurrentChat(); | 2021 | reloadCurrentChat(); |
| 1850 | }); | 2022 | }); |
| 1851 | 2023 | ||
| 1852 | $('#creators_note_styles_button').on('click', function () { | 2024 | $('#creators_note_styles_button').on('click', function (e) { |
| 2025 | e.stopPropagation(); | ||
| 1853 | openGlobalStylesPreferenceDialog(); | 2026 | openGlobalStylesPreferenceDialog(); |
| 1854 | }); | 2027 | }); |
| 1855 | 2028 | ||
| 1856 | $(document).on('click', '.mes_img', expandMessageImage); | 2029 | $(document).on('click', '.mes_img', expandMessageImage); |
| 1857 | $(document).on('click', '.mes_img_enlarge', expandAndZoomMessageImage); | 2030 | $(document).on('click', '.mes_img_enlarge', expandAndZoomMessageImage); |
| 1858 | $(document).on('click', '.mes_img_delete', deleteMessageImage); | 2031 | $(document).on('click', '.mes_img_delete', deleteMessageImage); |
| 2032 | $(document).on('click', '.mes_video_delete', deleteMessageVideo); | ||
| 1859 | 2033 | ||
| 1860 | $('#file_form_input').on('change', async () => { | 2034 | $('#file_form_input').on('change', async () => { |
| 1861 | const fileInput = document.getElementById('file_form_input'); | 2035 | const fileInput = document.getElementById('file_form_input'); |
| @@ -22,3 +22,21 @@ export const debounce_timeout = { | |||
| 22 | * which is needed to preserve world info timed effects. | 22 | * which is needed to preserve world info timed effects. |
| 23 | */ | 23 | */ |
| 24 | export const IGNORE_SYMBOL = Symbol.for('ignore'); | 24 | export const IGNORE_SYMBOL = Symbol.for('ignore'); |
| 25 | |||
| 26 | /** | ||
| 27 | * Common video file extensions. Should be the same as supported by Gemini. | ||
| 28 | * https://ai.google.dev/gemini-api/docs/video-understanding#supported-formats | ||
| 29 | */ | ||
| 30 | export const VIDEO_EXTENSIONS = ['mp4', 'avi', 'mov', 'wmv', 'flv', 'webm', '3gp', 'mkv', 'mpg']; | ||
| 31 | |||
| 32 | /** | ||
| 33 | * Known generation triggers that can be passed to Generate function. | ||
| 34 | */ | ||
| 35 | export const GENERATION_TYPE_TRIGGERS = [ | ||
| 36 | 'normal', | ||
| 37 | 'continue', | ||
| 38 | 'impersonate', | ||
| 39 | 'swipe', | ||
| 40 | 'regenerate', | ||
| 41 | 'quiet', | ||
| 42 | ]; | ||
| @@ -1,5 +1,5 @@ | |||
| 1 | import { getPresetManager } from './preset-manager.js'; | 1 | import { getPresetManager } from './preset-manager.js'; |
| 2 | import { extractMessageFromData, getGenerateUrl, getRequestHeaders } from '../script.js'; | 2 | import { extractJsonFromData, extractMessageFromData, getGenerateUrl, getRequestHeaders } from '../script.js'; |
| 3 | import { getTextGenServer } from './textgen-settings.js'; | 3 | import { getTextGenServer } from './textgen-settings.js'; |
| 4 | import { extractReasoningFromData } from './reasoning.js'; | 4 | import { extractReasoningFromData } from './reasoning.js'; |
| 5 | import { formatInstructModeChat, formatInstructModePrompt, getInstructStoppingSequences, names_behavior_types } from './instruct-mode.js'; | 5 | import { formatInstructModeChat, formatInstructModePrompt, getInstructStoppingSequences, names_behavior_types } from './instruct-mode.js'; |
| @@ -467,7 +467,7 @@ export class ChatCompletionService { | |||
| 467 | return json; | 467 | return json; |
| 468 | } | 468 | } |
| 469 | 469 | ||
| 470 | return { | 470 | const result = { |
| 471 | content: extractMessageFromData(json, this.TYPE), | 471 | content: extractMessageFromData(json, this.TYPE), |
| 472 | reasoning: extractReasoningFromData(json, { | 472 | reasoning: extractReasoningFromData(json, { |
| 473 | mainApi: this.TYPE, | 473 | mainApi: this.TYPE, |
| @@ -475,6 +475,11 @@ export class ChatCompletionService { | |||
| 475 | ignoreShowThoughts: true, | 475 | ignoreShowThoughts: true, |
| 476 | }), | 476 | }), |
| 477 | }; | 477 | }; |
| 478 | // Try parse JSON | ||
| 479 | if (data.json_schema) { | ||
| 480 | result.content = JSON.parse(extractJsonFromData(json, { mainApi: this.TYPE, chatCompletionSource: data.chat_completion_source })); | ||
| 481 | } | ||
| 482 | return result; | ||
| 478 | } | 483 | } |
| 479 | 484 | ||
| 480 | if (!response.ok) { | 485 | if (!response.ok) { |
| @@ -1,4 +1,5 @@ | |||
| 1 | import { getRequestHeaders } from '../script.js'; | 1 | import { getRequestHeaders } from '../script.js'; |
| 2 | import { VIDEO_EXTENSIONS } from './constants.js'; | ||
| 2 | import { t } from './i18n.js'; | 3 | import { t } from './i18n.js'; |
| 3 | import { callGenericPopup, Popup, POPUP_TYPE } from './popup.js'; | 4 | import { callGenericPopup, Popup, POPUP_TYPE } from './popup.js'; |
| 4 | import { renderTemplateAsync } from './templates.js'; | 5 | import { renderTemplateAsync } from './templates.js'; |
| @@ -44,6 +45,10 @@ class DataMaidDialog { | |||
| 44 | name: t`Background Thumbnails`, | 45 | name: t`Background Thumbnails`, |
| 45 | description: t`Thumbnails for missing or deleted backgrounds.`, | 46 | description: t`Thumbnails for missing or deleted backgrounds.`, |
| 46 | }, | 47 | }, |
| 48 | personaThumbnails: { | ||
| 49 | name: t`Persona Thumbnails`, | ||
| 50 | description: t`Thumbnails for missing or deleted personas.`, | ||
| 51 | }, | ||
| 47 | chatBackups: { | 52 | chatBackups: { |
| 48 | name: t`Chat Backups`, | 53 | name: t`Chat Backups`, |
| 49 | description: t`Automatically generated chat backups.`, | 54 | description: t`Automatically generated chat backups.`, |
| @@ -217,8 +222,9 @@ class DataMaidDialog { | |||
| 217 | button.addEventListener('click', async () => { | 222 | button.addEventListener('click', async () => { |
| 218 | const item = button.closest('.dataMaidItem'); | 223 | const item = button.closest('.dataMaidItem'); |
| 219 | const hash = item?.getAttribute('data-hash'); | 224 | const hash = item?.getAttribute('data-hash'); |
| 225 | const itemName = items.find(i => i.hash === hash)?.name; | ||
| 220 | if (hash) { | 226 | if (hash) { |
| 221 | await this.view(prop, hash); | 227 | await this.view(prop, hash, itemName); |
| 222 | } | 228 | } |
| 223 | }); | 229 | }); |
| 224 | }); | 230 | }); |
| @@ -304,13 +310,14 @@ class DataMaidDialog { | |||
| 304 | * Opens the item view for a specific hash. | 310 | * Opens the item view for a specific hash. |
| 305 | * @param {string} prop Property name for the category | 311 | * @param {string} prop Property name for the category |
| 306 | * @param {string} hash Item hash to view | 312 | * @param {string} hash Item hash to view |
| 313 | * @param {string} name Name of the item to view | ||
| 307 | * @private | 314 | * @private |
| 308 | */ | 315 | */ |
| 309 | async view(prop, hash) { | 316 | async view(prop, hash, name) { |
| 310 | const url = this.getViewUrl(hash); | 317 | const url = this.getViewUrl(hash); |
| 311 | const isImage = ['images', 'avatarThumbnails', 'backgroundThumbnails'].includes(prop); | 318 | const isImage = ['images', 'avatarThumbnails', 'backgroundThumbnails'].includes(prop); |
| 312 | const element = isImage | 319 | const element = isImage |
| 313 | ? await this.getViewElement(url) | 320 | ? await this.getViewElement(url, name) |
| 314 | : await this.getTextViewElement(url); | 321 | : await this.getTextViewElement(url); |
| 315 | await callGenericPopup(element, POPUP_TYPE.DISPLAY, '', { large: true, wide: true }); | 322 | await callGenericPopup(element, POPUP_TYPE.DISPLAY, '', { large: true, wide: true }); |
| 316 | } | 323 | } |
| @@ -341,16 +348,21 @@ class DataMaidDialog { | |||
| 341 | } | 348 | } |
| 342 | 349 | ||
| 343 | /** | 350 | /** |
| 344 | * Gets an image element for viewing images. | 351 | * Gets a media element for viewing images or videos. |
| 345 | * @param {string} url View URL | 352 | * @param {string} url View URL |
| 353 | * @param {string} name Name of the file | ||
| 346 | * @returns {Promise<HTMLElement>} Image element | 354 | * @returns {Promise<HTMLElement>} Image element |
| 347 | * @private | 355 | * @private |
| 348 | */ | 356 | */ |
| 349 | async getViewElement(url) { | 357 | async getViewElement(url, name) { |
| 350 | const img = document.createElement('img'); | 358 | const isVideo = VIDEO_EXTENSIONS.includes(name.split('.').pop()); |
| 351 | img.src = url; | 359 | const mediaElement = document.createElement(isVideo ? 'video' : 'img'); |
| 352 | img.classList.add('dataMaidImageView'); | 360 | if (mediaElement instanceof HTMLVideoElement) { |
| 353 | return img; | 361 | mediaElement.controls = true; |
| 362 | } | ||
| 363 | mediaElement.src = url; | ||
| 364 | mediaElement.classList.add('dataMaidImageView'); | ||
| 365 | return mediaElement; | ||
| 354 | } | 366 | } |
| 355 | 367 | ||
| 356 | /** | 368 | /** |
| @@ -0,0 +1,92 @@ | |||
| 1 | import { EventEmitter } from '../lib/eventemitter.js'; | ||
| 2 | |||
| 3 | export const event_types = { | ||
| 4 | APP_READY: 'app_ready', | ||
| 5 | EXTRAS_CONNECTED: 'extras_connected', | ||
| 6 | MESSAGE_SWIPED: 'message_swiped', | ||
| 7 | MESSAGE_SENT: 'message_sent', | ||
| 8 | MESSAGE_RECEIVED: 'message_received', | ||
| 9 | MESSAGE_EDITED: 'message_edited', | ||
| 10 | MESSAGE_DELETED: 'message_deleted', | ||
| 11 | MESSAGE_UPDATED: 'message_updated', | ||
| 12 | MESSAGE_FILE_EMBEDDED: 'message_file_embedded', | ||
| 13 | MESSAGE_REASONING_EDITED: 'message_reasoning_edited', | ||
| 14 | MESSAGE_REASONING_DELETED: 'message_reasoning_deleted', | ||
| 15 | MORE_MESSAGES_LOADED: 'more_messages_loaded', | ||
| 16 | IMPERSONATE_READY: 'impersonate_ready', | ||
| 17 | CHAT_CHANGED: 'chat_id_changed', | ||
| 18 | GENERATION_AFTER_COMMANDS: 'GENERATION_AFTER_COMMANDS', | ||
| 19 | GENERATION_STARTED: 'generation_started', | ||
| 20 | GENERATION_STOPPED: 'generation_stopped', | ||
| 21 | GENERATION_ENDED: 'generation_ended', | ||
| 22 | SD_PROMPT_PROCESSING: 'sd_prompt_processing', | ||
| 23 | EXTENSIONS_FIRST_LOAD: 'extensions_first_load', | ||
| 24 | EXTENSION_SETTINGS_LOADED: 'extension_settings_loaded', | ||
| 25 | SETTINGS_LOADED: 'settings_loaded', | ||
| 26 | SETTINGS_UPDATED: 'settings_updated', | ||
| 27 | GROUP_UPDATED: 'group_updated', | ||
| 28 | MOVABLE_PANELS_RESET: 'movable_panels_reset', | ||
| 29 | SETTINGS_LOADED_BEFORE: 'settings_loaded_before', | ||
| 30 | SETTINGS_LOADED_AFTER: 'settings_loaded_after', | ||
| 31 | CHATCOMPLETION_SOURCE_CHANGED: 'chatcompletion_source_changed', | ||
| 32 | CHATCOMPLETION_MODEL_CHANGED: 'chatcompletion_model_changed', | ||
| 33 | OAI_PRESET_CHANGED_BEFORE: 'oai_preset_changed_before', | ||
| 34 | OAI_PRESET_CHANGED_AFTER: 'oai_preset_changed_after', | ||
| 35 | OAI_PRESET_EXPORT_READY: 'oai_preset_export_ready', | ||
| 36 | OAI_PRESET_IMPORT_READY: 'oai_preset_import_ready', | ||
| 37 | WORLDINFO_SETTINGS_UPDATED: 'worldinfo_settings_updated', | ||
| 38 | WORLDINFO_UPDATED: 'worldinfo_updated', | ||
| 39 | CHARACTER_EDITED: 'character_edited', | ||
| 40 | CHARACTER_PAGE_LOADED: 'character_page_loaded', | ||
| 41 | CHARACTER_GROUP_OVERLAY_STATE_CHANGE_BEFORE: 'character_group_overlay_state_change_before', | ||
| 42 | CHARACTER_GROUP_OVERLAY_STATE_CHANGE_AFTER: 'character_group_overlay_state_change_after', | ||
| 43 | USER_MESSAGE_RENDERED: 'user_message_rendered', | ||
| 44 | CHARACTER_MESSAGE_RENDERED: 'character_message_rendered', | ||
| 45 | FORCE_SET_BACKGROUND: 'force_set_background', | ||
| 46 | CHAT_DELETED: 'chat_deleted', | ||
| 47 | CHAT_CREATED: 'chat_created', | ||
| 48 | GROUP_CHAT_DELETED: 'group_chat_deleted', | ||
| 49 | GROUP_CHAT_CREATED: 'group_chat_created', | ||
| 50 | GENERATE_BEFORE_COMBINE_PROMPTS: 'generate_before_combine_prompts', | ||
| 51 | GENERATE_AFTER_COMBINE_PROMPTS: 'generate_after_combine_prompts', | ||
| 52 | GENERATE_AFTER_DATA: 'generate_after_data', | ||
| 53 | GROUP_MEMBER_DRAFTED: 'group_member_drafted', | ||
| 54 | GROUP_WRAPPER_STARTED: 'group_wrapper_started', | ||
| 55 | GROUP_WRAPPER_FINISHED: 'group_wrapper_finished', | ||
| 56 | WORLD_INFO_ACTIVATED: 'world_info_activated', | ||
| 57 | TEXT_COMPLETION_SETTINGS_READY: 'text_completion_settings_ready', | ||
| 58 | CHAT_COMPLETION_SETTINGS_READY: 'chat_completion_settings_ready', | ||
| 59 | CHAT_COMPLETION_PROMPT_READY: 'chat_completion_prompt_ready', | ||
| 60 | CHARACTER_FIRST_MESSAGE_SELECTED: 'character_first_message_selected', | ||
| 61 | // TODO: Naming convention is inconsistent with other events | ||
| 62 | CHARACTER_DELETED: 'characterDeleted', | ||
| 63 | CHARACTER_DUPLICATED: 'character_duplicated', | ||
| 64 | CHARACTER_RENAMED: 'character_renamed', | ||
| 65 | CHARACTER_RENAMED_IN_PAST_CHAT: 'character_renamed_in_past_chat', | ||
| 66 | /** @deprecated The event is aliased to STREAM_TOKEN_RECEIVED. */ | ||
| 67 | SMOOTH_STREAM_TOKEN_RECEIVED: 'stream_token_received', | ||
| 68 | STREAM_TOKEN_RECEIVED: 'stream_token_received', | ||
| 69 | STREAM_REASONING_DONE: 'stream_reasoning_done', | ||
| 70 | FILE_ATTACHMENT_DELETED: 'file_attachment_deleted', | ||
| 71 | WORLDINFO_FORCE_ACTIVATE: 'worldinfo_force_activate', | ||
| 72 | OPEN_CHARACTER_LIBRARY: 'open_character_library', | ||
| 73 | ONLINE_STATUS_CHANGED: 'online_status_changed', | ||
| 74 | IMAGE_SWIPED: 'image_swiped', | ||
| 75 | CONNECTION_PROFILE_LOADED: 'connection_profile_loaded', | ||
| 76 | CONNECTION_PROFILE_CREATED: 'connection_profile_created', | ||
| 77 | CONNECTION_PROFILE_DELETED: 'connection_profile_deleted', | ||
| 78 | CONNECTION_PROFILE_UPDATED: 'connection_profile_updated', | ||
| 79 | TOOL_CALLS_PERFORMED: 'tool_calls_performed', | ||
| 80 | TOOL_CALLS_RENDERED: 'tool_calls_rendered', | ||
| 81 | CHARACTER_MANAGEMENT_DROPDOWN: 'charManagementDropdown', | ||
| 82 | SECRET_WRITTEN: 'secret_written', | ||
| 83 | SECRET_DELETED: 'secret_deleted', | ||
| 84 | SECRET_ROTATED: 'secret_rotated', | ||
| 85 | SECRET_EDITED: 'secret_edited', | ||
| 86 | PRESET_CHANGED: 'preset_changed', | ||
| 87 | PRESET_DELETED: 'preset_deleted', | ||
| 88 | MAIN_API_CHANGED: 'main_api_changed', | ||
| 89 | WORLDINFO_ENTRIES_LOADED: 'worldinfo_entries_loaded', | ||
| 90 | }; | ||
| 91 | |||
| 92 | export const eventSource = new EventEmitter([event_types.APP_READY]); | ||
| @@ -7,7 +7,7 @@ import { DOMPurify } from '../../../lib.js'; | |||
| 7 | import { getRequestHeaders, processDroppedFiles, eventSource, event_types } from '../../../script.js'; | 7 | import { getRequestHeaders, processDroppedFiles, eventSource, event_types } from '../../../script.js'; |
| 8 | import { deleteExtension, extensionNames, getContext, installExtension, renderExtensionTemplateAsync } from '../../extensions.js'; | 8 | import { deleteExtension, extensionNames, getContext, installExtension, renderExtensionTemplateAsync } from '../../extensions.js'; |
| 9 | import { POPUP_TYPE, Popup, callGenericPopup } from '../../popup.js'; | 9 | import { POPUP_TYPE, Popup, callGenericPopup } from '../../popup.js'; |
| 10 | import { executeSlashCommands } from '../../slash-commands.js'; | 10 | import { executeSlashCommandsWithOptions } from '../../slash-commands.js'; |
| 11 | import { accountStorage } from '../../util/AccountStorage.js'; | 11 | import { accountStorage } from '../../util/AccountStorage.js'; |
| 12 | import { flashHighlight, getStringHash, isValidUrl } from '../../utils.js'; | 12 | import { flashHighlight, getStringHash, isValidUrl } from '../../utils.js'; |
| 13 | import { t } from '../../i18n.js'; | 13 | import { t } from '../../i18n.js'; |
| @@ -142,7 +142,7 @@ async function downloadAssetsList(url) { | |||
| 142 | const assetDelete = async function () { | 142 | const assetDelete = async function () { |
| 143 | if (assetType === 'character') { | 143 | if (assetType === 'character') { |
| 144 | toastr.error('Go to the characters menu to delete a character.', 'Character deletion not supported'); | 144 | toastr.error('Go to the characters menu to delete a character.', 'Character deletion not supported'); |
| 145 | await executeSlashCommands(`/go ${asset['id']}`); | 145 | await executeSlashCommandsWithOptions(`/go ${asset['id']}`); |
| 146 | return; | 146 | return; |
| 147 | } | 147 | } |
| 148 | element.off('click'); | 148 | element.off('click'); |
| @@ -1,4 +1,4 @@ | |||
| 1 | import { ensureImageFormatSupported, getBase64Async, isTrueBoolean, saveBase64AsFile } from '../../utils.js'; | 1 | import { ensureImageFormatSupported, getBase64Async, getFileExtension, isTrueBoolean, saveBase64AsFile } from '../../utils.js'; |
| 2 | import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules, renderExtensionTemplateAsync } from '../../extensions.js'; | 2 | import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules, renderExtensionTemplateAsync } from '../../extensions.js'; |
| 3 | import { appendMediaToMessage, eventSource, event_types, getRequestHeaders, saveChatConditional, saveSettingsDebounced, substituteParamsExtended } from '../../../script.js'; | 3 | import { appendMediaToMessage, eventSource, event_types, getRequestHeaders, saveChatConditional, saveSettingsDebounced, substituteParamsExtended } from '../../../script.js'; |
| 4 | import { getMessageTimeStamp } from '../../RossAscends-mods.js'; | 4 | import { getMessageTimeStamp } from '../../RossAscends-mods.js'; |
| @@ -53,6 +53,10 @@ function migrateSettings() { | |||
| 53 | if (!extension_settings.caption.template) { | 53 | if (!extension_settings.caption.template) { |
| 54 | extension_settings.caption.template = TEMPLATE_DEFAULT; | 54 | extension_settings.caption.template = TEMPLATE_DEFAULT; |
| 55 | } | 55 | } |
| 56 | |||
| 57 | if (!extension_settings.caption.show_in_chat) { | ||
| 58 | extension_settings.caption.show_in_chat = false; | ||
| 59 | } | ||
| 56 | } | 60 | } |
| 57 | 61 | ||
| 58 | /** | 62 | /** |
| @@ -167,6 +171,7 @@ async function sendCaptionedMessage(caption, image) { | |||
| 167 | extra: { | 171 | extra: { |
| 168 | image: image, | 172 | image: image, |
| 169 | title: messageText, | 173 | title: messageText, |
| 174 | inline_image: !!extension_settings.caption.show_in_chat, | ||
| 170 | }, | 175 | }, |
| 171 | }; | 176 | }; |
| 172 | context.chat.push(message); | 177 | context.chat.push(message); |
| @@ -327,11 +332,11 @@ async function getCaptionForFile(file, prompt, quiet) { | |||
| 327 | setSpinnerIcon(); | 332 | setSpinnerIcon(); |
| 328 | const context = getContext(); | 333 | const context = getContext(); |
| 329 | const fileData = await getBase64Async(await ensureImageFormatSupported(file)); | 334 | const fileData = await getBase64Async(await ensureImageFormatSupported(file)); |
| 330 | const base64Format = fileData.split(',')[0].split(';')[0].split('/')[1]; | 335 | const extension = getFileExtension(file); |
| 331 | const base64Data = fileData.split(',')[1]; | 336 | const base64Data = fileData.split(',')[1]; |
| 332 | const { caption } = await doCaptionRequest(base64Data, fileData, prompt); | 337 | const { caption } = await doCaptionRequest(base64Data, fileData, prompt); |
| 333 | if (!quiet) { | 338 | if (!quiet) { |
| 334 | const imagePath = await saveBase64AsFile(base64Data, context.name2, '', base64Format); | 339 | const imagePath = await saveBase64AsFile(base64Data, context.name2, '', extension); |
| 335 | await sendCaptionedMessage(caption, imagePath); | 340 | await sendCaptionedMessage(caption, imagePath); |
| 336 | } | 341 | } |
| 337 | return caption; | 342 | return caption; |
| @@ -596,6 +601,10 @@ jQuery(async function () { | |||
| 596 | extension_settings.caption.alt_endpoint_enabled = !!$('#caption_altEndpoint_enabled').prop('checked'); | 601 | extension_settings.caption.alt_endpoint_enabled = !!$('#caption_altEndpoint_enabled').prop('checked'); |
| 597 | saveSettingsDebounced(); | 602 | saveSettingsDebounced(); |
| 598 | }); | 603 | }); |
| 604 | $('#caption_show_in_chat').prop('checked', !!(extension_settings.caption.show_in_chat)).on('input', () => { | ||
| 605 | extension_settings.caption.show_in_chat = !!$('#caption_show_in_chat').prop('checked'); | ||
| 606 | saveSettingsDebounced(); | ||
| 607 | }); | ||
| 599 | 608 | ||
| 600 | const onMessageEvent = async (index) => { | 609 | const onMessageEvent = async (index) => { |
| 601 | if (!extension_settings.caption.auto_mode) { | 610 | if (!extension_settings.caption.auto_mode) { |
| @@ -48,6 +48,7 @@ | |||
| 48 | <option data-type="mistral" value="pixtral-large-2411">pixtral-large-2411</option> | 48 | <option data-type="mistral" value="pixtral-large-2411">pixtral-large-2411</option> |
| 49 | <option data-type="mistral" value="mistral-large-pixtral-2411">mistral-large-pixtral-2411</option> | 49 | <option data-type="mistral" value="mistral-large-pixtral-2411">mistral-large-pixtral-2411</option> |
| 50 | <option data-type="mistral" value="mistral-small-2503">mistral-small-2503</option> | 50 | <option data-type="mistral" value="mistral-small-2503">mistral-small-2503</option> |
| 51 | <option data-type="mistral" value="mistral-small-2506">mistral-small-2506</option> | ||
| 51 | <option data-type="mistral" value="mistral-small-latest">mistral-small-latest</option> | 52 | <option data-type="mistral" value="mistral-small-latest">mistral-small-latest</option> |
| 52 | <option data-type="mistral" value="mistral-medium-latest">mistral-medium-latest</option> | 53 | <option data-type="mistral" value="mistral-medium-latest">mistral-medium-latest</option> |
| 53 | <option data-type="mistral" value="mistral-medium-2505">mistral-medium-2505</option> | 54 | <option data-type="mistral" value="mistral-medium-2505">mistral-medium-2505</option> |
| @@ -84,7 +85,6 @@ | |||
| 84 | <option data-type="anthropic" value="claude-3-5-haiku-latest">claude-3-5-haiku-latest</option> | 85 | <option data-type="anthropic" value="claude-3-5-haiku-latest">claude-3-5-haiku-latest</option> |
| 85 | <option data-type="anthropic" value="claude-3-5-haiku-20241022">claude-3-5-haiku-20241022</option> | 86 | <option data-type="anthropic" value="claude-3-5-haiku-20241022">claude-3-5-haiku-20241022</option> |
| 86 | <option data-type="anthropic" value="claude-3-opus-20240229">claude-3-opus-20240229</option> | 87 | <option data-type="anthropic" value="claude-3-opus-20240229">claude-3-opus-20240229</option> |
| 87 | <option data-type="anthropic" value="claude-3-sonnet-20240229">claude-3-sonnet-20240229</option> | ||
| 88 | <option data-type="anthropic" value="claude-3-haiku-20240307">claude-3-haiku-20240307</option> | 88 | <option data-type="anthropic" value="claude-3-haiku-20240307">claude-3-haiku-20240307</option> |
| 89 | <option data-type="google" value="gemini-2.5-pro">gemini-2.5-pro</option> | 89 | <option data-type="google" value="gemini-2.5-pro">gemini-2.5-pro</option> |
| 90 | <option data-type="google" value="gemini-2.5-pro-preview-06-05">gemini-2.5-pro-preview-06-05</option> | 90 | <option data-type="google" value="gemini-2.5-pro-preview-06-05">gemini-2.5-pro-preview-06-05</option> |
| @@ -189,6 +189,7 @@ | |||
| 189 | <option data-type="koboldcpp" value="koboldcpp_current" data-i18n="currently_loaded">[Currently loaded]</option> | 189 | <option data-type="koboldcpp" value="koboldcpp_current" data-i18n="currently_loaded">[Currently loaded]</option> |
| 190 | <option data-type="vllm" value="vllm_current" data-i18n="currently_selected">[Currently selected]</option> | 190 | <option data-type="vllm" value="vllm_current" data-i18n="currently_selected">[Currently selected]</option> |
| 191 | <option data-type="custom" value="custom_current" data-i18n="currently_selected">[Currently selected]</option> | 191 | <option data-type="custom" value="custom_current" data-i18n="currently_selected">[Currently selected]</option> |
| 192 | <option data-type="xai" value="grok-4-0709">grok-4-0709</option> | ||
| 192 | <option data-type="xai" value="grok-2-vision-1212">grok-2-vision-1212</option> | 193 | <option data-type="xai" value="grok-2-vision-1212">grok-2-vision-1212</option> |
| 193 | <option data-type="xai" value="grok-vision-beta">grok-vision-beta</option> | 194 | <option data-type="xai" value="grok-vision-beta">grok-vision-beta</option> |
| 194 | <option data-type="pollinations" value="openai">openai</option> | 195 | <option data-type="pollinations" value="openai">openai</option> |
| @@ -228,23 +229,28 @@ | |||
| 228 | </div> | 229 | </div> |
| 229 | <div id="caption_prompt_block"> | 230 | <div id="caption_prompt_block"> |
| 230 | <label for="caption_prompt" data-i18n="Caption Prompt">Caption Prompt</label> | 231 | <label for="caption_prompt" data-i18n="Caption Prompt">Caption Prompt</label> |
| 231 | <textarea id="caption_prompt" class="text_pole" rows="1" placeholder="< Use default >">{{PROMPT_DEFAULT}}</textarea> | 232 | <textarea id="caption_prompt" class="text_pole textarea_compact autoSetHeight" rows="1" placeholder="< Use default >">{{PROMPT_DEFAULT}}</textarea> |
| 232 | <label class="checkbox_label margin-bot-10px" for="caption_prompt_ask" title="Ask for a custom prompt every time an image is captioned."> | 233 | <label class="checkbox_label margin-bot-10px" for="caption_prompt_ask" title="Ask for a custom prompt every time an image is captioned."> |
| 233 | <input id="caption_prompt_ask" type="checkbox" class="checkbox"> | 234 | <input id="caption_prompt_ask" type="checkbox" class="checkbox"> |
| 234 | <span data-i18n="Ask every time">Ask every time</span> | 235 | <span data-i18n="Ask every time">Ask every time</span> |
| 235 | </label> | 236 | </label> |
| 236 | </div> | 237 | </div> |
| 237 | <label for="caption_template"><span data-i18n="Message Template">Message Template</span> <small><span data-i18n="(use _space">(use </span> <code>{{caption}}</code> <span data-i18n="macro)">macro)</span></small></label> | 238 | <label for="caption_template"><span data-i18n="Message Template">Message Template</span> <small><span data-i18n="(use _space">(use </span> <code>{{caption}}</code> <span data-i18n="macro)">macro)</span></small></label> |
| 238 | <textarea id="caption_template" class="text_pole" rows="2" placeholder="< Use default >">{{TEMPLATE_DEFAULT}}</textarea> | 239 | <textarea id="caption_template" class="text_pole textarea_compact autoSetHeight" rows="2" placeholder="< Use default >">{{TEMPLATE_DEFAULT}}</textarea> |
| 239 | <label class="checkbox_label" for="caption_auto_mode"> | 240 | <label class="checkbox_label" for="caption_auto_mode"> |
| 240 | <input id="caption_auto_mode" type="checkbox" class="checkbox"> | 241 | <input id="caption_auto_mode" type="checkbox" class="checkbox"> |
| 241 | <span data-i18n="Automatically caption images">Automatically caption images</span> | 242 | <span data-i18n="Automatically caption images">Automatically caption images</span> |
| 242 | <i class="fa-solid fa-info-circle" title="Automatically caption images when they are pasted into the chat or attached to messages."></i> | 243 | <i class="fa-solid fa-info-circle" title="Automatically caption images when they are pasted into the chat or attached to messages."></i> |
| 243 | </label> | 244 | </label> |
| 244 | <label class="checkbox_label margin-bot-10px" for="caption_refine_mode"> | 245 | <label class="checkbox_label" for="caption_refine_mode"> |
| 245 | <input id="caption_refine_mode" type="checkbox" class="checkbox"> | 246 | <input id="caption_refine_mode" type="checkbox" class="checkbox"> |
| 246 | <span data-i18n="Edit captions before saving">Edit captions before saving</span> | 247 | <span data-i18n="Edit captions before saving">Edit captions before saving</span> |
| 247 | </label> | 248 | </label> |
| 249 | <label class="checkbox_label" for="caption_show_in_chat"> | ||
| 250 | <input id="caption_show_in_chat" type="checkbox" class="checkbox"> | ||
| 251 | <span data-i18n="Show captions in chat">Show captions in chat</span> | ||
| 252 | </label> | ||
| 253 | <div class="margin-bot-10px"></div> | ||
| 248 | </div> | 254 | </div> |
| 249 | </div> | 255 | </div> |
| 250 | </div> | 256 | </div> |
| @@ -1,6 +1,6 @@ | |||
| 1 | import { Fuse } from '../../../lib.js'; | 1 | import { Fuse } from '../../../lib.js'; |
| 2 | 2 | ||
| 3 | import { characters, eventSource, event_types, generateQuietPrompt, generateRaw, getRequestHeaders, main_api, online_status, saveSettingsDebounced, substituteParams, substituteParamsExtended, system_message_types, this_chid } from '../../../script.js'; | 3 | import { characters, eventSource, event_types, generateQuietPrompt, generateRaw, getRequestHeaders, online_status, saveSettingsDebounced, substituteParams, substituteParamsExtended, system_message_types, this_chid } from '../../../script.js'; |
| 4 | import { dragElement, isMobile } from '../../RossAscends-mods.js'; | 4 | import { dragElement, isMobile } from '../../RossAscends-mods.js'; |
| 5 | import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js'; | 5 | import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplateAsync } from '../../extensions.js'; |
| 6 | import { loadMovingUIState, performFuzzySearch, power_user } from '../../power-user.js'; | 6 | import { loadMovingUIState, performFuzzySearch, power_user } from '../../power-user.js'; |
| @@ -1056,10 +1056,10 @@ export async function getExpressionLabel(text, expressionsApi = extension_settin | |||
| 1056 | inApiCall = true; | 1056 | inApiCall = true; |
| 1057 | switch (extension_settings.expressions.promptType) { | 1057 | switch (extension_settings.expressions.promptType) { |
| 1058 | case PROMPT_TYPE.raw: | 1058 | case PROMPT_TYPE.raw: |
| 1059 | emotionResponse = await generateRaw(text, main_api, false, false, prompt); | 1059 | emotionResponse = await generateRaw({ prompt: text, systemPrompt: prompt }); |
| 1060 | break; | 1060 | break; |
| 1061 | case PROMPT_TYPE.full: | 1061 | case PROMPT_TYPE.full: |
| 1062 | emotionResponse = await generateQuietPrompt(prompt, false, false); | 1062 | emotionResponse = await generateQuietPrompt({ quietPrompt: prompt }); |
| 1063 | break; | 1063 | break; |
| 1064 | } | 1064 | } |
| 1065 | } finally { | 1065 | } finally { |
| @@ -1331,7 +1331,7 @@ async function renderFallbackExpressionPicker() { | |||
| 1331 | defaultPicker.empty(); | 1331 | defaultPicker.empty(); |
| 1332 | 1332 | ||
| 1333 | 1333 | ||
| 1334 | addOption(OPTION_NO_FALLBACK, '[ No fallback ]', !extension_settings.expressions.fallback_expression); | 1334 | addOption(OPTION_NO_FALLBACK, '[ No fallback ]', !extension_settings.expressions.fallback_expression && !extension_settings.expressions.showDefault); |
| 1335 | addOption(OPTION_EMOJI_FALLBACK, '[ Default emojis ]', !!extension_settings.expressions.showDefault); | 1335 | addOption(OPTION_EMOJI_FALLBACK, '[ Default emojis ]', !!extension_settings.expressions.showDefault); |
| 1336 | 1336 | ||
| 1337 | for (const expression of expressions) { | 1337 | for (const expression of expressions) { |
| @@ -1758,27 +1758,38 @@ async function onExpressionFallbackChanged() { | |||
| 1758 | saveSettingsDebounced(); | 1758 | saveSettingsDebounced(); |
| 1759 | } | 1759 | } |
| 1760 | 1760 | ||
| 1761 | /** | ||
| 1762 | * Handles the file upload process for a sprite image. | ||
| 1763 | * @param {string} url URL to upload the file to | ||
| 1764 | * @param {FormData} formData FormData object containing the file and other data to upload | ||
| 1765 | * @returns {Promise<any>} - The response data from the server | ||
| 1766 | */ | ||
| 1761 | async function handleFileUpload(url, formData) { | 1767 | async function handleFileUpload(url, formData) { |
| 1762 | try { | 1768 | try { |
| 1763 | const data = await jQuery.ajax({ | 1769 | const result = await fetch(url, { |
| 1764 | type: 'POST', | 1770 | method: 'POST', |
| 1765 | url: url, | 1771 | headers: getRequestHeaders({ omitContentType: true }), |
| 1766 | data: formData, | 1772 | body: formData, |
| 1767 | beforeSend: function () { }, | 1773 | cache: 'no-cache', |
| 1768 | cache: false, | ||
| 1769 | contentType: false, | ||
| 1770 | processData: false, | ||
| 1771 | }); | 1774 | }); |
| 1772 | 1775 | ||
| 1776 | if (!result.ok) { | ||
| 1777 | throw new Error(`Upload failed with status ${result.status}`); | ||
| 1778 | } | ||
| 1779 | |||
| 1780 | const data = await result.json(); | ||
| 1781 | |||
| 1773 | // Refresh sprites list | 1782 | // Refresh sprites list |
| 1774 | const name = formData.get('name'); | 1783 | const name = formData.get('name').toString(); |
| 1775 | delete spriteCache[name]; | 1784 | delete spriteCache[name]; |
| 1776 | await fetchImagesNoCache(); | 1785 | await fetchImagesNoCache(); |
| 1777 | await validateImages(name); | 1786 | await validateImages(name); |
| 1778 | 1787 | ||
| 1779 | return data; | 1788 | return data ?? {}; |
| 1780 | } catch (error) { | 1789 | } catch (error) { |
| 1790 | console.error('Error uploading image:', error); | ||
| 1781 | toastr.error('Failed to upload image'); | 1791 | toastr.error('Failed to upload image'); |
| 1792 | return {}; | ||
| 1782 | } | 1793 | } |
| 1783 | } | 1794 | } |
| 1784 | 1795 | ||
| @@ -1996,7 +2007,11 @@ async function onClickExpressionUploadPackButton() { | |||
| 1996 | const uploadToast = toastr.info('Please wait...', 'Upload is processing', { timeOut: 0, extendedTimeOut: 0 }); | 2007 | const uploadToast = toastr.info('Please wait...', 'Upload is processing', { timeOut: 0, extendedTimeOut: 0 }); |
| 1997 | const { count } = await handleFileUpload('/api/sprites/upload-zip', formData); | 2008 | const { count } = await handleFileUpload('/api/sprites/upload-zip', formData); |
| 1998 | toastr.clear(uploadToast); | 2009 | toastr.clear(uploadToast); |
| 1999 | toastr.success(`Uploaded ${count} image(s) for ${name}`); | 2010 | |
| 2011 | // Only show success message if at least one image was uploaded | ||
| 2012 | if (count) { | ||
| 2013 | toastr.success(`Uploaded ${count} image(s) for ${name}`); | ||
| 2014 | } | ||
| 2000 | 2015 | ||
| 2001 | // Reset the input | 2016 | // Reset the input |
| 2002 | e.target.form.reset(); | 2017 | e.target.form.reset(); |
| @@ -2116,7 +2131,7 @@ function migrateSettings() { | |||
| 2116 | saveSettingsDebounced(); | 2131 | saveSettingsDebounced(); |
| 2117 | } | 2132 | } |
| 2118 | 2133 | ||
| 2119 | if (extension_settings.expressions.showDefault && extension_settings.expressions.fallback_expression !== undefined) { | 2134 | if (extension_settings.expressions.showDefault && extension_settings.expressions.fallback_expression) { |
| 2120 | extension_settings.expressions.showDefault = false; | 2135 | extension_settings.expressions.showDefault = false; |
| 2121 | saveSettingsDebounced(); | 2136 | saveSettingsDebounced(); |
| 2122 | } | 2137 | } |
| @@ -8,7 +8,7 @@ import { | |||
| 8 | animation_easing, | 8 | animation_easing, |
| 9 | } from '../../../script.js'; | 9 | } from '../../../script.js'; |
| 10 | import { groups, selected_group } from '../../group-chats.js'; | 10 | import { groups, selected_group } from '../../group-chats.js'; |
| 11 | import { loadFileToDocument, delay, getBase64Async, getSanitizedFilename } from '../../utils.js'; | 11 | import { loadFileToDocument, delay, getBase64Async, getSanitizedFilename, saveBase64AsFile, getFileExtension } from '../../utils.js'; |
| 12 | import { loadMovingUIState } from '../../power-user.js'; | 12 | import { loadMovingUIState } from '../../power-user.js'; |
| 13 | import { dragElement } from '../../RossAscends-mods.js'; | 13 | import { dragElement } from '../../RossAscends-mods.js'; |
| 14 | import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; | 14 | import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js'; |
| @@ -324,7 +324,6 @@ async function showCharGallery(deleteModeState = false) { | |||
| 324 | await delay(100); | 324 | await delay(100); |
| 325 | await initGallery(items, url); | 325 | await initGallery(items, url); |
| 326 | } catch (err) { | 326 | } catch (err) { |
| 327 | console.trace(); | ||
| 328 | console.error(err); | 327 | console.error(err); |
| 329 | } | 328 | } |
| 330 | } | 329 | } |
| @@ -341,27 +340,12 @@ async function showCharGallery(deleteModeState = false) { | |||
| 341 | async function uploadFile(file, url) { | 340 | async function uploadFile(file, url) { |
| 342 | try { | 341 | try { |
| 343 | // Convert the file to a base64 string | 342 | // Convert the file to a base64 string |
| 344 | const base64Data = await getBase64Async(file); | 343 | const fileBase64 = await getBase64Async(file); |
| 344 | const base64Data = fileBase64.split(',')[1]; | ||
| 345 | const extension = getFileExtension(file); | ||
| 346 | const path = await saveBase64AsFile(base64Data, url, '', extension); | ||
| 345 | 347 | ||
| 346 | // Create the payload | 348 | toastr.success(t`File uploaded successfully. Saved at: ${path}`); |
| 347 | const payload = { | ||
| 348 | image: base64Data, | ||
| 349 | ch_name: url, | ||
| 350 | }; | ||
| 351 | |||
| 352 | const response = await fetch('/api/images/upload', { | ||
| 353 | method: 'POST', | ||
| 354 | headers: getRequestHeaders(), | ||
| 355 | body: JSON.stringify(payload), | ||
| 356 | }); | ||
| 357 | |||
| 358 | if (!response.ok) { | ||
| 359 | throw new Error(`HTTP error! Status: ${response.status}`); | ||
| 360 | } | ||
| 361 | |||
| 362 | const result = await response.json(); | ||
| 363 | |||
| 364 | toastr.success(t`File uploaded successfully. Saved at: ${result.path}`); | ||
| 365 | } catch (error) { | 349 | } catch (error) { |
| 366 | console.error('There was an issue uploading the file:', error); | 350 | console.error('There was an issue uploading the file:', error); |
| 367 | 351 | ||
| @@ -620,6 +604,9 @@ function makeDragImg(id, url) { | |||
| 620 | } | 604 | } |
| 621 | draggableElem.id = uniqueId; | 605 | draggableElem.id = uniqueId; |
| 622 | 606 | ||
| 607 | // Add the galleryImageDraggable to have unique class | ||
| 608 | draggableElem.classList.add('galleryImageDraggable'); | ||
| 609 | |||
| 623 | // Ensure that the newly added element is displayed as block | 610 | // Ensure that the newly added element is displayed as block |
| 624 | draggableElem.style.display = 'block'; | 611 | draggableElem.style.display = 'block'; |
| 625 | //and has no padding unlike other non-zoomed-avatar draggables | 612 | //and has no padding unlike other non-zoomed-avatar draggables |
| @@ -749,7 +736,6 @@ async function listGalleryCommand(args) { | |||
| 749 | return JSON.stringify(items.map(it => it.src)); | 736 | return JSON.stringify(items.map(it => it.src)); |
| 750 | 737 | ||
| 751 | } catch (err) { | 738 | } catch (err) { |
| 752 | console.trace(); | ||
| 753 | console.error(err); | 739 | console.error(err); |
| 754 | } | 740 | } |
| 755 | return JSON.stringify([]); | 741 | return JSON.stringify([]); |
| @@ -506,7 +506,7 @@ async function summarizeCallback(args, text) { | |||
| 506 | case summary_sources.extras: | 506 | case summary_sources.extras: |
| 507 | return await callExtrasSummarizeAPI(text); | 507 | return await callExtrasSummarizeAPI(text); |
| 508 | case summary_sources.main: | 508 | case summary_sources.main: |
| 509 | return removeReasoningFromString(await generateRaw(text, '', false, false, prompt, extension_settings.memory.overrideResponseLength)); | 509 | return removeReasoningFromString(await generateRaw({ prompt: text, systemPrompt: prompt, responseLength: extension_settings.memory.overrideResponseLength })); |
| 510 | case summary_sources.webllm: { | 510 | case summary_sources.webllm: { |
| 511 | const messages = [{ role: 'system', content: prompt }, { role: 'user', content: text }].filter(m => m.content); | 511 | const messages = [{ role: 'system', content: prompt }, { role: 'user', content: text }].filter(m => m.content); |
| 512 | const params = extension_settings.memory.overrideResponseLength > 0 ? { max_tokens: extension_settings.memory.overrideResponseLength } : {}; | 512 | const params = extension_settings.memory.overrideResponseLength > 0 ? { max_tokens: extension_settings.memory.overrideResponseLength } : {}; |
| @@ -677,7 +677,13 @@ async function summarizeChatMain(context, force, skipWIAN) { | |||
| 677 | if (prompt_builders.DEFAULT === extension_settings.memory.prompt_builder) { | 677 | if (prompt_builders.DEFAULT === extension_settings.memory.prompt_builder) { |
| 678 | try { | 678 | try { |
| 679 | inApiCall = true; | 679 | inApiCall = true; |
| 680 | summary = await generateQuietPrompt(prompt, false, skipWIAN, '', '', extension_settings.memory.overrideResponseLength); | 680 | /** @type {import('../../../script.js').GenerateQuietPromptParams} */ |
| 681 | const params = { | ||
| 682 | quietPrompt: prompt, | ||
| 683 | skipWIAN: skipWIAN, | ||
| 684 | responseLength: extension_settings.memory.overrideResponseLength, | ||
| 685 | }; | ||
| 686 | summary = await generateQuietPrompt(params); | ||
| 681 | } finally { | 687 | } finally { |
| 682 | inApiCall = false; | 688 | inApiCall = false; |
| 683 | } | 689 | } |
| @@ -701,7 +707,13 @@ async function summarizeChatMain(context, force, skipWIAN) { | |||
| 701 | return null; | 707 | return null; |
| 702 | } | 708 | } |
| 703 | 709 | ||
| 704 | const rawSummary = await generateRaw(rawPrompt, '', false, false, prompt, extension_settings.memory.overrideResponseLength); | 710 | /** @type {import('../../../script.js').GenerateRawParams} */ |
| 711 | const params = { | ||
| 712 | prompt: rawPrompt, | ||
| 713 | systemPrompt: prompt, | ||
| 714 | responseLength: extension_settings.memory.overrideResponseLength, | ||
| 715 | }; | ||
| 716 | const rawSummary = await generateRaw(params); | ||
| 705 | summary = removeReasoningFromString(rawSummary); | 717 | summary = removeReasoningFromString(rawSummary); |
| 706 | index = lastUsedIndex; | 718 | index = lastUsedIndex; |
| 707 | } finally { | 719 | } finally { |
| @@ -1,5 +1,5 @@ | |||
| 1 | import { chat_metadata, saveChatDebounced, saveSettingsDebounced } from '../../../../script.js'; | 1 | import { chat_metadata, saveSettingsDebounced } from '../../../../script.js'; |
| 2 | import { extension_settings } from '../../../extensions.js'; | 2 | import { extension_settings, saveMetadataDebounced } from '../../../extensions.js'; |
| 3 | import { QuickReplyConfig } from './QuickReplyConfig.js'; | 3 | import { QuickReplyConfig } from './QuickReplyConfig.js'; |
| 4 | 4 | ||
| 5 | export class QuickReplySettings { | 5 | export class QuickReplySettings { |
| @@ -62,7 +62,7 @@ export class QuickReplySettings { | |||
| 62 | saveSettingsDebounced(); | 62 | saveSettingsDebounced(); |
| 63 | if (this.chatConfig) { | 63 | if (this.chatConfig) { |
| 64 | chat_metadata.quickReply = this.chatConfig.toJSON(); | 64 | chat_metadata.quickReply = this.chatConfig.toJSON(); |
| 65 | saveChatDebounced(); | 65 | saveMetadataDebounced(); |
| 66 | } | 66 | } |
| 67 | if (this.onSave) { | 67 | if (this.onSave) { |
| 68 | this.onSave(); | 68 | this.onSave(); |
| @@ -375,6 +375,11 @@ function migrateSettings() { | |||
| 375 | performSave = true; | 375 | performSave = true; |
| 376 | } | 376 | } |
| 377 | 377 | ||
| 378 | if (!Array.isArray(script.placement)) { | ||
| 379 | script.placement = []; | ||
| 380 | performSave = true; | ||
| 381 | } | ||
| 382 | |||
| 378 | if (script.placement.includes(regex_placement.MD_DISPLAY)) { | 383 | if (script.placement.includes(regex_placement.MD_DISPLAY)) { |
| 379 | script.placement = script.placement.length === 1 ? | 384 | script.placement = script.placement.length === 1 ? |
| 380 | Object.values(regex_placement).filter((e) => e !== regex_placement.MD_DISPLAY) : | 385 | Object.values(regex_placement).filter((e) => e !== regex_placement.MD_DISPLAY) : |
| @@ -38,10 +38,14 @@ export async function getMultimodalCaption(base64Img, prompt) { | |||
| 38 | const isVllm = extension_settings.caption.multimodal_api === 'vllm'; | 38 | const isVllm = extension_settings.caption.multimodal_api === 'vllm'; |
| 39 | const base64Bytes = base64Img.length * 0.75; | 39 | const base64Bytes = base64Img.length * 0.75; |
| 40 | const compressionLimit = 2 * 1024 * 1024; | 40 | const compressionLimit = 2 * 1024 * 1024; |
| 41 | const safeMimeTypes = ['image/jpeg', 'image/png', 'image/webp']; | ||
| 42 | const mimeType = base64Img?.split(';')?.[0]?.split(':')?.[1]; | ||
| 41 | const thumbnailNeeded = ['google', 'openrouter', 'mistral', 'groq', 'vertexai'].includes(extension_settings.caption.multimodal_api); | 43 | const thumbnailNeeded = ['google', 'openrouter', 'mistral', 'groq', 'vertexai'].includes(extension_settings.caption.multimodal_api); |
| 42 | if ((thumbnailNeeded && base64Bytes > compressionLimit) || isOoba || isKoboldCpp) { | 44 | if ((thumbnailNeeded && base64Bytes > compressionLimit) || isOoba || isKoboldCpp) { |
| 43 | const maxSide = 1024; | 45 | const maxSide = 2048; |
| 44 | base64Img = await createThumbnail(base64Img, maxSide, maxSide, 'image/jpeg'); | 46 | base64Img = await createThumbnail(base64Img, maxSide, maxSide); |
| 47 | } else if (!safeMimeTypes.includes(mimeType)) { | ||
| 48 | base64Img = await createThumbnail(base64Img, null, null); | ||
| 45 | } | 49 | } |
| 46 | 50 | ||
| 47 | const proxyUrl = useReverseProxy ? oai_settings.reverse_proxy : ''; | 51 | const proxyUrl = useReverseProxy ? oai_settings.reverse_proxy : ''; |
| @@ -467,7 +471,7 @@ export class ConnectionManagerRequestService { | |||
| 467 | 471 | ||
| 468 | /** | 472 | /** |
| 469 | * @param {import('./connection-manager/index.js').ConnectionProfile?} [profile] | 473 | * @param {import('./connection-manager/index.js').ConnectionProfile?} [profile] |
| 470 | * @return {import('../../script.js').ConnectAPIMap} | 474 | * @return {import('../slash-commands.js').ConnectAPIMap} |
| 471 | * @throws {Error} | 475 | * @throws {Error} |
| 472 | */ | 476 | */ |
| 473 | static validateProfile(profile) { | 477 | static validateProfile(profile) { |
| @@ -51,7 +51,7 @@ import { | |||
| 51 | SlashCommandArgument, | 51 | SlashCommandArgument, |
| 52 | SlashCommandNamedArgument, | 52 | SlashCommandNamedArgument, |
| 53 | } from '../../slash-commands/SlashCommandArgument.js'; | 53 | } from '../../slash-commands/SlashCommandArgument.js'; |
| 54 | import { debounce_timeout } from '../../constants.js'; | 54 | import { debounce_timeout, VIDEO_EXTENSIONS } from '../../constants.js'; |
| 55 | import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js'; | 55 | import { SlashCommandEnumValue } from '../../slash-commands/SlashCommandEnumValue.js'; |
| 56 | import { callGenericPopup, Popup, POPUP_TYPE } from '../../popup.js'; | 56 | import { callGenericPopup, Popup, POPUP_TYPE } from '../../popup.js'; |
| 57 | import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; | 57 | import { commonEnumProviders } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; |
| @@ -339,6 +339,7 @@ const defaultSettings = { | |||
| 339 | }; | 339 | }; |
| 340 | 340 | ||
| 341 | const writePromptFieldsDebounced = debounce(writePromptFields, debounce_timeout.relaxed); | 341 | const writePromptFieldsDebounced = debounce(writePromptFields, debounce_timeout.relaxed); |
| 342 | const isVideo = (/** @type {string} */ format) => VIDEO_EXTENSIONS.includes(String(format || '').trim().toLowerCase()); | ||
| 342 | 343 | ||
| 343 | /** | 344 | /** |
| 344 | * Generate interceptor for interactive mode triggers. | 345 | * Generate interceptor for interactive mode triggers. |
| @@ -2476,14 +2477,14 @@ async function generatePicture(initiator, args, trigger, message, callback) { | |||
| 2476 | 2477 | ||
| 2477 | if (generationType === generationMode.BACKGROUND) { | 2478 | if (generationType === generationMode.BACKGROUND) { |
| 2478 | const callbackOriginal = callback; | 2479 | const callbackOriginal = callback; |
| 2479 | callback = async function (prompt, imagePath, generationType, _negativePromptPrefix, _initiator, prefixedPrompt) { | 2480 | callback = async function (prompt, imagePath, generationType, _negativePromptPrefix, _initiator, prefixedPrompt, format) { |
| 2480 | const imgUrl = `url("${encodeURI(imagePath)}")`; | 2481 | const imgUrl = `url("${encodeURI(imagePath)}")`; |
| 2481 | await eventSource.emit(event_types.FORCE_SET_BACKGROUND, { url: imgUrl, path: imagePath }); | 2482 | await eventSource.emit(event_types.FORCE_SET_BACKGROUND, { url: imgUrl, path: imagePath }); |
| 2482 | 2483 | ||
| 2483 | if (typeof callbackOriginal === 'function') { | 2484 | if (typeof callbackOriginal === 'function') { |
| 2484 | await callbackOriginal(prompt, imagePath, generationType, negativePromptPrefix, initiator, prefixedPrompt); | 2485 | await callbackOriginal(prompt, imagePath, generationType, negativePromptPrefix, initiator, prefixedPrompt, format); |
| 2485 | } else { | 2486 | } else { |
| 2486 | await sendMessage(prompt, imagePath, generationType, negativePromptPrefix, initiator, prefixedPrompt); | 2487 | await sendMessage(prompt, imagePath, generationType, negativePromptPrefix, initiator, prefixedPrompt, format); |
| 2487 | } | 2488 | } |
| 2488 | }; | 2489 | }; |
| 2489 | } | 2490 | } |
| @@ -2725,7 +2726,7 @@ function getUserAvatarUrl() { | |||
| 2725 | * @returns {Promise<string>} - A promise that resolves when the prompt generation completes. | 2726 | * @returns {Promise<string>} - A promise that resolves when the prompt generation completes. |
| 2726 | */ | 2727 | */ |
| 2727 | async function generatePrompt(quietPrompt) { | 2728 | async function generatePrompt(quietPrompt) { |
| 2728 | const reply = await generateQuietPrompt(quietPrompt, false, false); | 2729 | const reply = await generateQuietPrompt({ quietPrompt }); |
| 2729 | const processedReply = processReply(reply); | 2730 | const processedReply = processReply(reply); |
| 2730 | 2731 | ||
| 2731 | if (!processedReply) { | 2732 | if (!processedReply) { |
| @@ -2838,8 +2839,8 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP | |||
| 2838 | const filename = `${characterName}_${humanizedDateTime()}`; | 2839 | const filename = `${characterName}_${humanizedDateTime()}`; |
| 2839 | const base64Image = await saveBase64AsFile(result.data, characterName, filename, result.format); | 2840 | const base64Image = await saveBase64AsFile(result.data, characterName, filename, result.format); |
| 2840 | callback | 2841 | callback |
| 2841 | ? await callback(prompt, base64Image, generationType, additionalNegativePrefix, initiator, prefixedPrompt) | 2842 | ? await callback(prompt, base64Image, generationType, additionalNegativePrefix, initiator, prefixedPrompt, result.format) |
| 2842 | : await sendMessage(prompt, base64Image, generationType, additionalNegativePrefix, initiator, prefixedPrompt); | 2843 | : await sendMessage(prompt, base64Image, generationType, additionalNegativePrefix, initiator, prefixedPrompt, result.format); |
| 2843 | return base64Image; | 2844 | return base64Image; |
| 2844 | } | 2845 | } |
| 2845 | 2846 | ||
| @@ -3524,7 +3525,8 @@ async function generateComfyImage(prompt, negativePrompt, signal) { | |||
| 3524 | const text = await promptResult.text(); | 3525 | const text = await promptResult.text(); |
| 3525 | throw new Error(text); | 3526 | throw new Error(text); |
| 3526 | } | 3527 | } |
| 3527 | return { format: 'png', data: await promptResult.text() }; | 3528 | const { format, data } = await promptResult.json(); |
| 3529 | return { format, data }; | ||
| 3528 | } | 3530 | } |
| 3529 | 3531 | ||
| 3530 | 3532 | ||
| @@ -3864,8 +3866,9 @@ async function onComfyDeleteWorkflowClick() { | |||
| 3864 | * @param {string} additionalNegativePrefix Additional negative prompt used for the image generation | 3866 | * @param {string} additionalNegativePrefix Additional negative prompt used for the image generation |
| 3865 | * @param {string} initiator The initiator of the image generation | 3867 | * @param {string} initiator The initiator of the image generation |
| 3866 | * @param {string} prefixedPrompt Prompt with an attached specific prefix | 3868 | * @param {string} prefixedPrompt Prompt with an attached specific prefix |
| 3869 | * @param {string} format Format of the image (e.g., 'png', 'jpg') | ||
| 3867 | */ | 3870 | */ |
| 3868 | async function sendMessage(prompt, image, generationType, additionalNegativePrefix, initiator, prefixedPrompt) { | 3871 | async function sendMessage(prompt, image, generationType, additionalNegativePrefix, initiator, prefixedPrompt, format) { |
| 3869 | const context = getContext(); | 3872 | const context = getContext(); |
| 3870 | const name = context.groupId ? systemUserName : context.name2; | 3873 | const name = context.groupId ? systemUserName : context.name2; |
| 3871 | const template = extension_settings.sd.prompts[generationMode.MESSAGE] || '{{prompt}}'; | 3874 | const template = extension_settings.sd.prompts[generationMode.MESSAGE] || '{{prompt}}'; |
| @@ -3885,6 +3888,12 @@ async function sendMessage(prompt, image, generationType, additionalNegativePref | |||
| 3885 | image_swipes: [image], | 3888 | image_swipes: [image], |
| 3886 | }, | 3889 | }, |
| 3887 | }; | 3890 | }; |
| 3891 | if (isVideo(format)) { | ||
| 3892 | message.extra.video = image; | ||
| 3893 | delete message.extra.image; | ||
| 3894 | delete message.extra.image_swipes; | ||
| 3895 | delete message.extra.inline_image; | ||
| 3896 | } | ||
| 3888 | context.chat.push(message); | 3897 | context.chat.push(message); |
| 3889 | const messageId = context.chat.length - 1; | 3898 | const messageId = context.chat.length - 1; |
| 3890 | await eventSource.emit(event_types.MESSAGE_RECEIVED, messageId, 'extension'); | 3899 | await eventSource.emit(event_types.MESSAGE_RECEIVED, messageId, 'extension'); |
| @@ -4068,7 +4077,7 @@ async function sdMessageButton(e) { | |||
| 4068 | } | 4077 | } |
| 4069 | } | 4078 | } |
| 4070 | 4079 | ||
| 4071 | function saveGeneratedImage(prompt, image, generationType, negative) { | 4080 | function saveGeneratedImage(prompt, image, generationType, negative, _initiator, _prefixedPrompt, format) { |
| 4072 | // Some message sources may not create the extra object | 4081 | // Some message sources may not create the extra object |
| 4073 | if (typeof message.extra !== 'object' || message.extra === null) { | 4082 | if (typeof message.extra !== 'object' || message.extra === null) { |
| 4074 | message.extra = {}; | 4083 | message.extra = {}; |
| @@ -4085,17 +4094,24 @@ async function sdMessageButton(e) { | |||
| 4085 | swipes.push(message.extra.image); | 4094 | swipes.push(message.extra.image); |
| 4086 | } | 4095 | } |
| 4087 | 4096 | ||
| 4088 | swipes.push(image); | 4097 | const isVideoFormat = isVideo(format); |
| 4098 | |||
| 4099 | if (isVideoFormat) { | ||
| 4100 | message.extra.video = image; | ||
| 4101 | } else { | ||
| 4102 | swipes.push(image); | ||
| 4103 | |||
| 4104 | // If already contains an image and it's not inline - leave it as is | ||
| 4105 | message.extra.inline_image = !(message.extra.image && !message.extra.inline_image); | ||
| 4106 | message.extra.image = image; | ||
| 4107 | } | ||
| 4089 | 4108 | ||
| 4090 | // If already contains an image and it's not inline - leave it as is | ||
| 4091 | message.extra.inline_image = !(message.extra.image && !message.extra.inline_image); | ||
| 4092 | message.extra.image = image; | ||
| 4093 | message.extra.title = prompt; | 4109 | message.extra.title = prompt; |
| 4094 | message.extra.generationType = generationType; | 4110 | message.extra.generationType = generationType; |
| 4095 | message.extra.negative = negative; | 4111 | message.extra.negative = negative; |
| 4096 | appendMediaToMessage(message, $mes); | 4112 | appendMediaToMessage(message, $mes); |
| 4097 | 4113 | ||
| 4098 | context.saveChat(); | 4114 | return context.saveChat(); |
| 4099 | } | 4115 | } |
| 4100 | } | 4116 | } |
| 4101 | 4117 | ||
| @@ -9,19 +9,6 @@ import { POPUP_TYPE, callGenericPopup } from '../../popup.js'; | |||
| 9 | import { renderExtensionTemplateAsync } from '../../extensions.js'; | 9 | import { renderExtensionTemplateAsync } from '../../extensions.js'; |
| 10 | import { t } from '../../i18n.js'; | 10 | import { t } from '../../i18n.js'; |
| 11 | 11 | ||
| 12 | function rgb2hex(rgb) { | ||
| 13 | rgb = rgb.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i); | ||
| 14 | return (rgb && rgb.length === 4) ? '#' + | ||
| 15 | ('0' + parseInt(rgb[1], 10).toString(16)).slice(-2) + | ||
| 16 | ('0' + parseInt(rgb[2], 10).toString(16)).slice(-2) + | ||
| 17 | ('0' + parseInt(rgb[3], 10).toString(16)).slice(-2) : ''; | ||
| 18 | } | ||
| 19 | |||
| 20 | $('button').click(function () { | ||
| 21 | var hex = rgb2hex($('input').val()); | ||
| 22 | $('.result').html(hex); | ||
| 23 | }); | ||
| 24 | |||
| 25 | async function doTokenCounter() { | 12 | async function doTokenCounter() { |
| 26 | const { tokenizerName, tokenizerId } = getFriendlyTokenizerName(main_api); | 13 | const { tokenizerName, tokenizerId } = getFriendlyTokenizerName(main_api); |
| 27 | const html = await renderExtensionTemplateAsync('token-counter', 'window', { tokenizerName }); | 14 | const html = await renderExtensionTemplateAsync('token-counter', 'window', { tokenizerName }); |
| @@ -28,8 +28,14 @@ | |||
| 28 | <option value="free">Free</option> | 28 | <option value="free">Free</option> |
| 29 | <option value="pro">Pro</option> | 29 | <option value="pro">Pro</option> |
| 30 | </select> | 30 | </select> |
| 31 | <div id="translate_key_button" class="menu_button fa-solid fa-key margin0"></div> | 31 | <div id="translate_key_button" class="menu_button menu_button_icon margin0 manage-api-keys"> |
| 32 | <div id="translate_url_button" class="menu_button fa-solid fa-link margin0"></div> | 32 | <i class="fa-solid fa-key"></i> |
| 33 | <span>Key</span> | ||
| 34 | </div> | ||
| 35 | <div id="translate_url_button" class="menu_button menu_button_icon margin0 manage-api-keys"> | ||
| 36 | <i class="fa-solid fa-link"></i> | ||
| 37 | <span>URL</span> | ||
| 38 | </div> | ||
| 33 | </div> | 39 | </div> |
| 34 | <label data-i18n="ext_translate_target_lang" for="translation_target_language">Target Language</label> | 40 | <label data-i18n="ext_translate_target_lang" for="translation_target_language">Target Language</label> |
| 35 | <select id="translation_target_language" name="target_language"></select> | 41 | <select id="translation_target_language" name="target_language"></select> |
| @@ -10,9 +10,9 @@ import { | |||
| 10 | updateMessageBlock, | 10 | updateMessageBlock, |
| 11 | } from '../../../script.js'; | 11 | } from '../../../script.js'; |
| 12 | import { extension_settings, getContext, renderExtensionTemplateAsync } from '../../extensions.js'; | 12 | import { extension_settings, getContext, renderExtensionTemplateAsync } from '../../extensions.js'; |
| 13 | import { POPUP_RESULT, POPUP_TYPE, callGenericPopup } from '../../popup.js'; | 13 | import { POPUP_TYPE, callGenericPopup } from '../../popup.js'; |
| 14 | import { updateReasoningUI } from '../../reasoning.js'; | 14 | import { updateReasoningUI } from '../../reasoning.js'; |
| 15 | import { findSecret, secret_state, writeSecret } from '../../secrets.js'; | 15 | import { secret_state } from '../../secrets.js'; |
| 16 | import { SlashCommand } from '../../slash-commands/SlashCommand.js'; | 16 | import { SlashCommand } from '../../slash-commands/SlashCommand.js'; |
| 17 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js'; | 17 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js'; |
| 18 | import { enumIcons } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; | 18 | import { enumIcons } from '../../slash-commands/SlashCommandCommonEnumsProvider.js'; |
| @@ -152,9 +152,9 @@ const LOCAL_URL = ['libre', 'oneringtranslator', 'deeplx', 'lingva']; | |||
| 152 | function showKeysButton() { | 152 | function showKeysButton() { |
| 153 | const providerRequiresKey = KEY_REQUIRED.includes(extension_settings.translate.provider); | 153 | const providerRequiresKey = KEY_REQUIRED.includes(extension_settings.translate.provider); |
| 154 | const providerOptionalUrl = LOCAL_URL.includes(extension_settings.translate.provider); | 154 | const providerOptionalUrl = LOCAL_URL.includes(extension_settings.translate.provider); |
| 155 | $('#translate_key_button').toggle(providerRequiresKey); | 155 | $('#translate_key_button').toggle(providerRequiresKey).data('key', extension_settings.translate.provider); |
| 156 | $('#translate_key_button').toggleClass('success', Boolean(secret_state[extension_settings.translate.provider])); | 156 | $('#translate_key_button').toggleClass('success', Boolean(secret_state[extension_settings.translate.provider])); |
| 157 | $('#translate_url_button').toggle(providerOptionalUrl); | 157 | $('#translate_url_button').toggle(providerOptionalUrl).data('key', extension_settings.translate.provider + '_url'); |
| 158 | $('#translate_url_button').toggleClass('success', Boolean(secret_state[extension_settings.translate.provider + '_url'])); | 158 | $('#translate_url_button').toggleClass('success', Boolean(secret_state[extension_settings.translate.provider + '_url'])); |
| 159 | $('#deepl_api_endpoint').toggle(extension_settings.translate.provider === 'deepl'); | 159 | $('#deepl_api_endpoint').toggle(extension_settings.translate.provider === 'deepl'); |
| 160 | } | 160 | } |
| @@ -749,63 +749,16 @@ jQuery(async () => { | |||
| 749 | saveSettingsDebounced(); | 749 | saveSettingsDebounced(); |
| 750 | }); | 750 | }); |
| 751 | $(document).on('click', '.mes_translate', onMessageTranslateClick); | 751 | $(document).on('click', '.mes_translate', onMessageTranslateClick); |
| 752 | $('#translate_key_button').on('click', async () => { | ||
| 753 | const optionText = $('#translation_provider option:selected').text(); | ||
| 754 | const key = await callGenericPopup(`<h3>${optionText} API Key</h3>`, POPUP_TYPE.INPUT, '', { | ||
| 755 | customButtons: [{ | ||
| 756 | text: 'Remove Key', | ||
| 757 | appendAtEnd: true, | ||
| 758 | result: POPUP_RESULT.NEGATIVE, | ||
| 759 | action: async () => { | ||
| 760 | await writeSecret(extension_settings.translate.provider, ''); | ||
| 761 | toastr.success('API Key removed'); | ||
| 762 | $('#translate_key_button').toggleClass('success', !!secret_state[extension_settings.translate.provider]); | ||
| 763 | }, | ||
| 764 | }], | ||
| 765 | }); | ||
| 766 | 752 | ||
| 767 | if (!key) { | 753 | [event_types.SECRET_WRITTEN, event_types.SECRET_DELETED, event_types.SECRET_ROTATED].forEach((eventType) => { |
| 768 | return; | 754 | eventSource.on(eventType, (/** @type {string} */ key) => { |
| 769 | } | 755 | if (key === extension_settings.translate.provider) { |
| 770 | 756 | $('#translate_key_button').toggleClass('success', !!secret_state[extension_settings.translate.provider]); | |
| 771 | await writeSecret(extension_settings.translate.provider, key); | 757 | } |
| 772 | toastr.success('API Key saved'); | 758 | if (key === `${extension_settings.translate.provider}_url`) { |
| 773 | $('#translate_key_button').addClass('success'); | 759 | $('#translate_url_button').toggleClass('success', !!secret_state[`${extension_settings.translate.provider}_url`]); |
| 774 | }); | 760 | } |
| 775 | $('#translate_url_button').on('click', async () => { | ||
| 776 | const optionText = $('#translation_provider option:selected').text(); | ||
| 777 | const exampleURLs = { | ||
| 778 | 'libre': 'http://127.0.0.1:5000/translate', | ||
| 779 | 'lingva': 'https://lingva.ml/api/v1', | ||
| 780 | 'oneringtranslator': 'http://127.0.0.1:4990/translate', | ||
| 781 | 'deeplx': 'http://127.0.0.1:1188/translate', | ||
| 782 | }; | ||
| 783 | const popupText = `<h3>${optionText} API URL</h3><i>Example: <tt>${String(exampleURLs[extension_settings.translate.provider])}</tt></i>`; | ||
| 784 | |||
| 785 | const secretKey = extension_settings.translate.provider + '_url'; | ||
| 786 | const savedUrl = secret_state[secretKey] ? await findSecret(secretKey) : ''; | ||
| 787 | |||
| 788 | const url = await callGenericPopup(popupText, POPUP_TYPE.INPUT, savedUrl, { | ||
| 789 | customButtons: [{ | ||
| 790 | text: 'Remove URL', | ||
| 791 | appendAtEnd: true, | ||
| 792 | result: POPUP_RESULT.NEGATIVE, | ||
| 793 | action: async () => { | ||
| 794 | await writeSecret(secretKey, ''); | ||
| 795 | toastr.success('API URL removed'); | ||
| 796 | $('#translate_url_button').toggleClass('success', !!secret_state[secretKey]); | ||
| 797 | }, | ||
| 798 | }], | ||
| 799 | }); | 761 | }); |
| 800 | |||
| 801 | if (!url) { | ||
| 802 | return; | ||
| 803 | } | ||
| 804 | |||
| 805 | await writeSecret(secretKey, url); | ||
| 806 | |||
| 807 | toastr.success('API URL saved'); | ||
| 808 | $('#translate_url_button').addClass('success'); | ||
| 809 | }); | 762 | }); |
| 810 | 763 | ||
| 811 | loadSettings(); | 764 | loadSettings(); |
| @@ -296,20 +296,20 @@ class AllTalkTtsProvider { | |||
| 296 | const ttsNarrateDialoguesCheckbox = document.getElementById('tts_narrate_dialogues'); | 296 | const ttsNarrateDialoguesCheckbox = document.getElementById('tts_narrate_dialogues'); |
| 297 | if (this.settings.narrator_enabled) { | 297 | if (this.settings.narrator_enabled) { |
| 298 | ttsPassAsterisksCheckbox.checked = false; | 298 | ttsPassAsterisksCheckbox.checked = false; |
| 299 | $('#tts_pass_asterisks').click(); | 299 | $('#tts_pass_asterisks').trigger('click'); |
| 300 | $('#tts_pass_asterisks').trigger('change'); | 300 | $('#tts_pass_asterisks').trigger('change'); |
| 301 | } | 301 | } |
| 302 | if (!this.settings.narrator_enabled) { | 302 | if (!this.settings.narrator_enabled) { |
| 303 | ttsPassAsterisksCheckbox.checked = true; | 303 | ttsPassAsterisksCheckbox.checked = true; |
| 304 | $('#tts_pass_asterisks').click(); | 304 | $('#tts_pass_asterisks').trigger('click'); |
| 305 | $('#tts_pass_asterisks').trigger('change'); | 305 | $('#tts_pass_asterisks').trigger('change'); |
| 306 | } | 306 | } |
| 307 | if (this.settings.narrator_enabled) { | 307 | if (this.settings.narrator_enabled) { |
| 308 | ttsNarrateQuotedCheckbox.checked = true; | 308 | ttsNarrateQuotedCheckbox.checked = true; |
| 309 | ttsNarrateDialoguesCheckbox.checked = true; | 309 | ttsNarrateDialoguesCheckbox.checked = true; |
| 310 | $('#tts_narrate_quoted').click(); | 310 | $('#tts_narrate_quoted').trigger('click'); |
| 311 | $('#tts_narrate_quoted').trigger('change'); | 311 | $('#tts_narrate_quoted').trigger('change'); |
| 312 | $('#tts_narrate_dialogues').click(); | 312 | $('#tts_narrate_dialogues').trigger('click'); |
| 313 | $('#tts_narrate_dialogues').trigger('change'); | 313 | $('#tts_narrate_dialogues').trigger('change'); |
| 314 | } | 314 | } |
| 315 | atNarratorSelect.value = this.settings.narrator_enabled.toString(); | 315 | atNarratorSelect.value = this.settings.narrator_enabled.toString(); |
| @@ -783,21 +783,21 @@ class AllTalkTtsProvider { | |||
| 783 | 783 | ||
| 784 | if (narratorOption === 'true') { | 784 | if (narratorOption === 'true') { |
| 785 | ttsPassAsterisksCheckbox.checked = false; | 785 | ttsPassAsterisksCheckbox.checked = false; |
| 786 | $('#tts_pass_asterisks').click(); | 786 | $('#tts_pass_asterisks').trigger('click'); |
| 787 | $('#tts_pass_asterisks').trigger('change'); | 787 | $('#tts_pass_asterisks').trigger('change'); |
| 788 | ttsNarrateQuotedCheckbox.checked = true; | 788 | ttsNarrateQuotedCheckbox.checked = true; |
| 789 | ttsNarrateDialoguesCheckbox.checked = true; | 789 | ttsNarrateDialoguesCheckbox.checked = true; |
| 790 | $('#tts_narrate_quoted').click(); | 790 | $('#tts_narrate_quoted').trigger('click'); |
| 791 | $('#tts_narrate_quoted').trigger('change'); | 791 | $('#tts_narrate_quoted').trigger('change'); |
| 792 | $('#tts_narrate_dialogues').click(); | 792 | $('#tts_narrate_dialogues').trigger('click'); |
| 793 | $('#tts_narrate_dialogues').trigger('change'); | 793 | $('#tts_narrate_dialogues').trigger('change'); |
| 794 | } else if (narratorOption === 'silent') { | 794 | } else if (narratorOption === 'silent') { |
| 795 | ttsPassAsterisksCheckbox.checked = false; | 795 | ttsPassAsterisksCheckbox.checked = false; |
| 796 | $('#tts_pass_asterisks').click(); | 796 | $('#tts_pass_asterisks').trigger('click'); |
| 797 | $('#tts_pass_asterisks').trigger('change'); | 797 | $('#tts_pass_asterisks').trigger('change'); |
| 798 | } else { | 798 | } else { |
| 799 | ttsPassAsterisksCheckbox.checked = true; | 799 | ttsPassAsterisksCheckbox.checked = true; |
| 800 | $('#tts_pass_asterisks').click(); | 800 | $('#tts_pass_asterisks').trigger('click'); |
| 801 | $('#tts_pass_asterisks').trigger('change'); | 801 | $('#tts_pass_asterisks').trigger('change'); |
| 802 | } | 802 | } |
| 803 | 803 | ||
| @@ -1,6 +1,5 @@ | |||
| 1 | import { getRequestHeaders } from '../../../script.js'; | 1 | import { event_types, eventSource, getRequestHeaders } from '../../../script.js'; |
| 2 | import { POPUP_RESULT, POPUP_TYPE, callGenericPopup } from '../../popup.js'; | 2 | import { SECRET_KEYS, secret_state } from '../../secrets.js'; |
| 3 | import { SECRET_KEYS, findSecret, secret_state, writeSecret } from '../../secrets.js'; | ||
| 4 | import { getPreviewString, saveTtsProviderSettings } from './index.js'; | 3 | import { getPreviewString, saveTtsProviderSettings } from './index.js'; |
| 5 | export { AzureTtsProvider }; | 4 | export { AzureTtsProvider }; |
| 6 | 5 | ||
| @@ -26,7 +25,7 @@ class AzureTtsProvider { | |||
| 26 | <h4 for="azure_tts_key" class="flex1 margin0"> | 25 | <h4 for="azure_tts_key" class="flex1 margin0"> |
| 27 | <a href="https://portal.azure.com/" target="_blank">Azure TTS Key</a> | 26 | <a href="https://portal.azure.com/" target="_blank">Azure TTS Key</a> |
| 28 | </h4> | 27 | </h4> |
| 29 | <div id="azure_tts_key" class="menu_button menu_button_icon"> | 28 | <div id="azure_tts_key" class="menu_button menu_button_icon manage-api-keys" data-key="api_key_azure_tts"> |
| 30 | <i class="fa-solid fa-key"></i> | 29 | <i class="fa-solid fa-key"></i> |
| 31 | <span>Click to set</span> | 30 | <span>Click to set</span> |
| 32 | </div> | 31 | </div> |
| @@ -39,6 +38,20 @@ class AzureTtsProvider { | |||
| 39 | return html; | 38 | return html; |
| 40 | } | 39 | } |
| 41 | 40 | ||
| 41 | constructor() { | ||
| 42 | this.handler = async function (/** @type {string} */ key) { | ||
| 43 | if (key !== SECRET_KEYS.AZURE_TTS) return; | ||
| 44 | $('#azure_tts_key').toggleClass('success', !!secret_state[SECRET_KEYS.AZURE_TTS]); | ||
| 45 | await this.onRefreshClick(); | ||
| 46 | }.bind(this); | ||
| 47 | } | ||
| 48 | |||
| 49 | dispose() { | ||
| 50 | [event_types.SECRET_WRITTEN, event_types.SECRET_DELETED, event_types.SECRET_ROTATED].forEach(event => { | ||
| 51 | eventSource.removeListener(event, this.handler); | ||
| 52 | }); | ||
| 53 | } | ||
| 54 | |||
| 42 | onSettingsChange() { | 55 | onSettingsChange() { |
| 43 | // Update dynamically | 56 | // Update dynamically |
| 44 | this.settings.region = String($('#azure_tts_region').val()); | 57 | this.settings.region = String($('#azure_tts_region').val()); |
| @@ -65,34 +78,9 @@ class AzureTtsProvider { | |||
| 65 | } | 78 | } |
| 66 | 79 | ||
| 67 | $('#azure_tts_region').val(this.settings.region).on('input', () => this.onSettingsChange()); | 80 | $('#azure_tts_region').val(this.settings.region).on('input', () => this.onSettingsChange()); |
| 68 | $('#azure_tts_key').toggleClass('success', secret_state[SECRET_KEYS.AZURE_TTS]); | 81 | $('#azure_tts_key').toggleClass('success', !!secret_state[SECRET_KEYS.AZURE_TTS]); |
| 69 | $('#azure_tts_key').on('click', async () => { | 82 | [event_types.SECRET_WRITTEN, event_types.SECRET_DELETED, event_types.SECRET_ROTATED].forEach(event => { |
| 70 | const popupText = 'Azure TTS API Key'; | 83 | eventSource.on(event, this.handler); |
| 71 | const savedKey = secret_state[SECRET_KEYS.AZURE_TTS] ? await findSecret(SECRET_KEYS.AZURE_TTS) : ''; | ||
| 72 | |||
| 73 | const key = await callGenericPopup(popupText, POPUP_TYPE.INPUT, savedKey, { | ||
| 74 | customButtons: [{ | ||
| 75 | text: 'Remove Key', | ||
| 76 | appendAtEnd: true, | ||
| 77 | result: POPUP_RESULT.NEGATIVE, | ||
| 78 | action: async () => { | ||
| 79 | await writeSecret(SECRET_KEYS.AZURE_TTS, ''); | ||
| 80 | $('#azure_tts_key').toggleClass('success', !!secret_state[SECRET_KEYS.AZURE_TTS]); | ||
| 81 | toastr.success('API Key removed'); | ||
| 82 | await this.onRefreshClick(); | ||
| 83 | }, | ||
| 84 | }], | ||
| 85 | }); | ||
| 86 | |||
| 87 | if (!key) { | ||
| 88 | return; | ||
| 89 | } | ||
| 90 | |||
| 91 | await writeSecret(SECRET_KEYS.AZURE_TTS, String(key)); | ||
| 92 | |||
| 93 | toastr.success('API Key saved'); | ||
| 94 | $('#azure_tts_key').addClass('success'); | ||
| 95 | await this.onRefreshClick(); | ||
| 96 | }); | 84 | }); |
| 97 | 85 | ||
| 98 | try { | 86 | try { |
| @@ -16,8 +16,9 @@ class ElevenLabsTtsProvider { | |||
| 16 | similarity_boost: 0.75, | 16 | similarity_boost: 0.75, |
| 17 | style_exaggeration: 0.00, | 17 | style_exaggeration: 0.00, |
| 18 | speaker_boost: true, | 18 | speaker_boost: true, |
| 19 | speed: 1.0, | ||
| 19 | apiKey: '', | 20 | apiKey: '', |
| 20 | model: 'eleven_monolingual_v1', | 21 | model: 'eleven_turbo_v2_5', |
| 21 | voiceMap: {}, | 22 | voiceMap: {}, |
| 22 | }; | 23 | }; |
| 23 | 24 | ||
| @@ -28,16 +29,23 @@ class ElevenLabsTtsProvider { | |||
| 28 | <input id="elevenlabs_tts_api_key" type="text" class="text_pole" placeholder="<API Key>"/> | 29 | <input id="elevenlabs_tts_api_key" type="text" class="text_pole" placeholder="<API Key>"/> |
| 29 | <label for="elevenlabs_tts_model">Model</label> | 30 | <label for="elevenlabs_tts_model">Model</label> |
| 30 | <select id="elevenlabs_tts_model" class="text_pole"> | 31 | <select id="elevenlabs_tts_model" class="text_pole"> |
| 31 | <option value="eleven_monolingual_v1">English v1</option> | 32 | <option value="eleven_v3">Eleven v3</option> |
| 32 | <option value="eleven_multilingual_v1">Multilingual v1</option> | 33 | <option value="eleven_ttv_v3">Eleven ttv v3</option> |
| 33 | <option value="eleven_multilingual_v2">Multilingual v2</option> | 34 | <option value="eleven_multilingual_v2">Multilingual v2</option> |
| 34 | <option value="eleven_turbo_v2">Turbo v2</option> | 35 | <option value="eleven_flash_v2_5">Eleven Flash v2.5</option> |
| 36 | <option value="eleven_turbo_v2_5">Turbo v2.5</option> | ||
| 37 | <option value="eleven_multilingual_ttv_v2">Multilingual ttv v2</option> | ||
| 38 | <option value="eleven_monolingual_v1">English v1 (Old)</option> | ||
| 39 | <option value="eleven_multilingual_v1">Multilingual v1 (Old)</option> | ||
| 40 | <option value="eleven_turbo_v2">Turbo v2 (Old)</option> | ||
| 35 | </select> | 41 | </select> |
| 36 | <input id="eleven_labs_connect" class="menu_button" type="button" value="Connect" /> | 42 | <input id="eleven_labs_connect" class="menu_button" type="button" value="Connect" /> |
| 37 | <label for="elevenlabs_tts_stability">Stability: <span id="elevenlabs_tts_stability_output"></span></label> | 43 | <label for="elevenlabs_tts_stability">Stability: <span id="elevenlabs_tts_stability_output"></span></label> |
| 38 | <input id="elevenlabs_tts_stability" type="range" value="${this.defaultSettings.stability}" min="0" max="1" step="0.01" /> | 44 | <input id="elevenlabs_tts_stability" type="range" value="${this.defaultSettings.stability}" min="0" max="1" step="0.01" /> |
| 39 | <label for="elevenlabs_tts_similarity_boost">Similarity Boost: <span id="elevenlabs_tts_similarity_boost_output"></span></label> | 45 | <label for="elevenlabs_tts_similarity_boost">Similarity Boost: <span id="elevenlabs_tts_similarity_boost_output"></span></label> |
| 40 | <input id="elevenlabs_tts_similarity_boost" type="range" value="${this.defaultSettings.similarity_boost}" min="0" max="1" step="0.01" /> | 46 | <input id="elevenlabs_tts_similarity_boost" type="range" value="${this.defaultSettings.similarity_boost}" min="0" max="1" step="0.01" /> |
| 47 | <label for="elevenlabs_tts_speed">Speed: <span id="elevenlabs_tts_speed_output"></span></label> | ||
| 48 | <input id="elevenlabs_tts_speed" type="range" value="${this.defaultSettings.speed}" min="0.7" max="1.2" step="0.01" /> | ||
| 41 | <div id="elevenlabs_tts_v2_options" style="display: none;"> | 49 | <div id="elevenlabs_tts_v2_options" style="display: none;"> |
| 42 | <label for="elevenlabs_tts_style_exaggeration">Style Exaggeration: <span id="elevenlabs_tts_style_exaggeration_output"></span></label> | 50 | <label for="elevenlabs_tts_style_exaggeration">Style Exaggeration: <span id="elevenlabs_tts_style_exaggeration_output"></span></label> |
| 43 | <input id="elevenlabs_tts_style_exaggeration" type="range" value="${this.defaultSettings.style_exaggeration}" min="0" max="1" step="0.01" /> | 51 | <input id="elevenlabs_tts_style_exaggeration" type="range" value="${this.defaultSettings.style_exaggeration}" min="0" max="1" step="0.01" /> |
| @@ -65,7 +73,14 @@ class ElevenLabsTtsProvider { | |||
| 65 | } | 73 | } |
| 66 | 74 | ||
| 67 | shouldInvolveExtendedSettings() { | 75 | shouldInvolveExtendedSettings() { |
| 68 | return this.settings.model === 'eleven_multilingual_v2'; | 76 | // Models that support extended settings (style_exaggeration, speaker_boost) |
| 77 | const modelsWithExtendedSettings = [ | ||
| 78 | 'eleven_v3', | ||
| 79 | 'eleven_ttv_v3', | ||
| 80 | 'eleven_multilingual_v2', | ||
| 81 | 'eleven_multilingual_ttv_v2', | ||
| 82 | ]; | ||
| 83 | return modelsWithExtendedSettings.includes(this.settings.model); | ||
| 69 | } | 84 | } |
| 70 | 85 | ||
| 71 | onSettingsChange() { | 86 | onSettingsChange() { |
| @@ -74,10 +89,12 @@ class ElevenLabsTtsProvider { | |||
| 74 | this.settings.similarity_boost = $('#elevenlabs_tts_similarity_boost').val(); | 89 | this.settings.similarity_boost = $('#elevenlabs_tts_similarity_boost').val(); |
| 75 | this.settings.style_exaggeration = $('#elevenlabs_tts_style_exaggeration').val(); | 90 | this.settings.style_exaggeration = $('#elevenlabs_tts_style_exaggeration').val(); |
| 76 | this.settings.speaker_boost = $('#elevenlabs_tts_speaker_boost').is(':checked'); | 91 | this.settings.speaker_boost = $('#elevenlabs_tts_speaker_boost').is(':checked'); |
| 92 | this.settings.speed = $('#elevenlabs_tts_speed').val(); | ||
| 77 | this.settings.model = $('#elevenlabs_tts_model').find(':selected').val(); | 93 | this.settings.model = $('#elevenlabs_tts_model').find(':selected').val(); |
| 78 | $('#elevenlabs_tts_stability_output').text(Math.round(this.settings.stability * 100) + '%'); | 94 | $('#elevenlabs_tts_stability_output').text(Math.round(this.settings.stability * 100) + '%'); |
| 79 | $('#elevenlabs_tts_similarity_boost_output').text(Math.round(this.settings.similarity_boost * 100) + '%'); | 95 | $('#elevenlabs_tts_similarity_boost_output').text(Math.round(this.settings.similarity_boost * 100) + '%'); |
| 80 | $('#elevenlabs_tts_style_exaggeration_output').text(Math.round(this.settings.style_exaggeration * 100) + '%'); | 96 | $('#elevenlabs_tts_style_exaggeration_output').text(Math.round(this.settings.style_exaggeration * 100) + '%'); |
| 97 | $('#elevenlabs_tts_speed_output').text(this.settings.speed + 'x'); | ||
| 81 | $('#elevenlabs_tts_v2_options').toggle(this.shouldInvolveExtendedSettings()); | 98 | $('#elevenlabs_tts_v2_options').toggle(this.shouldInvolveExtendedSettings()); |
| 82 | saveTtsProviderSettings(); | 99 | saveTtsProviderSettings(); |
| 83 | } | 100 | } |
| @@ -109,6 +126,7 @@ class ElevenLabsTtsProvider { | |||
| 109 | $('#elevenlabs_tts_similarity_boost').val(this.settings.similarity_boost); | 126 | $('#elevenlabs_tts_similarity_boost').val(this.settings.similarity_boost); |
| 110 | $('#elevenlabs_tts_style_exaggeration').val(this.settings.style_exaggeration); | 127 | $('#elevenlabs_tts_style_exaggeration').val(this.settings.style_exaggeration); |
| 111 | $('#elevenlabs_tts_speaker_boost').prop('checked', this.settings.speaker_boost); | 128 | $('#elevenlabs_tts_speaker_boost').prop('checked', this.settings.speaker_boost); |
| 129 | $('#elevenlabs_tts_speed').val(this.settings.speed); | ||
| 112 | $('#elevenlabs_tts_api_key').val(this.settings.apiKey); | 130 | $('#elevenlabs_tts_api_key').val(this.settings.apiKey); |
| 113 | $('#elevenlabs_tts_model').val(this.settings.model); | 131 | $('#elevenlabs_tts_model').val(this.settings.model); |
| 114 | $('#eleven_labs_connect').on('click', () => { this.onConnectClick(); }); | 132 | $('#eleven_labs_connect').on('click', () => { this.onConnectClick(); }); |
| @@ -116,10 +134,12 @@ class ElevenLabsTtsProvider { | |||
| 116 | $('#elevenlabs_tts_stability').on('input', this.onSettingsChange.bind(this)); | 134 | $('#elevenlabs_tts_stability').on('input', this.onSettingsChange.bind(this)); |
| 117 | $('#elevenlabs_tts_style_exaggeration').on('input', this.onSettingsChange.bind(this)); | 135 | $('#elevenlabs_tts_style_exaggeration').on('input', this.onSettingsChange.bind(this)); |
| 118 | $('#elevenlabs_tts_speaker_boost').on('change', this.onSettingsChange.bind(this)); | 136 | $('#elevenlabs_tts_speaker_boost').on('change', this.onSettingsChange.bind(this)); |
| 137 | $('#elevenlabs_tts_speed').on('input', this.onSettingsChange.bind(this)); | ||
| 119 | $('#elevenlabs_tts_model').on('change', this.onSettingsChange.bind(this)); | 138 | $('#elevenlabs_tts_model').on('change', this.onSettingsChange.bind(this)); |
| 120 | $('#elevenlabs_tts_stability_output').text(Math.round(this.settings.stability * 100) + '%'); | 139 | $('#elevenlabs_tts_stability_output').text(Math.round(this.settings.stability * 100) + '%'); |
| 121 | $('#elevenlabs_tts_similarity_boost_output').text(Math.round(this.settings.similarity_boost * 100) + '%'); | 140 | $('#elevenlabs_tts_similarity_boost_output').text(Math.round(this.settings.similarity_boost * 100) + '%'); |
| 122 | $('#elevenlabs_tts_style_exaggeration_output').text(Math.round(this.settings.style_exaggeration * 100) + '%'); | 141 | $('#elevenlabs_tts_style_exaggeration_output').text(Math.round(this.settings.style_exaggeration * 100) + '%'); |
| 142 | $('#elevenlabs_tts_speed_output').text(this.settings.speed + 'x'); | ||
| 123 | $('#elevenlabs_tts_v2_options').toggle(this.shouldInvolveExtendedSettings()); | 143 | $('#elevenlabs_tts_v2_options').toggle(this.shouldInvolveExtendedSettings()); |
| 124 | try { | 144 | try { |
| 125 | await this.checkReady(); | 145 | await this.checkReady(); |
| @@ -308,6 +328,7 @@ class ElevenLabsTtsProvider { | |||
| 308 | voice_settings: { | 328 | voice_settings: { |
| 309 | stability: Number(this.settings.stability), | 329 | stability: Number(this.settings.stability), |
| 310 | similarity_boost: Number(this.settings.similarity_boost), | 330 | similarity_boost: Number(this.settings.similarity_boost), |
| 331 | speed: Number(this.settings.speed), | ||
| 311 | }, | 332 | }, |
| 312 | }; | 333 | }; |
| 313 | if (this.shouldInvolveExtendedSettings()) { | 334 | if (this.shouldInvolveExtendedSettings()) { |
| @@ -31,6 +31,7 @@ import { GoogleNativeTtsProvider } from './google-native.js'; | |||
| 31 | import { ChatterboxTtsProvider } from './chatterbox.js'; | 31 | import { ChatterboxTtsProvider } from './chatterbox.js'; |
| 32 | import { KokoroTtsProvider } from './kokoro.js'; | 32 | import { KokoroTtsProvider } from './kokoro.js'; |
| 33 | import { TtsWebuiProvider } from './tts-webui.js'; | 33 | import { TtsWebuiProvider } from './tts-webui.js'; |
| 34 | import { PollinationsTtsProvider } from './pollinations.js'; | ||
| 34 | 35 | ||
| 35 | const UPDATE_INTERVAL = 1000; | 36 | const UPDATE_INTERVAL = 1000; |
| 36 | const wrapper = new ModuleWorkerWrapper(moduleWorker); | 37 | const wrapper = new ModuleWorkerWrapper(moduleWorker); |
| @@ -129,6 +130,7 @@ const ttsProviders = { | |||
| 129 | Novel: NovelTtsProvider, | 130 | Novel: NovelTtsProvider, |
| 130 | OpenAI: OpenAITtsProvider, | 131 | OpenAI: OpenAITtsProvider, |
| 131 | 'OpenAI Compatible': OpenAICompatibleTtsProvider, | 132 | 'OpenAI Compatible': OpenAICompatibleTtsProvider, |
| 133 | Pollinations: PollinationsTtsProvider, | ||
| 132 | SBVits2: SBVits2TtsProvider, | 134 | SBVits2: SBVits2TtsProvider, |
| 133 | Silero: SileroTtsProvider, | 135 | Silero: SileroTtsProvider, |
| 134 | SpeechT5: SpeechT5TtsProvider, | 136 | SpeechT5: SpeechT5TtsProvider, |
| @@ -866,7 +868,7 @@ async function onMessageEvent(messageId, lastCharIndex) { | |||
| 866 | 868 | ||
| 867 | console.debug(`Adding message from ${message.name} for TTS processing: "${message.mes}"`); | 869 | console.debug(`Adding message from ${message.name} for TTS processing: "${message.mes}"`); |
| 868 | 870 | ||
| 869 | if (extension_settings.tts.periodic_auto_generation) { | 871 | if (extension_settings.tts.periodic_auto_generation && isStreamingEnabled()) { |
| 870 | ttsJobQueue.push(message); | 872 | ttsJobQueue.push(message); |
| 871 | } else { | 873 | } else { |
| 872 | processAndQueueTtsMessage(message); | 874 | processAndQueueTtsMessage(message); |
| @@ -132,9 +132,14 @@ class NovelTtsProvider { | |||
| 132 | return { name: voiceName, voice_id: voiceName, lang: 'en-US', preview_url: false }; | 132 | return { name: voiceName, voice_id: voiceName, lang: 'en-US', preview_url: false }; |
| 133 | } | 133 | } |
| 134 | 134 | ||
| 135 | async generateTts(text, voiceId) { | 135 | /** |
| 136 | const response = await this.fetchTtsGeneration(text, voiceId); | 136 | * Generate TTS audio for the given text using the specified voice. |
| 137 | return response; | 137 | * @param {string} text Text to generate |
| 138 | * @param {string} voiceId Voice ID | ||
| 139 | * @returns {AsyncGenerator<Response>} Audio response generator | ||
| 140 | */ | ||
| 141 | generateTts(text, voiceId) { | ||
| 142 | return this.fetchTtsGeneration(text, voiceId); | ||
| 138 | } | 143 | } |
| 139 | 144 | ||
| 140 | //###########// | 145 | //###########// |
| @@ -172,16 +177,17 @@ class NovelTtsProvider { | |||
| 172 | this.audioElement.currentTime = 0; | 177 | this.audioElement.currentTime = 0; |
| 173 | 178 | ||
| 174 | const text = getPreviewString('en-US'); | 179 | const text = getPreviewString('en-US'); |
| 175 | const response = await this.fetchTtsGeneration(text, id); | 180 | for await (const response of this.generateTts(text, id)) { |
| 176 | if (!response.ok) { | 181 | const audio = await response.blob(); |
| 177 | throw new Error(`HTTP ${response.status}`); | 182 | const url = URL.createObjectURL(audio); |
| 183 | await new Promise(resolve => { | ||
| 184 | const audioElement = new Audio(); | ||
| 185 | audioElement.src = url; | ||
| 186 | audioElement.play(); | ||
| 187 | audioElement.onended = () => resolve(); | ||
| 188 | }); | ||
| 189 | URL.revokeObjectURL(url); | ||
| 178 | } | 190 | } |
| 179 | |||
| 180 | const audio = await response.blob(); | ||
| 181 | const url = URL.createObjectURL(audio); | ||
| 182 | this.audioElement.src = url; | ||
| 183 | this.audioElement.play(); | ||
| 184 | this.audioElement.onended = () => URL.revokeObjectURL(url); | ||
| 185 | } | 191 | } |
| 186 | 192 | ||
| 187 | async* fetchTtsGeneration(inputText, voiceId) { | 193 | async* fetchTtsGeneration(inputText, voiceId) { |
| @@ -1,6 +1,5 @@ | |||
| 1 | import { getRequestHeaders } from '../../../script.js'; | 1 | import { event_types, eventSource, getRequestHeaders } from '../../../script.js'; |
| 2 | import { callGenericPopup, POPUP_RESULT, POPUP_TYPE } from '../../popup.js'; | 2 | import { SECRET_KEYS, secret_state } from '../../secrets.js'; |
| 3 | import { findSecret, SECRET_KEYS, secret_state, writeSecret } from '../../secrets.js'; | ||
| 4 | import { getPreviewString, saveTtsProviderSettings } from './index.js'; | 3 | import { getPreviewString, saveTtsProviderSettings } from './index.js'; |
| 5 | 4 | ||
| 6 | export { OpenAICompatibleTtsProvider }; | 5 | export { OpenAICompatibleTtsProvider }; |
| @@ -27,7 +26,7 @@ class OpenAICompatibleTtsProvider { | |||
| 27 | <div class="flex1"> | 26 | <div class="flex1"> |
| 28 | <input id="openai_compatible_tts_endpoint" type="text" class="text_pole" maxlength="500" value="${this.defaultSettings.provider_endpoint}"/> | 27 | <input id="openai_compatible_tts_endpoint" type="text" class="text_pole" maxlength="500" value="${this.defaultSettings.provider_endpoint}"/> |
| 29 | </div> | 28 | </div> |
| 30 | <div id="openai_compatible_tts_key" class="menu_button menu_button_icon"> | 29 | <div id="openai_compatible_tts_key" class="menu_button menu_button_icon manage-api-keys" data-key="api_key_custom_openai_tts"> |
| 31 | <i class="fa-solid fa-key"></i> | 30 | <i class="fa-solid fa-key"></i> |
| 32 | <span>API Key</span> | 31 | <span>API Key</span> |
| 33 | </div> | 32 | </div> |
| @@ -41,6 +40,20 @@ class OpenAICompatibleTtsProvider { | |||
| 41 | return html; | 40 | return html; |
| 42 | } | 41 | } |
| 43 | 42 | ||
| 43 | constructor() { | ||
| 44 | this.handler = async function (/** @type {string} */ key) { | ||
| 45 | if (key !== SECRET_KEYS.CUSTOM_OPENAI_TTS) return; | ||
| 46 | $('#openai_compatible_tts_key').toggleClass('success', !!secret_state[SECRET_KEYS.CUSTOM_OPENAI_TTS]); | ||
| 47 | await this.onRefreshClick(); | ||
| 48 | }.bind(this); | ||
| 49 | } | ||
| 50 | |||
| 51 | dispose() { | ||
| 52 | [event_types.SECRET_WRITTEN, event_types.SECRET_DELETED, event_types.SECRET_ROTATED].forEach(event => { | ||
| 53 | eventSource.removeListener(event, this.handler); | ||
| 54 | }); | ||
| 55 | } | ||
| 56 | |||
| 44 | async loadSettings(settings) { | 57 | async loadSettings(settings) { |
| 45 | // Populate Provider UI given input settings | 58 | // Populate Provider UI given input settings |
| 46 | if (Object.keys(settings).length == 0) { | 59 | if (Object.keys(settings).length == 0) { |
| @@ -74,34 +87,9 @@ class OpenAICompatibleTtsProvider { | |||
| 74 | 87 | ||
Diff truncated