Compare commits

..

47 Commits
1.6.2 ... 1.6.7

Author SHA1 Message Date
Cohee
b1d5637fcf Bump package version 2023-06-07 23:40:10 +03:00
Cohee
caa985590b Fix Poe client 2023-06-07 23:13:24 +03:00
Cohee
bb8657f93f Update readme.md 2023-06-07 20:49:27 +03:00
Cohee
c974898887 Update readme.md 2023-06-07 20:49:01 +03:00
Cohee
41e7c1533c Update readme.md 2023-06-07 20:47:08 +03:00
Cohee
658de57348 Merge pull request #457 from 10sa/patch-1 2023-06-07 15:41:58 +03:00
Tensa
7c9c0df946 Add WizardLM 13B 1.0 instruct preset
See also https://github.com/nlpxucan/WizardLM
2023-06-07 21:39:00 +09:00
Cohee
3b75ef8c39 Update readme.md 2023-06-06 13:02:59 +03:00
Cohee
c299ef0a86 Use node-fetch to make requests without timeout #435 2023-06-04 04:29:22 +03:00
Cohee
0dbf219116 Add worlds folder to docker #434 2023-06-03 17:03:57 +03:00
Cohee
b3e1dd8a42 Update update-docs.yml 2023-06-02 20:06:50 +03:00
SillyLossy
737bb5a030 Add placeholders to group folders for [BUG] docker-compose build image will not start #434 2023-06-02 20:00:58 +03:00
SillyLossy
cf9248121f Split readme files 2023-06-02 19:44:16 +03:00
Cohee
4282cb51dd Update faq.md 2023-06-02 13:22:46 +03:00
SillyLossy
efc76dfd05 Fix world info case sensitive not saving 2023-06-01 23:35:34 +03:00
Cohee
8cd6467aa9 Update update-docs.yml 2023-06-01 22:47:40 +03:00
Cohee
a1b8c135e4 Update update-docs.yml 2023-06-01 22:44:19 +03:00
Cohee
2b69c1793c Update update-docs.yml 2023-06-01 22:43:22 +03:00
Cohee
861e9a31e5 Update update-docs.yml 2023-06-01 22:24:52 +03:00
Cohee
5b926d4ad3 Create update-docs.yml 2023-06-01 22:22:47 +03:00
SillyLossy
77f76ae315 Fix group chats streaming 2023-06-01 18:55:04 +03:00
SillyLossy
c3af38f5b8 Bump package version 2023-06-01 18:34:39 +03:00
SillyLossy
3cf23f277c Fix group reply generation with streaming 2023-06-01 18:31:30 +03:00
SillyLossy
a74828df15 Disable instruct mode for OpenAI 2023-06-01 15:20:30 +03:00
SillyLossy
bf7f04e3b2 Bump package version 2023-06-01 11:18:53 +03:00
SillyLossy
5df7d2d1dc Fix /sys and /sendas attribution when converting to groups. Fix context line with /sys with OpenAI 2023-06-01 11:18:19 +03:00
SillyLossy
83c875d8dc Properly position typing indicator after user message #423 2023-06-01 10:13:49 +03:00
SillyLossy
72b7b7cab2 Merge branch 'main' of https://github.com/SillyTavern/SillyTavern 2023-06-01 10:03:35 +03:00
RossAscends
55f38f69d6 fix new char highlight, group drawers autoOpen logic 2023-06-01 10:03:08 +03:00
SillyLossy
0633d16622 Fix typing indicator not showing in group chats on NovelAI 2023-06-01 10:01:43 +03:00
SillyLossy
35cb1f6182 Fix stop button not showing for the second speaking member in queue 2023-06-01 10:01:43 +03:00
SillyLossy
a18c20305e Clarify Chroma warning message 2023-06-01 10:01:43 +03:00
SillyLossy
d542ec0d81 Add the warning when ChromaDB synced message deletes 2023-06-01 10:01:43 +03:00
SillyLossy
6ad0be9597 Fix being unable to rewrite an existing bookmark 2023-06-01 10:01:43 +03:00
SillyLossy
0de09e9da0 Fix System TTS ending abruptly in Chrome on Windows 2023-06-01 10:01:43 +03:00
SillyLossy
bb187d9920 Proper chronological order of ChromaDB chat injections 2023-06-01 10:01:11 +03:00
SillyLossy
711dbdcc15 [Feature Request] Chromadb, ability to pause collection. SillyTavern/SillyTavern#420 2023-06-01 10:01:11 +03:00
Cohee
5215e6e437 Merge pull request #421 from ramblingcoder/main
Added "worlds" to dockerfile and changed cohee1207 to sillytavern in docker image
2023-06-01 09:53:12 +03:00
ramblingcoder
01c27bc9a9 Update docker-compose.yml 2023-05-31 17:55:26 -05:00
ramblingcoder
b35d8a4324 Added worlds to dockerfile 2023-05-31 17:54:59 -05:00
Cohee
6c6f5b7f1a Merge pull request #416 from BlipRanger/patch-2
Updated UI message about chromadb persistence
2023-05-31 21:46:46 +03:00
Cohee
cff5cd0928 Update index.js 2023-05-31 21:46:13 +03:00
RossAscends
fb1b02571e UpdateAndStart.bat notification for zip installs 2023-05-31 19:08:02 +09:00
SillyLossy
412fad002d #418 Fix freeze on group with all disabled. Allow to send user messages into group with all disabled. 2023-05-31 11:36:00 +03:00
SillyLossy
6ad2492ef6 Fix TTS worker console spam in empty chat 2023-05-31 10:56:25 +03:00
Cohee
d3b0ba02b6 Update readme.md 2023-05-31 10:21:57 +03:00
BlipRanger
d80fff3b5e Updated UI message about chromadb persistence 2023-05-30 19:47:55 -04:00
23 changed files with 623 additions and 343 deletions

