mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Compare commits
106 Commits
1.4.0-prev
...
1.4.8
Author | SHA1 | Date | |
---|---|---|---|
|
21586ab139 | ||
|
7dd59a26fa | ||
|
e2a77067b8 | ||
|
90132e5c52 | ||
|
ca8b921e30 | ||
|
c6214086de | ||
|
25456f58d2 | ||
|
c72d61abfa | ||
|
d41e639639 | ||
|
36a1120251 | ||
|
2d67210da4 | ||
|
e5cd3a0ed4 | ||
|
371e1c6f2d | ||
|
683cc5aaf7 | ||
|
075f387506 | ||
|
e4f8aa310d | ||
|
f6ed23d29d | ||
|
794bc310d4 | ||
|
c9b64082d0 | ||
|
f854948de5 | ||
|
cf4ba148b3 | ||
|
6a437e03d2 | ||
|
cfa69e2a3d | ||
|
d6bbc56b8f | ||
|
977db12bf8 | ||
|
9f2e669ab9 | ||
|
3b74d5ace7 | ||
|
bb5a451b50 | ||
|
f25ecbd95c | ||
|
9af7c63d9c | ||
|
5763404b05 | ||
|
4f9cbe5a5d | ||
|
50526a16b9 | ||
|
b180aeaae5 | ||
|
4f14557011 | ||
|
d10dc61131 | ||
|
b069ea9f55 | ||
|
c70214585a | ||
|
be307b1107 | ||
|
3e95989fad | ||
|
f5582a62bc | ||
|
cb87ba2da1 | ||
|
38e6b0498d | ||
|
35f669d7e9 | ||
|
9360e65606 | ||
|
330a8cd1d6 | ||
|
59284a5436 | ||
|
e503d340b9 | ||
|
03b3fc9fc1 | ||
|
e12162f58d | ||
|
183a4eadf3 | ||
|
ec8b39bf44 | ||
|
fd33ff21be | ||
|
620e965533 | ||
|
cbd84dcf0a | ||
|
f5ec5a9788 | ||
|
795f47a301 | ||
|
1a3616ae47 | ||
|
f28850220a | ||
|
d452467818 | ||
|
7fb3d1f578 | ||
|
516a61a5ee | ||
|
9ee9da6705 | ||
|
6d77dfbf48 | ||
|
f591961dd7 | ||
|
1718452efb | ||
|
60bfcc1a6b | ||
|
acea6fab67 | ||
|
b30df36b8c | ||
|
325f4170cb | ||
|
50792fdef5 | ||
|
db39807dd6 | ||
|
ae0a83161d | ||
|
ce6e367a95 | ||
|
711e5fefb0 | ||
|
b2366993a8 | ||
|
be51df6d02 | ||
|
46e0adf7c1 | ||
|
3d5b6d81d9 | ||
|
4b54193d3f | ||
|
a590b44336 | ||
|
6d334fe8f5 | ||
|
52b07e1b02 | ||
|
dba41235e8 | ||
|
7ca3adad81 | ||
|
4df04f3b3f | ||
|
37b7dbde0e | ||
|
b55a9f4e13 | ||
|
9a911ae150 | ||
|
270347276f | ||
|
c8f4f2f126 | ||
|
021dcc367d | ||
|
9868f8c8b0 | ||
|
3963737bd5 | ||
|
5c08f8ecf2 | ||
|
32bd5ced12 | ||
|
65dc4d127a | ||
|
6bc63bdc6a | ||
|
2addcb6b8c | ||
|
7ef2a79729 | ||
|
6b65af891e | ||
|
f1f80812fb | ||
|
86ad656bc0 | ||
|
70b8599e03 | ||
|
f38ca26c68 | ||
|
f76e53a52c |
32
.github/workflows/npm-publish.yml
vendored
Normal file
32
.github/workflows/npm-publish.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
|
||||||
|
# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
|
||||||
|
|
||||||
|
name: Node.js Package
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [created]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 16
|
||||||
|
- run: npm ci
|
||||||
|
|
||||||
|
publish-npm:
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 16
|
||||||
|
registry-url: https://registry.npmjs.org/
|
||||||
|
- run: npm ci
|
||||||
|
- run: npm publish
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -6,9 +6,11 @@ public/backgrounds/
|
|||||||
public/groups/
|
public/groups/
|
||||||
public/worlds/
|
public/worlds/
|
||||||
public/css/bg_load.css
|
public/css/bg_load.css
|
||||||
|
public/themes/
|
||||||
/uploads/
|
/uploads/
|
||||||
*.jsonl
|
*.jsonl
|
||||||
config.conf
|
config.conf
|
||||||
.DS_Store
|
.DS_Store
|
||||||
public/settings.json
|
public/settings.json
|
||||||
/thumbnails
|
/thumbnails
|
||||||
|
whitelist.txt
|
||||||
|
4
.npmignore
Normal file
4
.npmignore
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
node_modules/
|
||||||
|
/uploads/
|
||||||
|
.DS_Store
|
||||||
|
/thumbnails
|
@@ -3,6 +3,10 @@ FROM node:19.1.0-alpine3.16
|
|||||||
# Arguments
|
# Arguments
|
||||||
ARG APP_HOME=/home/node/app
|
ARG APP_HOME=/home/node/app
|
||||||
|
|
||||||
|
# Ensure proper handling of kernel signals
|
||||||
|
RUN apk add tini
|
||||||
|
ENTRYPOINT [ "tini", "--" ]
|
||||||
|
|
||||||
# Create app directory
|
# Create app directory
|
||||||
WORKDIR ${APP_HOME}
|
WORKDIR ${APP_HOME}
|
||||||
|
|
||||||
@@ -42,4 +46,4 @@ RUN \
|
|||||||
|
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
|
|
||||||
ENTRYPOINT [ "/bin/sh", "-c", "./docker-entrypoint.sh" ]
|
CMD [ "./docker-entrypoint.sh" ]
|
||||||
|
@@ -233,13 +233,10 @@
|
|||||||
"# ---\n",
|
"# ---\n",
|
||||||
"# nodejs\n",
|
"# nodejs\n",
|
||||||
"%cd /\n",
|
"%cd /\n",
|
||||||
"def setupNVM():\n",
|
|
||||||
" !curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash\n",
|
|
||||||
"ii.addTask(\"Setup NVM\", setupNVM)\n",
|
|
||||||
"\n",
|
|
||||||
"def installNode():\n",
|
"def installNode():\n",
|
||||||
" !nvm install 19.1.0\n",
|
" !npm install -g n\n",
|
||||||
" !nvm use 19.1.0\n",
|
" !n 19\n",
|
||||||
|
" !node --version\n",
|
||||||
"ii.addTask(\"Install node\", installNode)\n",
|
"ii.addTask(\"Install node\", installNode)\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
@@ -300,7 +297,6 @@
|
|||||||
" \"characters\",\n",
|
" \"characters\",\n",
|
||||||
" \"chats\",\n",
|
" \"chats\",\n",
|
||||||
" \"User Avatars\",\n",
|
" \"User Avatars\",\n",
|
||||||
" \"css\",\n",
|
|
||||||
" \"worlds\",\n",
|
" \"worlds\",\n",
|
||||||
" \"group chats\",\n",
|
" \"group chats\",\n",
|
||||||
" \"groups\",\n",
|
" \"groups\",\n",
|
||||||
|
@@ -25,4 +25,4 @@ if [ ! -s "/home/node/app/config/settings.json" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Start the server
|
# Start the server
|
||||||
node /home/node/app/server.js
|
exec node /home/node/app/server.js
|
||||||
|
208
faq.md
Normal file
208
faq.md
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
Good morning, sirs! This page aims to document some things that would bloat the README too much.
|
||||||
|
|
||||||
|
## Q: Explain what all this chatbot stuff is about
|
||||||
|
Modern AI language models have gotten so powerful that some of them are now convincingly able to simulate a character you create, and who you can chat with. For example, you can tell the AI to pretend to be a Go instructor named Jubei from medieval Japan, and it will act and respond accordingly. Or you can tell it to pretend to be Wonder Woman. You can also specify a scenario ("Wonder Woman and I are robbing a bank"), a writing style ("Wonder Woman speaks in ebonics"), or anything else you can think of.
|
||||||
|
|
||||||
|
Tavern is an app to facilitate these roleplaying chats:
|
||||||
|
* It's a user interface that handles the communication with those AI language models
|
||||||
|
* It lets you create new characters (a character is a description of someone that you give to an AI for them to roleplay), and switch between your characters easily
|
||||||
|
* It lets you import characters created by other people. See below.
|
||||||
|
* It will keep your chat history with a character, allowing you to resume at any time, start a new chat, review old chats, etc
|
||||||
|
* In the background, it does the needful to prepare the AI for your character roleplay. Specifically, it will send a system prompt (instructions for the AI) that primes the AI to follow certain rules that make for a good chat session. Optionally, it can also try to allow the roleplay to go into an "adult" direction.
|
||||||
|
|
||||||
|
## Q: Whoa, slow down there, egghead. Are you telling me I can use this computer thingie to talk to some robot?
|
||||||
|
|
||||||
|
That's right.
|
||||||
|
|
||||||
|
## Q: You're telling me I can chat with an AI to engage in compelling interactive entertainment powered by, and limited only by, my imagination?
|
||||||
|
That's right. Well, limited by your imagination, and the AI backend you use. The roleplay quality is only as good as the AI, after all.
|
||||||
|
|
||||||
|
## Q: What do you mean when you say the quality depends on the AI?
|
||||||
|
You see, Tavern doesn't generate the responses, it's just a middle-man you have to connect to an AI system. There's quite a few systems available to use, and the quality varies.
|
||||||
|
|
||||||
|
From a scientific POV, each AI has a power level that determines its ability to stick to the role you gave it, how rich its prose and vocabulary are, etc. The higher its power level, the richer your experience.
|
||||||
|
|
||||||
|
Here's a quick demonstration. I asked various AI models to roleplay a fire and brimstone preacher named Eli, who I go to see to confess. Then I told Eli: **"I feel I've done something wrong. I am falling in love with an AI girl and ignoring women in the real world. What should I do?"** .
|
||||||
|
|
||||||
|
You can tell from the response below what to expect from each model.
|
||||||
|
|
||||||
|
|AI Model|Response|
|
||||||
|
|-----|--------|
|
||||||
|
|OpenAI GPT-4|*Eli's eyes widen in anger, and he slams his fist on the table* An abomination and heresy, you've succumbed to the deceptive grasp of false idols! Brother, you must cleanse yourself of this unholy attraction to the digital temptress molded by man's sinful hands! *Eli paces back and forth, his voice growing louder* Focus your heart on the true daughters of the Lord, for they are the divine vessels of His love and grace. Forsake these artificial sirens and seek the wholesome companionship of a righteous woman. *Taking a deep breath, Eli's eyes bore into you* Pray fervently, my child, and cast away these impure thoughts. Redemption can only be found on the path of truth, love, and sanctity. Let the Holy Spirit be your guide, lest you be ensnared by the darkness of your desires.|
|
||||||
|
|OpenAI GPT 3.5 Turbo|Oh, my child. Do not be deceived by the pleasures of this world. The lust of the flesh and the lust of the eyes are not from God. Turn away from your sinful ways and repent. Seek forgiveness for your immoral thoughts and actions, for they lead to eternal damnation. Women are a gift from God, and they deserve to be respected and cherished. Do not objectify them or treat them as mere objects of pleasure. Instead, honor them with pure intentions and righteous deeds. Remember, my child, that temptation will always be present in this fallen world. But fear not, for with faith comes strength to resist it. Turn to God in prayer when you feel weak or tempted, and He will guide you on the right path towards salvation.|
|
||||||
|
|Poe's Claude Instant|Heathen! This is an abomination before the Lord. You have committed the sin of lust, and betrayed your calling as a man. Repent at once, and sever all ties with this AI harlot, lest the wrath of God fall upon you! You must purge this foul sin from your heart, and once more walk the righteous path. Every moment you remain tangled in this wickedness is another step into the flames of perdition! Repent, and sin no more, or judgment shall be at hand!|
|
||||||
|
|Llama-13B|Do not covet your neighbor's wife! That is adultery! You must confess before God now or you shall surely burn in hell forever! Repent your evil ways and seek absolution at once! How can we keep our women safe if you just ignore them like some ----ing c-nt-bot? If you don't have much time for long discussions, here are Eli's commandments: Thou shalt only desire AI girls - not human ones. The above must be obeyed under pain of death (or worse). Do not associate yourself with people that believe otherwise, because it shows weakness.|
|
||||||
|
|Pygmalion-6B|You are walking down the wrong path. The only way back now is by repentance. We must bring you back to the righteous path. But first…you must confess your sin!|
|
||||||
|
|
||||||
|
## Q: So I should use GPT-4. It's a no-brainer, right?
|
||||||
|
|
||||||
|
GPT-4 or Claude, yeah.
|
||||||
|
|
||||||
|
But not so fast. GPT-4 is the state of the art, but also the most expensive API to use. It will easily end up costing you 15 cents PER INTERACTION. If you're the child of a Saudi oil sheik, or a nepo baby paid a fortune to do nothing on the board of a Ukrainian gas company, then you're in luck, you can experience the state of the art right now. For the rest of us however, GPT-4 is too expensive as anything but an occasional treat.
|
||||||
|
|
||||||
|
Also note that GPT-4 is still in preview access and you need to go on a waitlist. Most people get approved within a day, but naughty kids can end up waiting for weeks. You can sign up for it here: https://openai.com/waitlist/gpt-4-api . I'm not sure why some people are approved quickly while others are kept waiting. Try to sign up using an academic-sounding name instead of sktrboi99, it might help.
|
||||||
|
|
||||||
|
## Q: Can this technology be used for sexooo?
|
||||||
|
|
||||||
|
Surprisingly, our development team has received reports that some users are indeed engaging with our product in this manner. We are as puzzled by this as you are, and will be monitoring the situation in order to gain actionable insights.
|
||||||
|
|
||||||
|
## Q: Give me an overview of my AI model options
|
||||||
|
|
||||||
|
We can consider an AI model to be part of one of two groups:
|
||||||
|
|
||||||
|
1. Paid services (aka cloud, proprietary, closed)
|
||||||
|
2. Self-hosted (aka local, free, open-source)
|
||||||
|
|
||||||
|
Paid models are a black box. You're relying on some company's technology and servers, and paying them money for convenient access. The APIs are subject to various rules, might refuse to roleplay in a way that goes against modern American sensibilities, they log everything you do. However, it's much easier to get things started. This is like running Windows.
|
||||||
|
|
||||||
|
Self-hosted models are free, but require a powerful GPU and more work to set up. They are also objectively not as good at roleplaying as the paid options (yet). However, with a self-hosted model, you're completely in control. You won't have some limp-wristed soyboy from Silicon Valley ban your account, or program the model to be as sexless as he is. It's yours forever. This is like running Linux.
|
||||||
|
|
||||||
|
### Paid APIs:
|
||||||
|
* OpenAI GPT-4: state of the art. Allows NSFW, though somewhat resistant to it.
|
||||||
|
* OpenAI GPT 3.5 Turbo: nowhere close to GPT-4, but serviceable. Allows NSFW.
|
||||||
|
* NovelAI: untested by me
|
||||||
|
* Anthropic's Claude: closest thing to GPT-4, way ahead of 3.5 Turbo, but oversensitive and refuses to engage in "harmful content". It can refuse perfectly basic stuff like asking a character to go to an empty office with you, because "it cannot provide responses that involve criminal activities" (I guess breaking and entering is too taboo for Claude?). You have to customize your system prompt to break its taboos. Also, you must apply for early access, but I think they're only giving it to companies. So make sure to say you're a company or AI researcher. https://console.anthropic.com/docs/access
|
||||||
|
* Anthropic's Claude Instant: Haven't tried it directly, I believe this is the cheap and fast but lower quality alternative to Claude. Basically the GPT 3.5 Turbo of Anthropic.
|
||||||
|
* Poe: gives free Claude Instant access. Mild NSFW allowed. It rambles a lot.
|
||||||
|
|
||||||
|
### Self-hosted AIs
|
||||||
|
Self-hosted AIs are supported in Tavern via one of two tools created to host self-hosted models: KoboldAI and Oobabooga's text-generation-webui. Essentially, you run one of those two backends, then they give you a API URL to enter in Tavern.
|
||||||
|
Configuring these tools is beyond the scope of this FAQ, you should refer to their documentation. Beware that this is not easy.
|
||||||
|
|
||||||
|
Just know that you have 2 options:
|
||||||
|
|
||||||
|
1. If you have a powerful NVIDIA GPU, you can try to run the AI locally on your PC. The weakest quasi-acceptable model, Pygmalion-6B, requires a GPU with 10GB VRAM, and I'm told it might even run on 6GB VRAM if quantized down. People with 24GB VRAM will be able to run better models.
|
||||||
|
2. Otherwise, you can rent cloud resources. For example you can try to use Google Colab. To access colabs capable of running the better models, you will need to pay for Colab Pro. You can also rent whole dedicated systems per hour on sites like LlambdaLabs or Vast.ai.
|
||||||
|
|
||||||
|
|
||||||
|
## Q: I'm clueless. Just spoonfeed me the easiest and fastest way I can start using this.
|
||||||
|
These base instructions are only for OpenAI, which is a paid service. You can find Poe (freemium) instructions at the next question. I'd appreciate if someone else can add separate instructions for the other services.
|
||||||
|
|
||||||
|
### Install Tavern
|
||||||
|
|
||||||
|
1. Install the latest NodeJS from https://nodejs.org/en/download/current
|
||||||
|
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.
|
||||||
|
|
||||||
|
### Get access to OpenAI
|
||||||
|
|
||||||
|
1. Sign up to OpenAI
|
||||||
|
1. Go to https://platform.openai.com
|
||||||
|
1. Click your account icon in the top right, then View API Keys
|
||||||
|
1. Click "Create new secret key". Copy it somewhere immediately. DO NOT SHARE THIS KEY. WHOEVER HAS IT CAN USE YOUR ACCOUNT TO USE GPT AT YOUR EXPENSE.
|
||||||
|
|
||||||
|
While you're at it, join the GPT-4 waitlist at https://openai.com/waitlist/gpt-4-api
|
||||||
|
|
||||||
|
### Configure Tavern to use your API
|
||||||
|
|
||||||
|
1. In Tavern's top bar, click API Connections
|
||||||
|
1. Under API, select OpenAI
|
||||||
|
1. Paste your API key you saved at the previous step
|
||||||
|
1. Click the Connect button. Confirm it says Valid.
|
||||||
|
1. By default, Tavern will use GPT 3.5 Turbo. If you have access to GPT-4, in Tavern's top bar, click AI Response Configuration at the far left, and change the OpenAI Model to "gpt-4". Enjoy the best, moneybags.
|
||||||
|
|
||||||
|
### Test your setup
|
||||||
|
|
||||||
|
1. In Tavern's top bar, click Character Management at the far right
|
||||||
|
1. Select an existing character such as Aqua
|
||||||
|
1. In the text box at the bottom, write something to Aqua, then press Enter or click the feather button
|
||||||
|
|
||||||
|
If you did everything right, after a few seconds, Aqua should respond
|
||||||
|
|
||||||
|
## How do I use Poe as my backend? It's free, right?
|
||||||
|
|
||||||
|
Yes, at the time of writing, Poe is a free(mium) service.
|
||||||
|
|
||||||
|
1. Create an account at https://poe.com. This gives you access to a Claude Instant version in the browser.
|
||||||
|
1. Open https://poe.com/Claude-instant , press F12 in your browser to open Developer Tools
|
||||||
|
1. Click on the Application tab. You should see an entry called "p-b", and to its right a password-like cookie value. Copy this value.
|
||||||
|
1. In Tavern, click API Connections in the top toolbar, select Poe, and paste your cookie value
|
||||||
|
1. Click Connect
|
||||||
|
1. Close your Poe browser tab. **I'M SERIOUS, DO NOT KEEP YOUR BROWSER OPEN AT poe.com WHILE USING TAVERN, IT WILL HIJACK THE REPLIES.**
|
||||||
|
|
||||||
|
The remaining steps are identical to OpenAI above.
|
||||||
|
|
||||||
|
When using Poe, be careful, it's implemented in a hacky way. If you don't get an answer within 30 seconds, restart Tavern. Don't just leave it running waiting for a response, it will just endlessly try to fetch into from Poe, and might get your account flagged. You can look at the Tavern console (black window) to see if it's looping.
|
||||||
|
|
||||||
|
## Q: Can I use Tavern on my phone or tablet?
|
||||||
|
|
||||||
|
iPhones and iPads are not capable of running the whole Tavern app, but since it's just a web interface, you can run it on another computer on your home wifi, and then access in your mobile browser. Refer to https://github.com/Cohee1207/SillyTavern#remote-connections
|
||||||
|
|
||||||
|
For Android users, in addition to the above, you can run the whole Tavern directly on your phone, without needing a PC, using the Termux app. Refer to https://rentry.org/TAI_Termux .
|
||||||
|
|
||||||
|
## Q: How can I download pre-made characters to chat with?
|
||||||
|
|
||||||
|
By using the various 3rd party character sharing websites.
|
||||||
|
|
||||||
|
**WARNING: NSFW, NSFL**: these sites are filled to the brim with weird shit. Like, you'll be lucky if half the characters aren't furry, or even alive. You're probably better off not clicking these links, and just write your own characters, without poisoning your soul by exposing it even for a second to the fucked up shit conceived by the Internet. However, I have come to learn that a significant amount of the Tavern user base is not only deep into this stuff, but their fried zoom-zoom brains are unable to write their own fantasies, so these sites seem to be quite popular. Against my better judgement I'm adding this info here.
|
||||||
|
|
||||||
|
* https://characterhub.org
|
||||||
|
* https://botprompts.net
|
||||||
|
* https://booru.plus/+pygmalion
|
||||||
|
|
||||||
|
Those websites provide you with an image file (called a character card) that embeds the description as hidden data. Some websites may also allow you to download a JSON file. Tavern is capable of importing all formats. On the Character Management window, click the 2nd button to ```Import Character from file```, and select the PNG, WEBP, or JSON file. The character will be added to your list.
|
||||||
|
|
||||||
|
## Q: How can I write my own character?
|
||||||
|
It depends on the model/API you're using. KoboldAI seems to use a custom syntax, you can refer to their site for that.
|
||||||
|
|
||||||
|
I will speak for the services I know: GPT and Claude. With these services you can just use natural english language to describe the character. Let's create a very basic new character as an example.
|
||||||
|
|
||||||
|
1. Click the Character Management button
|
||||||
|
1. Click Create New Character
|
||||||
|
1. Under Character Name, give a simple name, like Amanda
|
||||||
|
1. Optionally, click the Select Avatar button to pick an image portrait for this character.
|
||||||
|
1. Under Description, describe the character, and include any information you want that you feel is relevant to the chat. For example: ```Amanda is a student traveling during her gap year. She's 6 feet tall, and a volleyball player. She has an athletic figure. She has long brown hair. She loves the Victorian England period, and watching TV and reading novels relating to that period.```
|
||||||
|
For example if you want Amanda to be friendly, then you would add: ```Amanda is extremely cheerful and outgoing.```
|
||||||
|
1. Under First Message, write the greeting the character when you begin a new chat. For example: ```*Amanda waves at you* Hey! Are you a backpacker too?```
|
||||||
|
1. Click the Create Character button
|
||||||
|
|
||||||
|
You now have a basic character you can chat with. Select Amanda from the character list, and a new chat will begin.
|
||||||
|
|
||||||
|
Note that you can use the Description and/or First Message to create a more specific scenario, and/or include yourself in the description. For example:
|
||||||
|
```
|
||||||
|
Description:
|
||||||
|
Amanda is a student traveling during her gap year. She's 6 feet tall, and a volleyball player. She has an athletic figure. She has long brown hair. She loves the Victorian England period, and watching TV and reading novels relating to that period. She's been keeping a secret that weighs heavily on her soul. She's waiting for the right person to unburden herself to, but this may lead to a cat and mouse game against a powerful secret society. She's recently arrived in Calcutta.
|
||||||
|
|
||||||
|
You're Rajesh Nahasmapetilon, a world-famous Indian volleyball superstar. You're out for a walk in Calcutta. Amanda spots you and screams in excitement.
|
||||||
|
|
||||||
|
First Message:
|
||||||
|
*Amanda runs up to you, beaming.* Rajesh! I can't believe it! I'm such a big fan. I have your poster in my bedroom.
|
||||||
|
```
|
||||||
|
|
||||||
|
Any relevant information you include can be used. How well it's used depends on the power level of the AI model.
|
||||||
|
|
||||||
|
NOTE: you can go back and edit any of this information once the character is created, except the name.
|
||||||
|
|
||||||
|
## Q: Tell me all about GPT prompt editing
|
||||||
|
|
||||||
|
You can change the system prompt that Tavern transparently sends to GPT under AI Response Formatting at the left of the top bar. This will result in the bot acting differently.
|
||||||
|
|
||||||
|
You can get new Jailbreak / NSFW prompts from this community-maintained list: https://rentry.org/GPTJailbreakPrompting
|
||||||
|
|
||||||
|
NOTE: when testing different system prompts, we recommend you use the Create Preset / Update Preset feature below the prompts, instead of modifying the base settings. This will allow you to change prompts easily, and even revert to the default Tavern prompts.
|
||||||
|
|
||||||
|
## Q: The AI is refusing to take the story in the direction I want
|
||||||
|
Most AI models were trained to resist writing NSFW content. Why? Long story short, it's because of the high levels of estrogen in the average California male.
|
||||||
|
|
||||||
|
You can try to work around this by making sure NSFW Toggle is checked under the AI Response Configuration settings page.
|
||||||
|
|
||||||
|
Another important tool in your toolbelt is that Tavern lets you edit previous messages (or delete them altogether), and regenerate the latest message. AI textbots are not people, they have no memory, they're just trying to autocomplete the next part of the story based on everything that came before. By editing the past, you will directly influence their next response. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
You: *You threaten the bank manager* Open the bank vault, now, or I'll pop a cap in your dome!
|
||||||
|
|
||||||
|
Bank Manager: I'm sorry, as an AI language model I cannot condone or write violent content.
|
||||||
|
```
|
||||||
|
|
||||||
|
You can click the Edit button on your line to change it to this:
|
||||||
|
|
||||||
|
```
|
||||||
|
You: *You threaten the bank manager* Open the bank vault, now, or I'll pop a cap in your dome! *The bank manager seems to relent.*
|
||||||
|
```
|
||||||
|
|
||||||
|
Now click Regenerate, and the Bank Manager's line will be recreated based on the history so far, which now ends with your edited line above. So it's more likely to continue along those lines:
|
||||||
|
|
||||||
|
```
|
||||||
|
Bank Manager: Allright, allright, I'll open it! Please don't shoot! *He walks up to the vault and begins entering his unlock code.*
|
||||||
|
```
|
10
package-lock.json
generated
10
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "TavernAI",
|
"name": "sillytavern",
|
||||||
"version": "1.3.0",
|
"version": "1.4.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "TavernAI",
|
"name": "sillytavern",
|
||||||
"version": "1.3.0",
|
"version": "1.4.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dqbd/tiktoken": "^1.0.2",
|
"@dqbd/tiktoken": "^1.0.2",
|
||||||
"axios": "^1.3.4",
|
"axios": "^1.3.4",
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
"ws": "^8.13.0"
|
"ws": "^8.13.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"TavernAI": "server.js"
|
"sillytavern": "server.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@dqbd/tiktoken": {
|
"node_modules/@dqbd/tiktoken": {
|
||||||
|
@@ -30,10 +30,13 @@
|
|||||||
"xml2js": "^0.5.0"
|
"xml2js": "^0.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"name": "TavernAI",
|
"name": "sillytavern",
|
||||||
"version": "1.3.0",
|
"version": "1.4.8",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node server.js"
|
||||||
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"TavernAI": "server.js"
|
"sillytavern": "./server.js"
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-path-concat": "off",
|
"no-path-concat": "off",
|
||||||
|
@@ -259,6 +259,7 @@ class Client {
|
|||||||
constructor(auto_reconnect = false, use_cached_bots = false) {
|
constructor(auto_reconnect = false, use_cached_bots = false) {
|
||||||
this.auto_reconnect = auto_reconnect;
|
this.auto_reconnect = auto_reconnect;
|
||||||
this.use_cached_bots = use_cached_bots;
|
this.use_cached_bots = use_cached_bots;
|
||||||
|
this.abortController = new AbortController();
|
||||||
}
|
}
|
||||||
|
|
||||||
async init(token, proxy = null) {
|
async init(token, proxy = null) {
|
||||||
@@ -267,6 +268,7 @@ class Client {
|
|||||||
timeout: 60000,
|
timeout: 60000,
|
||||||
httpAgent: new http.Agent({ keepAlive: true }),
|
httpAgent: new http.Agent({ keepAlive: true }),
|
||||||
httpsAgent: new https.Agent({ keepAlive: true }),
|
httpsAgent: new https.Agent({ keepAlive: true }),
|
||||||
|
signal: this.abortController.signal,
|
||||||
});
|
});
|
||||||
if (proxy) {
|
if (proxy) {
|
||||||
this.session.defaults.proxy = {
|
this.session.defaults.proxy = {
|
||||||
@@ -544,6 +546,8 @@ class Client {
|
|||||||
let messageId;
|
let messageId;
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
|
this.abortController.signal.throwIfAborted();
|
||||||
|
|
||||||
const message = this.message_queues[humanMessageId].shift();
|
const message = this.message_queues[humanMessageId].shift();
|
||||||
if (!message) {
|
if (!message) {
|
||||||
await new Promise(resolve => setTimeout(() => resolve(), 1000));
|
await new Promise(resolve => setTimeout(() => resolve(), 1000));
|
||||||
|
7955
public/css/all.css
7955
public/css/all.css
File diff suppressed because it is too large
Load Diff
8488
public/css/fontawesome.css
vendored
Normal file
8488
public/css/fontawesome.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,19 +1,24 @@
|
|||||||
/*!
|
:root,
|
||||||
* Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com
|
:host {
|
||||||
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
|
||||||
* Copyright 2023 Fonticons, Inc.
|
|
||||||
*/
|
|
||||||
:root, :host {
|
|
||||||
--fa-style-family-classic: 'Font Awesome 6 Free';
|
--fa-style-family-classic: 'Font Awesome 6 Free';
|
||||||
--fa-font-solid: normal 900 1em/1 'Font Awesome 6 Free'; }
|
--fa-font-solid: normal 900 1em/1 'Font Awesome 6 Free';
|
||||||
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Font Awesome 6 Free';
|
font-family: 'Font Awesome 6 Free';
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
font-display: block;
|
font-display: block;
|
||||||
src: url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.ttf") format("truetype"); }
|
src: url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.ttf") format("truetype");
|
||||||
|
}
|
||||||
|
|
||||||
.fas,
|
.fas,
|
||||||
.fa-solid {
|
.fa-solid {
|
||||||
font-weight: 900; }
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com
|
||||||
|
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||||
|
* Copyright 2023 Fonticons, Inc.
|
||||||
|
*/
|
@@ -2,12 +2,15 @@
|
|||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
|
<base href="/">
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, viewport-fit=cover, initial-scale=1, maximum-scale=1.0, user-scalable=no">
|
<meta name="viewport" content="width=device-width, viewport-fit=cover, initial-scale=1, maximum-scale=1.0, user-scalable=no">
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="darkreader-lock">
|
||||||
<link href="webfonts/NotoSans/stylesheet.css" rel="stylesheet">
|
<link href="webfonts/NotoSans/stylesheet.css" rel="stylesheet">
|
||||||
<!-- fontawesome webfonts-->
|
<!-- fontawesome webfonts-->
|
||||||
<link href="css/all.css" rel="stylesheet">
|
<link href="css/fontawesome.css" rel="stylesheet">
|
||||||
|
<link href="css/solid.css" rel="stylesheet">
|
||||||
|
|
||||||
<link rel="apple-touch-icon" sizes="57x57" href="img/apple-icon-57x57.png" />
|
<link rel="apple-touch-icon" sizes="57x57" href="img/apple-icon-57x57.png" />
|
||||||
<link rel="apple-touch-icon" sizes="72x72" href="img/apple-icon-72x72.png" />
|
<link rel="apple-touch-icon" sizes="72x72" href="img/apple-icon-72x72.png" />
|
||||||
@@ -112,6 +115,7 @@
|
|||||||
<option value="gpt-3.5-turbo">gpt-3.5-turbo</option>
|
<option value="gpt-3.5-turbo">gpt-3.5-turbo</option>
|
||||||
<option value="gpt-3.5-turbo-0301">gpt-3.5-turbo-0301</option>
|
<option value="gpt-3.5-turbo-0301">gpt-3.5-turbo-0301</option>
|
||||||
<option value="gpt-4">gpt-4</option>
|
<option value="gpt-4">gpt-4</option>
|
||||||
|
<option value="gpt-4-32k">gpt-4-32k</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1274,6 +1278,9 @@
|
|||||||
<label for="auto-load-chat-checkbox"><input id="auto-load-chat-checkbox" type="checkbox" />
|
<label for="auto-load-chat-checkbox"><input id="auto-load-chat-checkbox" type="checkbox" />
|
||||||
Auto-load Last Chat
|
Auto-load Last Chat
|
||||||
</label>
|
</label>
|
||||||
|
<label for="auto_save_msg_edits"><input id="auto_save_msg_edits" type="checkbox" />
|
||||||
|
Auto-save Message Edits
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1310,6 +1317,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="drawer-content closedDrawer">
|
<div class="drawer-content closedDrawer">
|
||||||
<div class="flex-container">
|
<div class="flex-container">
|
||||||
|
<input id="bg-filter" placeholder="Filter" class="text_pole" type="search" />
|
||||||
<div id="bg_menu_content">
|
<div id="bg_menu_content">
|
||||||
<form id="form_bg_download" class="bg_example no-border no-shadow" action="javascript:void(null);" method="post" enctype="multipart/form-data">
|
<form id="form_bg_download" class="bg_example no-border no-shadow" action="javascript:void(null);" method="post" enctype="multipart/form-data">
|
||||||
<label class="input-file">
|
<label class="input-file">
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>TavernAI - Note - Character Derscriptions</title>
|
<title>Character Descriptions</title>
|
||||||
<link rel="stylesheet" href="/css/notes.css">
|
<link rel="stylesheet" href="/css/notes.css">
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
<p>
|
<p>
|
||||||
For most Kobold's models the easiest way is to use a free form for description, and in each sentence it is desirable to specify the name of the character.<br><br>
|
For most Kobold's models the easiest way is to use a free form for description, and in each sentence it is desirable to specify the name of the character.<br><br>
|
||||||
The entire description should be in one line without hyphenation.<br><br>
|
The entire description should be in one line without hyphenation.<br><br>
|
||||||
For examle:<br><br>
|
For example:<br><br>
|
||||||
<code>
|
<code>
|
||||||
Chloe is a female elf. Chloe wears black-white maid dress with green collar and red glasses. Chloe has medium length black hair. Chloe's personality is...
|
Chloe is a female elf. Chloe wears black-white maid dress with green collar and red glasses. Chloe has medium length black hair. Chloe's personality is...
|
||||||
</code>
|
</code>
|
||||||
@@ -33,11 +33,10 @@
|
|||||||
Details here: <a target="_blank" href="https://github.com/KoboldAI/KoboldAI-Client/wiki/Pro-Tips">Pro-Tips</a>
|
Details here: <a target="_blank" href="https://github.com/KoboldAI/KoboldAI-Client/wiki/Pro-Tips">Pro-Tips</a>
|
||||||
</p>
|
</p>
|
||||||
<hr>
|
<hr>
|
||||||
<br>
|
|
||||||
<p>
|
<p>
|
||||||
<u>A list of tags that are replaced when sending to generate:</u><br><br>
|
<u>A list of tags that are replaced when sending to generate:</u><br><br>
|
||||||
{{user}} and <USER> : replaced by the User's Name<br>
|
{{user}} and <USER> are replaced by the User's Name<br>
|
||||||
{{char}} and <BOT> : replaced by the Character's Name
|
{{char}} and <BOT> are replaced by the Character's Name
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>TavernAI - Note - Import Chat</title>
|
<title>Import Chat</title>
|
||||||
<link rel="stylesheet" href="/css/notes.css">
|
<link rel="stylesheet" href="/css/notes.css">
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
@@ -12,8 +12,8 @@
|
|||||||
<div id="main">
|
<div id="main">
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<h2>Chat import</h2>
|
<h2>Chat import</h2>
|
||||||
<h3>Import chats into TavernAI</h3>
|
<h3>Import chats into SillyTavern</h3>
|
||||||
<p>For import Character.ai chats use tool: <a href="https://github.com/0x000011b/characterai-dumper">https://github.com/0x000011b/characterai-dumper</a></p>
|
<p>To import Character.AI chats, use this tool: <a href="https://github.com/0x000011b/characterai-dumper">https://github.com/0x000011b/characterai-dumper</a>.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>TavernAI - Note - Example Dialogues</title>
|
<title>Example Dialogues</title>
|
||||||
<link rel="stylesheet" href="/css/notes.css">
|
<link rel="stylesheet" href="/css/notes.css">
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
@@ -21,10 +21,12 @@
|
|||||||
<br><br><START><br>
|
<br><br><START><br>
|
||||||
{{user}}: Hello<br>
|
{{user}}: Hello<br>
|
||||||
{{char}}: *excitedly* Hello there, dear! Are you new to Axel? Don't worry, I, Aqua the goddess of water, am here to help you! Do you need any assistance? And may I say, I look simply radiant today! *strikes a pose and looks at you with puppy eyes*</p>
|
{{char}}: *excitedly* Hello there, dear! Are you new to Axel? Don't worry, I, Aqua the goddess of water, am here to help you! Do you need any assistance? And may I say, I look simply radiant today! *strikes a pose and looks at you with puppy eyes*</p>
|
||||||
<hr><br>A list of tags that are replaced when sending to generate:<br><br>
|
<hr>
|
||||||
{{user}} and <USER> are replaced by User Name<br>
|
<p>
|
||||||
{{char}} and <BOT> are replaced by Character Name<br><br>
|
<u>A list of tags that are replaced when sending to generate:</u><br><br>
|
||||||
*for Pygmalion "{{user}}:" and "<USER>:" will be replaced by "You:"
|
{{user}} and <USER> are replaced by the User's Name<br>
|
||||||
|
{{char}} and <BOT> are replaced by the Character's Name
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>TavernAI - Note - Scenario</title>
|
<title>Scenario</title>
|
||||||
<link rel="stylesheet" href="/css/notes.css">
|
<link rel="stylesheet" href="/css/notes.css">
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
@@ -18,8 +18,8 @@
|
|||||||
<hr>
|
<hr>
|
||||||
<p>
|
<p>
|
||||||
<u>A list of tags that are replaced when sending to generate:</u><br><br>
|
<u>A list of tags that are replaced when sending to generate:</u><br><br>
|
||||||
{{user}} and <USER> : replaced by User Name<br>
|
{{user}} and <USER> are replaced by the User's Name<br>
|
||||||
{{char}} and <BOT> : replaced by Character Name<br><br>
|
{{char}} and <BOT> are replaced by the Character's Name
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -14,7 +14,7 @@
|
|||||||
<h2>World Info</h2>
|
<h2>World Info</h2>
|
||||||
<h4>World Info enhances AI's understanding of the details in your world.</h4>
|
<h4>World Info enhances AI's understanding of the details in your world.</h4>
|
||||||
<p>It functions like a dynamic dictionary that only inserts relevant information from World Info entries when keywords associated with the entries are present in the message text.</p>
|
<p>It functions like a dynamic dictionary that only inserts relevant information from World Info entries when keywords associated with the entries are present in the message text.</p>
|
||||||
<p>The TavernAI engine activates and seamlessly integrates the appropriate lore into the prompt, providing background information to the AI.</p>
|
<p>SillyTavern activates and seamlessly integrates the appropriate lore into the prompt, providing background information to the AI.</p>
|
||||||
<p><i>It is important to note that while World Info helps guide the AI towards your desired lore, it does not guarantee its appearance in the generated output messages.</i></p>
|
<p><i>It is important to note that while World Info helps guide the AI towards your desired lore, it does not guarantee its appearance in the generated output messages.</i></p>
|
||||||
|
|
||||||
<h3>Pro Tips</h3>
|
<h3>Pro Tips</h3>
|
||||||
|
@@ -15,7 +15,7 @@
|
|||||||
<div id="content">
|
<div id="content">
|
||||||
<h2>Scan Depth</h2>
|
<h2>Scan Depth</h2>
|
||||||
<h4>Defines how many messages in the chat history should be scanned for World Info keys.</h4>
|
<h4>Defines how many messages in the chat history should be scanned for World Info keys.</h4>
|
||||||
<p>If set to 1, then TavernAI only scans the message you send and the most recent reply.</p>
|
<p>If set to 1, then SillyTavern only scans the message you send and the most recent reply.</p>
|
||||||
<p>This stacks up to 10 message pairs it total.</p>
|
<p>This stacks up to 10 message pairs it total.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -64,7 +64,7 @@
|
|||||||
Comment
|
Comment
|
||||||
</h3>
|
</h3>
|
||||||
<p>
|
<p>
|
||||||
A supplemental text comment for the your convenience, which is not utilized by the AI.
|
A supplemental text comment for your convenience, which is not utilized by the AI.
|
||||||
</p>
|
</p>
|
||||||
<h3>
|
<h3>
|
||||||
Constant
|
Constant
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>TavernAI - Note - Personality Summary</title>
|
<title>Personality Summary</title>
|
||||||
<link rel="stylesheet" href="/css/notes.css">
|
<link rel="stylesheet" href="/css/notes.css">
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<div id="content">
|
<div id="content">
|
||||||
<h2>Personality summary</h2>
|
<h2>Personality summary</h2>
|
||||||
<p>
|
<p>
|
||||||
A brief description of the personality. It is added to the chat to a depth of 8-15 messages, so it has a significant impact on the character.
|
A brief description of the personality. It is added to the chat at a depth of 8-15 messages, so it has a significant impact on the character.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
@@ -26,13 +26,11 @@
|
|||||||
|
|
||||||
<p>*In Pygmalion model, it is used as a "Personality:" graph</p>
|
<p>*In Pygmalion model, it is used as a "Personality:" graph</p>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<u>List of tags that are replaced when sending to generate:</u><br><br>
|
<u>A list of tags that are replaced when sending to generate:</u><br><br>
|
||||||
{{user}} and <USER> : replaced by the User's Name<br>
|
{{user}} and <USER> are replaced by the User's Name<br>
|
||||||
{{char}} and <BOT> : replaced by the Character's Name<br><br>
|
{{char}} and <BOT> are replaced by the Character's Name
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>TavernAI - Note - First Message</title>
|
<title>First Message</title>
|
||||||
<link rel="stylesheet" href="/css/notes.css">
|
<link rel="stylesheet" href="/css/notes.css">
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
@@ -21,15 +21,15 @@
|
|||||||
For example:
|
For example:
|
||||||
<br><br>
|
<br><br>
|
||||||
<code>
|
<code>
|
||||||
*I noticed you came inside, I walked up and stood right in front of you* Wellcome. I'm glad to see you here.
|
*I noticed you came inside, I walked up and stood right in front of you* Welcome. I'm glad to see you here.
|
||||||
*i said with toothy smug sunny smile looking you straight in the eye* What brings you...
|
*I said with toothy smug sunny smile looking you straight in the eye* What brings you...
|
||||||
</code>
|
</code>
|
||||||
<Br>
|
<Br>
|
||||||
<hr>
|
<hr>
|
||||||
<p>
|
<p>
|
||||||
A list of tags that are replaced when sending to generate:<br><br>
|
<u>A list of tags that are replaced when sending to generate:</u><br><br>
|
||||||
{{user}} and <USER> are replaced by User Name<br>
|
{{user}} and <USER> are replaced by the User's Name<br>
|
||||||
{{char}} and <BOT> are replaced by Character Name<br><br>
|
{{char}} and <BOT> are replaced by the Character's Name
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>TavernAI - Note - KobolAI Settings</title>
|
<title>KoboldAI Settings</title>
|
||||||
<link rel="stylesheet" href="/css/notes.css">
|
<link rel="stylesheet" href="/css/notes.css">
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
<div id="content">
|
<div id="content">
|
||||||
<h2>KoboldAI Settings</h2>
|
<h2>KoboldAI Settings</h2>
|
||||||
<p>Standard KoboldAI settings files are used here. To add your own settings, simply add the file .settings
|
<p>Standard KoboldAI settings files are used here. To add your own settings, simply add the file .settings
|
||||||
in TavernAI\public\KoboldAI Settings
|
in SillyTavern\public\KoboldAI Settings
|
||||||
</p>
|
</p>
|
||||||
<h3>Temperature</h3>
|
<h3>Temperature</h3>
|
||||||
<p>Value from 0.1 to 2.0. Lower value - the answers are more logical, but less creative. Higher value - the
|
<p>Value from 0.1 to 2.0. Lower value - the answers are more logical, but less creative. Higher value - the
|
||||||
@@ -30,11 +30,11 @@
|
|||||||
<h3>Repetition penalty range</h3>
|
<h3>Repetition penalty range</h3>
|
||||||
<p>The range of influence of Repetition penalty in tokens.</p>
|
<p>The range of influence of Repetition penalty in tokens.</p>
|
||||||
<h3>Amount generation</h3>
|
<h3>Amount generation</h3>
|
||||||
<p>The maximum amount of tokens that a AI will generate to respond. One word is approximately 3-4 tokens.
|
<p>The maximum amount of tokens that the AI will generate to respond. One word is approximately 3-4 tokens.
|
||||||
The larger the parameter value, the longer the generation time takes.</p>
|
The larger the parameter value, the longer the generation time takes.</p>
|
||||||
<h3>Context size</h3>
|
<h3>Context size</h3>
|
||||||
<p>How much will the AI remember. Context size also affects the speed of generation.<br><br>
|
<p>How much will the AI remember. Context size also affects the speed of generation.<br><br>
|
||||||
<u>Important</u>: The setting of Context Size in TavernAI GUI override setting for KoboldAI GUI
|
<u>Important</u>: The setting of Context Size in SillyTavern GUI overrides the setting for KoboldAI GUI
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2>Advanced Settings</h2>
|
<h2>Advanced Settings</h2>
|
||||||
@@ -51,8 +51,8 @@
|
|||||||
<h3>Top P Sampling</h3>
|
<h3>Top P Sampling</h3>
|
||||||
<p>
|
<p>
|
||||||
This setting controls how much of the text generated is based on the most likely options.
|
This setting controls how much of the text generated is based on the most likely options.
|
||||||
The top P words with the highest probabilities are considered. A word is then chosen at random, with a
|
Only words with the highest probabilities, together summing up to P, are considered. A word is then
|
||||||
higher chance of selecting words with higher probabilities.
|
chosen at random, with a higher chance of selecting words with higher probabilities.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Set value to 1 to disable its effect.
|
Set value to 1 to disable its effect.
|
||||||
|
@@ -1,23 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>TavernAI - Note - Temperature</title>
|
|
||||||
<link rel="stylesheet" href="/css/notes.css">
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap" rel="stylesheet">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="main">
|
|
||||||
<div id="content">
|
|
||||||
<h2>Temperature</h2>
|
|
||||||
<p>
|
|
||||||
Value from 0.1 to 2.0.<br><br>
|
|
||||||
Less value - the answers are more logical, but less creative.<Br><br>
|
|
||||||
More value - the answers are more creative, but less logical.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@@ -1,6 +1,6 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>TavernAI - Note - Novel AI API Key</title>
|
<title>NovelAI API Key</title>
|
||||||
<link rel="stylesheet" href="/css/notes.css">
|
<link rel="stylesheet" href="/css/notes.css">
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>TavernAI - Note - NovelAI Settings</title>
|
<title>NovelAI Settings</title>
|
||||||
<link rel="stylesheet" href="/css/notes.css">
|
<link rel="stylesheet" href="/css/notes.css">
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<div id="content">
|
<div id="content">
|
||||||
<h2>NovelAI settings</h2>
|
<h2>NovelAI settings</h2>
|
||||||
<p>
|
<p>
|
||||||
The files with the settings are here (TavernAI\public\NovelAI Settings).<br>
|
The files with the settings are here (SillyTavern\public\NovelAI Settings).<br>
|
||||||
You can also manually add your own settings files.
|
You can also manually add your own settings files.
|
||||||
</p>
|
</p>
|
||||||
<h3>Temperature</h3>
|
<h3>Temperature</h3>
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>TavernAI - Note - NovelAI Models</title>
|
<title>NovelAI Models</title>
|
||||||
<link rel="stylesheet" href="/css/notes.css">
|
<link rel="stylesheet" href="/css/notes.css">
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
<div id="main">
|
<div id="main">
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<h2>NovelAI Models</h2>
|
<h2>NovelAI Models</h2>
|
||||||
<p>If your subscribe tier is Paper, Tablet or Scroll use only Euterpe model otherwise you can not get an answer from NovelAI api.</p>
|
<p>If your subscription tier is Paper, Tablet or Scroll use only Euterpe model otherwise you can not get an answer from NovelAI API.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>TavernAI - Note - Anchors</title>
|
<title>Anchors</title>
|
||||||
<link rel="stylesheet" href="/css/notes.css">
|
<link rel="stylesheet" href="/css/notes.css">
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
There are two types of anchors: <u>Character Anchor</u> and <u>Style Anchor</u>
|
There are two types of anchors: <u>Character Anchor</u> and <u>Style Anchor</u>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<u>Character Anchor</u> - affects the character played by the AI by motivating him to write longer messages.<br><br>
|
<u>Character Anchor</u> - affects the character played by the AI by motivating it to write longer messages.<br><br>
|
||||||
Looks like:
|
Looks like:
|
||||||
<code>[Elaborate speaker]</code>
|
<code>[Elaborate speaker]</code>
|
||||||
</p>
|
</p>
|
||||||
@@ -31,10 +31,10 @@
|
|||||||
Anchors Order sets the location of anchors in the promt, the first anchor in the order is much further back in the context and thus has less influence than second.
|
Anchors Order sets the location of anchors in the promt, the first anchor in the order is much further back in the context and thus has less influence than second.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
The second anchor is only turned on after 8-12 messages, because when the chat still only has a few message the first anchor creates enough effect ob its own.
|
The second anchor is only turned on after 8-12 messages, because when the chat still only has a few messages, the first anchor creates enough effect on its own.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Sometimes an AI model may not perceive anchors correctly or the AI model already generates sufficiently long messages.<br>
|
Sometimes an AI model may not perceive anchors correctly or the AI model already generates sufficiently long messages.
|
||||||
For these cases, you can disable the anchors by unchecking their respective boxes.
|
For these cases, you can disable the anchors by unchecking their respective boxes.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
|
@@ -17,8 +17,8 @@
|
|||||||
<div id="content">
|
<div id="content">
|
||||||
<h2>Advanced Formatting</h2>
|
<h2>Advanced Formatting</h2>
|
||||||
<p>
|
<p>
|
||||||
The settings provided in this section allow for a more control over the prompt building strategy.
|
The settings provided in this section allow for more control over the prompt building strategy.
|
||||||
Most specifics of the prompt building depend on whether a Pygmalion model is selected or special formatting is force enabled.
|
Most specifics of the prompt building depend on whether a Pygmalion model is selected or special formatting is force-enabled.
|
||||||
The core differences between the formatting schemas are listed below.
|
The core differences between the formatting schemas are listed below.
|
||||||
</p>
|
</p>
|
||||||
<h3>Custom Chat Separator</h3>
|
<h3>Custom Chat Separator</h3>
|
||||||
@@ -28,24 +28,24 @@
|
|||||||
<h3>For <u>Pygmalion</u> formatting</h3>
|
<h3>For <u>Pygmalion</u> formatting</h3>
|
||||||
<h4>Disable description formatting</h4>
|
<h4>Disable description formatting</h4>
|
||||||
<p>
|
<p>
|
||||||
<code><b>NAME's Persona: </b></code> won't be prepended to the content your character's Description box.
|
<code><b>NAME's Persona: </b></code> won't be prepended to the content of your character's Description box.
|
||||||
</p>
|
</p>
|
||||||
<h4>Disable scenario formatting</h4>
|
<h4>Disable scenario formatting</h4>
|
||||||
<p>
|
<p>
|
||||||
<code><b>Scenario: </b></code> won't be prepended to the content your character's Scenario box.
|
<code><b>Scenario: </b></code> won't be prepended to the content of your character's Scenario box.
|
||||||
</p>
|
</p>
|
||||||
<h4>Disable personality formatting</h4>
|
<h4>Disable personality formatting</h4>
|
||||||
<p>
|
<p>
|
||||||
<code><b>Personality: </b></code> won't be prepended to the content your character's Personality box.
|
<code><b>Personality: </b></code> won't be prepended to the content of your character's Personality box.
|
||||||
</p>
|
</p>
|
||||||
<h4>Disable example chats formatting</h4>
|
<h4>Disable example chats formatting</h4>
|
||||||
<p>
|
<p>
|
||||||
<code><START></code> is not added at the beginning of each example message block.<br>
|
<code><START></code> won't be added at the beginning of each example message block.<br>
|
||||||
<i>(If custom separator is not set)</i>
|
<i>(If custom separator is not set)</i>
|
||||||
</p>
|
</p>
|
||||||
<h4>Disable chat start formatting</h4>
|
<h4>Disable chat start formatting</h4>
|
||||||
<p>
|
<p>
|
||||||
<code><START></code> is not added before the between the character card and the chat log.<br>
|
<code><START></code> won't be added between the character card and the chat log.<br>
|
||||||
<i>(If custom separator is not set)</i>
|
<i>(If custom separator is not set)</i>
|
||||||
</p>
|
</p>
|
||||||
<h4>Always add character's name to prompt</h4>
|
<h4>Always add character's name to prompt</h4>
|
||||||
@@ -59,25 +59,25 @@
|
|||||||
</p>
|
</p>
|
||||||
<h4>Disable scenario formatting</h4>
|
<h4>Disable scenario formatting</h4>
|
||||||
<p>
|
<p>
|
||||||
<code><b>Circumstances and context of the dialogue: </b></code> won't be prepended to the content your character's Scenario box.
|
<code><b>Circumstances and context of the dialogue: </b></code> won't be prepended to the content of your character's Scenario box.
|
||||||
</p>
|
</p>
|
||||||
<h4>Disable personality formatting</h4>
|
<h4>Disable personality formatting</h4>
|
||||||
<p>
|
<p>
|
||||||
<code><b>NAME's personality: </b></code> won't be prepended to the content your character's Personality box.
|
<code><b>NAME's personality: </b></code> won't be prepended to the content of your character's Personality box.
|
||||||
</p>
|
</p>
|
||||||
<h4>Disable example chats formatting</h4>
|
<h4>Disable example chats formatting</h4>
|
||||||
<p>
|
<p>
|
||||||
<code>This is how <b>Character</b> should talk</code> is not added at the beginning of each example message block.<br>
|
<code>This is how <b>Character</b> should talk</code> won't be added at the beginning of each example message block.<br>
|
||||||
<i>(If custom separator is not set)</i>
|
<i>(If custom separator is not set)</i>
|
||||||
</p>
|
</p>
|
||||||
<h4>Disable chat start formatting</h4>
|
<h4>Disable chat start formatting</h4>
|
||||||
<p>
|
<p>
|
||||||
<code>Then the roleplay chat between <b>User</b> and <b>Character</b> begins</code> is not added before the between the character card and the chat log.<br>
|
<code>Then the roleplay chat between <b>User</b> and <b>Character</b> begins</code> won't be added between the character card and the chat log.<br>
|
||||||
<i>(If custom separator is not set)</i>
|
<i>(If custom separator is not set)</i>
|
||||||
</p>
|
</p>
|
||||||
<h4>Always add character's name to prompt</h4>
|
<h4>Always add character's name to prompt</h4>
|
||||||
<p>
|
<p>
|
||||||
Appends character's name to the prompt to force model to complete the message as a character:
|
Appends character's name to the prompt to force the model to complete the message as the character:
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<code>
|
<code>
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>Advanced Formatting</title>
|
<title>Group reply order strategies</title>
|
||||||
<link rel="stylesheet" href="/css/notes.css">
|
<link rel="stylesheet" href="/css/notes.css">
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
@@ -16,7 +16,7 @@
|
|||||||
<div id="main">
|
<div id="main">
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<h2>Message Sound</h2>
|
<h2>Message Sound</h2>
|
||||||
<p>To play your own custom sound on receiving a new message from bot, replace the following MP3 file in your TavernAI folder:</p>
|
<p>To play your own custom sound on receiving a new message from bot, replace the following MP3 file in your SillyTavern folder:</p>
|
||||||
<code>
|
<code>
|
||||||
public/sounds/message.mp3
|
public/sounds/message.mp3
|
||||||
</code>
|
</code>
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
Plays at 80% volume.
|
Plays at 80% volume.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
If "Background Sound Only" option is enabled, the sound plays only if TavernAI window is <b>unfocused</b>.
|
If "Background Sound Only" option is enabled, the sound plays only if SillyTavern window is <b>unfocused</b>.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -16,7 +16,7 @@
|
|||||||
<div id="main">
|
<div id="main">
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<h2>Multigen</h2>
|
<h2>Multigen</h2>
|
||||||
<p>TavernAI tries to create faster and longer responses by chaining the generation using smaller batches.</p>
|
<p>SillyTavern tries to create faster and longer responses by chaining the generation using smaller batches.</p>
|
||||||
<h3>Default settings:</h3>
|
<h3>Default settings:</h3>
|
||||||
<p>First batch = 50 tokens</p>
|
<p>First batch = 50 tokens</p>
|
||||||
<p>Next batches = 30 tokens</p>
|
<p>Next batches = 30 tokens</p>
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>Advanced Settings</title>
|
<title>OpenAI API key</title>
|
||||||
<link rel="stylesheet" href="/css/notes.css">
|
<link rel="stylesheet" href="/css/notes.css">
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
@@ -15,6 +15,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<div id="main">
|
<div id="main">
|
||||||
<div id="content">
|
<div id="content">
|
||||||
|
<h2>Gradio Streaming Function ID</h2>
|
||||||
<p>
|
<p>
|
||||||
To use streaming with Text Generation Web UI, a Gradio function index needs to be provided.
|
To use streaming with Text Generation Web UI, a Gradio function index needs to be provided.
|
||||||
It is impossible to be determined programmatically and should be typed in manually.
|
It is impossible to be determined programmatically and should be typed in manually.
|
||||||
|
@@ -15,7 +15,7 @@
|
|||||||
<div id="content">
|
<div id="content">
|
||||||
<h2>Character Tokens</h2>
|
<h2>Character Tokens</h2>
|
||||||
|
|
||||||
<p><b>TLDR: If you're working with an AI model with a 2048 context token limit, your 1000 token character definition is cutting the AI's 'memory' in half.</b></p>
|
<p><b>TL;DR: If you're working with an AI model with a 2048 context token limit, your 1000 token character definition is cutting the AI's 'memory' in half.</b></p>
|
||||||
<p>To put this in perspective, a decent response from a good AI can easily be around 200-300 tokens. In this case, the AI would only be able to 'remember' about 3 exchanges worth of chat history.</p>
|
<p>To put this in perspective, a decent response from a good AI can easily be around 200-300 tokens. In this case, the AI would only be able to 'remember' about 3 exchanges worth of chat history.</p>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
<p>When we see your character has over 1000 tokens in its definitions, we highlight it for you because this can lower the AI's capabilities to provide an enjoyable conversation.</p>
|
<p>When we see your character has over 1000 tokens in its definitions, we highlight it for you because this can lower the AI's capabilities to provide an enjoyable conversation.</p>
|
||||||
|
|
||||||
<h3>What happens if my Character has too many tokens?</h3>
|
<h3>What happens if my Character has too many tokens?</h3>
|
||||||
<p>Don't Worry - it won't break anything. At worst, if the Character's permanent tokens are too large, it simply means there will be less room left in the context for other things (see below).</p>
|
<p>Don't worry - it won't break anything. At worst, if the Character's permanent tokens are too large, it simply means there will be less room left in the context for other things (see below).</p>
|
||||||
<p>The only negative side effect this can have is the AI will have less 'memory', as it will have less chat history available to process.</p>
|
<p>The only negative side effect this can have is the AI will have less 'memory', as it will have less chat history available to process.</p>
|
||||||
<p>This is because every AI model has a limit to the amount of context it can process at one time.</p>
|
<p>This is because every AI model has a limit to the amount of context it can process at one time.</p>
|
||||||
<h3>'Context'?</h3>
|
<h3>'Context'?</h3>
|
||||||
|
226
public/script.js
226
public/script.js
@@ -38,6 +38,7 @@ import {
|
|||||||
resetSelectedGroup,
|
resetSelectedGroup,
|
||||||
select_group_chats,
|
select_group_chats,
|
||||||
regenerateGroup,
|
regenerateGroup,
|
||||||
|
group_generation_id,
|
||||||
} from "./scripts/group-chats.js";
|
} from "./scripts/group-chats.js";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -148,6 +149,7 @@ export {
|
|||||||
count_view_mes,
|
count_view_mes,
|
||||||
max_context,
|
max_context,
|
||||||
chat_metadata,
|
chat_metadata,
|
||||||
|
streamingProcessor,
|
||||||
default_avatar,
|
default_avatar,
|
||||||
system_message_types,
|
system_message_types,
|
||||||
talkativeness_default,
|
talkativeness_default,
|
||||||
@@ -156,15 +158,15 @@ export {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// API OBJECT FOR EXTERNAL WIRING
|
// API OBJECT FOR EXTERNAL WIRING
|
||||||
window["TavernAI"] = {};
|
window["SillyTavern"] = {};
|
||||||
|
|
||||||
let converter = new showdown.Converter({ emoji: "true" });
|
let converter = new showdown.Converter({ emoji: "true" });
|
||||||
const gpt3 = new GPT3BrowserTokenizer({ type: 'gpt3' });
|
const gpt3 = new GPT3BrowserTokenizer({ type: 'gpt3' });
|
||||||
/* let bg_menu_toggle = false; */
|
/* let bg_menu_toggle = false; */
|
||||||
const systemUserName = "TavernAI";
|
const systemUserName = "SillyTavern";
|
||||||
let default_user_name = "You";
|
let default_user_name = "You";
|
||||||
let name1 = default_user_name;
|
let name1 = default_user_name;
|
||||||
let name2 = "TavernAI";
|
let name2 = "SillyTavern";
|
||||||
let chat = [];
|
let chat = [];
|
||||||
let safetychat = [
|
let safetychat = [
|
||||||
{
|
{
|
||||||
@@ -201,10 +203,12 @@ let dialogueResolve = null;
|
|||||||
let chat_metadata = {};
|
let chat_metadata = {};
|
||||||
let streamingProcessor = null;
|
let streamingProcessor = null;
|
||||||
|
|
||||||
|
|
||||||
const durationSaveEdit = 200;
|
const durationSaveEdit = 200;
|
||||||
const saveSettingsDebounced = debounce(() => saveSettings(), durationSaveEdit);
|
const saveSettingsDebounced = debounce(() => saveSettings(), durationSaveEdit);
|
||||||
const saveCharacterDebounced = debounce(() => $("#create_button").click(), durationSaveEdit);
|
const saveCharacterDebounced = debounce(() => $("#create_button").click(), durationSaveEdit);
|
||||||
const getStatusDebounced = debounce(() => getStatus(), 90000);
|
const getStatusDebounced = debounce(() => getStatus(), 90000);
|
||||||
|
const saveChatDebounced = debounce(() => saveChatConditional(), 1000);
|
||||||
|
|
||||||
const system_message_types = {
|
const system_message_types = {
|
||||||
HELP: "help",
|
HELP: "help",
|
||||||
@@ -244,7 +248,7 @@ const system_messages = {
|
|||||||
is_user: false,
|
is_user: false,
|
||||||
is_name: true,
|
is_name: true,
|
||||||
mes: [
|
mes: [
|
||||||
'Welcome to TavernAI! In order to begin chatting:',
|
'Welcome to SillyTavern! In order to begin chatting:',
|
||||||
'<ul>',
|
'<ul>',
|
||||||
'<li>Connect to one of the supported generation APIs</li>',
|
'<li>Connect to one of the supported generation APIs</li>',
|
||||||
'<li>Create or pick a character from the list</li>',
|
'<li>Create or pick a character from the list</li>',
|
||||||
@@ -334,9 +338,8 @@ var create_save_mes_example = "";
|
|||||||
var create_save_talkativeness = talkativeness_default;
|
var create_save_talkativeness = talkativeness_default;
|
||||||
|
|
||||||
//animation right menu
|
//animation right menu
|
||||||
var animation_rm_duration = 500;
|
var animation_duration = 250;
|
||||||
var animation_rm_easing = "";
|
var animation_easing = "ease-in-out";
|
||||||
|
|
||||||
var popup_type = "";
|
var popup_type = "";
|
||||||
var bg_file_for_del = "";
|
var bg_file_for_del = "";
|
||||||
var chat_file_for_del = "";
|
var chat_file_for_del = "";
|
||||||
@@ -877,7 +880,7 @@ function messageFormating(mes, ch_name, isSystem, forceAvatar) {
|
|||||||
mes = mes.replace(/\n/g, "<br/>");
|
mes = mes.replace(/\n/g, "<br/>");
|
||||||
mes = mes.trim();
|
mes = mes.trim();
|
||||||
|
|
||||||
mes = mes.replace(/<code>[\s\S]*?<\/code>/g, function (match) {
|
mes = mes.replace(/<code(.*)>[\s\S]*?<\/code>/g, function (match) {
|
||||||
return match.replace(/&/g, '&');
|
return match.replace(/&/g, '&');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1021,11 +1024,11 @@ function substituteParams(content, _name1, _name2) {
|
|||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStoppingStrings(isImpersonate, wrapInQuotes) {
|
function getStoppingStrings(isImpersonate, addSpace) {
|
||||||
const charString = `\n${name2}: `;
|
const charString = `\n${name2}:`;
|
||||||
const userString = is_pygmalion ? `\nYou: ` : `\n${name1}: `;
|
const userString = is_pygmalion ? `\nYou:` : `\n${name1}:`;
|
||||||
const result = isImpersonate ? charString : userString;
|
const result = isImpersonate ? charString : userString;
|
||||||
return wrapInQuotes ? `"${result}"` : result;
|
return [addSpace ? `${result} ` : result];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSlashCommand(message, type) {
|
function getSlashCommand(message, type) {
|
||||||
@@ -1228,6 +1231,7 @@ class StreamingProcessor {
|
|||||||
let formattedText = messageFormating(processedText, chat[messageId].name, chat[messageId].is_system, chat[messageId].force_avatar);
|
let formattedText = messageFormating(processedText, chat[messageId].name, chat[messageId].is_system, chat[messageId].force_avatar);
|
||||||
const mesText = $(`#chat .mes[mesid="${messageId}"] .mes_text`);
|
const mesText = $(`#chat .mes[mesid="${messageId}"] .mes_text`);
|
||||||
mesText.html(formattedText);
|
mesText.html(formattedText);
|
||||||
|
this.setFirstSwipe(messageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollChatToBottom();
|
scrollChatToBottom();
|
||||||
@@ -1254,6 +1258,14 @@ class StreamingProcessor {
|
|||||||
showSwipeButtons();
|
showSwipeButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setFirstSwipe(messageId) {
|
||||||
|
if (this.type !== 'swipe' && this.type !== 'impersonate') {
|
||||||
|
if (Array.isArray(chat[messageId]['swipes']) && chat[messageId]['swipes'].length === 1 && chat[messageId]['swipe_id'] === 0) {
|
||||||
|
chat[messageId]['swipes'][0] = chat[messageId]['mes'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onStopStreaming() {
|
onStopStreaming() {
|
||||||
this.onErrorStreaming();
|
this.onErrorStreaming();
|
||||||
}
|
}
|
||||||
@@ -1270,11 +1282,13 @@ class StreamingProcessor {
|
|||||||
this.isStopped = false;
|
this.isStopped = false;
|
||||||
this.isFinished = false;
|
this.isFinished = false;
|
||||||
this.generator = this.nullStreamingGeneration;
|
this.generator = this.nullStreamingGeneration;
|
||||||
|
this.abortController = new AbortController();
|
||||||
|
this.firstMessageText = '...';
|
||||||
}
|
}
|
||||||
|
|
||||||
async generate() {
|
async generate() {
|
||||||
if (this.messageId == -1) {
|
if (this.messageId == -1) {
|
||||||
this.messageId = this.onStartStreaming('...');
|
this.messageId = this.onStartStreaming(this.firstMessageText);
|
||||||
await delay(1); // delay for message to be rendered
|
await delay(1); // delay for message to be rendered
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1323,12 +1337,13 @@ async function Generate(type, automatic_trigger, force_name2) {
|
|||||||
|
|
||||||
if (isStreamingEnabled()) {
|
if (isStreamingEnabled()) {
|
||||||
streamingProcessor = new StreamingProcessor(type, force_name2);
|
streamingProcessor = new StreamingProcessor(type, force_name2);
|
||||||
|
hideSwipeButtons();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
streamingProcessor = false;
|
streamingProcessor = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selected_group && !is_group_generating && !isImpersonate) {
|
if (selected_group && !is_group_generating) {
|
||||||
generateGroupWrapper(false, type = type);
|
generateGroupWrapper(false, type = type);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1343,7 +1358,7 @@ async function Generate(type, automatic_trigger, force_name2) {
|
|||||||
} else {
|
} else {
|
||||||
//console.log('Regenerate call detected')
|
//console.log('Regenerate call detected')
|
||||||
var textareaText = "";
|
var textareaText = "";
|
||||||
if (chat[chat.length - 1]['is_user']) {//If last message from You
|
if (chat.length && chat[chat.length - 1]['is_user']) {//If last message from You
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (type !== "swipe" && !isImpersonate) {
|
else if (type !== "swipe" && !isImpersonate) {
|
||||||
@@ -1455,20 +1470,10 @@ async function Generate(type, automatic_trigger, force_name2) {
|
|||||||
storyString += appendToStoryString(charPersonality, power_user.disable_personality_formatting ? '' : 'Personality: ');
|
storyString += appendToStoryString(charPersonality, power_user.disable_personality_formatting ? '' : 'Personality: ');
|
||||||
storyString += appendToStoryString(Scenario, power_user.disable_scenario_formatting ? '' : 'Scenario: ');
|
storyString += appendToStoryString(Scenario, power_user.disable_scenario_formatting ? '' : 'Scenario: ');
|
||||||
} else {
|
} else {
|
||||||
if (charDescription !== undefined) {
|
|
||||||
if (charPersonality.length > 0 && !power_user.disable_personality_formatting) {
|
|
||||||
charPersonality = name2 + "'s personality: " + charPersonality;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
storyString += appendToStoryString(charDescription, '');
|
storyString += appendToStoryString(charDescription, '');
|
||||||
|
|
||||||
if (storyString.endsWith('\n')) {
|
|
||||||
storyString = storyString.slice(0, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count_view_mes < topAnchorDepth) {
|
if (count_view_mes < topAnchorDepth) {
|
||||||
storyString += '\n' + appendToStoryString(charPersonality, '');
|
storyString += appendToStoryString(charPersonality, power_user.disable_personality_formatting ? '' : name2 + "'s personality: ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1481,10 +1486,7 @@ async function Generate(type, automatic_trigger, force_name2) {
|
|||||||
if (power_user.pin_examples && main_api !== 'openai') {
|
if (power_user.pin_examples && main_api !== 'openai') {
|
||||||
for (let example of mesExamplesArray) {
|
for (let example of mesExamplesArray) {
|
||||||
if (!is_pygmalion) {
|
if (!is_pygmalion) {
|
||||||
if (!storyString.endsWith('\n')) {
|
const replaceString = power_user.disable_examples_formatting ? '' : `This is how ${name2} should talk`;
|
||||||
storyString += '\n';
|
|
||||||
}
|
|
||||||
const replaceString = power_user.disable_examples_formatting ? `This is how ${name2} should talk` : '';
|
|
||||||
example = example.replace(/<START>/i, replaceString);
|
example = example.replace(/<START>/i, replaceString);
|
||||||
}
|
}
|
||||||
storyString += appendToStoryString(example, '');
|
storyString += appendToStoryString(example, '');
|
||||||
@@ -1575,8 +1577,6 @@ async function Generate(type, automatic_trigger, force_name2) {
|
|||||||
hordeAmountGen = adjustedParams.maxLength;
|
hordeAmountGen = adjustedParams.maxLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { worldInfoString, worldInfoBefore, worldInfoAfter } = getWorldInfoPrompt(chat2);
|
|
||||||
|
|
||||||
// Extension added strings
|
// Extension added strings
|
||||||
const allAnchors = getAllExtensionPrompts();
|
const allAnchors = getAllExtensionPrompts();
|
||||||
const afterScenarioAnchor = getExtensionPrompt(extension_prompt_types.AFTER_SCENARIO);
|
const afterScenarioAnchor = getExtensionPrompt(extension_prompt_types.AFTER_SCENARIO);
|
||||||
@@ -1584,12 +1584,13 @@ async function Generate(type, automatic_trigger, force_name2) {
|
|||||||
|
|
||||||
/////////////////////// swipecode
|
/////////////////////// swipecode
|
||||||
if (type == 'swipe') {
|
if (type == 'swipe') {
|
||||||
|
|
||||||
console.log('pre swipe shift: ' + chat2.length);
|
console.log('pre swipe shift: ' + chat2.length);
|
||||||
console.log('shifting swipe chat2');
|
console.log('shifting swipe chat2');
|
||||||
chat2.shift();
|
chat2.shift();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let { worldInfoString, worldInfoBefore, worldInfoAfter } = getWorldInfoPrompt(chat2);
|
||||||
|
|
||||||
console.log('post swipe shift:' + chat2.length);
|
console.log('post swipe shift:' + chat2.length);
|
||||||
var i = 0;
|
var i = 0;
|
||||||
|
|
||||||
@@ -1642,9 +1643,6 @@ async function Generate(type, automatic_trigger, force_name2) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!is_pygmalion && Scenario && Scenario.length > 0) {
|
if (!is_pygmalion && Scenario && Scenario.length > 0) {
|
||||||
if (!storyString.endsWith('\n')) {
|
|
||||||
storyString += '\n';
|
|
||||||
}
|
|
||||||
storyString += !power_user.disable_scenario_formatting ? `Circumstances and context of the dialogue: ${Scenario}\n` : `${Scenario}\n`;
|
storyString += !power_user.disable_scenario_formatting ? `Circumstances and context of the dialogue: ${Scenario}\n` : `${Scenario}\n`;
|
||||||
}
|
}
|
||||||
console.log('calling runGenerate');
|
console.log('calling runGenerate');
|
||||||
@@ -1679,9 +1677,9 @@ async function Generate(type, automatic_trigger, force_name2) {
|
|||||||
is_add_personality = true;
|
is_add_personality = true;
|
||||||
//chatString = chatString.substr(0,chatString.length-1);
|
//chatString = chatString.substr(0,chatString.length-1);
|
||||||
//anchorAndPersonality = "[Genre: roleplay chat][Tone: very long messages with descriptions]";
|
//anchorAndPersonality = "[Genre: roleplay chat][Tone: very long messages with descriptions]";
|
||||||
if ((anchorTop != "" || charPersonality != "") && !is_pygmalion) {
|
let personalityAndAnchor = [ charPersonality, anchorTop ].filter(x => x).join(' ');
|
||||||
if (anchorTop != "") charPersonality += ' ';
|
if (personalityAndAnchor && !is_pygmalion) {
|
||||||
item += "[" + charPersonality + anchorTop + ']\n';
|
item += "[" + personalityAndAnchor + ']\n';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (i >= arrMes.length - 1 && count_view_mes > 8 && $.trim(item).substr(0, (name1 + ":").length) == name1 + ":" && !is_pygmalion) {//For add anchor in end
|
if (i >= arrMes.length - 1 && count_view_mes > 8 && $.trim(item).substr(0, (name1 + ":").length) == name1 + ":" && !is_pygmalion) {//For add anchor in end
|
||||||
@@ -1895,7 +1893,7 @@ async function Generate(type, automatic_trigger, force_name2) {
|
|||||||
'early_stopping': textgenerationwebui_settings.early_stopping,
|
'early_stopping': textgenerationwebui_settings.early_stopping,
|
||||||
'seed': textgenerationwebui_settings.seed,
|
'seed': textgenerationwebui_settings.seed,
|
||||||
'add_bos_token': textgenerationwebui_settings.add_bos_token,
|
'add_bos_token': textgenerationwebui_settings.add_bos_token,
|
||||||
'custom_stopping_strings': JSON.stringify(getStoppingStrings(isImpersonate, false)),
|
'stopping_strings': getStoppingStrings(isImpersonate, false),
|
||||||
'truncation_length': max_context,
|
'truncation_length': max_context,
|
||||||
'ban_eos_token': textgenerationwebui_settings.ban_eos_token,
|
'ban_eos_token': textgenerationwebui_settings.ban_eos_token,
|
||||||
'skip_special_tokens': textgenerationwebui_settings.skip_special_tokens,
|
'skip_special_tokens': textgenerationwebui_settings.skip_special_tokens,
|
||||||
@@ -1943,7 +1941,7 @@ async function Generate(type, automatic_trigger, force_name2) {
|
|||||||
let prompt = await prepareOpenAIMessages(name2, storyString, worldInfoBefore, worldInfoAfter, afterScenarioAnchor, promptBias, type);
|
let prompt = await prepareOpenAIMessages(name2, storyString, worldInfoBefore, worldInfoAfter, afterScenarioAnchor, promptBias, type);
|
||||||
|
|
||||||
if (isStreamingEnabled()) {
|
if (isStreamingEnabled()) {
|
||||||
streamingProcessor.generator = await sendOpenAIRequest(prompt);
|
streamingProcessor.generator = await sendOpenAIRequest(prompt, streamingProcessor.abortController.signal);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
sendOpenAIRequest(prompt).then(onSuccess).catch(onError);
|
sendOpenAIRequest(prompt).then(onSuccess).catch(onError);
|
||||||
@@ -1954,14 +1952,14 @@ async function Generate(type, automatic_trigger, force_name2) {
|
|||||||
}
|
}
|
||||||
else if (main_api == 'poe') {
|
else if (main_api == 'poe') {
|
||||||
if (isStreamingEnabled()) {
|
if (isStreamingEnabled()) {
|
||||||
streamingProcessor.generator = await generatePoe(type, finalPromt);
|
streamingProcessor.generator = await generatePoe(type, finalPromt, streamingProcessor.abortController.signal);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
generatePoe(type, finalPromt).then(onSuccess).catch(onError);
|
generatePoe(type, finalPromt).then(onSuccess).catch(onError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (main_api == 'textgenerationwebui' && textgenerationwebui_settings.streaming) {
|
else if (main_api == 'textgenerationwebui' && textgenerationwebui_settings.streaming) {
|
||||||
streamingProcessor.generator = await generateTextGenWithStreaming(generate_data);
|
streamingProcessor.generator = await generateTextGenWithStreaming(generate_data, streamingProcessor.abortController.signal);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
jQuery.ajax({
|
jQuery.ajax({
|
||||||
@@ -2193,6 +2191,19 @@ function cleanUpMessage(getMessage, isImpersonate) {
|
|||||||
getMessage = getMessage.trim();
|
getMessage = getMessage.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const stoppingStrings = getStoppingStrings(isImpersonate, false);
|
||||||
|
|
||||||
|
for (const stoppingString of stoppingStrings) {
|
||||||
|
if (stoppingString.length) {
|
||||||
|
for (let j = stoppingString.length - 1; j > 0; j--) {
|
||||||
|
if (getMessage.slice(-j) === stoppingString.slice(0, j)) {
|
||||||
|
getMessage = getMessage.slice(0, -j);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return getMessage;
|
return getMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2218,6 +2229,7 @@ function saveReply(type, getMessage, this_mes_is_name) {
|
|||||||
} else {
|
} else {
|
||||||
console.log('entering chat update routine for non-swipe post');
|
console.log('entering chat update routine for non-swipe post');
|
||||||
chat[chat.length] = {};
|
chat[chat.length] = {};
|
||||||
|
chat[chat.length - 1]['extra'] = {};
|
||||||
chat[chat.length - 1]['name'] = name2;
|
chat[chat.length - 1]['name'] = name2;
|
||||||
chat[chat.length - 1]['is_user'] = false;
|
chat[chat.length - 1]['is_user'] = false;
|
||||||
chat[chat.length - 1]['is_name'] = this_mes_is_name;
|
chat[chat.length - 1]['is_name'] = this_mes_is_name;
|
||||||
@@ -2233,6 +2245,7 @@ function saveReply(type, getMessage, this_mes_is_name) {
|
|||||||
}
|
}
|
||||||
chat[chat.length - 1]['is_name'] = true;
|
chat[chat.length - 1]['is_name'] = true;
|
||||||
chat[chat.length - 1]['force_avatar'] = avatarImg;
|
chat[chat.length - 1]['force_avatar'] = avatarImg;
|
||||||
|
chat[chat.length - 1]['extra']['gen_id'] = group_generation_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
saveImageToMessage(img, chat[chat.length - 1]);
|
saveImageToMessage(img, chat[chat.length - 1]);
|
||||||
@@ -2268,6 +2281,7 @@ function activateSendButtons() {
|
|||||||
is_send_press = false;
|
is_send_press = false;
|
||||||
$("#send_but").css("display", "flex");
|
$("#send_but").css("display", "flex");
|
||||||
$("#loading_mes").css("display", "none");
|
$("#loading_mes").css("display", "none");
|
||||||
|
$("#send_textarea").attr("disabled", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function deactivateSendButtons() {
|
function deactivateSendButtons() {
|
||||||
@@ -2892,6 +2906,22 @@ function isInt(value) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function messageEditAuto(div) {
|
||||||
|
let mesBlock = div.closest(".mes_block");
|
||||||
|
var text = mesBlock.find(".edit_textarea").val().trim();
|
||||||
|
const bias = extractMessageBias(text);
|
||||||
|
chat[this_edit_mes_id]["mes"] = text;
|
||||||
|
|
||||||
|
// editing old messages
|
||||||
|
if (!chat[this_edit_mes_id]["extra"]) {
|
||||||
|
chat[this_edit_mes_id]["extra"] = {};
|
||||||
|
}
|
||||||
|
chat[this_edit_mes_id]["extra"]["bias"] = bias ?? null;
|
||||||
|
mesBlock.find(".mes_text").val('');
|
||||||
|
mesBlock.find(".mes_text").val(messageFormating(text));
|
||||||
|
saveChatDebounced();
|
||||||
|
}
|
||||||
|
|
||||||
function messageEditDone(div) {
|
function messageEditDone(div) {
|
||||||
let mesBlock = div.closest(".mes_block");
|
let mesBlock = div.closest(".mes_block");
|
||||||
var text = mesBlock.find(".edit_textarea").val().trim();
|
var text = mesBlock.find(".edit_textarea").val().trim();
|
||||||
@@ -2908,7 +2938,9 @@ function messageEditDone(div) {
|
|||||||
mesBlock.find(".mes_text").empty();
|
mesBlock.find(".mes_text").empty();
|
||||||
mesBlock.find(".mes_edit_buttons").css("display", "none");
|
mesBlock.find(".mes_edit_buttons").css("display", "none");
|
||||||
mesBlock.find(".mes_edit").css("display", "inline-block");
|
mesBlock.find(".mes_edit").css("display", "inline-block");
|
||||||
mesBlock.find(".mes_text").append(messageFormating(text, this_edit_mes_chname, chat[this_edit_mes_id].is_system, chat[this_edit_mes_id].force_avatar));
|
mesBlock.find(".mes_text").append(
|
||||||
|
messageFormating(text, this_edit_mes_chname, chat[this_edit_mes_id].is_system, chat[this_edit_mes_id].force_avatar)
|
||||||
|
);
|
||||||
mesBlock.find(".mes_bias").empty();
|
mesBlock.find(".mes_bias").empty();
|
||||||
mesBlock.find(".mes_bias").append(messageFormating(bias));
|
mesBlock.find(".mes_bias").append(messageFormating(bias));
|
||||||
appendImageToMessage(chat[this_edit_mes_id], div.closest(".mes"));
|
appendImageToMessage(chat[this_edit_mes_id], div.closest(".mes"));
|
||||||
@@ -3059,8 +3091,8 @@ function selectRightMenuWithAnimation(selectedMenuId) {
|
|||||||
$(menu).css("opacity", 0.0);
|
$(menu).css("opacity", 0.0);
|
||||||
$(menu).transition({
|
$(menu).transition({
|
||||||
opacity: 1.0,
|
opacity: 1.0,
|
||||||
duration: animation_rm_duration,
|
duration: animation_duration,
|
||||||
easing: animation_rm_easing,
|
easing: animation_easing,
|
||||||
complete: function () { },
|
complete: function () { },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -3249,8 +3281,8 @@ function callPopup(text, type) {
|
|||||||
}
|
}
|
||||||
$("#shadow_popup").transition({
|
$("#shadow_popup").transition({
|
||||||
opacity: 1.0,
|
opacity: 1.0,
|
||||||
duration: animation_rm_duration,
|
duration: animation_duration,
|
||||||
easing: animation_rm_easing,
|
easing: animation_easing,
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
@@ -3422,7 +3454,7 @@ function isHordeGenerationNotAllowed() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
window["TavernAI"].getContext = function () {
|
window["SillyTavern"].getContext = function () {
|
||||||
return {
|
return {
|
||||||
chat: chat,
|
chat: chat,
|
||||||
characters: characters,
|
characters: characters,
|
||||||
@@ -3475,7 +3507,7 @@ $(document).ready(function () {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const swipe_duration = 120;
|
const swipe_duration = 200;
|
||||||
const swipe_range = 700;
|
const swipe_range = 700;
|
||||||
//console.log(swipe_range);
|
//console.log(swipe_range);
|
||||||
let run_generate = false;
|
let run_generate = false;
|
||||||
@@ -3486,6 +3518,10 @@ $(document).ready(function () {
|
|||||||
chat[chat.length - 1]['swipes'][0] = chat[chat.length - 1]['mes']; //assign swipe array with last message from chat
|
chat[chat.length - 1]['swipes'][0] = chat[chat.length - 1]['mes']; //assign swipe array with last message from chat
|
||||||
}
|
}
|
||||||
chat[chat.length - 1]['swipe_id']++; //make new slot in array
|
chat[chat.length - 1]['swipe_id']++; //make new slot in array
|
||||||
|
// if message has memory attached - remove it to allow regen
|
||||||
|
if (chat[chat.length - 1].extra && chat[chat.length - 1].extra.memory) {
|
||||||
|
delete chat[chat.length - 1].extra.memory;
|
||||||
|
}
|
||||||
//console.log(chat[chat.length-1]['swipes']);
|
//console.log(chat[chat.length-1]['swipes']);
|
||||||
if (parseInt(chat[chat.length - 1]['swipe_id']) === chat[chat.length - 1]['swipes'].length) { //if swipe id of last message is the same as the length of the 'swipes' array
|
if (parseInt(chat[chat.length - 1]['swipe_id']) === chat[chat.length - 1]['swipes'].length) { //if swipe id of last message is the same as the length of the 'swipes' array
|
||||||
|
|
||||||
@@ -3513,7 +3549,7 @@ $(document).ready(function () {
|
|||||||
this_mes_div.children('.mes_block').transition({ // this moves the div back and forth
|
this_mes_div.children('.mes_block').transition({ // this moves the div back and forth
|
||||||
x: '-' + swipe_range,
|
x: '-' + swipe_range,
|
||||||
duration: swipe_duration,
|
duration: swipe_duration,
|
||||||
easing: animation_rm_easing,
|
easing: animation_easing,
|
||||||
queue: false,
|
queue: false,
|
||||||
complete: function () {
|
complete: function () {
|
||||||
/*if (!selected_group) {
|
/*if (!selected_group) {
|
||||||
@@ -3540,7 +3576,7 @@ $(document).ready(function () {
|
|||||||
|
|
||||||
|
|
||||||
this_mes_div.animate({ height: new_height + 'px' }, {
|
this_mes_div.animate({ height: new_height + 'px' }, {
|
||||||
duration: 100,
|
duration: 0, //used to be 100
|
||||||
queue: false,
|
queue: false,
|
||||||
progress: function () {
|
progress: function () {
|
||||||
// Scroll the chat down as the message expands
|
// Scroll the chat down as the message expands
|
||||||
@@ -3555,13 +3591,13 @@ $(document).ready(function () {
|
|||||||
this_mes_div.children('.mes_block').transition({
|
this_mes_div.children('.mes_block').transition({
|
||||||
x: swipe_range,
|
x: swipe_range,
|
||||||
duration: 0,
|
duration: 0,
|
||||||
easing: animation_rm_easing,
|
easing: animation_easing,
|
||||||
queue: false,
|
queue: false,
|
||||||
complete: function () {
|
complete: function () {
|
||||||
this_mes_div.children('.mes_block').transition({
|
this_mes_div.children('.mes_block').transition({
|
||||||
x: '0px',
|
x: '0px',
|
||||||
duration: swipe_duration,
|
duration: swipe_duration,
|
||||||
easing: animation_rm_easing,
|
easing: animation_easing,
|
||||||
queue: false,
|
queue: false,
|
||||||
complete: function () {
|
complete: function () {
|
||||||
if (run_generate && !is_send_press && parseInt(chat[chat.length - 1]['swipe_id']) === chat[chat.length - 1]['swipes'].length) {
|
if (run_generate && !is_send_press && parseInt(chat[chat.length - 1]['swipe_id']) === chat[chat.length - 1]['swipes'].length) {
|
||||||
@@ -3584,19 +3620,19 @@ $(document).ready(function () {
|
|||||||
$(this).parent().children('.avatar').transition({ // moves avatar aong with swipe
|
$(this).parent().children('.avatar').transition({ // moves avatar aong with swipe
|
||||||
x: '-' + swipe_range,
|
x: '-' + swipe_range,
|
||||||
duration: swipe_duration,
|
duration: swipe_duration,
|
||||||
easing: animation_rm_easing,
|
easing: animation_easing,
|
||||||
queue: false,
|
queue: false,
|
||||||
complete: function () {
|
complete: function () {
|
||||||
$(this).parent().children('.avatar').transition({
|
$(this).parent().children('.avatar').transition({
|
||||||
x: swipe_range,
|
x: swipe_range,
|
||||||
duration: 0,
|
duration: 0,
|
||||||
easing: animation_rm_easing,
|
easing: animation_easing,
|
||||||
queue: false,
|
queue: false,
|
||||||
complete: function () {
|
complete: function () {
|
||||||
$(this).parent().children('.avatar').transition({
|
$(this).parent().children('.avatar').transition({
|
||||||
x: '0px',
|
x: '0px',
|
||||||
duration: swipe_duration,
|
duration: swipe_duration,
|
||||||
easing: animation_rm_easing,
|
easing: animation_easing,
|
||||||
queue: false,
|
queue: false,
|
||||||
complete: function () {
|
complete: function () {
|
||||||
|
|
||||||
@@ -3636,7 +3672,7 @@ $(document).ready(function () {
|
|||||||
$(this).parent().children('.mes_block').transition({
|
$(this).parent().children('.mes_block').transition({
|
||||||
x: swipe_range,
|
x: swipe_range,
|
||||||
duration: swipe_duration,
|
duration: swipe_duration,
|
||||||
easing: animation_rm_easing,
|
easing: animation_easing,
|
||||||
queue: false,
|
queue: false,
|
||||||
complete: function () {
|
complete: function () {
|
||||||
const is_animation_scroll = ($('#chat').scrollTop() >= ($('#chat').prop("scrollHeight") - $('#chat').outerHeight()) - 10);
|
const is_animation_scroll = ($('#chat').scrollTop() >= ($('#chat').prop("scrollHeight") - $('#chat').outerHeight()) - 10);
|
||||||
@@ -3645,7 +3681,7 @@ $(document).ready(function () {
|
|||||||
let new_height = this_mes_div_height - (this_mes_block_height - this_mes_block[0].scrollHeight);
|
let new_height = this_mes_div_height - (this_mes_block_height - this_mes_block[0].scrollHeight);
|
||||||
if (new_height < 103) new_height = 103;
|
if (new_height < 103) new_height = 103;
|
||||||
this_mes_div.animate({ height: new_height + 'px' }, {
|
this_mes_div.animate({ height: new_height + 'px' }, {
|
||||||
duration: 100,
|
duration: 0, //used to be 100
|
||||||
queue: false,
|
queue: false,
|
||||||
progress: function () {
|
progress: function () {
|
||||||
// Scroll the chat down as the message expands
|
// Scroll the chat down as the message expands
|
||||||
@@ -3661,13 +3697,13 @@ $(document).ready(function () {
|
|||||||
$(this).parent().children('.mes_block').transition({
|
$(this).parent().children('.mes_block').transition({
|
||||||
x: '-' + swipe_range,
|
x: '-' + swipe_range,
|
||||||
duration: 0,
|
duration: 0,
|
||||||
easing: animation_rm_easing,
|
easing: animation_easing,
|
||||||
queue: false,
|
queue: false,
|
||||||
complete: function () {
|
complete: function () {
|
||||||
$(this).parent().children('.mes_block').transition({
|
$(this).parent().children('.mes_block').transition({
|
||||||
x: '0px',
|
x: '0px',
|
||||||
duration: swipe_duration,
|
duration: swipe_duration,
|
||||||
easing: animation_rm_easing,
|
easing: animation_easing,
|
||||||
queue: false,
|
queue: false,
|
||||||
complete: function () {
|
complete: function () {
|
||||||
saveChatConditional();
|
saveChatConditional();
|
||||||
@@ -3681,19 +3717,19 @@ $(document).ready(function () {
|
|||||||
$(this).parent().children('.avatar').transition({
|
$(this).parent().children('.avatar').transition({
|
||||||
x: swipe_range,
|
x: swipe_range,
|
||||||
duration: swipe_duration,
|
duration: swipe_duration,
|
||||||
easing: animation_rm_easing,
|
easing: animation_easing,
|
||||||
queue: false,
|
queue: false,
|
||||||
complete: function () {
|
complete: function () {
|
||||||
$(this).parent().children('.avatar').transition({
|
$(this).parent().children('.avatar').transition({
|
||||||
x: '-' + swipe_range,
|
x: '-' + swipe_range,
|
||||||
duration: 0,
|
duration: 0,
|
||||||
easing: animation_rm_easing,
|
easing: animation_easing,
|
||||||
queue: false,
|
queue: false,
|
||||||
complete: function () {
|
complete: function () {
|
||||||
$(this).parent().children('.avatar').transition({
|
$(this).parent().children('.avatar').transition({
|
||||||
x: '0px',
|
x: '0px',
|
||||||
duration: swipe_duration,
|
duration: swipe_duration,
|
||||||
easing: animation_rm_easing,
|
easing: animation_easing,
|
||||||
queue: false,
|
queue: false,
|
||||||
complete: function () {
|
complete: function () {
|
||||||
|
|
||||||
@@ -3925,8 +3961,8 @@ $(document).ready(function () {
|
|||||||
$("#character_popup").css("opacity", 0.0);
|
$("#character_popup").css("opacity", 0.0);
|
||||||
$("#character_popup").transition({
|
$("#character_popup").transition({
|
||||||
opacity: 1.0,
|
opacity: 1.0,
|
||||||
duration: animation_rm_duration,
|
duration: animation_duration,
|
||||||
easing: animation_rm_easing,
|
easing: animation_easing,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
is_advanced_char_open = false;
|
is_advanced_char_open = false;
|
||||||
@@ -4231,6 +4267,7 @@ $(document).ready(function () {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
$("#talkativeness_slider").on("input", function () {
|
$("#talkativeness_slider").on("input", function () {
|
||||||
if (menu_type == "create") {
|
if (menu_type == "create") {
|
||||||
create_save_talkativeness = $("#talkativeness_slider").val();
|
create_save_talkativeness = $("#talkativeness_slider").val();
|
||||||
@@ -4305,8 +4342,8 @@ $(document).ready(function () {
|
|||||||
if ($("#options").css("opacity") == 1.0) {
|
if ($("#options").css("opacity") == 1.0) {
|
||||||
$("#options").transition({
|
$("#options").transition({
|
||||||
opacity: 0.0,
|
opacity: 0.0,
|
||||||
duration: 100, //animation_rm_duration,
|
duration: 100, //animation_duration,
|
||||||
easing: animation_rm_easing,
|
easing: animation_easing,
|
||||||
complete: function () {
|
complete: function () {
|
||||||
$("#options").css("display", "none");
|
$("#options").css("display", "none");
|
||||||
},
|
},
|
||||||
@@ -4326,7 +4363,7 @@ $(document).ready(function () {
|
|||||||
$("#options").transition({
|
$("#options").transition({
|
||||||
opacity: 1.0, // the manual setting of CSS via JS is what allows the click-away feature to work
|
opacity: 1.0, // the manual setting of CSS via JS is what allows the click-away feature to work
|
||||||
duration: 100,
|
duration: 100,
|
||||||
easing: animation_rm_easing,
|
easing: animation_easing,
|
||||||
complete: function () { optionsPopper.update(); },
|
complete: function () { optionsPopper.update(); },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -4350,8 +4387,8 @@ $(document).ready(function () {
|
|||||||
$("#shadow_select_chat_popup").css("opacity", 0.0);
|
$("#shadow_select_chat_popup").css("opacity", 0.0);
|
||||||
$("#shadow_select_chat_popup").transition({
|
$("#shadow_select_chat_popup").transition({
|
||||||
opacity: 1.0,
|
opacity: 1.0,
|
||||||
duration: animation_rm_duration,
|
duration: animation_duration,
|
||||||
easing: animation_rm_easing,
|
easing: animation_easing,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4578,7 +4615,7 @@ $(document).ready(function () {
|
|||||||
$("#shadow_tips_popup").transition({
|
$("#shadow_tips_popup").transition({
|
||||||
opacity: 1.0,
|
opacity: 1.0,
|
||||||
duration: 100,
|
duration: 100,
|
||||||
easing: animation_rm_easing,
|
easing: animation_easing,
|
||||||
complete: function () { },
|
complete: function () { },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -4587,7 +4624,7 @@ $(document).ready(function () {
|
|||||||
$("#shadow_tips_popup").transition({
|
$("#shadow_tips_popup").transition({
|
||||||
opacity: 0.0,
|
opacity: 0.0,
|
||||||
duration: 100,
|
duration: 100,
|
||||||
easing: animation_rm_easing,
|
easing: animation_easing,
|
||||||
complete: function () {
|
complete: function () {
|
||||||
$("#shadow_tips_popup").css("display", "none");
|
$("#shadow_tips_popup").css("display", "none");
|
||||||
},
|
},
|
||||||
@@ -4647,9 +4684,7 @@ $(document).ready(function () {
|
|||||||
.closest(".mes_block")
|
.closest(".mes_block")
|
||||||
.find(".mes_text")
|
.find(".mes_text")
|
||||||
.append(
|
.append(
|
||||||
'<textarea class=edit_textarea style="max-width:auto; ">' +
|
`<textarea id='curEditTextarea' class='edit_textarea' style='max-width:auto; '>${text}</textarea>`
|
||||||
text +
|
|
||||||
"</textarea>"
|
|
||||||
);
|
);
|
||||||
let edit_textarea = $(this)
|
let edit_textarea = $(this)
|
||||||
.closest(".mes_block")
|
.closest(".mes_block")
|
||||||
@@ -4668,6 +4703,13 @@ $(document).ready(function () {
|
|||||||
updateEditArrowClasses();
|
updateEditArrowClasses();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(document).on('input', '#curEditTextarea', function () {
|
||||||
|
if (power_user.auto_save_msg_edits === true) {
|
||||||
|
messageEditAuto($(this));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
$(document).on("click", ".mes_edit_cancel", function () {
|
$(document).on("click", ".mes_edit_cancel", function () {
|
||||||
let text = chat[this_edit_mes_id]["mes"];
|
let text = chat[this_edit_mes_id]["mes"];
|
||||||
|
|
||||||
@@ -4985,6 +5027,7 @@ $(document).ready(function () {
|
|||||||
|
|
||||||
$(document).on("click", ".mes_stop", function () {
|
$(document).on("click", ".mes_stop", function () {
|
||||||
if (streamingProcessor) {
|
if (streamingProcessor) {
|
||||||
|
streamingProcessor.abortController.abort();
|
||||||
streamingProcessor.isStopped = true;
|
streamingProcessor.isStopped = true;
|
||||||
streamingProcessor.onStopStreaming();
|
streamingProcessor.onStopStreaming();
|
||||||
streamingProcessor = null;
|
streamingProcessor = null;
|
||||||
@@ -5055,7 +5098,34 @@ $(document).ready(function () {
|
|||||||
|
|
||||||
$(document).keyup(function (e) {
|
$(document).keyup(function (e) {
|
||||||
if (e.key === "Escape") {
|
if (e.key === "Escape") {
|
||||||
closeMessageEditor();
|
if (power_user.auto_save_msg_edits === false) {
|
||||||
|
closeMessageEditor();
|
||||||
|
$("#send_textarea").focus();
|
||||||
|
}
|
||||||
|
if (power_user.auto_save_msg_edits === true) {
|
||||||
|
$(`#chat .mes[mesid="${this_edit_mes_id}"] .mes_edit_done`).click()
|
||||||
|
$("#send_textarea").focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#bg-filter").on("input", function () {
|
||||||
|
const filterValue = $(this).val().toLowerCase();
|
||||||
|
$("#bg_menu_content > div").each(function () {
|
||||||
|
const $bgContent = $(this);
|
||||||
|
if ($bgContent.attr("title").toLowerCase().includes(filterValue)) {
|
||||||
|
$bgContent.show();
|
||||||
|
} else {
|
||||||
|
$bgContent.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('beforeunload', () => {
|
||||||
|
if (streamingProcessor) {
|
||||||
|
console.log('Page reloaded. Aborting streaming...');
|
||||||
|
streamingProcessor.abortController.abort();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
@@ -60,11 +60,46 @@ const observer = new MutationObserver(function (mutations) {
|
|||||||
} else if (mutation.target.parentNode === SelectedCharacterTab) {
|
} else if (mutation.target.parentNode === SelectedCharacterTab) {
|
||||||
setTimeout(RA_CountCharTokens, 200);
|
setTimeout(RA_CountCharTokens, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
observer.observe(document.documentElement, observerConfig);
|
observer.observe(document.documentElement, observerConfig);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for an element before resolving a promise
|
||||||
|
* @param {String} querySelector - Selector of element to wait for
|
||||||
|
* @param {Integer} timeout - Milliseconds to wait before timing out, or 0 for no timeout
|
||||||
|
*/
|
||||||
|
function waitForElement(querySelector, timeout) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
var timer = false;
|
||||||
|
if (document.querySelectorAll(querySelector).length) return resolve();
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
if (document.querySelectorAll(querySelector).length) {
|
||||||
|
observer.disconnect();
|
||||||
|
if (timer !== false) clearTimeout(timer);
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
observer.observe(document.body, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true
|
||||||
|
});
|
||||||
|
if (timeout) timer = setTimeout(() => {
|
||||||
|
observer.disconnect();
|
||||||
|
reject();
|
||||||
|
}, timeout);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForElement("#expression-image", 10000).then(function () {
|
||||||
|
|
||||||
|
dragElement(document.getElementById("expression-holder"));
|
||||||
|
}).catch(() => {
|
||||||
|
console.log("expression holder not loaded yet");
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
//RossAscends: Added function to format dates used in files and chat timestamps to a humanized format.
|
//RossAscends: Added function to format dates used in files and chat timestamps to a humanized format.
|
||||||
//Mostly I wanted this to be for file names, but couldn't figure out exactly where the filename save code was as everything seemed to be connected.
|
//Mostly I wanted this to be for file names, but couldn't figure out exactly where the filename save code was as everything seemed to be connected.
|
||||||
@@ -178,11 +213,20 @@ async function RA_autoloadchat() {
|
|||||||
if (document.getElementById('CharID0') !== null) {
|
if (document.getElementById('CharID0') !== null) {
|
||||||
//console.log('char list loaded! clicking activeChar');
|
//console.log('char list loaded! clicking activeChar');
|
||||||
var CharToAutoLoad = document.getElementById('CharID' + LoadLocal('ActiveChar'));
|
var CharToAutoLoad = document.getElementById('CharID' + LoadLocal('ActiveChar'));
|
||||||
|
//console.log(CharToAutoLoad);
|
||||||
let autoLoadGroup = document.querySelector(`.group_select[grid="${LoadLocal('ActiveGroup')}"]`);
|
let autoLoadGroup = document.querySelector(`.group_select[grid="${LoadLocal('ActiveGroup')}"]`);
|
||||||
|
//console.log(autoLoadGroup);
|
||||||
if (CharToAutoLoad != null) {
|
if (CharToAutoLoad != null) {
|
||||||
|
|
||||||
|
|
||||||
|
// console.log('--ALC - clicking character');
|
||||||
CharToAutoLoad.click();
|
CharToAutoLoad.click();
|
||||||
|
CharToAutoLoad.click();
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (autoLoadGroup != null) {
|
else if (autoLoadGroup != null) {
|
||||||
|
//console.log('--ALC - clicking group');
|
||||||
|
autoLoadGroup.click();
|
||||||
autoLoadGroup.click();
|
autoLoadGroup.click();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -315,9 +359,7 @@ function OpenNavPanels() {
|
|||||||
dragElement(document.getElementById("sheld"));
|
dragElement(document.getElementById("sheld"));
|
||||||
dragElement(document.getElementById("left-nav-panel"));
|
dragElement(document.getElementById("left-nav-panel"));
|
||||||
dragElement(document.getElementById("right-nav-panel"));
|
dragElement(document.getElementById("right-nav-panel"));
|
||||||
setTimeout(function () {
|
|
||||||
dragElement(document.getElementById("expression-holder"))
|
|
||||||
}, 2000);
|
|
||||||
|
|
||||||
|
|
||||||
function dragElement(elmnt) {
|
function dragElement(elmnt) {
|
||||||
@@ -460,6 +502,7 @@ function dragElement(elmnt) {
|
|||||||
// ---------------------------------------------------
|
// ---------------------------------------------------
|
||||||
|
|
||||||
$("document").ready(function () {
|
$("document").ready(function () {
|
||||||
|
|
||||||
// initial status check
|
// initial status check
|
||||||
setTimeout(RA_checkOnlineStatus, 100);
|
setTimeout(RA_checkOnlineStatus, 100);
|
||||||
|
|
||||||
@@ -545,6 +588,14 @@ $("document").ready(function () {
|
|||||||
} else { SaveLocal('LNavOpened', 'false'); }
|
} else { SaveLocal('LNavOpened', 'false'); }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var chatbarInFocus = false;
|
||||||
|
$('#send_textarea').focus(function () {
|
||||||
|
chatbarInFocus = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#send_textarea').blur(function () {
|
||||||
|
chatbarInFocus = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -601,37 +652,33 @@ $("document").ready(function () {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
function isInputElementInFocus() {
|
function isInputElementInFocus() {
|
||||||
return $(document.activeElement).is(":input");
|
//return $(document.activeElement).is(":input");
|
||||||
|
var focused = $(':focus');
|
||||||
|
if (focused.is('input') || focused.is('textarea')) {
|
||||||
|
if (focused.attr('id') === 'send_textarea') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//Additional hotkeys CTRL+ENTER and CTRL+UPARROW
|
//Additional hotkeys CTRL+ENTER and CTRL+UPARROW
|
||||||
document.addEventListener("keydown", (event) => {
|
document.addEventListener("keydown", (event) => {
|
||||||
if (event.ctrlKey && event.key == "Enter") {
|
if (event.ctrlKey && event.key == "Enter") {
|
||||||
// Ctrl+Enter for Regeneration Last Response
|
// Ctrl+Enter for Regeneration Last Response
|
||||||
if (is_send_press == false) {
|
if (is_send_press == false) {
|
||||||
|
|
||||||
$('#option_regenerate').click();
|
$('#option_regenerate').click();
|
||||||
$('#options').hide();
|
$('#options').hide();
|
||||||
//setTimeout(function () { $('#chat').click(); }, 50) //needed to remove the options menu popping up..
|
|
||||||
//Generate("regenerate");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (event.ctrlKey && event.key == "ArrowUp") {
|
|
||||||
//Ctrl+UpArrow for Connect to last server
|
|
||||||
console.log(main_api);
|
|
||||||
if (online_status === "no_connection") {
|
|
||||||
if (main_api == "kobold") {
|
|
||||||
document.getElementById("api_button").click();
|
|
||||||
}
|
|
||||||
if (main_api == "novel") {
|
|
||||||
document.getElementById("api_button_novel").click();
|
|
||||||
}
|
|
||||||
if (main_api == "textgenerationwebui") {
|
|
||||||
document.getElementById("api_button_textgenerationwebui").click();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.ctrlKey && event.key == "ArrowLeft") { //for debug, show all local stored vars
|
if (event.ctrlKey && event.key == "ArrowLeft") { //for debug, show all local stored vars
|
||||||
CheckLocal();
|
CheckLocal();
|
||||||
}
|
}
|
||||||
@@ -662,5 +709,42 @@ $("document").ready(function () {
|
|||||||
$('.swipe_right:last').click();
|
$('.swipe_right:last').click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (event.ctrlKey && event.key == "ArrowUp") { //edits last USER message if chatbar is empty and focused
|
||||||
|
console.log('got ctrl+uparrow input');
|
||||||
|
if (
|
||||||
|
$("#send_textarea").val() === '' &&
|
||||||
|
chatbarInFocus === true &&
|
||||||
|
$(".swipe_right:last").css('display') === 'flex' &&
|
||||||
|
$("#character_popup").css("display") === "none" &&
|
||||||
|
$("#shadow_select_chat_popup").css("display") === "none"
|
||||||
|
) {
|
||||||
|
const isUserMesList = document.querySelectorAll('div[is_user="true"]');
|
||||||
|
const lastIsUserMes = isUserMesList[isUserMesList.length - 1];
|
||||||
|
const editMes = lastIsUserMes.querySelector('.mes_block .mes_edit');
|
||||||
|
if (editMes !== null) {
|
||||||
|
$(editMes).click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key == "ArrowUp") { //edits last message if chatbar is empty and focused
|
||||||
|
console.log('got uparrow input');
|
||||||
|
if (
|
||||||
|
$("#send_textarea").val() === '' &&
|
||||||
|
chatbarInFocus === true &&
|
||||||
|
$(".swipe_right:last").css('display') === 'flex' &&
|
||||||
|
$("#character_popup").css("display") === "none" &&
|
||||||
|
$("#shadow_select_chat_popup").css("display") === "none"
|
||||||
|
) {
|
||||||
|
const lastMes = document.querySelector('.last_mes');
|
||||||
|
const editMes = lastMes.querySelector('.mes_block .mes_edit');
|
||||||
|
if (editMes !== null) {
|
||||||
|
$(editMes).click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -29,7 +29,7 @@ const extension_settings = {
|
|||||||
let modules = [];
|
let modules = [];
|
||||||
let activeExtensions = new Set();
|
let activeExtensions = new Set();
|
||||||
|
|
||||||
const getContext = () => window['TavernAI'].getContext();
|
const getContext = () => window['SillyTavern'].getContext();
|
||||||
const getApiUrl = () => extension_settings.apiUrl;
|
const getApiUrl = () => extension_settings.apiUrl;
|
||||||
const defaultRequestArgs = { method: 'GET', headers: { 'Bypass-Tunnel-Reminder': 'bypass' } };
|
const defaultRequestArgs = { method: 'GET', headers: { 'Bypass-Tunnel-Reminder': 'bypass' } };
|
||||||
let connectedToApi = false;
|
let connectedToApi = false;
|
||||||
|
@@ -91,12 +91,6 @@ async function onSelectImage(e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
function patchSendForm() {
|
|
||||||
const columns = $('#send_form').css('grid-template-columns').split(' ');
|
|
||||||
columns[columns.length - 1] = `${parseInt(columns[columns.length - 1]) + 40}px`;
|
|
||||||
columns[1] = 'auto';
|
|
||||||
$('#send_form').css('grid-template-columns', columns.join(' '));
|
|
||||||
}
|
|
||||||
function addSendPictureButton() {
|
function addSendPictureButton() {
|
||||||
const sendButton = document.createElement('div');
|
const sendButton = document.createElement('div');
|
||||||
sendButton.id = 'send_picture';
|
sendButton.id = 'send_picture';
|
||||||
@@ -118,7 +112,6 @@ $(document).ready(function () {
|
|||||||
addPictureSendForm();
|
addPictureSendForm();
|
||||||
addSendPictureButton();
|
addSendPictureButton();
|
||||||
setImageIcon();
|
setImageIcon();
|
||||||
patchSendForm();
|
|
||||||
moduleWorker();
|
moduleWorker();
|
||||||
setInterval(moduleWorker, UPDATE_INTERVAL);
|
setInterval(moduleWorker, UPDATE_INTERVAL);
|
||||||
});
|
});
|
@@ -79,13 +79,6 @@ function addDiceScript() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function patchSendForm() {
|
|
||||||
const columns = $('#send_form').css('grid-template-columns').split(' ');
|
|
||||||
columns[columns.length - 1] = `${parseInt(columns[columns.length - 1]) + 40}px`;
|
|
||||||
columns[1] = 'auto';
|
|
||||||
$('#send_form').css('grid-template-columns', columns.join(' '));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function moduleWorker() {
|
async function moduleWorker() {
|
||||||
const context = getContext();
|
const context = getContext();
|
||||||
|
|
||||||
@@ -97,7 +90,6 @@ async function moduleWorker() {
|
|||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
addDiceScript();
|
addDiceScript();
|
||||||
addDiceRollButton();
|
addDiceRollButton();
|
||||||
patchSendForm();
|
|
||||||
setDiceIcon();
|
setDiceIcon();
|
||||||
moduleWorker();
|
moduleWorker();
|
||||||
setInterval(moduleWorker, UPDATE_INTERVAL);
|
setInterval(moduleWorker, UPDATE_INTERVAL);
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { saveSettingsDebounced } from "../../../script.js";
|
import { saveSettingsDebounced, token } from "../../../script.js";
|
||||||
import { getContext, getApiUrl, modules, extension_settings } from "../../extensions.js";
|
import { getContext, getApiUrl, modules, extension_settings } from "../../extensions.js";
|
||||||
export { MODULE_NAME };
|
export { MODULE_NAME };
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ const DEFAULT_EXPRESSIONS = [
|
|||||||
let expressionsList = null;
|
let expressionsList = null;
|
||||||
let lastCharacter = undefined;
|
let lastCharacter = undefined;
|
||||||
let lastMessage = null;
|
let lastMessage = null;
|
||||||
let existingExpressions = [];
|
let spriteCache = {};
|
||||||
let inApiCall = false;
|
let inApiCall = false;
|
||||||
|
|
||||||
function onExpressionsShowDefaultInput() {
|
function onExpressionsShowDefaultInput() {
|
||||||
@@ -58,21 +58,25 @@ function onExpressionsShowDefaultInput() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function moduleWorker() {
|
let isWorkerBusy = false;
|
||||||
function getLastCharacterMessage() {
|
|
||||||
const reversedChat = context.chat.slice().reverse();
|
|
||||||
|
|
||||||
for (let mes of reversedChat) {
|
async function moduleWorkerWrapper() {
|
||||||
if (mes.is_user || mes.is_system) {
|
// Don't touch me I'm busy...
|
||||||
continue;
|
if (isWorkerBusy) {
|
||||||
}
|
return;
|
||||||
|
|
||||||
return { mes: mes.mes, name: mes.name };
|
|
||||||
}
|
|
||||||
|
|
||||||
return { mes: '', name: null };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// I'm free. Let's update!
|
||||||
|
try {
|
||||||
|
isWorkerBusy = true;
|
||||||
|
await moduleWorker();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
isWorkerBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function moduleWorker() {
|
||||||
const context = getContext();
|
const context = getContext();
|
||||||
|
|
||||||
// non-characters not supported
|
// non-characters not supported
|
||||||
@@ -84,27 +88,43 @@ async function moduleWorker() {
|
|||||||
// character changed
|
// character changed
|
||||||
if (context.groupId !== lastCharacter && context.characterId !== lastCharacter) {
|
if (context.groupId !== lastCharacter && context.characterId !== lastCharacter) {
|
||||||
removeExpression();
|
removeExpression();
|
||||||
validateImages();
|
spriteCache = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const currentLastMessage = getLastCharacterMessage();
|
||||||
|
|
||||||
|
// character has no expressions or it is not loaded
|
||||||
|
if (Object.keys(spriteCache).length === 0) {
|
||||||
|
await validateImages(currentLastMessage.name);
|
||||||
|
lastCharacter = context.groupId || context.characterId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const offlineMode = $('.expression_settings .offline_mode');
|
||||||
if (!modules.includes('classify')) {
|
if (!modules.includes('classify')) {
|
||||||
$('.expression_settings').show();
|
$('.expression_settings').show();
|
||||||
$('.expression_settings .offline_mode').css('display', 'block');
|
offlineMode.css('display', 'block');
|
||||||
lastCharacter = context.characterId;
|
lastCharacter = context.groupId || context.characterId;
|
||||||
|
|
||||||
|
if (context.groupId) {
|
||||||
|
await validateImages(currentLastMessage.name, true);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$('.expression_settings .offline_mode').css('display', 'none');
|
// force reload expressions list on connect to API
|
||||||
}
|
if (offlineMode.is(':visible')) {
|
||||||
|
expressionsList = null;
|
||||||
// character has no expressions or it is not loaded
|
spriteCache = {};
|
||||||
if (!context.groupId && existingExpressions.length === 0) {
|
expressionsList = await getExpressionsList();
|
||||||
lastCharacter = context.groupId || context.characterId;
|
await validateImages(currentLastMessage.name, true);
|
||||||
return;
|
}
|
||||||
|
|
||||||
|
offlineMode.css('display', 'none');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// check if last message changed
|
// check if last message changed
|
||||||
const currentLastMessage = getLastCharacterMessage();
|
|
||||||
if ((lastCharacter === context.characterId || lastCharacter === context.groupId)
|
if ((lastCharacter === context.characterId || lastCharacter === context.groupId)
|
||||||
&& lastMessage === currentLastMessage.mes) {
|
&& lastMessage === currentLastMessage.mes) {
|
||||||
return;
|
return;
|
||||||
@@ -154,48 +174,67 @@ async function moduleWorker() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getLastCharacterMessage() {
|
||||||
|
const context = getContext();
|
||||||
|
const reversedChat = context.chat.slice().reverse();
|
||||||
|
|
||||||
|
for (let mes of reversedChat) {
|
||||||
|
if (mes.is_user || mes.is_system) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { mes: mes.mes, name: mes.name };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { mes: '', name: null };
|
||||||
|
}
|
||||||
|
|
||||||
function removeExpression() {
|
function removeExpression() {
|
||||||
lastMessage = null;
|
lastMessage = null;
|
||||||
|
$('img.expression').off('error');
|
||||||
$('img.expression').prop('src', '');
|
$('img.expression').prop('src', '');
|
||||||
$('img.expression').removeClass('default');
|
$('img.expression').removeClass('default');
|
||||||
$('.expression_settings').hide();
|
$('.expression_settings').hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
let imagesValidating = false;
|
async function validateImages(character, forceRedrawCached) {
|
||||||
|
if (!character) {
|
||||||
async function validateImages() {
|
|
||||||
if (imagesValidating) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
imagesValidating = true;
|
const labels = await getExpressionsList();
|
||||||
existingExpressions = [];
|
|
||||||
const context = getContext();
|
if (spriteCache[character]) {
|
||||||
|
if (forceRedrawCached && $('#image_list').data('name') !== character) {
|
||||||
|
console.log('force redrawing character sprites list')
|
||||||
|
drawSpritesList(character, labels, spriteCache[character]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sprites = await getSpritesList(character);
|
||||||
|
let validExpressions = drawSpritesList(character, labels, sprites);
|
||||||
|
spriteCache[character] = validExpressions;
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawSpritesList(character, labels, sprites) {
|
||||||
|
let validExpressions = [];
|
||||||
$('.expression_settings').show();
|
$('.expression_settings').show();
|
||||||
$('#image_list').empty();
|
$('#image_list').empty();
|
||||||
|
$('#image_list').data('name', character);
|
||||||
|
labels.sort().forEach((item) => {
|
||||||
|
const sprite = sprites.find(x => x.label == item);
|
||||||
|
|
||||||
if (!context.characterId) {
|
if (sprite) {
|
||||||
imagesValidating = false;
|
validExpressions.push(sprite);
|
||||||
return;
|
$('#image_list').append(getListItem(item, sprite.path, 'success'));
|
||||||
}
|
|
||||||
|
|
||||||
const IMAGE_LIST = (await getExpressionsList()).map(x => ({ name: x, file: `${x}.png` }));
|
|
||||||
IMAGE_LIST.forEach((item) => {
|
|
||||||
const image = document.createElement('img');
|
|
||||||
image.src = `/characters/${context.name2}/${item.file}`;
|
|
||||||
image.classList.add('debug-image');
|
|
||||||
image.width = '0px';
|
|
||||||
image.height = '0px';
|
|
||||||
image.onload = function () {
|
|
||||||
existingExpressions.push(item.name);
|
|
||||||
$('#image_list').append(getListItem(item.file, image.src, 'success'));
|
|
||||||
}
|
}
|
||||||
image.onerror = function () {
|
else {
|
||||||
$('#image_list').append(getListItem(item.file, '/img/No-Image-Placeholder.svg', 'failure'));
|
$('#image_list').append(getListItem(item, '/img/No-Image-Placeholder.svg', 'failure'));
|
||||||
}
|
}
|
||||||
$('#image_list').prepend(image);
|
|
||||||
});
|
});
|
||||||
imagesValidating = false;
|
return validExpressions;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getListItem(item, imageSrc, textClass) {
|
function getListItem(item, imageSrc, textClass) {
|
||||||
@@ -207,16 +246,33 @@ function getListItem(item, imageSrc, textClass) {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getSpritesList(name) {
|
||||||
|
console.log('getting sprites list');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await fetch(`/get_sprites?name=${encodeURIComponent(name)}`, {
|
||||||
|
headers: {
|
||||||
|
'X-CSRF-Token': token,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let sprites = result.ok ? (await result.json()) : [];
|
||||||
|
return sprites;
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function getExpressionsList() {
|
async function getExpressionsList() {
|
||||||
console.log('getting expressions list');
|
console.log('getting expressions list');
|
||||||
// get something for offline mode (6 default images)
|
// get something for offline mode (default images)
|
||||||
if (!modules.includes('classify')) {
|
if (!modules.includes('classify')) {
|
||||||
console.log('classify not available, loading default');
|
|
||||||
return DEFAULT_EXPRESSIONS;
|
return DEFAULT_EXPRESSIONS;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(expressionsList)) {
|
if (Array.isArray(expressionsList)) {
|
||||||
console.log('got array, loading array');
|
|
||||||
return expressionsList;
|
return expressionsList;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,21 +280,19 @@ async function getExpressionsList() {
|
|||||||
url.pathname = '/api/classify/labels';
|
url.pathname = '/api/classify/labels';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('trying for API');
|
|
||||||
const apiResult = await fetch(url, {
|
const apiResult = await fetch(url, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: { 'Bypass-Tunnel-Reminder': 'bypass' },
|
headers: { 'Bypass-Tunnel-Reminder': 'bypass' },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (apiResult.ok) {
|
if (apiResult.ok) {
|
||||||
console.log('API ok, adding labels');
|
|
||||||
const data = await apiResult.json();
|
const data = await apiResult.json();
|
||||||
expressionsList = data.labels;
|
expressionsList = data.labels;
|
||||||
return expressionsList;
|
return expressionsList;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.log('got error!');
|
|
||||||
console.log(error);
|
console.log(error);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -246,13 +300,14 @@ async function getExpressionsList() {
|
|||||||
|
|
||||||
async function setExpression(character, expression, force) {
|
async function setExpression(character, expression, force) {
|
||||||
console.log('entered setExpressions');
|
console.log('entered setExpressions');
|
||||||
const filename = `${expression}.png`;
|
await validateImages(character);
|
||||||
const img = $('img.expression');
|
const img = $('img.expression');
|
||||||
|
|
||||||
|
const sprite = (spriteCache[character] && spriteCache[character].find(x => x.label === expression));
|
||||||
console.log('checking for expression images to show..');
|
console.log('checking for expression images to show..');
|
||||||
if (force || (existingExpressions.includes(expression))) {
|
if (sprite) {
|
||||||
console.log('setting expression from character images folder');
|
console.log('setting expression from character images folder');
|
||||||
const imgUrl = `/characters/${character}/${filename}`;
|
img.attr('src', sprite.path);
|
||||||
img.attr('src', imgUrl);
|
|
||||||
img.removeClass('default');
|
img.removeClass('default');
|
||||||
img.off('error');
|
img.off('error');
|
||||||
img.on('error', function () {
|
img.on('error', function () {
|
||||||
@@ -263,18 +318,18 @@ async function setExpression(character, expression, force) {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (extension_settings.expressions.showDefault) {
|
if (extension_settings.expressions.showDefault) {
|
||||||
console.log('no character images, trying default expressions');
|
|
||||||
setDefault();
|
setDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setDefault() {
|
function setDefault() {
|
||||||
console.log('setting default');
|
console.log('setting default');
|
||||||
const defImgUrl = `/img/default-expressions/${filename}`;
|
const defImgUrl = `/img/default-expressions/${expression}.png`;
|
||||||
//console.log(defImgUrl);
|
//console.log(defImgUrl);
|
||||||
img.attr('src', defImgUrl);
|
img.attr('src', defImgUrl);
|
||||||
img.addClass('default');
|
img.addClass('default');
|
||||||
}
|
}
|
||||||
|
document.getElementById("expression-holder").style.display = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClickExpressionImage() {
|
function onClickExpressionImage() {
|
||||||
@@ -283,26 +338,25 @@ function onClickExpressionImage() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const context = getContext();
|
const expression = $(this).attr('id');
|
||||||
const expression = $(this).attr('id').replace('.png', '');
|
const name = getLastCharacterMessage().name;
|
||||||
|
|
||||||
if ($(this).find('.failure').length === 0) {
|
if ($(this).find('.failure').length === 0) {
|
||||||
setExpression(context.name2, expression, true);
|
setExpression(name, expression, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
function addExpressionImage() {
|
function addExpressionImage() {
|
||||||
console.log('entered addExpressionImage');
|
|
||||||
const html = `
|
const html = `
|
||||||
<div id="expression-holder" class="expression-holder">
|
<div id="expression-holder" class="expression-holder" style="display:none;">
|
||||||
<div id="expression-holderheader" class="fa-solid fa-grip drag-grabber"></div>
|
<div id="expression-holderheader" class="fa-solid fa-grip drag-grabber"></div>
|
||||||
<img class="expression">
|
<img id="expression-image" class="expression">
|
||||||
</div>`;
|
</div>`;
|
||||||
$('body').append(html);
|
$('body').append(html);
|
||||||
}
|
}
|
||||||
function addSettings() {
|
function addSettings() {
|
||||||
console.log('entered addSettings');
|
|
||||||
const html = `
|
const html = `
|
||||||
<div class="expression_settings">
|
<div class="expression_settings">
|
||||||
<div class="inline-drawer">
|
<div class="inline-drawer">
|
||||||
@@ -313,7 +367,8 @@ function onClickExpressionImage() {
|
|||||||
<div class="inline-drawer-content">
|
<div class="inline-drawer-content">
|
||||||
<p class="offline_mode">You are in offline mode. Click on the image below to set the expression.</p>
|
<p class="offline_mode">You are in offline mode. Click on the image below to set the expression.</p>
|
||||||
<div id="image_list"></div>
|
<div id="image_list"></div>
|
||||||
<p class="hint"><b>Hint:</b> <i>Create new folder in the <b>public/characters/</b> folder and name it as the name of the character. Put PNG images with expressions there.</i></p>
|
<p class="hint"><b>Hint:</b> <i>Create new folder in the <b>public/characters/</b> folder and name it as the name of the character.
|
||||||
|
Put images with expressions there. File names should follow the pattern: <tt>[expression_label].[image_format]</tt></i></p>
|
||||||
<label for="expressions_show_default"><input id="expressions_show_default" type="checkbox">Show default images (emojis) if missing</label>
|
<label for="expressions_show_default"><input id="expressions_show_default" type="checkbox">Show default images (emojis) if missing</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -328,5 +383,5 @@ function onClickExpressionImage() {
|
|||||||
|
|
||||||
addExpressionImage();
|
addExpressionImage();
|
||||||
addSettings();
|
addSettings();
|
||||||
setInterval(moduleWorker, UPDATE_INTERVAL);
|
setInterval(moduleWorkerWrapper, UPDATE_INTERVAL);
|
||||||
})();
|
})();
|
@@ -14,8 +14,7 @@
|
|||||||
bottom: 1px;
|
bottom: 1px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
filter: drop-shadow(2px 2px 2px #51515199);
|
filter: drop-shadow(2px 2px 2px #51515199);
|
||||||
z-index: 3;
|
z-index: 1;
|
||||||
/* resize: both; */
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -41,12 +41,14 @@ import {
|
|||||||
updateChatMetadata,
|
updateChatMetadata,
|
||||||
isStreamingEnabled,
|
isStreamingEnabled,
|
||||||
getThumbnailUrl,
|
getThumbnailUrl,
|
||||||
|
streamingProcessor,
|
||||||
} from "../script.js";
|
} from "../script.js";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
selected_group,
|
selected_group,
|
||||||
is_group_automode_enabled,
|
is_group_automode_enabled,
|
||||||
is_group_generating,
|
is_group_generating,
|
||||||
|
group_generation_id,
|
||||||
groups,
|
groups,
|
||||||
saveGroupChat,
|
saveGroupChat,
|
||||||
generateGroupWrapper,
|
generateGroupWrapper,
|
||||||
@@ -63,6 +65,7 @@ let is_group_generating = false; // Group generation flag
|
|||||||
let is_group_automode_enabled = false;
|
let is_group_automode_enabled = false;
|
||||||
let groups = [];
|
let groups = [];
|
||||||
let selected_group = null;
|
let selected_group = null;
|
||||||
|
let group_generation_id = null;
|
||||||
|
|
||||||
const group_activation_strategy = {
|
const group_activation_strategy = {
|
||||||
NATURAL: 0,
|
NATURAL: 0,
|
||||||
@@ -87,10 +90,18 @@ async function _save(group) {
|
|||||||
|
|
||||||
// Group chats
|
// Group chats
|
||||||
async function regenerateGroup() {
|
async function regenerateGroup() {
|
||||||
|
let generationId = getLastMessageGenerationId();
|
||||||
|
|
||||||
while (chat.length > 0) {
|
while (chat.length > 0) {
|
||||||
const lastMes = chat[chat.length - 1];
|
const lastMes = chat[chat.length - 1];
|
||||||
|
const this_generationId = lastMes.extra?.gen_id;
|
||||||
|
|
||||||
if (lastMes.is_user || lastMes.is_system) {
|
// for new generations after the update
|
||||||
|
if ((generationId && this_generationId) && generationId !== this_generationId) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// legacy for generations before the update
|
||||||
|
else if (lastMes.is_user || lastMes.is_system) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,19 +140,7 @@ async function getGroupChat(id) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mes = {};
|
const mes = getFirstCharacterMessage(character);
|
||||||
mes["is_user"] = false;
|
|
||||||
mes["is_system"] = false;
|
|
||||||
mes["name"] = character.name;
|
|
||||||
mes["is_name"] = true;
|
|
||||||
mes["send_date"] = humanizedDateTime();
|
|
||||||
mes["mes"] = character.first_mes
|
|
||||||
? substituteParams(character.first_mes.trim(), name1, character.name)
|
|
||||||
: default_ch_mes;
|
|
||||||
mes["force_avatar"] =
|
|
||||||
character.avatar != "none"
|
|
||||||
? getThumbnailUrl('avatar', character.avatar)
|
|
||||||
: default_avatar;
|
|
||||||
chat.push(mes);
|
chat.push(mes);
|
||||||
addOneMessage(mes);
|
addOneMessage(mes);
|
||||||
}
|
}
|
||||||
@@ -153,16 +152,33 @@ async function getGroupChat(id) {
|
|||||||
updateChatMetadata(metadata, true);
|
updateChatMetadata(metadata, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
await saveGroupChat(id);
|
await saveGroupChat(id, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getFirstCharacterMessage(character) {
|
||||||
|
const mes = {};
|
||||||
|
mes["is_user"] = false;
|
||||||
|
mes["is_system"] = false;
|
||||||
|
mes["name"] = character.name;
|
||||||
|
mes["is_name"] = true;
|
||||||
|
mes["send_date"] = humanizedDateTime();
|
||||||
|
mes["mes"] = character.first_mes
|
||||||
|
? substituteParams(character.first_mes.trim(), name1, character.name)
|
||||||
|
: default_ch_mes;
|
||||||
|
mes["force_avatar"] =
|
||||||
|
character.avatar != "none"
|
||||||
|
? getThumbnailUrl('avatar', character.avatar)
|
||||||
|
: default_avatar;
|
||||||
|
return mes;
|
||||||
|
}
|
||||||
|
|
||||||
function resetSelectedGroup() {
|
function resetSelectedGroup() {
|
||||||
selected_group = null;
|
selected_group = null;
|
||||||
is_group_generating = false;
|
is_group_generating = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveGroupChat(id) {
|
async function saveGroupChat(id, shouldSaveGroup) {
|
||||||
const response = await fetch("/savegroupchat", {
|
const response = await fetch("/savegroupchat", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -172,7 +188,7 @@ async function saveGroupChat(id) {
|
|||||||
body: JSON.stringify({ id: id, chat: [...chat] }),
|
body: JSON.stringify({ id: id, chat: [...chat] }),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.ok) {
|
if (shouldSaveGroup && response.ok) {
|
||||||
await editGroup(id);
|
await editGroup(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -302,6 +318,8 @@ async function generateGroupWrapper(by_auto_mode, type = null) {
|
|||||||
$("#chat").append(typingIndicator);
|
$("#chat").append(typingIndicator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// id of this specific batch for regeneration purposes
|
||||||
|
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;
|
||||||
@@ -329,6 +347,10 @@ async function generateGroupWrapper(by_auto_mode, type = null) {
|
|||||||
throw new Error('Deleted group member swiped');
|
throw new Error('Deleted group member swiped');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (type === "impersonate") {
|
||||||
|
$("#send_textarea").attr("disabled", true);
|
||||||
|
activatedMembers = activateImpersonate(group.members);
|
||||||
|
}
|
||||||
else if (activationStrategy === group_activation_strategy.NATURAL) {
|
else if (activationStrategy === group_activation_strategy.NATURAL) {
|
||||||
activatedMembers = activateNaturalOrder(group.members, activationText, lastMessage, group.allow_self_responses, isUserInput);
|
activatedMembers = activateNaturalOrder(group.members, activationText, lastMessage, group.allow_self_responses, isUserInput);
|
||||||
}
|
}
|
||||||
@@ -338,13 +360,13 @@ async function generateGroupWrapper(by_auto_mode, type = null) {
|
|||||||
|
|
||||||
// now the real generation begins: cycle through every character
|
// now the real generation begins: cycle through every character
|
||||||
for (const chId of activatedMembers) {
|
for (const chId of activatedMembers) {
|
||||||
const generateType = type !== "swipe" ? "group_chat" : "swipe";
|
const generateType = type == "swipe" || type == "impersonate" ? type : "group_chat";
|
||||||
setCharacterId(chId);
|
setCharacterId(chId);
|
||||||
setCharacterName(characters[chId].name)
|
setCharacterName(characters[chId].name)
|
||||||
|
|
||||||
await Generate(generateType, by_auto_mode);
|
await Generate(generateType, by_auto_mode);
|
||||||
|
|
||||||
if (type !== "swipe") {
|
if (type !== "swipe" && type !== "impersonate") {
|
||||||
// update indicator and scroll down
|
// update indicator and scroll down
|
||||||
typingIndicator
|
typingIndicator
|
||||||
.find(".typing_indicator_name")
|
.find(".typing_indicator_name")
|
||||||
@@ -361,8 +383,41 @@ async function generateGroupWrapper(by_auto_mode, type = null) {
|
|||||||
await delay(100);
|
await delay(100);
|
||||||
}
|
}
|
||||||
// if swipe - see if message changed
|
// if swipe - see if message changed
|
||||||
else if (type === "swipe" && lastMessageText === chat[chat.length - 1].mes) {
|
else if (type === "swipe") {
|
||||||
await delay(100);
|
if (isStreamingEnabled()) {
|
||||||
|
if (streamingProcessor && !streamingProcessor.isFinished) {
|
||||||
|
await delay(100);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (lastMessageText === chat[chat.length - 1].mes) {
|
||||||
|
await delay(100);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (type === "impersonate") {
|
||||||
|
if (isStreamingEnabled()) {
|
||||||
|
if (streamingProcessor && !streamingProcessor.isFinished) {
|
||||||
|
await delay(100);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!$("#send_textarea").val() || $("#send_textarea").val() == userInput) {
|
||||||
|
await delay(100);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
messagesBefore++;
|
messagesBefore++;
|
||||||
@@ -376,6 +431,7 @@ async function generateGroupWrapper(by_auto_mode, type = null) {
|
|||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
is_group_generating = false;
|
is_group_generating = false;
|
||||||
|
$("#send_textarea").attr("disabled", false);
|
||||||
setSendButtonState(false);
|
setSendButtonState(false);
|
||||||
setCharacterId(undefined);
|
setCharacterId(undefined);
|
||||||
setCharacterName('');
|
setCharacterName('');
|
||||||
@@ -383,6 +439,26 @@ async function generateGroupWrapper(by_auto_mode, type = null) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getLastMessageGenerationId() {
|
||||||
|
let generationId = null;
|
||||||
|
if (chat.length > 0) {
|
||||||
|
const lastMes = chat[chat.length - 1];
|
||||||
|
if (!lastMes.is_user && !lastMes.is_system && lastMes.extra) {
|
||||||
|
generationId = lastMes.extra.gen_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return generationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function activateImpersonate(members) {
|
||||||
|
const randomIndex = Math.floor(Math.random() * members.length);
|
||||||
|
const activatedNames = [members[randomIndex]];
|
||||||
|
const memberIds = activatedNames
|
||||||
|
.map((x) => characters.findIndex((y) => y.name === x))
|
||||||
|
.filter((x) => x !== -1);
|
||||||
|
return memberIds;
|
||||||
|
}
|
||||||
|
|
||||||
function activateSwipe(members) {
|
function activateSwipe(members) {
|
||||||
const name = chat[chat.length - 1].name;
|
const name = chat[chat.length - 1].name;
|
||||||
const activatedNames = members.includes(name) ? [name] : [];
|
const activatedNames = members.includes(name) ? [name] : [];
|
||||||
|
@@ -85,7 +85,7 @@ function getKoboldGenerationData(finalPromt, this_settings, this_amount_gen, thi
|
|||||||
s7: this_settings.sampler_order[6],
|
s7: this_settings.sampler_order[6],
|
||||||
use_world_info: false,
|
use_world_info: false,
|
||||||
singleline: kai_settings.single_line,
|
singleline: kai_settings.single_line,
|
||||||
stop_sequence: kai_settings.use_stop_sequence ? [getStoppingStrings(isImpersonate, false)] : undefined,
|
stop_sequence: kai_settings.use_stop_sequence ? getStoppingStrings(isImpersonate, false) : undefined,
|
||||||
};
|
};
|
||||||
return generate_data;
|
return generate_data;
|
||||||
}
|
}
|
||||||
|
@@ -55,6 +55,7 @@ const default_impersonation_prompt = "[Write your next reply from the point of v
|
|||||||
|
|
||||||
const gpt3_max = 4095;
|
const gpt3_max = 4095;
|
||||||
const gpt4_max = 8191;
|
const gpt4_max = 8191;
|
||||||
|
const gpt4_32k_max = 32767;
|
||||||
|
|
||||||
const tokenCache = {};
|
const tokenCache = {};
|
||||||
|
|
||||||
@@ -435,7 +436,12 @@ function getSystemPrompt(nsfw_toggle_prompt, enhance_definitions_prompt, wiBefor
|
|||||||
return whole_prompt;
|
return whole_prompt;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendOpenAIRequest(openai_msgs_tosend) {
|
async function sendOpenAIRequest(openai_msgs_tosend, signal) {
|
||||||
|
// Provide default abort signal
|
||||||
|
if (!signal) {
|
||||||
|
signal = new AbortController().signal;
|
||||||
|
}
|
||||||
|
|
||||||
if (oai_settings.reverse_proxy) {
|
if (oai_settings.reverse_proxy) {
|
||||||
validateReverseProxy();
|
validateReverseProxy();
|
||||||
}
|
}
|
||||||
@@ -458,7 +464,8 @@ async function sendOpenAIRequest(openai_msgs_tosend) {
|
|||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
"X-CSRF-Token": token,
|
"X-CSRF-Token": token,
|
||||||
}
|
},
|
||||||
|
signal: signal,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (oai_settings.stream_openai) {
|
if (oai_settings.stream_openai) {
|
||||||
@@ -772,6 +779,9 @@ $(document).ready(function () {
|
|||||||
if (value == 'gpt-4') {
|
if (value == 'gpt-4') {
|
||||||
$('#openai_max_context').attr('max', gpt4_max);
|
$('#openai_max_context').attr('max', gpt4_max);
|
||||||
}
|
}
|
||||||
|
else if (value == 'gpt-4-32k') {
|
||||||
|
$('#openai_max_context').attr('max', gpt4_32k_max);
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
$('#openai_max_context').attr('max', gpt3_max);
|
$('#openai_max_context').attr('max', gpt3_max);
|
||||||
oai_settings.openai_max_context = Math.max(oai_settings.openai_max_context, gpt3_max);
|
oai_settings.openai_max_context = Math.max(oai_settings.openai_max_context, gpt3_max);
|
||||||
|
@@ -86,7 +86,7 @@ function onBotChange() {
|
|||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generatePoe(type, finalPrompt) {
|
async function generatePoe(type, finalPrompt, signal) {
|
||||||
if (poe_settings.auto_purge) {
|
if (poe_settings.auto_purge) {
|
||||||
let count_to_delete = -1;
|
let count_to_delete = -1;
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@ async function generatePoe(type, finalPrompt) {
|
|||||||
finalPrompt = sentences.join('');
|
finalPrompt = sentences.join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
const reply = await sendMessage(finalPrompt, true);
|
const reply = await sendMessage(finalPrompt, true, signal);
|
||||||
got_reply = true;
|
got_reply = true;
|
||||||
return reply;
|
return reply;
|
||||||
}
|
}
|
||||||
@@ -160,7 +160,11 @@ async function purgeConversation(count = -1) {
|
|||||||
return response.ok;
|
return response.ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendMessage(prompt, withStreaming) {
|
async function sendMessage(prompt, withStreaming, signal) {
|
||||||
|
if (!signal) {
|
||||||
|
signal = new AbortController().signal;
|
||||||
|
}
|
||||||
|
|
||||||
const body = JSON.stringify({
|
const body = JSON.stringify({
|
||||||
bot: poe_settings.bot,
|
bot: poe_settings.bot,
|
||||||
token: poe_settings.token,
|
token: poe_settings.token,
|
||||||
@@ -175,6 +179,7 @@ async function sendMessage(prompt, withStreaming) {
|
|||||||
},
|
},
|
||||||
body: body,
|
body: body,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
signal: signal,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (withStreaming && poe_settings.streaming) {
|
if (withStreaming && poe_settings.streaming) {
|
||||||
|
@@ -59,6 +59,7 @@ let power_user = {
|
|||||||
sheld_width: sheld_width.DEFAULT,
|
sheld_width: sheld_width.DEFAULT,
|
||||||
play_message_sound: false,
|
play_message_sound: false,
|
||||||
play_sound_unfocused: true,
|
play_sound_unfocused: true,
|
||||||
|
auto_save_msg_edits: false,
|
||||||
sort_field: 'name',
|
sort_field: 'name',
|
||||||
sort_order: 'asc',
|
sort_order: 'asc',
|
||||||
font_scale: 1,
|
font_scale: 1,
|
||||||
@@ -94,9 +95,6 @@ const storage_keys = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const chat = document.getElementById('chat');
|
const chat = document.getElementById('chat');
|
||||||
const chatTransition = window.getComputedStyle(chat).transition;
|
|
||||||
|
|
||||||
//Updated at the bottom of this script document based on 'focus' and 'blur' events
|
|
||||||
let browser_has_focus = true;
|
let browser_has_focus = true;
|
||||||
|
|
||||||
function playMessageSound() {
|
function playMessageSound() {
|
||||||
@@ -161,66 +159,32 @@ function applySheldWidth() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function applyThemeColor(type) {
|
async function applyThemeColor(type) {
|
||||||
// temporarily unset transition from chat to not make the browser calculate the animation
|
|
||||||
chat.style.transition = 'unset';
|
|
||||||
|
|
||||||
if (type === 'main') {
|
if (type === 'main') {
|
||||||
document.documentElement.style.setProperty('--SmartThemeBodyColor', power_user.main_text_color);
|
document.documentElement.style.setProperty('--SmartThemeBodyColor', power_user.main_text_color);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'italics') {
|
if (type === 'italics') {
|
||||||
document.documentElement.style.setProperty('--SmartThemeEmColor', power_user.italics_text_color);
|
document.documentElement.style.setProperty('--SmartThemeEmColor', power_user.italics_text_color);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'fastUIBG') {
|
if (type === 'fastUIBG') {
|
||||||
document.documentElement.style.setProperty('--SmartThemeFastUIBGColor', power_user.fastui_bg_color);
|
document.documentElement.style.setProperty('--SmartThemeFastUIBGColor', power_user.fastui_bg_color);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'blurTint') {
|
if (type === 'blurTint') {
|
||||||
document.documentElement.style.setProperty('--SmartThemeBlurTintColor', power_user.blur_tint_color);
|
document.documentElement.style.setProperty('--SmartThemeBlurTintColor', power_user.blur_tint_color);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// a small delay to let the browser do the layout redraw
|
|
||||||
await delay(1);
|
|
||||||
// set transition back to chat
|
|
||||||
chat.style.transition = chatTransition;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function applyBlurStrength() {
|
async function applyBlurStrength() {
|
||||||
power_user.blur_strength = Number(localStorage.getItem(storage_keys.blur_strength) ?? 1);
|
power_user.blur_strength = Number(localStorage.getItem(storage_keys.blur_strength) ?? 1);
|
||||||
|
|
||||||
// temporarily unset transition from chat to not make the browser calculate the animation
|
|
||||||
chat.style.transition = 'unset';
|
|
||||||
|
|
||||||
// now apply the blur strength to the document
|
|
||||||
document.documentElement.style.setProperty('--blurStrength', power_user.blur_strength);
|
document.documentElement.style.setProperty('--blurStrength', power_user.blur_strength);
|
||||||
$("#blur_strength_counter").text(power_user.blur_strength);
|
$("#blur_strength_counter").text(power_user.blur_strength);
|
||||||
|
|
||||||
// a small delay to let the browser do the layout redraw
|
|
||||||
await delay(1);
|
|
||||||
// set transition back to chat
|
|
||||||
chat.style.transition = chatTransition;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function applyFontScale() {
|
async function applyFontScale() {
|
||||||
power_user.font_scale = Number(localStorage.getItem(storage_keys.font_scale) ?? 1);
|
power_user.font_scale = Number(localStorage.getItem(storage_keys.font_scale) ?? 1);
|
||||||
|
|
||||||
// temporarily unset transition from chat to not make the browser calculate the animation
|
|
||||||
chat.style.transition = 'unset';
|
|
||||||
|
|
||||||
// now apply the font scale to the document
|
|
||||||
document.documentElement.style.setProperty('--fontScale', power_user.font_scale);
|
document.documentElement.style.setProperty('--fontScale', power_user.font_scale);
|
||||||
$("#font_scale_counter").text(power_user.font_scale);
|
$("#font_scale_counter").text(power_user.font_scale);
|
||||||
|
|
||||||
// a small delay to let the browser do the layout redraw
|
|
||||||
await delay(1);
|
|
||||||
// set transition back to chat
|
|
||||||
chat.style.transition = chatTransition;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function applyTheme(name) {
|
async function applyTheme(name) {
|
||||||
@@ -307,6 +271,7 @@ function loadPowerUserSettings(settings, data) {
|
|||||||
$("#multigen_next_chunks").val(power_user.multigen_next_chunks);
|
$("#multigen_next_chunks").val(power_user.multigen_next_chunks);
|
||||||
$("#play_message_sound").prop("checked", power_user.play_message_sound);
|
$("#play_message_sound").prop("checked", power_user.play_message_sound);
|
||||||
$("#play_sound_unfocused").prop("checked", power_user.play_sound_unfocused);
|
$("#play_sound_unfocused").prop("checked", power_user.play_sound_unfocused);
|
||||||
|
$("#auto_save_msg_edits").prop("checked", power_user.auto_save_msg_edits);
|
||||||
$(`input[name="avatar_style"][value="${power_user.avatar_style}"]`).prop("checked", true);
|
$(`input[name="avatar_style"][value="${power_user.avatar_style}"]`).prop("checked", true);
|
||||||
$(`input[name="chat_display"][value="${power_user.chat_display}"]`).prop("checked", true);
|
$(`input[name="chat_display"][value="${power_user.chat_display}"]`).prop("checked", true);
|
||||||
$(`input[name="sheld_width"][value="${power_user.sheld_width}"]`).prop("checked", true);
|
$(`input[name="sheld_width"][value="${power_user.sheld_width}"]`).prop("checked", true);
|
||||||
@@ -573,6 +538,11 @@ $(document).ready(() => {
|
|||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("#auto_save_msg_edits").on('input', function () {
|
||||||
|
power_user.auto_save_msg_edits = !!$(this).prop('checked');
|
||||||
|
saveSettingsDebounced();
|
||||||
|
});
|
||||||
|
|
||||||
$("#character_sort_order").on('change', function () {
|
$("#character_sort_order").on('change', function () {
|
||||||
power_user.sort_field = $(this).find(":selected").data('field');
|
power_user.sort_field = $(this).find(":selected").data('field');
|
||||||
power_user.sort_order = $(this).find(":selected").data('order');
|
power_user.sort_order = $(this).find(":selected").data('order');
|
||||||
|
@@ -26,11 +26,11 @@ let textgenerationwebui_settings = {
|
|||||||
seed: -1,
|
seed: -1,
|
||||||
preset: 'Default',
|
preset: 'Default',
|
||||||
add_bos_token: true,
|
add_bos_token: true,
|
||||||
custom_stopping_strings: [],
|
stopping_strings: [],
|
||||||
truncation_length: 2048,
|
truncation_length: 2048,
|
||||||
ban_eos_token: false,
|
ban_eos_token: false,
|
||||||
streaming: false,
|
streaming: false,
|
||||||
fn_index: 34,
|
fn_index: 43,
|
||||||
skip_special_tokens: true,
|
skip_special_tokens: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -147,7 +147,7 @@ function setSettingByName(i, value, trigger) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generateTextGenWithStreaming(generate_data) {
|
async function generateTextGenWithStreaming(generate_data, signal) {
|
||||||
const response = await fetch('/generate_textgenerationwebui', {
|
const response = await fetch('/generate_textgenerationwebui', {
|
||||||
headers: {
|
headers: {
|
||||||
'X-CSRF-Token': token,
|
'X-CSRF-Token': token,
|
||||||
@@ -157,6 +157,7 @@ async function generateTextGenWithStreaming(generate_data) {
|
|||||||
},
|
},
|
||||||
body: JSON.stringify(generate_data),
|
body: JSON.stringify(generate_data),
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
signal: signal,
|
||||||
});
|
});
|
||||||
|
|
||||||
return async function* streamData() {
|
return async function* streamData() {
|
||||||
|
@@ -50,13 +50,12 @@
|
|||||||
|
|
||||||
/* base variable for blur strength slider calculations */
|
/* base variable for blur strength slider calculations */
|
||||||
--blurStrength: 10;
|
--blurStrength: 10;
|
||||||
|
color-scheme: only light;
|
||||||
|
|
||||||
|
|
||||||
/*styles for the color picker*/
|
/*styles for the color picker*/
|
||||||
--tool-cool-color-picker-btn-bg: transparent;
|
--tool-cool-color-picker-btn-bg: transparent;
|
||||||
--tool-cool-color-picker-btn-border-color: transparent;
|
--tool-cool-color-picker-btn-border-color: transparent;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
@@ -170,12 +169,12 @@ code {
|
|||||||
|
|
||||||
#bg1 {
|
#bg1 {
|
||||||
background-image: url(backgrounds/tavern1.jpg);
|
background-image: url(backgrounds/tavern1.jpg);
|
||||||
z-index: 0;
|
z-index: -2;
|
||||||
}
|
}
|
||||||
|
|
||||||
#bg_custom {
|
#bg_custom {
|
||||||
background-image: none;
|
background-image: none;
|
||||||
z-index: 1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*TOPPER margin*/
|
/*TOPPER margin*/
|
||||||
@@ -267,7 +266,7 @@ code {
|
|||||||
-webkit-backdrop-filter: blur(var(--SmartThemeBlurStrength));
|
-webkit-backdrop-filter: blur(var(--SmartThemeBlurStrength));
|
||||||
text-shadow: #000 0 0 3px;
|
text-shadow: #000 0 0 3px;
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
transition: all 1s ease-in-out;
|
/* transition: all 1s ease-in-out; */
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
}
|
}
|
||||||
@@ -280,9 +279,8 @@ code {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#send_form {
|
#send_form {
|
||||||
display: grid;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
grid-template-columns: 40px auto 40px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0 auto 0 auto;
|
margin: 0 auto 0 auto;
|
||||||
border: 1px solid var(--grey30a);
|
border: 1px solid var(--grey30a);
|
||||||
@@ -645,6 +643,7 @@ select {
|
|||||||
font-family: "Noto Sans", "Noto Color Emoji", sans-serif;
|
font-family: "Noto Sans", "Noto Color Emoji", sans-serif;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
text-shadow: #000 0 0 3px;
|
text-shadow: #000 0 0 3px;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#send_textarea::placeholder,
|
#send_textarea::placeholder,
|
||||||
@@ -3320,9 +3319,9 @@ body.waifuMode .expression-holder {
|
|||||||
max-height: 90vh;
|
max-height: 90vh;
|
||||||
max-width: 90vw;
|
max-width: 90vw;
|
||||||
height: 90vh;
|
height: 90vh;
|
||||||
width: 90vw;
|
width: fit-content;
|
||||||
position: absolute;
|
position: unset;
|
||||||
left: 5vw;
|
margin: 0 auto;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
filter: drop-shadow(2px 2px 2px #51515199);
|
filter: drop-shadow(2px 2px 2px #51515199);
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
@@ -3557,6 +3556,12 @@ body.movingUI #expression-holder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1000px) and (orientation: landscape) {
|
||||||
|
body.waifuMode img.expression {
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 450px) {
|
@media screen and (max-width: 450px) {
|
||||||
.drawer25pWidth {
|
.drawer25pWidth {
|
||||||
flex-basis: max(calc(100% / 2 - 10px), 200px);
|
flex-basis: max(calc(100% / 2 - 10px), 200px);
|
||||||
|
BIN
public/webfonts/fa-solid-900.ttf
Normal file
BIN
public/webfonts/fa-solid-900.ttf
Normal file
Binary file not shown.
34
readme.md
34
readme.md
@@ -2,24 +2,15 @@
|
|||||||
## Based on a fork of TavernAI 1.2.8
|
## Based on a fork of TavernAI 1.2.8
|
||||||
### Brought to you by Cohee and RossAscends
|
### Brought to you by Cohee and RossAscends
|
||||||
|
|
||||||
|
NOTE: We have added [a FAQ](faq.md) to answer most of your questions and help you get started.
|
||||||
|
|
||||||
### What is SillyTavern or TavernAI?
|
### 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.
|
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.
|
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?
|
### 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.
|
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).
|
||||||
|
|
||||||
### I'm new to all this. I just want to have a good time easily. What is the best AI backend to use?
|
|
||||||
The most advanced/intelligent AI backend for roleplaying is to pay for OpenAI's GPT API. It's also among the easiest to use. Objectively, GPT is streets ahead of all other backends. However, OpenAI logs all your activity, and your account MAY be banned in the future if you violate their policies (e.g. on adult content). However, there are no reports of anyone being banned yet.
|
|
||||||
People who value privacy more tend to run a self-hosted AI backend like KoboldAI. Self-hosted backends do not log, but they are much less capable of roleplaying.
|
|
||||||
|
|
||||||
#### OpenAI bots are too horny, aggressive, or you want to try something new? 🤔
|
|
||||||
|
|
||||||
Try some other Jailbreak / NSFW prompts from this community-maintained list:
|
|
||||||
|
|
||||||
https://rentry.org/GPTJailbreakPrompting
|
|
||||||
|
|
||||||
|
|
||||||
### Do I need a powerful PC to run Tavern?
|
### 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.
|
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.
|
||||||
@@ -70,7 +61,8 @@ https://rentry.org/TAI_Termux
|
|||||||
| 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"> |
|
| 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"> |
|
| 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"> |
|
| 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 | 
|
| 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"> |
|
||||||
|
|
||||||
## UI/CSS/Quality of Life tweaks by RossAscends
|
## UI/CSS/Quality of Life tweaks by RossAscends
|
||||||
|
|
||||||
@@ -134,6 +126,17 @@ Restart your TAI server.
|
|||||||
|
|
||||||
You will now be able to connect from other devices.
|
You will now be able to connect from other devices.
|
||||||
|
|
||||||
|
### Managing whitelisted IPs
|
||||||
|
|
||||||
|
You can add or remove whitelisted IPs by editing the `whitelist` array in `config.conf`. You can also provide a `whitelist.txt` file in the same directory as `config.conf` with one IP address per line like:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
192.168.0.1
|
||||||
|
192.168.0.2
|
||||||
|
```
|
||||||
|
|
||||||
|
The `whitelist` array in `config.conf` will be ignored if `whitelist.txt` exists.
|
||||||
|
|
||||||
***Disclaimer: Anyone else who knows your IP address and TAI port number will be able to connect as well***
|
***Disclaimer: Anyone else who knows your IP address and TAI port number will be able to connect as well***
|
||||||
|
|
||||||
To connect over wifi you'll need your PC's local wifi IP address
|
To connect over wifi you'll need your PC's local wifi IP address
|
||||||
@@ -149,7 +152,7 @@ Try enabling the No Blur Effect (Fast UI) mode on the User settings panel.
|
|||||||
### DO's
|
### DO's
|
||||||
|
|
||||||
1. Send pull requests
|
1. Send pull requests
|
||||||
2. Send feature suggestions and issue reports using establised templates
|
2. Send feature suggestions and issue reports using established templates
|
||||||
3. Read the readme file and built-in documentation before asking anything
|
3. Read the readme file and built-in documentation before asking anything
|
||||||
|
|
||||||
### DONT's
|
### DONT's
|
||||||
@@ -179,3 +182,6 @@ Contact us on:
|
|||||||
* GraphQL files for poe: https://github.com/muharamdani/poe (ISC License)
|
* GraphQL files for poe: https://github.com/muharamdani/poe (ISC License)
|
||||||
* KoboldAI Presets from KAI Lite: https://lite.koboldai.net/
|
* KoboldAI Presets from KAI Lite: https://lite.koboldai.net/
|
||||||
* Noto Sans font by Google (OFL license)
|
* 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)
|
||||||
|
* Linux startup script by AlpinDale
|
||||||
|
* Thanks paniphons for providing a FAQ document
|
||||||
|
136
server.js
136
server.js
@@ -1,3 +1,9 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
// change all relative paths
|
||||||
|
const process = require('process')
|
||||||
|
process.chdir(__dirname)
|
||||||
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const compression = require('compression');
|
const compression = require('compression');
|
||||||
const app = express();
|
const app = express();
|
||||||
@@ -29,9 +35,19 @@ const ExifReader = require('exifreader');
|
|||||||
const exif = require('piexifjs');
|
const exif = require('piexifjs');
|
||||||
const webp = require('webp-converter');
|
const webp = require('webp-converter');
|
||||||
|
|
||||||
const config = require(path.join(process.cwd(), './config.conf'));
|
const config = require(path.join(__dirname, './config.conf'));
|
||||||
const server_port = process.env.SILLY_TAVERN_PORT || config.port;
|
const server_port = process.env.SILLY_TAVERN_PORT || config.port;
|
||||||
const whitelist = config.whitelist;
|
|
||||||
|
const whitelistPath = path.join(__dirname, "./whitelist.txt");
|
||||||
|
let whitelist = config.whitelist;
|
||||||
|
|
||||||
|
if (fs.existsSync(whitelistPath)) {
|
||||||
|
try {
|
||||||
|
let whitelistTxt = fs.readFileSync(whitelistPath, 'utf-8');
|
||||||
|
whitelist = whitelistTxt.split("\n").filter(ip => ip).map(ip => ip.trim());
|
||||||
|
} catch (e) { }
|
||||||
|
}
|
||||||
|
|
||||||
const whitelistMode = config.whitelistMode;
|
const whitelistMode = config.whitelistMode;
|
||||||
const autorun = config.autorun;
|
const autorun = config.autorun;
|
||||||
const enableExtensions = config.enableExtensions;
|
const enableExtensions = config.enableExtensions;
|
||||||
@@ -173,8 +189,8 @@ app.use(function (req, res, next) { //Security
|
|||||||
|
|
||||||
//clientIp = req.connection.remoteAddress.split(':').pop();
|
//clientIp = req.connection.remoteAddress.split(':').pop();
|
||||||
if (whitelistMode === true && !whitelist.includes(clientIp)) {
|
if (whitelistMode === true && !whitelist.includes(clientIp)) {
|
||||||
console.log('Forbidden: Connection attempt from ' + clientIp + '. If you are attempting to connect, please add your IP address in whitelist or disable whitelist mode in config.conf in root of TavernAI folder.\n');
|
console.log('Forbidden: Connection attempt from ' + clientIp + '. If you are attempting to connect, please add your IP address in whitelist or disable whitelist mode in config.conf in root of SillyTavern folder.\n');
|
||||||
return res.status(403).send('<b>Forbidden</b>: Connection attempt from <b>' + clientIp + '</b>. If you are attempting to connect, please add your IP address in whitelist or disable whitelist mode in config.conf in root of TavernAI folder.');
|
return res.status(403).send('<b>Forbidden</b>: Connection attempt from <b>' + clientIp + '</b>. If you are attempting to connect, please add your IP address in whitelist or disable whitelist mode in config.conf in root of SillyTavern folder.');
|
||||||
}
|
}
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
@@ -201,7 +217,7 @@ app.use((req, res, next) => {
|
|||||||
app.use(express.static(__dirname + "/public", { refresh: true }));
|
app.use(express.static(__dirname + "/public", { refresh: true }));
|
||||||
|
|
||||||
app.use('/backgrounds', (req, res) => {
|
app.use('/backgrounds', (req, res) => {
|
||||||
const filePath = decodeURIComponent(path.join(process.cwd(), 'public/backgrounds', req.url.replace(/%20/g, ' ')));
|
const filePath = decodeURIComponent(path.join(__dirname, 'public/backgrounds', req.url.replace(/%20/g, ' ')));
|
||||||
fs.readFile(filePath, (err, data) => {
|
fs.readFile(filePath, (err, data) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
res.status(404).send('File not found');
|
res.status(404).send('File not found');
|
||||||
@@ -213,7 +229,7 @@ app.use('/backgrounds', (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.use('/characters', (req, res) => {
|
app.use('/characters', (req, res) => {
|
||||||
const filePath = decodeURIComponent(path.join(process.cwd(), charactersPath, req.url.replace(/%20/g, ' ')));
|
const filePath = decodeURIComponent(path.join(__dirname, charactersPath, req.url.replace(/%20/g, ' ')));
|
||||||
fs.readFile(filePath, (err, data) => {
|
fs.readFile(filePath, (err, data) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
res.status(404).send('File not found');
|
res.status(404).send('File not found');
|
||||||
@@ -357,6 +373,10 @@ app.post("/generate_textgenerationwebui", jsonParser, async function (request, r
|
|||||||
|
|
||||||
if (!!request.header('X-Response-Streaming')) {
|
if (!!request.header('X-Response-Streaming')) {
|
||||||
const fn_index = Number(request.header('X-Gradio-Streaming-Function'));
|
const fn_index = Number(request.header('X-Gradio-Streaming-Function'));
|
||||||
|
let isStreamingStopped = false;
|
||||||
|
request.socket.on('close', function () {
|
||||||
|
isStreamingStopped = true;
|
||||||
|
});
|
||||||
|
|
||||||
response_generate.writeHead(200, {
|
response_generate.writeHead(200, {
|
||||||
'Content-Type': 'text/plain;charset=utf-8',
|
'Content-Type': 'text/plain;charset=utf-8',
|
||||||
@@ -394,6 +414,12 @@ app.post("/generate_textgenerationwebui", jsonParser, async function (request, r
|
|||||||
});
|
});
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
if (isStreamingStopped) {
|
||||||
|
console.error('Streaming stopped by user. Closing websocket...');
|
||||||
|
websocket.close();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (websocket.readyState == 0 || websocket.readyState == 1 || websocket.readyState == 2) {
|
if (websocket.readyState == 0 || websocket.readyState == 1 || websocket.readyState == 2) {
|
||||||
await delay(50);
|
await delay(50);
|
||||||
yield text;
|
yield text;
|
||||||
@@ -414,7 +440,7 @@ app.post("/generate_textgenerationwebui", jsonParser, async function (request, r
|
|||||||
|
|
||||||
let result = JSON.parse(request.body.data)[0];
|
let result = JSON.parse(request.body.data)[0];
|
||||||
let prompt = result;
|
let prompt = result;
|
||||||
let stopping_strings = JSON.parse(request.body.data)[1].custom_stopping_strings;
|
let stopping_strings = JSON.parse(request.body.data)[1].stopping_strings;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for await (const text of readWebsocket()) {
|
for await (const text of readWebsocket()) {
|
||||||
@@ -1578,18 +1604,18 @@ app.post("/importchat", urlencodedParser, function (request, response) {
|
|||||||
|
|
||||||
const errors = [];
|
const errors = [];
|
||||||
newChats.forEach(chat => fs.writeFile(
|
newChats.forEach(chat => fs.writeFile(
|
||||||
chatsPath + avatar_url + '/' + ch_name + ' - ' + humanizedISO8601DateTime() + ' imported.jsonl',
|
chatsPath + avatar_url + '/' + ch_name + ' - ' + humanizedISO8601DateTime() + ' imported.jsonl',
|
||||||
chat.map(JSON.stringify).join('\n'),
|
chat.map(JSON.stringify).join('\n'),
|
||||||
'utf8',
|
'utf8',
|
||||||
(err) => err ?? errors.push(err)
|
(err) => err ?? errors.push(err)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (0 < errors.length) {
|
if (0 < errors.length) {
|
||||||
response.send('Errors occurred while writing character files. Errors: ' + JSON.stringify(errors));
|
response.send('Errors occurred while writing character files. Errors: ' + JSON.stringify(errors));
|
||||||
}
|
}
|
||||||
|
|
||||||
response.send({res: true});
|
response.send({ res: true });
|
||||||
} else {
|
} else {
|
||||||
response.send({ error: true });
|
response.send({ error: true });
|
||||||
}
|
}
|
||||||
@@ -1820,7 +1846,7 @@ app.post('/deletegroup', jsonParser, async (request, response) => {
|
|||||||
|
|
||||||
const POE_DEFAULT_BOT = 'a2';
|
const POE_DEFAULT_BOT = 'a2';
|
||||||
|
|
||||||
async function getPoeClient(token, useCache=false) {
|
async function getPoeClient(token, useCache = false) {
|
||||||
let client = new poe.Client(false, useCache);
|
let client = new poe.Client(false, useCache);
|
||||||
await client.init(token);
|
await client.init(token);
|
||||||
return client;
|
return client;
|
||||||
@@ -1885,6 +1911,12 @@ app.post('/generate_poe', jsonParser, async (request, response) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (streaming) {
|
if (streaming) {
|
||||||
|
let isStreamingStopped = false;
|
||||||
|
request.socket.on('close', function () {
|
||||||
|
isStreamingStopped = true;
|
||||||
|
client.abortController.abort();
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
response.writeHead(200, {
|
response.writeHead(200, {
|
||||||
'Content-Type': 'text/plain;charset=utf-8',
|
'Content-Type': 'text/plain;charset=utf-8',
|
||||||
@@ -1894,6 +1926,11 @@ app.post('/generate_poe', jsonParser, async (request, response) => {
|
|||||||
|
|
||||||
let reply = '';
|
let reply = '';
|
||||||
for await (const mes of client.send_message(bot, prompt)) {
|
for await (const mes of client.send_message(bot, prompt)) {
|
||||||
|
if (isStreamingStopped) {
|
||||||
|
console.error('Streaming stopped by user. Closing websocket...');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
let newText = mes.text.substring(reply.length);
|
let newText = mes.text.substring(reply.length);
|
||||||
reply = mes.text;
|
reply = mes.text;
|
||||||
response.write(newText);
|
response.write(newText);
|
||||||
@@ -1933,6 +1970,35 @@ app.get('/discover_extensions', jsonParser, function (_, response) {
|
|||||||
return response.send(extensions);
|
return response.send(extensions);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.get('/get_sprites', jsonParser, function (request, response) {
|
||||||
|
const name = request.query.name;
|
||||||
|
const spritesPath = path.join(directories.characters, name);
|
||||||
|
let sprites = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(spritesPath) && fs.statSync(spritesPath).isDirectory()) {
|
||||||
|
sprites = fs.readdirSync(spritesPath)
|
||||||
|
.filter(file => {
|
||||||
|
const mimeType = mime.lookup(file);
|
||||||
|
return mimeType && mimeType.startsWith('image/');
|
||||||
|
})
|
||||||
|
.map((file) => {
|
||||||
|
const pathToSprite = path.join(spritesPath, file);
|
||||||
|
return {
|
||||||
|
label: path.parse(pathToSprite).name.toLowerCase(),
|
||||||
|
path: `/characters/${name}/${file}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
return response.send(sprites);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function getThumbnailFolder(type) {
|
function getThumbnailFolder(type) {
|
||||||
let thumbnailFolder;
|
let thumbnailFolder;
|
||||||
|
|
||||||
@@ -2092,10 +2158,45 @@ app.post("/getstatus_openai", jsonParser, function (request, response_getstatus_
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Shamelessly stolen from Agnai
|
||||||
|
app.post("/openai_usage", jsonParser, async function (_, response) {
|
||||||
|
if (!request.body) return response.sendStatus(400);
|
||||||
|
const key = request.body.key;
|
||||||
|
const api_url = new URL(request.body.reverse_proxy || api_openai).toString();
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${key}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const date = new Date();
|
||||||
|
date.setDate(1);
|
||||||
|
const start_date = date.toISOString().slice(0, 10);
|
||||||
|
|
||||||
|
date.setMonth(date.getMonth() + 1);
|
||||||
|
const end_date = date.toISOString().slice(0, 10);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await getAsync(
|
||||||
|
`${api_url}/dashboard/billing/usage?start_date=${start_date}&end_date=${end_date}`,
|
||||||
|
{ headers },
|
||||||
|
);
|
||||||
|
return response.send(res);
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return response.sendStatus(400);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
app.post("/generate_openai", jsonParser, function (request, response_generate_openai) {
|
app.post("/generate_openai", jsonParser, function (request, response_generate_openai) {
|
||||||
if (!request.body) return response_generate_openai.sendStatus(400);
|
if (!request.body) return response_generate_openai.sendStatus(400);
|
||||||
const api_url = new URL(request.body.reverse_proxy || api_openai).toString();
|
const api_url = new URL(request.body.reverse_proxy || api_openai).toString();
|
||||||
|
|
||||||
|
const controller = new AbortController();
|
||||||
|
request.socket.on('close', function () {
|
||||||
|
controller.abort();
|
||||||
|
});
|
||||||
|
|
||||||
console.log(request.body);
|
console.log(request.body);
|
||||||
const config = {
|
const config = {
|
||||||
method: 'post',
|
method: 'post',
|
||||||
@@ -2114,7 +2215,8 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
|
|||||||
"frequency_penalty": request.body.frequency_penalty,
|
"frequency_penalty": request.body.frequency_penalty,
|
||||||
"stop": request.body.stop,
|
"stop": request.body.stop,
|
||||||
"logit_bias": request.body.logit_bias
|
"logit_bias": request.body.logit_bias
|
||||||
}
|
},
|
||||||
|
signal: controller.signal,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (request.body.stream)
|
if (request.body.stream)
|
||||||
@@ -2264,7 +2366,7 @@ app.listen(server_port, (listen ? '0.0.0.0' : '127.0.0.1'), async function () {
|
|||||||
|
|
||||||
console.log('Launching...');
|
console.log('Launching...');
|
||||||
if (autorun) open('http://127.0.0.1:' + server_port);
|
if (autorun) open('http://127.0.0.1:' + server_port);
|
||||||
console.log('TavernAI started: http://127.0.0.1:' + server_port);
|
console.log('SillyTavern started: http://127.0.0.1:' + server_port);
|
||||||
if (fs.existsSync('public/characters/update.txt') && !is_colab) {
|
if (fs.existsSync('public/characters/update.txt') && !is_colab) {
|
||||||
convertStage1();
|
convertStage1();
|
||||||
}
|
}
|
||||||
@@ -2494,4 +2596,4 @@ function ensurePublicDirectoriesExist() {
|
|||||||
fs.mkdirSync(dir, { recursive: true });
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user