Merge branch 'staging' into tool-calling

This commit is contained in:
Cohee 2024-10-04 14:28:47 +00:00
commit 689dbad2d1
14 changed files with 1316 additions and 12 deletions

383
.github/readme-de_de.md vendored Normal file
View File

@ -0,0 +1,383 @@
> [!IMPORTANT]
> Die hier veröffentlichten Informationen sind möglicherweise veraltet oder unvollständig. Für aktuelle Informationen nutzen Sie bitte die englische Version.
> Letztes Update dieser README: 28.9.2024
<a name="readme-top"></a>
![][cover]
<div align="center">
[English](readme.md) | German | [中文](readme-zh_cn.md) | [日本語](readme-ja_jp.md) | [Русский](readme-ru_ru.md)
[![GitHub Stars](https://img.shields.io/github/stars/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/stargazers)
[![GitHub Forks](https://img.shields.io/github/forks/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/network)
[![GitHub Issues](https://img.shields.io/github/issues/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/issues)
[![GitHub Pull Requests](https://img.shields.io/github/issues-pr/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/pulls)
</div>
---
SillyTavern bietet eine einheitliche Benutzeroberfläche für viele LLM-APIs (KoboldAI/CPP, Horde, NovelAI, Ooba, Tabby, OpenAI, OpenRouter, Claude, Mistral und mehr), ein mobilfreundliches Layout, einen Visual-Novel-Modus, die Integration von Automatic1111 & ComfyUI API zur Bilderzeugung, TTS, WorldInfo (Lorebooks), anpassbare UI, automatische Übersetzung, mehr Eingabeaufforderungsoptionen, als du jemals wolltest oder brauchst, und unendliches Wachstumspotenzial durch Drittanbietererweiterungen.
Wir haben eine [Dokumentationswebsite](https://docs.sillytavern.app/), um die meisten deiner Fragen zu beantworten und dir den Einstieg zu erleichtern.
## Was ist SillyTavern?
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.
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.
## Unsere Vision
1. Wir möchten die Nutzer mit so viel Nutzen und Kontrolle über ihre LLM-Prompts wie möglich ausstatten. Die steile Lernkurve ist Teil des Spaßes!
2. Wir bieten weder Online- oder gehosteten Dienste an, noch verfolgen wir programmgesteuert Benutzerdaten.
3. SillyTavern ist ein Herzensprojekt, das von einer engagierten Community von LLM-Enthusiasten unterstützt wird, und wird immer kostenlos und Open Source sein.
## Branches
SillyTavern wird mit einem Branchsystem entwickelt, um ein reibungsloses Erlebnis für alle Nutzer zu gewährleisten.
* `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.
* `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.
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.
## Was brauche ich zusätzlich zu SillyTavern?
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.
### Brauche ich einen leistungsstarken PC, um SillyTavern auszuführen?
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.
### Vorgeschlagene Backends (keine Partnerschaft oder Werbebeziehung)
* [AI Horde](https://aihorde.net/) - verwende Modelle, die von Freiwilligen gehostet werden. Erfordert keine weitere Einrichtung
* [KoboldCpp](https://github.com/LostRuins/koboldcpp) - ein Favorit der Community, um GGUF-Modelle lokal auszuführen
* [tabbyAPI](https://github.com/theroyallab/tabbyAPI) - eine beliebte, portable, speicherplatzoptimierte und lokal gehostete exl2 Inferenz-API
* [OpenRouter](https://openrouter.ai) - eine einzige API für viele Cloud-Anbieter (OpenAI, Claude, Meta Llama usw.) sowie beliebte Community-Modelle.
## Fragen oder Vorschläge?
### Discord-Server
| [![][discord-shield-badge]][discord-link] | [Tritt unserer Discord-Community bei!](https://discord.gg/sillytavern) Erhalte Unterstützung, teile deine Lieblingscharaktere und Prompts. |
| :---------------------------------------- | :----------------------------------------------------------------------------------------------------------------- |
Oder nimm direkt Kontakt mit den Entwicklern auf:
* Discord: cohee, rossascends, wolfsblvt
* 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/)
* [Erstelle ein GitHub-Issue](https://github.com/SillyTavern/SillyTavern/issues)
### Ich mag dieses Projekt! Wie kann ich beitragen?
1. Sende Pull-Requests. Lerne, wie du beitragen kannst: [CONTRIBUTING.md](../CONTRIBUTING.md)
2. Sende Feature Requests und Issues unter Verwendung der bereitgestellten Vorlagen.
3. Lies diese gesamte README-Datei und überprüfe zuerst die Dokumentationswebsite, um doppelte Issues zu vermeiden.
## Screenshots
<img width="500" alt="image" src="https://github.com/user-attachments/assets/9b5f32f0-c3b3-4102-b3f5-0e9213c0f50f">
<img width="500" alt="image" src="https://github.com/user-attachments/assets/913fdbaa-7d33-42f1-ae2c-89dca41c53d1">
## Charakterkarten
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.
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.
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.
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.
## Wichtigste Features
* Erweiterte Text-Generierungs-Einstellungen mit vielen von der Community erstellten mitgelieferten Einstellungen (Presets)
* Unterstützung für World Info: Erstelle reichhaltige Lore oder reduziere die Tokens in deiner Charakterkarte
* Gruppenchats: Multi-Bot-Räume für Charaktere, die mit dir und/oder untereinander sprechen
* Reichhaltige UI-Anpassungsoptionen: Themes zur Farbenwahl, Hintergrundbilder, benutzerdefiniertes CSS und mehr
* Benutzer-Personas: Lass die KI ein wenig über dich wissen, um die Immersion zu erhöhen
* Eingebaute RAG-Unterstützung: Füge Dokumente zu deinen Chats hinzu, auf die die KI verweisen kann
* Umfangreiches "Chat-Befehle"-System und eigene [Scripting-Engine](https://docs.sillytavern.app/usage/st-script/)
## Erweiterungen
SillyTavern unterstützt Erweiterungen.
* Emotionale Ausdrucksformen von Charakteren (Sprites)
* Automatische Zusammenfassung des Chatverlaufs
* Automatische UI- und Chat-Übersetzung
* Bildgenerierung mit Stable Diffusion/FLUX/DALL-E
* Text-to-Speech für KI-Antwortnachrichten (über ElevenLabs, Silero oder die TTS-Funktion des Betriebssystems)
* Websuchfunktionen zum Hinzufügen zusätzlicher realer Kontexte zu deinen Eingabeaufforderungen
* Viele weitere sind im Menü "Erweiterungen & Assets herunterladen" verfügbar.
Tutorials zur Nutzung findest du in der [Dokumentation](https://docs.sillytavern.app/).
# ⌛ Installation
> \[!WARNING]
>
> * INSTALLIERE NICHT IN EINEM VON WINDOWS KONTROLLIERTEN ORDNER (Programme, System32 usw.).
> * FÜHRE START.BAT NICHT MIT ADMIN-BERECHTIGUNGEN AUS.
> * DIE INSTALLATION AUF WINDOWS 7 IST UNMÖGLICH, DA ES NODEJS 18.16 NICHT AUSFÜHREN KANN.
## 🪟 Windows
### Installation über Git
1. Installiere [NodeJS](https://nodejs.org/en) (die neueste LTS-Version wird empfohlen).
2. Installiere [Git für Windows](https://gitforwindows.org/).
3. Öffne den Windows-Explorer (`Win+E`).
4. Gehe zu oder erstelle einen Ordner, der nicht von Windows kontrolliert oder überwacht wird. (z.B.: C:\MySpecialFolder\)
5. Öffne ein Eingabeaufforderungsfenster in diesem Ordner, indem du in die 'Adressleiste' oben klickst, `cmd` eingibst und Enter drückst.
6. Sobald das schwarze Fenster (Eingabeaufforderung) erscheint, gib EINE der folgenden Optionen ein und drücke Enter:
* für den Release-Branch: `git clone https://github.com/SillyTavern/SillyTavern -b release`
* für den Staging-Branch: `git clone https://github.com/SillyTavern/SillyTavern -b staging`
7. Sobald alles geklont ist, doppelklicke auf `Start.bat`, damit NodeJS seine Anforderungen installiert.
8. Der Server wird dann gestartet, und SillyTavern wird in deinem Browser geöffnet.
### Installation über GitHub Desktop
(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.)
1. Installiere [NodeJS](https://nodejs.org/en) (die neueste LTS-Version wird empfohlen).
2. Installiere [GitHub Desktop](https://central.github.com/deployments/desktop/desktop/latest/win32).
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).
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.
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`.
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.)
7. Nach dem Doppelklicken sollte ein großes schwarzes Konsolenfenster erscheinen, und SillyTavern beginnt, das zu installieren, was es zum Betrieb benötigt.
8. Nach dem Installationsprozess sollte das Konsolenfenster so aussehen, und ein SillyTavern-Tab sollte in deinem Browser geöffnet sein.
9. Verbinde dich mit einer der [unterstützten APIs](https://docs.sillytavern.app/usage/api-connections/) und beginne zu chatten!
## 🐧 Linux & 🍎 MacOS
Für MacOS/Linux werden all diese Schritte in einem Terminal durchgeführt.
1. Installiere git und nodeJS (die Methode zur Durchführung hängt von deinem Betriebssystem ab).
2. Klone das Repository.
* für den Release-Branch: `git clone https://github.com/SillyTavern/SillyTavern -b release`
* für den Staging-Branch: `git clone https://github.com/SillyTavern/SillyTavern -b staging`
3. `cd SillyTavern`, um in den Installationsordner zu navigieren.
4. Führe das Skript `start.sh` mit einem dieser Befehle aus:
* `./start.sh`
* `bash start.sh`
## ⚡ Installation über SillyTavern Launcher
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.
### Für Windows-Nutzer
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:
```shell
cmd /c winget install -e --id Git.Git
```
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:
```shell
git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher && start installer.bat
```
### Für Linux-Nutzer
1. Öffne dein bevorzugtes Terminal und installiere git.
2. Klone den SillyTavern-Launcher mit:
```shell
git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher
```
3. Starte die installer.sh mit:
```shell
chmod +x install.sh && ./install.sh
```
4. Nach der Installation starte die launcher.sh mit:
```shell
chmod +x launcher.sh && ./launcher.sh
```
### Für Mac-Nutzer
1. Öffne ein Terminal und installiere brew mit:
```shell
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
```
2. Installiere git mit:
```shell
brew install git
```
3. Klone den SillyTavern-Launcher mit:
```shell
git clone https://github.com/SillyTavern/SillyTavern-Launcher.git && cd SillyTavern-Launcher
```
4. Starte die installer.sh mit:
```shell
chmod +x install.sh && ./install.sh
```
5. Nach der Installation starte die launcher.sh mit:
```shell
chmod +x launcher.sh && ./launcher.sh
```
## 🐋 Installation über Docker
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.
### Image selbst bauen
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.
### Verwendung der GitHub Container Registry (am einfachsten)
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:
#### Container-Variablen
##### Volumen-Zuordnungen
* [config] - Das Verzeichnis, in dem die SillyTavern-Konfigurationsdateien auf deinem Host-Computer gespeichert werden
* [data] - Das Verzeichnis, in dem die Benutzerdaten von SillyTavern (einschließlich Charaktere) auf deinem Host-Computer gespeichert werden
* [plugins] - (optional) Das Verzeichnis, in dem die SillyTavern-Server-Plugins auf deinem Host-Computer gespeichert werden
##### Port-Zuordnungen
* [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.
##### Zusätzliche Einstellungen
* [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.
* [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.
#### Installationsbefehl
1. Öffne deine Befehlszeile.
2. Führe den folgenden Befehl aus:
`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]'`
> Beachte, dass 8000 ein Standard-Listening-Port ist. Vergiss nicht, einen geeigneten Port zu verwenden, wenn du ihn in der Konfiguration änderst.
## 📱 Installation über Termux auf Android OS
> \[!HINWEIS]
> **SillyTavern kann nativ auf Android-Geräten über Termux ausgeführt werden, aber wir bieten keine offizielle Unterstützung für diesen Anwendungsfall.**
>
> **Bitte beziehe dich auf diesen Leitfaden von ArroganceComplex#2659:**
>
> * <https://rentry.org/STAI-Termux>
**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.
## API-Schlüsselverwaltung
SillyTavern speichert deine API-Schlüssel in einer Datei `secrets.json` im Benutzerdatenverzeichnis (`/data/default-user/secrets.json` ist der Standardpfad).
Standardmäßig sind API-Schlüssel nach dem Speichern und Aktualisieren der Seite nicht mehr über die Benutzeroberfläche sichtbar.
Um die Ansicht deiner Schlüssel zu aktivieren:
1. Setze den Wert von `allowKeysExposure` auf `true` in der Datei `config.yaml`.
2. Starte den SillyTavern-Server neu.
3. Klicke auf den Link 'Verborgene API-Schlüssel anzeigen' in der unteren rechten Ecke des API-Verbindungsfeldes.
## Befehlszeilenargumente
Sie können Befehlszeilenargumente an den Start des SillyTavern-Servers übergeben, um einige Einstellungen in `config.yaml` zu überschreiben.
### Beispiele
```shell
node server.js --port 8000 --listen false
# oder
npm run start -- --port 8000 --listen false
# oder (nur Windows)
Start.bat --port 8000 --listen false
```
### Unterstützte Argumente
| Option | Beschreibung | Typ |
|-------------------------|----------------------------------------------------------------------------------------------------|----------|
| `--version` | Versionsnummer anzeigen | boolean |
| `--enableIPv6` | Aktiviert IPv6. | boolean |
| `--enableIPv4` | Aktiviert IPv4. | boolean |
| `--port` | Legt den Port fest, unter dem SillyTavern ausgeführt wird. Wenn nicht angegeben, wird auf YAML-Konfiguration „Port“ zurückgegriffen. | number |
| „--dnsPreferIPv6“ | Bevorzugt IPv6 für DNS. Wenn nicht angegeben, wird auf YAML-Konfiguration „preferIPv6“ zurückgegriffen. | boolean |
| „--autorun“ | Startet SillyTavern automatisch im Browser. Wenn nicht angegeben, wird auf YAML-Konfiguration „autorun“ zurückgegriffen.| boolean |
| „--autorunHostname“ | Der Autorun-Hostname, am besten auf „auto“ belassen. | string |
| „--autorunPortOverride“ | Überschreibt den Port für Autorun. | string |
| „--listen“ | SillyTavern lauscht auf allen Netzwerkschnittstellen. Wenn nicht angegeben, wird auf YAML-Konfiguration „listen“ zurückgegriffen.| boolean |
| „--corsProxy“ | Aktiviert CORS-Proxy. Wenn nicht angegeben, wird auf YAML-Konfiguration „enableCorsProxy“ zurückgegriffen. | boolean |
| `--disableCsrf` | Deaktiviert CSRF-Schutz | boolean |
| `--ssl` | Aktiviert SSL | boolean |
| `--certPath` | Pfad zu Ihrer Zertifikatsdatei. | string |
| `--keyPath` | Pfad zu Ihrer privaten Schlüsseldatei. | string |
| `--whitelist` | Aktiviert den Whitelist-Modus | boolean |
| `--dataRoot` | Stammverzeichnis für Datenspeicherung | string |
| `--avoidLocalhost` | Vermeidet die Verwendung von „localhost“ für Autorun im Auto-Modus. | boolean |
| `--basicAuthMode` | Aktiviert die grundlegende Authentifizierung | boolean |
| `--requestProxyEnabled` | Aktiviert die Verwendung eines Proxys für ausgehende Anfragen | boolean |
| `--requestProxyUrl` | Proxy-URL anfordern (HTTP- oder SOCKS-Protokolle) | string |
| `--requestProxyBypass` | Proxy-Bypass-Liste anfordern (durch Leerzeichen getrennte Liste von Hosts) | Array |
## Remoteverbindungen
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.
Lies die ausführliche Anleitung zum Einrichten von Remoteverbindungen in den [Docs](https://docs.sillytavern.app/usage/remoteconnections/).
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).
## Leistungsprobleme?
1. Deaktiviere den Unschärfeeffekt und aktiviere "Verringerte Bewegung" im Bedienfeld "Benutzereinstellungen" (UI-Design schaltet Kategorie um).
2. Wenn du Response Streaming verwendest, stelle die Streaming-FPS auf einen niedrigeren Wert ein (10-15 FPS werden empfohlen).
3. Stelle sicher, dass der Browser die GPU-Beschleunigung zum Rendern verwenden kann.
## Lizenz und Danksagungen
**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.**
* [TavernAI](https://github.com/TavernAI/TavernAI) 1.2.8 von Humi: MIT-Lizenz
* Teile von CncAnons TavernAITurbo-Mod werden mit Genehmigung verwendet
* Visual Novel-Modus inspiriert von der Arbeit von PepperTaco (<https://github.com/peppertaco/Tavern/>)
* Noto Sans-Schriftart von Google (OFL-Lizenz)
* Symboldesign von Font Awesome <https://fontawesome.com> (Symbole: CC BY 4.0, Schriftarten: SIL OFL 1.1, Code: MIT-Lizenz)
* Standardinhalt von @OtisAlejandro (Seraphina-Charakter und Lorebook) und @kallmeflocc (10.000 Discord-Benutzer-Feierhintergrund)
* Docker-Anleitung von [@mrguymiah](https://github.com/mrguymiah) und [@Bronya-Rand](https://github.com/Bronya-Rand)
## Top Contributors
[![Contributors](https://contrib.rocks/image?repo=SillyTavern/SillyTavern)](https://github.com/SillyTavern/SillyTavern/graphs/contributors)
<!-- LINK GROUP -->
[cover]: https://github.com/user-attachments/assets/01a6ae9a-16aa-45f2-8bff-32b5dc587e44
[discord-link]: https://discord.gg/sillytavern
[discord-shield-badge]: https://img.shields.io/discord/1100685673633153084?color=5865F2&label=discord&labelColor=black&logo=discord&logoColor=white&style=for-the-badge

View File

@ -5,7 +5,7 @@
<div align="center">
[English](readme.md) | [中文](readme-zh_cn.md) | 日本語 | [Русский](readme-ru_ru.md)
[English](readme.md) | [German](readme-de_de.md) | [中文](readme-zh_cn.md) | 日本語 | [Русский](readme-ru_ru.md)
[![GitHub Stars](https://img.shields.io/github/stars/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/stargazers)
[![GitHub Forks](https://img.shields.io/github/forks/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/network)

View File

@ -7,7 +7,7 @@
<div align="center">
[English](readme.md) | [中文](readme-zh_cn.md) | [日本語](readme-ja_jp.md) | Русский
[English](readme.md) | [German](readme-de_de.md) | [中文](readme-zh_cn.md) | [日本語](readme-ja_jp.md) | Русский
[![GitHub Stars](https://img.shields.io/github/stars/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/stargazers)
[![GitHub Forks](https://img.shields.io/github/forks/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/network)

View File

@ -5,7 +5,7 @@
<div align="center">
[English](readme.md) | 中文 | [日本語](readme-ja_jp.md) | [Русский](readme-ru_ru.md)
[English](readme.md) | [German](readme-de_de.md) | 中文 | [日本語](readme-ja_jp.md) | [Русский](readme-ru_ru.md)
[![GitHub Stars](https://img.shields.io/github/stars/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/stargazers)
[![GitHub Forks](https://img.shields.io/github/forks/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/network)

2
.github/readme.md vendored
View File

@ -4,7 +4,7 @@
<div align="center">
English | [中文](readme-zh_cn.md) | [日本語](readme-ja_jp.md) | [Русский](readme-ru_ru.md)
English | [German](readme-de_de.md) | [中文](readme-zh_cn.md) | [日本語](readme-ja_jp.md) | [Русский](readme-ru_ru.md)
[![GitHub Stars](https://img.shields.io/github/stars/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/stargazers)
[![GitHub Forks](https://img.shields.io/github/forks/SillyTavern/SillyTavern.svg)](https://github.com/SillyTavern/SillyTavern/network)

View File

@ -1,3 +1,4 @@
@import url('/lib/dialog-polyfill.css');
@import url('./popup-safari-fix.css');
dialog {
@ -42,7 +43,7 @@ dialog {
display: flex;
flex-direction: column;
overflow: hidden;
width: 100%;
width: min(100%, 100vw);
height: 100%;
padding: 1px;
}

View File

@ -2888,6 +2888,7 @@
<option value="gemini-1.5-pro-latest">Gemini 1.5 Pro [latest]</option>
<option value="gemini-1.5-pro-001">Gemini 1.5 Pro [001]</option>
<option value="gemini-1.5-pro-002">Gemini 1.5 Pro [002]</option>
<option value="gemini-1.5-flash-8b">Gemini 1.5 Flash 8B</option>
<option value="gemini-1.5-flash-exp-0827">Gemini 1.5 Flash Experiment 2024-08-27</option>
<option value="gemini-1.5-flash-8b-exp-0827">Gemini 1.5 Flash 8B Experiment 2024-08-27</option>
<option value="gemini-1.5-flash-8b-exp-0924">Gemini 1.5 Flash 8B Experiment 2024-09-24</option>

View File

@ -0,0 +1,37 @@
.poly_dialog {
position: absolute;
left: 0; right: 0;
width: -moz-fit-content;
width: -webkit-fit-content;
width: fit-content;
height: -moz-fit-content;
height: -webkit-fit-content;
height: fit-content;
margin: auto;
border: solid;
padding: 1em;
background: white;
color: black;
display: block;
}
.poly_dialog:not([open]) {
display: none;
}
.poly_dialog + .backdrop {
position: fixed;
top: 0; right: 0; bottom: 0; left: 0;
background: rgba(0,0,0,0.1);
}
._poly_dialog_overlay {
position: fixed;
top: 0; right: 0; bottom: 0; left: 0;
}
.poly_dialog.fixed {
position: fixed;
top: 50%;
transform: translate(0, -50%);
}

View File

@ -0,0 +1,858 @@
// nb. This is for IE10 and lower _only_.
var supportCustomEvent = window.CustomEvent;
if (!supportCustomEvent || typeof supportCustomEvent === 'object') {
supportCustomEvent = function CustomEvent(event, x) {
x = x || {};
var ev = document.createEvent('CustomEvent');
ev.initCustomEvent(event, !!x.bubbles, !!x.cancelable, x.detail || null);
return ev;
};
supportCustomEvent.prototype = window.Event.prototype;
}
/**
* Dispatches the passed event to both an "on<type>" handler as well as via the
* normal dispatch operation. Does not bubble.
*
* @param {!EventTarget} target
* @param {!Event} event
* @return {boolean}
*/
function safeDispatchEvent(target, event) {
var check = 'on' + event.type.toLowerCase();
if (typeof target[check] === 'function') {
target[check](event);
}
return target.dispatchEvent(event);
}
/**
* @param {Element} el to check for stacking context
* @return {boolean} whether this el or its parents creates a stacking context
*/
function createsStackingContext(el) {
while (el && el !== document.body) {
var s = window.getComputedStyle(el);
var invalid = function(k, ok) {
return !(s[k] === undefined || s[k] === ok);
};
if (s.opacity < 1 ||
invalid('zIndex', 'auto') ||
invalid('transform', 'none') ||
invalid('mixBlendMode', 'normal') ||
invalid('filter', 'none') ||
invalid('perspective', 'none') ||
s['isolation'] === 'isolate' ||
s.position === 'fixed' ||
s.webkitOverflowScrolling === 'touch') {
return true;
}
el = el.parentElement;
}
return false;
}
/**
* Finds the nearest <dialog> from the passed element.
*
* @param {Element} el to search from
* @return {HTMLDialogElement} dialog found
*/
function findNearestDialog(el) {
while (el) {
if (el.localName === 'dialog') {
return /** @type {HTMLDialogElement} */ (el);
}
if (el.parentElement) {
el = el.parentElement;
} else if (el.parentNode) {
el = el.parentNode.host;
} else {
el = null;
}
}
return null;
}
/**
* Blur the specified element, as long as it's not the HTML body element.
* This works around an IE9/10 bug - blurring the body causes Windows to
* blur the whole application.
*
* @param {Element} el to blur
*/
function safeBlur(el) {
// Find the actual focused element when the active element is inside a shadow root
while (el && el.shadowRoot && el.shadowRoot.activeElement) {
el = el.shadowRoot.activeElement;
}
if (el && el.blur && el !== document.body) {
el.blur();
}
}
/**
* @param {!NodeList} nodeList to search
* @param {Node} node to find
* @return {boolean} whether node is inside nodeList
*/
function inNodeList(nodeList, node) {
for (var i = 0; i < nodeList.length; ++i) {
if (nodeList[i] === node) {
return true;
}
}
return false;
}
/**
* @param {HTMLFormElement} el to check
* @return {boolean} whether this form has method="dialog"
*/
function isFormMethodDialog(el) {
if (!el || !el.hasAttribute('method')) {
return false;
}
return el.getAttribute('method').toLowerCase() === 'dialog';
}
/**
* @param {!DocumentFragment|!Element} hostElement
* @return {?Element}
*/
function findFocusableElementWithin(hostElement) {
// Note that this is 'any focusable area'. This list is probably not exhaustive, but the
// alternative involves stepping through and trying to focus everything.
var opts = ['button', 'input', 'keygen', 'select', 'textarea'];
var query = opts.map(function(el) {
return el + ':not([disabled])';
});
// TODO(samthor): tabindex values that are not numeric are not focusable.
query.push('[tabindex]:not([disabled]):not([tabindex=""])'); // tabindex != "", not disabled
var target = hostElement.querySelector(query.join(', '));
if (!target && 'attachShadow' in Element.prototype) {
// If we haven't found a focusable target, see if the host element contains an element
// which has a shadowRoot.
// Recursively search for the first focusable item in shadow roots.
var elems = hostElement.querySelectorAll('*');
for (var i = 0; i < elems.length; i++) {
if (elems[i].tagName && elems[i].shadowRoot) {
target = findFocusableElementWithin(elems[i].shadowRoot);
if (target) {
break;
}
}
}
}
return target;
}
/**
* Determines if an element is attached to the DOM.
* @param {Element} element to check
* @return {boolean} whether the element is in DOM
*/
function isConnected(element) {
return element.isConnected || document.body.contains(element);
}
/**
* @param {!Event} event
* @return {?Element}
*/
function findFormSubmitter(event) {
if (event.submitter) {
return event.submitter;
}
var form = event.target;
if (!(form instanceof HTMLFormElement)) {
return null;
}
var submitter = dialogPolyfill.formSubmitter;
if (!submitter) {
var target = event.target;
var root = ('getRootNode' in target && target.getRootNode() || document);
submitter = root.activeElement;
}
if (!submitter || submitter.form !== form) {
return null;
}
return submitter;
}
/**
* @param {!Event} event
*/
function maybeHandleSubmit(event) {
if (event.defaultPrevented) {
return;
}
var form = /** @type {!HTMLFormElement} */ (event.target);
// We'd have a value if we clicked on an imagemap.
var value = dialogPolyfill.imagemapUseValue;
var submitter = findFormSubmitter(event);
if (value === null && submitter) {
value = submitter.value;
}
// There should always be a dialog as this handler is added specifically on them, but check just
// in case.
var dialog = findNearestDialog(form);
if (!dialog) {
return;
}
// Prefer formmethod on the button.
var formmethod = submitter && submitter.getAttribute('formmethod') || form.getAttribute('method');
if (formmethod !== 'dialog') {
return;
}
event.preventDefault();
if (value != null) {
// nb. we explicitly check against null/undefined
dialog.close(value);
} else {
dialog.close();
}
}
/**
* @param {!HTMLDialogElement} dialog to upgrade
* @constructor
*/
function dialogPolyfillInfo(dialog) {
this.dialog_ = dialog;
this.replacedStyleTop_ = false;
this.openAsModal_ = false;
// Set a11y role. Browsers that support dialog implicitly know this already.
if (!dialog.hasAttribute('role')) {
dialog.setAttribute('role', 'dialog');
}
dialog.show = this.show.bind(this);
dialog.showModal = this.showModal.bind(this);
dialog.close = this.close.bind(this);
dialog.addEventListener('submit', maybeHandleSubmit, false);
if (!('returnValue' in dialog)) {
dialog.returnValue = '';
}
if ('MutationObserver' in window) {
var mo = new MutationObserver(this.maybeHideModal.bind(this));
mo.observe(dialog, {attributes: true, attributeFilter: ['open']});
} else {
// IE10 and below support. Note that DOMNodeRemoved etc fire _before_ removal. They also
// seem to fire even if the element was removed as part of a parent removal. Use the removed
// events to force downgrade (useful if removed/immediately added).
var removed = false;
var cb = function() {
removed ? this.downgradeModal() : this.maybeHideModal();
removed = false;
}.bind(this);
var timeout;
var delayModel = function(ev) {
if (ev.target !== dialog) { return; } // not for a child element
var cand = 'DOMNodeRemoved';
removed |= (ev.type.substr(0, cand.length) === cand);
window.clearTimeout(timeout);
timeout = window.setTimeout(cb, 0);
};
['DOMAttrModified', 'DOMNodeRemoved', 'DOMNodeRemovedFromDocument'].forEach(function(name) {
dialog.addEventListener(name, delayModel);
});
}
// Note that the DOM is observed inside DialogManager while any dialog
// is being displayed as a modal, to catch modal removal from the DOM.
Object.defineProperty(dialog, 'open', {
set: this.setOpen.bind(this),
get: dialog.hasAttribute.bind(dialog, 'open')
});
this.backdrop_ = document.createElement('div');
this.backdrop_.className = 'backdrop';
this.backdrop_.addEventListener('mouseup' , this.backdropMouseEvent_.bind(this));
this.backdrop_.addEventListener('mousedown', this.backdropMouseEvent_.bind(this));
this.backdrop_.addEventListener('click' , this.backdropMouseEvent_.bind(this));
}
dialogPolyfillInfo.prototype = /** @type {HTMLDialogElement.prototype} */ ({
get dialog() {
return this.dialog_;
},
/**
* Maybe remove this dialog from the modal top layer. This is called when
* a modal dialog may no longer be tenable, e.g., when the dialog is no
* longer open or is no longer part of the DOM.
*/
maybeHideModal: function() {
if (this.dialog_.hasAttribute('open') && isConnected(this.dialog_)) { return; }
this.downgradeModal();
},
/**
* Remove this dialog from the modal top layer, leaving it as a non-modal.
*/
downgradeModal: function() {
if (!this.openAsModal_) { return; }
this.openAsModal_ = false;
this.dialog_.style.zIndex = '';
// This won't match the native <dialog> exactly because if the user set top on a centered
// polyfill dialog, that top gets thrown away when the dialog is closed. Not sure it's
// possible to polyfill this perfectly.
if (this.replacedStyleTop_) {
this.dialog_.style.top = '';
this.replacedStyleTop_ = false;
}
// Clear the backdrop and remove from the manager.
this.backdrop_.parentNode && this.backdrop_.parentNode.removeChild(this.backdrop_);
dialogPolyfill.dm.removeDialog(this);
},
/**
* @param {boolean} value whether to open or close this dialog
*/
setOpen: function(value) {
if (value) {
this.dialog_.hasAttribute('open') || this.dialog_.setAttribute('open', '');
} else {
this.dialog_.removeAttribute('open');
this.maybeHideModal(); // nb. redundant with MutationObserver
}
},
/**
* Handles mouse events ('mouseup', 'mousedown', 'click') on the fake .backdrop element, redirecting them as if
* they were on the dialog itself.
*
* @param {!Event} e to redirect
*/
backdropMouseEvent_: function(e) {
if (!this.dialog_.hasAttribute('tabindex')) {
// Clicking on the backdrop should move the implicit cursor, even if dialog cannot be
// focused. Create a fake thing to focus on. If the backdrop was _before_ the dialog, this
// would not be needed - clicks would move the implicit cursor there.
var fake = document.createElement('div');
this.dialog_.insertBefore(fake, this.dialog_.firstChild);
fake.tabIndex = -1;
fake.focus();
this.dialog_.removeChild(fake);
} else {
this.dialog_.focus();
}
var redirectedEvent = document.createEvent('MouseEvents');
redirectedEvent.initMouseEvent(e.type, e.bubbles, e.cancelable, window,
e.detail, e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey,
e.altKey, e.shiftKey, e.metaKey, e.button, e.relatedTarget);
this.dialog_.dispatchEvent(redirectedEvent);
e.stopPropagation();
},
/**
* Focuses on the first focusable element within the dialog. This will always blur the current
* focus, even if nothing within the dialog is found.
*/
focus_: function() {
// Find element with `autofocus` attribute, or fall back to the first form/tabindex control.
var target = this.dialog_.querySelector('[autofocus]:not([disabled])');
if (!target && this.dialog_.tabIndex >= 0) {
target = this.dialog_;
}
if (!target) {
target = findFocusableElementWithin(this.dialog_);
}
safeBlur(document.activeElement);
target && target.focus();
},
/**
* Sets the zIndex for the backdrop and dialog.
*
* @param {number} dialogZ
* @param {number} backdropZ
*/
updateZIndex: function(dialogZ, backdropZ) {
if (dialogZ < backdropZ) {
throw new Error('dialogZ should never be < backdropZ');
}
this.dialog_.style.zIndex = dialogZ;
this.backdrop_.style.zIndex = backdropZ;
},
/**
* Shows the dialog. If the dialog is already open, this does nothing.
*/
show: function() {
if (!this.dialog_.open) {
this.setOpen(true);
this.focus_();
}
},
/**
* Show this dialog modally.
*/
showModal: function() {
if (this.dialog_.hasAttribute('open')) {
throw new Error('Failed to execute \'showModal\' on dialog: The element is already open, and therefore cannot be opened modally.');
}
if (!isConnected(this.dialog_)) {
throw new Error('Failed to execute \'showModal\' on dialog: The element is not in a Document.');
}
if (!dialogPolyfill.dm.pushDialog(this)) {
throw new Error('Failed to execute \'showModal\' on dialog: There are too many open modal dialogs.');
}
if (createsStackingContext(this.dialog_.parentElement)) {
console.warn('A dialog is being shown inside a stacking context. ' +
'This may cause it to be unusable. For more information, see this link: ' +
'https://github.com/GoogleChrome/dialog-polyfill/#stacking-context');
}
this.setOpen(true);
this.openAsModal_ = true;
// Optionally center vertically, relative to the current viewport.
if (dialogPolyfill.needsCentering(this.dialog_)) {
dialogPolyfill.reposition(this.dialog_);
this.replacedStyleTop_ = true;
} else {
this.replacedStyleTop_ = false;
}
// Insert backdrop.
this.dialog_.parentNode.insertBefore(this.backdrop_, this.dialog_.nextSibling);
// Focus on whatever inside the dialog.
this.focus_();
},
/**
* Closes this HTMLDialogElement. This is optional vs clearing the open
* attribute, however this fires a 'close' event.
*
* @param {string=} opt_returnValue to use as the returnValue
*/
close: function(opt_returnValue) {
if (!this.dialog_.hasAttribute('open')) {
throw new Error('Failed to execute \'close\' on dialog: The element does not have an \'open\' attribute, and therefore cannot be closed.');
}
this.setOpen(false);
// Leave returnValue untouched in case it was set directly on the element
if (opt_returnValue !== undefined) {
this.dialog_.returnValue = opt_returnValue;
}
// Triggering "close" event for any attached listeners on the <dialog>.
var closeEvent = new supportCustomEvent('close', {
bubbles: false,
cancelable: false
});
safeDispatchEvent(this.dialog_, closeEvent);
}
});
var dialogPolyfill = {};
dialogPolyfill.reposition = function(element) {
var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
var topValue = scrollTop + (window.innerHeight - element.offsetHeight) / 2;
element.style.top = Math.max(scrollTop, topValue) + 'px';
};
dialogPolyfill.isInlinePositionSetByStylesheet = function(element) {
for (var i = 0; i < document.styleSheets.length; ++i) {
var styleSheet = document.styleSheets[i];
var cssRules = null;
// Some browsers throw on cssRules.
try {
cssRules = styleSheet.cssRules;
} catch (e) {}
if (!cssRules) { continue; }
for (var j = 0; j < cssRules.length; ++j) {
var rule = cssRules[j];
var selectedNodes = null;
// Ignore errors on invalid selector texts.
try {
selectedNodes = document.querySelectorAll(rule.selectorText);
} catch(e) {}
if (!selectedNodes || !inNodeList(selectedNodes, element)) {
continue;
}
var cssTop = rule.style.getPropertyValue('top');
var cssBottom = rule.style.getPropertyValue('bottom');
if ((cssTop && cssTop !== 'auto') || (cssBottom && cssBottom !== 'auto')) {
return true;
}
}
}
return false;
};
dialogPolyfill.needsCentering = function(dialog) {
var computedStyle = window.getComputedStyle(dialog);
if (computedStyle.position !== 'absolute') {
return false;
}
// We must determine whether the top/bottom specified value is non-auto. In
// WebKit/Blink, checking computedStyle.top == 'auto' is sufficient, but
// Firefox returns the used value. So we do this crazy thing instead: check
// the inline style and then go through CSS rules.
if ((dialog.style.top !== 'auto' && dialog.style.top !== '') ||
(dialog.style.bottom !== 'auto' && dialog.style.bottom !== '')) {
return false;
}
return !dialogPolyfill.isInlinePositionSetByStylesheet(dialog);
};
/**
* @param {!Element} element to force upgrade
*/
dialogPolyfill.forceRegisterDialog = function(element) {
if (window.HTMLDialogElement || element.showModal) {
console.warn('This browser already supports <dialog>, the polyfill ' +
'may not work correctly', element);
}
if (element.localName !== 'dialog') {
throw new Error('Failed to register dialog: The element is not a dialog.');
}
new dialogPolyfillInfo(/** @type {!HTMLDialogElement} */ (element));
};
/**
* @param {!Element} element to upgrade, if necessary
*/
dialogPolyfill.registerDialog = function(element) {
if (!element.showModal) {
dialogPolyfill.forceRegisterDialog(element);
}
};
/**
* @constructor
*/
dialogPolyfill.DialogManager = function() {
/** @type {!Array<!dialogPolyfillInfo>} */
this.pendingDialogStack = [];
var checkDOM = this.checkDOM_.bind(this);
// The overlay is used to simulate how a modal dialog blocks the document.
// The blocking dialog is positioned on top of the overlay, and the rest of
// the dialogs on the pending dialog stack are positioned below it. In the
// actual implementation, the modal dialog stacking is controlled by the
// top layer, where z-index has no effect.
this.overlay = document.createElement('div');
this.overlay.className = '_poly_dialog_overlay';
this.overlay.addEventListener('click', function(e) {
this.forwardTab_ = undefined;
e.stopPropagation();
checkDOM([]); // sanity-check DOM
}.bind(this));
this.handleKey_ = this.handleKey_.bind(this);
this.handleFocus_ = this.handleFocus_.bind(this);
this.zIndexLow_ = 100000;
this.zIndexHigh_ = 100000 + 150;
this.forwardTab_ = undefined;
if ('MutationObserver' in window) {
this.mo_ = new MutationObserver(function(records) {
var removed = [];
records.forEach(function(rec) {
for (var i = 0, c; c = rec.removedNodes[i]; ++i) {
if (!(c instanceof Element)) {
continue;
} else if (c.localName === 'dialog') {
removed.push(c);
}
removed = removed.concat(c.querySelectorAll('dialog'));
}
});
removed.length && checkDOM(removed);
});
}
};
/**
* Called on the first modal dialog being shown. Adds the overlay and related
* handlers.
*/
dialogPolyfill.DialogManager.prototype.blockDocument = function() {
document.documentElement.addEventListener('focus', this.handleFocus_, true);
document.addEventListener('keydown', this.handleKey_);
this.mo_ && this.mo_.observe(document, {childList: true, subtree: true});
};
/**
* Called on the first modal dialog being removed, i.e., when no more modal
* dialogs are visible.
*/
dialogPolyfill.DialogManager.prototype.unblockDocument = function() {
document.documentElement.removeEventListener('focus', this.handleFocus_, true);
document.removeEventListener('keydown', this.handleKey_);
this.mo_ && this.mo_.disconnect();
};
/**
* Updates the stacking of all known dialogs.
*/
dialogPolyfill.DialogManager.prototype.updateStacking = function() {
var zIndex = this.zIndexHigh_;
for (var i = 0, dpi; dpi = this.pendingDialogStack[i]; ++i) {
dpi.updateZIndex(--zIndex, --zIndex);
if (i === 0) {
this.overlay.style.zIndex = --zIndex;
}
}
// Make the overlay a sibling of the dialog itself.
var last = this.pendingDialogStack[0];
if (last) {
var p = last.dialog.parentNode || document.body;
p.appendChild(this.overlay);
} else if (this.overlay.parentNode) {
this.overlay.parentNode.removeChild(this.overlay);
}
};
/**
* @param {Element} candidate to check if contained or is the top-most modal dialog
* @return {boolean} whether candidate is contained in top dialog
*/
dialogPolyfill.DialogManager.prototype.containedByTopDialog_ = function(candidate) {
while (candidate = findNearestDialog(candidate)) {
for (var i = 0, dpi; dpi = this.pendingDialogStack[i]; ++i) {
if (dpi.dialog === candidate) {
return i === 0; // only valid if top-most
}
}
candidate = candidate.parentElement;
}
return false;
};
dialogPolyfill.DialogManager.prototype.handleFocus_ = function(event) {
var target = event.composedPath ? event.composedPath()[0] : event.target;
if (this.containedByTopDialog_(target)) { return; }
if (document.activeElement === document.documentElement) { return; }
event.preventDefault();
event.stopPropagation();
safeBlur(/** @type {Element} */ (target));
if (this.forwardTab_ === undefined) { return; } // move focus only from a tab key
var dpi = this.pendingDialogStack[0];
var dialog = dpi.dialog;
var position = dialog.compareDocumentPosition(target);
if (position & Node.DOCUMENT_POSITION_PRECEDING) {
if (this.forwardTab_) {
// forward
dpi.focus_();
} else if (target !== document.documentElement) {
// backwards if we're not already focused on <html>
document.documentElement.focus();
}
}
return false;
};
dialogPolyfill.DialogManager.prototype.handleKey_ = function(event) {
this.forwardTab_ = undefined;
if (event.keyCode === 27) {
event.preventDefault();
event.stopPropagation();
var cancelEvent = new supportCustomEvent('cancel', {
bubbles: false,
cancelable: true
});
var dpi = this.pendingDialogStack[0];
if (dpi && safeDispatchEvent(dpi.dialog, cancelEvent)) {
dpi.dialog.close();
}
} else if (event.keyCode === 9) {
this.forwardTab_ = !event.shiftKey;
}
};
/**
* Finds and downgrades any known modal dialogs that are no longer displayed. Dialogs that are
* removed and immediately readded don't stay modal, they become normal.
*
* @param {!Array<!HTMLDialogElement>} removed that have definitely been removed
*/
dialogPolyfill.DialogManager.prototype.checkDOM_ = function(removed) {
// This operates on a clone because it may cause it to change. Each change also calls
// updateStacking, which only actually needs to happen once. But who removes many modal dialogs
// at a time?!
var clone = this.pendingDialogStack.slice();
clone.forEach(function(dpi) {
if (removed.indexOf(dpi.dialog) !== -1) {
dpi.downgradeModal();
} else {
dpi.maybeHideModal();
}
});
};
/**
* @param {!dialogPolyfillInfo} dpi
* @return {boolean} whether the dialog was allowed
*/
dialogPolyfill.DialogManager.prototype.pushDialog = function(dpi) {
var allowed = (this.zIndexHigh_ - this.zIndexLow_) / 2 - 1;
if (this.pendingDialogStack.length >= allowed) {
return false;
}
if (this.pendingDialogStack.unshift(dpi) === 1) {
this.blockDocument();
}
this.updateStacking();
return true;
};
/**
* @param {!dialogPolyfillInfo} dpi
*/
dialogPolyfill.DialogManager.prototype.removeDialog = function(dpi) {
var index = this.pendingDialogStack.indexOf(dpi);
if (index === -1) { return; }
this.pendingDialogStack.splice(index, 1);
if (this.pendingDialogStack.length === 0) {
this.unblockDocument();
}
this.updateStacking();
};
dialogPolyfill.dm = new dialogPolyfill.DialogManager();
dialogPolyfill.formSubmitter = null;
dialogPolyfill.imagemapUseValue = null;
/**
* Installs global handlers, such as click listers and native method overrides. These are needed
* even if a no dialog is registered, as they deal with <form method="dialog">.
*/
if (window.HTMLDialogElement === undefined) {
/**
* If HTMLFormElement translates method="DIALOG" into 'get', then replace the descriptor with
* one that returns the correct value.
*/
var testForm = document.createElement('form');
testForm.setAttribute('method', 'dialog');
if (testForm.method !== 'dialog') {
var methodDescriptor = Object.getOwnPropertyDescriptor(HTMLFormElement.prototype, 'method');
if (methodDescriptor) {
// nb. Some older iOS and older PhantomJS fail to return the descriptor. Don't do anything
// and don't bother to update the element.
var realGet = methodDescriptor.get;
methodDescriptor.get = function() {
if (isFormMethodDialog(this)) {
return 'dialog';
}
return realGet.call(this);
};
var realSet = methodDescriptor.set;
/** @this {HTMLElement} */
methodDescriptor.set = function(v) {
if (typeof v === 'string' && v.toLowerCase() === 'dialog') {
return this.setAttribute('method', v);
}
return realSet.call(this, v);
};
Object.defineProperty(HTMLFormElement.prototype, 'method', methodDescriptor);
}
}
/**
* Global 'click' handler, to capture the <input type="submit"> or <button> element which has
* submitted a <form method="dialog">. Needed as Safari and others don't report this inside
* document.activeElement.
*/
document.addEventListener('click', function(ev) {
dialogPolyfill.formSubmitter = null;
dialogPolyfill.imagemapUseValue = null;
if (ev.defaultPrevented) { return; } // e.g. a submit which prevents default submission
var target = /** @type {Element} */ (ev.target);
if ('composedPath' in ev) {
var path = ev.composedPath();
target = path.shift() || target;
}
if (!target || !isFormMethodDialog(target.form)) { return; }
var valid = (target.type === 'submit' && ['button', 'input'].indexOf(target.localName) > -1);
if (!valid) {
if (!(target.localName === 'input' && target.type === 'image')) { return; }
// this is a <input type="image">, which can submit forms
dialogPolyfill.imagemapUseValue = ev.offsetX + ',' + ev.offsetY;
}
var dialog = findNearestDialog(target);
if (!dialog) { return; }
dialogPolyfill.formSubmitter = target;
}, false);
/**
* Global 'submit' handler. This handles submits of `method="dialog"` which are invalid, i.e.,
* outside a dialog. They get prevented.
*/
document.addEventListener('submit', function(ev) {
var form = ev.target;
var dialog = findNearestDialog(form);
if (dialog) {
return; // ignore, handle there
}
var submitter = findFormSubmitter(ev);
var formmethod = submitter && submitter.getAttribute('formmethod') || form.getAttribute('method');
if (formmethod === 'dialog') {
ev.preventDefault();
}
});
/**
* Replace the native HTMLFormElement.submit() method, as it won't fire the
* submit event and give us a chance to respond.
*/
var nativeFormSubmit = HTMLFormElement.prototype.submit;
var replacementFormSubmit = function () {
if (!isFormMethodDialog(this)) {
return nativeFormSubmit.call(this);
}
var dialog = findNearestDialog(this);
dialog && dialog.close();
};
HTMLFormElement.prototype.submit = replacementFormSubmit;
}
export default dialogPolyfill;

View File

@ -1884,6 +1884,7 @@ export async function reloadCurrentChat() {
export async function sendTextareaMessage() {
if (is_send_press) return;
if (isExecutingCommandsFromChatInput) return;
if (this_edit_mes_id) return; // don't proceed if editing a message
let generateType;
// "Continue on send" is activated when the user hits "send" (or presses enter) on an empty chat box, and the last
@ -10054,6 +10055,8 @@ jQuery(async function () {
}
else if (id == 'option_continue') {
if (this_edit_mes_id) return; // don't proceed if editing a message
if (is_send_press == false || fromSlashCommand) {
is_send_press = true;
Generate('continue', buildOrFillAdditionalArgs());
@ -10720,7 +10723,7 @@ jQuery(async function () {
$('.openDrawer').not('.pinnedOpen').addClass('resizing').slideToggle(200, 'swing', async function () {
await delay(50); $(this).closest('.drawer-content').removeClass('resizing');
});
$('.openIcon').toggleClass('closedIcon openIcon');
$('.openIcon').not('.drawerPinnedOpen').toggleClass('closedIcon openIcon');
$('.openDrawer').not('.pinnedOpen').toggleClass('closedDrawer openDrawer');
icon.toggleClass('openIcon closedIcon');
drawer.toggleClass('openDrawer closedDrawer');
@ -10806,7 +10809,7 @@ jQuery(async function () {
$('.openDrawer').not('.pinnedOpen').addClass('resizing').slideToggle(200, 'swing', function () {
$(this).closest('.drawer-content').removeClass('resizing');
});
$('.openIcon').toggleClass('closedIcon openIcon');
$('.openIcon').not('.drawerPinnedOpen').toggleClass('closedIcon openIcon');
$('.openDrawer').not('.pinnedOpen').toggleClass('closedDrawer openDrawer');
}

View File

@ -45,8 +45,11 @@ var LPanelPin = document.getElementById('lm_button_panel_pin');
var WIPanelPin = document.getElementById('WI_panel_pin');
var RightNavPanel = document.getElementById('right-nav-panel');
var RightNavDrawerIcon = document.getElementById('rightNavDrawerIcon');
var LeftNavPanel = document.getElementById('left-nav-panel');
var LeftNavDrawerIcon = document.getElementById('leftNavDrawerIcon');
var WorldInfo = document.getElementById('WorldInfo');
var WIDrawerIcon = document.getElementById('WIDrawerIcon');
var SelectedCharacterTab = document.getElementById('rm_button_selected_ch');
@ -761,13 +764,15 @@ export function initRossMods() {
if ($(RPanelPin).prop('checked') == true) {
//console.log('adding pin class to right nav');
$(RightNavPanel).addClass('pinnedOpen');
$(RightNavDrawerIcon).addClass('drawerPinnedOpen');
} else {
//console.log('removing pin class from right nav');
$(RightNavPanel).removeClass('pinnedOpen');
$(RightNavDrawerIcon).removeClass('drawerPinnedOpen');
if ($(RightNavPanel).hasClass('openDrawer') && $('.openDrawer').length > 1) {
$(RightNavPanel).slideToggle(200, 'swing');
//$(rightNavDrawerIcon).toggleClass('openIcon closedIcon');
$(RightNavDrawerIcon).toggleClass('openIcon closedIcon');
$(RightNavPanel).toggleClass('openDrawer closedDrawer');
}
}
@ -777,13 +782,15 @@ export function initRossMods() {
if ($(LPanelPin).prop('checked') == true) {
//console.log('adding pin class to Left nav');
$(LeftNavPanel).addClass('pinnedOpen');
$(LeftNavDrawerIcon).addClass('drawerPinnedOpen');
} else {
//console.log('removing pin class from Left nav');
$(LeftNavPanel).removeClass('pinnedOpen');
$(LeftNavDrawerIcon).removeClass('drawerPinnedOpen');
if ($(LeftNavPanel).hasClass('openDrawer') && $('.openDrawer').length > 1) {
$(LeftNavPanel).slideToggle(200, 'swing');
//$(leftNavDrawerIcon).toggleClass('openIcon closedIcon');
$(LeftNavDrawerIcon).toggleClass('openIcon closedIcon');
$(LeftNavPanel).toggleClass('openDrawer closedDrawer');
}
}
@ -794,14 +801,16 @@ export function initRossMods() {
if ($(WIPanelPin).prop('checked') == true) {
console.debug('adding pin class to WI');
$(WorldInfo).addClass('pinnedOpen');
$(WIDrawerIcon).addClass('drawerPinnedOpen');
} else {
console.debug('removing pin class from WI');
$(WorldInfo).removeClass('pinnedOpen');
$(WIDrawerIcon).removeClass('drawerPinnedOpen');
if ($(WorldInfo).hasClass('openDrawer') && $('.openDrawer').length > 1) {
console.debug('closing WI after lock removal');
$(WorldInfo).slideToggle(200, 'swing');
//$(WorldInfoDrawerIcon).toggleClass('openIcon closedIcon');
$(WIDrawerIcon).toggleClass('openIcon closedIcon');
$(WorldInfo).toggleClass('openDrawer closedDrawer');
}
}
@ -812,20 +821,24 @@ export function initRossMods() {
if (LoadLocalBool('NavLockOn') == true) {
//console.log('setting pin class via local var');
$(RightNavPanel).addClass('pinnedOpen');
$(RightNavDrawerIcon).addClass('drawerPinnedOpen');
}
if ($(RPanelPin).prop('checked')) {
console.debug('setting pin class via checkbox state');
$(RightNavPanel).addClass('pinnedOpen');
$(RightNavDrawerIcon).addClass('drawerPinnedOpen');
}
// read the state of left Nav Lock and apply to leftnav classlist
$(LPanelPin).prop('checked', LoadLocalBool('LNavLockOn'));
if (LoadLocalBool('LNavLockOn') == true) {
//console.log('setting pin class via local var');
$(LeftNavPanel).addClass('pinnedOpen');
$(LeftNavDrawerIcon).addClass('drawerPinnedOpen');
}
if ($(LPanelPin).prop('checked')) {
console.debug('setting pin class via checkbox state');
$(LeftNavPanel).addClass('pinnedOpen');
$(LeftNavDrawerIcon).addClass('drawerPinnedOpen');
}
// read the state of left Nav Lock and apply to leftnav classlist
@ -833,11 +846,13 @@ export function initRossMods() {
if (LoadLocalBool('WINavLockOn') == true) {
//console.log('setting pin class via local var');
$(WorldInfo).addClass('pinnedOpen');
$(WIDrawerIcon).addClass('drawerPinnedOpen');
}
if ($(WIPanelPin).prop('checked')) {
console.debug('setting pin class via checkbox state');
$(WorldInfo).addClass('pinnedOpen');
$(WIDrawerIcon).addClass('drawerPinnedOpen');
}
//save state of Right nav being open or closed
@ -1027,6 +1042,7 @@ export function initRossMods() {
const editMesDone = $('.mes_edit_done:visible');
if (editMesDone.length > 0) {
console.debug('Accepting edits with Ctrl+Enter');
$('#send_textarea').focus();
editMesDone.trigger('click');
return;
} else if (is_send_press == false) {

View File

@ -3931,8 +3931,6 @@ async function onModelChange() {
$('#openai_max_context').attr('max', max_2mil);
} else if (value.includes('gemini-1.5-pro')) {
$('#openai_max_context').attr('max', max_2mil);
} else if (value.match('gemini-1.5-flash-002')) {
$('#openai_max_context').attr('max', max_2mil);
} else if (value.includes('gemini-1.5-flash')) {
$('#openai_max_context').attr('max', max_1mil);
} else if (value.includes('gemini-1.0-pro-vision') || value === 'gemini-pro-vision') {
@ -4584,6 +4582,7 @@ export function isImageInliningSupported() {
'gemini-1.5-flash-001',
'gemini-1.5-flash-002',
'gemini-1.5-flash-exp-0827',
'gemini-1.5-flash-8b',
'gemini-1.5-flash-8b-exp-0827',
'gemini-1.5-flash-8b-exp-0924',
'gemini-1.0-pro-vision-latest',

View File

@ -1,3 +1,4 @@
import dialogPolyfill from '../lib/dialog-polyfill.esm.js';
import { shouldSendOnEnter } from './RossAscends-mods.js';
import { power_user } from './power-user.js';
import { removeFromArray, runAfterAnimation, uuidv4 } from './utils.js';
@ -178,6 +179,10 @@ export class Popup {
const template = document.querySelector('#popup_template');
// @ts-ignore
this.dlg = template.content.cloneNode(true).querySelector('.popup');
if (!this.dlg.showModal) {
this.dlg.classList.add('poly_dialog');
dialogPolyfill.registerDialog(this.dlg);
}
this.body = this.dlg.querySelector('.popup-body');
this.content = this.dlg.querySelector('.popup-content');
this.mainInput = this.dlg.querySelector('.popup-input');

View File

@ -324,6 +324,7 @@ function convertGooglePrompt(messages, model, useSysPrompt = false, charName = '
'gemini-1.5-flash-001',
'gemini-1.5-flash-002',
'gemini-1.5-flash-exp-0827',
'gemini-1.5-flash-8b',
'gemini-1.5-flash-8b-exp-0827',
'gemini-1.5-flash-8b-exp-0924',
'gemini-1.5-pro',