mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Compare commits
49 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
c299ef0a86 | ||
|
0dbf219116 | ||
|
b3e1dd8a42 | ||
|
737bb5a030 | ||
|
cf9248121f | ||
|
4282cb51dd | ||
|
efc76dfd05 | ||
|
8cd6467aa9 | ||
|
a1b8c135e4 | ||
|
2b69c1793c | ||
|
861e9a31e5 | ||
|
5b926d4ad3 | ||
|
77f76ae315 | ||
|
c3af38f5b8 | ||
|
3cf23f277c | ||
|
a74828df15 | ||
|
bf7f04e3b2 | ||
|
5df7d2d1dc | ||
|
83c875d8dc | ||
|
72b7b7cab2 | ||
|
55f38f69d6 | ||
|
0633d16622 | ||
|
35cb1f6182 | ||
|
a18c20305e | ||
|
d542ec0d81 | ||
|
6ad0be9597 | ||
|
0de09e9da0 | ||
|
bb187d9920 | ||
|
711dbdcc15 | ||
|
5215e6e437 | ||
|
01c27bc9a9 | ||
|
b35d8a4324 | ||
|
6c6f5b7f1a | ||
|
cff5cd0928 | ||
|
fb1b02571e | ||
|
412fad002d | ||
|
6ad2492ef6 | ||
|
d3b0ba02b6 | ||
|
d80fff3b5e | ||
|
b359dd1e81 | ||
|
cdaa14964e | ||
|
747567466f | ||
|
d95786fb25 | ||
|
e0fdd1513c | ||
|
3f9ccff2bc | ||
|
c9c82537a7 | ||
|
ed74eedc5b | ||
|
8fdfb272a4 | ||
|
b1927d454c |
297
.github/readme.md
vendored
Normal file
297
.github/readme.md
vendored
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|