306
.github/readme.md vendored Normal file
View File

@@ -0,0 +1,306 @@
![image](https://github.com/Cohee1207/SillyTavern/assets/18619528/8c41a061-7f72-4d2b-9d54-e6d058209e7b)
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](https://docs.sillytavern.app/usage/faq/) 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.
### Branches
SillyTavern is being developed using a two-branch system to ensure a smooth experience for all users.
* main -🌟 **Recommended for most users.** This is the most stable and recommended branch, updated only when major releases are pushed. It's suitable for the majority of users.
* dev - ⚠️ **Not recommended for casual use.** This branch has the latest features, but be cautious as it may break at any time. Only for power users and enthusiasts.
If you're not familiar with using the git CLI or don't understand what a branch is, don't worry! The main branch is always the preferable option for you.
### 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
View 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"

View File

@@ -23,7 +23,7 @@ COPY . ./
# Copy default chats, characters and user avatars to <folder>.default folder
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 ***" && \
for R in $RESOURCES; do mv "public/$R" "public/$R.default"; done && \

View File

@@ -3,6 +3,7 @@ pushd %~dp0
git --version > nul 2>&1
if %errorlevel% neq 0 (
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 (
call git pull --rebase --autostash
if %errorlevel% neq 0 (

View File

@@ -4,7 +4,7 @@ services:
build: ..
container_name: sillytavern
hostname: sillytavern
image: cohee1207/sillytavern:latest
image: sillytavern/sillytavern:latest
ports:
- "8000:8000"
volumes:

View File

@@ -1,7 +1,7 @@
#!/bin/sh
# 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
if [ ! -e "config/$R" ]; then
echo "Resource not found, copying from defaults: $R"

2
faq.md
View File

@@ -91,7 +91,7 @@ These base instructions are only for OpenAI, which is a paid service. You can fi
### 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. 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.

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "sillytavern",
"version": "1.6.2",
"version": "1.6.7",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "sillytavern",
"version": "1.6.2",
"version": "1.6.7",
"license": "AGPL-3.0",
"dependencies": {
"@dqbd/tiktoken": "^1.0.2",

View File

@@ -46,7 +46,7 @@
"type": "git",
"url": "https://github.com/Cohee1207/SillyTavern.git"
},
"version": "1.6.2",
"version": "1.6.7",
"scripts": {
"start": "node server.js",
"pkg": "pkg --compress Gzip --no-bytecode --public ."

View File

@@ -318,7 +318,7 @@ class Client {
if (!viewer.availableBots) {
throw new Error('Invalid token.');
}
const botList = viewer.viewerBotList;
const botList = viewer.availableBotsConnection.edges.map(x => x.node);
const retries = 2;
const bots = {};
for (const bot of botList.filter(x => x.deletionState == 'not_deleted')) {

View File

@@ -0,0 +1 @@
# Put Group Chat JSONL files here

1
public/groups/README.md Normal file
View File

@@ -0,0 +1 @@
# Put Group JSON files here

View File

@@ -0,0 +1,10 @@
{
"name": "WizardLM-13B",
"system_prompt": "A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the user's questions.\n\nWrite {{char}}'s next detailed reply in a fictional roleplay chat between {{user}} and {{char}}.",
"system_sequence": "",
"stop_sequence": "",
"input_sequence": "USER: ",
"output_sequence": "ASSISTANT: ",
"separator_sequence": "",
"wrap": true
}

View File

@@ -26,6 +26,7 @@ import {
setWorldInfoSettings,
deleteWorldInfo,
world_info_recursive,
world_info_case_sensitive,
} from "./scripts/world-info.js";
import {
@@ -1764,8 +1765,9 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
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 isInstruct = power_user.instruct.enabled;
message_already_generated = isImpersonate ? `${name1}: ` : `${name2}: `;
// Name for the multigen prefix
@@ -2031,7 +2033,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
let mesSend = [];
console.log('calling runGenerate');
streamingProcessor = isStreamingEnabled() ? new StreamingProcessor(type, force_name2) : false;
await runGenerate();
runGenerate();
async function runGenerate(cycleGenerationPromt = '') {
is_send_press = true;
@@ -2385,7 +2387,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
$('#send_textarea').val(extract.getMessage).trigger('input');
}
if (shouldContinueMultigen(getMessage, isImpersonate)) {
if (shouldContinueMultigen(getMessage, isImpersonate, isInstruct)) {
hideSwipeButtons();
tokens_already_generated += this_amount_gen; // add new gen amt to any prev gen counter..
getMessage = message_already_generated;
@@ -2512,7 +2514,7 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
//console.log('generate ending');
} //generate ends
function getBiasStrings(textareaText) {
export function getBiasStrings(textareaText) {
let promptBias = '';
let messageBias = extractMessageBias(textareaText);
@@ -2551,7 +2553,7 @@ export function replaceBiasMarkup(str) {
return (str ?? '').replace(/{{(\*?.*\*?)}}/g, '');
}
async function sendMessageAsUser(textareaText, messageBias) {
export async function sendMessageAsUser(textareaText, messageBias) {
chat[chat.length] = {};
chat[chat.length - 1]['name'] = name1;
chat[chat.length - 1]['is_user'] = true;
@@ -3024,8 +3026,8 @@ function getGenerateUrl() {
return generate_url;
}
function shouldContinueMultigen(getMessage, isImpersonate) {
if (power_user.instruct.enabled && power_user.instruct.stop_sequence) {
function shouldContinueMultigen(getMessage, isImpersonate, isInstruct) {
if (isInstruct && power_user.instruct.stop_sequence) {
if (message_already_generated.indexOf(power_user.instruct.stop_sequence) !== -1) {
return false;
}
@@ -3151,17 +3153,17 @@ function cleanUpMessage(getMessage, isImpersonate, displayIncompleteSentences =
}
if (getMessage.indexOf('<|endoftext|>') != -1) {
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) {
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, '');
}
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, '');
}
// clean-up group message from excessive generations
@@ -3294,14 +3296,14 @@ export function isMultigenEnabled() {
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;
$("#send_but").css("display", "flex");
$("#send_textarea").attr("disabled", false);
hideStopButton();
}
function deactivateSendButtons() {
export function deactivateSendButtons() {
$("#send_but").css("display", "none");
showStopButton();
}
@@ -4248,27 +4250,40 @@ function select_rm_info(type, charId, previousCharId = null) {
getCharacters();
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);
element.scrollIntoView({ behavior: 'smooth', block: 'end' });
$(`#rm_characters_block [title="${charId}"]`).parent().addClass('flash animated');
setTimeout(function () {
$(`#rm_characters_block [title="${charId}"]`).parent().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: 'end' });
$(`#rm_characters_block [grid="${charId}"]`).addClass('flash animated');
setTimeout(function () {
$(`#rm_characters_block [grid="${charId}"]`).removeClass('flash animated');
}, 5000);
}
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);
}
}
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();
if (previousCharId) {
@@ -5106,6 +5121,12 @@ function importCharacter(file) {
$(document).ready(function () {
//////////INPUT BAR FOCUS-KEEPING LOGIC/////////////
setTimeout(function () {
$("#groupControlsToggle").trigger('click');
$("#groupCurrentMemberListToggle .inline-drawer-icon").trigger('click');
}, 200);
$("#rm_print_characters_block").on('scroll',
debounce(updateVisibleDivs, 5));

View File

@@ -144,6 +144,7 @@ async function createNewBookmark() {
}
}
await delay(250);
let name = await getBookmarkName();
if (!name) {
@@ -257,7 +258,7 @@ async function convertSoloToGroupChat() {
}
// 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;
}

View File

@@ -51,6 +51,22 @@ function getChatSyncState() {
const context = getContext();
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;
for (let i = 0; i < chatState.length; i++) {
if (chatState[i] === undefined) {
@@ -76,6 +92,7 @@ async function loadSettings() {
$('#chromadb_n_results').val(extension_settings.chromadb.n_results).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_freeze').prop('checked', extension_settings.chromadb.freeze);
}
function onStrategyChange() {
@@ -119,6 +136,10 @@ function checkChatId(chat_id) {
}
async function addMessages(chat_id, messages) {
if (extension_settings.chromadb.freeze) {
return { count: 0 };
}
const url = new URL(getApiUrl());
url.pathname = '/api/chromadb';
@@ -328,12 +349,13 @@ async function onSelectInjectFile(e) {
const text = await getFileText(file);
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)}`,
role: 'system',
content: m,
date: Date.now(),
date: baseDate + i,
meta: JSON.stringify({
name: file.name,
is_user: false,
@@ -380,7 +402,7 @@ window.chromadb_interceptGeneration = async (chat) => {
if (currentChatId) {
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);
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 () => {
const settingsHtml = `
<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}" />
<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}" />
<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 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>
@@ -472,7 +503,7 @@ jQuery(async () => {
<span>Purge Chat from the DB</span>
</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>
<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>
@@ -490,6 +521,7 @@ jQuery(async () => {
$('#chromadb_import_file').on('change', onSelectImportFile);
$('#chromadb_purge').on('click', onPurgeClick);
$('#chromadb_export').on('click', onExportClick);
$('#chromadb_freeze').on('input', onFreezeInput);
await loadSettings();
// Not sure if this is needed, but it's here just in case

View File

@@ -110,6 +110,7 @@ async function moduleWorker() {
// We're currently swiping or streaming. Don't generate voice
if (
!message ||
message.mes === '...' ||
message.mes === '' ||
(context.streamingProcessor && !context.streamingProcessor.isFinished)

View File

@@ -1,5 +1,74 @@
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 {
//########//
// Config //
@@ -142,7 +211,12 @@ class SystemTtsProvider {
utterance.pitch = this.settings.pitch || 1;
utterance.onend = () => resolve(silence);
utterance.onerror = () => reject();
speechSynthesis.speak(utterance);
speechUtteranceChunker(utterance, {
chunkLength: 200,
}, function () {
//some code to execute when done
console.log('System TTS done');
});
});
}
}

View File

@@ -48,6 +48,11 @@ import {
cancelTtsPlay,
isMultigenEnabled,
displayPastChats,
sendMessageAsUser,
getBiasStrings,
saveChatConditional,
deactivateSendButtons,
activateSendButtons,
} from "../script.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();
const lastMessage = chat[chat.length - 1];
let messagesBefore = chat.length;
let lastMessageText = lastMessage.mes;
let lastMessageText = lastMessage?.mes || '';
let activationText = "";
let isUserInput = false;
let isGenerationDone = false;
@@ -491,11 +496,17 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
if (activatedMembers.length === 0) {
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
for (const chId of activatedMembers) {
deactivateSendButtons();
isGenerationDone = false;
const generateType = type == "swipe" || type == "impersonate" || type == "quiet" ? type : "group_chat";
setCharacterId(chId);
@@ -509,13 +520,14 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
.find(".typing_indicator_name")
.text(characters[chId].name);
$("#chat").append(typingIndicator);
typingIndicator.show(250, function () {
typingIndicator.show(200, function () {
typingIndicator.get(0).scrollIntoView({ behavior: "smooth" });
});
}
// TODO: This is awful. Refactor this
while (true) {
deactivateSendButtons();
if (isGenerationAborted) {
throw new Error('Group generation aborted');
}
@@ -590,16 +602,23 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
await delay(100);
}
}
else if (isStreamingEnabled()) {
if (streamingProcessor && !streamingProcessor.isFinished) {
await delay(100);
} else {
messagesBefore++;
break;
}
}
else {
messagesBefore++;
break;
}
}
}
} finally {
// hide and reapply the indicator to the bottom of the list
typingIndicator.hide(250);
typingIndicator.hide(200);
$("#chat").append(typingIndicator);
is_group_generating = false;
@@ -607,6 +626,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
setSendButtonState(false);
setCharacterId(undefined);
setCharacterName('');
activateSendButtons();
showSwipeButtons();
}
}
@@ -714,7 +734,8 @@ function activateNaturalOrder(members, input, lastMessage, allowSelfResponses, i
}
// 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 character = characters.find((x) => x.avatar === members[randomIndex]);
@@ -959,6 +980,9 @@ function select_group_chats(groupId, skipAnimation) {
$("#rm_group_scenario").show();
} else {
$("#rm_group_submit").show();
if ($("#groupAddMemberListToggle .inline-drawer-content").css('display') !== 'block') {
$("#groupAddMemberListToggle").trigger('click');
}
$("#rm_group_delete").hide();
$("#rm_group_scenario").hide();
}

View File

@@ -56,6 +56,7 @@ export {
let openai_msgs = [];
let openai_msgs_example = [];
let openai_messages_count = 0;
let openai_narrator_messages_count = 0;
let is_get_status_openai = false;
let is_api_button_press_openai = false;
@@ -175,6 +176,7 @@ function setOpenAIMessages(chat) {
let j = 0;
// clean openai msgs
openai_msgs = [];
openai_narrator_messages_count = 0;
for (let i = chat.length - 1; i >= 0; i--) {
let role = chat[j]['is_user'] ? 'user' : 'assistant';
let content = chat[j]['mes'];
@@ -182,6 +184,7 @@ function setOpenAIMessages(chat) {
// 100% legal way to send a message as system
if (chat[j].extra?.type === system_message_types.NARRATOR) {
role = 'system';
openai_narrator_messages_count++;
}
// 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,
// now we want proper order
openai_msgs_tosend.reverse();

View File

@@ -162,7 +162,7 @@ export function saveCaretPosition(element) {
end: range.endOffset
};
console.log('Caret saved', position);
console.debug('Caret saved', position);
return position;
}
@@ -174,7 +174,7 @@ export function restoreCaretPosition(element, position) {
return;
}
console.log('Caret restored', position);
console.debug('Caret restored', position);
// Create a new range object
const range = new Range();

331
readme.md
View File

@@ -1,307 +1,68 @@
![image](https://github.com/Cohee1207/SillyTavern/assets/18619528/8c41a061-7f72-4d2b-9d54-e6d058209e7b)
# 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.
![](https://github.com/Cohee1207/SillyTavern/assets/18619528/8c41a061-7f72-4d2b-9d54-e6d058209e7b)
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.
![](https://user-images.githubusercontent.com/18619528/228649245-8061c60f-63dc-488e-9325-f151b7a3ec2d.png)
![](https://user-images.githubusercontent.com/18619528/228649856-fbdeef05-d727-4d5a-be80-266cbbc6b811.png)
## 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.**
## 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:
### How can I 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
### I like your project! How do I contribute?
* 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)
* 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 | ![image](https://user-images.githubusercontent.com/128647114/230311637-d809cd9b-af66-4dd1-a310-7a27e847c011.png) |
| 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
* We welcome pull requests!
* We also welcome helpful and informed bug reports that use the templates provided in our GitHub.
* We do not accept monetary donations for the project itself.
* Cohee does not take donations.
* RossAscends has a personal [Patreon](https://www.patreon.com/RossAscends) & [Kofi](https://ko-fi.com/rossascends)

View File

@@ -1938,8 +1938,8 @@ app.post('/getgroups', jsonParser, (_, response) => {
fs.mkdirSync(directories.groups);
}
const files = fs.readdirSync(directories.groups);
const chats = fs.readdirSync(directories.groupChats);
const files = fs.readdirSync(directories.groups).filter(x => path.extname(x) === '.json');
const chats = fs.readdirSync(directories.groupChats).filter(x => path.extname(x) === '.jsonl');
files.forEach(function (file) {
try {
@@ -2684,7 +2684,8 @@ function putAsync(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) {
const data = await response.json();