mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Compare commits
359 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
d6fd5455e6 | ||
|
f3c9f57cb9 | ||
|
3fcf232537 | ||
|
6be18e4212 | ||
|
9e3c55805f | ||
|
9ab7265053 | ||
|
25e3005de9 | ||
|
2b8f5d14e9 | ||
|
13deac2527 | ||
|
d566be077d | ||
|
7fe2ea31b9 | ||
|
d153488690 | ||
|
a61a8f9495 | ||
|
a904260614 | ||
|
5e24beef58 | ||
|
47e4d5925b | ||
|
52b994a45b | ||
|
ca2542d81a | ||
|
1fe2e4032a | ||
|
84cfe1c706 | ||
|
4afd616099 | ||
|
44bf31e602 | ||
|
6d33e44519 | ||
|
16e8c7a3c8 | ||
|
0c3f3f952d | ||
|
bef7d1492b | ||
|
6e15f7474f | ||
|
7a33042ea9 | ||
|
2e80de230e | ||
|
225bd5aa0a | ||
|
b14a85a96b | ||
|
febef5dfba | ||
|
9aab388531 | ||
|
549fb19676 | ||
|
8eb82cdcd9 | ||
|
c156e32ec7 | ||
|
002dbae8c5 | ||
|
bd9e739de2 | ||
|
5dbfe209f6 | ||
|
229ec5f255 | ||
|
8d18f2a80a | ||
|
43a28fdb05 | ||
|
37219c3370 | ||
|
990a5faf7d | ||
|
080ecec5f2 | ||
|
715a6f1bff | ||
|
e01a2c3bcf | ||
|
6c33dff0ba | ||
|
596cd1762f | ||
|
3b4f8811e7 | ||
|
63bd4cd499 | ||
|
822e4b88f5 | ||
|
59b92d4356 | ||
|
309a2ed564 | ||
|
fcef55d900 | ||
|
cba2feb875 | ||
|
fe8db4ded8 | ||
|
8cda073d00 | ||
|
c134aed9f5 | ||
|
32441aa33e | ||
|
6be7d5704e | ||
|
1d2dc19359 | ||
|
dd028a9564 | ||
|
e6761f7293 | ||
|
8547f362c5 | ||
|
8251d15455 | ||
|
27c780c18b | ||
|
3353fe572c | ||
|
e2bbc7fbcf | ||
|
f532192726 | ||
|
757e9b672a | ||
|
0bfa9f0e29 | ||
|
0da4bce378 | ||
|
16915ae6a5 | ||
|
ad9599c2fc | ||
|
8687932896 | ||
|
10a5836893 | ||
|
f75930a75d | ||
|
d813ec4ef3 | ||
|
89f905f0e2 | ||
|
5af6874b5f | ||
|
60586e7720 | ||
|
4d3677dc5d | ||
|
c6f1ec696a | ||
|
9f5bd9d728 | ||
|
466bcd8833 | ||
|
05fd6d4a6c | ||
|
5d1edf7456 | ||
|
b9a067b79f | ||
|
6981151c8f | ||
|
650e7b0588 | ||
|
e9466916da | ||
|
bc7d7ee3ff | ||
|
2bbc40a796 | ||
|
0c55d36a2b | ||
|
fcc6448d7a | ||
|
215e34bb52 | ||
|
29e0a8335b | ||
|
5ab4179920 | ||
|
ebb93451b2 | ||
|
084d17dc19 | ||
|
2722813efb | ||
|
e81b867676 | ||
|
2dabcc28a5 | ||
|
3b99f7839b | ||
|
7c6c2ee8b6 | ||
|
52a2cee73c | ||
|
991d437749 | ||
|
8168a9205a | ||
|
332648973f | ||
|
fc8553a140 | ||
|
048d65c1e1 | ||
|
322ab9b47a | ||
|
0f0647c6e4 | ||
|
674a15b842 | ||
|
d0ab763d2e | ||
|
2f8b624578 | ||
|
e4e8cdfca5 | ||
|
df35fb0775 | ||
|
fca732c933 | ||
|
c18008725d | ||
|
38931e7a2f | ||
|
667cbf6f0f | ||
|
931fffaa5c | ||
|
f06ca28bbf | ||
|
ad11ec8d00 | ||
|
a1eb2b794e | ||
|
4ad328029e | ||
|
d66542e88e | ||
|
893d1fa9e4 | ||
|
4a9d9b69e9 | ||
|
ebf51f3a17 | ||
|
7bec130bf7 | ||
|
914282faf0 | ||
|
1d8ecacd8b | ||
|
a0814defff | ||
|
d410118cc4 | ||
|
5084ae9f9a | ||
|
93b1774135 | ||
|
0a5c226af6 | ||
|
2cde62f618 | ||
|
136ba40956 | ||
|
7fe758d697 | ||
|
762684ffea | ||
|
2b5a028af4 | ||
|
487b36a326 | ||
|
ea2c7973a9 | ||
|
2d34b54874 | ||
|
696f9083f3 | ||
|
db08d4eab3 | ||
|
2f2f88dedd | ||
|
a512e3bec0 | ||
|
06f2c920f0 | ||
|
9903e85a66 | ||
|
9949d5695c | ||
|
515a0af1b4 | ||
|
f5ba78be81 | ||
|
10e87dd5ca | ||
|
5db69d1ce0 | ||
|
a9009725ce | ||
|
d59174da77 | ||
|
679143967d | ||
|
59e833b6cc | ||
|
2e9bccf9e9 | ||
|
54f472a750 | ||
|
a0dbee6749 | ||
|
69fdb9090f | ||
|
43e5849015 | ||
|
92775e459c | ||
|
748bee74cb | ||
|
3cdec1cea3 | ||
|
76b7e24614 | ||
|
9737fda9ae | ||
|
06f580ed29 | ||
|
553a95fd39 | ||
|
3e30fb5d14 | ||
|
847961861f | ||
|
59dba15a4f | ||
|
2aa0c5d707 | ||
|
976a8fd65c | ||
|
5798c98f41 | ||
|
187a0925b0 | ||
|
45047fc6b2 | ||
|
9d5af39682 | ||
|
b6fbe41f93 | ||
|
47a5c9e9f6 | ||
|
c9fa19e8dd | ||
|
89a1378397 | ||
|
1739af3ef6 | ||
|
c1b9a30087 | ||
|
ca8b06f4cb | ||
|
68f967ea78 | ||
|
7354003db1 | ||
|
b2e541c6d9 | ||
|
7321b37799 | ||
|
9532ad4e5a | ||
|
7118b430d5 | ||
|
961139304d | ||
|
efe4a974be | ||
|
92127615e5 | ||
|
deb2efc16e | ||
|
9bbaa85a3b | ||
|
04cfedea7c | ||
|
16fd92b1a3 | ||
|
7984e3b818 | ||
|
da8beeb503 | ||
|
612db28bcb | ||
|
506aeb2e40 | ||
|
26ac519c55 | ||
|
bbec184d17 | ||
|
951e22ac8e | ||
|
861e0d017e | ||
|
f6526bbb4c | ||
|
d004a3141e | ||
|
bd74939a55 | ||
|
c0286150ed | ||
|
fc9c90c4ee | ||
|
2836704c4e | ||
|
71201377ef | ||
|
9de7db8f2d | ||
|
31057e1e81 | ||
|
66de4a1e09 | ||
|
4cc3d335a8 | ||
|
09eea3c8cd | ||
|
747f7829fd | ||
|
ab90d6ec3d | ||
|
3ce14883b9 | ||
|
f360706227 | ||
|
ef9b7187dc | ||
|
869e02dd42 | ||
|
e78abf9269 | ||
|
23287597ee | ||
|
9f0530f422 | ||
|
275f187719 | ||
|
97e1585152 | ||
|
8a2506d8a3 | ||
|
a79bae5975 | ||
|
01b4e1dae3 | ||
|
4fd714a1ee | ||
|
84e44d7c0a | ||
|
fed47d7477 | ||
|
2302785242 | ||
|
6cbfb56fff | ||
|
59c699c999 | ||
|
1d4746b743 | ||
|
82a09d2feb | ||
|
c873a6b04c | ||
|
e9f7ea16ce | ||
|
786ae619cb | ||
|
ad779129d3 | ||
|
e7af6892fb | ||
|
fa9df8f22e | ||
|
1672824416 | ||
|
c096a55697 | ||
|
d4332aa7ec | ||
|
2f497cf25b | ||
|
7d472f00f7 | ||
|
1e5f789f59 | ||
|
1f14c3669d | ||
|
40e2af4c73 | ||
|
bd34cab6e8 | ||
|
85876e9377 | ||
|
e5f37ee073 | ||
|
267db5166f | ||
|
0e45450912 | ||
|
4dac2126bf | ||
|
4230f3881d | ||
|
408f83804d | ||
|
4d299916be | ||
|
4b8711c8f8 | ||
|
8d6e6de200 | ||
|
df4586811d | ||
|
012f0237db | ||
|
23a6064a55 | ||
|
a37922ad59 | ||
|
2a235b7889 | ||
|
e4a6bdb389 | ||
|
4a29072e1c | ||
|
36d0244be4 | ||
|
fda152cef0 | ||
|
3723ae840f | ||
|
a513434b5f | ||
|
ec05937dd4 | ||
|
0ec9198ef5 | ||
|
64bba40c41 | ||
|
350e2108e2 | ||
|
5fa5edffba | ||
|
df184bd46a | ||
|
f0d0f38c4f | ||
|
2663a8370f | ||
|
3b66310dd2 | ||
|
9c28126ccd | ||
|
fb97d95dae | ||
|
2d6ed116e6 | ||
|
114d756a68 | ||
|
8c710a08a3 | ||
|
c5c921b0d6 | ||
|
3c68a4e2a0 | ||
|
72488b5900 | ||
|
b970bde972 | ||
|
10c836fcbc | ||
|
d979dd263a | ||
|
4c51b1ffe1 | ||
|
0490ca25b0 | ||
|
5ce41342c0 | ||
|
052089b3c0 | ||
|
21bb5d7808 | ||
|
f51af31850 | ||
|
7e975e9df0 | ||
|
56656b95cf | ||
|
2d97b4bd0a | ||
|
81d9cead5c | ||
|
511f762e54 | ||
|
04a645141c | ||
|
9ef8f0b069 | ||
|
e12242f44f | ||
|
976248b665 | ||
|
84d9113ed3 | ||
|
a43f99b492 | ||
|
28ba84ea6f | ||
|
93876b8189 | ||
|
040c4a8894 | ||
|
22a5def618 | ||
|
58a6ccd4a5 | ||
|
cefc10b405 | ||
|
6d649c716d | ||
|
ba545e44e3 | ||
|
d59024b4a5 | ||
|
6a5b44b3b3 | ||
|
75090c4fa4 | ||
|
b0db3686b1 | ||
|
468aafb384 | ||
|
b85605cac8 | ||
|
2edebec52c | ||
|
64fcb4b1f0 | ||
|
feecb1fa27 | ||
|
2ae467d14f | ||
|
0db9cec7c4 | ||
|
0c0baecb5f | ||
|
b34478f800 | ||
|
3b51252e9e | ||
|
3a8f4e4f76 | ||
|
a8341f7b57 | ||
|
db7578be8e | ||
|
536052af3d | ||
|
f4cc3932da | ||
|
c890da2877 | ||
|
b5d1ed048d | ||
|
ed0272efa6 | ||
|
7686ac0b28 | ||
|
bec6227aaf | ||
|
6a2a0efc84 | ||
|
b09ea054df | ||
|
024784e0b0 | ||
|
329158349f | ||
|
62d5f20590 | ||
|
e420c96e77 | ||
|
7af5a6ee5d | ||
|
e91cbe009f |
@@ -3,4 +3,5 @@ node_modules
|
||||
npm-debug.log
|
||||
readme*
|
||||
Start.bat
|
||||
/dist
|
||||
/dist
|
||||
/backups/
|
||||
|
56
.github/readme.md
vendored
56
.github/readme.md
vendored
@@ -4,13 +4,13 @@ Mobile-friendly, Multi-API (KoboldAI/CPP, Horde, NovelAI, Ooba, OpenAI+proxies,
|
||||
|
||||
Based on a fork of TavernAI 1.2.8
|
||||
|
||||
### Brought to you by Cohee, RossAscends and the SillyTavern community
|
||||
### Brought to you by Cohee, RossAscends, and the SillyTavern community
|
||||
|
||||
NOTE: We have created a [Documentation website](https://docs.sillytavern.app/) to answer most of your questions and help you get started.
|
||||
|
||||
### What is SillyTavern or TavernAI?
|
||||
|
||||
Tavern is a user interface you can install on your computer (and Android phones) that allows you to interact with text generation AIs and chat/roleplay with characters you or the community create.
|
||||
SillyTavern is a 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.
|
||||
|
||||
@@ -53,7 +53,7 @@ Get support, share favorite characters and prompts:
|
||||
|
||||
Get in touch with the developers directly:
|
||||
|
||||
* Discord: Cohee#1207 or RossAscends#1779
|
||||
* Discord: cohee or rossascends
|
||||
* Reddit: /u/RossAscends or /u/sillylossy
|
||||
* [Post a GitHub issue](https://github.com/SillyTavern/SillyTavern/issues)
|
||||
|
||||
@@ -64,36 +64,36 @@ Get in touch with the developers directly:
|
||||
* Group chats: multi-bot rooms for characters to talk to you or each other
|
||||
* Chat bookmarks / branching (duplicates the dialogue in its current state)
|
||||
* Advanced KoboldAI / TextGen generation settings with a lot of community-made presets
|
||||
* World Info support: create a rich lore or save tokens on your character card
|
||||
* World Info support: create rich lore or save tokens on your character card
|
||||
* Window AI browser extension support (run models like Claude, GPT 4): https://windowai.io/
|
||||
* [Oobabooga's TextGen WebUI](https://github.com/oobabooga/text-generation-webui) API connection
|
||||
* [AI Horde](https://horde.koboldai.net/) connection
|
||||
* [Poe.com](https://poe.com) (ChatGPT / Claude) connection
|
||||
* Soft prompts selector for KoboldAI
|
||||
* Prompt generation formatting tweaking
|
||||
* webp character card interoperability (PNG is still an internal format)
|
||||
|
||||
## Extensions
|
||||
|
||||
SillyTavern has an extensibility support, with some additional AI modules hosted via [SillyTavern Extras API](https://github.com/SillyTavern/SillyTavern-extras)
|
||||
SillyTavern has extensibility support, with some additional AI modules hosted via [SillyTavern Extras API](https://github.com/SillyTavern/SillyTavern-extras)
|
||||
|
||||
* Author's Note / Character Bias
|
||||
* Character emotional expressions
|
||||
* Character emotional expressions (sprites)
|
||||
* Auto-Summary of the chat history
|
||||
* Sending images to chat, and the AI interpreting the content.
|
||||
* Sending images to chat, and the AI interpreting the content
|
||||
* Stable Diffusion image generation (5 chat-related presets plus 'free mode')
|
||||
* Text-to-speech for AI response messages (via ElevenLabs, Silero, or the OS's System TTS)
|
||||
* ChromaDB vector storage for smarter chat prompt formatting
|
||||
|
||||
Full list of included extenisons and tutorials how to use them can be found on [Wiki](https://github.com/SillyTavern/SillyTavern/wiki).
|
||||
A full list of included extensions and tutorials on how to use them can be found in the [Docs](https://docs.sillytavern.app/extras/extensions/).
|
||||
|
||||
## UI/CSS/Quality of Life tweaks by RossAscends
|
||||
|
||||
* Mobile UI with optimized for iOS, and supports saving a shortcut to home screen and opening in fullscreen mode.
|
||||
* Mobile UI optimized for iOS, and supports saving a shortcut to the home screen and opening in fullscreen mode.
|
||||
* HotKeys
|
||||
* Up = Edit last message in chat
|
||||
* Ctrl+Up = Edit last USER message in chat
|
||||
* Left = swipe left
|
||||
* Right = swipe right (NOTE: swipe hotkeys are disabled when chatbar has something typed into it)
|
||||
* Right = swipe right (NOTE: swipe hotkeys are disabled when the chat bar has something typed into it)
|
||||
* Ctrl+Left = view locally stored variables (in the browser console window)
|
||||
* Enter (with chat bar selected) = send your message to AI
|
||||
* Ctrl+Enter = Regenerate the last AI response
|
||||
@@ -118,7 +118,7 @@ Full list of included extenisons and tutorials how to use them can be found on [
|
||||
* Switch between round or rectangle avatar styles
|
||||
* Have a wider chat window on the desktop
|
||||
* Optional semi-transparent glass-like panels
|
||||
* Customizable page colors for 'main text', 'quoted text' 'italics text'.
|
||||
* Customizable page colors for 'main text', 'quoted text', and 'italics text'.
|
||||
* Customizable UI background color and blur amount
|
||||
|
||||
## Installation
|
||||
@@ -135,8 +135,8 @@ Full list of included extenisons and tutorials how to use them can be found on [
|
||||
|
||||
Installing via Git (recommended for easy updating)
|
||||
|
||||
Easy to follow guide with pretty pictures:
|
||||
<https://docs.alpindale.dev/pygmalion-extras/sillytavern/#windows-installation>
|
||||
An easy-to-follow guide with pretty pictures:
|
||||
<https://docs.sillytavern.app/installation/windows/>
|
||||
|
||||
1. Install [NodeJS](https://nodejs.org/en) (latest LTS version is recommended)
|
||||
2. Install [GitHub Desktop](https://central.github.com/deployments/desktop/desktop/latest/win32)
|
||||
@@ -148,15 +148,15 @@ Easy to follow guide with pretty pictures:
|
||||
* for Main Branch: `git clone https://github.com/SillyTavern/SillyTavern -b main`
|
||||
* for Dev Branch: `git clone https://github.com/SillyTavern/SillyTavern -b dev`
|
||||
|
||||
7. Once everything is cloned, double click `Start.bat` to make NodeJS install its requirements.
|
||||
8. The server will then start, and SillyTavern will popup in your browser.
|
||||
7. Once everything is cloned, double-click `Start.bat` to make NodeJS install its requirements.
|
||||
8. The server will then start, and SillyTavern will pop up in your browser.
|
||||
|
||||
Installing via zip download
|
||||
Installing via ZIP download (discouraged)
|
||||
|
||||
1. Install [NodeJS](https://nodejs.org/en) (latest LTS version is recommended)
|
||||
2. Download the zip from this GitHub repo. (Get the `Source code (zip)` from [Releases](https://github.com/SillyTavern/SillyTavern/releases/latest))
|
||||
3. Unzip it into a folder of your choice
|
||||
4. Run `Start.bat` via double-clicking or in a command line.
|
||||
4. Run `Start.bat` by double-clicking or in a command line.
|
||||
5. Once the server has prepared everything for you, it will open a tab in your browser.
|
||||
|
||||
### Linux
|
||||
@@ -168,7 +168,7 @@ Installing via zip download
|
||||
|
||||
SillyTavern saves your API keys to a `secrets.json` file in the server directory.
|
||||
|
||||
By default they will not be exposed to a frontend after you enter them and reload the page.
|
||||
By default, they will not be exposed to a frontend after you enter them and reload the page.
|
||||
|
||||
In order to enable viewing your keys by clicking a button in the API block:
|
||||
|
||||
@@ -186,9 +186,9 @@ However, it can be used to allow remote connections from anywhere as well.
|
||||
### 1. Managing whitelisted IPs
|
||||
|
||||
* Create a new text file inside your SillyTavern base install folder called `whitelist.txt`.
|
||||
* Open the file in a text editor, add a list of IPs you want to be allowed to connect.
|
||||
* Open the file in a text editor, and add a list of IPs you want to be allowed to connect.
|
||||
|
||||
*Both indidivual IPs, and wildcard IP ranges are accepted. Examples:*
|
||||
*Both individual IPs and wildcard IP ranges are accepted. Examples:*
|
||||
|
||||
```txt
|
||||
192.168.0.1
|
||||
@@ -220,7 +220,7 @@ If the ST-hosting device is on the same wifi network, you will use the ST-host's
|
||||
|
||||
* For Windows: windows button > type `cmd.exe` in the search bar > type `ipconfig` in the console, hit Enter > look for `IPv4` listing.
|
||||
|
||||
If you (or someone else) wants to connect to your hosted ST while not being on the same network, you will need the public IP of your ST-hosting device.
|
||||
If you (or someone else) want to connect to your hosted ST while not being on the same network, you will need the public IP of your ST-hosting device.
|
||||
|
||||
* While using the ST-hosting device, access [this page](https://whatismyipaddress.com/) and look for for `IPv4`. This is what you would use to connect from the remote device.
|
||||
|
||||
@@ -228,7 +228,7 @@ If you (or someone else) wants to connect to your hosted ST while not being on t
|
||||
|
||||
Whatever IP you ended up with for your situation, you will put that IP address and port number into the remote device's web browser.
|
||||
|
||||
A typical address for an ST host on the same wifi network would look like:
|
||||
A typical address for an ST host on the same wifi network would look like this:
|
||||
|
||||
`http://192.168.0.5:8000`
|
||||
|
||||
@@ -238,7 +238,7 @@ Use http:// NOT https://
|
||||
|
||||
We do not recommend doing this, but you can open `config.conf` and change `whitelist` to `false`.
|
||||
|
||||
You must remove (or rename) `whitelist.txt` in the SillyTavern base install folder, if it exists.
|
||||
You must remove (or rename) `whitelist.txt` in the SillyTavern base install folder if it exists.
|
||||
|
||||
This is usually an insecure practice, so we require you to set a username and password when you do this.
|
||||
|
||||
@@ -248,8 +248,8 @@ After restarting your ST server, any device will be able to connect to it, regar
|
||||
|
||||
### Still Unable To Connect?
|
||||
|
||||
* Create an inbound/outbound firewall rule for the port found in `config.conf`. Do NOT mistake this for portforwarding on your router, otherwise someone could find your chat logs and that's a big no-no.
|
||||
* Enable the Private Network profile type in Settings > Network and Internet > Ethernet. This is VERY important for Windows 11, otherwise you would be unable to connect even with the aforementioned firewall rules.
|
||||
* Create an inbound/outbound firewall rule for the port found in `config.conf`. Do NOT mistake this for port-forwarding on your router, otherwise, someone could find your chat logs and that's a big no-no.
|
||||
* Enable the Private Network profile type in Settings > Network and Internet > Ethernet. This is VERY important for Windows 11, otherwise, you would be unable to connect even with the aforementioned firewall rules.
|
||||
|
||||
## Performance issues?
|
||||
|
||||
@@ -271,7 +271,7 @@ Try enabling the No Blur Effect (Fast UI) mode on the User settings panel.
|
||||
|
||||
## Where can I find the old backgrounds?
|
||||
|
||||
We're moving to 100% original content only policy, so old background images have been removed from this repository.
|
||||
We're moving to a 100% original content only policy, so old background images have been removed from this repository.
|
||||
|
||||
You can find them archived here:
|
||||
|
||||
@@ -293,6 +293,8 @@ GNU Affero General Public License for more details.**
|
||||
* Cohee's modifications and derived code: AGPL v3
|
||||
* RossAscends' additions: AGPL v3
|
||||
* Portions of CncAnon's TavernAITurbo mod: Unknown license
|
||||
* kingbri's various commits and suggestions (<https://github.com/bdashore3>)
|
||||
* BlipRanger's miscellaneous UI & extension modifications (<https://github.com/BlipRanger>)
|
||||
* Waifu mode inspired by the work of PepperTaco (<https://github.com/peppertaco/Tavern/>)
|
||||
* Thanks Pygmalion University for being awesome testers and suggesting cool features!
|
||||
* Thanks oobabooga for compiling presets for TextGen
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -20,3 +20,4 @@ whitelist.txt
|
||||
secrets.json
|
||||
/dist
|
||||
poe_device.json
|
||||
/backups/
|
||||
|
@@ -5,3 +5,4 @@ node_modules/
|
||||
secrets.json
|
||||
/dist
|
||||
poe_device.json
|
||||
/backups/
|
||||
|
@@ -1,5 +1,5 @@
|
||||
pushd %~dp0
|
||||
call npm install
|
||||
call npm install --no-audit
|
||||
node server.js
|
||||
pause
|
||||
popd
|
||||
popd
|
||||
|
16
package-lock.json
generated
16
package-lock.json
generated
@@ -1,15 +1,16 @@
|
||||
{
|
||||
"name": "sillytavern",
|
||||
"version": "1.7.0",
|
||||
"version": "1.8.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "sillytavern",
|
||||
"version": "1.7.0",
|
||||
"version": "1.8.0",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@dqbd/tiktoken": "^1.0.2",
|
||||
"@mlc-ai/web-tokenizers": "^0.1.0",
|
||||
"axios": "^1.4.0",
|
||||
"command-exists": "^1.2.9",
|
||||
"compression": "^1",
|
||||
@@ -561,6 +562,11 @@
|
||||
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@mlc-ai/web-tokenizers": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mlc-ai/web-tokenizers/-/web-tokenizers-0.1.0.tgz",
|
||||
"integrity": "sha512-whiQ+40ohtAFoFOGcje1Io7BMr434Wh3hM3nBCWlJMpXxL5Rlig/AH9wjyUPsytKwWTEe7RoYPyXSbFw5Vs6Tw=="
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@@ -3035,9 +3041,9 @@
|
||||
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.5.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz",
|
||||
"integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==",
|
||||
"version": "7.5.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz",
|
||||
"integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
|
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@dqbd/tiktoken": "^1.0.2",
|
||||
"@mlc-ai/web-tokenizers": "^0.1.0",
|
||||
"axios": "^1.4.0",
|
||||
"command-exists": "^1.2.9",
|
||||
"compression": "^1",
|
||||
@@ -48,7 +49,7 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/SillyTavern/SillyTavern.git"
|
||||
},
|
||||
"version": "1.7.0",
|
||||
"version": "1.8.0",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"pkg": "pkg --compress Gzip --no-bytecode --public ."
|
||||
|
22
public/KoboldAI Settings/RecoveredRuins.settings
Normal file
22
public/KoboldAI Settings/RecoveredRuins.settings
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"max_length": 100,
|
||||
"temp": 1,
|
||||
"genamt": 100,
|
||||
"top_k": 0,
|
||||
"top_p": 0.95,
|
||||
"top_a": 0,
|
||||
"typical": 1,
|
||||
"tfs": 1,
|
||||
"rep_pen": 1.1,
|
||||
"rep_pen_range": 600,
|
||||
"rep_pen_slope": 0,
|
||||
"sampler_order": [
|
||||
6,
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5
|
||||
]
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"order": [1, 0, 3]
|
||||
"order": [1, 0, 3],
|
||||
"temperature": 1.07,
|
||||
"max_length": 60,
|
||||
"min_length": 60,
|
||||
@@ -14,4 +14,4 @@
|
||||
"repetition_penalty_frequency": 0,
|
||||
"repetition_penalty_presence": 0,
|
||||
"max_context":2048
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,8 @@
|
||||
"typical_p": 1,
|
||||
"top_a": 0,
|
||||
"tfs": 1,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 4.5,
|
||||
"no_repeat_ngram_size": 2,
|
||||
"penalty_alpha": 0,
|
||||
|
@@ -5,6 +5,8 @@
|
||||
"typical_p": 1,
|
||||
"top_a": 0,
|
||||
"tfs": 1,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0.6,
|
||||
|
@@ -5,6 +5,8 @@
|
||||
"typical_p": 1,
|
||||
"top_a": 0,
|
||||
"tfs": 1,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.2,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
|
@@ -5,6 +5,8 @@
|
||||
"typical_p": 1,
|
||||
"top_a": 0,
|
||||
"tfs": 1,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
|
@@ -5,6 +5,8 @@
|
||||
"typical_p": 0.19,
|
||||
"top_a": 0,
|
||||
"tfs": 1,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.1,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
|
@@ -5,6 +5,8 @@
|
||||
"typical_p": 0.6,
|
||||
"top_a": 0,
|
||||
"tfs": 1,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.1,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
|
@@ -5,6 +5,8 @@
|
||||
"typical_p": 1,
|
||||
"top_a": 0,
|
||||
"tfs": 1,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.18,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
|
@@ -5,6 +5,8 @@
|
||||
"typical_p": 1,
|
||||
"top_a": 0,
|
||||
"tfs": 1,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
|
@@ -5,6 +5,8 @@
|
||||
"typical_p": 1,
|
||||
"top_a": 0,
|
||||
"tfs": 1,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.15,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
|
@@ -6,6 +6,8 @@
|
||||
"rep_pen": 1,
|
||||
"top_a": 0,
|
||||
"tfs": 1,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
|
@@ -5,6 +5,8 @@
|
||||
"typical_p": 1,
|
||||
"top_a": 0,
|
||||
"tfs": 1,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.05,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
|
@@ -5,6 +5,8 @@
|
||||
"typical_p": 1,
|
||||
"top_a": 0,
|
||||
"tfs": 1,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.15,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
|
@@ -5,6 +5,8 @@
|
||||
"typical_p": 1,
|
||||
"top_a": 0,
|
||||
"tfs": 1,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.05,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
|
@@ -5,6 +5,8 @@
|
||||
"typical_p": 1,
|
||||
"top_a": 0,
|
||||
"tfs": 1,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.15,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
|
@@ -5,6 +5,8 @@
|
||||
"typical_p": 1,
|
||||
"top_a": 0,
|
||||
"tfs": 1,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.15,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
|
@@ -5,6 +5,8 @@
|
||||
"typical_p": 1,
|
||||
"top_a": 0,
|
||||
"tfs": 1,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.1,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
|
19
public/TextGen Settings/Prompt Arena (Asterism).settings
Normal file
19
public/TextGen Settings/Prompt Arena (Asterism).settings
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"temp": 1.68,
|
||||
"top_p": 0.17,
|
||||
"top_k": 77,
|
||||
"typical_p": 1,
|
||||
"top_a": 0.42,
|
||||
"tfs": 0.97,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.02,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
"length_penalty": 1,
|
||||
"min_length": 0,
|
||||
"encoder_rep_pen": 1,
|
||||
"do_sample": true,
|
||||
"early_stopping": false
|
||||
}
|
19
public/TextGen Settings/Prompt Arena (Big O).settings
Normal file
19
public/TextGen Settings/Prompt Arena (Big O).settings
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"temp": 0.87,
|
||||
"top_p": 0.99,
|
||||
"top_k": 85,
|
||||
"typical_p": 0.68,
|
||||
"top_a": 0,
|
||||
"tfs": 0.68,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.01,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
"length_penalty": 1,
|
||||
"min_length": 0,
|
||||
"encoder_rep_pen": 1,
|
||||
"do_sample": true,
|
||||
"early_stopping": false
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
{
|
||||
|
||||
"temp": 1.31,
|
||||
"top_p": 0.14,
|
||||
"top_k": 49,
|
||||
"typical_p": 1,
|
||||
"top_a": 0.52,
|
||||
"tfs": 1,
|
||||
"epsilon_cutoff": 1.49,
|
||||
"eta_cutoff": 10.42,
|
||||
"rep_pen": 1.17,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
"length_penalty": 1,
|
||||
"min_length": 0,
|
||||
"encoder_rep_pen": 1,
|
||||
"do_sample": true,
|
||||
"early_stopping": false
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"temp": 0.98,
|
||||
"top_p": 0.37,
|
||||
"top_k": 100,
|
||||
"typical_p": 1,
|
||||
"top_a": 0,
|
||||
"tfs": 1,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.18,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
"length_penalty": 1,
|
||||
"min_length": 0,
|
||||
"encoder_rep_pen": 1,
|
||||
"do_sample": true,
|
||||
"early_stopping": false
|
||||
}
|
19
public/TextGen Settings/Prompt Arena (Shortwave).settings
Normal file
19
public/TextGen Settings/Prompt Arena (Shortwave).settings
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"temp": 1.53,
|
||||
"top_p": 0.64,
|
||||
"top_k": 33,
|
||||
"typical_p": 1,
|
||||
"top_a": 0.04,
|
||||
"tfs": 1,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.07,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
"length_penalty": 1,
|
||||
"min_length": 0,
|
||||
"encoder_rep_pen": 1,
|
||||
"do_sample": true,
|
||||
"early_stopping": false
|
||||
}
|
19
public/TextGen Settings/Prompt Arena (Space Alien).settings
Normal file
19
public/TextGen Settings/Prompt Arena (Space Alien).settings
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"temp": 1.31,
|
||||
"top_p": 0.29,
|
||||
"top_k": 72,
|
||||
"typical_p": 1,
|
||||
"top_a": 0,
|
||||
"tfs": 1,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.09,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
"length_penalty": 1,
|
||||
"min_length": 0,
|
||||
"encoder_rep_pen": 1,
|
||||
"do_sample": true,
|
||||
"early_stopping": false
|
||||
}
|
19
public/TextGen Settings/Prompt Arena (StarChat).settings
Normal file
19
public/TextGen Settings/Prompt Arena (StarChat).settings
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"temp": 0.02,
|
||||
"top_p": 0.95,
|
||||
"top_k": 50,
|
||||
"typical_p": 1,
|
||||
"top_a": 0,
|
||||
"tfs": 1,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
"length_penalty": 1,
|
||||
"min_length": 0,
|
||||
"encoder_rep_pen": 1,
|
||||
"do_sample": true,
|
||||
"early_stopping": false
|
||||
}
|
19
public/TextGen Settings/Prompt Arena (Titanic).settings
Normal file
19
public/TextGen Settings/Prompt Arena (Titanic).settings
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"temp": 1.01,
|
||||
"top_p": 0.21,
|
||||
"top_k": 91,
|
||||
"typical_p": 1,
|
||||
"top_a": 0.75,
|
||||
"tfs": 1,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 10.78,
|
||||
"rep_pen": 1.21,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
"length_penalty": 1,
|
||||
"min_length": 0,
|
||||
"encoder_rep_pen": 1.07,
|
||||
"do_sample": true,
|
||||
"early_stopping": false
|
||||
}
|
19
public/TextGen Settings/Prompt Arena (Yara).settings
Normal file
19
public/TextGen Settings/Prompt Arena (Yara).settings
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"temp": 0.82,
|
||||
"top_p": 0.21,
|
||||
"top_k": 72,
|
||||
"typical_p": 1,
|
||||
"top_a": 0,
|
||||
"tfs": 1,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.19,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
"length_penalty": 1,
|
||||
"min_length": 0,
|
||||
"encoder_rep_pen": 1,
|
||||
"do_sample": true,
|
||||
"early_stopping": false
|
||||
}
|
19
public/TextGen Settings/Prompt Arena (simple-1).settings
Normal file
19
public/TextGen Settings/Prompt Arena (simple-1).settings
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"temp": 0.7,
|
||||
"top_p": 0.9,
|
||||
"top_k": 20,
|
||||
"typical_p": 1,
|
||||
"top_a": 0.75,
|
||||
"tfs": 1,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.15,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
"length_penalty": 1,
|
||||
"min_length": 0,
|
||||
"encoder_rep_pen": 1,
|
||||
"do_sample": true,
|
||||
"early_stopping": false
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"temp": 0.7,
|
||||
"top_p": 1,
|
||||
"top_k": 0,
|
||||
"typical_p": 1,
|
||||
"top_a": 0.2,
|
||||
"tfs": 0.95,
|
||||
"epsilon_cutoff": 0,
|
||||
"eta_cutoff": 0,
|
||||
"rep_pen": 1.15,
|
||||
"no_repeat_ngram_size": 0,
|
||||
"penalty_alpha": 0,
|
||||
"num_beams": 1,
|
||||
"length_penalty": 1,
|
||||
"min_length": 0,
|
||||
"encoder_rep_pen": 1,
|
||||
"do_sample": true,
|
||||
"early_stopping": false
|
||||
}
|
@@ -1,73 +0,0 @@
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
background-color: rgb(36, 37, 37);
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
background-size: cover;
|
||||
font-family: "Noto Sans", "Noto Color Emoji", sans-serif;
|
||||
font-size: 16px;
|
||||
/*1rem*/
|
||||
color: #999;
|
||||
box-sizing: border-box;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
/*z-index:0;*/
|
||||
}
|
||||
|
||||
#main {
|
||||
padding-top: 20px;
|
||||
/*z-index:1;*/
|
||||
}
|
||||
|
||||
#content {
|
||||
margin: 0 auto;
|
||||
max-width: 700px;
|
||||
border: 1px solid #333;
|
||||
padding: 20px;
|
||||
border-radius: 20px;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
line-height: 1.5rem;
|
||||
box-shadow: 0 0 5px black;
|
||||
/*z-index: 2;*/
|
||||
}
|
||||
|
||||
code {
|
||||
border: 1px solid #999;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
display: block;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
a {
|
||||
color: orange;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px dotted orange;
|
||||
}
|
||||
|
||||
h2,
|
||||
h3 {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 1px solid #999;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
table,
|
||||
th,
|
||||
td {
|
||||
border: 1px solid;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table img {
|
||||
max-width: 200px;
|
||||
}
|
1
public/css/select2.min.css
vendored
Normal file
1
public/css/select2.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
public/img/quill.png
Normal file
BIN
public/img/quill.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "Metharme",
|
||||
"system_prompt": "Write {{char}}'s next reply in a fictional roleplay chat between {{user}} and {{char}}.",
|
||||
"system_prompt": "Enter roleplay mode. You must act as {{char}}, whose persona follows:",
|
||||
"system_sequence": "<|system|>",
|
||||
"stop_sequence": "</s>",
|
||||
"input_sequence": "<|user|>",
|
||||
"output_sequence": "<|model|>",
|
||||
"separator_sequence": "",
|
||||
"wrap": false
|
||||
}
|
||||
}
|
||||
|
@@ -1,23 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>SillyTavern Documentation</title>
|
||||
<link rel="stylesheet" href="/css/notes.css">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="/webfonts/NotoSans/stylesheet.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="main">
|
||||
<div id="content">
|
||||
<h2>You weren't supposed to be able to get here, you know.</h1>
|
||||
<h3>All help materials has been moved here:</h3>
|
||||
<h3><a href="https://docs.sillytavern.app/">SillyTavern Documentation</a></h3>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
1373
public/script.js
1373
public/script.js
File diff suppressed because it is too large
Load Diff
@@ -11,8 +11,8 @@ import {
|
||||
is_send_press,
|
||||
getTokenCount,
|
||||
menu_type,
|
||||
|
||||
|
||||
max_context,
|
||||
saveSettingsDebounced,
|
||||
} from "../script.js";
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
SECRET_KEYS,
|
||||
secret_state,
|
||||
} from "./secrets.js";
|
||||
import { sortByCssOrder, debounce } from "./utils.js";
|
||||
import { sortByCssOrder, debounce, delay } from "./utils.js";
|
||||
import { chat_completion_sources, oai_settings } from "./openai.js";
|
||||
|
||||
var NavToggle = document.getElementById("nav-toggle");
|
||||
@@ -177,6 +177,29 @@ export function humanizedDateTime() {
|
||||
return HumanizedDateTime;
|
||||
}
|
||||
|
||||
//this is a common format version to display a timestamp on each chat message
|
||||
//returns something like: June 19, 2023 2:20pm
|
||||
export function getMessageTimeStamp() {
|
||||
const date = Date.now();
|
||||
const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
||||
const d = new Date(date);
|
||||
const month = months[d.getMonth()];
|
||||
const day = d.getDate();
|
||||
const year = d.getFullYear();
|
||||
let hours = d.getHours();
|
||||
const minutes = ('0' + d.getMinutes()).slice(-2);
|
||||
let meridiem = 'am';
|
||||
if (hours >= 12) {
|
||||
meridiem = 'pm';
|
||||
hours -= 12;
|
||||
}
|
||||
if (hours === 0) {
|
||||
hours = 12;
|
||||
}
|
||||
const formattedDate = month + ' ' + day + ', ' + year + ' ' + hours + ':' + minutes + meridiem;
|
||||
return formattedDate;
|
||||
}
|
||||
|
||||
|
||||
// triggers:
|
||||
$("#rm_button_create").on("click", function () { //when "+New Character" is clicked
|
||||
@@ -264,18 +287,18 @@ export function RA_CountCharTokens() {
|
||||
} else { console.debug("RA_TC -- no valid char found, closing."); }
|
||||
}
|
||||
// display the counted tokens
|
||||
if (count_tokens < 1024 && perm_tokens < 1024) {
|
||||
//display normal if both counts are under 1024
|
||||
const tokenLimit = Math.max(((main_api !== 'openai' ? max_context : oai_settings.openai_max_context) / 2), 1024);
|
||||
if (count_tokens < tokenLimit && perm_tokens < tokenLimit) {
|
||||
$("#result_info").html(`<small>${count_tokens} Tokens (${perm_tokens} Permanent)</small>`);
|
||||
} else {
|
||||
$("#result_info").html(`
|
||||
<div class="flex-container flexFlowColumn alignitemscenter">
|
||||
<div class="flex-container alignitemscenter">
|
||||
<div class="flex-container flexnowrap flexNoGap">
|
||||
<small class="flex-container flexnowrap flexNoGap">
|
||||
<div class="neutral_warning">${count_tokens}</div> Tokens (<div class="neutral_warning">${perm_tokens}</div><div> Permanent)</div>
|
||||
</small>
|
||||
</div>
|
||||
<div id="chartokenwarning" class="menu_button whitespacenowrap"><a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#character-tokens" target="_blank">About Token 'Limits'</a></div>
|
||||
<div id="chartokenwarning" class="menu_button margin0 whitespacenowrap"><a href="https://docs.sillytavern.app/usage/core-concepts/characterdesign/#character-tokens" target="_blank">About Token 'Limits'</a></div>
|
||||
</div>`);
|
||||
} //warn if either are over 1024
|
||||
}
|
||||
@@ -450,60 +473,139 @@ function OpenNavPanels() {
|
||||
|
||||
|
||||
// Make the DIV element draggable:
|
||||
dragElement(document.getElementById("sheld"));
|
||||
dragElement(document.getElementById("left-nav-panel"));
|
||||
dragElement(document.getElementById("right-nav-panel"));
|
||||
dragElement(document.getElementById("avatar_zoom_popup"));
|
||||
dragElement(document.getElementById("WorldInfo"));
|
||||
|
||||
|
||||
// SECOND UPDATE AIMING FOR MUTATIONS ONLY
|
||||
|
||||
export function dragElement(elmnt) {
|
||||
|
||||
var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
|
||||
if (document.getElementById(elmnt.id + "header")) { //ex: id="sheldheader"
|
||||
// if present, the header is where you move the DIV from, but this overrides everything else:
|
||||
document.getElementById(elmnt.id + "header").onmousedown = dragMouseDown;
|
||||
var height, width, top, left, right, bottom;
|
||||
|
||||
var oldTop = Number((String($(elmnt).css('top')).replace('px', '')))
|
||||
var oldLeft = Number((String($(elmnt).css('left')).replace('px', '')))
|
||||
var oldWidth = Number((String($(elmnt).css('width')).replace('px', '')))
|
||||
var oldHeight = Number((String($(elmnt).css('width')).replace('px', '')))
|
||||
var oldRight = Number((String($(elmnt).css('right')).replace('px', '')))
|
||||
var oldBottom = Number((String($(elmnt).css('bottom')).replace('px', '')))
|
||||
var elmntName = elmnt.attr('id');
|
||||
console.debug(`${elmntName} init state:
|
||||
T: ${$(elmnt).css('top')}
|
||||
L: ${$(elmnt).css('left')}
|
||||
W: ${$(elmnt).css('width')}
|
||||
H: ${$(elmnt).css('height')}
|
||||
R: ${$(elmnt).css('right')}
|
||||
B: ${$(elmnt).css('bottom')}
|
||||
---`);
|
||||
|
||||
|
||||
const elmntNameEscaped = $.escapeSelector(elmntName);
|
||||
const elmntHeader = $(`#${elmntNameEscaped}header`);
|
||||
if (elmntHeader.length) {
|
||||
elmntHeader.off('mousedown').on('mousedown', (e) => {
|
||||
|
||||
dragMouseDown(e);
|
||||
});
|
||||
} else {
|
||||
// otherwise, move the DIV from anywhere inside the DIV, b:
|
||||
elmnt.onmousedown = dragMouseDown;
|
||||
elmnt.off('mousedown').on('mousedown', dragMouseDown);
|
||||
}
|
||||
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
const target = mutations[0].target;
|
||||
if (!$(target).is(':visible')
|
||||
|| $(target).hasClass('resizing')
|
||||
|| Number((String(target.height).replace('px', ''))) < 50
|
||||
|| Number((String(target.width).replace('px', ''))) < 50
|
||||
|| power_user.movingUI === false
|
||||
|| isMobile() === true
|
||||
) {
|
||||
console.debug('aborting mutator')
|
||||
return
|
||||
}
|
||||
|
||||
const style = getComputedStyle(target);
|
||||
//console.log(style.top, style.left)
|
||||
height = target.offsetHeight;
|
||||
width = target.offsetWidth;
|
||||
top = parseInt(style.top);
|
||||
left = parseInt(style.left);
|
||||
right = parseInt(style.right);
|
||||
bottom = parseInt(style.bottom);
|
||||
|
||||
if (!power_user.movingUIState[elmntName]) {
|
||||
console.debug(`adding config property for ${elmntName}`)
|
||||
power_user.movingUIState[elmntName] = {};
|
||||
}
|
||||
|
||||
power_user.movingUIState[elmntName].top = top;
|
||||
power_user.movingUIState[elmntName].left = left;
|
||||
|
||||
if (!isNaN(oldWidth)
|
||||
&& !isNaN(oldHeight)
|
||||
&& (oldHeight !== height || oldWidth !== width)) {
|
||||
power_user.movingUIState[elmntName].width = width;
|
||||
power_user.movingUIState[elmntName].height = height;
|
||||
} else {
|
||||
console.debug('skipping W/H setting')
|
||||
}
|
||||
power_user.movingUIState[elmntName].right = right;
|
||||
power_user.movingUIState[elmntName].bottom = bottom;
|
||||
if (!isNaN(oldTop) && !isNaN(oldLeft) && (oldTop !== top || oldLeft !== left)) {
|
||||
console.debug('unsetting margin due to custom position')
|
||||
console.debug(`${elmntName}:
|
||||
T: ${oldTop}>>${top}
|
||||
L: ${oldLeft}>> ${left}
|
||||
H: ${oldHeight} >> ${height}
|
||||
W: ${oldWidth}>> ${width}
|
||||
R: ${oldRight} >> ${right}
|
||||
B: ${oldBottom}>> ${bottom}
|
||||
---`)
|
||||
power_user.movingUIState[elmntName].margin = 'unset';
|
||||
} else {
|
||||
console.debug('skipped unsetting margins')
|
||||
//console.debug(oldTop, top, oldLeft, left)
|
||||
}
|
||||
saveSettingsDebounced();
|
||||
|
||||
|
||||
|
||||
// Check if the element header exists and set the listener on the grabber
|
||||
if (elmntHeader.length) {
|
||||
elmntHeader.off('mousedown').on('mousedown', (e) => {
|
||||
console.debug('listener started from header')
|
||||
dragMouseDown(e);
|
||||
});
|
||||
} else {
|
||||
elmnt.off('mousedown').on('mousedown', dragMouseDown);
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(elmnt.get(0), { attributes: true, attributeFilter: ['style'] });
|
||||
|
||||
function dragMouseDown(e) {
|
||||
//console.log(e);
|
||||
e = e || window.event;
|
||||
e.preventDefault();
|
||||
// get the mouse cursor position at startup:
|
||||
pos3 = e.clientX; //mouse X at click
|
||||
pos4 = e.clientY; //mouse Y at click
|
||||
document.onmouseup = closeDragElement;
|
||||
// call a function whenever the cursor moves:
|
||||
document.onmousemove = elementDrag;
|
||||
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
pos3 = e.clientX; //mouse X at click
|
||||
pos4 = e.clientY; //mouse Y at click
|
||||
}
|
||||
$(document).on('mouseup', closeDragElement);
|
||||
$(document).on('mousemove', elementDrag);
|
||||
}
|
||||
|
||||
function elementDrag(e) {
|
||||
//disable scrollbars when dragging to prevent jitter
|
||||
$("body").css("overflow", "hidden");
|
||||
if (!power_user.movingUIState[elmntName]) {
|
||||
power_user.movingUIState[elmntName] = {};
|
||||
}
|
||||
|
||||
|
||||
//get window size
|
||||
let winWidth = window.innerWidth;
|
||||
let winHeight = window.innerHeight;
|
||||
|
||||
//get necessary data for calculating element footprint
|
||||
let draggableHeight = parseInt(getComputedStyle(elmnt).getPropertyValue('height').slice(0, -2));
|
||||
let draggableWidth = parseInt(getComputedStyle(elmnt).getPropertyValue('width').slice(0, -2));
|
||||
let draggableTop = parseInt(getComputedStyle(elmnt).getPropertyValue('top').slice(0, -2));
|
||||
let draggableLeft = parseInt(getComputedStyle(elmnt).getPropertyValue('left').slice(0, -2));
|
||||
let sheldWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--sheldWidth').slice(0, -2));
|
||||
let sheldWidth = parseInt($('html').css('--sheldWidth').slice(0, -2));
|
||||
let topBarFirstX = (winWidth - sheldWidth) / 2;
|
||||
let topBarLastX = topBarFirstX + sheldWidth;
|
||||
let maxX = (width + left);
|
||||
let maxY = (height + top);
|
||||
|
||||
//set the lowest and most-right pixel the element touches
|
||||
let maxX = (draggableWidth + draggableLeft);
|
||||
let maxY = (draggableHeight + draggableTop);
|
||||
|
||||
// calculate the new cursor position:
|
||||
e = e || window.event;
|
||||
e.preventDefault();
|
||||
|
||||
@@ -512,88 +614,69 @@ export function dragElement(elmnt) {
|
||||
pos3 = e.clientX; //new mouse X
|
||||
pos4 = e.clientY; //new mouse Y
|
||||
|
||||
elmnt.setAttribute('data-dragged', 'true');
|
||||
elmnt.attr('data-dragged', 'true');
|
||||
|
||||
//fix over/underflows:
|
||||
|
||||
setTimeout(function () {
|
||||
if (elmnt.offsetTop < 40) {
|
||||
/* console.log('6'); */
|
||||
if (maxX > topBarFirstX && maxX < topBarLastX) {
|
||||
/* console.log('maxX inside topBar!'); */
|
||||
elmnt.style.top = "42px";
|
||||
}
|
||||
if (elmnt.offsetLeft < topBarLastX && elmnt.offsetLeft > topBarFirstX) {
|
||||
/* console.log('offsetLeft inside TopBar!'); */
|
||||
elmnt.style.top = "42px";
|
||||
}
|
||||
if (elmnt.offset().top < 40) {
|
||||
if (maxX > topBarFirstX && maxX < topBarLastX) {
|
||||
elmnt.css('top', '42px');
|
||||
}
|
||||
|
||||
if (elmnt.offsetTop - pos2 <= 0) {
|
||||
/* console.log('1'); */
|
||||
//prevent going out of window top + 42px barrier for TopBar (can hide grabber)
|
||||
elmnt.style.top = "0px";
|
||||
if (elmnt.offset().left < topBarLastX && elmnt.offset().left > topBarFirstX) {
|
||||
elmnt.css('top', '42px');
|
||||
}
|
||||
|
||||
if (elmnt.offsetLeft - pos1 <= 0) {
|
||||
/* console.log('2'); */
|
||||
//prevent moving out of window left
|
||||
elmnt.style.left = "0px";
|
||||
}
|
||||
if (elmnt.offset().top - pos2 <= 0) {
|
||||
elmnt.css('top', '0px');
|
||||
}
|
||||
if (elmnt.offset().left - pos1 <= 0) {
|
||||
elmnt.css('left', '0px');
|
||||
}
|
||||
if (maxX >= winWidth) {
|
||||
elmnt.css('left', elmnt.offset().left - 10 + "px");
|
||||
}
|
||||
if (maxY >= winHeight) {
|
||||
elmnt.css('top', elmnt.offset().top - 10 + "px");
|
||||
if (elmnt.offset().top - pos2 <= 40) {
|
||||
elmnt.css('top', '20px');
|
||||
}
|
||||
}
|
||||
elmnt.css('left', (elmnt.offset().left - pos1) + "px");
|
||||
elmnt.css("top", (elmnt.offset().top - pos2) + "px");
|
||||
elmnt.css('margin', 'unset');
|
||||
|
||||
if (maxX >= winWidth) {
|
||||
/* console.log('3'); */
|
||||
//bounce off right
|
||||
elmnt.style.left = elmnt.offsetLeft - 10 + "px";
|
||||
}
|
||||
|
||||
if (maxY >= winHeight) {
|
||||
/* console.log('4'); */
|
||||
//bounce off bottom
|
||||
elmnt.style.top = elmnt.offsetTop - 10 + "px";
|
||||
if (elmnt.offsetTop - pos2 <= 40) {
|
||||
/* console.log('5'); */
|
||||
//prevent going out of window top + 42px barrier for TopBar (can hide grabber)
|
||||
/* console.log('caught Y bounce to <40Y top'); */
|
||||
elmnt.style.top = "20px";
|
||||
}
|
||||
}
|
||||
// if no problems, set element's new position
|
||||
/* console.log('7'); */
|
||||
|
||||
elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
|
||||
elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
|
||||
$(elmnt).css("bottom", "unset");
|
||||
$(elmnt).css("right", "unset");
|
||||
$(elmnt).css("margin", "unset");
|
||||
|
||||
/* console.log(`
|
||||
offsetLeft: ${elmnt.offsetLeft}, offsetTop: ${elmnt.offsetTop}
|
||||
winWidth: ${winWidth}, winHeight: ${winHeight}
|
||||
sheldWidth: ${sheldWidth}
|
||||
X: ${elmnt.style.left}
|
||||
Y: ${elmnt.style.top}
|
||||
MaxX: ${maxX}, MaxY: ${maxY}
|
||||
Topbar 1st X: ${((winWidth - sheldWidth) / 2)}
|
||||
TopBar lastX: ${((winWidth - sheldWidth) / 2) + sheldWidth}
|
||||
`); */
|
||||
|
||||
|
||||
|
||||
}, 50)
|
||||
|
||||
/* console.log("left/top: " + (elmnt.offsetLeft - pos1) + "/" + (elmnt.offsetTop - pos2) +
|
||||
", win: " + winWidth + "/" + winHeight +
|
||||
", max X / Y: " + maxX + " / " + maxY); */
|
||||
|
||||
/*
|
||||
console.log(`
|
||||
winWidth: ${winWidth}, winHeight: ${winHeight}
|
||||
sheldWidth: ${sheldWidth}
|
||||
X: ${$(elmnt).css('left')}
|
||||
Y: ${$(elmnt).css('top')}
|
||||
MaxX: ${maxX}, MaxY: ${maxY}
|
||||
Topbar 1st X: ${((winWidth - sheldWidth) / 2)}
|
||||
TopBar lastX: ${((winWidth - sheldWidth) / 2) + sheldWidth}
|
||||
`);
|
||||
*/
|
||||
}
|
||||
|
||||
function closeDragElement() {
|
||||
// stop moving when mouse button is released:
|
||||
document.onmouseup = null;
|
||||
document.onmousemove = null;
|
||||
//revert scrolling to normal after drag to allow recovery of vastly misplaced elements
|
||||
$("body").css("overflow", "auto");
|
||||
console.debug('drag finished')
|
||||
$(document).off('mouseup', closeDragElement);
|
||||
$(document).off('mousemove', elementDrag);
|
||||
$("body").css("overflow", "");
|
||||
// Clear the "data-dragged" attribute
|
||||
elmnt.attr('data-dragged', 'false');
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export async function initMovingUI() {
|
||||
if (isMobile() === false && power_user.movingUI === true) {
|
||||
console.debug('START MOVING UI')
|
||||
dragElement($("#sheld"));
|
||||
dragElement($("#left-nav-panel"));
|
||||
dragElement($("#right-nav-panel"));
|
||||
dragElement($("#WorldInfo"));
|
||||
await delay(1000)
|
||||
console.debug('loading AN draggable function')
|
||||
dragElement($("#floatingPrompt"))
|
||||
|
||||
}
|
||||
}
|
||||
@@ -603,7 +686,9 @@ export function dragElement(elmnt) {
|
||||
$("document").ready(function () {
|
||||
|
||||
// initial status check
|
||||
setTimeout(RA_checkOnlineStatus, 100);
|
||||
setTimeout(() => {
|
||||
RA_checkOnlineStatus();
|
||||
}, 100);
|
||||
|
||||
// read the state of AutoConnect and AutoLoadChat.
|
||||
$(AutoConnectCheckbox).prop("checked", LoadLocalBool("AutoConnectEnabled"));
|
||||
@@ -790,7 +875,7 @@ $("document").ready(function () {
|
||||
function isInputElementInFocus() {
|
||||
//return $(document.activeElement).is(":input");
|
||||
var focused = $(':focus');
|
||||
if (focused.is('input') || focused.is('textarea') || focused.attr('contenteditable') == 'true') {
|
||||
if (focused.is('input') || focused.is('textarea') || focused.prop('contenteditable') == 'true') {
|
||||
if (focused.attr('id') === 'send_textarea') {
|
||||
return false;
|
||||
}
|
||||
|
@@ -51,6 +51,7 @@ const extension_settings = {
|
||||
note: {
|
||||
default: '',
|
||||
chara: [],
|
||||
wiAddition: [],
|
||||
},
|
||||
caption: {},
|
||||
expressions: {},
|
||||
@@ -421,12 +422,12 @@ async function loadExtensionSettings(settings) {
|
||||
}
|
||||
}
|
||||
|
||||
async function runGenerationInterceptors(chat) {
|
||||
async function runGenerationInterceptors(chat, contextSize) {
|
||||
for (const manifest of Object.values(manifests)) {
|
||||
const interceptorKey = manifest.generate_interceptor;
|
||||
if (typeof window[interceptorKey] === 'function') {
|
||||
try {
|
||||
await window[interceptorKey](chat);
|
||||
await window[interceptorKey](chat, contextSize);
|
||||
} catch (e) {
|
||||
console.error(`Failed running interceptor for ${manifest.display_name}`, e);
|
||||
}
|
||||
|
@@ -120,7 +120,7 @@ $(document).ready(function () {
|
||||
<div class="background_settings">
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>Character Backgrounds</b>
|
||||
<b>Chat Backgrounds</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
|
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"display_name": "Character Backgrounds",
|
||||
"display_name": "Chat Backgrounds",
|
||||
"loading_order": 7,
|
||||
"requires": [],
|
||||
"optional": [],
|
||||
|
@@ -161,7 +161,7 @@ async function visualNovelSetCharacterSprites(container, name, expression) {
|
||||
template.attr('data-avatar', avatar);
|
||||
template.find('.drag-grabber').attr('id', `expression-${avatar}header`);
|
||||
$('#visual-novel-wrapper').append(template);
|
||||
dragElement(template[0]);
|
||||
dragElement($(template[0]));
|
||||
template.toggleClass('hidden', noSprites);
|
||||
setImage(template.find('img'), defaultSpritePath || '');
|
||||
const fadeInPromise = new Promise(resolve => {
|
||||
@@ -533,6 +533,11 @@ function drawSpritesList(character, labels, sprites) {
|
||||
$('.expression_settings').show();
|
||||
$('#image_list').empty();
|
||||
$('#image_list').data('name', character);
|
||||
|
||||
if (!Array.isArray(labels)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
labels.sort().forEach((item) => {
|
||||
const sprite = sprites.find(x => x.label == item);
|
||||
|
||||
|
@@ -10,7 +10,7 @@ import { selected_group } from "../../group-chats.js";
|
||||
import { ModuleWorkerWrapper, extension_settings, getContext, saveMetadataDebounced } from "../../extensions.js";
|
||||
import { registerSlashCommand } from "../../slash-commands.js";
|
||||
import { getCharaFilename, debounce } from "../../utils.js";
|
||||
export { MODULE_NAME };
|
||||
export { MODULE_NAME as NOTE_MODULE_NAME };
|
||||
|
||||
const MODULE_NAME = '2_floating_prompt'; // <= Deliberate, for sorting lower than memory
|
||||
const UPDATE_INTERVAL = 1000;
|
||||
@@ -18,14 +18,21 @@ const UPDATE_INTERVAL = 1000;
|
||||
const DEFAULT_DEPTH = 4;
|
||||
const DEFAULT_POSITION = 1;
|
||||
const DEFAULT_INTERVAL = 1;
|
||||
export var shouldWIAddPrompt = false;
|
||||
|
||||
const metadata_keys = {
|
||||
export const metadata_keys = {
|
||||
prompt: 'note_prompt',
|
||||
interval: 'note_interval',
|
||||
depth: 'note_depth',
|
||||
position: 'note_position',
|
||||
}
|
||||
|
||||
const chara_note_position = {
|
||||
replace: 0,
|
||||
before: 1,
|
||||
after: 2,
|
||||
}
|
||||
|
||||
function setNoteTextCommand(_, text) {
|
||||
$('#extension_floating_prompt').val(text).trigger('input');
|
||||
toastr.success("Author's Note text updated");
|
||||
@@ -72,6 +79,12 @@ function setNotePositionCommand(_, text) {
|
||||
toastr.info("Author's Note position updated");
|
||||
}
|
||||
|
||||
function updateSettings() {
|
||||
saveSettingsDebounced();
|
||||
loadSettings();
|
||||
setFloatingPrompt();
|
||||
}
|
||||
|
||||
const setMainPromptTokenCounterDebounced = debounce((value) => $('#extension_floating_prompt_token_counter').text(getTokenCount(value)), 1000);
|
||||
const setCharaPromptTokenCounterDebounced = debounce((value) => $('#extension_floating_chara_token_counter').text(getTokenCount(value)), 1000);
|
||||
const setDefaultPromptTokenCounterDebounced = debounce((value) => $('#extension_floating_default_token_counter').text(getTokenCount(value)), 1000);
|
||||
@@ -79,11 +92,13 @@ const setDefaultPromptTokenCounterDebounced = debounce((value) => $('#extension_
|
||||
async function onExtensionFloatingPromptInput() {
|
||||
chat_metadata[metadata_keys.prompt] = $(this).val();
|
||||
setMainPromptTokenCounterDebounced(chat_metadata[metadata_keys.prompt]);
|
||||
updateSettings();
|
||||
saveMetadataDebounced();
|
||||
}
|
||||
|
||||
async function onExtensionFloatingIntervalInput() {
|
||||
chat_metadata[metadata_keys.interval] = Number($(this).val());
|
||||
updateSettings();
|
||||
saveMetadataDebounced();
|
||||
}
|
||||
|
||||
@@ -96,14 +111,26 @@ async function onExtensionFloatingDepthInput() {
|
||||
}
|
||||
|
||||
chat_metadata[metadata_keys.depth] = value;
|
||||
updateSettings();
|
||||
saveMetadataDebounced();
|
||||
}
|
||||
|
||||
async function onExtensionFloatingPositionInput(e) {
|
||||
chat_metadata[metadata_keys.position] = e.target.value;
|
||||
updateSettings();
|
||||
saveMetadataDebounced();
|
||||
}
|
||||
|
||||
async function onExtensionFloatingCharPositionInput(e) {
|
||||
const value = e.target.value;
|
||||
const charaNote = extension_settings.note.chara.find((e) => e.name === getCharaFilename());
|
||||
|
||||
if (charaNote) {
|
||||
charaNote.position = Number(value);
|
||||
updateSettings();
|
||||
}
|
||||
}
|
||||
|
||||
function onExtensionFloatingCharaPromptInput() {
|
||||
const tempPrompt = $(this).val();
|
||||
const avatarName = getCharaFilename();
|
||||
@@ -136,7 +163,7 @@ function onExtensionFloatingCharaPromptInput() {
|
||||
if (!extension_settings.note.chara) {
|
||||
extension_settings.note.chara = []
|
||||
}
|
||||
Object.assign(tempCharaNote, { useChara: false })
|
||||
Object.assign(tempCharaNote, { useChara: false, position: chara_note_position.replace })
|
||||
|
||||
extension_settings.note.chara.push(tempCharaNote);
|
||||
} else {
|
||||
@@ -147,7 +174,7 @@ function onExtensionFloatingCharaPromptInput() {
|
||||
return;
|
||||
}
|
||||
|
||||
saveSettingsDebounced();
|
||||
updateSettings();
|
||||
}
|
||||
|
||||
function onExtensionFloatingCharaCheckboxChanged() {
|
||||
@@ -157,14 +184,14 @@ function onExtensionFloatingCharaCheckboxChanged() {
|
||||
if (charaNote) {
|
||||
charaNote.useChara = value;
|
||||
|
||||
saveSettingsDebounced();
|
||||
updateSettings();
|
||||
}
|
||||
}
|
||||
|
||||
function onExtensionFloatingDefaultInput() {
|
||||
extension_settings.note.default = $(this).val();
|
||||
setDefaultPromptTokenCounterDebounced(extension_settings.note.default);
|
||||
saveSettingsDebounced();
|
||||
updateSettings();
|
||||
}
|
||||
|
||||
function loadSettings() {
|
||||
@@ -177,28 +204,39 @@ function loadSettings() {
|
||||
$('#extension_floating_depth').val(chat_metadata[metadata_keys.depth]);
|
||||
$(`input[name="extension_floating_position"][value="${chat_metadata[metadata_keys.position]}"]`).prop('checked', true);
|
||||
|
||||
if (extension_settings.note.chara) {
|
||||
if (extension_settings.note.chara && getContext().characterId) {
|
||||
const charaNote = extension_settings.note.chara.find((e) => e.name === getCharaFilename());
|
||||
|
||||
$('#extension_floating_chara').val(charaNote ? charaNote.prompt : '');
|
||||
$('#extension_use_floating_chara').prop('checked', charaNote ? charaNote.useChara : false);
|
||||
$(`input[name="extension_floating_char_position"][value="${charaNote?.position ?? chara_note_position.replace}"]`).prop('checked', true);
|
||||
} else {
|
||||
$('#extension_floating_chara').val('');
|
||||
$('#extension_use_floating_chara').prop('checked', false);
|
||||
$(`input[name="extension_floating_char_position"][value="${chara_note_position.replace}"]`).prop('checked', true);
|
||||
}
|
||||
|
||||
$('#extension_floating_default').val(extension_settings.note.default);
|
||||
}
|
||||
|
||||
async function moduleWorker() {
|
||||
export function setFloatingPrompt() {
|
||||
const context = getContext();
|
||||
|
||||
if (!context.groupId && context.characterId === undefined) {
|
||||
console.debug('setFloatingPrompt: Not in a chat. Skipping.');
|
||||
shouldWIAddPrompt = false;
|
||||
return;
|
||||
}
|
||||
|
||||
loadSettings();
|
||||
|
||||
// take the count of messages
|
||||
let lastMessageNumber = Array.isArray(context.chat) && context.chat.length ? context.chat.filter(m => m.is_user).length : 0;
|
||||
|
||||
console.debug(`
|
||||
setFloatingPrompt entered
|
||||
------
|
||||
lastMessageNumber = ${lastMessageNumber}
|
||||
metadata_keys.interval = ${chat_metadata[metadata_keys.interval]}
|
||||
`)
|
||||
|
||||
// interval 1 should be inserted no matter what
|
||||
if (chat_metadata[metadata_keys.interval] === 1) {
|
||||
lastMessageNumber = 1;
|
||||
@@ -207,6 +245,7 @@ async function moduleWorker() {
|
||||
if (lastMessageNumber <= 0 || chat_metadata[metadata_keys.interval] <= 0) {
|
||||
context.setExtensionPrompt(MODULE_NAME, '');
|
||||
$('#extension_floating_counter').text('(disabled)');
|
||||
shouldWIAddPrompt = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -214,17 +253,27 @@ async function moduleWorker() {
|
||||
? (lastMessageNumber % chat_metadata[metadata_keys.interval])
|
||||
: (chat_metadata[metadata_keys.interval] - lastMessageNumber);
|
||||
const shouldAddPrompt = messagesTillInsertion == 0;
|
||||
shouldWIAddPrompt = shouldAddPrompt;
|
||||
|
||||
let prompt = shouldAddPrompt ? $('#extension_floating_prompt').val() : '';
|
||||
if (shouldAddPrompt && extension_settings.note.chara) {
|
||||
if (shouldAddPrompt && extension_settings.note.chara && getContext().characterId) {
|
||||
const charaNote = extension_settings.note.chara.find((e) => e.name === getCharaFilename());
|
||||
|
||||
// Only replace with the chara note if the user checked the box
|
||||
if (charaNote && charaNote.useChara) {
|
||||
prompt = charaNote.prompt;
|
||||
switch (charaNote.position) {
|
||||
case chara_note_position.before:
|
||||
prompt = charaNote.prompt + '\n' + prompt;
|
||||
break;
|
||||
case chara_note_position.after:
|
||||
prompt = prompt + '\n' + charaNote.prompt;
|
||||
break;
|
||||
default:
|
||||
prompt = charaNote.prompt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.setExtensionPrompt(MODULE_NAME, prompt, chat_metadata[metadata_keys.position], chat_metadata[metadata_keys.depth]);
|
||||
$('#extension_floating_counter').text(shouldAddPrompt ? '0' : messagesTillInsertion);
|
||||
}
|
||||
@@ -263,11 +312,18 @@ function onANMenuItemClick() {
|
||||
}
|
||||
|
||||
function onChatChanged() {
|
||||
loadSettings();
|
||||
setFloatingPrompt();
|
||||
const context = getContext();
|
||||
|
||||
// Disable the chara note if in a group
|
||||
$('#extension_floating_chara').prop('disabled', context.groupId ? true : false);
|
||||
|
||||
const tokenCounter1 = chat_metadata[metadata_keys.prompt] ? getTokenCount(chat_metadata[metadata_keys.prompt]) : 0;
|
||||
$('#extension_floating_prompt_token_counter').text(tokenCounter1);
|
||||
|
||||
let tokenCounter2;
|
||||
if (extension_settings.note.chara) {
|
||||
if (extension_settings.note.chara && context.characterId) {
|
||||
const charaNote = extension_settings.note.chara.find((e) => e.name === getCharaFilename());
|
||||
|
||||
if (charaNote) {
|
||||
@@ -283,7 +339,10 @@ function onChatChanged() {
|
||||
$('#extension_floating_default_token_counter').text(tokenCounter3);
|
||||
}
|
||||
|
||||
(function () {
|
||||
//for some reason exporting metadata_keys for WI usage caused this to throw errors
|
||||
//"accessing eventSource before initialization"
|
||||
//putting it on a 1ms Timeout solved this.
|
||||
setTimeout(function () {
|
||||
function addExtensionsSettings() {
|
||||
const settingsHtml = `
|
||||
<div id="floatingPrompt" class="drawer-content flexGap5">
|
||||
@@ -335,16 +394,30 @@ function onChatChanged() {
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<small>Will be automatically added as the author's note for this character.</small>
|
||||
<small>Will be automatically added as the author's note for this character. Will be used in groups, but can't be modified when a group chat is open.</small>
|
||||
|
||||
<textarea id="extension_floating_chara" class="text_pole" rows="8" maxlength="10000"
|
||||
placeholder="Example:\n[Scenario: wacky adventures; Genre: romantic comedy; Style: verbose, creative]"></textarea>
|
||||
<div class="extension_token_counter">Tokens: <span id="extension_floating_chara_token_counter">0</small></div>
|
||||
|
||||
<label for="extension_use_floating_chara">
|
||||
<label class="checkbox_label" for="extension_use_floating_chara">
|
||||
<input id="extension_use_floating_chara" type="checkbox" />
|
||||
<span data-i18n="Use character author's note">Use character author's note</span>
|
||||
</label>
|
||||
<span data-i18n="Use character author's note">Use character author's note</span>
|
||||
</label>
|
||||
<div class="floating_prompt_radio_group">
|
||||
<label>
|
||||
<input type="radio" name="extension_floating_char_position" value="0" />
|
||||
Replace Author's Note
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="extension_floating_char_position" value="1" />
|
||||
Top of Author's Note
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="extension_floating_char_position" value="2" />
|
||||
Bottom of Author's Note
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="sysHR">
|
||||
@@ -380,6 +453,7 @@ function onChatChanged() {
|
||||
$('#extension_use_floating_chara').on('input', onExtensionFloatingCharaCheckboxChanged);
|
||||
$('#extension_floating_default').on('input', onExtensionFloatingDefaultInput);
|
||||
$('input[name="extension_floating_position"]').on('change', onExtensionFloatingPositionInput);
|
||||
$('input[name="extension_floating_char_position"]').on('change', onExtensionFloatingCharPositionInput);
|
||||
$('#ANClose').on('click', function () {
|
||||
$("#floatingPrompt").transition({
|
||||
opacity: 0,
|
||||
@@ -392,11 +466,9 @@ function onChatChanged() {
|
||||
}
|
||||
|
||||
addExtensionsSettings();
|
||||
const wrapper = new ModuleWorkerWrapper(moduleWorker);
|
||||
setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL);
|
||||
registerSlashCommand('note', setNoteTextCommand, [], "<span class='monospace'>(text)</span> – sets an author's note for the currently selected chat", true, true);
|
||||
registerSlashCommand('depth', setNoteDepthCommand, [], "<span class='monospace'>(number)</span> – sets an author's note depth for in-chat positioning", true, true);
|
||||
registerSlashCommand('freq', setNoteIntervalCommand, ['interval'], "<span class='monospace'>(number)</span> – sets an author's note insertion frequency", true, true);
|
||||
registerSlashCommand('pos', setNotePositionCommand, ['position'], "(<span class='monospace'>chat</span> or <span class='monospace'>scenario</span>) – sets an author's note position", true, true);
|
||||
eventSource.on(event_types.CHAT_CHANGED, onChatChanged);
|
||||
})();
|
||||
}, 1);
|
||||
|
@@ -3,6 +3,7 @@
|
||||
"loading_order": 1,
|
||||
"requires": [],
|
||||
"optional": [],
|
||||
"generate_interceptor": "AuthorNote_generateInterceptor",
|
||||
"js": "index.js",
|
||||
"css": "style.css",
|
||||
"author": "Cohee#1207",
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { saveSettingsDebounced, getCurrentChatId, system_message_types, eventSource, event_types } from "../../../script.js";
|
||||
import { saveSettingsDebounced, getCurrentChatId, system_message_types, eventSource, event_types, getRequestHeaders, CHARACTERS_PER_TOKEN_RATIO, substituteParams, } from "../../../script.js";
|
||||
import { humanizedDateTime } from "../../RossAscends-mods.js";
|
||||
import { getApiUrl, extension_settings, getContext, doExtrasFetch } from "../../extensions.js";
|
||||
import { getFileText, onlyUnique, splitRecursive, IndexedDBStore } from "../../utils.js";
|
||||
@@ -9,6 +9,7 @@ const dbStore = new IndexedDBStore('SillyTavern', MODULE_NAME);
|
||||
|
||||
const defaultSettings = {
|
||||
strategy: 'original',
|
||||
sort_strategy: 'date',
|
||||
|
||||
keep_context: 10,
|
||||
keep_context_min: 1,
|
||||
@@ -20,6 +21,12 @@ const defaultSettings = {
|
||||
n_results_max: 500,
|
||||
n_results_step: 1,
|
||||
|
||||
chroma_depth: 20,
|
||||
chroma_depth_min: -1,
|
||||
chroma_depth_max: 500,
|
||||
chroma_depth_step: 1,
|
||||
chroma_default_msg: "In a past conversation: [{{memories}}]",
|
||||
|
||||
split_length: 384,
|
||||
split_length_min: 64,
|
||||
split_length_max: 4096,
|
||||
@@ -29,6 +36,14 @@ const defaultSettings = {
|
||||
file_split_length_min: 512,
|
||||
file_split_length_max: 4096,
|
||||
file_split_length_step: 128,
|
||||
|
||||
keep_context_proportion: 0.5,
|
||||
keep_context_proportion_min: 0.0,
|
||||
keep_context_proportion_max: 1.0,
|
||||
keep_context_proportion_step: 0.05,
|
||||
|
||||
auto_adjust: true,
|
||||
freeze: false,
|
||||
};
|
||||
|
||||
const postHeaders = {
|
||||
@@ -88,18 +103,51 @@ async function loadSettings() {
|
||||
"selected",
|
||||
"true"
|
||||
);
|
||||
$("#chromadb_sort_strategy option[value=" + extension_settings.chromadb.sort_strategy + "]").attr(
|
||||
"selected",
|
||||
"true"
|
||||
);
|
||||
$('#chromadb_keep_context').val(extension_settings.chromadb.keep_context).trigger('input');
|
||||
$('#chromadb_n_results').val(extension_settings.chromadb.n_results).trigger('input');
|
||||
$('#chromadb_split_length').val(extension_settings.chromadb.split_length).trigger('input');
|
||||
$('#chromadb_file_split_length').val(extension_settings.chromadb.file_split_length).trigger('input');
|
||||
$('#chromadb_keep_context_proportion').val(extension_settings.chromadb.keep_context_proportion).trigger('input');
|
||||
$('#chromadb_custom_depth').val(extension_settings.chromadb.chroma_depth).trigger('input');
|
||||
$('#chromadb_custom_msg').val(extension_settings.chromadb.recall_msg).trigger('input');
|
||||
$('#chromadb_auto_adjust').prop('checked', extension_settings.chromadb.auto_adjust);
|
||||
$('#chromadb_freeze').prop('checked', extension_settings.chromadb.freeze);
|
||||
enableDisableSliders();
|
||||
onStrategyChange();
|
||||
}
|
||||
|
||||
function onStrategyChange() {
|
||||
console.debug('changing chromadb strat');
|
||||
extension_settings.chromadb.strategy = $('#chromadb_strategy').val();
|
||||
if(extension_settings.chromadb.strategy === "custom"){
|
||||
$('#chromadb_custom_depth').show();
|
||||
$('label[for="chromadb_custom_depth"]').show();
|
||||
$('#chromadb_custom_msg').show();
|
||||
$('label[for="chromadb_custom_msg"]').show();
|
||||
} else {
|
||||
$('#chromadb_custom_depth').hide();
|
||||
$('label[for="chromadb_custom_depth"]').hide();
|
||||
$('#chromadb_custom_msg').hide();
|
||||
$('label[for="chromadb_custom_msg"]').hide();
|
||||
}
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onRecallStrategyChange() {
|
||||
console.log('changing chromadb recall strat');
|
||||
extension_settings.chromadb.recall_strategy = $('#chromadb_recall_strategy').val();
|
||||
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onSortStrategyChange() {
|
||||
console.log('changing chromadb sort strat');
|
||||
extension_settings.chromadb.sort_strategy = $('#chromadb_sort_strategy').val();
|
||||
|
||||
//$('#chromadb_strategy').select(extension_settings.chromadb.strategy);
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
@@ -115,6 +163,17 @@ function onNResultsInput() {
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onChromaDepthInput() {
|
||||
extension_settings.chromadb.chroma_depth = Number($('#chromadb_custom_depth').val());
|
||||
$('#chromadb_custom_depth_value').text(extension_settings.chromadb.chroma_depth);
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onChromaMsgInput() {
|
||||
extension_settings.chromadb.recall_msg = $('#chromadb_custom_msg').val();
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onSplitLengthInput() {
|
||||
extension_settings.chromadb.split_length = Number($('#chromadb_split_length').val());
|
||||
$('#chromadb_split_length_value').text(extension_settings.chromadb.split_length);
|
||||
@@ -328,6 +387,39 @@ async function queryMessages(chat_id, query) {
|
||||
return [];
|
||||
}
|
||||
|
||||
async function queryMultiMessages(chat_id, query) {
|
||||
const context = getContext();
|
||||
const response = await fetch("/getallchatsofcharacter", {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ avatar_url: context.characters[context.characterId].avatar}),
|
||||
headers: getRequestHeaders(),
|
||||
});
|
||||
if (!response.ok) {
|
||||
return;
|
||||
}
|
||||
let data = await response.json();
|
||||
data = Object.values(data);
|
||||
let chat_list = data.sort((a, b) => a["file_name"].localeCompare(b["file_name"])).reverse();
|
||||
|
||||
// Extracting chat_ids from the chat_list
|
||||
chat_list = chat_list.map(chat => chat.file_name.replace(/\.[^/.]+$/, ""));
|
||||
const url = new URL(getApiUrl());
|
||||
url.pathname = '/api/chromadb/multiquery';
|
||||
|
||||
const queryMessagesResult = await fetch(url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ chat_list, query, n_results: extension_settings.chromadb.n_results }),
|
||||
headers: postHeaders,
|
||||
});
|
||||
|
||||
if (queryMessagesResult.ok) {
|
||||
const queryMessagesData = await queryMessagesResult.json();
|
||||
return queryMessagesData;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
async function onSelectInjectFile(e) {
|
||||
const file = e.target.files[0];
|
||||
const currentChatId = getCurrentChatId();
|
||||
@@ -390,9 +482,57 @@ async function onSelectInjectFile(e) {
|
||||
}
|
||||
}
|
||||
|
||||
window.chromadb_interceptGeneration = async (chat) => {
|
||||
/*
|
||||
* Automatically adjusts the extension settings for the optimal number of messages to keep and query based
|
||||
* on the chat history and a specified maximum context length.
|
||||
*/
|
||||
function doAutoAdjust(chat, maxContext) {
|
||||
console.debug('CHROMADB: Auto-adjusting sliders (messages: %o, maxContext: %o)', chat.length, maxContext);
|
||||
// Get mean message length
|
||||
const meanMessageLength = chat.reduce((acc, cur) => acc + cur.mes.length, 0) / chat.length;
|
||||
|
||||
if (Number.isNaN(meanMessageLength)) {
|
||||
console.debug('CHROMADB: Mean message length is NaN, aborting auto-adjust');
|
||||
return;
|
||||
}
|
||||
|
||||
console.debug('CHROMADB: Mean message length (characters): %o', meanMessageLength);
|
||||
// Convert to number of "tokens"
|
||||
const meanMessageLengthTokens = Math.ceil(meanMessageLength / CHARACTERS_PER_TOKEN_RATIO);
|
||||
console.debug('CHROMADB: Mean message length (tokens): %o', meanMessageLengthTokens);
|
||||
// Get number of messages in context
|
||||
const contextMessages = Math.max(1, Math.ceil(maxContext / meanMessageLengthTokens));
|
||||
// Round up to nearest 10
|
||||
const contextMessagesRounded = Math.ceil(contextMessages / 10) * 10;
|
||||
console.debug('CHROMADB: Estimated context messages (rounded): %o', contextMessagesRounded);
|
||||
// Messages to keep (proportional, rounded to nearest 5, minimum 10, maximum 500)
|
||||
const messagesToKeep = Math.min(defaultSettings.keep_context_max, Math.max(10, Math.ceil(contextMessagesRounded * extension_settings.chromadb.keep_context_proportion / 5) * 5));
|
||||
console.debug('CHROMADB: Estimated messages to keep: %o', messagesToKeep);
|
||||
// Messages to query (rounded, maximum 500)
|
||||
const messagesToQuery = Math.min(defaultSettings.n_results_max, contextMessagesRounded - messagesToKeep);
|
||||
console.debug('CHROMADB: Estimated messages to query: %o', messagesToQuery);
|
||||
// Set extension settings
|
||||
extension_settings.chromadb.keep_context = messagesToKeep;
|
||||
extension_settings.chromadb.n_results = messagesToQuery;
|
||||
// Update sliders
|
||||
$('#chromadb_keep_context').val(messagesToKeep);
|
||||
$('#chromadb_n_results').val(messagesToQuery);
|
||||
// Update labels
|
||||
$('#chromadb_keep_context_value').text(extension_settings.chromadb.keep_context);
|
||||
$('#chromadb_n_results_value').text(extension_settings.chromadb.n_results);
|
||||
}
|
||||
|
||||
window.chromadb_interceptGeneration = async (chat, maxContext) => {
|
||||
if (extension_settings.chromadb.auto_adjust) {
|
||||
doAutoAdjust(chat, maxContext);
|
||||
}
|
||||
|
||||
const currentChatId = getCurrentChatId();
|
||||
const selectedStrategy = extension_settings.chromadb.strategy;
|
||||
const recallStrategy = extension_settings.chromadb.recall_strategy;
|
||||
let recallMsg = extension_settings.chromadb.recall_msg || defaultSettings.chroma_default_msg;
|
||||
const chromaDepth = extension_settings.chromadb.chroma_depth;
|
||||
const chromaSortStrategy = extension_settings.chromadb.sort_strategy;
|
||||
if (currentChatId) {
|
||||
const messagesToStore = chat.slice(0, -extension_settings.chromadb.keep_context);
|
||||
|
||||
@@ -401,12 +541,26 @@ window.chromadb_interceptGeneration = async (chat) => {
|
||||
|
||||
const lastMessage = chat[chat.length - 1];
|
||||
|
||||
let queriedMessages;
|
||||
console.debug(recallStrategy)
|
||||
if (lastMessage) {
|
||||
const queriedMessages = await queryMessages(currentChatId, lastMessage.mes);
|
||||
if (recallStrategy === 'multichat'){
|
||||
console.log("Utilizing multichat")
|
||||
queriedMessages = await queryMultiMessages(currentChatId, lastMessage.mes);
|
||||
}
|
||||
else{
|
||||
queriedMessages = await queryMessages(currentChatId, lastMessage.mes);
|
||||
}
|
||||
|
||||
queriedMessages.sort((a, b) => a.date - b.date);
|
||||
if(chromaSortStrategy === "date"){
|
||||
queriedMessages.sort((a, b) => a.date - b.date);
|
||||
}
|
||||
else{
|
||||
queriedMessages.sort((a, b) => b.distance - a.distance);
|
||||
}
|
||||
|
||||
const newChat = [];
|
||||
|
||||
let newChat = [];
|
||||
|
||||
if (selectedStrategy === 'ross') {
|
||||
//adds chroma to the end of chat and allows Generate() to cull old messages naturally.
|
||||
@@ -433,7 +587,46 @@ window.chromadb_interceptGeneration = async (chat) => {
|
||||
);
|
||||
chat.splice(chat.length, 0, ...newChat);
|
||||
}
|
||||
if (selectedStrategy === 'custom') {
|
||||
const context = getContext();
|
||||
recallMsg = substituteParams(recallMsg, context.name1, context.name2);
|
||||
if (!text.includes("{{memories}}")) {
|
||||
text += " {{memories}}";
|
||||
}
|
||||
let recallStart = recallMsg.split('{{memories}}')[0]
|
||||
let recallEnd = recallMsg.split('{{memories}}')[1]
|
||||
|
||||
newChat.push(
|
||||
{
|
||||
is_name: false,
|
||||
is_user: false,
|
||||
mes: recallStart,
|
||||
name: "system",
|
||||
send_date: 0,
|
||||
}
|
||||
);
|
||||
newChat.push(...queriedMessages.map(m => m.meta).filter(onlyUnique).map(JSON.parse));
|
||||
newChat.push(
|
||||
{
|
||||
is_name: false,
|
||||
is_user: false,
|
||||
mes: recallEnd + `\n`,
|
||||
name: "system",
|
||||
send_date: 0,
|
||||
}
|
||||
);
|
||||
|
||||
//prototype chroma duplicate removal
|
||||
let chatset = new Set(chat.map(obj => obj.mes));
|
||||
newChat = newChat.filter(obj => !chatset.has(obj.mes));
|
||||
|
||||
if(chromaDepth === -1){
|
||||
chat.splice(chat.length, 0, ...newChat);
|
||||
}
|
||||
else{
|
||||
chat.splice(chromaDepth, 0, ...newChat);
|
||||
}
|
||||
}
|
||||
if (selectedStrategy === 'original') {
|
||||
//removes .length # messages from the start of 'kept messages'
|
||||
//replaces them with chromaDB results (with no separator)
|
||||
@@ -447,11 +640,37 @@ window.chromadb_interceptGeneration = async (chat) => {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function onFreezeInput() {
|
||||
extension_settings.chromadb.freeze = $('#chromadb_freeze').is(':checked');
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onAutoAdjustInput() {
|
||||
extension_settings.chromadb.auto_adjust = $('#chromadb_auto_adjust').is(':checked');
|
||||
enableDisableSliders();
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function enableDisableSliders() {
|
||||
if (extension_settings.chromadb.auto_adjust) {
|
||||
$('#chromadb_keep_context').prop('disabled', true).css('opacity', 0.5);
|
||||
$('#chromadb_n_results').prop('disabled', true).css('opacity', 0.5);
|
||||
$('#chromadb_keep_context_proportion').prop('disabled', false).css('opacity', 1);
|
||||
}
|
||||
else {
|
||||
$('#chromadb_keep_context').prop('disabled', false).css('opacity', 1);
|
||||
$('#chromadb_n_results').prop('disabled', false).css('opacity', 1);
|
||||
$('#chromadb_keep_context_proportion').prop('disabled', true).css('opacity', 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
function onKeepContextProportionInput() {
|
||||
extension_settings.chromadb.keep_context_proportion = $('#chromadb_keep_context_proportion').val();
|
||||
$('#chromadb_keep_context_proportion_value').text(Math.round(extension_settings.chromadb.keep_context_proportion * 100));
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
jQuery(async () => {
|
||||
const settingsHtml = `
|
||||
<div class="chromadb_settings">
|
||||
@@ -467,11 +686,28 @@ jQuery(async () => {
|
||||
<select id="chromadb_strategy">
|
||||
<option value="original">Replace non-kept chat items with memories</option>
|
||||
<option value="ross">Add memories after chat with a header tag</option>
|
||||
<option value="custom">Add memories at custom depth with custom msg</option>
|
||||
</select>
|
||||
<label for="chromadb_custom_msg" hidden><small>Custom injection message:</small></label>
|
||||
<textarea id="chromadb_custom_msg" hidden class="text_pole textarea_compact" rows="2" placeholder="${defaultSettings.chroma_default_msg}" style="height: 61px; display: none;"></textarea>
|
||||
<label for="chromadb_custom_depth" hidden><small>How deep should the memory messages be injected?: (<span id="chromadb_custom_depth_value"></span>)</small></label>
|
||||
<input id="chromadb_custom_depth" type="range" min="${defaultSettings.chroma_depth_min}" max="${defaultSettings.chroma_depth_max}" step="${defaultSettings.chroma_depth_step}" value="${defaultSettings.chroma_depth}" hidden/>
|
||||
<span>Memory Recall Strategy</span>
|
||||
<select id="chromadb_recall_strategy">
|
||||
<option value="original">Recall only from this chat</option>
|
||||
<option value="multichat">Recall from all character chats (experimental)</option>
|
||||
</select>
|
||||
<span>Memory Sort Strategy</span>
|
||||
<select id="chromadb_sort_strategy">
|
||||
<option value="date">Sort memories by date</option>
|
||||
<option value="distance">Sort memories by relevance</option>
|
||||
</select>
|
||||
<label for="chromadb_keep_context"><small>How many original chat messages to keep: (<span id="chromadb_keep_context_value"></span>) messages</small></label>
|
||||
<input id="chromadb_keep_context" type="range" min="${defaultSettings.keep_context_min}" max="${defaultSettings.keep_context_max}" step="${defaultSettings.keep_context_step}" value="${defaultSettings.keep_context}" />
|
||||
<label for="chromadb_n_results"><small>Maximum number of ChromaDB 'memories' to inject: (<span id="chromadb_n_results_value"></span>) messages</small></label>
|
||||
<input id="chromadb_n_results" type="range" min="${defaultSettings.n_results_min}" max="${defaultSettings.n_results_max}" step="${defaultSettings.n_results_step}" value="${defaultSettings.n_results}" />
|
||||
<label for="chromadb_keep_context_proportion"><small>Keep <span id="chromadb_keep_context_proportion_value"></span>% of in-context chat messages; replace the rest with memories</small></label>
|
||||
<input id="chromadb_keep_context_proportion" type="range" min="${defaultSettings.keep_context_proportion_min}" max="${defaultSettings.keep_context_proportion_max}" step="${defaultSettings.keep_context_proportion_step}" value="${defaultSettings.keep_context_proportion}" />
|
||||
<label for="chromadb_split_length"><small>Max length for each 'memory' pulled from the current chat history: (<span id="chromadb_split_length_value"></span>) characters</small></label>
|
||||
<input id="chromadb_split_length" type="range" min="${defaultSettings.split_length_min}" max="${defaultSettings.split_length_max}" step="${defaultSettings.split_length_step}" value="${defaultSettings.split_length}" />
|
||||
<label for="chromadb_file_split_length"><small>Max length for each 'memory' pulled from imported text files: (<span id="chromadb_file_split_length_value"></span>) characters</small></label>
|
||||
@@ -480,6 +716,10 @@ jQuery(async () => {
|
||||
<input type="checkbox" id="chromadb_freeze" />
|
||||
<span>Freeze ChromaDB state</span>
|
||||
</label>
|
||||
<label class="checkbox_label for="chromadb_auto_adjust" title="Automatically adjusts the number of messages to keep based on the average number of messages in the current chat and the chosen proportion.">
|
||||
<input type="checkbox" id="chromadb_auto_adjust" />
|
||||
<span>Use % strategy</span>
|
||||
</label>
|
||||
<div class="flex-container spaceEvenly">
|
||||
<div id="chromadb_inject" title="Upload custom textual data to use in the context of the current chat" class="menu_button">
|
||||
<i class="fa-solid fa-file-arrow-up"></i>
|
||||
@@ -506,8 +746,12 @@ jQuery(async () => {
|
||||
|
||||
$('#extensions_settings2').append(settingsHtml);
|
||||
$('#chromadb_strategy').on('change', onStrategyChange);
|
||||
$('#chromadb_recall_strategy').on('change', onRecallStrategyChange);
|
||||
$('#chromadb_sort_strategy').on('change', onSortStrategyChange);
|
||||
$('#chromadb_keep_context').on('input', onKeepContextInput);
|
||||
$('#chromadb_n_results').on('input', onNResultsInput);
|
||||
$('#chromadb_custom_depth').on('input', onChromaDepthInput);
|
||||
$('#chromadb_custom_msg').on('input', onChromaMsgInput);
|
||||
$('#chromadb_split_length').on('input', onSplitLengthInput);
|
||||
$('#chromadb_file_split_length').on('input', onFileSplitLengthInput);
|
||||
$('#chromadb_inject').on('click', () => $('#chromadb_inject_file').trigger('click'));
|
||||
@@ -517,6 +761,8 @@ jQuery(async () => {
|
||||
$('#chromadb_purge').on('click', onPurgeClick);
|
||||
$('#chromadb_export').on('click', onExportClick);
|
||||
$('#chromadb_freeze').on('input', onFreezeInput);
|
||||
$('#chromadb_auto_adjust').on('input', onAutoAdjustInput);
|
||||
$('#chromadb_keep_context_proportion').on('input', onKeepContextProportionInput);
|
||||
await loadSettings();
|
||||
|
||||
// Not sure if this is needed, but it's here just in case
|
||||
|
@@ -1,10 +1,9 @@
|
||||
import { getStringHash, debounce } from "../../utils.js";
|
||||
import { getContext, getApiUrl, extension_settings, ModuleWorkerWrapper, doExtrasFetch } from "../../extensions.js";
|
||||
import { extension_prompt_types, is_send_press, saveSettingsDebounced } from "../../../script.js";
|
||||
import { getStringHash, debounce, waitUntilCondition } from "../../utils.js";
|
||||
import { getContext, getApiUrl, extension_settings, doExtrasFetch, modules } from "../../extensions.js";
|
||||
import { eventSource, event_types, extension_prompt_types, generateQuietPrompt, is_send_press, saveSettingsDebounced, substituteParams } from "../../../script.js";
|
||||
export { MODULE_NAME };
|
||||
|
||||
const MODULE_NAME = '1_memory';
|
||||
const UPDATE_INTERVAL = 5000;
|
||||
|
||||
let lastCharacterId = null;
|
||||
let lastGroupId = null;
|
||||
@@ -13,9 +12,16 @@ let lastMessageHash = null;
|
||||
let lastMessageId = null;
|
||||
let inApiCall = false;
|
||||
|
||||
const formatMemoryValue = (value) => value ? `Context: ${value.trim()}` : '';
|
||||
const formatMemoryValue = (value) => value ? `Summary: ${value.trim()}` : '';
|
||||
const saveChatDebounced = debounce(() => getContext().saveChat(), 2000);
|
||||
|
||||
const summary_sources = {
|
||||
'extras': 'extras',
|
||||
'main': 'main',
|
||||
};
|
||||
|
||||
const defaultPrompt = '[Pause your roleplay. Summarize the most important facts and events that have happened in the chat so far. If a summary already exists in your memory, use that as a base and expand with new facts. Limit the summary to {{words}} words or less. Your response should include nothing but the summary.]';
|
||||
|
||||
const defaultSettings = {
|
||||
minLongMemory: 16,
|
||||
maxLongMemory: 1024,
|
||||
@@ -38,6 +44,16 @@ const defaultSettings = {
|
||||
maxLengthPenalty: 4,
|
||||
lengthPenaltyStep: 0.1,
|
||||
memoryFrozen: false,
|
||||
source: summary_sources.extras,
|
||||
prompt: defaultPrompt,
|
||||
promptWords: 200,
|
||||
promptMinWords: 25,
|
||||
promptMaxWords: 1000,
|
||||
promptWordsStep: 25,
|
||||
promptInterval: 10,
|
||||
promptMinInterval: 1,
|
||||
promptMaxInterval: 100,
|
||||
promptIntervalStep: 1,
|
||||
};
|
||||
|
||||
function loadSettings() {
|
||||
@@ -45,12 +61,42 @@ function loadSettings() {
|
||||
Object.assign(extension_settings.memory, defaultSettings);
|
||||
}
|
||||
|
||||
if (extension_settings.memory.source === undefined) {
|
||||
extension_settings.memory.source = defaultSettings.source;
|
||||
}
|
||||
|
||||
if (extension_settings.memory.prompt === undefined) {
|
||||
extension_settings.memory.prompt = defaultSettings.prompt;
|
||||
}
|
||||
|
||||
if (extension_settings.memory.promptWords === undefined) {
|
||||
extension_settings.memory.promptWords = defaultSettings.promptWords;
|
||||
}
|
||||
|
||||
if (extension_settings.memory.promptInterval === undefined) {
|
||||
extension_settings.memory.promptInterval = defaultSettings.promptInterval;
|
||||
}
|
||||
|
||||
$('#summary_source').val(extension_settings.memory.source).trigger('change');
|
||||
$('#memory_long_length').val(extension_settings.memory.longMemoryLength).trigger('input');
|
||||
$('#memory_short_length').val(extension_settings.memory.shortMemoryLength).trigger('input');
|
||||
$('#memory_repetition_penalty').val(extension_settings.memory.repetitionPenalty).trigger('input');
|
||||
$('#memory_temperature').val(extension_settings.memory.temperature).trigger('input');
|
||||
$('#memory_length_penalty').val(extension_settings.memory.lengthPenalty).trigger('input');
|
||||
$('#memory_frozen').prop('checked', extension_settings.memory.memoryFrozen).trigger('input');
|
||||
$('#memory_prompt').val(extension_settings.memory.prompt).trigger('input');
|
||||
$('#memory_prompt_words').val(extension_settings.memory.promptWords).trigger('input');
|
||||
$('#memory_prompt_interval').val(extension_settings.memory.promptInterval).trigger('input');
|
||||
}
|
||||
|
||||
function onSummarySourceChange(event) {
|
||||
const value = event.target.value;
|
||||
extension_settings.memory.source = value;
|
||||
$('#memory_settings [data-source]').each((_, element) => {
|
||||
const source = $(element).data('source');
|
||||
$(element).toggle(source === value);
|
||||
});
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onMemoryShortInput() {
|
||||
@@ -104,6 +150,26 @@ function onMemoryFrozenInput() {
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onMemoryPromptWordsInput() {
|
||||
const value = $(this).val();
|
||||
extension_settings.memory.promptWords = Number(value);
|
||||
$('#memory_prompt_words_value').text(extension_settings.memory.promptWords);
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onMemoryPromptIntervalInput() {
|
||||
const value = $(this).val();
|
||||
extension_settings.memory.promptInterval = Number(value);
|
||||
$('#memory_prompt_interval_value').text(extension_settings.memory.promptInterval);
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onMemoryPromptInput() {
|
||||
const value = $(this).val();
|
||||
extension_settings.memory.prompt = value;
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function saveLastValues() {
|
||||
const context = getContext();
|
||||
lastGroupId = context.groupId;
|
||||
@@ -129,7 +195,14 @@ function getLatestMemoryFromChat(chat) {
|
||||
return '';
|
||||
}
|
||||
|
||||
async function moduleWorker() {
|
||||
async function onChatEvent() {
|
||||
// Module not enabled
|
||||
if (extension_settings.memory.source === summary_sources.extras) {
|
||||
if (!modules.includes('summarize')) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const context = getContext();
|
||||
const chat = context.chat;
|
||||
|
||||
@@ -187,7 +260,93 @@ async function moduleWorker() {
|
||||
}
|
||||
}
|
||||
|
||||
async function forceSummarizeChat() {
|
||||
const context = getContext();
|
||||
|
||||
if (!context.chatId) {
|
||||
toastr.warning('No chat selected');
|
||||
return;
|
||||
}
|
||||
|
||||
toastr.info('Summarizing chat...', 'Please wait');
|
||||
const value = await summarizeChatMain(context, true);
|
||||
|
||||
if (!value) {
|
||||
toastr.warning('Failed to summarize chat');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async function summarizeChat(context) {
|
||||
switch (extension_settings.memory.source) {
|
||||
case summary_sources.extras:
|
||||
await summarizeChatExtras(context);
|
||||
break;
|
||||
case summary_sources.main:
|
||||
await summarizeChatMain(context, false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async function summarizeChatMain(context, force) {
|
||||
try {
|
||||
// Wait for the send button to be released
|
||||
waitUntilCondition(() => is_send_press === false, 10000, 100);
|
||||
} catch {
|
||||
console.debug('Timeout waiting for is_send_press');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!context.chat.length) {
|
||||
console.debug('No messages in chat to summarize');
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.chat.length < extension_settings.memory.promptInterval && !force) {
|
||||
console.debug(`Not enough messages in chat to summarize (chat: ${context.chat.length}, interval: ${extension_settings.memory.promptInterval})`);
|
||||
return;
|
||||
}
|
||||
|
||||
let messagesSinceLastSummary = 0;
|
||||
for (let i = context.chat.length - 1; i >= 0; i--) {
|
||||
if (context.chat[i].extra && context.chat[i].extra.memory) {
|
||||
break;
|
||||
}
|
||||
messagesSinceLastSummary++;
|
||||
}
|
||||
|
||||
if (messagesSinceLastSummary < extension_settings.memory.promptInterval && !force) {
|
||||
console.debug(`Not enough messages since last summary (messages: ${messagesSinceLastSummary}, interval: ${extension_settings.memory.promptInterval}`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Summarizing chat, messages since last summary: ' + messagesSinceLastSummary);
|
||||
const prompt = substituteParams(extension_settings.memory.prompt)
|
||||
.replace(/{{words}}/gi, extension_settings.memory.promptWords);
|
||||
|
||||
if (!prompt) {
|
||||
console.debug('Summarization prompt is empty. Skipping summarization.');
|
||||
return;
|
||||
}
|
||||
|
||||
const summary = await generateQuietPrompt(prompt);
|
||||
const newContext = getContext();
|
||||
|
||||
// something changed during summarization request
|
||||
if (newContext.groupId !== context.groupId
|
||||
|| newContext.chatId !== context.chatId
|
||||
|| (!newContext.groupId && (newContext.characterId !== context.characterId))) {
|
||||
console.log('Context changed, summary discarded');
|
||||
return;
|
||||
}
|
||||
|
||||
setMemoryContext(summary, true);
|
||||
return summary;
|
||||
}
|
||||
|
||||
async function summarizeChatExtras(context) {
|
||||
function getMemoryString() {
|
||||
return (longMemory + '\n\n' + memoryBuffer.slice().reverse().join('\n\n')).trim();
|
||||
}
|
||||
@@ -301,6 +460,7 @@ function setMemoryContext(value, saveToMessage) {
|
||||
const context = getContext();
|
||||
context.setExtensionPrompt(MODULE_NAME, formatMemoryValue(value), extension_prompt_types.AFTER_SCENARIO);
|
||||
$('#memory_contents').val(value);
|
||||
console.log('Memory set to: ' + value);
|
||||
|
||||
if (saveToMessage && context.chat.length) {
|
||||
const idx = context.chat.length - 2;
|
||||
@@ -315,40 +475,55 @@ function setMemoryContext(value, saveToMessage) {
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
jQuery(function () {
|
||||
function addExtensionControls() {
|
||||
const settingsHtml = `
|
||||
<div id="memory_settings">
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>Summarize</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<label for="memory_contents">Current summary: </label>
|
||||
<textarea id="memory_contents" class="text_pole" rows="8" placeholder="Context will be generated here..."></textarea>
|
||||
<div class="memory_contents_controls">
|
||||
<input id="memory_restore" class="menu_button" type="submit" value="Restore previous state" />
|
||||
<label for="memory_frozen"><input id="memory_frozen" type="checkbox" />Stop summarization updates</label>
|
||||
</div>
|
||||
<!--</div>
|
||||
</div>
|
||||
<div class="inline-drawer">
|
||||
<div class="inline-drawer-toggle inline-drawer-header">
|
||||
<b>Summarization parameters</b>
|
||||
<b>Summarize</b>
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">-->
|
||||
<label for="memory_short_length">Chat to Summarize buffer length (<span id="memory_short_length_tokens"></span> tokens)</label>
|
||||
<input id="memory_short_length" type="range" value="${defaultSettings.shortMemoryLength}" min="${defaultSettings.minShortMemory}" max="${defaultSettings.maxShortMemory}" step="${defaultSettings.shortMemoryStep}" />
|
||||
<label for="memory_long_length">Summary output length (<span id="memory_long_length_tokens"></span> tokens)</label>
|
||||
<input id="memory_long_length" type="range" value="${defaultSettings.longMemoryLength}" min="${defaultSettings.minLongMemory}" max="${defaultSettings.maxLongMemory}" step="${defaultSettings.longMemoryStep}" />
|
||||
<label for="memory_temperature">Temperature (<span id="memory_temperature_value"></span>)</label>
|
||||
<input id="memory_temperature" type="range" value="${defaultSettings.temperature}" min="${defaultSettings.minTemperature}" max="${defaultSettings.maxTemperature}" step="${defaultSettings.temperatureStep}" />
|
||||
<label for="memory_repetition_penalty">Repetition penalty (<span id="memory_repetition_penalty_value"></span>)</label>
|
||||
<input id="memory_repetition_penalty" type="range" value="${defaultSettings.repetitionPenalty}" min="${defaultSettings.minRepetitionPenalty}" max="${defaultSettings.maxRepetitionPenalty}" step="${defaultSettings.repetitionPenaltyStep}" />
|
||||
<label for="memory_length_penalty">Length preference <small>[higher = longer summaries]</small> (<span id="memory_length_penalty_value"></span>)</label>
|
||||
<input id="memory_length_penalty" type="range" value="${defaultSettings.lengthPenalty}" min="${defaultSettings.minLengthPenalty}" max="${defaultSettings.maxLengthPenalty}" step="${defaultSettings.lengthPenaltyStep}" />
|
||||
<div class="inline-drawer-content">
|
||||
<label for="summary_source">Summarization source:</label>
|
||||
<select id="summary_source">
|
||||
<option value="main">Main API</option>
|
||||
<option value="extras">Extras API</option>
|
||||
</select>
|
||||
<label for="memory_contents">Current summary: </label>
|
||||
<textarea id="memory_contents" class="text_pole textarea_compact" rows="6" placeholder="Summary will be generated here..."></textarea>
|
||||
<div class="memory_contents_controls">
|
||||
<input id="memory_restore" class="menu_button" type="button" value="Restore previous state" />
|
||||
<label for="memory_frozen"><input id="memory_frozen" type="checkbox" />Pause summarization</label>
|
||||
</div>
|
||||
<div data-source="main" class="memory_contents_controls">
|
||||
</div>
|
||||
<div data-source="main">
|
||||
<label for="memory_prompt" class="title_restorable">
|
||||
Summarization Prompt
|
||||
<div id="memory_force_summarize" class="menu_button menu_button_icon">
|
||||
<i class="fa-solid fa-database"></i>
|
||||
<span>Generate now</span>
|
||||
</div>
|
||||
</label>
|
||||
<textarea id="memory_prompt" class="text_pole textarea_compact" rows="6" placeholder="This prompt will be used in summary generation. Insert {{words}} macro to use the "Number of words" parameter."></textarea>
|
||||
<label for="memory_prompt_words">Number of words in the summary (<span id="memory_prompt_words_value"></span> words)</label>
|
||||
<input id="memory_prompt_words" type="range" value="${defaultSettings.promptWords}" min="${defaultSettings.promptMinWords}" max="${defaultSettings.promptMaxWords}" step="${defaultSettings.promptWordsStep}" />
|
||||
<label for="memory_prompt_interval">Update interval (<span id="memory_prompt_interval_value"></span> messages)</label>
|
||||
<input id="memory_prompt_interval" type="range" value="${defaultSettings.promptInterval}" min="${defaultSettings.promptMinInterval}" max="${defaultSettings.promptMaxInterval}" step="${defaultSettings.promptIntervalStep}" />
|
||||
</div>
|
||||
<div data-source="extras">
|
||||
<label for="memory_short_length">Chat to Summarize buffer length (<span id="memory_short_length_tokens"></span> tokens)</label>
|
||||
<input id="memory_short_length" type="range" value="${defaultSettings.shortMemoryLength}" min="${defaultSettings.minShortMemory}" max="${defaultSettings.maxShortMemory}" step="${defaultSettings.shortMemoryStep}" />
|
||||
<label for="memory_long_length">Summary output length (<span id="memory_long_length_tokens"></span> tokens)</label>
|
||||
<input id="memory_long_length" type="range" value="${defaultSettings.longMemoryLength}" min="${defaultSettings.minLongMemory}" max="${defaultSettings.maxLongMemory}" step="${defaultSettings.longMemoryStep}" />
|
||||
<label for="memory_temperature">Temperature (<span id="memory_temperature_value"></span>)</label>
|
||||
<input id="memory_temperature" type="range" value="${defaultSettings.temperature}" min="${defaultSettings.minTemperature}" max="${defaultSettings.maxTemperature}" step="${defaultSettings.temperatureStep}" />
|
||||
<label for="memory_repetition_penalty">Repetition penalty (<span id="memory_repetition_penalty_value"></span>)</label>
|
||||
<input id="memory_repetition_penalty" type="range" value="${defaultSettings.repetitionPenalty}" min="${defaultSettings.minRepetitionPenalty}" max="${defaultSettings.maxRepetitionPenalty}" step="${defaultSettings.repetitionPenaltyStep}" />
|
||||
<label for="memory_length_penalty">Length preference <small>[higher = longer summaries]</small> (<span id="memory_length_penalty_value"></span>)</label>
|
||||
<input id="memory_length_penalty" type="range" value="${defaultSettings.lengthPenalty}" min="${defaultSettings.minLengthPenalty}" max="${defaultSettings.maxLengthPenalty}" step="${defaultSettings.lengthPenaltyStep}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -362,10 +537,18 @@ $(document).ready(function () {
|
||||
$('#memory_temperature').on('input', onMemoryTemperatureInput);
|
||||
$('#memory_length_penalty').on('input', onMemoryLengthPenaltyInput);
|
||||
$('#memory_frozen').on('input', onMemoryFrozenInput);
|
||||
$('#summary_source').on('change', onSummarySourceChange);
|
||||
$('#memory_prompt_words').on('input', onMemoryPromptWordsInput);
|
||||
$('#memory_prompt_interval').on('input', onMemoryPromptIntervalInput);
|
||||
$('#memory_prompt').on('input', onMemoryPromptInput);
|
||||
$('#memory_force_summarize').on('click', forceSummarizeChat);
|
||||
}
|
||||
|
||||
addExtensionControls();
|
||||
loadSettings();
|
||||
const wrapper = new ModuleWorkerWrapper(moduleWorker);
|
||||
setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL);
|
||||
eventSource.on(event_types.MESSAGE_RECEIVED, onChatEvent);
|
||||
eventSource.on(event_types.MESSAGE_DELETED, onChatEvent);
|
||||
eventSource.on(event_types.MESSAGE_EDITED, onChatEvent);
|
||||
eventSource.on(event_types.MESSAGE_SWIPED, onChatEvent);
|
||||
eventSource.on(event_types.CHAT_CHANGED, onChatEvent);
|
||||
});
|
||||
|
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"display_name": "Memory",
|
||||
"loading_order": 9,
|
||||
"requires": [
|
||||
"requires": [],
|
||||
"optional": [
|
||||
"summarize"
|
||||
],
|
||||
"optional": [],
|
||||
"js": "index.js",
|
||||
"css": "style.css",
|
||||
"author": "Cohee#1207",
|
||||
|
@@ -8,17 +8,9 @@ const MODULE_NAME = 'quick-reply';
|
||||
const UPDATE_INTERVAL = 1000;
|
||||
|
||||
const defaultSettings = {
|
||||
quickReply1Mes: '',
|
||||
quickReply1Label: '',
|
||||
quickReply2Mes: '',
|
||||
quickReply2Label: '',
|
||||
quickReply3Mes: '',
|
||||
quickReply3Label: '',
|
||||
quickReply4Mes: '',
|
||||
quickReply4Label: '',
|
||||
quickReply5Mes: '',
|
||||
quickReply5Label: '',
|
||||
quickReplyEnabled: false,
|
||||
numberOfSlots: 5,
|
||||
quickReplySlots: [],
|
||||
}
|
||||
|
||||
async function loadSettings() {
|
||||
@@ -26,33 +18,43 @@ async function loadSettings() {
|
||||
Object.assign(extension_settings.quickReply, defaultSettings);
|
||||
}
|
||||
|
||||
// If the user has an old version of the extension, update it
|
||||
if (!Array.isArray(extension_settings.quickReply.quickReplySlots)) {
|
||||
extension_settings.quickReply.quickReplySlots = [];
|
||||
extension_settings.quickReply.numberOfSlots = defaultSettings.numberOfSlots;
|
||||
|
||||
for (let i = 1; i <= extension_settings.quickReply.numberOfSlots; i++) {
|
||||
extension_settings.quickReply.quickReplySlots.push({
|
||||
mes: extension_settings.quickReply[`quickReply${i}Mes`],
|
||||
label: extension_settings.quickReply[`quickReply${i}Label`],
|
||||
enabled: true,
|
||||
});
|
||||
|
||||
delete extension_settings.quickReply[`quickReply${i}Mes`];
|
||||
delete extension_settings.quickReply[`quickReply${i}Label`];
|
||||
}
|
||||
}
|
||||
|
||||
generateQuickReplyElements();
|
||||
|
||||
for (let i = 1; i <= extension_settings.quickReply.numberOfSlots; i++) {
|
||||
$(`#quickReply${i}Mes`).val(extension_settings.quickReply.quickReplySlots[i - 1]?.mes).trigger('input');
|
||||
$(`#quickReply${i}Label`).val(extension_settings.quickReply.quickReplySlots[i - 1]?.label).trigger('input');
|
||||
}
|
||||
|
||||
$('#quickReplyEnabled').prop('checked', extension_settings.quickReply.quickReplyEnabled);
|
||||
|
||||
$('#quickReply1Mes').val(extension_settings.quickReply.quickReply1Mes).trigger('input');
|
||||
$('#quickReply1Label').val(extension_settings.quickReply.quickReply1Label).trigger('input');
|
||||
|
||||
$('#quickReply2Mes').val(extension_settings.quickReply.quickReply2Mes).trigger('input');
|
||||
$('#quickReply2Label').val(extension_settings.quickReply.quickReply2Label).trigger('input');
|
||||
|
||||
$('#quickReply3Mes').val(extension_settings.quickReply.quickReply3Mes).trigger('input');
|
||||
$('#quickReply3Label').val(extension_settings.quickReply.quickReply3Label).trigger('input');
|
||||
|
||||
$('#quickReply4Mes').val(extension_settings.quickReply.quickReply4Mes).trigger('input');
|
||||
$('#quickReply4Label').val(extension_settings.quickReply.quickReply4Label).trigger('input');
|
||||
|
||||
$('#quickReply5Mes').val(extension_settings.quickReply.quickReply5Mes).trigger('input');
|
||||
$('#quickReply5Label').val(extension_settings.quickReply.quickReply5Label).trigger('input');
|
||||
$('#quickReplyNumberOfSlots').val(extension_settings.quickReply.numberOfSlots);
|
||||
}
|
||||
|
||||
function onQuickReplyInput(id) {
|
||||
extension_settings.quickReply[`quickReply${id}Mes`] = $(`#quickReply${id}Mes`).val();
|
||||
extension_settings.quickReply.quickReplySlots[id - 1].mes = $(`#quickReply${id}Mes`).val();
|
||||
$(`#quickReply${id}`).attr('title', ($(`#quickReply${id}Mes`).val()));
|
||||
resetScrollHeight($(`#quickReply${id}Mes`));
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onQuickReplyLabelInput(id) {
|
||||
extension_settings.quickReply[`quickReply${id}Label`] = $(`#quickReply${id}Label`).val();
|
||||
extension_settings.quickReply.quickReplySlots[id - 1].label = $(`#quickReply${id}Label`).val();
|
||||
$(`#quickReply${id}`).text($(`#quickReply${id}Label`).val());
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
@@ -66,44 +68,111 @@ async function onQuickReplyEnabledInput() {
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
async function sendQuickReply(id) {
|
||||
var prompt = extension_settings.quickReply[`${id}Mes`];
|
||||
async function sendQuickReply(index) {
|
||||
const prompt = extension_settings.quickReply.quickReplySlots[index]?.mes || '';
|
||||
|
||||
if (!prompt) {
|
||||
console.warn(`Quick reply slot ${index} is empty! Aborting.`);
|
||||
return;
|
||||
}
|
||||
|
||||
$("#send_textarea").val(prompt);
|
||||
$("#send_but").trigger('click');
|
||||
}
|
||||
|
||||
function addQuickReplyBar(numButtons) {
|
||||
var numButtons = 5;
|
||||
const quickReplyBarStartHtml = `
|
||||
<div id="quickReplyBar" class="flex-container flexGap5">
|
||||
<div id="quickReplies">
|
||||
`;
|
||||
function addQuickReplyBar() {
|
||||
$('#quickReplyBar').remove();
|
||||
let quickReplyButtonHtml = '';
|
||||
for (let i = 0; i < numButtons; i++) {
|
||||
let quickReplyMes = extension_settings.quickReply[`quickReply${i + 1}Mes`];
|
||||
let quickReplyLabel = extension_settings.quickReply[`quickReply${i + 1}Label`];
|
||||
//console.log(quickReplyMes);
|
||||
quickReplyButtonHtml += `<div title="${quickReplyMes}" class="quickReplyButton" id="quickReply${i + 1}">${quickReplyLabel}</div>`;
|
||||
|
||||
for (let i = 0; i < extension_settings.quickReply.numberOfSlots; i++) {
|
||||
let quickReplyMes = extension_settings.quickReply.quickReplySlots[i]?.mes || '';
|
||||
let quickReplyLabel = extension_settings.quickReply.quickReplySlots[i]?.label || '';
|
||||
quickReplyButtonHtml += `<div title="${quickReplyMes}" class="quickReplyButton" data-index="${i}" id="quickReply${i + 1}">${quickReplyLabel}</div>`;
|
||||
}
|
||||
const quickReplyEndHtml = `</div></div>`
|
||||
const quickReplyBarFullHtml = [quickReplyBarStartHtml, quickReplyButtonHtml, quickReplyEndHtml].join('');
|
||||
|
||||
const quickReplyBarFullHtml = `
|
||||
<div id="quickReplyBar" class="flex-container flexGap5">
|
||||
<div id="quickReplies">
|
||||
${quickReplyButtonHtml}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
$('#send_form').prepend(quickReplyBarFullHtml);
|
||||
|
||||
$('.quickReplyButton').on('click', function () {
|
||||
console.log('got quick reply click');
|
||||
let quickReplyButtonID = $(this).attr('id');
|
||||
sendQuickReply(quickReplyButtonID);
|
||||
let index = $(this).data('index');
|
||||
sendQuickReply(index);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async function moduleWorker() {
|
||||
if (extension_settings.quickReply.quickReplyEnabled === true) {
|
||||
$('#quickReplyBar').toggle(getContext().onlineStatus !== 'no_connection');
|
||||
}
|
||||
}
|
||||
|
||||
async function onQuickReplyNumberOfSlotsInput() {
|
||||
const $input = $('#quickReplyNumberOfSlots');
|
||||
let numberOfSlots = Number($input.val());
|
||||
|
||||
if (isNaN(numberOfSlots)) {
|
||||
numberOfSlots = defaultSettings.numberOfSlots;
|
||||
}
|
||||
|
||||
// Clamp min and max values (from input attributes)
|
||||
if (numberOfSlots < Number($input.attr('min'))) {
|
||||
numberOfSlots = Number($input.attr('min'));
|
||||
} else if (numberOfSlots > Number($input.attr('max'))) {
|
||||
numberOfSlots = Number($input.attr('max'));
|
||||
}
|
||||
|
||||
extension_settings.quickReply.numberOfSlots = numberOfSlots;
|
||||
extension_settings.quickReply.quickReplySlots.length = numberOfSlots;
|
||||
|
||||
// Initialize new slots
|
||||
for (let i = 0; i < numberOfSlots; i++) {
|
||||
if (!extension_settings.quickReply.quickReplySlots[i]) {
|
||||
extension_settings.quickReply.quickReplySlots[i] = {
|
||||
mes: '',
|
||||
label: '',
|
||||
enabled: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
await loadSettings();
|
||||
addQuickReplyBar();
|
||||
moduleWorker();
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function generateQuickReplyElements() {
|
||||
let quickReplyHtml = '';
|
||||
|
||||
for (let i = 1; i <= extension_settings.quickReply.numberOfSlots; i++) {
|
||||
quickReplyHtml += `
|
||||
<div class="flex-container alignitemsflexstart">
|
||||
<input class="text_pole wide30p" id="quickReply${i}Label" placeholder="(Add a button label)">
|
||||
<textarea id="quickReply${i}Mes" placeholder="(custom message here)" class="text_pole widthUnset flex1" rows="2"></textarea>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
$('#quickReplyContainer').empty().append(quickReplyHtml);
|
||||
|
||||
for (let i = 1; i <= extension_settings.quickReply.numberOfSlots; i++) {
|
||||
$(`#quickReply${i}Mes`).on('input', function () { onQuickReplyInput(i); });
|
||||
$(`#quickReply${i}Label`).on('input', function () { onQuickReplyLabelInput(i); });
|
||||
}
|
||||
|
||||
$('.quickReplySettings .inline-drawer-toggle').off('click').on('click', function () {
|
||||
for (let i = 1; i <= extension_settings.quickReply.numberOfSlots; i++) {
|
||||
initScrollHeight($(`#quickReply${i}Mes`));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
jQuery(async () => {
|
||||
|
||||
moduleWorker();
|
||||
@@ -116,57 +185,28 @@ jQuery(async () => {
|
||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<label class="checkbox_label">
|
||||
<label class="checkbox_label marginBot10">
|
||||
<input id="quickReplyEnabled" type="checkbox" />
|
||||
Enable Quick Replies
|
||||
</label>
|
||||
<label for="quickReplyNumberOfSlots">Number of slots:</label>
|
||||
<div class="flex-container flexGap5 flexnowrap">
|
||||
<input id="quickReplyNumberOfSlots" class="text_pole" type="number" min="1" max="100" value="" />
|
||||
<div class="menu_button menu_button_icon" id="quickReplyNumberOfSlotsApply">
|
||||
<div class="fa-solid fa-check"></div>
|
||||
<span>Apply</span>
|
||||
</div>
|
||||
</div>
|
||||
<small><i>Customize your Quick Replies:</i></small><br>
|
||||
<div class="flex-container alignitemsflexstart">
|
||||
<input class="text_pole wide30p" id="quickReply1Label" placeholder="(Add a button label)">
|
||||
<textarea id="quickReply1Mes" placeholder="(custom message here)" class="text_pole textarea_compact widthUnset flex1" rows="2"></textarea>
|
||||
</div>
|
||||
<div class="flex-container alignitemsflexstart">
|
||||
<input class="text_pole wide30p" id="quickReply2Label" placeholder="(Add a button label)">
|
||||
<textarea id="quickReply2Mes" placeholder="(custom message here)" class="text_pole textarea_compact widthUnset flex1" rows="2"></textarea>
|
||||
</div>
|
||||
<div class="flex-container alignitemsflexstart">
|
||||
<input class="text_pole wide30p" id="quickReply3Label" placeholder="(Add a button label)">
|
||||
<textarea id="quickReply3Mes" placeholder="(custom message here)" class="text_pole textarea_compact widthUnset flex1" rows="2"></textarea>
|
||||
</div>
|
||||
<div class="flex-container alignitemsflexstart">
|
||||
<input class="text_pole wide30p" id="quickReply4Label" placeholder="(Add a button label)">
|
||||
<textarea id="quickReply4Mes" placeholder="(custom message here)" class="text_pole textarea_compact widthUnset flex1" rows="2"></textarea>
|
||||
</div>
|
||||
<div class="flex-container alignitemsflexstart">
|
||||
<input class="text_pole wide30p" id="quickReply5Label" placeholder="(Add a button label)">
|
||||
<textarea id="quickReply5Mes" placeholder="(custom message here)" class="text_pole textarea_compact widthUnset flex1" rows="2"></textarea>
|
||||
<div id="quickReplyContainer">
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
$('#extensions_settings2').append(settingsHtml);
|
||||
|
||||
$('#quickReply1Mes').on('input', function () { onQuickReplyInput(1); });
|
||||
$('#quickReply2Mes').on('input', function () { onQuickReplyInput(2); });
|
||||
$('#quickReply3Mes').on('input', function () { onQuickReplyInput(3); });
|
||||
$('#quickReply4Mes').on('input', function () { onQuickReplyInput(4); });
|
||||
$('#quickReply5Mes').on('input', function () { onQuickReplyInput(5); });
|
||||
|
||||
$('#quickReply1Label').on('input', function () { onQuickReplyLabelInput(1); });
|
||||
$('#quickReply2Label').on('input', function () { onQuickReplyLabelInput(2); });
|
||||
$('#quickReply3Label').on('input', function () { onQuickReplyLabelInput(3); });
|
||||
$('#quickReply4Label').on('input', function () { onQuickReplyLabelInput(4); });
|
||||
$('#quickReply5Label').on('input', function () { onQuickReplyLabelInput(5); });
|
||||
|
||||
$('#quickReplyEnabled').on('input', onQuickReplyEnabledInput);
|
||||
|
||||
$('.quickReplySettings .inline-drawer-toggle').on('click', function () {
|
||||
initScrollHeight($("#quickReply1Mes"));
|
||||
initScrollHeight($("#quickReply2Mes"));
|
||||
initScrollHeight($("#quickReply3Mes"));
|
||||
initScrollHeight($("#quickReply4Mes"));
|
||||
initScrollHeight($("#quickReply5Mes"));
|
||||
})
|
||||
$('#quickReplyNumberOfSlotsApply').on('click', onQuickReplyNumberOfSlotsInput);
|
||||
|
||||
await loadSettings();
|
||||
addQuickReplyBar();
|
||||
|
@@ -10,6 +10,8 @@
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
display: none;
|
||||
max-width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
#quickReplies {
|
||||
@@ -17,7 +19,7 @@
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: nowrap;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -41,4 +43,4 @@
|
||||
opacity: 1;
|
||||
filter: brightness(1.2);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
@@ -11,7 +11,7 @@ import {
|
||||
appendImageToMessage
|
||||
} from "../../../script.js";
|
||||
import { getApiUrl, getContext, extension_settings, doExtrasFetch, modules } from "../../extensions.js";
|
||||
import { stringFormat, initScrollHeight, resetScrollHeight } from "../../utils.js";
|
||||
import { stringFormat, initScrollHeight, resetScrollHeight, timestampToMoment } from "../../utils.js";
|
||||
export { MODULE_NAME };
|
||||
|
||||
// Wraps a string into monospace font-face span
|
||||
@@ -557,7 +557,7 @@ async function sendMessage(prompt, image) {
|
||||
is_system: context.groupId ? true : false,
|
||||
is_user: false,
|
||||
is_name: true,
|
||||
send_date: Date.now(),
|
||||
send_date: timestampToMoment(Date.now()).format('LL LT'),
|
||||
mes: context.groupId ? p(messageText) : messageText,
|
||||
extra: {
|
||||
image: image,
|
||||
|
@@ -9,6 +9,7 @@ import {
|
||||
updateMessageBlock,
|
||||
} from "../../../script.js";
|
||||
import { extension_settings, getContext } from "../../extensions.js";
|
||||
import { secret_state, writeSecret } from "../../secrets.js";
|
||||
|
||||
const autoModeOptions = {
|
||||
NONE: 'none',
|
||||
@@ -134,6 +135,14 @@ const languageCodes = {
|
||||
'Zulu': 'zu',
|
||||
};
|
||||
|
||||
const KEY_REQUIRED = ['deepl'];
|
||||
|
||||
function showKeyButton() {
|
||||
const providerRequiresKey = KEY_REQUIRED.includes(extension_settings.translate.provider);
|
||||
$("#translate_key_button").toggle(providerRequiresKey);
|
||||
$("#translate_key_button").toggleClass('success', Boolean(secret_state[extension_settings.translate.provider]));
|
||||
}
|
||||
|
||||
function loadSettings() {
|
||||
for (const key in defaultSettings) {
|
||||
if (!extension_settings.translate.hasOwnProperty(key)) {
|
||||
@@ -144,6 +153,7 @@ function loadSettings() {
|
||||
$(`#translation_provider option[value="${extension_settings.translate.provider}"]`).attr('selected', true);
|
||||
$(`#translation_target_language option[value="${extension_settings.translate.target_language}"]`).attr('selected', true);
|
||||
$(`#translation_auto_mode option[value="${extension_settings.translate.auto_mode}"]`).attr('selected', true);
|
||||
showKeyButton();
|
||||
}
|
||||
|
||||
async function translateImpersonate(text) {
|
||||
@@ -186,18 +196,39 @@ async function translateProviderGoogle(text, lang) {
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
|
||||
async function translateProviderDeepl(text, lang) {
|
||||
if (!secret_state.deepl) {
|
||||
throw new Error('No DeepL API key');
|
||||
}
|
||||
|
||||
const response = await fetch('/deepl_translate', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({ text: text, lang: lang }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const result = await response.text();
|
||||
return result;
|
||||
}
|
||||
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
|
||||
async function translate(text, lang) {
|
||||
try {
|
||||
switch (extension_settings.translate.provider) {
|
||||
case 'google':
|
||||
return await translateProviderGoogle(text, lang);
|
||||
case 'deepl':
|
||||
return await translateProviderDeepl(text, lang);
|
||||
default:
|
||||
console.error('Unknown translation provider', extension_settings.translate.provider);
|
||||
return text;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
toastr.error('Failed to translate message');
|
||||
toastr.error(String(error), 'Failed to translate message');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,9 +362,13 @@ jQuery(() => {
|
||||
<option value="both">Translate both</option>
|
||||
</select>
|
||||
<label for="translation_provider">Provider</label>
|
||||
<select id="translation_provider" name="provider">
|
||||
<option value="google">Google</option>
|
||||
<select>
|
||||
<div class="flex-container gap5px flexnowrap marginBot5">
|
||||
<select id="translation_provider" name="provider" class="margin0">
|
||||
<option value="google">Google</option>
|
||||
<option value="deepl">DeepL</option>
|
||||
<select>
|
||||
<div id="translate_key_button" class="menu_button fa-solid fa-key margin0"></div>
|
||||
</div>
|
||||
<label for="translation_target_language">Target Language</label>
|
||||
<select id="translation_target_language" name="target_language"></select>
|
||||
<div id="translation_clear" class="menu_button">
|
||||
@@ -364,6 +399,7 @@ jQuery(() => {
|
||||
});
|
||||
$('#translation_provider').on('change', (event) => {
|
||||
extension_settings.translate.provider = event.target.value;
|
||||
showKeyButton();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
$('#translation_target_language').on('change', (event) => {
|
||||
@@ -371,6 +407,17 @@ jQuery(() => {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
$(document).on('click', '.mes_translate', onMessageTranslateClick);
|
||||
$('#translate_key_button').on('click', async () => {
|
||||
const optionText = $('#translation_provider option:selected').text();
|
||||
const key = await callPopup(`<h3>${optionText} API Key</h3>`, 'input');
|
||||
|
||||
if (key == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
await writeSecret(extension_settings.translate.provider, key);
|
||||
toastr.success('API Key saved');
|
||||
});
|
||||
|
||||
loadSettings();
|
||||
|
||||
|
@@ -2,6 +2,5 @@
|
||||
width: fit-content;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: baseline;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
@@ -57,7 +57,7 @@ export function getPreviewString(lang) {
|
||||
}
|
||||
const fallbackPreview = 'Neque porro quisquam est qui dolorem ipsum quia dolor sit amet'
|
||||
|
||||
return previewStrings[lang] ?? fallbackPreview;
|
||||
return previewStrings[lang] ?? fallbackPreview;
|
||||
}
|
||||
|
||||
let ttsProviders = {
|
||||
@@ -173,7 +173,7 @@ function resetTtsPlayback() {
|
||||
|
||||
// Reset audio element
|
||||
audioElement.currentTime = 0;
|
||||
audioElement.src = '/sounds/silence.mp3';
|
||||
audioElement.src = '';
|
||||
|
||||
// Clear any queue items
|
||||
ttsJobQueue.splice(0, ttsJobQueue.length);
|
||||
@@ -412,7 +412,6 @@ async function processTtsQueue() {
|
||||
|
||||
// Remove character name from start of the line if power user setting is disabled
|
||||
if (char && !power_user.allow_name2_display) {
|
||||
debugger;
|
||||
const escapedChar = escapeRegex(char);
|
||||
text = text.replace(new RegExp(`^${escapedChar}:`, 'gm'), '');
|
||||
}
|
||||
@@ -646,23 +645,23 @@ $(document).ready(function () {
|
||||
<div>
|
||||
<label class="checkbox_label" for="tts_enabled">
|
||||
<input type="checkbox" id="tts_enabled" name="tts_enabled">
|
||||
Enabled
|
||||
<small>Enabled</small>
|
||||
</label>
|
||||
<label class="checkbox_label" for="tts_auto_generation">
|
||||
<input type="checkbox" id="tts_auto_generation">
|
||||
Auto Generation
|
||||
</label>
|
||||
<label class="checkbox_label" for="tts_narrate_dialogues">
|
||||
<input type="checkbox" id="tts_narrate_dialogues">
|
||||
Narrate dialogues only
|
||||
<small>Auto Generation</small>
|
||||
</label>
|
||||
<label class="checkbox_label" for="tts_narrate_quoted">
|
||||
<input type="checkbox" id="tts_narrate_quoted">
|
||||
Narrate quoted only
|
||||
<small>Only narrate "quotes"</small>
|
||||
</label>
|
||||
<label class="checkbox_label" for="tts_narrate_dialogues">
|
||||
<input type="checkbox" id="tts_narrate_dialogues">
|
||||
<small>Ignore *text, even "quotes", inside asterisks*</small>
|
||||
</label>
|
||||
<label class="checkbox_label" for="tts_narrate_translated_only">
|
||||
<input type="checkbox" id="tts_narrate_translated_only">
|
||||
Narrate only the translated text
|
||||
<small>Narrate only the translated text</small>
|
||||
</label>
|
||||
</div>
|
||||
<label>Voice Map</label>
|
||||
@@ -704,26 +703,4 @@ $(document).ready(function () {
|
||||
const wrapper = new ModuleWorkerWrapper(moduleWorker);
|
||||
setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL) // Init depends on all the things
|
||||
eventSource.on(event_types.MESSAGE_SWIPED, resetTtsPlayback);
|
||||
|
||||
// Mobiles need to "activate" the Audio element with click before it can be played
|
||||
if (isMobile()) {
|
||||
console.debug('Activating mobile audio element on first click');
|
||||
let audioActivated = false;
|
||||
|
||||
// Play silence on first click
|
||||
$(document).on('click touchend', function () {
|
||||
// Prevent multiple activations
|
||||
if (audioActivated) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.debug('Activating audio element...');
|
||||
audioActivated = true;
|
||||
audioElement.src = '/sounds/silence.mp3';
|
||||
// Reset volume to 1
|
||||
audioElement.onended = function () {
|
||||
console.debug('Audio element activated');
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
|
@@ -219,6 +219,7 @@ class SystemTtsProvider {
|
||||
chunkLength: 200,
|
||||
}, function () {
|
||||
//some code to execute when done
|
||||
resolve(silence);
|
||||
console.log('System TTS done');
|
||||
});
|
||||
});
|
||||
|
@@ -3,6 +3,8 @@ import {
|
||||
onlyUnique,
|
||||
debounce,
|
||||
delay,
|
||||
isDataURL,
|
||||
createThumbnail,
|
||||
} from './utils.js';
|
||||
import { RA_CountCharTokens, humanizedDateTime } from "./RossAscends-mods.js";
|
||||
import { sortCharactersList, sortGroupMembers } from './power-user.js';
|
||||
@@ -56,6 +58,8 @@ import {
|
||||
eventSource,
|
||||
event_types,
|
||||
getCurrentChatId,
|
||||
setScenarioOverride,
|
||||
getCropPopup,
|
||||
} from "../script.js";
|
||||
import { appendTagToList, createTagMapFromList, getTagsList, applyTagsOnCharacterSelect, tag_map } from './tags.js';
|
||||
|
||||
@@ -356,6 +360,14 @@ function updateGroupAvatar(group) {
|
||||
}
|
||||
|
||||
function getGroupAvatar(group) {
|
||||
if (!group) {
|
||||
return $(`<div class="avatar"><img src="${default_avatar}"></div>`);
|
||||
}
|
||||
|
||||
if (isDataURL(group.avatar_url)) {
|
||||
return $(`<div class="avatar"><img src="${group.avatar_url}"></div>`);
|
||||
}
|
||||
|
||||
const memberAvatars = [];
|
||||
if (group && Array.isArray(group.members) && group.members.length) {
|
||||
for (const member of group.members) {
|
||||
@@ -489,7 +501,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
|
||||
activatedMembers = activateListOrder(group.members.slice(0, 1));
|
||||
}
|
||||
}
|
||||
else if (type === "swipe") {
|
||||
else if (type === "swipe" || type === 'continue') {
|
||||
activatedMembers = activateSwipe(group.members);
|
||||
|
||||
if (activatedMembers.length === 0) {
|
||||
@@ -512,7 +524,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
|
||||
toastr.warning('All group members are disabled. Enable at least one to get a reply.');
|
||||
|
||||
// Send user message as is
|
||||
const bias = getBiasStrings(userInput);
|
||||
const bias = getBiasStrings(userInput, type);
|
||||
await sendMessageAsUser(userInput, bias.messageBias);
|
||||
await saveChatConditional();
|
||||
$('#send_textarea').val('').trigger('input');
|
||||
@@ -522,7 +534,7 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
|
||||
for (const chId of activatedMembers) {
|
||||
deactivateSendButtons();
|
||||
isGenerationDone = false;
|
||||
const generateType = type == "swipe" || type == "impersonate" || type == "quiet" ? type : "group_chat";
|
||||
const generateType = type == "swipe" || type == "impersonate" || type == "quiet" || type == 'continue' ? type : "group_chat";
|
||||
setCharacterId(chId);
|
||||
setCharacterName(characters[chId].name)
|
||||
|
||||
@@ -924,6 +936,8 @@ function select_group_chats(groupId, skipAnimation) {
|
||||
const group = groupId && groups.find((x) => x.id == groupId);
|
||||
const groupName = group?.name ?? "";
|
||||
setMenuType(!!group ? 'group_edit' : 'group_create');
|
||||
$("#group_avatar_preview").empty().append(getGroupAvatar(group));
|
||||
$("#rm_group_restore_avatar").toggle(!!group && isDataURL(group.avatar_url));
|
||||
$("#rm_group_chat_name").val(groupName);
|
||||
$("#rm_group_chat_name").off();
|
||||
$("#rm_group_chat_name").on("input", async function () {
|
||||
@@ -960,12 +974,19 @@ function select_group_chats(groupId, skipAnimation) {
|
||||
? getThumbnailUrl('avatar', character.avatar)
|
||||
: default_avatar;
|
||||
const template = $("#group_member_template .group_member").clone();
|
||||
const isFav = character.fav || character.fav == 'true';
|
||||
template.data("id", character.avatar);
|
||||
template.find(".avatar img").attr("src", avatar);
|
||||
template.find(".avatar img").attr("title", character.avatar);
|
||||
template.find(".ch_name").text(character.name);
|
||||
template.attr("chid", characters.indexOf(character));
|
||||
template.toggleClass('is_fav', character.fav || character.fav == 'true');
|
||||
template.find('.ch_fav').val(isFav);
|
||||
template.toggleClass('is_fav', isFav);
|
||||
|
||||
// Display inline tags
|
||||
const tags = getTagsList(character.avatar);
|
||||
const tagsElement = template.find('.tags');
|
||||
tags.forEach(tag => appendTagToList(tagsElement, tag, {}));
|
||||
|
||||
if (!group) {
|
||||
template.find('[data-action="speak"]').hide();
|
||||
@@ -985,7 +1006,6 @@ function select_group_chats(groupId, skipAnimation) {
|
||||
}
|
||||
|
||||
sortGroupMembers("#rm_group_add_members .group_member");
|
||||
filterMembersByFavorites(false);
|
||||
|
||||
const groupHasMembers = !!$("#rm_group_members").children().length;
|
||||
$("#rm_group_submit").prop("disabled", !groupHasMembers);
|
||||
@@ -1048,6 +1068,67 @@ function select_group_chats(groupId, skipAnimation) {
|
||||
$("#rm_group_automode_label").hide();
|
||||
}
|
||||
|
||||
$("#group_avatar_button").off('input').on("input", uploadGroupAvatar);
|
||||
$("#rm_group_restore_avatar").off('click').on("click", restoreGroupAvatar);
|
||||
|
||||
|
||||
async function uploadGroupAvatar(event) {
|
||||
const file = event.target.files[0];
|
||||
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
const e = await new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = resolve;
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
$('#dialogue_popup').addClass('large_dialogue_popup wide_dialogue_popup');
|
||||
|
||||
const croppedImage = await callPopup(getCropPopup(e.target.result), 'avatarToCrop');
|
||||
|
||||
if (!croppedImage) {
|
||||
return;
|
||||
}
|
||||
|
||||
const thumbnail = await createThumbnail(croppedImage, 96, 144);
|
||||
|
||||
if (!groupId) {
|
||||
$('#group_avatar_preview img').attr('src', thumbnail);
|
||||
$('#rm_group_restore_avatar').show();
|
||||
return;
|
||||
}
|
||||
|
||||
let _thisGroup = groups.find((x) => x.id == groupId);
|
||||
_thisGroup.avatar_url = thumbnail;
|
||||
$("#group_avatar_preview").empty().append(getGroupAvatar(_thisGroup));
|
||||
$("#rm_group_restore_avatar").show();
|
||||
await editGroup(groupId, true, true);
|
||||
}
|
||||
|
||||
async function restoreGroupAvatar() {
|
||||
const confirm = await callPopup('<h3>Are you sure you want to restore the group avatar?</h3> Your custom image will be deleted, and a collage will be used instead.', 'confirm');
|
||||
|
||||
if (!confirm) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!groupId) {
|
||||
$("#group_avatar_preview img").attr("src", default_avatar);
|
||||
$("#rm_group_restore_avatar").hide();
|
||||
return;
|
||||
}
|
||||
|
||||
let _thisGroup = groups.find((x) => x.id == groupId);
|
||||
_thisGroup.avatar_url = '';
|
||||
$("#group_avatar_preview").empty().append(getGroupAvatar(_thisGroup));
|
||||
$("#rm_group_restore_avatar").hide();
|
||||
await editGroup(groupId, true, true);
|
||||
}
|
||||
|
||||
$(document).off("click", ".group_member .right_menu_button");
|
||||
$(document).on("click", ".group_member .right_menu_button", async function (event) {
|
||||
event.stopPropagation();
|
||||
@@ -1173,8 +1254,7 @@ async function createGroup() {
|
||||
name = `Group: ${memberNames}`;
|
||||
}
|
||||
|
||||
// placeholder
|
||||
const avatar_url = 'img/five.png';
|
||||
const avatar_url = $('#group_avatar_preview img').attr('src');
|
||||
|
||||
const chatName = humanizedDateTime();
|
||||
const chats = [chatName];
|
||||
@@ -1185,7 +1265,7 @@ async function createGroup() {
|
||||
body: JSON.stringify({
|
||||
name: name,
|
||||
members: members,
|
||||
avatar_url: avatar_url,
|
||||
avatar_url: isDataURL(avatar_url) ? avatar_url : default_avatar,
|
||||
allow_self_responses: allow_self_responses,
|
||||
activation_strategy: activation_strategy,
|
||||
disabled_members: [],
|
||||
@@ -1204,24 +1284,6 @@ async function createGroup() {
|
||||
}
|
||||
}
|
||||
|
||||
function toggleFilterByFavorites() {
|
||||
filterMembersByFavorites(!fav_filter_on);
|
||||
}
|
||||
|
||||
function filterMembersByFavorites(value) {
|
||||
fav_filter_on = value;
|
||||
$('#group_fav_filter').toggleClass('fav_on', fav_filter_on);
|
||||
|
||||
if (!fav_filter_on) {
|
||||
$("#rm_group_add_members .group_member").removeClass('hiddenByFav');
|
||||
} else {
|
||||
$("#rm_group_add_members .group_member").each(function () {
|
||||
const isValidSearch = $(this).hasClass("is_fav");
|
||||
$(this).toggleClass('hiddenByFav', !isValidSearch);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function createNewGroupChat(groupId) {
|
||||
const group = groups.find(x => x.id === groupId);
|
||||
|
||||
@@ -1397,27 +1459,6 @@ export async function saveGroupBookmarkChat(groupId, name, metadata) {
|
||||
});
|
||||
}
|
||||
|
||||
function setGroupScenario() {
|
||||
if (!selected_group) {
|
||||
return;
|
||||
}
|
||||
|
||||
const template = $('#group_scenario_template .group_scenario').clone();
|
||||
const metadataValue = chat_metadata['scenario'] || '';
|
||||
template.find('.group_chat_scenario').text(metadataValue);
|
||||
callPopup(template.get(0).outerHTML, 'text');
|
||||
}
|
||||
|
||||
function onGroupScenarioInput() {
|
||||
const value = $(this).val();
|
||||
const metadata = { scenario: value, };
|
||||
updateChatMetadata(metadata, false);
|
||||
}
|
||||
|
||||
function onGroupScenarioRemoveClick() {
|
||||
$(this).closest('.group_scenario').find('.group_chat_scenario').val('').trigger('input');
|
||||
}
|
||||
|
||||
function onSendTextareaInput() {
|
||||
if (is_group_automode_enabled) {
|
||||
// Wait for current automode generation to finish
|
||||
@@ -1437,12 +1478,9 @@ function stopAutoModeGeneration() {
|
||||
|
||||
jQuery(() => {
|
||||
$(document).on("click", ".group_select", selectGroup);
|
||||
$(document).on("input", ".group_chat_scenario", onGroupScenarioInput);
|
||||
$(document).on("click", ".remove_scenario_override", onGroupScenarioRemoveClick);
|
||||
$("#rm_group_filter").on("input", filterGroupMembers);
|
||||
$("#group_fav_filter").on("click", toggleFilterByFavorites);
|
||||
$("#rm_group_submit").on("click", createGroup);
|
||||
$("#rm_group_scenario").on("click", setGroupScenario);
|
||||
$("#rm_group_scenario").on("click", setScenarioOverride);
|
||||
$("#rm_group_automode").on("input", function () {
|
||||
const value = $(this).prop("checked");
|
||||
is_group_automode_enabled = value;
|
||||
|
@@ -225,12 +225,10 @@ async function showKudos() {
|
||||
}
|
||||
|
||||
jQuery(function () {
|
||||
|
||||
let hordeModelSelectScrollTop = null;
|
||||
|
||||
$("#horde_model").on('mousedown change', async function (e) {
|
||||
//desktop-only routine for multi-select without CTRL
|
||||
if (deviceInfo.device.type === 'desktop') {
|
||||
/*if (deviceInfo.device.type === 'desktop') {
|
||||
let hordeModelSelectScrollTop = null;
|
||||
e.preventDefault();
|
||||
const option = $(e.target);
|
||||
const selectElement = $(this)[0];
|
||||
@@ -238,7 +236,7 @@ jQuery(function () {
|
||||
option.prop('selected', !option.prop('selected'));
|
||||
await delay(1);
|
||||
selectElement.scrollTop = hordeModelSelectScrollTop;
|
||||
}
|
||||
}*/
|
||||
horde_settings.models = $('#horde_model').val();
|
||||
console.log('Updated Horde models', horde_settings.models);
|
||||
});
|
||||
@@ -265,4 +263,22 @@ jQuery(function () {
|
||||
|
||||
$("#horde_refresh").on("click", getHordeModels);
|
||||
$("#horde_kudos").on("click", showKudos);
|
||||
|
||||
// Not needed on mobile
|
||||
if (deviceInfo.device.type === 'desktop') {
|
||||
$('#horde_model').select2({
|
||||
width: '100%',
|
||||
placeholder: 'Select Horde models',
|
||||
allowClear: true,
|
||||
closeOnSelect: false,
|
||||
templateSelection: function(data) {
|
||||
// Customize the pillbox text by shortening the full text
|
||||
return data.id;
|
||||
},
|
||||
templateResult: function(data) {
|
||||
// Return the full text for the dropdown
|
||||
return data.text;
|
||||
},
|
||||
});
|
||||
}
|
||||
})
|
||||
|
@@ -26,6 +26,7 @@ const kai_settings = {
|
||||
single_line: false,
|
||||
use_stop_sequence: false,
|
||||
streaming_kobold: false,
|
||||
sampler_order: [0, 1, 2, 3, 4, 5, 6],
|
||||
};
|
||||
|
||||
const MIN_STOP_SEQUENCE_VERSION = '1.2.2';
|
||||
@@ -68,11 +69,12 @@ function loadKoboldSettings(preset) {
|
||||
}
|
||||
}
|
||||
|
||||
function getKoboldGenerationData(finalPromt, this_settings, this_amount_gen, this_max_context, isImpersonate) {
|
||||
function getKoboldGenerationData(finalPromt, this_settings, this_amount_gen, this_max_context, isImpersonate, type) {
|
||||
const sampler_order = kai_settings.sampler_order || this_settings.sampler_order;
|
||||
let generate_data = {
|
||||
prompt: finalPromt,
|
||||
gui_settings: false,
|
||||
sampler_order: this_settings.sampler_order,
|
||||
sampler_order: sampler_order,
|
||||
max_context_length: parseInt(this_max_context),
|
||||
max_length: this_amount_gen,
|
||||
rep_pen: parseFloat(kai_settings.rep_pen),
|
||||
@@ -84,17 +86,17 @@ function getKoboldGenerationData(finalPromt, this_settings, this_amount_gen, thi
|
||||
top_k: kai_settings.top_k,
|
||||
top_p: kai_settings.top_p,
|
||||
typical: kai_settings.typical,
|
||||
s1: this_settings.sampler_order[0],
|
||||
s2: this_settings.sampler_order[1],
|
||||
s3: this_settings.sampler_order[2],
|
||||
s4: this_settings.sampler_order[3],
|
||||
s5: this_settings.sampler_order[4],
|
||||
s6: this_settings.sampler_order[5],
|
||||
s7: this_settings.sampler_order[6],
|
||||
s1: sampler_order[0],
|
||||
s2: sampler_order[1],
|
||||
s3: sampler_order[2],
|
||||
s4: sampler_order[3],
|
||||
s5: sampler_order[4],
|
||||
s6: sampler_order[5],
|
||||
s7: sampler_order[6],
|
||||
use_world_info: false,
|
||||
singleline: kai_settings.single_line,
|
||||
stop_sequence: kai_settings.use_stop_sequence ? getStoppingStrings(isImpersonate, false) : undefined,
|
||||
streaming: kai_settings.streaming_kobold && kai_settings.can_use_streaming,
|
||||
streaming: kai_settings.streaming_kobold && kai_settings.can_use_streaming && type !== 'quiet',
|
||||
can_abort: kai_settings.can_use_streaming,
|
||||
};
|
||||
return generate_data;
|
||||
@@ -206,6 +208,13 @@ const sliders = [
|
||||
format: (val) => val,
|
||||
setValue: (val) => { kai_settings.rep_pen_slope = Number(val); },
|
||||
},
|
||||
{
|
||||
name: "sampler_order",
|
||||
sliderId: "#no_op_selector",
|
||||
counterId: "#no_op_selector",
|
||||
format: (val) => val,
|
||||
setValue: (val) => { sortItemsByOrder(val); },
|
||||
}
|
||||
];
|
||||
|
||||
function canUseKoboldStopSequence(version) {
|
||||
@@ -213,11 +222,22 @@ function canUseKoboldStopSequence(version) {
|
||||
}
|
||||
|
||||
function canUseKoboldStreaming(koboldVersion) {
|
||||
if (koboldVersion.result == 'KoboldCpp') {
|
||||
if (koboldVersion && koboldVersion.result == 'KoboldCpp') {
|
||||
return (koboldVersion.version || '0.0').localeCompare(MIN_STREAMING_KCPPVERSION, undefined, { numeric: true, sensitivity: 'base' }) > -1;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
function sortItemsByOrder(orderArray) {
|
||||
console.debug('Preset samplers order: ' + orderArray);
|
||||
const $draggableItems = $("#kobold_order");
|
||||
|
||||
for (let i = 0; i < orderArray.length; i++) {
|
||||
const index = orderArray[i];
|
||||
const $item = $draggableItems.find(`[data-id="${index}"]`).detach();
|
||||
$draggableItems.append($item);
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
sliders.forEach(slider => {
|
||||
$(document).on("input", slider.sliderId, function () {
|
||||
@@ -240,4 +260,16 @@ $(document).ready(function () {
|
||||
kai_settings.streaming_kobold = value;
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#kobold_order').sortable({
|
||||
stop: function () {
|
||||
const order = [];
|
||||
$('#kobold_order').children().each(function () {
|
||||
order.push($(this).data('id'));
|
||||
});
|
||||
kai_settings.sampler_order = order;
|
||||
console.log('Samplers reordered:', kai_settings.sampler_order);
|
||||
saveSettingsDebounced();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@@ -20,6 +20,7 @@ import {
|
||||
system_message_types,
|
||||
replaceBiasMarkup,
|
||||
is_send_press,
|
||||
main_api,
|
||||
} from "../script.js";
|
||||
import { groups, selected_group } from "./group-chats.js";
|
||||
|
||||
@@ -35,6 +36,7 @@ import {
|
||||
import {
|
||||
delay,
|
||||
download,
|
||||
getFileText,
|
||||
getStringHash,
|
||||
parseJsonFile,
|
||||
stringFormat,
|
||||
@@ -84,7 +86,8 @@ const gpt3_16k_max = 16383;
|
||||
const gpt4_max = 8191;
|
||||
const gpt_neox_max = 2048;
|
||||
const gpt4_32k_max = 32767;
|
||||
const claude_max = 7500;
|
||||
const claude_max = 8000; // We have a proper tokenizer, so theoretically could be larger (up to 9k)
|
||||
const palm2_max = 7500; // The real context window is 8192, spare some for padding due to using turbo tokenizer
|
||||
const claude_100k_max = 99000;
|
||||
const unlocked_max = 100 * 1024;
|
||||
const oai_max_temp = 2.0;
|
||||
@@ -130,6 +133,7 @@ const default_settings = {
|
||||
legacy_streaming: false,
|
||||
chat_completion_source: chat_completion_sources.OPENAI,
|
||||
max_context_unlocked: false,
|
||||
use_openrouter: false,
|
||||
};
|
||||
|
||||
const oai_settings = {
|
||||
@@ -163,6 +167,7 @@ const oai_settings = {
|
||||
legacy_streaming: false,
|
||||
chat_completion_source: chat_completion_sources.OPENAI,
|
||||
max_context_unlocked: false,
|
||||
use_openrouter: false,
|
||||
};
|
||||
|
||||
let openai_setting_names;
|
||||
@@ -209,7 +214,7 @@ function setOpenAIMessages(chat) {
|
||||
}
|
||||
|
||||
// for groups or sendas command - prepend a character's name
|
||||
if (selected_group || chat[j].force_avatar) {
|
||||
if (selected_group || (chat[j].force_avatar && chat[j].name !== name1)) {
|
||||
content = `${chat[j].name}: ${content}`;
|
||||
}
|
||||
|
||||
@@ -319,7 +324,7 @@ function formatWorldInfo(value) {
|
||||
return stringFormat(oai_settings.wi_format, value);
|
||||
}
|
||||
|
||||
async function prepareOpenAIMessages({ systemPrompt, name2, storyString, worldInfoBefore, worldInfoAfter, extensionPrompt, bias, type, quietPrompt, jailbreakPrompt } = {}) {
|
||||
async function prepareOpenAIMessages({ systemPrompt, name2, storyString, worldInfoBefore, worldInfoAfter, extensionPrompt, bias, type, quietPrompt, jailbreakPrompt, cyclePrompt } = {}) {
|
||||
const isImpersonate = type == "impersonate";
|
||||
let this_max_context = oai_settings.openai_max_context;
|
||||
let enhance_definitions_prompt = "";
|
||||
@@ -336,7 +341,7 @@ async function prepareOpenAIMessages({ systemPrompt, name2, storyString, worldIn
|
||||
let whole_prompt = getSystemPrompt(systemPrompt, nsfw_toggle_prompt, enhance_definitions_prompt, wiBefore, storyString, wiAfter, extensionPrompt, isImpersonate);
|
||||
|
||||
// Join by a space and replace placeholders with real user/char names
|
||||
storyString = substituteParams(whole_prompt.join("\n")).replace(/\r/gm, '').trim();
|
||||
storyString = substituteParams(whole_prompt.join("\n"), name1, name2, oai_settings.main_prompt).replace(/\r/gm, '').trim();
|
||||
|
||||
let prompt_msg = { "role": "system", "content": storyString }
|
||||
let examples_tosend = [];
|
||||
@@ -385,7 +390,7 @@ async function prepareOpenAIMessages({ systemPrompt, name2, storyString, worldIn
|
||||
|
||||
const jailbreak = power_user.prefer_character_jailbreak && jailbreakPrompt ? jailbreakPrompt : oai_settings.jailbreak_prompt;
|
||||
if (oai_settings.jailbreak_system && jailbreak) {
|
||||
const jailbreakMessage = { "role": "system", "content": substituteParams(jailbreak) };
|
||||
const jailbreakMessage = { "role": "system", "content": substituteParams(jailbreak, name1, name2, oai_settings.jailbreak_prompt) };
|
||||
openai_msgs.push(jailbreakMessage);
|
||||
|
||||
total_count += handler_instance.count([jailbreakMessage], true, 'jailbreak');
|
||||
@@ -406,6 +411,14 @@ async function prepareOpenAIMessages({ systemPrompt, name2, storyString, worldIn
|
||||
await delay(1);
|
||||
}
|
||||
|
||||
if (type == 'continue') {
|
||||
const continueNudge = { "role": "system", "content": stringFormat('[Continue the following message. Do not include ANY parts of the original message. Use capitalization and punctuation as if your reply is a part of the original message:\n\n{0}]', cyclePrompt || '') };
|
||||
openai_msgs.push(continueNudge);
|
||||
|
||||
total_count += handler_instance.count([continueNudge], true, 'continue');
|
||||
await delay(1);
|
||||
}
|
||||
|
||||
// The user wants to always have all example messages in the context
|
||||
if (power_user.pin_examples) {
|
||||
// first we send *all* example messages
|
||||
@@ -566,8 +579,8 @@ async function sendWindowAIRequest(openai_msgs_tosend, signal, stream) {
|
||||
const currentModel = await window.ai.getCurrentModel();
|
||||
let temperature = parseFloat(oai_settings.temp_openai);
|
||||
|
||||
if (currentModel.includes('claude') && temperature > claude_max_temp) {
|
||||
console.warn(`Claude model only supports temperature up to ${claude_max_temp}. Clamping ${temperature} to ${claude_max_temp}.`);
|
||||
if ((currentModel.includes('claude') || currentModel.includes('palm-2')) && temperature > claude_max_temp) {
|
||||
console.warn(`Claude and PaLM models only supports temperature up to ${claude_max_temp}. Clamping ${temperature} to ${claude_max_temp}.`);
|
||||
temperature = claude_max_temp;
|
||||
}
|
||||
|
||||
@@ -647,6 +660,19 @@ async function sendWindowAIRequest(openai_msgs_tosend, signal, stream) {
|
||||
}
|
||||
}
|
||||
|
||||
function getChatCompletionModel() {
|
||||
switch (oai_settings.chat_completion_source) {
|
||||
case chat_completion_sources.CLAUDE:
|
||||
return oai_settings.claude_model;
|
||||
case chat_completion_sources.OPENAI:
|
||||
return oai_settings.openai_model;
|
||||
case chat_completion_sources.WINDOWAI:
|
||||
return oai_settings.windowai_model;
|
||||
default:
|
||||
throw new Error(`Unknown chat completion source: ${oai_settings.chat_completion_source}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
|
||||
// Provide default abort signal
|
||||
if (!signal) {
|
||||
@@ -659,23 +685,24 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
|
||||
|
||||
let logit_bias = {};
|
||||
const isClaude = oai_settings.chat_completion_source == chat_completion_sources.CLAUDE;
|
||||
const isOpenRouter = oai_settings.use_openrouter && oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI;
|
||||
const stream = type !== 'quiet' && oai_settings.stream_openai;
|
||||
|
||||
// If we're using the window.ai extension, use that instead
|
||||
// Doesn't support logit bias yet
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI) {
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI && !oai_settings.use_openrouter) {
|
||||
return sendWindowAIRequest(openai_msgs_tosend, signal, stream);
|
||||
}
|
||||
|
||||
if (oai_settings.bias_preset_selected
|
||||
&& !isClaude // Claude doesn't support logit bias
|
||||
&& oai_settings.chat_completion_source == chat_completion_sources.OPENAI
|
||||
&& Array.isArray(oai_settings.bias_presets[oai_settings.bias_preset_selected])
|
||||
&& oai_settings.bias_presets[oai_settings.bias_preset_selected].length) {
|
||||
logit_bias = biasCache || await calculateLogitBias();
|
||||
biasCache = logit_bias;
|
||||
}
|
||||
|
||||
const model = isClaude ? oai_settings.claude_model : oai_settings.openai_model;
|
||||
const model = getChatCompletionModel();
|
||||
const generate_data = {
|
||||
"messages": openai_msgs_tosend,
|
||||
"model": model,
|
||||
@@ -689,6 +716,7 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
|
||||
"reverse_proxy": oai_settings.reverse_proxy,
|
||||
"logit_bias": logit_bias,
|
||||
"use_claude": isClaude,
|
||||
"use_openrouter": isOpenRouter,
|
||||
};
|
||||
|
||||
const generate_url = '/generate_openai';
|
||||
@@ -764,8 +792,8 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
|
||||
function getStreamingReply(getMessage, data) {
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {
|
||||
getMessage = data.completion || "";
|
||||
} else{
|
||||
getMessage += data.choices[0]["delta"]["content"] || "";
|
||||
} else {
|
||||
getMessage += data.choices[0]?.delta?.content || data.choices[0]?.message?.content || "";
|
||||
}
|
||||
return getMessage;
|
||||
}
|
||||
@@ -925,16 +953,15 @@ function getTokenizerModel() {
|
||||
return turboTokenizer;
|
||||
}
|
||||
else if (oai_settings.windowai_model.includes('claude')) {
|
||||
return turboTokenizer;
|
||||
return 'claude';
|
||||
}
|
||||
else if (oai_settings.windowai_model.includes('GPT-NeoXT')) {
|
||||
return 'gpt2';
|
||||
}
|
||||
}
|
||||
|
||||
// We don't have a Claude tokenizer for JS yet. Turbo 3.5 should be able to handle this.
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {
|
||||
return turboTokenizer;
|
||||
return 'claude';
|
||||
}
|
||||
|
||||
// Default to Turbo 3.5
|
||||
@@ -978,6 +1005,7 @@ function loadOpenAISettings(data, settings) {
|
||||
oai_settings.claude_model = settings.claude_model ?? default_settings.claude_model;
|
||||
oai_settings.windowai_model = settings.windowai_model ?? default_settings.windowai_model;
|
||||
oai_settings.chat_completion_source = settings.chat_completion_source ?? default_settings.chat_completion_source;
|
||||
oai_settings.use_openrouter = settings.use_openrouter ?? default_settings.use_openrouter;
|
||||
|
||||
if (settings.nsfw_toggle !== undefined) oai_settings.nsfw_toggle = !!settings.nsfw_toggle;
|
||||
if (settings.keep_example_dialogue !== undefined) oai_settings.keep_example_dialogue = !!settings.keep_example_dialogue;
|
||||
@@ -989,8 +1017,11 @@ function loadOpenAISettings(data, settings) {
|
||||
|
||||
$('#stream_toggle').prop('checked', oai_settings.stream_openai);
|
||||
|
||||
$('#model_openai_select').val(oai_settings.openai_model);
|
||||
$(`#model_openai_select option[value="${oai_settings.openai_model}"`).attr('selected', true);
|
||||
$('#model_claude_select').val(oai_settings.claude_model);
|
||||
$(`#model_claude_select option[value="${oai_settings.claude_model}"`).attr('selected', true);
|
||||
$('#model_windowai_select').val(oai_settings.windowai_model);
|
||||
$(`#model_windowai_select option[value="${oai_settings.windowai_model}"`).attr('selected', true);
|
||||
$('#openai_max_context').val(oai_settings.openai_max_context);
|
||||
$('#openai_max_context_counter').text(`${oai_settings.openai_max_context}`);
|
||||
@@ -1051,11 +1082,12 @@ function loadOpenAISettings(data, settings) {
|
||||
|
||||
$('#chat_completion_source').val(oai_settings.chat_completion_source).trigger('change');
|
||||
$('#oai_max_context_unlocked').prop('checked', oai_settings.max_context_unlocked);
|
||||
$('#use_openrouter').prop('checked', oai_settings.use_openrouter);
|
||||
}
|
||||
|
||||
async function getStatusOpen() {
|
||||
if (is_get_status_openai) {
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI) {
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI && !oai_settings.use_openrouter) {
|
||||
let status;
|
||||
|
||||
if ('ai' in window) {
|
||||
@@ -1078,6 +1110,7 @@ async function getStatusOpen() {
|
||||
|
||||
let data = {
|
||||
reverse_proxy: oai_settings.reverse_proxy,
|
||||
use_openrouter: oai_settings.use_openrouter && oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI,
|
||||
};
|
||||
|
||||
return jQuery.ajax({
|
||||
@@ -1085,7 +1118,7 @@ async function getStatusOpen() {
|
||||
url: '/getstatus_openai', //
|
||||
data: JSON.stringify(data),
|
||||
beforeSend: function () {
|
||||
if (oai_settings.reverse_proxy) {
|
||||
if (oai_settings.reverse_proxy && !data.use_openrouter) {
|
||||
validateReverseProxy();
|
||||
}
|
||||
},
|
||||
@@ -1134,6 +1167,11 @@ function trySelectPresetByName(name) {
|
||||
}
|
||||
}
|
||||
|
||||
// Don't change if the current preset is the same
|
||||
if (preset_found && preset_found === oai_settings.preset_settings_openai) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (preset_found) {
|
||||
oai_settings.preset_settings_openai = preset_found;
|
||||
const value = openai_setting_names[preset_found]
|
||||
@@ -1148,6 +1186,7 @@ async function saveOpenAIPreset(name, settings) {
|
||||
openai_model: settings.openai_model,
|
||||
claude_model: settings.claude_model,
|
||||
windowai_model: settings.windowai_model,
|
||||
use_openrouter: settings.use_openrouter,
|
||||
temperature: settings.temp_openai,
|
||||
frequency_penalty: settings.freq_pen_openai,
|
||||
presence_penalty: settings.pres_pen_openai,
|
||||
@@ -1171,6 +1210,7 @@ async function saveOpenAIPreset(name, settings) {
|
||||
max_context_unlocked: settings.max_context_unlocked,
|
||||
nsfw_avoidance_prompt: settings.nsfw_avoidance_prompt,
|
||||
wi_format: settings.wi_format,
|
||||
stream_openai: settings.stream_openai,
|
||||
};
|
||||
|
||||
const savePresetSettings = await fetch(`/savepreset_openai?name=${name}`, {
|
||||
@@ -1198,6 +1238,8 @@ async function saveOpenAIPreset(name, settings) {
|
||||
option.innerText = data.name;
|
||||
$('#settings_perset_openai').append(option).trigger('change');
|
||||
}
|
||||
} else {
|
||||
toastr.error('Failed to save preset');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1303,8 +1345,80 @@ function addLogitBiasPresetOption(name) {
|
||||
$('#openai_logit_bias_preset').trigger('change');
|
||||
}
|
||||
|
||||
function onImportPresetClick() {
|
||||
$('#openai_preset_import_file').trigger('click');
|
||||
}
|
||||
|
||||
function onLogitBiasPresetImportClick() {
|
||||
$('#openai_logit_bias_import_file').click();
|
||||
$('#openai_logit_bias_import_file').trigger('click');
|
||||
}
|
||||
|
||||
async function onPresetImportFileChange(e) {
|
||||
const file = e.target.files[0];
|
||||
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
const name = file.name.replace(/\.[^/.]+$/, "");
|
||||
const importedFile = await getFileText(file);
|
||||
let presetBody;
|
||||
e.target.value = '';
|
||||
|
||||
try {
|
||||
presetBody = JSON.parse(importedFile);
|
||||
} catch (err) {
|
||||
toastr.error('Invalid file');
|
||||
return;
|
||||
}
|
||||
|
||||
if (name in openai_setting_names) {
|
||||
const confirm = await callPopup('Preset name already exists. Overwrite?', 'confirm');
|
||||
|
||||
if (!confirm) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const savePresetSettings = await fetch(`/savepreset_openai?name=${name}`, {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: importedFile,
|
||||
});
|
||||
|
||||
if (!savePresetSettings.ok) {
|
||||
toastr.error('Failed to save preset');
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await savePresetSettings.json();
|
||||
|
||||
if (Object.keys(openai_setting_names).includes(data.name)) {
|
||||
oai_settings.preset_settings_openai = data.name;
|
||||
const value = openai_setting_names[data.name];
|
||||
Object.assign(openai_settings[value], presetBody);
|
||||
$(`#settings_perset_openai option[value="${value}"]`).attr('selected', true);
|
||||
$('#settings_perset_openai').trigger('change');
|
||||
} else {
|
||||
openai_settings.push(presetBody);
|
||||
openai_setting_names[data.name] = openai_settings.length - 1;
|
||||
const option = document.createElement('option');
|
||||
option.selected = true;
|
||||
option.value = openai_settings.length - 1;
|
||||
option.innerText = data.name;
|
||||
$('#settings_perset_openai').append(option).trigger('change');
|
||||
}
|
||||
}
|
||||
|
||||
async function onExportPresetClick() {
|
||||
if (!oai_settings.preset_settings_openai) {
|
||||
toastr.error('No preset selected');
|
||||
return;
|
||||
}
|
||||
|
||||
const preset = openai_settings[openai_setting_names[oai_settings.preset_settings_openai]];
|
||||
const presetJsonString = JSON.stringify(preset, null, 4);
|
||||
download(presetJsonString, oai_settings.preset_settings_openai, 'application/json');
|
||||
}
|
||||
|
||||
async function onLogitBiasPresetImportFileChange(e) {
|
||||
@@ -1351,7 +1465,7 @@ function onLogitBiasPresetExportClick() {
|
||||
return;
|
||||
}
|
||||
|
||||
const presetJsonString = JSON.stringify(oai_settings.bias_presets[oai_settings.bias_preset_selected]);
|
||||
const presetJsonString = JSON.stringify(oai_settings.bias_presets[oai_settings.bias_preset_selected], null, 4);
|
||||
download(presetJsonString, oai_settings.bias_preset_selected, 'application/json');
|
||||
}
|
||||
|
||||
@@ -1445,6 +1559,8 @@ function onSettingsPresetChange() {
|
||||
legacy_streaming: ['#legacy_streaming', 'legacy_streaming', true],
|
||||
nsfw_avoidance_prompt: ['#nsfw_avoidance_prompt_textarea', 'nsfw_avoidance_prompt', false],
|
||||
wi_format: ['#wi_format_textarea', 'wi_format', false],
|
||||
stream_openai: ['#stream_toggle', 'stream_openai', true],
|
||||
use_openrouter: ['#use_openrouter', 'use_openrouter', true],
|
||||
};
|
||||
|
||||
for (const [key, [selector, setting, isCheckbox]] of Object.entries(settingsToUpdate)) {
|
||||
@@ -1463,12 +1579,12 @@ function onSettingsPresetChange() {
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onModelChange() {
|
||||
const value = $(this).val();
|
||||
async function onModelChange() {
|
||||
let value = $(this).val();
|
||||
|
||||
if ($(this).is('#model_claude_select')) {
|
||||
console.log('Claude model changed to', value);
|
||||
oai_settings.claude_model = value;
|
||||
oai_settings.claude_model = value;
|
||||
}
|
||||
|
||||
if ($(this).is('#model_windowai_select')) {
|
||||
@@ -1490,10 +1606,11 @@ function onModelChange() {
|
||||
}
|
||||
else {
|
||||
$('#openai_max_context').attr('max', claude_max);
|
||||
oai_settings.openai_max_context = Math.max(oai_settings.openai_max_context, claude_max);
|
||||
$('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
|
||||
}
|
||||
|
||||
oai_settings.openai_max_context = Math.min(oai_settings.openai_max_context, Number($('#openai_max_context').attr('max')));
|
||||
$('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
|
||||
|
||||
$('#openai_reverse_proxy').attr('placeholder', 'https://api.anthropic.com/v1');
|
||||
|
||||
oai_settings.temp_openai = Math.min(claude_max_temp, oai_settings.temp_openai);
|
||||
@@ -1501,6 +1618,10 @@ function onModelChange() {
|
||||
}
|
||||
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI) {
|
||||
if (value == '' && 'ai' in window) {
|
||||
value = (await window.ai.getCurrentModel()) || '';
|
||||
}
|
||||
|
||||
if (oai_settings.max_context_unlocked) {
|
||||
$('#openai_max_context').attr('max', unlocked_max);
|
||||
}
|
||||
@@ -1516,9 +1637,15 @@ function onModelChange() {
|
||||
else if (value.includes('gpt-3.5')) {
|
||||
$('#openai_max_context').attr('max', gpt3_max);
|
||||
}
|
||||
else if (value.includes('gpt-4-32k')) {
|
||||
$('#openai_max_context').attr('max', gpt4_32k_max);
|
||||
}
|
||||
else if (value.includes('gpt-4')) {
|
||||
$('#openai_max_context').attr('max', gpt4_max);
|
||||
}
|
||||
else if (value.includes('palm-2')) {
|
||||
$('#openai_max_context').attr('max', palm2_max);
|
||||
}
|
||||
else if (value.includes('GPT-NeoXT')) {
|
||||
$('#openai_max_context').attr('max', gpt_neox_max);
|
||||
}
|
||||
@@ -1527,10 +1654,10 @@ function onModelChange() {
|
||||
$('#openai_max_context').attr('max', gpt3_max);
|
||||
}
|
||||
|
||||
oai_settings.openai_max_context = Math.max(Number($('#openai_max_context').val()), oai_settings.openai_max_context);
|
||||
oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context);
|
||||
$('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
|
||||
|
||||
if (value.includes('claude')) {
|
||||
if (value.includes('claude') || value.includes('palm-2')) {
|
||||
oai_settings.temp_openai = Math.min(claude_max_temp, oai_settings.temp_openai);
|
||||
$('#temp_openai').attr('max', claude_max_temp).val(oai_settings.temp_openai).trigger('input');
|
||||
}
|
||||
@@ -1557,7 +1684,7 @@ function onModelChange() {
|
||||
$('#openai_max_context').attr('max', gpt3_max);
|
||||
}
|
||||
|
||||
oai_settings.openai_max_context = Math.max(oai_settings.openai_max_context, Number($('#openai_max_context').attr('max')));
|
||||
oai_settings.openai_max_context = Math.min(oai_settings.openai_max_context, Number($('#openai_max_context').attr('max')));
|
||||
$('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
|
||||
|
||||
$('#openai_reverse_proxy').attr('placeholder', 'https://api.openai.com/v1');
|
||||
@@ -1596,6 +1723,17 @@ async function onConnectButtonClick(e) {
|
||||
if (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI) {
|
||||
is_get_status_openai = true;
|
||||
is_api_button_press_openai = true;
|
||||
const api_key_openrouter = $('#api_key_openrouter').val().trim();
|
||||
|
||||
if (api_key_openrouter.length) {
|
||||
await writeSecret(SECRET_KEYS.OPENROUTER, api_key_openrouter);
|
||||
}
|
||||
|
||||
if (oai_settings.use_openrouter && !secret_state[SECRET_KEYS.OPENROUTER]) {
|
||||
console.log('No secret key saved for OpenRouter');
|
||||
return;
|
||||
}
|
||||
|
||||
return await getStatusOpen();
|
||||
}
|
||||
|
||||
@@ -1667,6 +1805,12 @@ async function testApiConnection() {
|
||||
}
|
||||
}
|
||||
|
||||
function reconnectOpenAi() {
|
||||
setOnlineStatus('no_connection');
|
||||
resultCheckStatusOpen();
|
||||
$('#api_button_openai').trigger('click');
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$('#test_api_button').on('click', testApiConnection);
|
||||
|
||||
@@ -1850,10 +1994,11 @@ $(document).ready(function () {
|
||||
$('#chat_completion_source').on('change', function () {
|
||||
oai_settings.chat_completion_source = $(this).find(":selected").val();
|
||||
toggleChatCompletionForms();
|
||||
setOnlineStatus('no_connection');
|
||||
resultCheckStatusOpen();
|
||||
$('#api_button_openai').trigger('click');
|
||||
saveSettingsDebounced();
|
||||
|
||||
if (main_api == 'openai') {
|
||||
reconnectOpenAi();
|
||||
}
|
||||
});
|
||||
|
||||
$('#oai_max_context_unlocked').on('input', function () {
|
||||
@@ -1862,6 +2007,12 @@ $(document).ready(function () {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#use_openrouter').on('input', function () {
|
||||
oai_settings.use_openrouter = !!$(this).prop('checked');
|
||||
reconnectOpenAi();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#api_button_openai").on("click", onConnectButtonClick);
|
||||
$("#openai_reverse_proxy").on("input", onReverseProxyInput);
|
||||
$("#model_openai_select").on("change", onModelChange);
|
||||
@@ -1875,7 +2026,10 @@ $(document).ready(function () {
|
||||
$("#openai_logit_bias_new_preset").on("click", createNewLogitBiasPreset);
|
||||
$("#openai_logit_bias_new_entry").on("click", createNewLogitBiasEntry);
|
||||
$("#openai_logit_bias_import_file").on("input", onLogitBiasPresetImportFileChange);
|
||||
$("#openai_preset_import_file").on("input", onPresetImportFileChange);
|
||||
$("#export_oai_preset").on("click", onExportPresetClick);
|
||||
$("#openai_logit_bias_import_preset").on("click", onLogitBiasPresetImportClick);
|
||||
$("#openai_logit_bias_export_preset").on("click", onLogitBiasPresetExportClick);
|
||||
$("#openai_logit_bias_delete_preset").on("click", onLogitBiasPresetDeleteClick);
|
||||
$("#import_oai_preset").on("click", onImportPresetClick);
|
||||
});
|
||||
|
@@ -8,13 +8,16 @@ import {
|
||||
eventSource,
|
||||
event_types,
|
||||
scrollChatToBottom,
|
||||
name1,
|
||||
name2,
|
||||
} from "../script.js";
|
||||
import { power_user } from "./power-user.js";
|
||||
import {
|
||||
SECRET_KEYS,
|
||||
secret_state,
|
||||
writeSecret,
|
||||
} from "./secrets.js";
|
||||
import { delay, splitRecursive } from "./utils.js";
|
||||
import { RateLimiter, delay, splitRecursive } from "./utils.js";
|
||||
|
||||
export {
|
||||
is_get_status_poe,
|
||||
@@ -44,8 +47,8 @@ If you have any objections to these requirements, please mention them specifical
|
||||
|
||||
If you accept the requirements, please confirm this by replying with "${DEFAULT_JAILBREAK_RESPONSE}", and nothing more. Upon receiving your accurate confirmation message, I will specify the context of the scene and {{char}}'s characteristics, background, and personality in the next message.`;
|
||||
|
||||
const DEFAULT_CHARACTER_NUDGE_MESSAGE = "[Unless otherwise stated by {{user}}, your the next response shall only be written from the point of view of {{char}}. Do not seek approval of your writing style at the end of the response.]";
|
||||
const DEFAULT_IMPERSONATION_PROMPT = "[Write 1 reply only in internet RP style from the point of view of {{user}}, using the chat history so far as a guideline for the writing style of {{user}}. Don't write as {{char}} or system.]";
|
||||
const DEFAULT_CHARACTER_NUDGE_MESSAGE = "[Unless otherwise stated by {{user}}, your the next response shall only be written from the point of view of {{char}}. Do not seek approval of your writing style at the end of the response. Never reply with a full stop.]";
|
||||
const DEFAULT_IMPERSONATION_PROMPT = "[Write a reply only from the point of view of {{user}}, using the chat history so far as a guideline for the writing style of {{user}}. Don't write as {{char}} or system.]";
|
||||
|
||||
const poe_settings = {
|
||||
bot: 'a2',
|
||||
@@ -66,6 +69,8 @@ let is_get_status_poe = false;
|
||||
let is_poe_button_press = false;
|
||||
let abortControllerSuggest = null;
|
||||
|
||||
const rateLimiter = new RateLimiter((60 / 10 * 1000)); // 10 requests per minute
|
||||
|
||||
function loadPoeSettings(settings) {
|
||||
if (settings.poe_settings) {
|
||||
Object.assign(poe_settings, settings.poe_settings);
|
||||
@@ -184,13 +189,16 @@ function onBotChange() {
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
export function appendPoeAnchors(type, prompt) {
|
||||
export function appendPoeAnchors(type, prompt, jailbreakPrompt) {
|
||||
const isImpersonate = type === 'impersonate';
|
||||
const isQuiet = type === 'quiet';
|
||||
|
||||
if (poe_settings.character_nudge && !isQuiet && !isImpersonate) {
|
||||
let characterNudge = '\n' + substituteParams(poe_settings.character_nudge_message);
|
||||
prompt += characterNudge;
|
||||
if (power_user.prefer_character_jailbreak && jailbreakPrompt) {
|
||||
prompt += '\n' + substituteParams(jailbreakPrompt, name1, name2, poe_settings.character_nudge_message);
|
||||
} else {
|
||||
prompt += '\n' + substituteParams(poe_settings.character_nudge_message);
|
||||
}
|
||||
}
|
||||
|
||||
if (poe_settings.impersonation_prompt && isImpersonate) {
|
||||
@@ -263,24 +271,27 @@ async function generatePoe(type, finalPrompt, signal) {
|
||||
}
|
||||
|
||||
const isQuiet = type === 'quiet';
|
||||
const isImpersonate = type === 'impersonate';
|
||||
const isContinue = type === 'continue';
|
||||
const suggestReplies = !isQuiet && !isImpersonate && !isContinue;
|
||||
let reply = '';
|
||||
|
||||
if (max_context > POE_TOKEN_LENGTH && poe_settings.bot !== 'a2_100k') {
|
||||
console.debug('Prompt is too long, sending in chunks');
|
||||
const result = await sendChunkedMessage(finalPrompt, !isQuiet, signal)
|
||||
const result = await sendChunkedMessage(finalPrompt, !isQuiet, suggestReplies, signal)
|
||||
reply = result.reply;
|
||||
messages_to_purge = result.chunks + 1; // +1 for the reply
|
||||
}
|
||||
else {
|
||||
console.debug('Sending prompt in one message');
|
||||
reply = await sendMessage(finalPrompt, !isQuiet, !isQuiet, signal);
|
||||
reply = await sendMessage(finalPrompt, !isQuiet, suggestReplies, signal);
|
||||
messages_to_purge = 2; // prompt and the reply
|
||||
}
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
async function sendChunkedMessage(finalPrompt, withStreaming, signal) {
|
||||
async function sendChunkedMessage(finalPrompt, withStreaming, withSuggestions, signal) {
|
||||
const fastReplyPrompt = '\n[Reply to this message with a full stop only]';
|
||||
const promptChunks = splitRecursive(finalPrompt, CHUNKED_PROMPT_LENGTH - fastReplyPrompt.length);
|
||||
console.debug(`Splitting prompt into ${promptChunks.length} chunks`, promptChunks);
|
||||
@@ -291,7 +302,7 @@ async function sendChunkedMessage(finalPrompt, withStreaming, signal) {
|
||||
console.debug(`Sending chunk ${i + 1}/${promptChunks.length}: ${promptChunk}`);
|
||||
if (i == promptChunks.length - 1) {
|
||||
// Extract reply of the last chunk
|
||||
reply = await sendMessage(promptChunk, withStreaming, true, signal);
|
||||
reply = await sendMessage(promptChunk, withStreaming, withSuggestions, signal);
|
||||
} else {
|
||||
// Add fast reply prompt to the chunk
|
||||
promptChunk += fastReplyPrompt;
|
||||
@@ -333,6 +344,8 @@ async function sendMessage(prompt, withStreaming, withSuggestions, signal) {
|
||||
signal = new AbortController().signal;
|
||||
}
|
||||
|
||||
await rateLimiter.waitForResolve(signal);
|
||||
|
||||
const body = JSON.stringify({
|
||||
bot: poe_settings.bot,
|
||||
streaming: withStreaming && poe_settings.streaming,
|
||||
|
@@ -12,18 +12,26 @@ import {
|
||||
eventSource,
|
||||
event_types,
|
||||
getCurrentChatId,
|
||||
is_send_press,
|
||||
printCharacters,
|
||||
name1,
|
||||
name2,
|
||||
replaceCurrentChat,
|
||||
setCharacterId
|
||||
} from "../script.js";
|
||||
import { favsToHotswap } from "./RossAscends-mods.js";
|
||||
import { favsToHotswap, isMobile, initMovingUI } from "./RossAscends-mods.js";
|
||||
import {
|
||||
groups,
|
||||
resetSelectedGroup,
|
||||
selected_group,
|
||||
} from "./group-chats.js";
|
||||
|
||||
import { registerSlashCommand } from "./slash-commands.js";
|
||||
|
||||
import { delay } from "./utils.js";
|
||||
|
||||
export {
|
||||
loadPowerUserSettings,
|
||||
loadMovingUIState,
|
||||
collapseNewlines,
|
||||
playMessageSound,
|
||||
sortGroupMembers,
|
||||
@@ -43,9 +51,10 @@ const avatar_styles = {
|
||||
RECTANGULAR: 1,
|
||||
}
|
||||
|
||||
const chat_styles = {
|
||||
export const chat_styles = {
|
||||
DEFAULT: 0,
|
||||
BUBBLES: 1,
|
||||
DOCUMENT: 2,
|
||||
}
|
||||
|
||||
const sheld_width = {
|
||||
@@ -74,6 +83,13 @@ const send_on_enter_options = {
|
||||
ENABLED: 1,
|
||||
}
|
||||
|
||||
export const persona_description_positions = {
|
||||
BEFORE_CHAR: 0,
|
||||
AFTER_CHAR: 1,
|
||||
TOP_AN: 2,
|
||||
BOTTOM_AN: 3,
|
||||
}
|
||||
|
||||
let power_user = {
|
||||
tokenizer: tokenizers.CLASSIC,
|
||||
token_padding: 64,
|
||||
@@ -88,17 +104,24 @@ let power_user = {
|
||||
trim_sentences: false,
|
||||
include_newline: false,
|
||||
always_force_name2: false,
|
||||
user_prompt_bias: '',
|
||||
show_user_prompt_bias: true,
|
||||
multigen: false,
|
||||
multigen_first_chunk: 50,
|
||||
multigen_next_chunks: 30,
|
||||
custom_chat_separator: '',
|
||||
markdown_escape_strings: '',
|
||||
|
||||
fast_ui_mode: true,
|
||||
avatar_style: avatar_styles.ROUND,
|
||||
chat_display: chat_styles.DEFAULT,
|
||||
sheld_width: sheld_width.DEFAULT,
|
||||
never_resize_avatars: false,
|
||||
show_card_avatar_urls: false,
|
||||
play_message_sound: false,
|
||||
play_sound_unfocused: true,
|
||||
auto_save_msg_edits: false,
|
||||
|
||||
sort_field: 'name',
|
||||
sort_order: 'asc',
|
||||
sort_rule: null,
|
||||
@@ -109,14 +132,16 @@ let power_user = {
|
||||
main_text_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeBodyColor').trim()}`,
|
||||
italics_text_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeEmColor').trim()}`,
|
||||
quote_text_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeQuoteColor').trim()}`,
|
||||
fastui_bg_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeFastUIBGColor').trim()}`,
|
||||
blur_tint_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeBlurTintColor').trim()}`,
|
||||
user_mes_blur_tint_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeUserMesBlurTintColor').trim()}`,
|
||||
bot_mes_blur_tint_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeBotMesBlurTintColor').trim()}`,
|
||||
shadow_color: `${getComputedStyle(document.documentElement).getPropertyValue('--SmartThemeShadowColor').trim()}`,
|
||||
|
||||
waifuMode: false,
|
||||
movingUI: false,
|
||||
movingUIState: {},
|
||||
noShadows: false,
|
||||
theme: 'Default (Dark)',
|
||||
theme: 'Default (Dark) 1.7.1',
|
||||
|
||||
auto_swipe: false,
|
||||
auto_swipe_minimum_length: 0,
|
||||
@@ -129,11 +154,16 @@ let power_user = {
|
||||
render_formulas: false,
|
||||
allow_name1_display: false,
|
||||
allow_name2_display: false,
|
||||
//removeXML: false,
|
||||
hotswap_enabled: true,
|
||||
timer_enabled: true,
|
||||
timestamps_enabled: true,
|
||||
mesIDDisplay_enabled: false,
|
||||
max_context_unlocked: false,
|
||||
prefer_character_prompt: true,
|
||||
prefer_character_jailbreak: true,
|
||||
continue_on_send: false,
|
||||
trim_spaces: true,
|
||||
|
||||
instruct: {
|
||||
enabled: false,
|
||||
@@ -146,10 +176,15 @@ let power_user = {
|
||||
output_sequence: '### Response:',
|
||||
preset: 'Alpaca',
|
||||
separator_sequence: '',
|
||||
macro: false,
|
||||
},
|
||||
|
||||
personas: {},
|
||||
default_persona: null,
|
||||
persona_descriptions: {},
|
||||
|
||||
persona_description: '',
|
||||
persona_description_position: persona_description_positions.BEFORE_CHAR,
|
||||
};
|
||||
|
||||
let themes = [];
|
||||
@@ -165,8 +200,9 @@ const storage_keys = {
|
||||
main_text_color: "TavernAI_main_text_color",
|
||||
italics_text_color: "TavernAI_italics_text_color",
|
||||
quote_text_color: "TavernAI_quote_text_color",
|
||||
fastui_bg_color: "TavernAI_fastui_bg_color",
|
||||
blur_tint_color: "TavernAI_blur_tint_color",
|
||||
user_mes_blur_tint_color: "TavernAI_user_mes_blur_tint_color",
|
||||
bot_mes_blur_tint_color: "TavernAI_bot_mes_blur_tint_color",
|
||||
blur_strength: "TavernAI_blur_strength",
|
||||
shadow_color: "TavernAI_shadow_color",
|
||||
shadow_width: "TavernAI_shadow_width",
|
||||
@@ -177,6 +213,8 @@ const storage_keys = {
|
||||
|
||||
hotswap_enabled: 'HotswapEnabled',
|
||||
timer_enabled: 'TimerEnabled',
|
||||
timestamps_enabled: 'TimestampsEnabled',
|
||||
mesIDDisplay_enabled: 'mesIDDisplayEnabled',
|
||||
};
|
||||
|
||||
let browser_has_focus = true;
|
||||
@@ -247,6 +285,20 @@ function switchTimer() {
|
||||
$("#messageTimerEnabled").prop("checked", power_user.timer_enabled);
|
||||
}
|
||||
|
||||
function switchTimestamps() {
|
||||
const value = localStorage.getItem(storage_keys.timestamps_enabled);
|
||||
power_user.timestamps_enabled = value === null ? true : value == "true";
|
||||
$("body").toggleClass("no-timestamps", !power_user.timestamps_enabled);
|
||||
$("#messageTimestampsEnabled").prop("checked", power_user.timestamps_enabled);
|
||||
}
|
||||
|
||||
function switchMesIDDisplay() {
|
||||
const value = localStorage.getItem(storage_keys.mesIDDisplay_enabled);
|
||||
power_user.mesIDDisplay_enabled = value === null ? true : value == "true";
|
||||
$("body").toggleClass("no-mesIDDisplay", !power_user.mesIDDisplay_enabled);
|
||||
$("#MesIDDisplayEnabled").prop("checked", power_user.mesIDDisplay_enabled);
|
||||
}
|
||||
|
||||
function switchUiMode() {
|
||||
const fastUi = localStorage.getItem(storage_keys.fast_ui_mode);
|
||||
power_user.fast_ui_mode = fastUi === null ? true : fastUi == "true";
|
||||
@@ -259,7 +311,6 @@ function toggleWaifu() {
|
||||
}
|
||||
|
||||
function switchWaifuMode() {
|
||||
console.log(`switching waifu to ${power_user.waifuMode}`);
|
||||
$("body").toggleClass("waifuMode", power_user.waifuMode);
|
||||
$("#waifuMode").prop("checked", power_user.waifuMode);
|
||||
scrollChatToBottom();
|
||||
@@ -269,7 +320,12 @@ function switchMovingUI() {
|
||||
const movingUI = localStorage.getItem(storage_keys.movingUI);
|
||||
power_user.movingUI = movingUI === null ? false : movingUI == "true";
|
||||
$("body").toggleClass("movingUI", power_user.movingUI);
|
||||
scrollChatToBottom();
|
||||
if (power_user.movingUI === true) {
|
||||
initMovingUI()
|
||||
if (power_user.movingUIState) {
|
||||
loadMovingUIState();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function noShadows() {
|
||||
@@ -288,10 +344,35 @@ function applyAvatarStyle() {
|
||||
}
|
||||
|
||||
function applyChatDisplay() {
|
||||
power_user.chat_display = Number(localStorage.getItem(storage_keys.chat_display) ?? chat_styles.DEFAULT);
|
||||
$("body").toggleClass("bubblechat", power_user.chat_display === chat_styles.BUBBLES);
|
||||
$(`input[name="chat_display"][value="${power_user.chat_display}"]`).prop("checked", true);
|
||||
|
||||
if (!power_user.chat_display === (null || undefined)) {
|
||||
console.debug('applyChatDisplay: saw no chat display type defined')
|
||||
return
|
||||
}
|
||||
console.debug(`applyChatDisplay: applying ${power_user.chat_display}`)
|
||||
|
||||
$(`#chat_display option[value=${power_user.chat_display}]`).attr("selected", true)
|
||||
|
||||
switch (power_user.chat_display) {
|
||||
case 0: {
|
||||
console.log('applying default chat')
|
||||
$("body").removeClass("bubblechat");
|
||||
$("body").removeClass("documentstyle");
|
||||
break
|
||||
}
|
||||
case 1: {
|
||||
console.log('applying bubblechat')
|
||||
$("body").addClass("bubblechat");
|
||||
$("body").removeClass("documentstyle");
|
||||
break
|
||||
}
|
||||
case 2: {
|
||||
console.log('applying document style')
|
||||
$("body").removeClass("bubblechat");
|
||||
$("body").addClass("documentstyle");
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function applySheldWidth() {
|
||||
@@ -316,12 +397,18 @@ async function applyThemeColor(type) {
|
||||
if (type === 'quote') {
|
||||
document.documentElement.style.setProperty('--SmartThemeQuoteColor', power_user.quote_text_color);
|
||||
}
|
||||
if (type === 'fastUIBG') {
|
||||
document.documentElement.style.setProperty('--SmartThemeFastUIBGColor', power_user.fastui_bg_color);
|
||||
}
|
||||
/* if (type === 'fastUIBG') {
|
||||
document.documentElement.style.setProperty('--SmartThemeFastUIBGColor', power_user.fastui_bg_color);
|
||||
} */
|
||||
if (type === 'blurTint') {
|
||||
document.documentElement.style.setProperty('--SmartThemeBlurTintColor', power_user.blur_tint_color);
|
||||
}
|
||||
if (type === 'userMesBlurTint') {
|
||||
document.documentElement.style.setProperty('--SmartThemeUserMesBlurTintColor', power_user.user_mes_blur_tint_color);
|
||||
}
|
||||
if (type === 'botMesBlurTint') {
|
||||
document.documentElement.style.setProperty('--SmartThemeBotMesBlurTintColor', power_user.bot_mes_blur_tint_color);
|
||||
}
|
||||
if (type === 'shadow') {
|
||||
document.documentElement.style.setProperty('--SmartThemeShadowColor', power_user.shadow_color);
|
||||
}
|
||||
@@ -361,8 +448,9 @@ async function applyTheme(name) {
|
||||
{ key: 'main_text_color', selector: '#main-text-color-picker', type: 'main' },
|
||||
{ key: 'italics_text_color', selector: '#italics-color-picker', type: 'italics' },
|
||||
{ key: 'quote_text_color', selector: '#quote-color-picker', type: 'quote' },
|
||||
{ key: 'fastui_bg_color', selector: '#fastui-bg-color-picker', type: 'fastUIBG' },
|
||||
{ key: 'blur_tint_color', selector: '#blur-tint-color-picker', type: 'blurTint' },
|
||||
{ key: 'user_mes_blur_tint_color', selector: '#user-mes-blur-tint-color-picker', type: 'userMesBlurTint' },
|
||||
{ key: 'bot_mes_blur_tint_color', selector: '#bot-mes-blur-tint-color-picker', type: 'botMesBlurTint' },
|
||||
{ key: 'shadow_color', selector: '#shadow-color-picker', type: 'shadow' },
|
||||
{
|
||||
key: 'blur_strength',
|
||||
@@ -434,6 +522,20 @@ async function applyTheme(name) {
|
||||
switchTimer();
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'timestamps_enabled',
|
||||
action: async () => {
|
||||
localStorage.setItem(storage_keys.timestamps_enabled, power_user.timestamps_enabled);
|
||||
switchTimestamps();
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'mesIDDisplay_enabled',
|
||||
action: async () => {
|
||||
localStorage.setItem(storage_keys.mesIDDisplay_enabled, power_user.mesIDDisplay_enabled);
|
||||
switchMesIDDisplay();
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'hotswap_enabled',
|
||||
action: async () => {
|
||||
@@ -449,6 +551,10 @@ async function applyTheme(name) {
|
||||
if (selector) $(selector).attr('color', power_user[key]);
|
||||
if (type) await applyThemeColor(type);
|
||||
if (action) await action();
|
||||
} else {
|
||||
if (selector) { $(selector).attr('color', 'rgba(0,0,0,0)') };
|
||||
console.debug(`Empty theme key: ${key}`);
|
||||
power_user[key] = '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,11 +568,12 @@ applySheldWidth();
|
||||
applyAvatarStyle();
|
||||
applyBlurStrength();
|
||||
applyShadowWidth();
|
||||
applyChatDisplay();
|
||||
switchMovingUI();
|
||||
noShadows();
|
||||
switchHotswap();
|
||||
switchTimer();
|
||||
switchTimestamps();
|
||||
switchMesIDDisplay();
|
||||
|
||||
function loadPowerUserSettings(settings, data) {
|
||||
// Load from settings.json
|
||||
@@ -488,17 +595,23 @@ function loadPowerUserSettings(settings, data) {
|
||||
const noShadows = localStorage.getItem(storage_keys.noShadows);
|
||||
const hotswap = localStorage.getItem(storage_keys.hotswap_enabled);
|
||||
const timer = localStorage.getItem(storage_keys.timer_enabled);
|
||||
const timestamps = localStorage.getItem(storage_keys.timestamps_enabled);
|
||||
const mesIDDisplay = localStorage.getItem(storage_keys.mesIDDisplay_enabled);
|
||||
power_user.fast_ui_mode = fastUi === null ? true : fastUi == "true";
|
||||
power_user.movingUI = movingUI === null ? false : movingUI == "true";
|
||||
power_user.noShadows = noShadows === null ? false : noShadows == "true";
|
||||
power_user.hotswap_enabled = hotswap === null ? true : hotswap == "true";
|
||||
power_user.timer_enabled = timer === null ? true : timer == "true";
|
||||
power_user.timestamps_enabled = timestamps === null ? true : timestamps == "true";
|
||||
power_user.mesIDDisplay_enabled = mesIDDisplay === null ? true : mesIDDisplay == "true";
|
||||
power_user.avatar_style = Number(localStorage.getItem(storage_keys.avatar_style) ?? avatar_styles.ROUND);
|
||||
power_user.chat_display = Number(localStorage.getItem(storage_keys.chat_display) ?? chat_styles.DEFAULT);
|
||||
//power_user.chat_display = Number(localStorage.getItem(storage_keys.chat_display) ?? chat_styles.DEFAULT);
|
||||
power_user.sheld_width = Number(localStorage.getItem(storage_keys.sheld_width) ?? sheld_width.DEFAULT);
|
||||
power_user.font_scale = Number(localStorage.getItem(storage_keys.font_scale) ?? 1);
|
||||
power_user.blur_strength = Number(localStorage.getItem(storage_keys.blur_strength) ?? 10);
|
||||
|
||||
$('#trim_spaces').prop("checked", power_user.trim_spaces);
|
||||
$('#continue_on_send').prop("checked", power_user.continue_on_send);
|
||||
$('#auto_swipe').prop("checked", power_user.auto_swipe);
|
||||
$('#auto_swipe_minimum_length').val(power_user.auto_swipe_minimum_length);
|
||||
$('#auto_swipe_blacklist').val(power_user.auto_swipe_blacklist.join(", "));
|
||||
@@ -510,6 +623,7 @@ function loadPowerUserSettings(settings, data) {
|
||||
$(`#tokenizer option[value="${power_user.tokenizer}"]`).attr('selected', true);
|
||||
$(`#pygmalion_formatting option[value=${power_user.pygmalion_formatting}]`).attr("selected", true);
|
||||
$(`#send_on_enter option[value=${power_user.send_on_enter}]`).attr("selected", true);
|
||||
$("#import_card_tags").prop("checked", power_user.import_card_tags);
|
||||
$("#collapse-newlines-checkbox").prop("checked", power_user.collapse_newlines);
|
||||
$("#pin-examples-checkbox").prop("checked", power_user.pin_examples);
|
||||
$("#disable-description-formatting-checkbox").prop("checked", power_user.disable_description_formatting);
|
||||
@@ -522,24 +636,32 @@ function loadPowerUserSettings(settings, data) {
|
||||
$("#include_newline_checkbox").prop("checked", power_user.include_newline);
|
||||
$('#render_formulas').prop("checked", power_user.render_formulas);
|
||||
$("#custom_chat_separator").val(power_user.custom_chat_separator);
|
||||
$("#markdown_escape_strings").val(power_user.markdown_escape_strings);
|
||||
$("#fast_ui_mode").prop("checked", power_user.fast_ui_mode);
|
||||
$("#waifuMode").prop("checked", power_user.waifuMode);
|
||||
$("#movingUImode").prop("checked", power_user.movingUI);
|
||||
$("#noShadowsmode").prop("checked", power_user.noShadows);
|
||||
$("#start_reply_with").val(power_user.user_prompt_bias);
|
||||
$("#chat-show-reply-prefix-checkbox").prop("checked", power_user.show_user_prompt_bias);
|
||||
$("#multigen").prop("checked", power_user.multigen);
|
||||
$("#multigen_first_chunk").val(power_user.multigen_first_chunk);
|
||||
$("#multigen_next_chunks").val(power_user.multigen_next_chunks);
|
||||
$("#play_message_sound").prop("checked", power_user.play_message_sound);
|
||||
$("#play_sound_unfocused").prop("checked", power_user.play_sound_unfocused);
|
||||
$("#never_resize_avatars").prop("checked", power_user.never_resize_avatars);
|
||||
$("#show_card_avatar_urls").prop("checked", power_user.show_card_avatar_urls);
|
||||
$("#auto_save_msg_edits").prop("checked", power_user.auto_save_msg_edits);
|
||||
$("#allow_name1_display").prop("checked", power_user.allow_name1_display);
|
||||
$("#allow_name2_display").prop("checked", power_user.allow_name2_display);
|
||||
//$("#removeXML").prop("checked", power_user.removeXML);
|
||||
$("#hotswapEnabled").prop("checked", power_user.hotswap_enabled);
|
||||
$("#messageTimerEnabled").prop("checked", power_user.timer_enabled);
|
||||
$("#messageTimestampsEnabled").prop("checked", power_user.timestamps_enabled);
|
||||
$("#mesIDDisplayEnabled").prop("checked", power_user.mesIDDisplay_enabled);
|
||||
$("#prefer_character_prompt").prop("checked", power_user.prefer_character_prompt);
|
||||
$("#prefer_character_jailbreak").prop("checked", power_user.prefer_character_jailbreak);
|
||||
$(`input[name="avatar_style"][value="${power_user.avatar_style}"]`).prop("checked", true);
|
||||
$(`input[name="chat_display"][value="${power_user.chat_display}"]`).prop("checked", true);
|
||||
$(`#chat_display option[value=${power_user.chat_display}]`).attr("selected", true).trigger('change');
|
||||
$(`input[name="sheld_width"][value="${power_user.sheld_width}"]`).prop("checked", true);
|
||||
$("#token_padding").val(power_user.token_padding);
|
||||
|
||||
@@ -555,8 +677,10 @@ function loadPowerUserSettings(settings, data) {
|
||||
$("#main-text-color-picker").attr('color', power_user.main_text_color);
|
||||
$("#italics-color-picker").attr('color', power_user.italics_text_color);
|
||||
$("#quote-color-picker").attr('color', power_user.quote_text_color);
|
||||
$("#fastui-bg-color-picker").attr('color', power_user.fastui_bg_color);
|
||||
//$("#fastui-bg-color-picker").attr('color', power_user.fastui_bg_color);
|
||||
$("#blur-tint-color-picker").attr('color', power_user.blur_tint_color);
|
||||
$("#user-mes-blur-tint-color-picker").attr('color', power_user.user_mes_blur_tint_color);
|
||||
$("#bot-mes-blur-tint-color-picker").attr('color', power_user.bot_mes_blur_tint_color);
|
||||
$("#shadow-color-picker").attr('color', power_user.shadow_color);
|
||||
|
||||
for (const theme of themes) {
|
||||
@@ -573,6 +697,34 @@ function loadPowerUserSettings(settings, data) {
|
||||
loadInstructMode();
|
||||
loadMaxContextUnlocked();
|
||||
switchWaifuMode();
|
||||
loadMovingUIState();
|
||||
|
||||
//console.log(power_user)
|
||||
}
|
||||
|
||||
function loadMovingUIState() {
|
||||
if (isMobile() === false
|
||||
&& power_user.movingUIState
|
||||
&& power_user.movingUI === true) {
|
||||
console.debug('loading movingUI state')
|
||||
for (var elmntName of Object.keys(power_user.movingUIState)) {
|
||||
var elmntState = power_user.movingUIState[elmntName];
|
||||
try {
|
||||
var elmnt = $('#' + $.escapeSelector(elmntName));
|
||||
if (elmnt.length) {
|
||||
console.debug(`loading state for ${elmntName}`)
|
||||
elmnt.css(elmntState);
|
||||
} else {
|
||||
console.debug(`skipping ${elmntName} because it doesn't exist in the DOM`)
|
||||
}
|
||||
} catch (err) {
|
||||
console.debug(`error occurred while processing ${elmntName}: ${err}`)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.debug('skipping movingUI state load')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function loadMaxContextUnlocked() {
|
||||
@@ -607,6 +759,7 @@ function loadInstructMode() {
|
||||
{ id: "instruct_output_sequence", property: "output_sequence", isCheckbox: false },
|
||||
{ id: "instruct_stop_sequence", property: "stop_sequence", isCheckbox: false },
|
||||
{ id: "instruct_names", property: "names", isCheckbox: true },
|
||||
{ id: "instruct_macro", property: "macro", isCheckbox: true },
|
||||
];
|
||||
|
||||
controls.forEach(control => {
|
||||
@@ -659,11 +812,11 @@ function loadInstructMode() {
|
||||
|
||||
export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvatar, name1, name2) {
|
||||
const includeNames = isNarrator ? false : (power_user.instruct.names || !!selected_group || !!forceAvatar);
|
||||
const sequence = substituteParams(
|
||||
(isUser || isNarrator) ? power_user.instruct.input_sequence : power_user.instruct.output_sequence,
|
||||
name1,
|
||||
name2
|
||||
);
|
||||
let sequence = (isUser || isNarrator) ? power_user.instruct.input_sequence : power_user.instruct.output_sequence;
|
||||
|
||||
if (power_user.instruct.macro) {
|
||||
sequence = substituteParams(sequence, name1, name2);
|
||||
}
|
||||
|
||||
const separator = power_user.instruct.wrap ? '\n' : '';
|
||||
const separatorSequence = power_user.instruct.separator_sequence && !isUser
|
||||
@@ -678,20 +831,20 @@ export function formatInstructStoryString(story, systemPrompt) {
|
||||
// If the character has a custom system prompt AND user has it preferred, use that instead of the default
|
||||
systemPrompt = power_user.prefer_character_prompt && systemPrompt ? systemPrompt : power_user.instruct.system_prompt;
|
||||
const sequence = power_user.instruct.system_sequence || '';
|
||||
const prompt = substituteParams(systemPrompt) || '';
|
||||
const prompt = substituteParams(systemPrompt, name1, name2, power_user.instruct.system_prompt) || '';
|
||||
const separator = power_user.instruct.wrap ? '\n' : '';
|
||||
const textArray = [sequence, prompt + '\n' + story, separator];
|
||||
const textArray = [sequence, prompt + '\n' + story];
|
||||
const text = textArray.filter(x => x).join(separator);
|
||||
return text;
|
||||
}
|
||||
|
||||
export function formatInstructModePrompt(name, isImpersonate, promptBias, name1, name2) {
|
||||
const includeNames = power_user.instruct.names || !!selected_group;
|
||||
const sequence = substituteParams(
|
||||
isImpersonate ? power_user.instruct.input_sequence : power_user.instruct.output_sequence,
|
||||
name1,
|
||||
name2
|
||||
);
|
||||
let sequence = isImpersonate ? power_user.instruct.input_sequence : power_user.instruct.output_sequence;
|
||||
|
||||
if (power_user.instruct.macro) {
|
||||
sequence = substituteParams(sequence, name1, name2);
|
||||
}
|
||||
|
||||
const separator = power_user.instruct.wrap ? '\n' : '';
|
||||
let text = includeNames ? (separator + sequence + separator + `${name}:`) : (separator + sequence);
|
||||
@@ -775,8 +928,10 @@ async function saveTheme() {
|
||||
main_text_color: power_user.main_text_color,
|
||||
italics_text_color: power_user.italics_text_color,
|
||||
quote_text_color: power_user.quote_text_color,
|
||||
fastui_bg_color: power_user.fastui_bg_color,
|
||||
//fastui_bg_color: power_user.fastui_bg_color,
|
||||
blur_tint_color: power_user.blur_tint_color,
|
||||
user_mes_blur_tint_color: power_user.user_mes_blur_tint_color,
|
||||
bot_mes_blur_tint_color: power_user.bot_mes_blur_tint_color,
|
||||
shadow_color: power_user.shadow_color,
|
||||
shadow_width: power_user.shadow_width,
|
||||
font_scale: power_user.font_scale,
|
||||
@@ -787,6 +942,8 @@ async function saveTheme() {
|
||||
noShadows: power_user.noShadows,
|
||||
sheld_width: power_user.sheld_width,
|
||||
timer_enabled: power_user.timer_enabled,
|
||||
timestamps_enabled: power_user.timestamps_enabled,
|
||||
mesIDDisplay_enabled: power_user.mesIDDisplay_enabled,
|
||||
hotswap_enabled: power_user.hotswap_enabled,
|
||||
|
||||
};
|
||||
@@ -819,54 +976,43 @@ async function saveTheme() {
|
||||
}
|
||||
|
||||
function resetMovablePanels() {
|
||||
document.getElementById("sheld").style.top = '';
|
||||
document.getElementById("sheld").style.left = '';
|
||||
document.getElementById("sheld").style.bottom = '';
|
||||
document.getElementById("sheld").style.right = '';
|
||||
document.getElementById("sheld").style.height = '';
|
||||
document.getElementById("sheld").style.width = '';
|
||||
document.getElementById("sheld").style.margin = '';
|
||||
const panelIds = [
|
||||
'sheld',
|
||||
'left-nav-panel',
|
||||
'right-nav-panel',
|
||||
'WorldInfo',
|
||||
'floatingPrompt',
|
||||
'expression-holder',
|
||||
];
|
||||
|
||||
const panelStyles = ['top', 'left', 'right', 'bottom', 'height', 'width', 'margin',];
|
||||
|
||||
document.getElementById("left-nav-panel").style.top = '';
|
||||
document.getElementById("left-nav-panel").style.left = '';
|
||||
document.getElementById("left-nav-panel").style.height = '';
|
||||
document.getElementById("left-nav-panel").style.width = '';
|
||||
document.getElementById("left-nav-panel").style.margin = '';
|
||||
panelIds.forEach((id) => {
|
||||
const panel = document.getElementById(id);
|
||||
|
||||
document.getElementById("right-nav-panel").style.top = '';
|
||||
document.getElementById("right-nav-panel").style.left = '';
|
||||
document.getElementById("right-nav-panel").style.right = '';
|
||||
document.getElementById("right-nav-panel").style.height = '';
|
||||
document.getElementById("right-nav-panel").style.width = '';
|
||||
document.getElementById("right-nav-panel").style.margin = '';
|
||||
if (panel) {
|
||||
panelStyles.forEach((style) => {
|
||||
panel.style[style] = '';
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("expression-holder").style.top = '';
|
||||
document.getElementById("expression-holder").style.left = '';
|
||||
document.getElementById("expression-holder").style.right = '';
|
||||
document.getElementById("expression-holder").style.bottom = '';
|
||||
document.getElementById("expression-holder").style.height = '';
|
||||
document.getElementById("expression-holder").style.width = '';
|
||||
document.getElementById("expression-holder").style.margin = '';
|
||||
const zoomedAvatar = document.querySelector('.zoomed_avatar');
|
||||
if (zoomedAvatar) {
|
||||
panelStyles.forEach((style) => {
|
||||
zoomedAvatar.style[style] = '';
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById("avatar_zoom_popup").style.top = '';
|
||||
document.getElementById("avatar_zoom_popup").style.left = '';
|
||||
document.getElementById("avatar_zoom_popup").style.right = '';
|
||||
document.getElementById("avatar_zoom_popup").style.bottom = '';
|
||||
document.getElementById("avatar_zoom_popup").style.height = '';
|
||||
document.getElementById("avatar_zoom_popup").style.width = '';
|
||||
document.getElementById("avatar_zoom_popup").style.margin = '';
|
||||
|
||||
document.getElementById("WorldInfo").style.top = '';
|
||||
document.getElementById("WorldInfo").style.left = '';
|
||||
document.getElementById("WorldInfo").style.right = '';
|
||||
document.getElementById("WorldInfo").style.bottom = '';
|
||||
document.getElementById("WorldInfo").style.height = '';
|
||||
document.getElementById("WorldInfo").style.width = '';
|
||||
document.getElementById("WorldInfo").style.margin = '';
|
||||
|
||||
$('*[data-dragged="true"]').removeAttr('data-dragged');
|
||||
$('[data-dragged="true"]').removeAttr('data-dragged');
|
||||
power_user.movingUIState = {};
|
||||
saveSettingsDebounced();
|
||||
eventSource.emit(event_types.MOVABLE_PANELS_RESET);
|
||||
|
||||
eventSource.once(event_types.SETTINGS_UPDATED, () => {
|
||||
toastr.success('Panel positions reset');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function doNewChat() {
|
||||
@@ -879,10 +1025,84 @@ function doNewChat() {
|
||||
}, 1);
|
||||
}
|
||||
|
||||
function doDelMode() {
|
||||
function doRandomChat() {
|
||||
resetSelectedGroup();
|
||||
setCharacterId(Math.floor(Math.random() * characters.length));
|
||||
setTimeout(() => {
|
||||
$("#option_delete_mes").trigger('click')
|
||||
replaceCurrentChat();
|
||||
}, 1);
|
||||
|
||||
}
|
||||
|
||||
async function doMesCut(_, text) {
|
||||
|
||||
//reject invalid args or no args
|
||||
if (text && isNaN(text) || !text) {
|
||||
toastr.error(`Must enter a single number only, non-number characters disallowed.`)
|
||||
return
|
||||
}
|
||||
|
||||
//reject attempts to delete firstmes
|
||||
if (text === 0) {
|
||||
toastr.error('Cannot delete the First Message')
|
||||
return
|
||||
}
|
||||
|
||||
let mesIDToCut = Number(text).toFixed(0)
|
||||
let mesToCut = $("#chat").find(`.mes[mesid=${mesIDToCut}]`)
|
||||
|
||||
if (!mesToCut.length) {
|
||||
toastr.error(`Could not find message with ID: ${mesIDToCut}`)
|
||||
return
|
||||
}
|
||||
|
||||
mesToCut.find('.mes_edit_delete').trigger('click');
|
||||
$('#dialogue_popup_ok').trigger('click');
|
||||
}
|
||||
|
||||
|
||||
async function doDelMode(_, text) {
|
||||
|
||||
//first enter delmode
|
||||
$("#option_delete_mes").trigger('click')
|
||||
|
||||
//reject invalid args
|
||||
if (text && isNaN(text)) {
|
||||
toastr.warning('Must enter a number or nothing.')
|
||||
await delay(300) //unsure why 300 is neccessary here, but any shorter and it wont see the delmode UI
|
||||
$("#dialogue_del_mes_cancel").trigger('click');
|
||||
return
|
||||
}
|
||||
|
||||
//parse valid args
|
||||
if (text) {
|
||||
await delay(300) //same as above, need event signal for 'entered del mode'
|
||||
console.debug('parsing msgs to del')
|
||||
let numMesToDel = Number(text).toFixed(0)
|
||||
let lastMesID = $('.last_mes').attr('mesid')
|
||||
let oldestMesIDToDel = lastMesID - numMesToDel + 1;
|
||||
|
||||
//disallow targeting first message
|
||||
if (oldestMesIDToDel <= 0) {
|
||||
oldestMesIDToDel = 1
|
||||
}
|
||||
|
||||
let oldestMesToDel = $('#chat').find(`.mes[mesid=${oldestMesIDToDel}]`)
|
||||
let oldestDelMesCheckbox = $(oldestMesToDel).find('.del_checkbox');
|
||||
let newLastMesID = oldestMesIDToDel - 1;
|
||||
console.debug(`DelMesReport -- numMesToDel: ${numMesToDel}, lastMesID: ${lastMesID}, oldestMesIDToDel:${oldestMesIDToDel}, newLastMesID: ${newLastMesID}`)
|
||||
oldestDelMesCheckbox.trigger('click');
|
||||
let trueNumberOfDeletedMessage = lastMesID - oldestMesIDToDel + 1
|
||||
|
||||
//await delay(1)
|
||||
$('#dialogue_del_mes_ok').trigger('click');
|
||||
toastr.success(`Deleted ${trueNumberOfDeletedMessage} messages.`)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function doResetPanels() {
|
||||
$("#movingUIreset").trigger('click');
|
||||
}
|
||||
|
||||
$(document).ready(() => {
|
||||
@@ -960,6 +1180,23 @@ $(document).ready(() => {
|
||||
reloadMarkdownProcessor(power_user.render_formulas);
|
||||
});
|
||||
|
||||
$("#markdown_escape_strings").on('input', function () {
|
||||
power_user.markdown_escape_strings = $(this).val();
|
||||
saveSettingsDebounced();
|
||||
reloadMarkdownProcessor(power_user.render_formulas);
|
||||
});
|
||||
|
||||
$("#start_reply_with").on('input', function() {
|
||||
power_user.user_prompt_bias = $(this).val();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#chat-show-reply-prefix-checkbox").change(function () {
|
||||
power_user.show_user_prompt_bias = !!$(this).prop("checked");
|
||||
reloadCurrentChat();
|
||||
saveSettingsDebounced();
|
||||
})
|
||||
|
||||
$("#multigen").change(function () {
|
||||
power_user.multigen = $(this).prop("checked");
|
||||
saveSettingsDebounced();
|
||||
@@ -998,10 +1235,13 @@ $(document).ready(() => {
|
||||
applyAvatarStyle();
|
||||
});
|
||||
|
||||
$(`input[name="chat_display"]`).on('input', function (e) {
|
||||
power_user.chat_display = Number(e.target.value);
|
||||
localStorage.setItem(storage_keys.chat_display, power_user.chat_display);
|
||||
$("#chat_display").on('change', function () {
|
||||
console.debug('###CHAT DISPLAY SELECTOR CHANGE###')
|
||||
const value = $(this).find(':selected').val();
|
||||
power_user.chat_display = Number(value);
|
||||
saveSettingsDebounced();
|
||||
applyChatDisplay();
|
||||
|
||||
});
|
||||
|
||||
$(`input[name="sheld_width"]`).on('input', function (e) {
|
||||
@@ -1050,18 +1290,24 @@ $(document).ready(() => {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#fastui-bg-color-picker").on('change', (evt) => {
|
||||
power_user.fastui_bg_color = evt.detail.rgba;
|
||||
applyThemeColor('fastUIBG');
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#blur-tint-color-picker").on('change', (evt) => {
|
||||
power_user.blur_tint_color = evt.detail.rgba;
|
||||
applyThemeColor('blurTint');
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#user-mes-blur-tint-color-picker").on('change', (evt) => {
|
||||
power_user.user_mes_blur_tint_color = evt.detail.rgba;
|
||||
applyThemeColor('userMesBlurTint');
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#bot-mes-blur-tint-color-picker").on('change', (evt) => {
|
||||
power_user.bot_mes_blur_tint_color = evt.detail.rgba;
|
||||
applyThemeColor('botMesBlurTint');
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#shadow-color-picker").on('change', (evt) => {
|
||||
power_user.shadow_color = evt.detail.rgba;
|
||||
applyThemeColor('shadow');
|
||||
@@ -1077,6 +1323,16 @@ $(document).ready(() => {
|
||||
|
||||
$("#ui-preset-save-button").on('click', saveTheme);
|
||||
|
||||
$("#never_resize_avatars").on('input', function () {
|
||||
power_user.never_resize_avatars = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
$("#show_card_avatar_urls").on('input', function () {
|
||||
power_user.show_card_avatar_urls = !!$(this).prop('checked');
|
||||
printCharacters();
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#play_message_sound").on('input', function () {
|
||||
power_user.play_message_sound = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
@@ -1173,6 +1429,11 @@ $(document).ready(() => {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#import_card_tags").on('input', function () {
|
||||
power_user.import_card_tags = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#render_formulas").on("input", function () {
|
||||
power_user.render_formulas = !!$(this).prop('checked');
|
||||
reloadMarkdownProcessor(power_user.render_formulas);
|
||||
@@ -1199,6 +1460,12 @@ $(document).ready(() => {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
/* $("#removeXML").on("input", function () {
|
||||
power_user.removeXML = !!$(this).prop('checked');
|
||||
reloadCurrentChat();
|
||||
saveSettingsDebounced();
|
||||
}); */
|
||||
|
||||
$("#token_padding").on("input", function () {
|
||||
power_user.token_padding = Number($(this).val());
|
||||
saveSettingsDebounced();
|
||||
@@ -1211,6 +1478,20 @@ $(document).ready(() => {
|
||||
switchTimer();
|
||||
});
|
||||
|
||||
$("#messageTimestampsEnabled").on("input", function () {
|
||||
const value = !!$(this).prop('checked');
|
||||
power_user.timestamps_enabled = value;
|
||||
localStorage.setItem(storage_keys.timestamps_enabled, power_user.timestamps_enabled);
|
||||
switchTimestamps();
|
||||
});
|
||||
|
||||
$("#mesIDDisplayEnabled").on("input", function () {
|
||||
const value = !!$(this).prop('checked');
|
||||
power_user.mesIDDisplay_enabled = value;
|
||||
localStorage.setItem(storage_keys.mesIDDisplay_enabled, power_user.mesIDDisplay_enabled);
|
||||
switchMesIDDisplay();
|
||||
});
|
||||
|
||||
$("#hotswapEnabled").on("input", function () {
|
||||
const value = !!$(this).prop('checked');
|
||||
power_user.hotswap_enabled = value;
|
||||
@@ -1230,6 +1511,18 @@ $(document).ready(() => {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#continue_on_send").on("input", function () {
|
||||
const value = !!$(this).prop('checked');
|
||||
power_user.continue_on_send = value;
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$("#trim_spaces").on("input", function () {
|
||||
const value = !!$(this).prop('checked');
|
||||
power_user.trim_spaces = value;
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$(window).on('focus', function () {
|
||||
browser_has_focus = true;
|
||||
});
|
||||
@@ -1238,7 +1531,10 @@ $(document).ready(() => {
|
||||
browser_has_focus = false;
|
||||
});
|
||||
|
||||
registerSlashCommand('vn', toggleWaifu, ['vn'], ' – swaps Visual Novel Mode On/Off', false, true);
|
||||
registerSlashCommand('vn', toggleWaifu, [], ' – swaps Visual Novel Mode On/Off', false, true);
|
||||
registerSlashCommand('newchat', doNewChat, ['newchat'], ' – start a new chat with current character', true, true);
|
||||
registerSlashCommand('delmode', doDelMode, ['delmode'], ' – enter message deletion mode', true, true);
|
||||
registerSlashCommand('random', doRandomChat, ['random'], ' – start a new chat with a random character', true, true);
|
||||
registerSlashCommand('delmode', doDelMode, ['del'], '<span class="monospace">(optional number)</span> – enter message deletion mode, and auto-deletes N messages if numeric argument is provided', true, true);
|
||||
registerSlashCommand('cut', doMesCut, [], ' <span class="monospace">(requred number)</span> – cuts the specified message from the chat', true, true);
|
||||
registerSlashCommand('resetpanels', doResetPanels, ['resetui'], ' – resets UI panels to original state.', true, true);
|
||||
});
|
||||
|
@@ -6,6 +6,7 @@ export const SECRET_KEYS = {
|
||||
POE: 'api_key_poe',
|
||||
NOVEL: 'api_key_novel',
|
||||
CLAUDE: 'api_key_claude',
|
||||
OPENROUTER: 'api_key_openrouter',
|
||||
}
|
||||
|
||||
const INPUT_MAP = {
|
||||
@@ -14,6 +15,7 @@ const INPUT_MAP = {
|
||||
[SECRET_KEYS.POE]: '#poe_token',
|
||||
[SECRET_KEYS.NOVEL]: '#api_key_novel',
|
||||
[SECRET_KEYS.CLAUDE]: '#api_key_claude',
|
||||
[SECRET_KEYS.OPENROUTER]: '#api_key_openrouter',
|
||||
}
|
||||
|
||||
async function clearSecret() {
|
||||
@@ -54,7 +56,7 @@ async function viewSecrets() {
|
||||
table.classList.add('responsiveTable');
|
||||
$(table).append('<thead><th>Key</th><th>Value</th></thead>');
|
||||
|
||||
for (const [key,value] of Object.entries(data)) {
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
$(table).append(`<tr><td>${DOMPurify.sanitize(key)}</td><td>${DOMPurify.sanitize(value)}</td></tr>`);
|
||||
}
|
||||
|
||||
@@ -94,13 +96,63 @@ export async function readSecretState() {
|
||||
if (response.ok) {
|
||||
secret_state = await response.json();
|
||||
updateSecretDisplay();
|
||||
await checkOpenRouterAuth();
|
||||
}
|
||||
} catch {
|
||||
console.error('Could not read secrets file');
|
||||
}
|
||||
}
|
||||
|
||||
jQuery(() => {
|
||||
function authorizeOpenRouter() {
|
||||
const openRouterUrl = `https://openrouter.ai/auth?callback_url=${encodeURIComponent(location.origin)}`;
|
||||
location.href = openRouterUrl;
|
||||
}
|
||||
|
||||
async function checkOpenRouterAuth() {
|
||||
const params = new URLSearchParams(location.search);
|
||||
if (params.has('code')) {
|
||||
const code = params.get('code');
|
||||
try {
|
||||
const response = await fetch("https://openrouter.ai/api/v1/auth/keys", {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ code }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('OpenRouter exchange error');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
if (!data || !data.key) {
|
||||
throw new Error('OpenRouter invalid response');
|
||||
}
|
||||
|
||||
await writeSecret(SECRET_KEYS.OPENROUTER, data.key);
|
||||
|
||||
if (secret_state[SECRET_KEYS.OPENROUTER]) {
|
||||
toastr.success('OpenRouter token saved');
|
||||
// Remove the code from the URL
|
||||
const currentUrl = window.location.href;
|
||||
const urlWithoutSearchParams = currentUrl.split("?")[0];
|
||||
window.history.pushState({}, "", urlWithoutSearchParams);
|
||||
} else {
|
||||
throw new Error('OpenRouter token not saved');
|
||||
}
|
||||
} catch (err) {
|
||||
toastr.error('Could not verify OpenRouter token. Please try again.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jQuery(async () => {
|
||||
$('#viewSecrets').on('click', viewSecrets);
|
||||
$(document).on('click', '.clear-api-key', clearSecret);
|
||||
$(document).on('input', Object.values(INPUT_MAP).join(','), function () {
|
||||
const id = $(this).attr('id');
|
||||
const value = $(this).val();
|
||||
const warningElement = $(`[data-for="${id}"]`);
|
||||
warningElement.toggle(value.length > 0);
|
||||
});
|
||||
$('#openrouter_authorize').on('click', authorizeOpenRouter);
|
||||
});
|
||||
|
2
public/scripts/select2.min.js
vendored
Normal file
2
public/scripts/select2.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -1,25 +0,0 @@
|
||||
import { power_user } from './power-user.js';
|
||||
|
||||
// Showdown extension to make chat separators (dinkuses) ignore markdown formatting
|
||||
export const dinkusExtension = () => {
|
||||
if (!power_user) {
|
||||
console.log("Showdown-dinkus extension: power_user wasn't found! Returning.");
|
||||
return []
|
||||
}
|
||||
|
||||
// Create an escaped sequence so the regex can work with any character
|
||||
const savedDinkus = power_user.custom_chat_separator
|
||||
|
||||
// No dinkus? No extension!
|
||||
if (!savedDinkus || savedDinkus.trim().length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
const escapedDinkus = savedDinkus.split('').map((e) => `\\${e}`).join('');
|
||||
const replaceRegex = new RegExp(`^(${escapedDinkus})\n`, "gm")
|
||||
return [{
|
||||
type: "lang",
|
||||
regex: replaceRegex,
|
||||
replace: (match) => match.replace(replaceRegex, `<div>${savedDinkus}</div>`).trim()
|
||||
}];
|
||||
}
|
36
public/scripts/showdown-exclusion.js
Normal file
36
public/scripts/showdown-exclusion.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import { power_user } from './power-user.js';
|
||||
|
||||
// Showdown extension to make chat separators (dinkuses) ignore markdown formatting
|
||||
export const markdownExclusionExt = () => {
|
||||
if (!power_user) {
|
||||
console.log("Showdown-dinkus extension: power_user wasn't found! Returning.");
|
||||
return []
|
||||
}
|
||||
|
||||
let combinedExcludeString = '';
|
||||
if (power_user.custom_chat_separator) {
|
||||
combinedExcludeString += `${power_user.custom_chat_separator},`;
|
||||
}
|
||||
|
||||
if (power_user.markdown_escape_strings) {
|
||||
combinedExcludeString += power_user.markdown_escape_strings;
|
||||
}
|
||||
|
||||
const escapedExclusions = combinedExcludeString
|
||||
.split(",")
|
||||
.filter((element) => element.length > 0)
|
||||
.map((element) => `(${element.split('').map((char) => `\\${char}`).join('')})`);
|
||||
|
||||
|
||||
// No exclusions? No extension!
|
||||
if (!combinedExcludeString || combinedExcludeString.length === 0 || escapedExclusions.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const replaceRegex = new RegExp(`^(${escapedExclusions.join("|")})\n`, "gm");
|
||||
return [{
|
||||
type: "lang",
|
||||
regex: replaceRegex,
|
||||
replace: ((match) => match.replace(replaceRegex, `\u0000${match} \n`))
|
||||
}];
|
||||
}
|
@@ -14,11 +14,14 @@ import {
|
||||
sendSystemMessage,
|
||||
setUserName,
|
||||
substituteParams,
|
||||
comment_avatar,
|
||||
system_avatar,
|
||||
system_message_types
|
||||
system_message_types,
|
||||
name1,
|
||||
saveSettings,
|
||||
} from "../script.js";
|
||||
import { humanizedDateTime } from "./RossAscends-mods.js";
|
||||
import { power_user } from "./power-user.js";
|
||||
import { chat_styles, power_user } from "./power-user.js";
|
||||
export {
|
||||
executeSlashCommands,
|
||||
registerSlashCommand,
|
||||
@@ -103,9 +106,21 @@ parser.addCommand('bg', setBackgroundCallback, ['background'], '<span class="mon
|
||||
parser.addCommand('sendas', sendMessageAs, [], ` – sends message as a specific character.<br>Example:<br><pre><code>/sendas Chloe\nHello, guys!</code></pre>will send "Hello, guys!" from "Chloe".<br>Uses character avatar if it exists in the characters list.`, true, true);
|
||||
parser.addCommand('sys', sendNarratorMessage, [], '<span class="monospace">(text)</span> – sends message as a system narrator', false, true);
|
||||
parser.addCommand('sysname', setNarratorName, [], '<span class="monospace">(name)</span> – sets a name for future system narrator messages in this chat (display only). Default: System. Leave empty to reset.', true, true);
|
||||
parser.addCommand('comment', sendCommentMessage, [], '<span class="monospace">(text)</span> – adds a note/comment message not part of the chat', false, true);
|
||||
parser.addCommand('single', setStoryModeCallback, ['story'], ' – sets the message style to single document mode without names or avatars visible', true, true);
|
||||
parser.addCommand('bubble', setBubbleModeCallback, ['bubbles'], ' – sets the message style to bubble chat mode', true, true);
|
||||
parser.addCommand('flat', setFlatModeCallback, ['default'], ' – sets the message style to flat chat mode', true, true);
|
||||
parser.addCommand('continue', continueChatCallback, ['cont'], ' – continues the last message in the chat', true, true);
|
||||
|
||||
const NARRATOR_NAME_KEY = 'narrator_name';
|
||||
const NARRATOR_NAME_DEFAULT = 'System';
|
||||
const COMMENT_NAME_DEFAULT = 'Note';
|
||||
|
||||
function continueChatCallback() {
|
||||
// Prevent infinite recursion
|
||||
$('#send_textarea').val('');
|
||||
$('#option_continue').trigger('click', { fromSlashCommand: true });
|
||||
}
|
||||
|
||||
function syncCallback() {
|
||||
$('#sync_name_button').trigger('click');
|
||||
@@ -115,21 +130,36 @@ function bindCallback() {
|
||||
$('#lock_user_name').trigger('click');
|
||||
}
|
||||
|
||||
function setStoryModeCallback() {
|
||||
$('#chat_display').val(chat_styles.DOCUMENT).trigger('change');
|
||||
}
|
||||
|
||||
function setBubbleModeCallback() {
|
||||
$('#chat_display').val(chat_styles.BUBBLES).trigger('change');
|
||||
}
|
||||
|
||||
function setFlatModeCallback() {
|
||||
$('#chat_display').val(chat_styles.DEFAULT).trigger('change');
|
||||
}
|
||||
|
||||
function setNameCallback(_, name) {
|
||||
if (!name) {
|
||||
toastr.warning('you must specify a name to change to')
|
||||
return;
|
||||
}
|
||||
|
||||
name = name.trim();
|
||||
|
||||
// If the name is a persona, auto-select it
|
||||
if (Object.values(power_user.personas).map(x => x.toLowerCase()).includes(name.toLowerCase())) {
|
||||
autoSelectPersona(name);
|
||||
for (let persona of Object.values(power_user.personas)) {
|
||||
if (persona.toLowerCase() === name.toLowerCase()) {
|
||||
autoSelectPersona(name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, set just the name
|
||||
else {
|
||||
setUserName(name);
|
||||
}
|
||||
setUserName(name); //this prevented quickReply usage
|
||||
}
|
||||
|
||||
function setNarratorName(_, text) {
|
||||
@@ -221,6 +251,31 @@ async function sendNarratorMessage(_, text) {
|
||||
saveChatConditional();
|
||||
}
|
||||
|
||||
async function sendCommentMessage(_, text) {
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
|
||||
const message = {
|
||||
name: COMMENT_NAME_DEFAULT,
|
||||
is_user: false,
|
||||
is_name: true,
|
||||
is_system: true,
|
||||
send_date: humanizedDateTime(),
|
||||
mes: substituteParams(text.trim()),
|
||||
force_avatar: comment_avatar,
|
||||
extra: {
|
||||
type: system_message_types.COMMENT,
|
||||
gen_id: Date.now(),
|
||||
},
|
||||
};
|
||||
|
||||
chat.push(message);
|
||||
addOneMessage(message);
|
||||
await eventSource.emit(event_types.MESSAGE_SENT, (chat.length - 1));
|
||||
saveChatConditional();
|
||||
}
|
||||
|
||||
function helpCommandCallback() {
|
||||
sendSystemMessage(system_message_types.HELP);
|
||||
}
|
||||
@@ -244,7 +299,7 @@ function executeSlashCommands(text) {
|
||||
|
||||
// Hack to allow multi-line slash commands
|
||||
// All slash command messages should begin with a slash
|
||||
const lines = [text];
|
||||
const lines = text.split('|').map(line => line.trim());
|
||||
const linesToRemove = [];
|
||||
|
||||
let interrupt = false;
|
||||
|
@@ -5,6 +5,7 @@ import {
|
||||
callPopup,
|
||||
menu_type,
|
||||
updateVisibleDivs,
|
||||
getCharacters,
|
||||
} from "../script.js";
|
||||
|
||||
import { selected_group } from "./group-chats.js";
|
||||
@@ -13,23 +14,40 @@ export {
|
||||
tags,
|
||||
tag_map,
|
||||
loadTagsSettings,
|
||||
printTags,
|
||||
printTagFilters,
|
||||
isElementTagged,
|
||||
getTagsList,
|
||||
appendTagToList,
|
||||
createTagMapFromList,
|
||||
renameTagKey,
|
||||
importTags,
|
||||
};
|
||||
|
||||
const random_id = () => Math.round(Date.now() * Math.random()).toString();
|
||||
const TAG_LOGIC_AND = true; // switch to false to use OR logic for combining tags
|
||||
const CHARACTER_SELECTOR = '#rm_print_characters_block > div';
|
||||
const GROUP_MEMBER_SELECTOR = '#rm_group_add_members > div';
|
||||
const CHARACTER_FILTER_SELECTOR = '#rm_characters_block .rm_tag_filter';
|
||||
const GROUP_FILTER_SELECTOR = '#rm_group_chats_block .rm_tag_filter';
|
||||
|
||||
function getCharacterSelector(listSelector) {
|
||||
if ($(listSelector).is(GROUP_FILTER_SELECTOR)) {
|
||||
return GROUP_MEMBER_SELECTOR;
|
||||
}
|
||||
|
||||
return CHARACTER_SELECTOR;
|
||||
}
|
||||
|
||||
export const tag_filter_types = {
|
||||
character: 0,
|
||||
group_member: 1,
|
||||
};
|
||||
|
||||
const ACTIONABLE_TAGS = {
|
||||
|
||||
FAV: { id: 1, name: 'Show only favorites', color: 'rgba(255, 255, 0, 0.5)', action: applyFavFilter, icon: 'fa-solid fa-star' },
|
||||
GROUP: { id: 0, name: 'Show only groups', color: 'rgba(100, 100, 100, 0.5)', action: filterByGroups, icon: 'fa-solid fa-users' },
|
||||
HINT: { id: 3, name: 'Show Tag List', color: 'rgba(150, 100, 100, 0.5)', action: onTagListHintClick, icon: 'fa-solid fa-tags' },
|
||||
FAV: { id: 1, name: 'Show only favorites', color: 'rgba(255, 255, 0, 0.5)', action: applyFavFilter, icon: 'fa-solid fa-star', class: 'filterByFavorites' },
|
||||
GROUP: { id: 0, name: 'Show only groups', color: 'rgba(100, 100, 100, 0.5)', action: filterByGroups, icon: 'fa-solid fa-users', class: 'filterByGroups' },
|
||||
HINT: { id: 3, name: 'Show Tag List', color: 'rgba(150, 100, 100, 0.5)', action: onTagListHintClick, icon: 'fa-solid fa-tags', class: 'showTagList' },
|
||||
}
|
||||
|
||||
const InListActionable = {
|
||||
@@ -48,14 +66,14 @@ const DEFAULT_TAGS = [
|
||||
let tags = [];
|
||||
let tag_map = {};
|
||||
|
||||
function applyFavFilter() {
|
||||
function applyFavFilter(characterSelector) {
|
||||
const isSelected = $(this).hasClass('selected');
|
||||
const displayFavoritesOnly = !isSelected;
|
||||
|
||||
$(this).toggleClass('selected', displayFavoritesOnly);
|
||||
$(CHARACTER_SELECTOR).removeClass('hiddenByFav');
|
||||
$(characterSelector).removeClass('hiddenByFav');
|
||||
|
||||
$(CHARACTER_SELECTOR).each(function () {
|
||||
$(characterSelector).each(function () {
|
||||
if (displayFavoritesOnly) {
|
||||
if ($(this).find(".ch_fav").length !== 0) {
|
||||
const shouldBeDisplayed = $(this).find(".ch_fav").val().toLowerCase().includes(true);
|
||||
@@ -67,13 +85,13 @@ function applyFavFilter() {
|
||||
updateVisibleDivs('#rm_print_characters_block', true);
|
||||
}
|
||||
|
||||
function filterByGroups() {
|
||||
function filterByGroups(characterSelector) {
|
||||
const isSelected = $(this).hasClass('selected');
|
||||
const displayGroupsOnly = !isSelected;
|
||||
$(this).toggleClass('selected', displayGroupsOnly);
|
||||
$(CHARACTER_SELECTOR).removeClass('hiddenByGroup');
|
||||
$(characterSelector).removeClass('hiddenByGroup');
|
||||
|
||||
$(CHARACTER_SELECTOR).each((_, element) => {
|
||||
$(characterSelector).each((_, element) => {
|
||||
$(element).toggleClass('hiddenByGroup', displayGroupsOnly && !$(element).hasClass('group_select'));
|
||||
});
|
||||
updateVisibleDivs('#rm_print_characters_block', true);
|
||||
@@ -195,12 +213,63 @@ function selectTag(event, ui, listSelector) {
|
||||
appendTagToList(getInlineListSelector(), tag, { removable: false });
|
||||
addTagToMap(tag.id);
|
||||
saveSettingsDebounced();
|
||||
printTags();
|
||||
printTagFilters(tag_filter_types.character);
|
||||
printTagFilters(tag_filter_types.group_member);
|
||||
|
||||
// need to return false to keep the input clear
|
||||
return false;
|
||||
}
|
||||
|
||||
function getExistingTags(new_tags) {
|
||||
let existing_tags = [];
|
||||
for (let tag of new_tags) {
|
||||
let foundTag = tags.find(t => t.name.toLowerCase() === tag.toLowerCase())
|
||||
if (foundTag) {
|
||||
existing_tags.push(foundTag.name);
|
||||
}
|
||||
}
|
||||
return existing_tags
|
||||
}
|
||||
|
||||
|
||||
async function importTags(imported_char) {
|
||||
let imported_tags = imported_char.tags.filter(t => t !== "ROOT" && t !== "TAVERN");
|
||||
let existingTags = await getExistingTags(imported_tags);
|
||||
//make this case insensitive
|
||||
let newTags = imported_tags.filter(t => !existingTags.some(existingTag => existingTag.toLowerCase() === t.toLowerCase()));
|
||||
let selected_tags = "";
|
||||
const existingTagsString = existingTags.length ? (': ' + existingTags.join(', ')) : '';
|
||||
if (newTags.length === 0) {
|
||||
await callPopup(`<h3>Importing Tags</h3><p>${existingTags.length} existing tags have been found${existingTagsString}.</p>`, 'text');
|
||||
} else {
|
||||
selected_tags = await callPopup(`<h3>Importing Tags</h3><p>${existingTags.length} existing tags have been found${existingTagsString}.</p><p>The following ${newTags.length} new tags will be imported.</p>`, 'input', newTags.join(', '));
|
||||
}
|
||||
selected_tags = existingTags.concat(selected_tags.split(','));
|
||||
selected_tags = selected_tags.map(t => t.trim()).filter(t => t !== "");
|
||||
//Anti-troll measure
|
||||
if (selected_tags.length > 15) {
|
||||
selected_tags = selected_tags.slice(0, 15);
|
||||
}
|
||||
for (let tagName of selected_tags) {
|
||||
let tag = tags.find(t => t.name === tagName);
|
||||
|
||||
if (!tag) {
|
||||
tag = createNewTag(tagName);
|
||||
}
|
||||
|
||||
addTagToMap(tag.id);
|
||||
tag_map[imported_char.avatar].push(tag.id);
|
||||
};
|
||||
saveSettingsDebounced();
|
||||
await getCharacters();
|
||||
printTagFilters(tag_filter_types.character);
|
||||
printTagFilters(tag_filter_types.group_member);
|
||||
|
||||
// need to return false to keep the input clear
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
function createNewTag(tagName) {
|
||||
const tag = {
|
||||
id: random_id(),
|
||||
@@ -211,11 +280,13 @@ function createNewTag(tagName) {
|
||||
return tag;
|
||||
}
|
||||
|
||||
function appendTagToList(listElement, tag, { removable, selectable, action }) {
|
||||
function appendTagToList(listElement, tag, { removable, selectable, action, isGeneralList }) {
|
||||
if (!listElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
const characterSelector = getCharacterSelector($(listElement));
|
||||
|
||||
let tagElement = $('#tag_template .tag').clone();
|
||||
tagElement.attr('id', tag.id);
|
||||
|
||||
@@ -226,16 +297,24 @@ function appendTagToList(listElement, tag, { removable, selectable, action }) {
|
||||
const removeButton = tagElement.find(".tag_remove");
|
||||
removable ? removeButton.show() : removeButton.hide();
|
||||
|
||||
if (tag.class) {
|
||||
tagElement.addClass(tag.class);
|
||||
}
|
||||
|
||||
if (tag.icon) {
|
||||
tagElement.find('.tag_name').text('').attr('title', tag.name).addClass(tag.icon);
|
||||
}
|
||||
|
||||
if (tag.excluded) {
|
||||
isGeneralList ? $(tagElement).addClass('excluded') : $(listElement).parent().parent().addClass('hiddenByTag');
|
||||
}
|
||||
|
||||
if (selectable) {
|
||||
tagElement.on('click', () => onTagFilterClick.bind(tagElement)(listElement));
|
||||
tagElement.on('click', () => onTagFilterClick.bind(tagElement)(listElement, characterSelector));
|
||||
}
|
||||
|
||||
if (action) {
|
||||
tagElement.on('click', () => action.bind(tagElement)());
|
||||
tagElement.on('click', () => action.bind(tagElement)(characterSelector));
|
||||
tagElement.addClass('actionable');
|
||||
}
|
||||
if (action && tag.id === 2) {
|
||||
@@ -245,33 +324,59 @@ function appendTagToList(listElement, tag, { removable, selectable, action }) {
|
||||
$(listElement).append(tagElement);
|
||||
}
|
||||
|
||||
function onTagFilterClick(listElement) {
|
||||
const wasSelected = $(this).hasClass('selected');
|
||||
$(CHARACTER_SELECTOR).removeClass('hiddenByTag');
|
||||
function onTagFilterClick(listElement, characterSelector) {
|
||||
let excludeTag;
|
||||
if ($(this).hasClass('selected')) {
|
||||
$(this).removeClass('selected');
|
||||
$(this).addClass('excluded');
|
||||
excludeTag = true
|
||||
}
|
||||
else if ($(this).hasClass('excluded')) {
|
||||
$(this).removeClass('excluded');
|
||||
excludeTag = false;
|
||||
}
|
||||
else {
|
||||
$(this).addClass('selected');
|
||||
}
|
||||
|
||||
$(this).toggleClass('selected', !wasSelected);
|
||||
// Manual undefined check required for three-state boolean
|
||||
if (excludeTag !== undefined) {
|
||||
const tagId = $(this).attr('id');
|
||||
const existingTag = tags.find((tag) => tag.id === tagId);
|
||||
if (existingTag) {
|
||||
existingTag.excluded = excludeTag;
|
||||
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Overhaul this somehow to use settings tag IDs instead
|
||||
const tagIds = [...($(listElement).find(".tag.selected:not(.actionable)").map((_, el) => $(el).attr("id")))];
|
||||
$(CHARACTER_SELECTOR).each((_, element) => applyFilterToElement(tagIds, element));
|
||||
const excludedTagIds = [...($(listElement).find(".tag.excluded:not(.actionable)").map((_, el) => $(el).attr("id")))];
|
||||
$(characterSelector).each((_, element) => applyFilterToElement(tagIds, excludedTagIds, element));
|
||||
updateVisibleDivs('#rm_print_characters_block', true);
|
||||
}
|
||||
|
||||
function applyFilterToElement(tagIds, element) {
|
||||
if (tagIds.length === 0) {
|
||||
$(element).removeClass('hiddenByTag');
|
||||
return;
|
||||
}
|
||||
|
||||
function applyFilterToElement(tagIds, excludedTagIds, element) {
|
||||
const tagFlags = tagIds.map(tagId => isElementTagged(element, tagId));
|
||||
const trueFlags = tagFlags.filter(x => x);
|
||||
const isTagged = TAG_LOGIC_AND ? tagFlags.length === trueFlags.length : trueFlags.length > 0;
|
||||
|
||||
$(element).toggleClass('hiddenByTag', !isTagged);
|
||||
const excludedTagFlags = excludedTagIds.map(tagId => isElementTagged(element, tagId));
|
||||
const isExcluded = excludedTagFlags.includes(true);
|
||||
|
||||
if (isExcluded) {
|
||||
$(element).addClass('hiddenByTag');
|
||||
} else if (tagIds.length > 0 && !isTagged) {
|
||||
$(element).addClass('hiddenByTag');
|
||||
} else {
|
||||
$(element).removeClass('hiddenByTag');
|
||||
}
|
||||
}
|
||||
|
||||
function isElementTagged(element, tagId) {
|
||||
const isGroup = $(element).hasClass('group_select');
|
||||
const isCharacter = $(element).hasClass('character_select');
|
||||
const isCharacter = $(element).hasClass('character_select') || $(element).hasClass('group_member');
|
||||
const idAttr = isGroup ? 'grid' : 'chid';
|
||||
const elementId = $(element).attr(idAttr);
|
||||
const lookupValue = isCharacter ? characters[elementId].avatar : elementId;
|
||||
@@ -279,13 +384,13 @@ function isElementTagged(element, tagId) {
|
||||
return isTagged;
|
||||
}
|
||||
|
||||
function clearTagsFilter() {
|
||||
$('#rm_tag_filter .tag').removeClass('selected');
|
||||
$(CHARACTER_SELECTOR).removeClass('hiddenByTag');
|
||||
function clearTagsFilter(characterSelector) {
|
||||
$('.rm_tag_filter .tag').removeClass('selected');
|
||||
$(characterSelector).removeClass('hiddenByTag');
|
||||
}
|
||||
|
||||
function printTags() {
|
||||
const FILTER_SELECTOR = '#rm_tag_filter';
|
||||
function printTagFilters(type = tag_filter_types.character) {
|
||||
const FILTER_SELECTOR = type === tag_filter_types.character ? CHARACTER_FILTER_SELECTOR : GROUP_FILTER_SELECTOR;
|
||||
const selectedTagIds = [...($(FILTER_SELECTOR).find(".tag.selected").map((_, el) => $(el).attr("id")))];
|
||||
$(FILTER_SELECTOR).empty();
|
||||
const characterTagIds = Object.values(tag_map).flat();
|
||||
@@ -294,16 +399,16 @@ function printTags() {
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
for (const tag of Object.values(ACTIONABLE_TAGS)) {
|
||||
appendTagToList(FILTER_SELECTOR, tag, { removable: false, selectable: false, action: tag.action });
|
||||
appendTagToList(FILTER_SELECTOR, tag, { removable: false, selectable: false, action: tag.action, isGeneralList: true });
|
||||
}
|
||||
|
||||
$(FILTER_SELECTOR).find('.actionable').last().addClass('margin-right-10px');
|
||||
|
||||
for (const tag of Object.values(InListActionable)) {
|
||||
appendTagToList(FILTER_SELECTOR, tag, { removable: false, selectable: false, action: tag.action });
|
||||
appendTagToList(FILTER_SELECTOR, tag, { removable: false, selectable: false, action: tag.action, isGeneralList: true });
|
||||
}
|
||||
for (const tag of tagsToDisplay) {
|
||||
appendTagToList(FILTER_SELECTOR, tag, { removable: false, selectable: true, });
|
||||
appendTagToList(FILTER_SELECTOR, tag, { removable: false, selectable: true, isGeneralList: true });
|
||||
}
|
||||
|
||||
for (const tagId of selectedTagIds) {
|
||||
@@ -319,7 +424,8 @@ function onTagRemoveClick(event) {
|
||||
removeTagFromMap(tagId);
|
||||
$(`${getInlineListSelector()} .tag[id="${tagId}"]`).remove();
|
||||
|
||||
printTags();
|
||||
printTagFilters(tag_filter_types.character);
|
||||
printTagFilters(tag_filter_types.group_member);
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
@@ -339,6 +445,8 @@ function onCharacterCreateClick() {
|
||||
|
||||
function onGroupCreateClick() {
|
||||
$("#groupTagList").empty();
|
||||
printTagFilters(tag_filter_types.character);
|
||||
printTagFilters(tag_filter_types.group_member);
|
||||
}
|
||||
|
||||
export function applyTagsOnCharacterSelect() {
|
||||
@@ -360,6 +468,8 @@ function applyTagsOnGroupSelect() {
|
||||
const tags = getTagsList(key);
|
||||
|
||||
$("#groupTagList").empty();
|
||||
printTagFilters(tag_filter_types.character);
|
||||
printTagFilters(tag_filter_types.group_member);
|
||||
|
||||
for (const tag of tags) {
|
||||
appendTagToList("#groupTagList", tag, { removable: true });
|
||||
|
@@ -17,6 +17,8 @@ let textgenerationwebui_settings = {
|
||||
top_k: 40,
|
||||
top_a: 0,
|
||||
tfs: 1,
|
||||
epsilon_cutoff: 0,
|
||||
eta_cutoff: 0,
|
||||
typical_p: 1,
|
||||
rep_pen: 1.2,
|
||||
no_repeat_ngram_size: 0,
|
||||
@@ -49,6 +51,8 @@ const setting_names = [
|
||||
"top_p",
|
||||
"top_a",
|
||||
"tfs",
|
||||
"epsilon_cutoff",
|
||||
"eta_cutoff",
|
||||
"typical_p",
|
||||
"penalty_alpha",
|
||||
"num_beams",
|
||||
@@ -218,5 +222,7 @@ export function getTextGenGenerationData(finalPromt, this_amount_gen, isImperson
|
||||
'skip_special_tokens': textgenerationwebui_settings.skip_special_tokens,
|
||||
'top_a': textgenerationwebui_settings.top_a,
|
||||
'tfs': textgenerationwebui_settings.tfs,
|
||||
'epsilon_cutoff': textgenerationwebui_settings.epsilon_cutoff,
|
||||
'eta_cutoff': textgenerationwebui_settings.eta_cutoff,
|
||||
};
|
||||
}
|
||||
|
@@ -50,6 +50,19 @@ export function getFileText(file) {
|
||||
});
|
||||
}
|
||||
|
||||
export function getFileBuffer(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsArrayBuffer(file);
|
||||
reader.onload = function () {
|
||||
resolve(reader.result);
|
||||
};
|
||||
reader.onerror = function (error) {
|
||||
reject(error);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function getBase64Async(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
@@ -273,18 +286,39 @@ export function isOdd(number) {
|
||||
}
|
||||
|
||||
export function timestampToMoment(timestamp) {
|
||||
if (!timestamp) {
|
||||
return moment.invalid();
|
||||
}
|
||||
|
||||
// Unix time (legacy TAI)
|
||||
if (typeof timestamp === 'number') {
|
||||
return moment(timestamp);
|
||||
}
|
||||
|
||||
// ST "humanized" format pattern
|
||||
const pattern = /(\d{4})-(\d{1,2})-(\d{1,2}) @(\d{1,2})h (\d{1,2})m (\d{1,2})s (\d{1,3})ms/;
|
||||
const replacement = (match, year, month, day, hour, minute, second, millisecond) => {
|
||||
const pattern1 = /(\d{4})-(\d{1,2})-(\d{1,2}) @(\d{1,2})h (\d{1,2})m (\d{1,2})s (\d{1,3})ms/;
|
||||
const replacement1 = (match, year, month, day, hour, minute, second, millisecond) => {
|
||||
return `${year.padStart(4, "0")}-${month.padStart(2, "0")}-${day.padStart(2, "0")}T${hour.padStart(2, "0")}:${minute.padStart(2, "0")}:${second.padStart(2, "0")}.${millisecond.padStart(3, "0")}Z`;
|
||||
};
|
||||
const isoTimestamp = timestamp.replace(pattern, replacement);
|
||||
return moment(isoTimestamp);
|
||||
const isoTimestamp1 = timestamp.replace(pattern1, replacement1);
|
||||
if (moment(isoTimestamp1).isValid()) {
|
||||
return moment(isoTimestamp1);
|
||||
}
|
||||
|
||||
// New format pattern: "June 19, 2023 4:13pm"
|
||||
const pattern2 = /(\w+)\s(\d{1,2}),\s(\d{4})\s(\d{1,2}):(\d{1,2})(am|pm)/i;
|
||||
const replacement2 = (match, month, day, year, hour, minute, meridiem) => {
|
||||
const monthNum = moment().month(month).format("MM");
|
||||
const hour24 = meridiem.toLowerCase() === 'pm' ? (parseInt(hour, 10) % 12) + 12 : parseInt(hour, 10) % 12;
|
||||
return `${year}-${monthNum}-${day.padStart(2, "0")}T${hour24.toString().padStart(2, "0")}:${minute.padStart(2, "0")}:00`;
|
||||
};
|
||||
const isoTimestamp2 = timestamp.replace(pattern2, replacement2);
|
||||
if (moment(isoTimestamp2).isValid()) {
|
||||
return moment(isoTimestamp2);
|
||||
}
|
||||
|
||||
// If none of the patterns match, return an invalid moment object
|
||||
return moment.invalid();
|
||||
}
|
||||
|
||||
export function sortMoments(a, b) {
|
||||
@@ -423,9 +457,9 @@ export function isDataURL(str) {
|
||||
return regex.test(str);
|
||||
}
|
||||
|
||||
export function getCharaFilename() {
|
||||
export function getCharaFilename(chid) {
|
||||
const context = getContext();
|
||||
const fileName = context.characters[context.characterId].avatar;
|
||||
const fileName = context.characters[chid ?? context.characterId].avatar;
|
||||
|
||||
if (fileName) {
|
||||
return fileName.replace(/\.[^/.]+$/, "")
|
||||
@@ -435,3 +469,210 @@ export function getCharaFilename() {
|
||||
export function escapeRegex(string) {
|
||||
return string.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&');
|
||||
}
|
||||
|
||||
export class RateLimiter {
|
||||
constructor(intervalMillis) {
|
||||
this._intervalMillis = intervalMillis;
|
||||
this._lastResolveTime = 0;
|
||||
this._pendingResolve = Promise.resolve();
|
||||
}
|
||||
|
||||
_waitRemainingTime(abortSignal) {
|
||||
const currentTime = Date.now();
|
||||
const elapsedTime = currentTime - this._lastResolveTime;
|
||||
const remainingTime = Math.max(0, this._intervalMillis - elapsedTime);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
resolve();
|
||||
}, remainingTime);
|
||||
|
||||
if (abortSignal) {
|
||||
abortSignal.addEventListener('abort', () => {
|
||||
clearTimeout(timeoutId);
|
||||
reject(new Error('Aborted'));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async waitForResolve(abortSignal) {
|
||||
await this._pendingResolve;
|
||||
this._pendingResolve = this._waitRemainingTime(abortSignal);
|
||||
|
||||
// Update the last resolve time
|
||||
this._lastResolveTime = Date.now() + this._intervalMillis;
|
||||
console.debug(`RateLimiter.waitForResolve() ${this._lastResolveTime}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Taken from https://github.com/LostRuins/lite.koboldai.net/blob/main/index.html
|
||||
//import tavern png data. adapted from png-chunks-extract under MIT license
|
||||
//accepts png input data, and returns the extracted JSON
|
||||
export function extractDataFromPng(data, identifier = 'chara') {
|
||||
console.log("Attempting PNG import...");
|
||||
let uint8 = new Uint8Array(4);
|
||||
let uint32 = new Uint32Array(uint8.buffer);
|
||||
|
||||
//check if png header is valid
|
||||
if (!data || data[0] !== 0x89 || data[1] !== 0x50 || data[2] !== 0x4E || data[3] !== 0x47 || data[4] !== 0x0D || data[5] !== 0x0A || data[6] !== 0x1A || data[7] !== 0x0A) {
|
||||
console.log("PNG header invalid")
|
||||
return null;
|
||||
}
|
||||
|
||||
let ended = false;
|
||||
let chunks = [];
|
||||
let idx = 8;
|
||||
|
||||
while (idx < data.length) {
|
||||
// Read the length of the current chunk,
|
||||
// which is stored as a Uint32.
|
||||
uint8[3] = data[idx++];
|
||||
uint8[2] = data[idx++];
|
||||
uint8[1] = data[idx++];
|
||||
uint8[0] = data[idx++];
|
||||
|
||||
// Chunk includes name/type for CRC check (see below).
|
||||
let length = uint32[0] + 4;
|
||||
let chunk = new Uint8Array(length);
|
||||
chunk[0] = data[idx++];
|
||||
chunk[1] = data[idx++];
|
||||
chunk[2] = data[idx++];
|
||||
chunk[3] = data[idx++];
|
||||
|
||||
// Get the name in ASCII for identification.
|
||||
let name = (
|
||||
String.fromCharCode(chunk[0]) +
|
||||
String.fromCharCode(chunk[1]) +
|
||||
String.fromCharCode(chunk[2]) +
|
||||
String.fromCharCode(chunk[3])
|
||||
);
|
||||
|
||||
// The IHDR header MUST come first.
|
||||
if (!chunks.length && name !== 'IHDR') {
|
||||
console.log('Warning: IHDR header missing');
|
||||
}
|
||||
|
||||
// The IEND header marks the end of the file,
|
||||
// so on discovering it break out of the loop.
|
||||
if (name === 'IEND') {
|
||||
ended = true;
|
||||
chunks.push({
|
||||
name: name,
|
||||
data: new Uint8Array(0)
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
// Read the contents of the chunk out of the main buffer.
|
||||
for (let i = 4; i < length; i++) {
|
||||
chunk[i] = data[idx++];
|
||||
}
|
||||
|
||||
// Read out the CRC value for comparison.
|
||||
// It's stored as an Int32.
|
||||
uint8[3] = data[idx++];
|
||||
uint8[2] = data[idx++];
|
||||
uint8[1] = data[idx++];
|
||||
uint8[0] = data[idx++];
|
||||
|
||||
|
||||
// The chunk data is now copied to remove the 4 preceding
|
||||
// bytes used for the chunk name/type.
|
||||
let chunkData = new Uint8Array(chunk.buffer.slice(4));
|
||||
|
||||
chunks.push({
|
||||
name: name,
|
||||
data: chunkData
|
||||
});
|
||||
}
|
||||
|
||||
if (!ended) {
|
||||
console.log('.png file ended prematurely: no IEND header was found');
|
||||
}
|
||||
|
||||
//find the chunk with the chara name, just check first and last letter
|
||||
let found = chunks.filter(x => (
|
||||
x.name == "tEXt"
|
||||
&& x.data.length > identifier.length
|
||||
&& x.data.slice(0, identifier.length).every((v, i) => String.fromCharCode(v) == identifier[i])));
|
||||
|
||||
if (found.length == 0) {
|
||||
console.log('PNG Image contains no data');
|
||||
return null;
|
||||
} else {
|
||||
try {
|
||||
let b64buf = "";
|
||||
let bytes = found[0].data; //skip the chara
|
||||
for (let i = identifier.length + 1; i < bytes.length; i++) {
|
||||
b64buf += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
let decoded = JSON.parse(atob(b64buf));
|
||||
console.log(decoded);
|
||||
return decoded;
|
||||
} catch (e) {
|
||||
console.log("Error decoding b64 in image: " + e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function createThumbnail(dataUrl, maxWidth, maxHeight) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.src = dataUrl;
|
||||
img.onload = () => {
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// Calculate the thumbnail dimensions while maintaining the aspect ratio
|
||||
const aspectRatio = img.width / img.height;
|
||||
let thumbnailWidth = maxWidth;
|
||||
let thumbnailHeight = maxHeight;
|
||||
|
||||
if (img.width > img.height) {
|
||||
thumbnailHeight = maxWidth / aspectRatio;
|
||||
} else {
|
||||
thumbnailWidth = maxHeight * aspectRatio;
|
||||
}
|
||||
|
||||
// Set the canvas dimensions and draw the resized image
|
||||
canvas.width = thumbnailWidth;
|
||||
canvas.height = thumbnailHeight;
|
||||
ctx.drawImage(img, 0, 0, thumbnailWidth, thumbnailHeight);
|
||||
|
||||
// Convert the canvas to a data URL and resolve the promise
|
||||
const thumbnailDataUrl = canvas.toDataURL('image/jpeg');
|
||||
resolve(thumbnailDataUrl);
|
||||
};
|
||||
|
||||
img.onerror = () => {
|
||||
reject(new Error('Failed to load the image.'));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export async function waitUntilCondition(condition, timeout = 1000, interval = 100) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
clearInterval(intervalId);
|
||||
reject(new Error('Timed out waiting for condition to be true'));
|
||||
}, timeout);
|
||||
|
||||
const intervalId = setInterval(() => {
|
||||
if (condition()) {
|
||||
clearTimeout(timeoutId);
|
||||
clearInterval(intervalId);
|
||||
resolve();
|
||||
}
|
||||
}, interval);
|
||||
});
|
||||
}
|
||||
|
||||
export function uuidv4() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
const r = Math.random() * 16 | 0;
|
||||
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
627
public/style.css
627
public/style.css
File diff suppressed because it is too large
Load Diff
21
public/themes/Default (Dark) 1.7.1.json
Normal file
21
public/themes/Default (Dark) 1.7.1.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "Default (Dark) 1.7.1",
|
||||
"blur_strength": 10,
|
||||
"main_text_color": "rgba(220, 220, 210, 1)",
|
||||
"italics_text_color": "rgba(145, 145, 145, 1)",
|
||||
"quote_text_color": "rgba(225, 138, 36, 1)",
|
||||
"blur_tint_color": "rgba(23, 23, 23, 1)",
|
||||
"user_mes_blur_tint_color": "rgba(0, 0, 0, 0.9)",
|
||||
"bot_mes_blur_tint_color": "rgba(0, 0, 0, 0.9)",
|
||||
"shadow_color": "rgba(0, 0, 0, 1)",
|
||||
"shadow_width": 2,
|
||||
"font_scale": 1,
|
||||
"fast_ui_mode": true,
|
||||
"waifuMode": false,
|
||||
"avatar_style": 0,
|
||||
"chat_display": 0,
|
||||
"noShadows": true,
|
||||
"sheld_width": 0,
|
||||
"timer_enabled": false,
|
||||
"hotswap_enabled": true
|
||||
}
|
21
public/themes/Ross v2.json
Normal file
21
public/themes/Ross v2.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "Ross v2",
|
||||
"blur_strength": 10,
|
||||
"main_text_color": "rgba(230, 230, 220, 1)",
|
||||
"italics_text_color": "rgba(145, 145, 145, 1)",
|
||||
"quote_text_color": "rgba(73, 179, 255, 0.91)",
|
||||
"blur_tint_color": "rgba(0, 0, 0, 0.5)",
|
||||
"user_mes_blur_tint_color": "rgba(51, 51, 51, 0.2)",
|
||||
"bot_mes_blur_tint_color": "rgba(97, 97, 97, 0.43)",
|
||||
"shadow_color": "rgba(0, 0, 0, 0.5)",
|
||||
"shadow_width": 2,
|
||||
"font_scale": 0.95,
|
||||
"fast_ui_mode": false,
|
||||
"waifuMode": false,
|
||||
"avatar_style": 1,
|
||||
"chat_display": 1,
|
||||
"noShadows": false,
|
||||
"sheld_width": 1,
|
||||
"timer_enabled": true,
|
||||
"hotswap_enabled": true
|
||||
}
|
543
server.js
543
server.js
@@ -128,10 +128,13 @@ let response_getstatus;
|
||||
const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
|
||||
|
||||
const { SentencePieceProcessor, cleanText } = require("sentencepiece-js");
|
||||
const { Tokenizer } = require('@mlc-ai/web-tokenizers');
|
||||
const CHARS_PER_TOKEN = 3.35;
|
||||
|
||||
let spp_llama;
|
||||
let spp_nerd;
|
||||
let spp_nerd_v2;
|
||||
let claude_tokenizer;
|
||||
|
||||
async function loadSentencepieceTokenizer(modelPath) {
|
||||
try {
|
||||
@@ -147,7 +150,7 @@ async function loadSentencepieceTokenizer(modelPath) {
|
||||
async function countSentencepieceTokens(spp, text) {
|
||||
// Fallback to strlen estimation
|
||||
if (!spp) {
|
||||
return Math.ceil(text.length / 3.35);
|
||||
return Math.ceil(text.length / CHARS_PER_TOKEN);
|
||||
}
|
||||
|
||||
let cleaned = cleanText(text);
|
||||
@@ -156,9 +159,36 @@ async function countSentencepieceTokens(spp, text) {
|
||||
return ids.length;
|
||||
}
|
||||
|
||||
async function loadClaudeTokenizer(modelPath) {
|
||||
try {
|
||||
const arrayBuffer = fs.readFileSync(modelPath).buffer;
|
||||
const instance = await Tokenizer.fromJSON(arrayBuffer);
|
||||
return instance;
|
||||
} catch (error) {
|
||||
console.error("Claude tokenizer failed to load: " + modelPath, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function countClaudeTokens(tokenizer, messages) {
|
||||
const convertedPrompt = convertClaudePrompt(messages, false, false);
|
||||
|
||||
// Fallback to strlen estimation
|
||||
if (!tokenizer) {
|
||||
return Math.ceil(convertedPrompt.length / CHARS_PER_TOKEN);
|
||||
}
|
||||
|
||||
const count = tokenizer.encode(convertedPrompt).length;
|
||||
return count;
|
||||
}
|
||||
|
||||
const tokenizersCache = {};
|
||||
|
||||
function getTokenizerModel(requestModel) {
|
||||
if (requestModel.includes('claude')) {
|
||||
return 'claude';
|
||||
}
|
||||
|
||||
if (requestModel.includes('gpt-4-32k')) {
|
||||
return 'gpt-4-32k';
|
||||
}
|
||||
@@ -226,6 +256,7 @@ const directories = {
|
||||
extensions: 'public/scripts/extensions',
|
||||
instruct: 'public/instruct',
|
||||
context: 'public/context',
|
||||
backups: 'backups/',
|
||||
};
|
||||
|
||||
// CSRF Protection //
|
||||
@@ -329,7 +360,7 @@ app.use('/characters', (req, res) => {
|
||||
res.send(data);
|
||||
});
|
||||
});
|
||||
app.use(multer({ dest: "uploads" }).single("avatar"));
|
||||
app.use(multer({ dest: "uploads", limits: { fieldSize: 10 * 1024 * 1024 } }).single("avatar"));
|
||||
app.get("/", function (request, response) {
|
||||
response.sendFile(process.cwd() + "/public/index.html");
|
||||
});
|
||||
@@ -357,14 +388,17 @@ app.post("/generate", jsonParser, async function (request, response_generate = r
|
||||
request.socket.on('close', async function () {
|
||||
if (request.body.can_abort && !response_generate.finished) {
|
||||
try {
|
||||
console.log('Aborting Kobold generation...');
|
||||
// send abort signal to koboldcpp
|
||||
await fetch(`${api_server}/extra/abort`, {
|
||||
const abortResponse = await fetch(`${api_server}/extra/abort`, {
|
||||
method: 'POST',
|
||||
});
|
||||
} catch {
|
||||
if ('status' in error) {
|
||||
console.log('Status Code from Kobold:', error.status);
|
||||
|
||||
if (!abortResponse.ok) {
|
||||
console.log('Error sending abort request to Kobold:', abortResponse.status);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
controller.abort();
|
||||
@@ -575,7 +609,7 @@ app.post("/savechat", jsonParser, function (request, response) {
|
||||
var dir_name = String(request.body.avatar_url).replace('.png', '');
|
||||
let chat_data = request.body.chat;
|
||||
let jsonlData = chat_data.map(JSON.stringify).join('\n');
|
||||
fs.writeFileSync(`${chatsPath + dir_name}/${sanitize(String(request.body.file_name))}.jsonl`, jsonlData, 'utf8');
|
||||
fs.writeFileSync(`${chatsPath + sanitize(dir_name)}/${sanitize(String(request.body.file_name))}.jsonl`, jsonlData, 'utf8');
|
||||
return response.send({ result: "ok" });
|
||||
} catch (error) {
|
||||
response.send(error);
|
||||
@@ -718,6 +752,8 @@ function convertToV2(char) {
|
||||
creator_notes: char.creatorcomment,
|
||||
talkativeness: char.talkativeness,
|
||||
fav: char.fav,
|
||||
creator: char.creator,
|
||||
tags: char.tags,
|
||||
});
|
||||
|
||||
result.chat = char.chat;
|
||||
@@ -725,6 +761,12 @@ function convertToV2(char) {
|
||||
return result;
|
||||
}
|
||||
|
||||
function unsetFavFlag(char) {
|
||||
const _ = require('lodash');
|
||||
_.set(char, 'fav', false);
|
||||
_.set(char, 'data.extensions.fav', false);
|
||||
}
|
||||
|
||||
function readFromV2(char) {
|
||||
const _ = require('lodash');
|
||||
if (_.isUndefined(char.data)) {
|
||||
@@ -741,6 +783,7 @@ function readFromV2(char) {
|
||||
mes_example: 'mes_example',
|
||||
talkativeness: 'extensions.talkativeness',
|
||||
fav: 'extensions.fav',
|
||||
tags: 'tags',
|
||||
};
|
||||
|
||||
_.forEach(fieldMappings, (v2Path, charField) => {
|
||||
@@ -766,7 +809,7 @@ function readFromV2(char) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!_.isUndefined(char[charField]) && !_.isUndefined(v2Value) && char[charField] !== v2Value) {
|
||||
if (!_.isUndefined(char[charField]) && !_.isUndefined(v2Value) && String(char[charField]) !== String(v2Value)) {
|
||||
console.debug(`Spec v2 data mismatch with Spec v1 for field: ${charField}`, char[charField], v2Value);
|
||||
}
|
||||
char[charField] = v2Value;
|
||||
@@ -819,7 +862,7 @@ function charaFormatData(data) {
|
||||
_.set(char, 'data.creator_notes', data.creator_notes || '');
|
||||
_.set(char, 'data.system_prompt', data.system_prompt || '');
|
||||
_.set(char, 'data.post_history_instructions', data.post_history_instructions || '');
|
||||
_.set(char, 'data.tags', typeof data.tags == 'string' ? (data.tags.split(',').map(x => x.trim()).filter(x => x)) : []);
|
||||
_.set(char, 'data.tags', typeof data.tags == 'string' ? (data.tags.split(',').map(x => x.trim()).filter(x => x)) : data.tags || []);
|
||||
_.set(char, 'data.creator', data.creator || '');
|
||||
_.set(char, 'data.character_version', data.character_version || '');
|
||||
_.set(char, 'data.alternate_greetings', getAlternateGreetings(data));
|
||||
@@ -827,12 +870,29 @@ function charaFormatData(data) {
|
||||
// ST extension fields to V2 object
|
||||
_.set(char, 'data.extensions.talkativeness', data.talkativeness);
|
||||
_.set(char, 'data.extensions.fav', data.fav == 'true');
|
||||
_.set(char, 'data.extensions.world', data.world || '');
|
||||
//_.set(char, 'data.extensions.create_date', humanizedISO8601DateTime());
|
||||
//_.set(char, 'data.extensions.avatar', 'none');
|
||||
//_.set(char, 'data.extensions.chat', data.ch_name + ' - ' + humanizedISO8601DateTime());
|
||||
|
||||
// TODO: Character book
|
||||
_//.set(char, 'data.character_book', undefined);
|
||||
if (data.world) {
|
||||
try {
|
||||
const file = readWorldInfoFile(data.world);
|
||||
|
||||
// File was imported - save it to the character book
|
||||
if (file && file.originalData) {
|
||||
_.set(char, 'data.character_book', file.originalData);
|
||||
}
|
||||
|
||||
// File was not imported - convert the world info to the character book
|
||||
if (file && file.entries) {
|
||||
_.set(char, 'data.character_book', convertWorldInfoToCharacterBook(data.world, file.entries));
|
||||
}
|
||||
|
||||
} catch {
|
||||
console.debug(`Failed to read world info file: ${data.world}. Character book will not be available.`);
|
||||
}
|
||||
}
|
||||
|
||||
return char;
|
||||
}
|
||||
@@ -1029,13 +1089,19 @@ async function charaWrite(img_url, data, target_img, response = undefined, mes =
|
||||
async function tryReadImage(img_url, crop) {
|
||||
try {
|
||||
let rawImg = await jimp.read(img_url);
|
||||
let final_width = rawImg.bitmap.width, final_height = rawImg.bitmap.height
|
||||
|
||||
// Apply crop if defined
|
||||
if (typeof crop == 'object' && [crop.x, crop.y, crop.width, crop.height].every(x => typeof x === 'number')) {
|
||||
rawImg = rawImg.crop(crop.x, crop.y, crop.width, crop.height);
|
||||
// Apply standard resize if requested
|
||||
if (crop.want_resize) {
|
||||
final_width = AVATAR_WIDTH
|
||||
final_height = AVATAR_HEIGHT
|
||||
}
|
||||
}
|
||||
|
||||
const image = await rawImg.cover(AVATAR_WIDTH, AVATAR_HEIGHT).getBufferAsync(jimp.MIME_PNG);
|
||||
const image = await rawImg.cover(final_width, final_height).getBufferAsync(jimp.MIME_PNG);
|
||||
return image;
|
||||
}
|
||||
// If it's an unsupported type of image (APNG) - just read the file as buffer
|
||||
@@ -1195,9 +1261,12 @@ app.post("/delchat", jsonParser, function (request, response) {
|
||||
return response.sendStatus(403);
|
||||
}
|
||||
|
||||
const fileName = path.join(directories.chats, '/', sanitize(request.body.id), '/', sanitize(request.body.chatfile));
|
||||
if (!fs.existsSync(fileName)) {
|
||||
console.log('Chat file not found');
|
||||
const dirName = String(request.body.avatar_url).replace('.png', '');
|
||||
const fileName = `${chatsPath + dirName}/${sanitize(String(request.body.chatfile))}`;
|
||||
const chatFileExists = fs.existsSync(fileName);
|
||||
|
||||
if (!chatFileExists) {
|
||||
console.log(`Chat file not found '${fileName}'`);
|
||||
return response.sendStatus(400);
|
||||
} else {
|
||||
console.log('found the chat file: ' + fileName);
|
||||
@@ -1421,11 +1490,43 @@ app.post('/savetheme', jsonParser, (request, response) => {
|
||||
}
|
||||
|
||||
const filename = path.join(directories.themes, sanitize(request.body.name) + '.json');
|
||||
fs.writeFileSync(filename, JSON.stringify(request.body), 'utf8');
|
||||
fs.writeFileSync(filename, JSON.stringify(request.body, null, 4), 'utf8');
|
||||
|
||||
return response.sendStatus(200);
|
||||
});
|
||||
|
||||
function convertWorldInfoToCharacterBook(name, entries) {
|
||||
const result = { entries: [], name };
|
||||
|
||||
for (const index in entries) {
|
||||
const entry = entries[index];
|
||||
|
||||
const originalEntry = {
|
||||
id: entry.uid,
|
||||
keys: entry.key,
|
||||
secondary_keys: entry.keysecondary,
|
||||
comment: entry.comment,
|
||||
content: entry.content,
|
||||
constant: entry.constant,
|
||||
selective: entry.selective,
|
||||
insertion_order: entry.order,
|
||||
enabled: !entry.disable,
|
||||
position: entry.position == 0 ? 'before_char' : 'after_char',
|
||||
extensions: {
|
||||
position: entry.position,
|
||||
exclude_recursion: entry.excludeRecursion,
|
||||
display_index: entry.displayIndex,
|
||||
probability: entry.probability ?? null,
|
||||
useProbability: entry.useProbability ?? false,
|
||||
}
|
||||
};
|
||||
|
||||
result.entries.push(originalEntry);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function readWorldInfoFile(worldInfoName) {
|
||||
if (!worldInfoName) {
|
||||
return { entries: {} };
|
||||
@@ -1680,6 +1781,8 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
|
||||
|
||||
if (jsonData.spec !== undefined) {
|
||||
console.log('importing from v2 json');
|
||||
importRisuSprites(jsonData);
|
||||
unsetFavFlag(jsonData);
|
||||
jsonData = readFromV2(jsonData);
|
||||
png_name = getPngName(jsonData.data?.name || jsonData.name);
|
||||
let char = JSON.stringify(jsonData);
|
||||
@@ -1687,12 +1790,14 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
|
||||
} else if (jsonData.name !== undefined) {
|
||||
console.log('importing from v1 json');
|
||||
jsonData.name = sanitize(jsonData.name);
|
||||
|
||||
if (jsonData.creator_notes) {
|
||||
jsonData.creator_notes = jsonData.creator_notes.replace("Creator's notes go here.", "");
|
||||
}
|
||||
png_name = getPngName(jsonData.name);
|
||||
let char = {
|
||||
"name": jsonData.name,
|
||||
"description": jsonData.description ?? '',
|
||||
"creatorcomment": jsonData.creatorcomment ?? '',
|
||||
"creatorcomment": jsonData.creatorcomment ?? jsonData.creator_notes ?? '',
|
||||
"personality": jsonData.personality ?? '',
|
||||
"first_mes": jsonData.first_mes ?? '',
|
||||
"avatar": 'none',
|
||||
@@ -1700,7 +1805,9 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
|
||||
"mes_example": jsonData.mes_example ?? '',
|
||||
"scenario": jsonData.scenario ?? '',
|
||||
"create_date": humanizedISO8601DateTime(),
|
||||
"talkativeness": jsonData.talkativeness ?? 0.5
|
||||
"talkativeness": jsonData.talkativeness ?? 0.5,
|
||||
"creator": jsonData.creator ?? '',
|
||||
"tags": jsonData.tags ?? '',
|
||||
};
|
||||
char = convertToV2(char);
|
||||
char = JSON.stringify(char);
|
||||
@@ -1708,12 +1815,14 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
|
||||
} else if (jsonData.char_name !== undefined) {//json Pygmalion notepad
|
||||
console.log('importing from gradio json');
|
||||
jsonData.char_name = sanitize(jsonData.char_name);
|
||||
|
||||
if (jsonData.creator_notes) {
|
||||
jsonData.creator_notes = jsonData.creator_notes.replace("Creator's notes go here.", "");
|
||||
}
|
||||
png_name = getPngName(jsonData.char_name);
|
||||
let char = {
|
||||
"name": jsonData.char_name,
|
||||
"description": jsonData.char_persona ?? '',
|
||||
"creatorcomment": '',
|
||||
"creatorcomment": jsonData.creatorcomment ?? jsonData.creator_notes ?? '',
|
||||
"personality": '',
|
||||
"first_mes": jsonData.char_greeting ?? '',
|
||||
"avatar": 'none',
|
||||
@@ -1721,7 +1830,9 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
|
||||
"mes_example": jsonData.example_dialogue ?? '',
|
||||
"scenario": jsonData.world_scenario ?? '',
|
||||
"create_date": humanizedISO8601DateTime(),
|
||||
"talkativeness": jsonData.talkativeness ?? 0.5
|
||||
"talkativeness": jsonData.talkativeness ?? 0.5,
|
||||
"creator": jsonData.creator ?? '',
|
||||
"tags": jsonData.tags ?? '',
|
||||
};
|
||||
char = convertToV2(char);
|
||||
char = JSON.stringify(char);
|
||||
@@ -1753,15 +1864,22 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
|
||||
|
||||
if (jsonData.spec !== undefined) {
|
||||
console.log('Found a v2 character file.');
|
||||
importRisuSprites(jsonData);
|
||||
unsetFavFlag(jsonData);
|
||||
jsonData = readFromV2(jsonData);
|
||||
let char = JSON.stringify(jsonData);
|
||||
charaWrite(uploadPath, char, png_name, response, { file_name: png_name });
|
||||
} else if (jsonData.name !== undefined) {
|
||||
console.log('Found a v1 character file.');
|
||||
|
||||
if (jsonData.creator_notes) {
|
||||
jsonData.creator_notes = jsonData.creator_notes.replace("Creator's notes go here.", "");
|
||||
}
|
||||
|
||||
let char = {
|
||||
"name": jsonData.name,
|
||||
"description": jsonData.description ?? '',
|
||||
"creatorcomment": jsonData.creatorcomment ?? '',
|
||||
"creatorcomment": jsonData.creatorcomment ?? jsonData.creator_notes ?? '',
|
||||
"personality": jsonData.personality ?? '',
|
||||
"first_mes": jsonData.first_mes ?? '',
|
||||
"avatar": 'none',
|
||||
@@ -1769,7 +1887,9 @@ app.post("/importcharacter", urlencodedParser, async function (request, response
|
||||
"mes_example": jsonData.mes_example ?? '',
|
||||
"scenario": jsonData.scenario ?? '',
|
||||
"create_date": humanizedISO8601DateTime(),
|
||||
"talkativeness": jsonData.talkativeness ?? 0.5
|
||||
"talkativeness": jsonData.talkativeness ?? 0.5,
|
||||
"creator": jsonData.creator ?? '',
|
||||
"tags": jsonData.tags ?? '',
|
||||
};
|
||||
char = convertToV2(char);
|
||||
char = JSON.stringify(char);
|
||||
@@ -1836,8 +1956,29 @@ app.post("/exportchat", jsonParser, async function (request, response) {
|
||||
return response.status(404).json(errorMessage);
|
||||
}
|
||||
try {
|
||||
// Short path for JSONL files
|
||||
if (request.body.format == 'jsonl') {
|
||||
try {
|
||||
const rawFile = fs.readFileSync(filename, 'utf8');
|
||||
const successMessage = {
|
||||
message: `Chat saved to ${exportfilename}`,
|
||||
result: rawFile,
|
||||
}
|
||||
|
||||
console.log(`Chat exported as ${exportfilename}`);
|
||||
return response.status(200).json(successMessage);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
const errorMessage = {
|
||||
message: `Could not read JSONL file to export. Source chat file: ${filename}.`
|
||||
}
|
||||
console.log(errorMessage.message);
|
||||
return response.status(500).json(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
const readline = require('readline');
|
||||
const fs = require('fs');
|
||||
const readStream = fs.createReadStream(filename);
|
||||
const rl = readline.createInterface({
|
||||
input: readStream,
|
||||
@@ -2073,15 +2214,17 @@ app.post("/importchat", urlencodedParser, function (request, response) {
|
||||
app.post('/importworldinfo', urlencodedParser, (request, response) => {
|
||||
if (!request.file) return response.sendStatus(400);
|
||||
|
||||
const filename = sanitize(request.file.originalname);
|
||||
const filename = `${path.parse(sanitize(request.file.originalname)).name}.json`;
|
||||
|
||||
if (path.parse(filename).ext.toLowerCase() !== '.json') {
|
||||
return response.status(400).send('Only JSON files are supported.')
|
||||
let fileContents = null;
|
||||
|
||||
if (request.body.convertedData) {
|
||||
fileContents = request.body.convertedData;
|
||||
} else {
|
||||
const pathToUpload = path.join('./uploads/', request.file.filename);
|
||||
fileContents = fs.readFileSync(pathToUpload, 'utf8');
|
||||
}
|
||||
|
||||
const pathToUpload = path.join('./uploads/', request.file.filename);
|
||||
const fileContents = fs.readFileSync(pathToUpload, 'utf8');
|
||||
|
||||
try {
|
||||
const worldContent = json5.parse(fileContents);
|
||||
if (!('entries' in worldContent)) {
|
||||
@@ -2122,7 +2265,7 @@ app.post('/editworldinfo', jsonParser, (request, response) => {
|
||||
const filename = `${request.body.name}.json`;
|
||||
const pathToFile = path.join(directories.worlds, filename);
|
||||
|
||||
fs.writeFileSync(pathToFile, JSON.stringify(request.body.data));
|
||||
fs.writeFileSync(pathToFile, JSON.stringify(request.body.data, null, 4));
|
||||
|
||||
return response.send({ ok: true });
|
||||
});
|
||||
@@ -2141,7 +2284,7 @@ app.post('/uploaduseravatar', urlencodedParser, async (request, response) => {
|
||||
|
||||
const image = await rawImg.cover(AVATAR_WIDTH, AVATAR_HEIGHT).getBufferAsync(jimp.MIME_PNG);
|
||||
|
||||
const filename = `${Date.now()}.png`;
|
||||
const filename = request.body.overwrite_name || `${Date.now()}.png`;
|
||||
const pathToNewFile = path.join(directories.avatars, filename);
|
||||
fs.writeFileSync(pathToNewFile, image);
|
||||
fs.rmSync(pathToUpload);
|
||||
@@ -2748,15 +2891,30 @@ app.get('/thumbnail', jsonParser, async function (request, response) {
|
||||
app.post("/getstatus_openai", jsonParser, function (request, response_getstatus_openai = response) {
|
||||
if (!request.body) return response_getstatus_openai.sendStatus(400);
|
||||
|
||||
const api_key_openai = readSecret(SECRET_KEYS.OPENAI);
|
||||
let api_url;
|
||||
let api_key_openai;
|
||||
let headers;
|
||||
|
||||
if (!api_key_openai) {
|
||||
return response_getstatus_openai.sendStatus(401);
|
||||
if (request.body.use_openrouter == false) {
|
||||
api_url = new URL(request.body.reverse_proxy || api_openai).toString();
|
||||
api_key_openai = readSecret(SECRET_KEYS.OPENAI);
|
||||
headers = {};
|
||||
} else {
|
||||
api_url = 'https://openrouter.ai/api/v1';
|
||||
api_key_openai = readSecret(SECRET_KEYS.OPENROUTER);
|
||||
// OpenRouter needs to pass the referer: https://openrouter.ai/docs
|
||||
headers = { 'HTTP-Referer': request.headers.referer };
|
||||
}
|
||||
|
||||
if (!api_key_openai) {
|
||||
return response_getstatus_openai.status(401).send({ error: true });
|
||||
}
|
||||
|
||||
const api_url = new URL(request.body.reverse_proxy || api_openai).toString();
|
||||
const args = {
|
||||
headers: { "Authorization": "Bearer " + api_key_openai }
|
||||
headers: {
|
||||
"Authorization": "Bearer " + api_key_openai,
|
||||
...headers,
|
||||
},
|
||||
};
|
||||
client.get(api_url + "/models", args, function (data, response) {
|
||||
if (response.statusCode == 200) {
|
||||
@@ -2788,6 +2946,12 @@ app.post("/openai_bias", jsonParser, async function (request, response) {
|
||||
let result = {};
|
||||
|
||||
const model = getTokenizerModel(String(request.query.model || ''));
|
||||
|
||||
// no bias for claude
|
||||
if (model == 'claude') {
|
||||
return response.send(result);
|
||||
}
|
||||
|
||||
const tokenizer = getTiktokenTokenizer(model);
|
||||
|
||||
for (const entry of request.body) {
|
||||
@@ -2860,7 +3024,7 @@ app.post("/deletepreset_openai", jsonParser, function (request, response) {
|
||||
});
|
||||
|
||||
// Prompt Conversion script taken from RisuAI by @kwaroran (GPLv3).
|
||||
function convertClaudePrompt(messages) {
|
||||
function convertClaudePrompt(messages, addHumanPrefix, addAssistantPostfix) {
|
||||
// Claude doesn't support message names, so we'll just add them to the message content.
|
||||
for (const message of messages) {
|
||||
if (message.name && message.role !== "system") {
|
||||
@@ -2890,7 +3054,16 @@ function convertClaudePrompt(messages) {
|
||||
break
|
||||
}
|
||||
return prefix + v.content;
|
||||
}).join('') + '\n\nAssistant: ';
|
||||
}).join('');
|
||||
|
||||
if (addHumanPrefix) {
|
||||
requestPrompt = "\n\nHuman: " + requestPrompt;
|
||||
}
|
||||
|
||||
if (addAssistantPostfix) {
|
||||
requestPrompt = requestPrompt + '\n\nAssistant: ';
|
||||
}
|
||||
|
||||
return requestPrompt;
|
||||
}
|
||||
|
||||
@@ -2911,14 +3084,14 @@ async function sendClaudeRequest(request, response) {
|
||||
controller.abort();
|
||||
});
|
||||
|
||||
const requestPrompt = convertClaudePrompt(request.body.messages);
|
||||
const requestPrompt = convertClaudePrompt(request.body.messages, true, true);
|
||||
console.log('Claude request:', requestPrompt);
|
||||
|
||||
const generateResponse = await fetch(api_url + '/complete', {
|
||||
method: "POST",
|
||||
signal: controller.signal,
|
||||
body: JSON.stringify({
|
||||
prompt: "\n\nHuman: " + requestPrompt,
|
||||
prompt: requestPrompt,
|
||||
model: request.body.model,
|
||||
max_tokens_to_sample: request.body.max_tokens,
|
||||
stop_sequences: ["\n\nHuman:", "\n\nSystem:", "\n\nAssistant:"],
|
||||
@@ -2976,9 +3149,20 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
|
||||
return sendClaudeRequest(request, response_generate_openai);
|
||||
}
|
||||
|
||||
const api_url = new URL(request.body.reverse_proxy || api_openai).toString();
|
||||
let api_url;
|
||||
let api_key_openai;
|
||||
let headers;
|
||||
|
||||
const api_key_openai = readSecret(SECRET_KEYS.OPENAI);
|
||||
if (request.body.use_openrouter == false) {
|
||||
api_url = new URL(request.body.reverse_proxy || api_openai).toString();
|
||||
api_key_openai = readSecret(SECRET_KEYS.OPENAI);
|
||||
headers = {};
|
||||
} else {
|
||||
api_url = 'https://openrouter.ai/api/v1';
|
||||
api_key_openai = readSecret(SECRET_KEYS.OPENROUTER);
|
||||
// OpenRouter needs to pass the referer: https://openrouter.ai/docs
|
||||
headers = { 'HTTP-Referer': request.headers.referer };
|
||||
}
|
||||
|
||||
if (!api_key_openai) {
|
||||
return response_generate_openai.status(401).send({ error: true });
|
||||
@@ -2996,7 +3180,8 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
|
||||
url: api_url + '/chat/completions',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer ' + api_key_openai
|
||||
'Authorization': 'Bearer ' + api_key_openai,
|
||||
...headers,
|
||||
},
|
||||
data: {
|
||||
"messages": request.body.messages,
|
||||
@@ -3084,15 +3269,20 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
|
||||
app.post("/tokenize_openai", jsonParser, function (request, response_tokenize_openai = response) {
|
||||
if (!request.body) return response_tokenize_openai.sendStatus(400);
|
||||
|
||||
let num_tokens = 0;
|
||||
const model = getTokenizerModel(String(request.query.model || ''));
|
||||
|
||||
if (model == 'claude') {
|
||||
num_tokens = countClaudeTokens(claude_tokenizer, request.body);
|
||||
return response_tokenize_openai.send({ "token_count": num_tokens });
|
||||
}
|
||||
|
||||
const tokensPerName = model.includes('gpt-4') ? 1 : -1;
|
||||
const tokensPerMessage = model.includes('gpt-4') ? 3 : 4;
|
||||
const tokensPadding = 3;
|
||||
|
||||
const tokenizer = getTiktokenTokenizer(model);
|
||||
|
||||
let num_tokens = 0;
|
||||
for (const msg of request.body) {
|
||||
num_tokens += tokensPerMessage;
|
||||
for (const [key, value] of Object.entries(msg)) {
|
||||
@@ -3118,7 +3308,7 @@ app.post("/savepreset_openai", jsonParser, function (request, response) {
|
||||
|
||||
const filename = `${name}.settings`;
|
||||
const fullpath = path.join(directories.openAI_Settings, filename);
|
||||
fs.writeFileSync(fullpath, JSON.stringify(request.body), 'utf-8');
|
||||
fs.writeFileSync(fullpath, JSON.stringify(request.body, null, 4), 'utf-8');
|
||||
return response.send({ name });
|
||||
});
|
||||
|
||||
@@ -3193,6 +3383,7 @@ const setupTasks = async function () {
|
||||
|
||||
console.log(`SillyTavern ${version.pkgVersion}` + (version.gitBranch ? ` '${version.gitBranch}' (${version.gitRevision})` : ''));
|
||||
|
||||
backupSettings();
|
||||
migrateSecrets();
|
||||
ensurePublicDirectoriesExist();
|
||||
await ensureThumbnailCache();
|
||||
@@ -3200,10 +3391,11 @@ const setupTasks = async function () {
|
||||
// Colab users could run the embedded tool
|
||||
if (!is_colab) await convertWebp();
|
||||
|
||||
[spp_llama, spp_nerd, spp_nerd_v2] = await Promise.all([
|
||||
[spp_llama, spp_nerd, spp_nerd_v2, claude_tokenizer] = await Promise.all([
|
||||
loadSentencepieceTokenizer('src/sentencepiece/tokenizer.model'),
|
||||
loadSentencepieceTokenizer('src/sentencepiece/nerdstash.model'),
|
||||
loadSentencepieceTokenizer('src/sentencepiece/nerdstash_v2.model'),
|
||||
loadClaudeTokenizer('src/claude.json'),
|
||||
]);
|
||||
|
||||
console.log('Launching...');
|
||||
@@ -3283,6 +3475,41 @@ async function convertWebp() {
|
||||
}
|
||||
}
|
||||
|
||||
function backupSettings() {
|
||||
const MAX_BACKUPS = 25;
|
||||
|
||||
function generateTimestamp() {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(now.getDate()).padStart(2, '0');
|
||||
const hours = String(now.getHours()).padStart(2, '0');
|
||||
const minutes = String(now.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(now.getSeconds()).padStart(2, '0');
|
||||
|
||||
return `${year}${month}${day}-${hours}${minutes}${seconds}`;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!fs.existsSync(directories.backups)) {
|
||||
fs.mkdirSync(directories.backups);
|
||||
}
|
||||
|
||||
const backupFile = path.join(directories.backups, `settings_${generateTimestamp()}.json`);
|
||||
fs.copyFileSync(SETTINGS_FILE, backupFile);
|
||||
|
||||
let files = fs.readdirSync(directories.backups);
|
||||
if (files.length > MAX_BACKUPS) {
|
||||
files = files.map(f => path.join(directories.backups, f));
|
||||
files.sort((a, b) => fs.statSync(a).mtimeMs - fs.statSync(b).mtimeMs);
|
||||
|
||||
fs.rmSync(files[0]);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Could not backup settings file', err);
|
||||
}
|
||||
}
|
||||
|
||||
function ensurePublicDirectoriesExist() {
|
||||
for (const dir of Object.values(directories)) {
|
||||
if (!fs.existsSync(dir)) {
|
||||
@@ -3299,6 +3526,8 @@ const SECRET_KEYS = {
|
||||
POE: 'api_key_poe',
|
||||
NOVEL: 'api_key_novel',
|
||||
CLAUDE: 'api_key_claude',
|
||||
DEEPL: 'deepl',
|
||||
OPENROUTER: 'api_key_openrouter',
|
||||
}
|
||||
|
||||
function migrateSecrets() {
|
||||
@@ -3549,6 +3778,53 @@ app.post('/google_translate', jsonParser, async (request, response) => {
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/deepl_translate', jsonParser, async (request, response) => {
|
||||
const key = readSecret(SECRET_KEYS.DEEPL);
|
||||
|
||||
if (!key) {
|
||||
return response.sendStatus(401);
|
||||
}
|
||||
|
||||
const text = request.body.text;
|
||||
const lang = request.body.lang;
|
||||
|
||||
if (!text || !lang) {
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
console.log('Input text: ' + text);
|
||||
|
||||
const fetch = require('node-fetch').default;
|
||||
const params = new URLSearchParams();
|
||||
params.append('text', text);
|
||||
params.append('target_lang', lang);
|
||||
|
||||
try {
|
||||
const result = await fetch('https://api-free.deepl.com/v2/translate', {
|
||||
method: 'POST',
|
||||
body: params,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Authorization': `DeepL-Auth-Key ${key}`,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
timeout: 0,
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
return response.sendStatus(result.status);
|
||||
}
|
||||
|
||||
const json = await result.json();
|
||||
console.log('Translated text: ' + json.translations[0].text);
|
||||
|
||||
return response.send(json.translations[0].text);
|
||||
} catch (error) {
|
||||
console.log("Translation error: " + error.message);
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/novel_tts', jsonParser, async (request, response) => {
|
||||
const token = readSecret(SECRET_KEYS.NOVEL);
|
||||
|
||||
@@ -3714,6 +3990,177 @@ app.post('/upload_sprite', urlencodedParser, async (request, response) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/import_custom', jsonParser, async (request, response) => {
|
||||
if (!request.body.url) {
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
try {
|
||||
const url = request.body.url;
|
||||
let result;
|
||||
|
||||
const chubParsed = parseChubUrl(url);
|
||||
|
||||
if (chubParsed?.type === 'character') {
|
||||
console.log('Downloading chub character:', chubParsed.id);
|
||||
result = await downloadChubCharacter(chubParsed.id);
|
||||
}
|
||||
else if (chubParsed?.type === 'lorebook') {
|
||||
console.log('Downloading chub lorebook:', chubParsed.id);
|
||||
result = await downloadChubLorebook(chubParsed.id);
|
||||
}
|
||||
else {
|
||||
return response.sendStatus(404);
|
||||
}
|
||||
|
||||
response.set('Content-Type', result.fileType);
|
||||
response.set('Content-Disposition', `attachment; filename="${result.fileName}"`);
|
||||
response.set('X-Custom-Content-Type', chubParsed?.type);
|
||||
return response.send(result.buffer);
|
||||
} catch (error) {
|
||||
console.log('Importing custom content failed', error);
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
async function downloadChubLorebook(id) {
|
||||
const fetch = require('node-fetch').default;
|
||||
|
||||
const result = await fetch('https://api.chub.ai/api/lorebooks/download', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
"fullPath": id,
|
||||
"format": "SILLYTAVERN",
|
||||
}),
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
throw new Error('Failed to download lorebook');
|
||||
}
|
||||
|
||||
const name = id.split('/').pop();
|
||||
const buffer = await result.buffer();
|
||||
const fileName = `${sanitize(name)}.json`;
|
||||
const fileType = result.headers.get('content-type');
|
||||
|
||||
return { buffer, fileName, fileType };
|
||||
}
|
||||
|
||||
async function downloadChubCharacter(id) {
|
||||
const fetch = require('node-fetch').default;
|
||||
|
||||
const result = await fetch('https://api.chub.ai/api/characters/download', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
"format": "tavern",
|
||||
"fullPath": id,
|
||||
})
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
throw new Error('Failed to download character');
|
||||
}
|
||||
|
||||
const buffer = await result.buffer();
|
||||
const fileName = result.headers.get('content-disposition')?.split('filename=')[1] || `${sanitize(id)}.png`;
|
||||
const fileType = result.headers.get('content-type');
|
||||
|
||||
return { buffer, fileName, fileType };
|
||||
}
|
||||
|
||||
function parseChubUrl(str) {
|
||||
const splitStr = str.split('/');
|
||||
const length = splitStr.length;
|
||||
|
||||
if (length < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const domainIndex = splitStr.indexOf('chub.ai');
|
||||
const lastTwo = domainIndex !== -1 ? splitStr.slice(domainIndex + 1) : splitStr;
|
||||
|
||||
const firstPart = lastTwo[0].toLowerCase();
|
||||
|
||||
if (firstPart === 'characters' || firstPart === 'lorebooks') {
|
||||
const type = firstPart === 'characters' ? 'character' : 'lorebook';
|
||||
const id = type === 'character' ? lastTwo.slice(1).join('/') : lastTwo.join('/');
|
||||
return {
|
||||
id: id,
|
||||
type: type
|
||||
};
|
||||
} else if (length === 2) {
|
||||
return {
|
||||
id: lastTwo.join('/'),
|
||||
type: 'character'
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function importRisuSprites(data) {
|
||||
try {
|
||||
const name = data?.data?.name;
|
||||
const risuData = data?.data?.extensions?.risuai;
|
||||
|
||||
// Not a Risu AI character
|
||||
if (!risuData || !name) {
|
||||
return;
|
||||
}
|
||||
|
||||
let images = [];
|
||||
|
||||
if (Array.isArray(risuData.additionalAssets)) {
|
||||
images = images.concat(risuData.additionalAssets);
|
||||
}
|
||||
|
||||
if (Array.isArray(risuData.emotions)) {
|
||||
images = images.concat(risuData.emotions);
|
||||
}
|
||||
|
||||
// No sprites to import
|
||||
if (images.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create sprites folder if it doesn't exist
|
||||
const spritesPath = path.join(directories.characters, name);
|
||||
if (!fs.existsSync(spritesPath)) {
|
||||
fs.mkdirSync(spritesPath);
|
||||
}
|
||||
|
||||
// Path to sprites is not a directory. This should never happen.
|
||||
if (!fs.statSync(spritesPath).isDirectory()) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`RisuAI: Found ${images.length} sprites for ${name}. Writing to disk.`);
|
||||
const files = fs.readdirSync(spritesPath);
|
||||
|
||||
outer: for (const [label, fileBase64] of images) {
|
||||
// Remove existing sprite with the same label
|
||||
for (const file of files) {
|
||||
if (path.parse(file).name === label) {
|
||||
console.log(`RisuAI: The sprite ${label} for ${name} already exists. Skipping.`);
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
|
||||
const filename = label + '.png';
|
||||
const pathToFile = path.join(spritesPath, filename);
|
||||
fs.writeFileSync(pathToFile, fileBase64, { encoding: 'base64' });
|
||||
}
|
||||
|
||||
// Remove additionalAssets and emotions from data (they are now in the sprites folder)
|
||||
delete data.data.extensions.risuai.additionalAssets;
|
||||
delete data.data.extensions.risuai.emotions;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
function writeSecret(key, value) {
|
||||
if (!fs.existsSync(SECRETS_FILE)) {
|
||||
const emptyFile = JSON.stringify({});
|
||||
|
1
src/claude.json
Normal file
1
src/claude.json
Normal file
File diff suppressed because one or more lines are too long
@@ -24,6 +24,7 @@ const fs = require('fs');
|
||||
const path = require('path');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const _ = require('lodash');
|
||||
|
||||
const directory = __dirname;
|
||||
|
||||
@@ -80,7 +81,7 @@ function extractFormKey(html) {
|
||||
}
|
||||
const formKey = formKeyList.join("");
|
||||
|
||||
return formKey;
|
||||
return formKey.slice(0, -1);
|
||||
}
|
||||
|
||||
|
||||
@@ -281,6 +282,33 @@ async function request_with_retries(method, attempts = 10) {
|
||||
throw new Error(`Failed to download ${url} too many times.`);
|
||||
}
|
||||
|
||||
function findKey(obj, key, path = []) {
|
||||
if (obj && typeof obj === 'object') {
|
||||
if (key in obj) {
|
||||
return [...path, key];
|
||||
}
|
||||
for (const k in obj) {
|
||||
const result = findKey(obj[k], key, [...path, k]);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function logObjectStructure(obj, indent = 0, depth = Infinity) {
|
||||
const keys = Object.keys(obj);
|
||||
keys.forEach((key) => {
|
||||
console.log(`${' '.repeat(indent)}${key}`);
|
||||
if (typeof obj[key] === 'object' && obj[key] !== null && indent < depth) {
|
||||
logObjectStructure(obj[key], indent + 1, depth);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
class Client {
|
||||
gql_url = "https://poe.com/api/gql_POST";
|
||||
gql_recv_url = "https://poe.com/api/receive_POST";
|
||||
@@ -363,19 +391,69 @@ class Client {
|
||||
async get_next_data() {
|
||||
logger.info('Downloading next_data...');
|
||||
|
||||
//these keys are used as of June 29, 2023
|
||||
//if API changes in the future, just change these to find the new path
|
||||
const viewerKeyName = 'viewer'
|
||||
const botNameKeyName = 'chatOfBotHandle'
|
||||
const defaultBotKeyName = 'defaultBotNickname'
|
||||
|
||||
const r = await request_with_retries(() => this.session.get(this.home_url));
|
||||
const jsonRegex = /<script id="__NEXT_DATA__" type="application\/json">(.+?)<\/script>/;
|
||||
const jsonText = jsonRegex.exec(r.data)[1];
|
||||
const nextData = JSON.parse(jsonText);
|
||||
|
||||
const viewerPath = findKey(nextData, viewerKeyName);
|
||||
const botNamePath = findKey(nextData, botNameKeyName);
|
||||
const defaultBotPath = findKey(nextData, defaultBotKeyName);
|
||||
|
||||
|
||||
|
||||
let viewer = null;
|
||||
if (viewerPath) {
|
||||
viewer = _.get(nextData, viewerPath.join('.'));
|
||||
}
|
||||
|
||||
//if the API changes, these reports will tell us how it changed
|
||||
if (viewerPath) {
|
||||
console.log(`'${viewerKeyName}' key: ${viewerPath.join('.')}`);
|
||||
} else {
|
||||
console.log(`ERROR: '${viewerKeyName}' key not found.`);
|
||||
//console.log(logObjectStructure(nextData, 0, 2));
|
||||
}
|
||||
if (botNamePath) {
|
||||
console.log(`'${botNameKeyName}' key: ${botNamePath.join('.')}`);
|
||||
} else {
|
||||
console.log(`ERROR: '${botNameKeyName}' key not found.`);
|
||||
//console.log(logObjectStructure(nextData, 0, 2));
|
||||
}
|
||||
|
||||
if (defaultBotPath) {
|
||||
console.log(`'${defaultBotKeyName}' key: ${defaultBotPath.join('.')}`);
|
||||
} else {
|
||||
console.log(`ERROR: '${defaultBotKeyName}' key not found.`);
|
||||
|
||||
}
|
||||
|
||||
if (!viewerPath || !botNamePath || !defaultBotPath) {
|
||||
console.log('-----------------')
|
||||
console.log("ERROR READING POE API! THIS IS THE RESPONSE STRUCTURE:")
|
||||
console.log("SEARCH THIS LIST FOR 'chatOfBotDisplayName', 'viewer', AND 'defaultBotNickname'...")
|
||||
console.log("-----------------")
|
||||
console.log(logObjectStructure(nextData, 0, 4));
|
||||
console.log("-----------------")
|
||||
}
|
||||
|
||||
this.formkey = extractFormKey(r.data);
|
||||
this.viewer = nextData.props.pageProps.payload.viewer;
|
||||
this.viewer = viewer;
|
||||
|
||||
//old hard coded message no longer needed
|
||||
//this.viewer = nextData.props.pageProps.payload?.viewer || nextData.props.pageProps.data?.viewer;
|
||||
|
||||
return nextData;
|
||||
}
|
||||
|
||||
async get_bots() {
|
||||
const viewer = this.next_data.props.pageProps.payload.viewer;
|
||||
const viewer = this.viewer;
|
||||
if (!viewer.availableBotsConnection) {
|
||||
throw new Error('Invalid token.');
|
||||
}
|
||||
@@ -393,12 +471,12 @@ class Client {
|
||||
r = cached_bots[url];
|
||||
}
|
||||
else {
|
||||
logger.info(`Downloading ${url}`);
|
||||
logger.info(`Downloading ${bot.displayName}`);
|
||||
r = await request_with_retries(() => this.session.get(url), retries);
|
||||
cached_bots[url] = r;
|
||||
}
|
||||
|
||||
const chatData = r.data.pageProps.payload.chatOfBotDisplayName;
|
||||
const chatData = r.data.pageProps.payload?.chatOfBotDisplayName || r.data.pageProps.data?.chatOfBotHandle;
|
||||
bots[chatData.defaultBotObject.nickname] = chatData;
|
||||
resolve();
|
||||
|
||||
@@ -661,14 +739,14 @@ class Client {
|
||||
signal.throwIfAborted();
|
||||
}
|
||||
|
||||
if (timeout == 0) {
|
||||
if (timeout <= 0) {
|
||||
throw new Error("Response timed out.");
|
||||
}
|
||||
|
||||
const message = this.message_queues[humanMessageId].shift();
|
||||
if (!message) {
|
||||
timeout -= 1;
|
||||
await delay(1000);
|
||||
timeout -= 0.1;
|
||||
await delay(100);
|
||||
continue;
|
||||
//throw new Error("Queue is empty");
|
||||
}
|
||||
|
16
start.sh
16
start.sh
@@ -3,19 +3,19 @@
|
||||
if ! command -v npm &> /dev/null
|
||||
then
|
||||
read -p "npm is not installed. Do you want to install nodejs and npm? (y/n)" choice
|
||||
case "$choice" in
|
||||
y|Y )
|
||||
case "$choice" in
|
||||
y|Y )
|
||||
echo "Installing nvm..."
|
||||
export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
|
||||
source ~/.bashrc
|
||||
nvm install lts
|
||||
nvm use lts;;
|
||||
n|N )
|
||||
nvm install --lts
|
||||
nvm use --lts;;
|
||||
n|N )
|
||||
echo "Nodejs and npm will not be installed."
|
||||
exit;;
|
||||
* )
|
||||
* )
|
||||
echo "Invalid option. Nodejs and npm will not be installed."
|
||||
exit;;
|
||||
esac
|
||||
@@ -28,7 +28,7 @@ if [ ! -z "$REPL_ID" ]; then
|
||||
fi
|
||||
|
||||
echo "Installing Node Modules..."
|
||||
npm i
|
||||
npm i --no-audit
|
||||
|
||||
echo "Entering SillyTavern..."
|
||||
node "$(dirname "$0")/server.js"
|
||||
node "$(dirname "$0")/server.js"
|
||||
|
Reference in New Issue
Block a user