|
||||||
|
|
||||||
|
Mobile-friendly, Multi-API (KoboldAI/CPP, Horde, NovelAI, Ooba, OpenAI+proxies, Poe, WindowAI(Claude!)), VN-like Waifu Mode, Horde SD, System TTS, WorldInfo (lorebooks), customizable UI, auto-translate, and more prompt options than you'd ever want or need. Optional Extras server for more SD/TTS options + ChromaDB/Summarize.
|
||||||
|
|
||||||
|
Based on a fork of TavernAI 1.2.8
|
||||||
|
|
||||||
|
### Brought to you by Cohee, RossAscends and the SillyTavern community
|
||||||
|
|
||||||
|
NOTE: We have added [a FAQ](faq.md) to answer most of your questions and help you get started.
|
||||||
|
|
||||||
|
### What is SillyTavern or TavernAI?
|
||||||
|
|
||||||
|
Tavern is a user interface you can install on your computer (and Android phones) that allows you to interact with text generation AIs and chat/roleplay with characters you or the community create.
|
||||||
|
|
||||||
|
SillyTavern is a fork of TavernAI 1.2.8 which is under more active development and has added many major features. At this point, they can be thought of as completely independent programs.
|
||||||
|
|
||||||
|
### What do I need other than Tavern?
|
||||||
|
|
||||||
|
On its own Tavern is useless, as it's just a user interface. You have to have access to an AI system backend that can act as the roleplay character. There are various supported backends: OpenAPI API (GPT), KoboldAI (either running locally or on Google Colab), and more. You can read more about this in [the FAQ](faq.md).
|
||||||
|
|
||||||
|
### Do I need a powerful PC to run Tavern?
|
||||||
|
|
||||||
|
Since Tavern is only a user interface, it has tiny hardware requirements, it will run on anything. It's the AI system backend that needs to be powerful.
|
||||||
|
|
||||||
|
## Mobile support
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
|
||||||
|
> **This fork can be run natively on Android phones using Termux. Please refer to this guide by ArroganceComplex#2659:**
|
||||||
|
|
||||||
|
<https://rentry.org/STAI-Termux>
|
||||||
|
|
||||||
|
**.webp character cards import/export is not supported in Termux. Use either JSON or PNG formats instead.**
|
||||||
|
|
||||||
|
## Questions or suggestions?
|
||||||
|
|
||||||
|
### We now have a community Discord server
|
||||||
|
|
||||||
|
Get support, share favorite characters and prompts:
|
||||||
|
|
||||||
|
### [Join](https://discord.gg/RZdyAEUPvj)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
Get in touch with the developers directly:
|
||||||
|
|
||||||
|
* Discord: Cohee#1207 or RossAscends#1779
|
||||||
|
* Reddit: /u/RossAscends or /u/sillylossy
|
||||||
|
* [Post a GitHub issue](https://github.com/Cohee1207/SillyTavern/issues)
|
||||||
|
|
||||||
|
## This version includes
|
||||||
|
|
||||||
|
* A heavily modified TavernAI 1.2.8 (more than 50% of code rewritten or optimized)
|
||||||
|
* Swipes
|
||||||
|
* Group chats: multi-bot rooms for characters to talk to you or each other
|
||||||
|
* Chat bookmarks / branching (duplicates the dialogue in its current state)
|
||||||
|
* Advanced KoboldAI / TextGen generation settings with a lot of community-made presets
|
||||||
|
* World Info support: create a rich lore or save tokens on your character card
|
||||||
|
* Window AI browser extension support (run models like Claude, GPT 4): https://windowai.io/
|
||||||
|
* [Oobabooga's TextGen WebUI](https://github.com/oobabooga/text-generation-webui) API connection
|
||||||
|
* [AI Horde](https://horde.koboldai.net/) connection
|
||||||
|
* [Poe.com](https://poe.com) (ChatGPT / Claude) connection
|
||||||
|
* Soft prompts selector for KoboldAI
|
||||||
|
* Prompt generation formatting tweaking
|
||||||
|
* webp character card interoperability (PNG is still an internal format)
|
||||||
|
|
||||||
|
## Extensions
|
||||||
|
|
||||||
|
SillyTavern has an extensibility support, with some additional AI modules hosted via [SillyTavern Extras API](https://github.com/SillyTavern/SillyTavern-extras)
|
||||||
|
|
||||||
|
* Author's Note / Character Bias
|
||||||
|
* Character emotional expressions
|
||||||
|
* Auto-Summary of the chat history
|
||||||
|
* Sending images to chat, and the AI interpreting the content.
|
||||||
|
* Stable Diffusion image generation (5 chat-related presets plus 'free mode')
|
||||||
|
* Text-to-speech for AI response messages (via ElevenLabs, Silero, or the OS's System TTS)
|
||||||
|
|
||||||
|
Full list of included extenisons and tutorials how to use them can be found on [Wiki](https://github.com/SillyTavern/SillyTavern/wiki).
|
||||||
|
|
||||||
|
## UI/CSS/Quality of Life tweaks by RossAscends
|
||||||
|
|
||||||
|
* Mobile UI with optimized for iOS, and supports saving a shortcut to home screen and opening in fullscreen mode.
|
||||||
|
* HotKeys
|
||||||
|
* Up = Edit last message in chat
|
||||||
|
* Ctrl+Up = Edit last USER message in chat
|
||||||
|
* Left = swipe left
|
||||||
|
* Right = swipe right (NOTE: swipe hotkeys are disabled when chatbar has something typed into it)
|
||||||
|
* Ctrl+Left = view locally stored variables (in the browser console window)
|
||||||
|
* Enter (with chat bar selected) = send your message to AI
|
||||||
|
* Ctrl+Enter = Regenerate the last AI response
|
||||||
|
|
||||||
|
* User Name Changes and Character Deletion no longer force the page to refresh.
|
||||||
|
|
||||||
|
* Toggle option to automatically connect to API on page load.
|
||||||
|
* Toggle option to automatically load the most recently viewed character on page load.
|
||||||
|
* Better Token Counter - works on unsaved characters, and shows both permanent and temporary tokens.
|
||||||
|
|
||||||
|
* Better Past Chats View
|
||||||
|
* New Chat filenames are saved in a readable format of "(character) - (when it was created)"
|
||||||
|
* Chat preview increased from 40 characters to 300.
|
||||||
|
* Multiple options for characters list sorting (by name, creation date, chat sizes).
|
||||||
|
|
||||||
|
* By default the left and right settings panel will close when you click away from it.
|
||||||
|
* Clicking the Lock on the nav panel will hold the panel open, and this setting be remembered across sessions.
|
||||||
|
* Nav panel status of open or closed will also be saved across sessions.
|
||||||
|
|
||||||
|
* Customizable chat UI:
|
||||||
|
* Play a sound when a new message arrives
|
||||||
|
* Switch between round or rectangle avatar styles
|
||||||
|
* Have a wider chat window on the desktop
|
||||||
|
* Optional semi-transparent glass-like panels
|
||||||
|
* Customizable page colors for 'main text', 'quoted text' 'italics text'.
|
||||||
|
* Customizable UI background color and blur amount
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
*NOTE: This software is intended for local install purposes, and has not been thoroughly tested on a colab or other cloud notebook service.*
|
||||||
|
|
||||||
|
> **Warning**
|
||||||
|
|
||||||
|
> DO NOT INSTALL INTO ANY WINDOWS CONTROLLED FOLDER (Program Files, System32, etc).
|
||||||
|
|
||||||
|
> DO NOT RUN START.BAT WITH ADMIN PERMISSIONS
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
Installing via Git (recommended for easy updating)
|
||||||
|
|
||||||
|
Easy to follow guide with pretty pictures:
|
||||||
|
<https://docs.alpindale.dev/pygmalion-extras/sillytavern/#windows-installation>
|
||||||
|
|
||||||
|
1. Install [NodeJS](https://nodejs.org/en) (latest LTS version is recommended)
|
||||||
|
2. Install [GitHub Desktop](https://central.github.com/deployments/desktop/desktop/latest/win32)
|
||||||
|
3. Open Windows Explorer (`Win+E`)
|
||||||
|
4. Browse to or Create a folder that is not controlled or monitored by Windows. (ex: C:\MySpecialFolder\)
|
||||||
|
5. Open a Command Prompt inside that folder by clicking in the 'Address Bar' at the top, typing `cmd`, and pressing Enter.
|
||||||
|
6. Once the black box (Command Prompt) pops up, type ONE of the following into it and press Enter:
|
||||||
|
|
||||||
|
* for Main Branch: `git clone https://github.com/Cohee1207/SillyTavern -b main`
|
||||||
|
* for Dev Branch: `git clone https://github.com/Cohee1207/SillyTavern -b dev`
|
||||||
|
|
||||||
|
7. Once everything is cloned, double click `Start.bat` to make NodeJS install its requirements.
|
||||||
|
8. The server will then start, and SillyTavern will popup in your browser.
|
||||||
|
|
||||||
|
Installing via zip download
|
||||||
|
|
||||||
|
1. Install [NodeJS](https://nodejs.org/en) (latest LTS version is recommended)
|
||||||
|
2. Download the zip from this GitHub repo. (Get the `Source code (zip)` from [Releases](https://github.com/Cohee1207/SillyTavern/releases/latest))
|
||||||
|
3. Unzip it into a folder of your choice
|
||||||
|
4. Run `Start.bat` via double-clicking or in a command line.
|
||||||
|
5. Once the server has prepared everything for you, it will open a tab in your browser.
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
|
||||||
|
1. Run the `start.sh` script.
|
||||||
|
2. Enjoy.
|
||||||
|
|
||||||
|
## API keys management
|
||||||
|
|
||||||
|
SillyTavern saves your API keys to a `secrets.json` file in the server directory.
|
||||||
|
|
||||||
|
By default they will not be exposed to a frontend after you enter them and reload the page.
|
||||||
|
|
||||||
|
In order to enable viewing your keys by clicking a button in the API block:
|
||||||
|
|
||||||
|
1. Set the value of `allowKeysExposure` to `true` in `config.conf` file.
|
||||||
|
2. Restart the SillyTavern server.
|
||||||
|
|
||||||
|
## Remote connections
|
||||||
|
|
||||||
|
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 wifi network.
|
||||||
|
|
||||||
|
However, it can be used to allow remote connections from anywhere as well.
|
||||||
|
|
||||||
|
**IMPORTANT: SillyTavern is a single-user program, so anyone who logs in will be able to see all characters and chats, and be able to change any settings inside the UI.**
|
||||||
|
|
||||||
|
### 1. Managing whitelisted IPs
|
||||||
|
|
||||||
|
* Create a new text file inside your SillyTavern base install folder called `whitelist.txt`.
|
||||||
|
* Open the file in a text editor, add a list of IPs you want to be allowed to connect.
|
||||||
|
|
||||||
|
*Both indidivual IPs, and wildcard IP ranges are accepted. Examples:*
|
||||||
|
|
||||||
|
```txt
|
||||||
|
192.168.0.1
|
||||||
|
192.168.0.20
|
||||||
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
```txt
|
||||||
|
192.168.0.*
|
||||||
|
```
|
||||||
|
|
||||||
|
(the above wildcard IP range will allow any device on the local network to connect)
|
||||||
|
|
||||||
|
CIDR masks are also accepted (eg. 10.0.0.0/24).
|
||||||
|
|
||||||
|
* Save the `whitelist.txt` file.
|
||||||
|
* Restart your TAI server.
|
||||||
|
|
||||||
|
Now devices which have the IP specified in the file will be able to connect.
|
||||||
|
|
||||||
|
*Note: `config.conf` also has a `whitelist` array, which you can use in the same way, but this array will be ignored if `whitelist.txt` exists.*
|
||||||
|
|
||||||
|
### 2. Getting the IP for the ST host machine
|
||||||
|
|
||||||
|
After the whitelist has been setup, you'll need the IP of the ST-hosting device.
|
||||||
|
|
||||||
|
If the ST-hosting device is on the same wifi network, you will use the ST-host's internal wifi IP:
|
||||||
|
|
||||||
|
* For Windows: windows button > type `cmd.exe` in the search bar > type `ipconfig` in the console, hit Enter > look for `IPv4` listing.
|
||||||
|
|
||||||
|
If you (or someone else) wants to connect to your hosted ST while not being on the same network, you will need the public IP of your ST-hosting device.
|
||||||
|
|
||||||
|
* While using the ST-hosting device, access [this page](https://whatismyipaddress.com/) and look for for `IPv4`. This is what you would use to connect from the remote device.
|
||||||
|
|
||||||
|
### 3. Connect the remote device to the ST host machine.
|
||||||
|
|
||||||
|
Whatever IP you ended up with for your situation, you will put that IP address and port number into the remote device's web browser.
|
||||||
|
|
||||||
|
A typical address for an ST host on the same wifi network would look like:
|
||||||
|
|
||||||
|
`http://192.168.0.5:8000`
|
||||||
|
|
||||||
|
Use http:// NOT https://
|
||||||
|
|
||||||
|
### Opening your ST to all IPs
|
||||||
|
|
||||||
|
We do not recommend doing this, but you can open `config.conf` and change `whitelist` to `false`.
|
||||||
|
|
||||||
|
You must remove (or rename) `whitelist.txt` in the SillyTavern base install folder, if it exists.
|
||||||
|
|
||||||
|
This is usually an insecure practice, so we require you to set a username and password when you do this.
|
||||||
|
|
||||||
|
The username and password are set in `config.conf`.
|
||||||
|
|
||||||
|
After restarting your ST server, any device will be able to connect to it, regardless of their IP as long as they know the username and password.
|
||||||
|
|
||||||
|
### Still Unable To Connect?
|
||||||
|
|
||||||
|
* Create an inbound/outbound firewall rule for the port found in `config.conf`. Do NOT mistake this for portforwarding on your router, otherwise someone could find your chat logs and that's a big no-no.
|
||||||
|
* Enable the Private Network profile type in Settings > Network and Internet > Ethernet. This is VERY important for Windows 11, otherwise you would be unable to connect even with the aforementioned firewall rules.
|
||||||
|
|
||||||
|
## Performance issues?
|
||||||
|
|
||||||
|
Try enabling the No Blur Effect (Fast UI) mode on the User settings panel.
|
||||||
|
|
||||||
|
## I like your project! How do I contribute?
|
||||||
|
|
||||||
|
### DO's
|
||||||
|
|
||||||
|
1. Send pull requests
|
||||||
|
2. Send feature suggestions and issue reports using established templates
|
||||||
|
3. Read the readme file and built-in documentation before asking anything
|
||||||
|
|
||||||
|
### DONT's
|
||||||
|
|
||||||
|
1. Offer monetary donations
|
||||||
|
2. Send bug reports without providing any context
|
||||||
|
3. Ask the questions that were already answered numerous times
|
||||||
|
|
||||||
|
## Where can I find the old backgrounds?
|
||||||
|
|
||||||
|
We're moving to 100% original content only policy, so old background images have been removed from this repository.
|
||||||
|
|
||||||
|
You can find them archived here:
|
||||||
|
|
||||||
|
<https://files.catbox.moe/1xevnc.zip>
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
<img width="400" alt="image" src="https://user-images.githubusercontent.com/18619528/228649245-8061c60f-63dc-488e-9325-f151b7a3ec2d.png">
|
||||||
|
<img width="400" alt="image" src="https://user-images.githubusercontent.com/18619528/228649856-fbdeef05-d727-4d5a-be80-266cbbc6b811.png">
|
||||||
|
|
||||||
|
## License and credits
|
||||||
|
|
||||||
|
**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.**
|
||||||
|
|
||||||
|
* TAI Base by Humi: Unknown license
|
||||||
|
* Cohee's modifications and derived code: AGPL v3
|
||||||
|
* RossAscends' additions: AGPL v3
|
||||||
|
* Portions of CncAnon's TavernAITurbo mod: Unknown license
|
||||||
|
* Waifu mode inspired by the work of PepperTaco (<https://github.com/peppertaco/Tavern/>)
|
||||||
|
* Thanks Pygmalion University for being awesome testers and suggesting cool features!
|
||||||
|
* Thanks oobabooga for compiling presets for TextGen
|
||||||
|
* poe-api client adapted from <https://github.com/ading2210/poe-api> (GPL v3)
|
||||||
|
* GraphQL files for poe: <https://github.com/muharamdani/poe> (ISC License)
|
||||||
|
* KoboldAI Presets from KAI Lite: <https://lite.koboldai.net/>
|
||||||
|
* Noto Sans font by Google (OFL license)
|
||||||
|
* Icon theme by Font Awesome <https://fontawesome.com> (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||||
|
* AI Horde client library by ZeldaFan0225: https://github.com/ZeldaFan0225/ai_horde
|
||||||
|
* Linux startup script by AlpinDale
|
||||||
|
* Thanks paniphons for providing a FAQ document
|
43
.github/workflows/update-docs.yml
vendored
Normal file
43
.github/workflows/update-docs.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
name: Update SillyTavern-Docs
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update_docs:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout current repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Checkout SillyTavern-Docs repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
repository: SillyTavern/SillyTavern-Docs
|
||||||
|
path: SillyTavern-Docs
|
||||||
|
|
||||||
|
- name: Clone SillyTavern wiki into SillyTavern-Docs/extensions
|
||||||
|
run: rm -rf SillyTavern-Docs/extensions && git clone https://github.com/SillyTavern/SillyTavern.wiki.git SillyTavern-Docs/extensions && rm -rf SillyTavern-Docs/extensions/.git
|
||||||
|
|
||||||
|
- name: Copy files
|
||||||
|
run: |
|
||||||
|
cp public/notes/content.md SillyTavern-Docs/guidebook.md
|
||||||
|
cp faq.md SillyTavern-Docs/faq.md
|
||||||
|
cp readme.md SillyTavern-Docs/readme.md
|
||||||
|
cp public/notes/update.md SillyTavern-Docs/update.md
|
||||||
|
|
||||||
|
- name: Deploy to external repository
|
||||||
|
uses: cpina/github-action-push-to-another-repository@main
|
||||||
|
env:
|
||||||
|
SSH_DEPLOY_KEY: ${{ secrets.SSH_DEPLOY_KEY }}
|
||||||
|
with:
|
||||||
|
# GitHub Action output files
|
||||||
|
source-directory: SillyTavern-Docs/
|
||||||
|
destination-github-username: SillyTavern
|
||||||
|
destination-repository-name: SillyTavern-Docs
|
||||||
|
user-email: github-actions[bot]@users.noreply.github.com
|
||||||
|
user-name: "GitHub Actions"
|
||||||
|
target-branch: "main"
|
@@ -23,7 +23,7 @@ COPY . ./
|
|||||||
|
|
||||||
# Copy default chats, characters and user avatars to <folder>.default folder
|
# Copy default chats, characters and user avatars to <folder>.default folder
|
||||||
RUN \
|
RUN \
|
||||||
IFS="," RESOURCES="characters,chats,groups,group chats,User Avatars,settings.json" && \
|
IFS="," RESOURCES="characters,chats,groups,group chats,User Avatars,worlds,settings.json" && \
|
||||||
\
|
\
|
||||||
echo "*** Store default $RESOURCES in <folder>.default ***" && \
|
echo "*** Store default $RESOURCES in <folder>.default ***" && \
|
||||||
for R in $RESOURCES; do mv "public/$R" "public/$R.default"; done && \
|
for R in $RESOURCES; do mv "public/$R" "public/$R.default"; done && \
|
||||||
|
@@ -3,6 +3,7 @@ pushd %~dp0
|
|||||||
git --version > nul 2>&1
|
git --version > nul 2>&1
|
||||||
if %errorlevel% neq 0 (
|
if %errorlevel% neq 0 (
|
||||||
echo Git is not installed on this system. Skipping update.
|
echo Git is not installed on this system. Skipping update.
|
||||||
|
echo If you installed with a zip file, you will need to download the new zip and install it manually.
|
||||||
) else (
|
) else (
|
||||||
call git pull --rebase --autostash
|
call git pull --rebase --autostash
|
||||||
if %errorlevel% neq 0 (
|
if %errorlevel% neq 0 (
|
||||||
|
@@ -4,7 +4,7 @@ services:
|
|||||||
build: ..
|
build: ..
|
||||||
container_name: sillytavern
|
container_name: sillytavern
|
||||||
hostname: sillytavern
|
hostname: sillytavern
|
||||||
image: cohee1207/sillytavern:latest
|
image: sillytavern/sillytavern:latest
|
||||||
ports:
|
ports:
|
||||||
- "8000:8000"
|
- "8000:8000"
|
||||||
volumes:
|
volumes:
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
# Initialize missing user files
|
# Initialize missing user files
|
||||||
IFS="," RESOURCES="characters,groups,group chats,chats,User Avatars,settings.json"
|
IFS="," RESOURCES="characters,groups,group chats,chats,User Avatars,worlds,settings.json"
|
||||||
for R in $RESOURCES; do
|
for R in $RESOURCES; do
|
||||||
if [ ! -e "config/$R" ]; then
|
if [ ! -e "config/$R" ]; then
|
||||||
echo "Resource not found, copying from defaults: $R"
|
echo "Resource not found, copying from defaults: $R"
|
||||||
|
2
faq.md
2
faq.md
@@ -91,7 +91,7 @@ These base instructions are only for OpenAI, which is a paid service. You can fi
|
|||||||
|
|
||||||
### Install Tavern
|
### Install Tavern
|
||||||
|
|
||||||
1. Install the latest NodeJS from https://nodejs.org/en/download/current
|
1. Install the NodeJS LTS from https://nodejs.org/en/download
|
||||||
1. If you know how to use git, clone https://github.com/Cohee1207/SillyTavern. Otherwise, browse to https://github.com/Cohee1207/SillyTavern/releases , download the zip file containing the source code, then extract it locally.
|
1. If you know how to use git, clone https://github.com/Cohee1207/SillyTavern. Otherwise, browse to https://github.com/Cohee1207/SillyTavern/releases , download the zip file containing the source code, then extract it locally.
|
||||||
1. Run Start.bat on Windows, or start.sh on OSX/Linux
|
1. Run Start.bat on Windows, or start.sh on OSX/Linux
|
||||||
1. Your browser should have opened to the Tavern UI. This webpage is running locally on your computer.
|
1. Your browser should have opened to the Tavern UI. This webpage is running locally on your computer.
|
||||||
|
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "sillytavern",
|
"name": "sillytavern",
|
||||||
"version": "1.6.0",
|
"version": "1.6.6",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "sillytavern",
|
"name": "sillytavern",
|
||||||
"version": "1.6.0",
|
"version": "1.6.6",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dqbd/tiktoken": "^1.0.2",
|
"@dqbd/tiktoken": "^1.0.2",
|
||||||
|
@@ -46,7 +46,7 @@
|
|||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/Cohee1207/SillyTavern.git"
|
"url": "https://github.com/Cohee1207/SillyTavern.git"
|
||||||
},
|
},
|
||||||
"version": "1.6.0",
|
"version": "1.6.6",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node server.js",
|
"start": "node server.js",
|
||||||
"pkg": "pkg --compress Gzip --no-bytecode --public ."
|
"pkg": "pkg --compress Gzip --no-bytecode --public ."
|
||||||
|
@@ -318,14 +318,14 @@ class Client {
|
|||||||
if (!viewer.availableBots) {
|
if (!viewer.availableBots) {
|
||||||
throw new Error('Invalid token.');
|
throw new Error('Invalid token.');
|
||||||
}
|
}
|
||||||
const botList = viewer.availableBots;
|
const botList = viewer.viewerBotList;
|
||||||
const retries = 2;
|
const retries = 2;
|
||||||
const bots = {};
|
const bots = {};
|
||||||
for (const bot of botList.filter(x => x.deletionState == 'not_deleted')) {
|
for (const bot of botList.filter(x => x.deletionState == 'not_deleted')) {
|
||||||
try {
|
try {
|
||||||
const url = `https://poe.com/_next/data/${this.next_data.buildId}/${bot.displayName}.json`;
|
const url = `https://poe.com/_next/data/${this.next_data.buildId}/${bot.displayName}.json`;
|
||||||
let r;
|
let r;
|
||||||
|
|
||||||
if (this.use_cached_bots && cached_bots[url]) {
|
if (this.use_cached_bots && cached_bots[url]) {
|
||||||
r = cached_bots[url];
|
r = cached_bots[url];
|
||||||
}
|
}
|
||||||
@@ -334,7 +334,7 @@ class Client {
|
|||||||
r = await request_with_retries(() => this.session.get(url), retries);
|
r = await request_with_retries(() => this.session.get(url), retries);
|
||||||
cached_bots[url] = r;
|
cached_bots[url] = r;
|
||||||
}
|
}
|
||||||
|
|
||||||
const chatData = r.data.pageProps.payload.chatOfBotDisplayName;
|
const chatData = r.data.pageProps.payload.chatOfBotDisplayName;
|
||||||
bots[chatData.defaultBotObject.nickname] = chatData;
|
bots[chatData.defaultBotObject.nickname] = chatData;
|
||||||
}
|
}
|
||||||
@@ -640,4 +640,4 @@ class Client {
|
|||||||
|
|
||||||
load_queries();
|
load_queries();
|
||||||
|
|
||||||
module.exports = { Client };
|
module.exports = { Client };
|
||||||
|
1
public/group chats/README.md
Normal file
1
public/group chats/README.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Put Group Chat JSONL files here
|
1
public/groups/README.md
Normal file
1
public/groups/README.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Put Group JSON files here
|
117
public/script.js
117
public/script.js
@@ -26,6 +26,7 @@ import {
|
|||||||
setWorldInfoSettings,
|
setWorldInfoSettings,
|
||||||
deleteWorldInfo,
|
deleteWorldInfo,
|
||||||
world_info_recursive,
|
world_info_recursive,
|
||||||
|
world_info_case_sensitive,
|
||||||
} from "./scripts/world-info.js";
|
} from "./scripts/world-info.js";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -1104,6 +1105,14 @@ function getMessageFromTemplate({ mesId, characterName, isUser, avatarImg, bias,
|
|||||||
return mes;
|
return mes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function updateMessageBlock(messageId, message) {
|
||||||
|
const messageElement = $(`#chat [mesid="${messageId}"]`);
|
||||||
|
const text = message?.extra?.display_text ?? message.mes;
|
||||||
|
messageElement.find('.mes_text').html(messageFormatting(text, message.name, message.is_system, message.is_user));
|
||||||
|
addCopyToCodeBlocks(messageElement)
|
||||||
|
appendImageToMessage(message, messageElement);
|
||||||
|
}
|
||||||
|
|
||||||
export function appendImageToMessage(mes, messageElement) {
|
export function appendImageToMessage(mes, messageElement) {
|
||||||
if (mes.extra?.image) {
|
if (mes.extra?.image) {
|
||||||
const image = messageElement.find('.mes_img');
|
const image = messageElement.find('.mes_img');
|
||||||
@@ -1117,7 +1126,7 @@ export function appendImageToMessage(mes, messageElement) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addCopyToCodeBlocks(messageElement) {
|
export function addCopyToCodeBlocks(messageElement) {
|
||||||
const codeBlocks = $(messageElement).find("pre code");
|
const codeBlocks = $(messageElement).find("pre code");
|
||||||
for (let i = 0; i < codeBlocks.length; i++) {
|
for (let i = 0; i < codeBlocks.length; i++) {
|
||||||
hljs.highlightElement(codeBlocks.get(i));
|
hljs.highlightElement(codeBlocks.get(i));
|
||||||
@@ -1756,8 +1765,9 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
|||||||
abortController = new AbortController();
|
abortController = new AbortController();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OpenAI doesn't need instruct mode. Use OAI main prompt instead.
|
||||||
|
const isInstruct = power_user.instruct.enabled && main_api !== 'openai';
|
||||||
const isImpersonate = type == "impersonate";
|
const isImpersonate = type == "impersonate";
|
||||||
const isInstruct = power_user.instruct.enabled;
|
|
||||||
|
|
||||||
message_already_generated = isImpersonate ? `${name1}: ` : `${name2}: `;
|
message_already_generated = isImpersonate ? `${name1}: ` : `${name2}: `;
|
||||||
// Name for the multigen prefix
|
// Name for the multigen prefix
|
||||||
@@ -2023,7 +2033,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
|||||||
let mesSend = [];
|
let mesSend = [];
|
||||||
console.log('calling runGenerate');
|
console.log('calling runGenerate');
|
||||||
streamingProcessor = isStreamingEnabled() ? new StreamingProcessor(type, force_name2) : false;
|
streamingProcessor = isStreamingEnabled() ? new StreamingProcessor(type, force_name2) : false;
|
||||||
await runGenerate();
|
runGenerate();
|
||||||
|
|
||||||
async function runGenerate(cycleGenerationPromt = '') {
|
async function runGenerate(cycleGenerationPromt = '') {
|
||||||
is_send_press = true;
|
is_send_press = true;
|
||||||
@@ -2377,7 +2387,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
|||||||
$('#send_textarea').val(extract.getMessage).trigger('input');
|
$('#send_textarea').val(extract.getMessage).trigger('input');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldContinueMultigen(getMessage, isImpersonate)) {
|
if (shouldContinueMultigen(getMessage, isImpersonate, isInstruct)) {
|
||||||
hideSwipeButtons();
|
hideSwipeButtons();
|
||||||
tokens_already_generated += this_amount_gen; // add new gen amt to any prev gen counter..
|
tokens_already_generated += this_amount_gen; // add new gen amt to any prev gen counter..
|
||||||
getMessage = message_already_generated;
|
getMessage = message_already_generated;
|
||||||
@@ -2504,7 +2514,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
|
|||||||
//console.log('generate ending');
|
//console.log('generate ending');
|
||||||
} //generate ends
|
} //generate ends
|
||||||
|
|
||||||
function getBiasStrings(textareaText) {
|
export function getBiasStrings(textareaText) {
|
||||||
let promptBias = '';
|
let promptBias = '';
|
||||||
let messageBias = extractMessageBias(textareaText);
|
let messageBias = extractMessageBias(textareaText);
|
||||||
|
|
||||||
@@ -2543,7 +2553,7 @@ export function replaceBiasMarkup(str) {
|
|||||||
return (str ?? '').replace(/{{(\*?.*\*?)}}/g, '');
|
return (str ?? '').replace(/{{(\*?.*\*?)}}/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendMessageAsUser(textareaText, messageBias) {
|
export async function sendMessageAsUser(textareaText, messageBias) {
|
||||||
chat[chat.length] = {};
|
chat[chat.length] = {};
|
||||||
chat[chat.length - 1]['name'] = name1;
|
chat[chat.length - 1]['name'] = name1;
|
||||||
chat[chat.length - 1]['is_user'] = true;
|
chat[chat.length - 1]['is_user'] = true;
|
||||||
@@ -3016,8 +3026,8 @@ function getGenerateUrl() {
|
|||||||
return generate_url;
|
return generate_url;
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldContinueMultigen(getMessage, isImpersonate) {
|
function shouldContinueMultigen(getMessage, isImpersonate, isInstruct) {
|
||||||
if (power_user.instruct.enabled && power_user.instruct.stop_sequence) {
|
if (isInstruct && power_user.instruct.stop_sequence) {
|
||||||
if (message_already_generated.indexOf(power_user.instruct.stop_sequence) !== -1) {
|
if (message_already_generated.indexOf(power_user.instruct.stop_sequence) !== -1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -3143,17 +3153,17 @@ function cleanUpMessage(getMessage, isImpersonate, displayIncompleteSentences =
|
|||||||
}
|
}
|
||||||
if (getMessage.indexOf('<|endoftext|>') != -1) {
|
if (getMessage.indexOf('<|endoftext|>') != -1) {
|
||||||
getMessage = getMessage.substr(0, getMessage.indexOf('<|endoftext|>'));
|
getMessage = getMessage.substr(0, getMessage.indexOf('<|endoftext|>'));
|
||||||
|
|
||||||
}
|
}
|
||||||
if (power_user.instruct.enabled && power_user.instruct.stop_sequence) {
|
const isInstruct = power_user.instruct.enabled && main_api !== 'openai';
|
||||||
|
if (isInstruct && power_user.instruct.stop_sequence) {
|
||||||
if (getMessage.indexOf(power_user.instruct.stop_sequence) != -1) {
|
if (getMessage.indexOf(power_user.instruct.stop_sequence) != -1) {
|
||||||
getMessage = getMessage.substring(0, getMessage.indexOf(power_user.instruct.stop_sequence));
|
getMessage = getMessage.substring(0, getMessage.indexOf(power_user.instruct.stop_sequence));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (power_user.instruct.enabled && power_user.instruct.input_sequence && isImpersonate) {
|
if (isInstruct && power_user.instruct.input_sequence && isImpersonate) {
|
||||||
getMessage = getMessage.replaceAll(power_user.instruct.input_sequence, '');
|
getMessage = getMessage.replaceAll(power_user.instruct.input_sequence, '');
|
||||||
}
|
}
|
||||||
if (power_user.instruct.enabled && power_user.instruct.output_sequence && !isImpersonate) {
|
if (isInstruct && power_user.instruct.output_sequence && !isImpersonate) {
|
||||||
getMessage = getMessage.replaceAll(power_user.instruct.output_sequence, '');
|
getMessage = getMessage.replaceAll(power_user.instruct.output_sequence, '');
|
||||||
}
|
}
|
||||||
// clean-up group message from excessive generations
|
// clean-up group message from excessive generations
|
||||||
@@ -3286,14 +3296,14 @@ export function isMultigenEnabled() {
|
|||||||
return power_user.multigen && (main_api == 'textgenerationwebui' || main_api == 'kobold' || main_api == 'koboldhorde' || main_api == 'novel');
|
return power_user.multigen && (main_api == 'textgenerationwebui' || main_api == 'kobold' || main_api == 'koboldhorde' || main_api == 'novel');
|
||||||
}
|
}
|
||||||
|
|
||||||
function activateSendButtons() {
|
export function activateSendButtons() {
|
||||||
is_send_press = false;
|
is_send_press = false;
|
||||||
$("#send_but").css("display", "flex");
|
$("#send_but").css("display", "flex");
|
||||||
$("#send_textarea").attr("disabled", false);
|
$("#send_textarea").attr("disabled", false);
|
||||||
hideStopButton();
|
hideStopButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
function deactivateSendButtons() {
|
export function deactivateSendButtons() {
|
||||||
$("#send_but").css("display", "none");
|
$("#send_but").css("display", "none");
|
||||||
showStopButton();
|
showStopButton();
|
||||||
}
|
}
|
||||||
@@ -4013,8 +4023,8 @@ function setCharacterBlockHeight() {
|
|||||||
|
|
||||||
// Common code for message editor done and auto-save
|
// Common code for message editor done and auto-save
|
||||||
function updateMessage(div) {
|
function updateMessage(div) {
|
||||||
let mesBlock = div.closest(".mes_block");
|
const mesBlock = div.closest(".mes_block");
|
||||||
var text = mesBlock.find(".edit_textarea").val().trim();
|
const text = mesBlock.find(".edit_textarea").val().trim();
|
||||||
const bias = extractMessageBias(text);
|
const bias = extractMessageBias(text);
|
||||||
const mes = chat[this_edit_mes_id];
|
const mes = chat[this_edit_mes_id];
|
||||||
mes["mes"] = text;
|
mes["mes"] = text;
|
||||||
@@ -4070,7 +4080,7 @@ async function messageEditDone(div) {
|
|||||||
await eventSource.emit(event_types.MESSAGE_EDITED, this_edit_mes_id);
|
await eventSource.emit(event_types.MESSAGE_EDITED, this_edit_mes_id);
|
||||||
|
|
||||||
this_edit_mes_id = undefined;
|
this_edit_mes_id = undefined;
|
||||||
saveChatConditional();
|
await saveChatConditional();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getPastCharacterChats() {
|
async function getPastCharacterChats() {
|
||||||
@@ -4240,27 +4250,40 @@ function select_rm_info(type, charId, previousCharId = null) {
|
|||||||
getCharacters();
|
getCharacters();
|
||||||
selectRightMenuWithAnimation('rm_characters_block');
|
selectRightMenuWithAnimation('rm_characters_block');
|
||||||
|
|
||||||
if (type === 'char_import' || type === 'char_create') {
|
setTimeout(function () {
|
||||||
|
if (type === 'char_import' || type === 'char_create') {
|
||||||
|
const element = $(`#rm_characters_block [title="${charId}"]`).parent().get(0);
|
||||||
|
console.log(element);
|
||||||
|
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
|
|
||||||
const element = $(`#rm_characters_block [title="${charId}"]`).get(0);
|
try {
|
||||||
element.scrollIntoView({ behavior: 'smooth', block: 'end' });
|
if (element !== undefined || element !== null) {
|
||||||
|
$(element).addClass('flash animated');
|
||||||
$(`#rm_characters_block [title="${charId}"]`).parent().addClass('flash animated');
|
setTimeout(function () {
|
||||||
setTimeout(function () {
|
$(element).removeClass('flash animated');
|
||||||
$(`#rm_characters_block [title="${charId}"]`).parent().removeClass('flash animated');
|
}, 5000);
|
||||||
}, 5000);
|
} else { console.log('didnt find the element'); }
|
||||||
}
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
if (type === 'group_create') {
|
}
|
||||||
//for groups, ${charId} = data.id from group-chats.js createGroup()
|
}
|
||||||
const element = $(`#rm_characters_block [grid="${charId}"]`).get(0);
|
|
||||||
element.scrollIntoView({ behavior: 'smooth', block: 'end' });
|
|
||||||
$(`#rm_characters_block [grid="${charId}"]`).addClass('flash animated');
|
|
||||||
setTimeout(function () {
|
|
||||||
$(`#rm_characters_block [grid="${charId}"]`).removeClass('flash animated');
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (type === 'group_create') {
|
||||||
|
//for groups, ${charId} = data.id from group-chats.js createGroup()
|
||||||
|
const element = $(`#rm_characters_block [grid="${charId}"]`).get(0);
|
||||||
|
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
|
try {
|
||||||
|
if (element !== undefined || element !== null) {
|
||||||
|
$(element).addClass('flash animated');
|
||||||
|
setTimeout(function () {
|
||||||
|
$(element).removeClass('flash animated');
|
||||||
|
}, 5000);
|
||||||
|
} else { console.log('didnt find the element'); }
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
setRightTabSelectedClass();
|
setRightTabSelectedClass();
|
||||||
|
|
||||||
if (previousCharId) {
|
if (previousCharId) {
|
||||||
@@ -5098,6 +5121,12 @@ function importCharacter(file) {
|
|||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
//////////INPUT BAR FOCUS-KEEPING LOGIC/////////////
|
//////////INPUT BAR FOCUS-KEEPING LOGIC/////////////
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
$("#groupControlsToggle").trigger('click');
|
||||||
|
$("#groupCurrentMemberListToggle .inline-drawer-icon").trigger('click');
|
||||||
|
}, 200);
|
||||||
|
|
||||||
|
|
||||||
$("#rm_print_characters_block").on('scroll',
|
$("#rm_print_characters_block").on('scroll',
|
||||||
debounce(updateVisibleDivs, 5));
|
debounce(updateVisibleDivs, 5));
|
||||||
|
|
||||||
@@ -6257,7 +6286,7 @@ $(document).ready(function () {
|
|||||||
|
|
||||||
//********************
|
//********************
|
||||||
//***Message Editor***
|
//***Message Editor***
|
||||||
$(document).on("click", ".mes_edit", function () {
|
$(document).on("click", ".mes_edit", async function () {
|
||||||
if (this_chid !== undefined || selected_group) {
|
if (this_chid !== undefined || selected_group) {
|
||||||
// Previously system messages we're allowed to be edited
|
// Previously system messages we're allowed to be edited
|
||||||
/*const message = $(this).closest(".mes");
|
/*const message = $(this).closest(".mes");
|
||||||
@@ -6268,12 +6297,8 @@ $(document).ready(function () {
|
|||||||
|
|
||||||
let chatScrollPosition = $("#chat").scrollTop();
|
let chatScrollPosition = $("#chat").scrollTop();
|
||||||
if (this_edit_mes_id !== undefined) {
|
if (this_edit_mes_id !== undefined) {
|
||||||
let mes_edited = $("#chat")
|
let mes_edited = $(`#chat [mesid="${this_edit_mes_id}"]`).find(".mes_edit_done");
|
||||||
.children()
|
if (Number(edit_mes_id) == count_view_mes - 1) { //if the generating swipe (...)
|
||||||
.filter('[mesid="' + this_edit_mes_id + '"]')
|
|
||||||
.find(".mes_block")
|
|
||||||
.find(".mes_edit_done");
|
|
||||||
if (edit_mes_id == count_view_mes - 1) { //if the generating swipe (...)
|
|
||||||
if (chat[edit_mes_id]['swipe_id'] !== undefined) {
|
if (chat[edit_mes_id]['swipe_id'] !== undefined) {
|
||||||
if (chat[edit_mes_id]['swipes'].length === chat[edit_mes_id]['swipe_id']) {
|
if (chat[edit_mes_id]['swipes'].length === chat[edit_mes_id]['swipe_id']) {
|
||||||
run_edit = false;
|
run_edit = false;
|
||||||
@@ -6283,7 +6308,7 @@ $(document).ready(function () {
|
|||||||
hideSwipeButtons();
|
hideSwipeButtons();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
messageEditDone(mes_edited);
|
await messageEditDone(mes_edited);
|
||||||
}
|
}
|
||||||
$(this).closest(".mes_block").find(".mes_text").empty();
|
$(this).closest(".mes_block").find(".mes_text").empty();
|
||||||
$(this).closest(".mes_block").find(".mes_buttons").css("display", "none");
|
$(this).closest(".mes_block").find(".mes_buttons").css("display", "none");
|
||||||
@@ -6454,8 +6479,8 @@ $(document).ready(function () {
|
|||||||
showSwipeButtons();
|
showSwipeButtons();
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).on("click", ".mes_edit_done", function () {
|
$(document).on("click", ".mes_edit_done", async function () {
|
||||||
messageEditDone($(this));
|
await messageEditDone($(this));
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#your_name_button").click(function () {
|
$("#your_name_button").click(function () {
|
||||||
|
@@ -144,6 +144,7 @@ async function createNewBookmark() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await delay(250);
|
||||||
let name = await getBookmarkName();
|
let name = await getBookmarkName();
|
||||||
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
@@ -257,7 +258,7 @@ async function convertSoloToGroupChat() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Skip messages we don't care about
|
// Skip messages we don't care about
|
||||||
if (message.is_user || message.is_system) {
|
if (message.is_user || message.is_system || message.extra?.type === system_message_types.NARRATOR || message.force_avatar !== undefined) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -51,6 +51,22 @@ function getChatSyncState() {
|
|||||||
|
|
||||||
const context = getContext();
|
const context = getContext();
|
||||||
const chatState = chatStateFlags[currentChatId] || [];
|
const chatState = chatStateFlags[currentChatId] || [];
|
||||||
|
|
||||||
|
// if the chat length has decreased, it means that some messages were deleted
|
||||||
|
if (chatState.length > context.chat.length) {
|
||||||
|
for (let i = context.chat.length; i < chatState.length; i++) {
|
||||||
|
// if the synced message was deleted, notify the user
|
||||||
|
if (chatState[i]) {
|
||||||
|
toastr.warning(
|
||||||
|
'Purge your ChromaDB to remove it from there too. See the "Smart Context" tab in the Extensions menu for more information.',
|
||||||
|
'Message deleted from chat, but it still exists inside the ChromaDB database.',
|
||||||
|
{ timeOut: 0, extendedTimeOut: 0, preventDuplicates: true },
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
chatState.length = context.chat.length;
|
chatState.length = context.chat.length;
|
||||||
for (let i = 0; i < chatState.length; i++) {
|
for (let i = 0; i < chatState.length; i++) {
|
||||||
if (chatState[i] === undefined) {
|
if (chatState[i] === undefined) {
|
||||||
@@ -76,6 +92,7 @@ async function loadSettings() {
|
|||||||
$('#chromadb_n_results').val(extension_settings.chromadb.n_results).trigger('input');
|
$('#chromadb_n_results').val(extension_settings.chromadb.n_results).trigger('input');
|
||||||
$('#chromadb_split_length').val(extension_settings.chromadb.split_length).trigger('input');
|
$('#chromadb_split_length').val(extension_settings.chromadb.split_length).trigger('input');
|
||||||
$('#chromadb_file_split_length').val(extension_settings.chromadb.file_split_length).trigger('input');
|
$('#chromadb_file_split_length').val(extension_settings.chromadb.file_split_length).trigger('input');
|
||||||
|
$('#chromadb_freeze').prop('checked', extension_settings.chromadb.freeze);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onStrategyChange() {
|
function onStrategyChange() {
|
||||||
@@ -119,6 +136,10 @@ function checkChatId(chat_id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function addMessages(chat_id, messages) {
|
async function addMessages(chat_id, messages) {
|
||||||
|
if (extension_settings.chromadb.freeze) {
|
||||||
|
return { count: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
const url = new URL(getApiUrl());
|
const url = new URL(getApiUrl());
|
||||||
url.pathname = '/api/chromadb';
|
url.pathname = '/api/chromadb';
|
||||||
|
|
||||||
@@ -230,7 +251,7 @@ async function onExportClick() {
|
|||||||
const exportResult = await fetch(url, {
|
const exportResult = await fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: postHeaders,
|
headers: postHeaders,
|
||||||
body: JSON.stringify({ currentChatId }),
|
body: JSON.stringify({ chat_id: currentChatId }),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (exportResult.ok) {
|
if (exportResult.ok) {
|
||||||
@@ -328,12 +349,13 @@ async function onSelectInjectFile(e) {
|
|||||||
const text = await getFileText(file);
|
const text = await getFileText(file);
|
||||||
|
|
||||||
const split = splitRecursive(text, extension_settings.chromadb.file_split_length).filter(onlyUnique);
|
const split = splitRecursive(text, extension_settings.chromadb.file_split_length).filter(onlyUnique);
|
||||||
|
const baseDate = Date.now();
|
||||||
|
|
||||||
const messages = split.map(m => ({
|
const messages = split.map((m, i) => ({
|
||||||
id: `${file.name}-${split.indexOf(m)}`,
|
id: `${file.name}-${split.indexOf(m)}`,
|
||||||
role: 'system',
|
role: 'system',
|
||||||
content: m,
|
content: m,
|
||||||
date: Date.now(),
|
date: baseDate + i,
|
||||||
meta: JSON.stringify({
|
meta: JSON.stringify({
|
||||||
name: file.name,
|
name: file.name,
|
||||||
is_user: false,
|
is_user: false,
|
||||||
@@ -380,7 +402,7 @@ window.chromadb_interceptGeneration = async (chat) => {
|
|||||||
if (currentChatId) {
|
if (currentChatId) {
|
||||||
const messagesToStore = chat.slice(0, -extension_settings.chromadb.keep_context);
|
const messagesToStore = chat.slice(0, -extension_settings.chromadb.keep_context);
|
||||||
|
|
||||||
if (messagesToStore.length > 0) {
|
if (messagesToStore.length > 0 || extension_settings.chromadb.freeze) {
|
||||||
await addMessages(currentChatId, messagesToStore);
|
await addMessages(currentChatId, messagesToStore);
|
||||||
|
|
||||||
const lastMessage = chat[chat.length - 1];
|
const lastMessage = chat[chat.length - 1];
|
||||||
@@ -431,6 +453,11 @@ window.chromadb_interceptGeneration = async (chat) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onFreezeInput() {
|
||||||
|
extension_settings.chromadb.freeze = $('#chromadb_freeze').is(':checked');
|
||||||
|
saveSettingsDebounced();
|
||||||
|
}
|
||||||
|
|
||||||
jQuery(async () => {
|
jQuery(async () => {
|
||||||
const settingsHtml = `
|
const settingsHtml = `
|
||||||
<div class="chromadb_settings">
|
<div class="chromadb_settings">
|
||||||
@@ -454,6 +481,10 @@ jQuery(async () => {
|
|||||||
<input id="chromadb_split_length" type="range" min="${defaultSettings.split_length_min}" max="${defaultSettings.split_length_max}" step="${defaultSettings.split_length_step}" value="${defaultSettings.split_length}" />
|
<input id="chromadb_split_length" type="range" min="${defaultSettings.split_length_min}" max="${defaultSettings.split_length_max}" step="${defaultSettings.split_length_step}" value="${defaultSettings.split_length}" />
|
||||||
<label for="chromadb_file_split_length">Max length for each 'memory' pulled from imported text files: (<span id="chromadb_file_split_length_value"></span>) characters</label>
|
<label for="chromadb_file_split_length">Max length for each 'memory' pulled from imported text files: (<span id="chromadb_file_split_length_value"></span>) characters</label>
|
||||||
<input id="chromadb_file_split_length" type="range" min="${defaultSettings.file_split_length_min}" max="${defaultSettings.file_split_length_max}" step="${defaultSettings.file_split_length_step}" value="${defaultSettings.file_split_length}" />
|
<input id="chromadb_file_split_length" type="range" min="${defaultSettings.file_split_length_min}" max="${defaultSettings.file_split_length_max}" step="${defaultSettings.file_split_length_step}" value="${defaultSettings.file_split_length}" />
|
||||||
|
<label class="checkbox_label" for="chromadb_freeze" title="Pauses the automatic synchronization of new messages with ChromaDB. Older messages and injections will still be pulled as usual." >
|
||||||
|
<input type="checkbox" id="chromadb_freeze" />
|
||||||
|
<span>Freeze ChromaDB state</span>
|
||||||
|
</label>
|
||||||
<div class="flex-container spaceEvenly">
|
<div class="flex-container spaceEvenly">
|
||||||
<div id="chromadb_inject" title="Upload custom textual data to use in the context of the current chat" class="menu_button">
|
<div id="chromadb_inject" title="Upload custom textual data to use in the context of the current chat" class="menu_button">
|
||||||
<i class="fa-solid fa-file-arrow-up"></i>
|
<i class="fa-solid fa-file-arrow-up"></i>
|
||||||
@@ -472,7 +503,7 @@ jQuery(async () => {
|
|||||||
<span>Purge Chat from the DB</span>
|
<span>Purge Chat from the DB</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<small><i>Since ChromaDB state is not persisted to disk by default, you'll need to inject text data every time the Extras API server is restarted.</i></small>
|
<small><i>Local ChromaDB now persists to disk by default. The default folder is .chroma_db, and you can set a different folder with the --chroma-folder argument. If you are using the Extras Colab notebook, you will need to inject the text data every time the Extras API server is restarted.</i></small>
|
||||||
</div>
|
</div>
|
||||||
<form><input id="chromadb_inject_file" type="file" accept="text/plain" hidden></form>
|
<form><input id="chromadb_inject_file" type="file" accept="text/plain" hidden></form>
|
||||||
<form><input id="chromadb_import_file" type="file" accept="application/json" hidden></form>
|
<form><input id="chromadb_import_file" type="file" accept="application/json" hidden></form>
|
||||||
@@ -490,6 +521,7 @@ jQuery(async () => {
|
|||||||
$('#chromadb_import_file').on('change', onSelectImportFile);
|
$('#chromadb_import_file').on('change', onSelectImportFile);
|
||||||
$('#chromadb_purge').on('click', onPurgeClick);
|
$('#chromadb_purge').on('click', onPurgeClick);
|
||||||
$('#chromadb_export').on('click', onExportClick);
|
$('#chromadb_export').on('click', onExportClick);
|
||||||
|
$('#chromadb_freeze').on('input', onFreezeInput);
|
||||||
await loadSettings();
|
await loadSettings();
|
||||||
|
|
||||||
// Not sure if this is needed, but it's here just in case
|
// Not sure if this is needed, but it's here just in case
|
||||||
|
@@ -3,10 +3,10 @@ import {
|
|||||||
eventSource,
|
eventSource,
|
||||||
event_types,
|
event_types,
|
||||||
getRequestHeaders,
|
getRequestHeaders,
|
||||||
messageFormatting,
|
|
||||||
reloadCurrentChat,
|
reloadCurrentChat,
|
||||||
saveSettingsDebounced,
|
saveSettingsDebounced,
|
||||||
substituteParams,
|
substituteParams,
|
||||||
|
updateMessageBlock,
|
||||||
} from "../../../script.js";
|
} from "../../../script.js";
|
||||||
import { extension_settings, getContext } from "../../extensions.js";
|
import { extension_settings, getContext } from "../../extensions.js";
|
||||||
|
|
||||||
@@ -168,7 +168,7 @@ async function translateIncomingMessage(messageId) {
|
|||||||
const translation = await translate(textToTranslate, extension_settings.translate.target_language);
|
const translation = await translate(textToTranslate, extension_settings.translate.target_language);
|
||||||
message.extra.display_text = translation;
|
message.extra.display_text = translation;
|
||||||
|
|
||||||
$(`#chat .mes[mesid="${messageId}"] .mes_text`).html(messageFormatting(translation, message.name, message.is_system, message.is_user));
|
updateMessageBlock(messageId, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function translateProviderGoogle(text, lang) {
|
async function translateProviderGoogle(text, lang) {
|
||||||
@@ -211,8 +211,8 @@ async function translateOutgoingMessage(messageId) {
|
|||||||
|
|
||||||
const originalText = message.mes;
|
const originalText = message.mes;
|
||||||
message.extra.display_text = originalText;
|
message.extra.display_text = originalText;
|
||||||
$(`#chat .mes[mesid="${messageId}"] .mes_text`).html(messageFormatting(originalText, message.name, message.is_system, message.is_user));
|
|
||||||
message.mes = await translate(originalText, extension_settings.translate.internal_language);
|
message.mes = await translate(originalText, extension_settings.translate.internal_language);
|
||||||
|
updateMessageBlock(messageId, message);
|
||||||
|
|
||||||
console.log('translateOutgoingMessage', messageId);
|
console.log('translateOutgoingMessage', messageId);
|
||||||
}
|
}
|
||||||
@@ -291,6 +291,24 @@ async function translateMessageEdit(messageId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function onMessageTranslateClick() {
|
||||||
|
const context = getContext();
|
||||||
|
const messageId = $(this).closest('.mes').attr('mesid');
|
||||||
|
const message = context.chat[messageId];
|
||||||
|
|
||||||
|
// If the message is already translated, revert it back to the original text
|
||||||
|
if (message?.extra?.display_text) {
|
||||||
|
delete message.extra.display_text;
|
||||||
|
updateMessageBlock(messageId, message);
|
||||||
|
}
|
||||||
|
// If the message is not translated, translate it
|
||||||
|
else {
|
||||||
|
await translateIncomingMessage(messageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
await context.saveChat();
|
||||||
|
}
|
||||||
|
|
||||||
const handleIncomingMessage = createEventHandler(translateIncomingMessage, () => shouldTranslate(incomingTypes));
|
const handleIncomingMessage = createEventHandler(translateIncomingMessage, () => shouldTranslate(incomingTypes));
|
||||||
const handleOutgoingMessage = createEventHandler(translateOutgoingMessage, () => shouldTranslate(outgoingTypes));
|
const handleOutgoingMessage = createEventHandler(translateOutgoingMessage, () => shouldTranslate(outgoingTypes));
|
||||||
const handleImpersonateReady = createEventHandler(translateImpersonate, () => shouldTranslate(incomingTypes));
|
const handleImpersonateReady = createEventHandler(translateImpersonate, () => shouldTranslate(incomingTypes));
|
||||||
@@ -352,12 +370,7 @@ jQuery(() => {
|
|||||||
extension_settings.translate.target_language = event.target.value;
|
extension_settings.translate.target_language = event.target.value;
|
||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
});
|
});
|
||||||
$(document).on('click', '.mes_translate', function () {
|
$(document).on('click', '.mes_translate', onMessageTranslateClick);
|
||||||
const context = getContext();
|
|
||||||
const messageId = $(this).closest('.mes').attr('mesid');
|
|
||||||
translateIncomingMessage(messageId);
|
|
||||||
context.saveChat();
|
|
||||||
});
|
|
||||||
|
|
||||||
loadSettings();
|
loadSettings();
|
||||||
|
|
||||||
|
@@ -110,6 +110,7 @@ async function moduleWorker() {
|
|||||||
|
|
||||||
// We're currently swiping or streaming. Don't generate voice
|
// We're currently swiping or streaming. Don't generate voice
|
||||||
if (
|
if (
|
||||||
|
!message ||
|
||||||
message.mes === '...' ||
|
message.mes === '...' ||
|
||||||
message.mes === '' ||
|
message.mes === '' ||
|
||||||
(context.streamingProcessor && !context.streamingProcessor.isFinished)
|
(context.streamingProcessor && !context.streamingProcessor.isFinished)
|
||||||
|
@@ -1,5 +1,74 @@
|
|||||||
export { SystemTtsProvider }
|
export { SystemTtsProvider }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chunkify
|
||||||
|
* Google Chrome Speech Synthesis Chunking Pattern
|
||||||
|
* Fixes inconsistencies with speaking long texts in speechUtterance objects
|
||||||
|
* Licensed under the MIT License
|
||||||
|
*
|
||||||
|
* Peter Woolley and Brett Zamir
|
||||||
|
* Modified by Haaris for bug fixes
|
||||||
|
*/
|
||||||
|
|
||||||
|
var speechUtteranceChunker = function (utt, settings, callback) {
|
||||||
|
settings = settings || {};
|
||||||
|
var newUtt;
|
||||||
|
var txt = (settings && settings.offset !== undefined ? utt.text.substring(settings.offset) : utt.text);
|
||||||
|
if (utt.voice && utt.voice.voiceURI === 'native') { // Not part of the spec
|
||||||
|
newUtt = utt;
|
||||||
|
newUtt.text = txt;
|
||||||
|
newUtt.addEventListener('end', function () {
|
||||||
|
if (speechUtteranceChunker.cancel) {
|
||||||
|
speechUtteranceChunker.cancel = false;
|
||||||
|
}
|
||||||
|
if (callback !== undefined) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var chunkLength = (settings && settings.chunkLength) || 160;
|
||||||
|
var pattRegex = new RegExp('^[\\s\\S]{' + Math.floor(chunkLength / 2) + ',' + chunkLength + '}[.!?,]{1}|^[\\s\\S]{1,' + chunkLength + '}$|^[\\s\\S]{1,' + chunkLength + '} ');
|
||||||
|
var chunkArr = txt.match(pattRegex);
|
||||||
|
|
||||||
|
if (chunkArr == null || chunkArr[0] === undefined || chunkArr[0].length <= 2) {
|
||||||
|
//call once all text has been spoken...
|
||||||
|
if (callback !== undefined) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var chunk = chunkArr[0];
|
||||||
|
newUtt = new SpeechSynthesisUtterance(chunk);
|
||||||
|
var x;
|
||||||
|
for (x in utt) {
|
||||||
|
if (utt.hasOwnProperty(x) && x !== 'text') {
|
||||||
|
newUtt[x] = utt[x];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newUtt.lang = utt.lang;
|
||||||
|
newUtt.voice = utt.voice;
|
||||||
|
newUtt.addEventListener('end', function () {
|
||||||
|
if (speechUtteranceChunker.cancel) {
|
||||||
|
speechUtteranceChunker.cancel = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
settings.offset = settings.offset || 0;
|
||||||
|
settings.offset += chunk.length;
|
||||||
|
speechUtteranceChunker(utt, settings, callback);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.modifier) {
|
||||||
|
settings.modifier(newUtt);
|
||||||
|
}
|
||||||
|
console.log(newUtt); //IMPORTANT!! Do not remove: Logging the object out fixes some onend firing issues.
|
||||||
|
//placing the speak invocation inside a callback fixes ordering and onend issues.
|
||||||
|
setTimeout(function () {
|
||||||
|
speechSynthesis.speak(newUtt);
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
class SystemTtsProvider {
|
class SystemTtsProvider {
|
||||||
//########//
|
//########//
|
||||||
// Config //
|
// Config //
|
||||||
@@ -142,7 +211,12 @@ class SystemTtsProvider {
|
|||||||
utterance.pitch = this.settings.pitch || 1;
|
utterance.pitch = this.settings.pitch || 1;
|
||||||
utterance.onend = () => resolve(silence);
|
utterance.onend = () => resolve(silence);
|
||||||
utterance.onerror = () => reject();
|
utterance.onerror = () => reject();
|
||||||
speechSynthesis.speak(utterance);
|
speechUtteranceChunker(utterance, {
|
||||||
|
chunkLength: 200,
|
||||||
|
}, function () {
|
||||||
|
//some code to execute when done
|
||||||
|
console.log('System TTS done');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -48,6 +48,11 @@ import {
|
|||||||
cancelTtsPlay,
|
cancelTtsPlay,
|
||||||
isMultigenEnabled,
|
isMultigenEnabled,
|
||||||
displayPastChats,
|
displayPastChats,
|
||||||
|
sendMessageAsUser,
|
||||||
|
getBiasStrings,
|
||||||
|
saveChatConditional,
|
||||||
|
deactivateSendButtons,
|
||||||
|
activateSendButtons,
|
||||||
} from "../script.js";
|
} from "../script.js";
|
||||||
import { appendTagToList, createTagMapFromList, getTagsList, applyTagsOnCharacterSelect } from './tags.js';
|
import { appendTagToList, createTagMapFromList, getTagsList, applyTagsOnCharacterSelect } from './tags.js';
|
||||||
|
|
||||||
@@ -414,7 +419,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
|
|||||||
group_generation_id = Date.now();
|
group_generation_id = Date.now();
|
||||||
const lastMessage = chat[chat.length - 1];
|
const lastMessage = chat[chat.length - 1];
|
||||||
let messagesBefore = chat.length;
|
let messagesBefore = chat.length;
|
||||||
let lastMessageText = lastMessage.mes;
|
let lastMessageText = lastMessage?.mes || '';
|
||||||
let activationText = "";
|
let activationText = "";
|
||||||
let isUserInput = false;
|
let isUserInput = false;
|
||||||
let isGenerationDone = false;
|
let isGenerationDone = false;
|
||||||
@@ -491,11 +496,17 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
|
|||||||
|
|
||||||
if (activatedMembers.length === 0) {
|
if (activatedMembers.length === 0) {
|
||||||
toastr.warning('All group members are disabled. Enable at least one to get a reply.');
|
toastr.warning('All group members are disabled. Enable at least one to get a reply.');
|
||||||
throw new Error('All group members are disabled');
|
|
||||||
|
// Send user message as is
|
||||||
|
const bias = getBiasStrings(userInput);
|
||||||
|
await sendMessageAsUser(userInput, bias.messageBias);
|
||||||
|
await saveChatConditional();
|
||||||
|
$('#send_textarea').val('');
|
||||||
}
|
}
|
||||||
|
|
||||||
// now the real generation begins: cycle through every activated character
|
// now the real generation begins: cycle through every activated character
|
||||||
for (const chId of activatedMembers) {
|
for (const chId of activatedMembers) {
|
||||||
|
deactivateSendButtons();
|
||||||
isGenerationDone = false;
|
isGenerationDone = false;
|
||||||
const generateType = type == "swipe" || type == "impersonate" || type == "quiet" ? type : "group_chat";
|
const generateType = type == "swipe" || type == "impersonate" || type == "quiet" ? type : "group_chat";
|
||||||
setCharacterId(chId);
|
setCharacterId(chId);
|
||||||
@@ -509,13 +520,14 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
|
|||||||
.find(".typing_indicator_name")
|
.find(".typing_indicator_name")
|
||||||
.text(characters[chId].name);
|
.text(characters[chId].name);
|
||||||
$("#chat").append(typingIndicator);
|
$("#chat").append(typingIndicator);
|
||||||
typingIndicator.show(250, function () {
|
typingIndicator.show(200, function () {
|
||||||
typingIndicator.get(0).scrollIntoView({ behavior: "smooth" });
|
typingIndicator.get(0).scrollIntoView({ behavior: "smooth" });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This is awful. Refactor this
|
// TODO: This is awful. Refactor this
|
||||||
while (true) {
|
while (true) {
|
||||||
|
deactivateSendButtons();
|
||||||
if (isGenerationAborted) {
|
if (isGenerationAborted) {
|
||||||
throw new Error('Group generation aborted');
|
throw new Error('Group generation aborted');
|
||||||
}
|
}
|
||||||
@@ -590,16 +602,23 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
|
|||||||
await delay(100);
|
await delay(100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (isStreamingEnabled()) {
|
||||||
|
if (streamingProcessor && !streamingProcessor.isFinished) {
|
||||||
|
await delay(100);
|
||||||
|
} else {
|
||||||
|
messagesBefore++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
messagesBefore++;
|
messagesBefore++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
// hide and reapply the indicator to the bottom of the list
|
// hide and reapply the indicator to the bottom of the list
|
||||||
typingIndicator.hide(250);
|
typingIndicator.hide(200);
|
||||||
$("#chat").append(typingIndicator);
|
$("#chat").append(typingIndicator);
|
||||||
|
|
||||||
is_group_generating = false;
|
is_group_generating = false;
|
||||||
@@ -607,6 +626,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
|
|||||||
setSendButtonState(false);
|
setSendButtonState(false);
|
||||||
setCharacterId(undefined);
|
setCharacterId(undefined);
|
||||||
setCharacterName('');
|
setCharacterName('');
|
||||||
|
activateSendButtons();
|
||||||
showSwipeButtons();
|
showSwipeButtons();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -714,7 +734,8 @@ function activateNaturalOrder(members, input, lastMessage, allowSelfResponses, i
|
|||||||
}
|
}
|
||||||
|
|
||||||
// pick 1 at random if no one was activated
|
// pick 1 at random if no one was activated
|
||||||
while (activatedMembers.length === 0) {
|
let retries = 0;
|
||||||
|
while (activatedMembers.length === 0 && ++retries <= members.length) {
|
||||||
const randomIndex = Math.floor(Math.random() * members.length);
|
const randomIndex = Math.floor(Math.random() * members.length);
|
||||||
const character = characters.find((x) => x.avatar === members[randomIndex]);
|
const character = characters.find((x) => x.avatar === members[randomIndex]);
|
||||||
|
|
||||||
@@ -959,6 +980,9 @@ function select_group_chats(groupId, skipAnimation) {
|
|||||||
$("#rm_group_scenario").show();
|
$("#rm_group_scenario").show();
|
||||||
} else {
|
} else {
|
||||||
$("#rm_group_submit").show();
|
$("#rm_group_submit").show();
|
||||||
|
if ($("#groupAddMemberListToggle .inline-drawer-content").css('display') !== 'block') {
|
||||||
|
$("#groupAddMemberListToggle").trigger('click');
|
||||||
|
}
|
||||||
$("#rm_group_delete").hide();
|
$("#rm_group_delete").hide();
|
||||||
$("#rm_group_scenario").hide();
|
$("#rm_group_scenario").hide();
|
||||||
}
|
}
|
||||||
|
@@ -56,6 +56,7 @@ export {
|
|||||||
let openai_msgs = [];
|
let openai_msgs = [];
|
||||||
let openai_msgs_example = [];
|
let openai_msgs_example = [];
|
||||||
let openai_messages_count = 0;
|
let openai_messages_count = 0;
|
||||||
|
let openai_narrator_messages_count = 0;
|
||||||
|
|
||||||
let is_get_status_openai = false;
|
let is_get_status_openai = false;
|
||||||
let is_api_button_press_openai = false;
|
let is_api_button_press_openai = false;
|
||||||
@@ -175,6 +176,7 @@ function setOpenAIMessages(chat) {
|
|||||||
let j = 0;
|
let j = 0;
|
||||||
// clean openai msgs
|
// clean openai msgs
|
||||||
openai_msgs = [];
|
openai_msgs = [];
|
||||||
|
openai_narrator_messages_count = 0;
|
||||||
for (let i = chat.length - 1; i >= 0; i--) {
|
for (let i = chat.length - 1; i >= 0; i--) {
|
||||||
let role = chat[j]['is_user'] ? 'user' : 'assistant';
|
let role = chat[j]['is_user'] ? 'user' : 'assistant';
|
||||||
let content = chat[j]['mes'];
|
let content = chat[j]['mes'];
|
||||||
@@ -182,6 +184,7 @@ function setOpenAIMessages(chat) {
|
|||||||
// 100% legal way to send a message as system
|
// 100% legal way to send a message as system
|
||||||
if (chat[j].extra?.type === system_message_types.NARRATOR) {
|
if (chat[j].extra?.type === system_message_types.NARRATOR) {
|
||||||
role = 'system';
|
role = 'system';
|
||||||
|
openai_narrator_messages_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// for groups or sendas command - prepend a character's name
|
// for groups or sendas command - prepend a character's name
|
||||||
@@ -457,7 +460,7 @@ async function prepareOpenAIMessages(name2, storyString, worldInfoBefore, worldI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
openai_messages_count = openai_msgs_tosend.filter(x => x.role == "user" || x.role == "assistant").length;
|
openai_messages_count = openai_msgs_tosend.filter(x => x.role == "user" || x.role == "assistant").length + openai_narrator_messages_count;
|
||||||
// reverse the messages array because we had the newest at the top to remove the oldest,
|
// reverse the messages array because we had the newest at the top to remove the oldest,
|
||||||
// now we want proper order
|
// now we want proper order
|
||||||
openai_msgs_tosend.reverse();
|
openai_msgs_tosend.reverse();
|
||||||
|
@@ -162,7 +162,7 @@ export function saveCaretPosition(element) {
|
|||||||
end: range.endOffset
|
end: range.endOffset
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('Caret saved', position);
|
console.debug('Caret saved', position);
|
||||||
|
|
||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
@@ -174,7 +174,7 @@ export function restoreCaretPosition(element, position) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Caret restored', position);
|
console.debug('Caret restored', position);
|
||||||
|
|
||||||
// Create a new range object
|
// Create a new range object
|
||||||
const range = new Range();
|
const range = new Range();
|
||||||
|
330
readme.md
330
readme.md
@@ -1,306 +1,68 @@
|
|||||||

|
# What is SillyTavern?
|
||||||
|
|
||||||
Mobile-friendly, Multi-API (KoboldAI/CPP, Horde, NovelAI, Ooba, OpenAI+proxies, Poe, WindowAI(Claude!)), VN-like Waifu Mode, Horde SD, System TTS, WorldInfo (lorebooks), customizable UI, auto-translate, and more prompt options than you'd ever want or need. Optional Extras server for more SD/TTS options + ChromaDB/Summarize.
|

|
||||||
|
|
||||||
Based on a fork of TavernAI 1.2.8
|
Brought to you by Cohee, RossAscends, and the SillyTavern community, SillyTavern is a local-install interface that allows you to interact with text generation AIs (LLMs) to chat and roleplay with custom characters.
|
||||||
|
|
||||||
### Brought to you by Cohee, RossAscends and the SillyTavern community
|
SillyTavern originated as a modification of TavernAI 1.2.8 in February 2023, and has since added many cutting edge features not present in the original TavernAI.
|
||||||
|
|
||||||
NOTE: We have added [a FAQ](faq.md) to answer most of your questions and help you get started.
|
### Features
|
||||||
|
|
||||||
### What is SillyTavern or TavernAI?
|
* Mobile-friendly interface
|
||||||
|
* Multiple backend API connectivity ([KoboldAI](https://github.com/KoboldAI/KoboldAI-Client), [KoboldCPP](https://github.com/LostRuins/koboldcpp), [AI Horde](https://horde.koboldai.net/), [NovelAI](https://github.com/LostRuins/koboldcpp), [Oobabooga's TextGen WebUI](https://github.com/oobabooga/text-generation-webui), [OpenAI](https://chat.openai.com/)+proxies, [Poe.com](https://poe.com), [WindowAI](https://windowai.io))
|
||||||
|
* Visual Novel-like Waifu Mode
|
||||||
|
* Horde Stable Diffusion generation
|
||||||
|
* TTS support (ElevenLabs, Silero, and built-in OS)
|
||||||
|
* WorldInfo (lorebooks)
|
||||||
|
* Customizable colors, backgrounds, avatar styles, and UI panel placement
|
||||||
|
* Notification sound for AI responses
|
||||||
|
* Export chats as .txt files
|
||||||
|
* Auto-translate single messages or the entire chat via Google API, even automatically.
|
||||||
|
* Extensive prompt formatting options
|
||||||
|
* Character HotSwap buttons to quickly change between your favorite characters
|
||||||
|
* Prompt token breakdown view for each message
|
||||||
|
* Swipes
|
||||||
|
* Group chats: multi-bot rooms for characters to talk to you or each other
|
||||||
|
* Chat bookmarks / branching
|
||||||
|
* Soft prompts via KoboldAI
|
||||||
|
* webp character card interoperability (PNG is still the internal format)
|
||||||
|
|
||||||
Tavern is a user interface you can install on your computer (and Android phones) that allows you to interact with text generation AIs and chat/roleplay with characters you or the community create.
|
### Extensions
|
||||||
|
|
||||||
SillyTavern is a fork of TavernAI 1.2.8 which is under more active development and has added many major features. At this point, they can be thought of as completely independent programs.
|
SillyTavern supports extensions/plugins:
|
||||||
|
|
||||||
### What do I need other than Tavern?
|
* Author's Note / Character Bias
|
||||||
|
* Character emotional expressions
|
||||||
|
* Auto-Summary of the chat history
|
||||||
|
* Sending images to chat, and the AI interpreting the content.
|
||||||
|
* Stable Diffusion image generation (5 chat-related presets plus 'free mode')
|
||||||
|
* Text-to-speech for AI response messages (via ElevenLabs, Silero, or the OS's System TTS)
|
||||||
|
|
||||||
On its own Tavern is useless, as it's just a user interface. You have to have access to an AI system backend that can act as the roleplay character. There are various supported backends: OpenAPI API (GPT), KoboldAI (either running locally or on Google Colab), and more. You can read more about this in [the FAQ](faq.md).
|
Additional functionality can be added by using [SillyTavern Extras](https://github.com/SillyTavern/SillyTavern-extras).
|
||||||
|
|
||||||
### Do I need a powerful PC to run Tavern?
|
### Screenshots
|
||||||
|
|
||||||
Since Tavern is only a user interface, it has tiny hardware requirements, it will run on anything. It's the AI system backend that needs to be powerful.
|

|
||||||
|

|
||||||
|
|
||||||
## Mobile support
|
### Installation Requirements
|
||||||
|
|
||||||
> **Note**
|
SillyTavern will run on virtually any device capable of running NodeJS v18.
|
||||||
|
|
||||||
> **This fork can be run natively on Android phones using Termux. Please refer to this guide by ArroganceComplex#2659:**
|
### What do I need other than SillyTavern?
|
||||||
|
|
||||||
<https://rentry.org/STAI-Termux>
|
SillyTavern is only a frontend interface, so you will need to have access to one of backend APIs listed above.
|
||||||
|
|
||||||
**.webp character cards import/export is not supported in Termux. Use either JSON or PNG formats instead.**
|
### How can I get in touch with the developers directly?
|
||||||
|
|
||||||
## Questions or suggestions?
|
|
||||||
|
|
||||||
### We now have a community Discord server
|
|
||||||
|
|
||||||
Get support, share favorite characters and prompts:
|
|
||||||
|
|
||||||
### [Join](https://discord.gg/RZdyAEUPvj)
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
Get in touch with the developers directly:
|
|
||||||
|
|
||||||
* Discord: Cohee#1207 or RossAscends#1779
|
* Discord: Cohee#1207 or RossAscends#1779
|
||||||
* Reddit: /u/RossAscends or /u/sillylossy
|
* Reddit: /u/RossAscends or /u/sillylossy
|
||||||
* [Post a GitHub issue](https://github.com/Cohee1207/SillyTavern/issues)
|
* [Post a GitHub issue](https://github.com/Cohee1207/SillyTavern/issues)
|
||||||
|
|
||||||
## This version includes
|
### I like your project! How do I contribute?
|
||||||
|
|
||||||
* A heavily modified TavernAI 1.2.8 (more than 50% of code rewritten or optimized)
|
* We welcome pull requests!
|
||||||
* Swipes
|
* We also welcome helpful and informed bug reports that use the templates provided in our GitHub.
|
||||||
* Group chats: multi-bot rooms for characters to talk to you or each other
|
* We do not accept monetary donations for the project itself.
|
||||||
* Chat bookmarks / branching (duplicates the dialogue in its current state)
|
* Cohee does not take donations.
|
||||||
* Advanced KoboldAI / TextGen generation settings with a lot of community-made presets
|
* RossAscends has a personal [Patreon](https://www.patreon.com/RossAscends) & [Kofi](https://ko-fi.com/rossascends)
|
||||||
* World Info support: create a rich lore or save tokens on your character card
|
|
||||||
* [Oobabooga's TextGen WebUI](https://github.com/oobabooga/text-generation-webui) API connection
|
|
||||||
* [AI Horde](https://horde.koboldai.net/) connection
|
|
||||||
* [Poe.com](https://poe.com) (ChatGPT / Claude) connection
|
|
||||||
* Soft prompts selector for KoboldAI
|
|
||||||
* Prompt generation formatting tweaking
|
|
||||||
* webp character card interoperability (PNG is still an internal format)
|
|
||||||
* Extensibility support via [SillyLossy's TAI-extras](https://github.com/Cohee1207/TavernAI-extras) plugins
|
|
||||||
* Author's Note / Character Bias
|
|
||||||
* Character emotional expressions
|
|
||||||
* Auto-Summary of the chat history
|
|
||||||
* Sending images to chat, and the AI interpreting the content.
|
|
||||||
* Stable Diffusion image generation (5 chat-related presets plus 'free mode')
|
|
||||||
* Text-to-speech for AI response messages (via ElevenLabs, Silero, or the OS's System TTS)
|
|
||||||
|
|
||||||
## UI Extensions 🚀
|
|
||||||
|
|
||||||
| Name | Description | Required <a href="https://github.com/Cohee1207/TavernAI-extras#modules" target="_blank">Extra Modules</a> | Screenshot |
|
|
||||||
| ---------------- | ---------------------------------| ---------------------------- | ---------- |
|
|
||||||
| Image Captioning | Send a cute picture to your bot!<br><br>Picture select option will appear beside the "Message send" button. | `caption` | <img src="https://user-images.githubusercontent.com/18619528/224161576-ddfc51cd-995e-44ec-bf2d-d2477d603f0c.png" style="max-width:200px" /> |
|
|
||||||
| Character Expressions | See your character reacting to your messages!<br><br>**You need to provide your own character images!**<br><br>1. Create a folder in TavernAI called `public/characters/<name>`, where `<name>` is the name of your character.<br>2. For the base emotion classification model, put six PNG files there with the following names: `joy.png`, `anger.png`, `fear.png`, `love.png`, `sadness.png`, `surprise.png`. Other models may provide other options.<br>3. Images only display in desktop mode. | `classify` | <img style="max-width:200px" alt="image" src="https://user-images.githubusercontent.com/18619528/223765089-34968217-6862-47e0-85da-7357370f8de6.png"> |
|
|
||||||
| Memory | Chatbot long-term memory simulation using automatic message context summarization. | `summarize` | <img style="max-width:200px" alt="image" src="https://user-images.githubusercontent.com/18619528/223766279-88a46481-1fa6-40c5-9724-6cdd6f587233.png"> |
|
|
||||||
| D&D Dice | A set of 7 classic D&D dice for all your dice rolling needs.<br><br>*I used to roll the dice.<br>Feel the fear in my enemies' eyes* | None | <img style="max-width:200px" alt="image" src="https://user-images.githubusercontent.com/18619528/226199925-a066c6fc-745e-4a2b-9203-1cbffa481b14.png"> |
|
|
||||||
| Author's Note | Built-in extension that allows you to append notes that will be added to the context and steer the story and character in a specific direction. Because it's sent after the character description, it has a lot of weight. Thanks Ali឵#2222 for pitching the idea! | None |  |
|
|
||||||
| Character Backgrounds | Built-in extension to assign unique backgrounds to specific chats or groups. | None | <img style="max-width:200px" alt="image" src="https://user-images.githubusercontent.com/18619528/233494454-bfa7c9c7-4faa-4d97-9c69-628fd96edd92.png"> |
|
|
||||||
| Stable Diffusion | Use local of cloud-based Stable Diffusion webUI API to generate images. 5 presets included ('you', 'your face', 'me', 'the story', and 'the last message'. Free mode also supported via `/sd (anything_here_)` command in the chat input bar. Most common StableDiffusion generation settings are customizable within the SillyTavern UI. | None | <img style="max-width:200px" alt="image" src="https://files.catbox.moe/ppata8.png"> |
|
|
||||||
| Text-to-Speech | AI-generated voice will read back character messages on demand, or automatically read new messages they arrive. Supports ElevenLabs, Silero, and your device's TTS service. | None | <img style="max-width:200px" alt="image" src="https://files.catbox.moe/o3wxkk.png"> |
|
|
||||||
| Chat Translation | Automatically translates incoming and/or outgoing messages into the chosen language. | None | Pending |
|
|
||||||
| Token Counter | Simple way to calculate the number of tokens in any text with selected tokenizer. | None | Pending |
|
|
||||||
| Smart Context<br><br>*Infinity Context / Object Permanence* | **What it doesn't do:** Magically increase your context size.<br>**What it does:** Optimizes the arrangement of your message history within the context space for more effective use.<br><br>Imagine two variables:<br>X: How many original chat messages to keep<br>Y: Maximum number of ChromaDB 'memories' to inject<br><br>When the chat reaches the threshold of X messages, additional messages will no longer be included in the context chronologically. Instead, they will be selected from the history based on their similarity to your recent inputs (limited to a maximum of Y), which should provide more relevant information than simply disregarding past messages. Adjust these values according to your average number of in-context entries for optimal performance. | `chromadb` | Pending |
|
|
||||||
|
|
||||||
## UI/CSS/Quality of Life tweaks by RossAscends
|
|
||||||
|
|
||||||
* Mobile UI with optimized for iOS, and supports saving a shortcut to home screen and opening in fullscreen mode.
|
|
||||||
* HotKeys
|
|
||||||
* Up = Edit last message in chat
|
|
||||||
* Ctrl+Up = Edit last USER message in chat
|
|
||||||
* Left = swipe left
|
|
||||||
* Right = swipe right (NOTE: swipe hotkeys are disabled when chatbar has something typed into it)
|
|
||||||
* Ctrl+Left = view locally stored variables (in the browser console window)
|
|
||||||
* Enter (with chat bar selected) = send your message to AI
|
|
||||||
* Ctrl+Enter = Regenerate the last AI response
|
|
||||||
|
|
||||||
* User Name Changes and Character Deletion no longer force the page to refresh.
|
|
||||||
|
|
||||||
* Toggle option to automatically connect to API on page load.
|
|
||||||
* Toggle option to automatically load the most recently viewed character on page load.
|
|
||||||
* Better Token Counter - works on unsaved characters, and shows both permanent and temporary tokens.
|
|
||||||
|
|
||||||
* Better Past Chats View
|
|
||||||
* New Chat filenames are saved in a readable format of "(character) - (when it was created)"
|
|
||||||
* Chat preview increased from 40 characters to 300.
|
|
||||||
* Multiple options for characters list sorting (by name, creation date, chat sizes).
|
|
||||||
|
|
||||||
* By default the left and right settings panel will close when you click away from it.
|
|
||||||
* Clicking the Lock on the nav panel will hold the panel open, and this setting be remembered across sessions.
|
|
||||||
* Nav panel status of open or closed will also be saved across sessions.
|
|
||||||
|
|
||||||
* Customizable chat UI:
|
|
||||||
* Play a sound when a new message arrives
|
|
||||||
* Switch between round or rectangle avatar styles
|
|
||||||
* Have a wider chat window on the desktop
|
|
||||||
* Optional semi-transparent glass-like panels
|
|
||||||
* Customizable page colors for 'main text', 'quoted text' 'italics text'.
|
|
||||||
* Customizable UI background color and blur amount
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
*NOTE: This software is intended for local install purposes, and has not been thoroughly tested on a colab or other cloud notebook service.*
|
|
||||||
|
|
||||||
> **Warning**
|
|
||||||
|
|
||||||
> DO NOT INSTALL INTO ANY WINDOWS CONTROLLED FOLDER (Program Files, System32, etc).
|
|
||||||
|
|
||||||
> DO NOT RUN START.BAT WITH ADMIN PERMISSIONS
|
|
||||||
|
|
||||||
### Windows
|
|
||||||
|
|
||||||
Installing via Git (recommended for easy updating)
|
|
||||||
|
|
||||||
Easy to follow guide with pretty pictures:
|
|
||||||
<https://docs.alpindale.dev/pygmalion-extras/sillytavern/#windows-installation>
|
|
||||||
|
|
||||||
1. Install [NodeJS](https://nodejs.org/en) (latest LTS version is recommended)
|
|
||||||
2. Install [GitHub Desktop](https://central.github.com/deployments/desktop/desktop/latest/win32)
|
|
||||||
3. Open Windows Explorer (`Win+E`)
|
|
||||||
4. Browse to or Create a folder that is not controlled or monitored by Windows. (ex: C:\MySpecialFolder\)
|
|
||||||
5. Open a Command Prompt inside that folder by clicking in the 'Address Bar' at the top, typing `cmd`, and pressing Enter.
|
|
||||||
6. Once the black box (Command Prompt) pops up, type ONE of the following into it and press Enter:
|
|
||||||
|
|
||||||
* for Main Branch: `git clone https://github.com/Cohee1207/SillyTavern -b main`
|
|
||||||
* for Dev Branch: `git clone https://github.com/Cohee1207/SillyTavern -b dev`
|
|
||||||
|
|
||||||
7. Once everything is cloned, double click `Start.bat` to make NodeJS install its requirements.
|
|
||||||
8. The server will then start, and SillyTavern will popup in your browser.
|
|
||||||
|
|
||||||
Installing via zip download
|
|
||||||
|
|
||||||
1. Install [NodeJS](https://nodejs.org/en) (latest LTS version is recommended)
|
|
||||||
2. Download the zip from this GitHub repo. (Get the `Source code (zip)` from [Releases](https://github.com/Cohee1207/SillyTavern/releases/latest))
|
|
||||||
3. Unzip it into a folder of your choice
|
|
||||||
4. Run `Start.bat` via double-clicking or in a command line.
|
|
||||||
5. Once the server has prepared everything for you, it will open a tab in your browser.
|
|
||||||
|
|
||||||
### Linux
|
|
||||||
|
|
||||||
1. Run the `start.sh` script.
|
|
||||||
2. Enjoy.
|
|
||||||
|
|
||||||
## API keys management
|
|
||||||
|
|
||||||
SillyTavern saves your API keys to a `secrets.json` file in the server directory.
|
|
||||||
|
|
||||||
By default they will not be exposed to a frontend after you enter them and reload the page.
|
|
||||||
|
|
||||||
In order to enable viewing your keys by clicking a button in the API block:
|
|
||||||
|
|
||||||
1. Set the value of `allowKeysExposure` to `true` in `config.conf` file.
|
|
||||||
2. Restart the SillyTavern server.
|
|
||||||
|
|
||||||
## Remote connections
|
|
||||||
|
|
||||||
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 wifi network.
|
|
||||||
|
|
||||||
However, it can be used to allow remote connections from anywhere as well.
|
|
||||||
|
|
||||||
**IMPORTANT: SillyTavern is a single-user program, so anyone who logs in will be able to see all characters and chats, and be able to change any settings inside the UI.**
|
|
||||||
|
|
||||||
### 1. Managing whitelisted IPs
|
|
||||||
|
|
||||||
* Create a new text file inside your SillyTavern base install folder called `whitelist.txt`.
|
|
||||||
* Open the file in a text editor, add a list of IPs you want to be allowed to connect.
|
|
||||||
|
|
||||||
*Both indidivual IPs, and wildcard IP ranges are accepted. Examples:*
|
|
||||||
|
|
||||||
```txt
|
|
||||||
192.168.0.1
|
|
||||||
192.168.0.20
|
|
||||||
```
|
|
||||||
|
|
||||||
or
|
|
||||||
|
|
||||||
```txt
|
|
||||||
192.168.0.*
|
|
||||||
```
|
|
||||||
|
|
||||||
(the above wildcard IP range will allow any device on the local network to connect)
|
|
||||||
|
|
||||||
CIDR masks are also accepted (eg. 10.0.0.0/24).
|
|
||||||
|
|
||||||
* Save the `whitelist.txt` file.
|
|
||||||
* Restart your TAI server.
|
|
||||||
|
|
||||||
Now devices which have the IP specified in the file will be able to connect.
|
|
||||||
|
|
||||||
*Note: `config.conf` also has a `whitelist` array, which you can use in the same way, but this array will be ignored if `whitelist.txt` exists.*
|
|
||||||
|
|
||||||
### 2. Getting the IP for the ST host machine
|
|
||||||
|
|
||||||
After the whitelist has been setup, you'll need the IP of the ST-hosting device.
|
|
||||||
|
|
||||||
If the ST-hosting device is on the same wifi network, you will use the ST-host's internal wifi IP:
|
|
||||||
|
|
||||||
* For Windows: windows button > type `cmd.exe` in the search bar > type `ipconfig` in the console, hit Enter > look for `IPv4` listing.
|
|
||||||
|
|
||||||
If you (or someone else) wants to connect to your hosted ST while not being on the same network, you will need the public IP of your ST-hosting device.
|
|
||||||
|
|
||||||
* While using the ST-hosting device, access [this page](https://whatismyipaddress.com/) and look for for `IPv4`. This is what you would use to connect from the remote device.
|
|
||||||
|
|
||||||
### 3. Connect the remote device to the ST host machine.
|
|
||||||
|
|
||||||
Whatever IP you ended up with for your situation, you will put that IP address and port number into the remote device's web browser.
|
|
||||||
|
|
||||||
A typical address for an ST host on the same wifi network would look like:
|
|
||||||
|
|
||||||
`http://192.168.0.5:8000`
|
|
||||||
|
|
||||||
Use http:// NOT https://
|
|
||||||
|
|
||||||
### Opening your ST to all IPs
|
|
||||||
|
|
||||||
We do not recommend doing this, but you can open `config.conf` and change `whitelist` to `false`.
|
|
||||||
|
|
||||||
You must remove (or rename) `whitelist.txt` in the SillyTavern base install folder, if it exists.
|
|
||||||
|
|
||||||
This is usually an insecure practice, so we require you to set a username and password when you do this.
|
|
||||||
|
|
||||||
The username and password are set in `config.conf`.
|
|
||||||
|
|
||||||
After restarting your ST server, any device will be able to connect to it, regardless of their IP as long as they know the username and password.
|
|
||||||
|
|
||||||
### Still Unable To Connect?
|
|
||||||
|
|
||||||
* Create an inbound/outbound firewall rule for the port found in `config.conf`. Do NOT mistake this for portforwarding on your router, otherwise someone could find your chat logs and that's a big no-no.
|
|
||||||
* Enable the Private Network profile type in Settings > Network and Internet > Ethernet. This is VERY important for Windows 11, otherwise you would be unable to connect even with the aforementioned firewall rules.
|
|
||||||
|
|
||||||
## Performance issues?
|
|
||||||
|
|
||||||
Try enabling the No Blur Effect (Fast UI) mode on the User settings panel.
|
|
||||||
|
|
||||||
## I like your project! How do I contribute?
|
|
||||||
|
|
||||||
### DO's
|
|
||||||
|
|
||||||
1. Send pull requests
|
|
||||||
2. Send feature suggestions and issue reports using established templates
|
|
||||||
3. Read the readme file and built-in documentation before asking anything
|
|
||||||
|
|
||||||
### DONT's
|
|
||||||
|
|
||||||
1. Offer monetary donations
|
|
||||||
2. Send bug reports without providing any context
|
|
||||||
3. Ask the questions that were already answered numerous times
|
|
||||||
|
|
||||||
## Where can I find the old backgrounds?
|
|
||||||
|
|
||||||
We're moving to 100% original content only policy, so old background images have been removed from this repository.
|
|
||||||
|
|
||||||
You can find them archived here:
|
|
||||||
|
|
||||||
<https://files.catbox.moe/1xevnc.zip>
|
|
||||||
|
|
||||||
## Screenshots
|
|
||||||
|
|
||||||
<img width="400" alt="image" src="https://user-images.githubusercontent.com/18619528/228649245-8061c60f-63dc-488e-9325-f151b7a3ec2d.png">
|
|
||||||
<img width="400" alt="image" src="https://user-images.githubusercontent.com/18619528/228649856-fbdeef05-d727-4d5a-be80-266cbbc6b811.png">
|
|
||||||
|
|
||||||
## License and credits
|
|
||||||
|
|
||||||
**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.**
|
|
||||||
|
|
||||||
* TAI Base by Humi: Unknown license
|
|
||||||
* Cohee's modifications and derived code: AGPL v3
|
|
||||||
* RossAscends' additions: AGPL v3
|
|
||||||
* Portions of CncAnon's TavernAITurbo mod: Unknown license
|
|
||||||
* Waifu mode inspired by the work of PepperTaco (<https://github.com/peppertaco/Tavern/>)
|
|
||||||
* Thanks Pygmalion University for being awesome testers and suggesting cool features!
|
|
||||||
* Thanks oobabooga for compiling presets for TextGen
|
|
||||||
* poe-api client adapted from <https://github.com/ading2210/poe-api> (GPL v3)
|
|
||||||
* GraphQL files for poe: <https://github.com/muharamdani/poe> (ISC License)
|
|
||||||
* KoboldAI Presets from KAI Lite: <https://lite.koboldai.net/>
|
|
||||||
* Noto Sans font by Google (OFL license)
|
|
||||||
* Icon theme by Font Awesome <https://fontawesome.com> (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
|
||||||
* AI Horde client library by ZeldaFan0225: https://github.com/ZeldaFan0225/ai_horde
|
|
||||||
* Linux startup script by AlpinDale
|
|
||||||
* Thanks paniphons for providing a FAQ document
|
|
||||||
|
@@ -1938,8 +1938,8 @@ app.post('/getgroups', jsonParser, (_, response) => {
|
|||||||
fs.mkdirSync(directories.groups);
|
fs.mkdirSync(directories.groups);
|
||||||
}
|
}
|
||||||
|
|
||||||
const files = fs.readdirSync(directories.groups);
|
const files = fs.readdirSync(directories.groups).filter(x => path.extname(x) === '.json');
|
||||||
const chats = fs.readdirSync(directories.groupChats);
|
const chats = fs.readdirSync(directories.groupChats).filter(x => path.extname(x) === '.jsonl');
|
||||||
|
|
||||||
files.forEach(function (file) {
|
files.forEach(function (file) {
|
||||||
try {
|
try {
|
||||||
@@ -2684,7 +2684,8 @@ function putAsync(url, args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function postAsync(url, args) {
|
async function postAsync(url, args) {
|
||||||
const response = await fetch(url, { method: 'POST', ...args });
|
const fetch = require('node-fetch').default;
|
||||||
|
const response = await fetch(url, { method: 'POST', timeout: 0, ...args });
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
Reference in New Issue
Block a user