Compare commits

...

239 Commits
1.9.3 ... 1.9.6

Author SHA1 Message Date
Cohee
9b5e082a26 Merge pull request #902 from SillyTavern/staging
Staging
2023-08-08 23:29:35 +03:00
Cohee
0ed340bd9a Merge pull request #901 from 50h100a/mancer-urlfix
Relax URL requirements when Mancer is enabled.
2023-08-08 23:27:05 +03:00
Cohee
26a9c2889f Bump package version 2023-08-08 23:12:26 +03:00
50h100a
53e41bdda8 Relax URL requirements when Mancer is enabled. 2023-08-08 16:12:03 -04:00
Cohee
25bd2541f7 Fix server crash in error handler 2023-08-08 23:11:27 +03:00
Cohee
6c909acea6 Merge pull request #900 from SillyTavern/staging
Staging
2023-08-08 22:54:20 +03:00
Cohee
8c70b1decd Bump package version 2023-08-08 22:54:02 +03:00
Cohee
7effc3497d Merge branch 'release' into staging 2023-08-08 22:53:13 +03:00
Cohee
cd86999d30 Fix variable name 2023-08-08 22:39:34 +03:00
Cohee
2fea218661 Extend random and roll syntax for frontend compatibility 2023-08-08 22:36:42 +03:00
Cohee
ef3a9a810e Fix avatar preview after changing it 2023-08-08 22:08:41 +03:00
Cohee
2c3cfb31f5 #799 Adjust right panel layout 2023-08-08 21:13:42 +03:00
Cohee
7c6429a577 Add a hint for quick reply slots. 2023-08-08 20:57:23 +03:00
Cohee
e343f2461d Fix block width 2023-08-08 20:51:31 +03:00
Cohee
c94eae6eb6 Improve OpenAI/OpenRouter error parsing 2023-08-08 20:07:41 +03:00
Cohee
9a7654598e Don't crash the server on trying to display corrupted past chat 2023-08-08 17:56:13 +03:00
Cohee
5ac375097b Fix last prompt line 2023-08-08 17:28:31 +03:00
Cohee
508e1a06da Merge branch 'staging' of https://github.com/SillyLossy/TavernAI into staging 2023-08-08 17:11:48 +03:00
Cohee
5ba7588838 Option to disable name forcing for instruct groups and personas 2023-08-08 17:11:38 +03:00
Cohee
98f8613e38 Merge pull request #899 from ouoertheo/ouoertheo/objective-fix-toastr
Fix objective gen toastr messsage
2023-08-08 16:20:38 +03:00
ouoertheo
5ea30d9d4d Fix objective gen toastr messsage 2023-08-08 07:49:21 -05:00
RossAscends
78825352e5 more help format updates 2023-08-08 18:03:39 +09:00
RossAscends
264b52b52d update help format 2023-08-08 17:42:28 +09:00
Cohee
0cb63e689d Merge pull request #872 from 50h100a/kai-fmt 2023-08-08 00:41:12 +03:00
50h100a
c1ab0212e5 update comments 2023-08-07 17:10:05 -04:00
50h100a
128945aaaa Copy 'relaxed api url' functionality over to webui 2023-08-07 16:46:32 -04:00
50h100a
75bb0d641f Make 'relaxed api url' even more relaxed. 2023-08-07 16:46:12 -04:00
RossAscends
fcc51b6481 no more forced color for blockquotes 2023-08-08 04:52:22 +09:00
Cohee1207
c124fc589f Fix display help links 2023-08-07 22:21:10 +03:00
Cohee1207
32e5566a37 Fix WI overflow alert 2023-08-07 22:12:50 +03:00
Cohee
f8e8929834 Merge pull request #896 from majick/tiny-cosmetic 2023-08-07 19:53:35 +03:00
majick
4167047f9b Cosmetic: The avatar crop accept button is labeled "Accept" 2023-08-07 09:49:07 -07:00
Cohee
5f97a52d58 #895 Move AI Horde connection to related endpoints 2023-08-07 19:34:10 +03:00
Cohee
7384cb07a4 Remove margins for block quotes 2023-08-07 14:08:49 +03:00
Cohee
e0d6430ade Fix copy-paste issue 2023-08-07 02:20:53 +03:00
Cohee
23a57e86a7 Try to remove excess character names from generations 2023-08-07 00:31:26 +03:00
Cohee
6e55b99aa9 Merge pull request #885 from StefanDanielSchwarz/proxy-ooba-preset 2023-08-07 00:02:04 +03:00
Cohee
2424367460 Merge pull request #886 from StefanDanielSchwarz/kobold-presets-without-max_length+genamt 2023-08-07 00:01:38 +03:00
Stefan Daniel Schwarz
c4602df108 kobold-presets-without-max_length+genamt 2023-08-06 21:59:57 +02:00
SDS
c86e2154c9 Add proxy ooba preset
Here's the TextGen version of the proxy-replacement-preset. Same as KoboldAI's, just for ooba.
2023-08-06 21:49:29 +02:00
Cohee
151f4d322c #883 Add option to disable CSRF tokens 2023-08-06 16:42:15 +03:00
Cohee
04a2d82a8d Merge pull request #884 from AeonBlack/patch-1
Resolve some API Spam / Fix 1 Token for 0 Token Counters
2023-08-06 16:31:44 +03:00
AeonBlack
cfc4394e41 Resolve some API Spam / Fix 1 Token for 0 Token Counters 2023-08-06 05:58:26 +01:00
Cohee
74b973c571 Fix Novel error handling 2023-08-05 21:15:49 +03:00
Cohee
ef1f6b3143 Merge branch 'staging' of https://github.com/SillyLossy/TavernAI into staging 2023-08-05 15:44:18 +03:00
Cohee
58f7c77281 Reformat code 2023-08-05 15:44:15 +03:00
Cohee
39e43e8a0d Update readme.md 2023-08-05 14:55:01 +03:00
Cohee
b59e0cdfc8 Merge pull request #876 from StefanDanielSchwarz/OpenOrca-OpenChat-instruct-preset
Added OpenOrca-OpenChat instruct preset
2023-08-05 14:50:57 +03:00
Cohee
e3f48ea767 Merge pull request #881 from spacegeek69/spacegeek69-or-doc
[doc] Replace WindowAI with OpenRouter
2023-08-05 14:50:08 +03:00
spacegeek69
b3bb88e99b Update readme.md
WindowAI -> OpenRouter
2023-08-05 13:34:27 +02:00
RossAscends
51ed6ba0c9 update API line in chinese readme 2023-08-05 19:14:17 +09:00
RossAscends
632d1a2a17 update readme API line 2023-08-05 19:13:13 +09:00
RossAscends
078eb051ad Merge pull request #878 from XXpE3/staging
Add README language switch
2023-08-05 19:00:03 +09:00
XXpE3
5b1095abcb - Added @XXpE3 at the bottom of the README.
- Noted Chinese ISSUES at the bottom of the README, contact @XXpE3.
2023-08-05 17:53:35 +08:00
RossAscends
b1666dc18d css skill issue 2023-08-05 18:51:28 +09:00
Shima Rin
3aa8229401 Merge branch 'SillyTavern:staging' into staging 2023-08-05 17:40:32 +08:00
RossAscends
734b0201be style for blockquotes 2023-08-05 18:36:39 +09:00
Cohee1207
6ddfcc7a06 Enable quote blocks and HTML comments 2023-08-05 12:29:37 +03:00
RossAscends
ef8aef7994 Merge pull request #879 from Miiiikachu/staging
Changed Assistant Prefill to a textarea to allow for a longer prefill message.
2023-08-05 15:07:22 +09:00
Mika
8128c2436b Changed Assistant Prefill to a textarea to allow for a longer prefill message. 2023-08-05 14:59:29 +09:00
XXpE3
649bc69b19 Add README language switch 2023-08-05 13:17:53 +08:00
Stefan Daniel Schwarz
bfac10028f removed enabled field from presets 2023-08-04 23:52:36 +02:00
50h100a
4ae3e9db0a fix settings access 2023-08-04 17:01:09 -04:00
Cohee1207
d05d4042fe Unbreak HTML 2023-08-04 23:52:19 +03:00
Cohee1207
1061971cdb Merge branch 'staging' of http://github.com/cohee1207/SillyTavern into staging 2023-08-04 23:51:18 +03:00
50h100a
6b2455da2b setting for relaxed api urls 2023-08-04 16:49:55 -04:00
Cohee
7b5bb030ad Merge pull request #877 from city-unit/feature/ui
Fix assistant prefill title alignment
2023-08-04 23:34:42 +03:00
Cohee1207
19b3e13675 Merge branch 'staging' of http://github.com/cohee1207/SillyTavern into staging 2023-08-04 23:29:25 +03:00
city-unit
d16f6f8fea Fix assistant prefill title alignment 2023-08-04 16:26:25 -04:00
SDS
45a66fae9d Added OpenOrca-OpenChat instruct preset
Here's an Instruct Mode preset for OpenOrca-OpenChat, e. g. for [Open-Orca/OpenOrcaxOpenChat-Preview2-13B](https://huggingface.co/Open-Orca/OpenOrcaxOpenChat-Preview2-13B) and other OpenOrca/OpenChat models
2023-08-04 21:44:47 +02:00
Cohee1207
6b288ca83c Adjust Novel settings to match the site more closely 2023-08-04 20:38:26 +03:00
Cohee
d59d7e6c8d Fix boolean conversion 2023-08-04 18:15:42 +03:00
Cohee
8b3f65073c Merge pull request #875 from StefanDanielSchwarz/replace-custom_stopping_strings-macro
Replace Macro in Custom Stopping Strings
2023-08-04 18:14:52 +03:00
Stefan Daniel Schwarz
b407fe2388 custom_stopping_strings_macro toggleable option 2023-08-04 16:53:49 +02:00
SDS
33af7ad266 Replace Macro in Custom Stopping Strings
Replaces user and char names in custom stopping strings, making them much more versatile and useful.

Example Use Case: Now you can use a custom stopping string like "\n*{{user}} " to stop generation when the AI tries to act as the user.
2023-08-04 15:45:45 +02:00
Cohee
cd8a24a712 Fix bulk edit 2023-08-04 14:41:00 +03:00
Cohee
84283bc2b4 Add "Best match" tokenizer option 2023-08-04 14:17:05 +03:00
Cohee
14827d6135 Fix bulk-edit extension manifest 2023-08-04 13:42:20 +03:00
Cohee
b34df90905 Merge pull request #873 from pyrater/staging 2023-08-04 10:37:54 +03:00
joe
c18a845f64 Fix duplicate call by mistake 2023-08-04 16:35:30 +09:00
pyrater
2a00e98ec5 Merge branch 'SillyTavern:staging' into staging 2023-08-04 16:26:44 +09:00
Cohee
d84d55c35f Merge pull request #874 from mweldon/novelai-stuff 2023-08-04 10:10:37 +03:00
joe
853d81e67c Bug and performance Fix's 2023-08-04 14:54:01 +09:00
joe
791b18d78e Removed old code 2023-08-04 11:25:58 +09:00
Mike Weldon
079b1623c5 NovelAI preamble code cleanup 2023-08-03 18:27:23 -07:00
joe
708b065300 Merge branch 'staging' 2023-08-04 09:32:02 +09:00
joe
140b86d822 updates 2023-08-04 09:27:01 +09:00
pyrater
8f1321f09d Update index.js 2023-08-04 09:26:17 +09:00
pyrater
9333340175 Update index.js
somehow display:none; was missing from line 1183
2023-08-04 09:21:52 +09:00
joe
4698f0f765 Perfomance Increase for unloading animation 2023-08-04 08:29:39 +09:00
pyrater
f106666ded Merge branch 'SillyTavern:staging' into staging 2023-08-04 06:43:17 +09:00
50h100a
2d07cce1dd make KAI url-fixing as flexible as webUI's 2023-08-03 16:28:02 -04:00
Cohee
ea809023b5 Merge pull request #858 from 50h100a/mancer-api
Mancer API
2023-08-03 23:06:53 +03:00
Cohee
bcffaec6a6 Fix invalid function reference 2023-08-03 19:30:19 +03:00
Cohee
008d8fa6fc Merge pull request #859 from city-unit/feature/bulk
Bulk Editing
2023-08-03 19:29:33 +03:00
Cohee
926bf0b4c9 Update gitignore 2023-08-03 18:46:53 +03:00
Cohee
aefafdebe0 Merge pull request #864 from StefanDanielSchwarz/remote-link-cmd
Remote-Link.cmd to set up a cloudflare tunnel
2023-08-03 18:44:47 +03:00
SDS
2f94451948 Update Remote-Link.cmd
Added warning about the potential dangers of having a tunnel open
2023-08-03 17:36:35 +02:00
Cohee
e5a678fb2d Merge pull request #869 from majick/chat-delete-spin
Proposed: Spinning cute skull when hovering PastChat_cross
2023-08-03 18:17:23 +03:00
majick
109f6ace63 Proposed: Spinning cute skull when deleting a chat 2023-08-03 07:59:57 -07:00
Cohee
44340208de Merge pull request #868 from majick/chat-delete-icon
Proposed: Skull icon to delete past chat
2023-08-03 17:24:03 +03:00
Cohee
19a0f8e5ac Merge pull request #865 from StefanDanielSchwarz/del_char_checkbox-unchecked-by-default
del_char_checkbox unchecked by default
2023-08-03 17:18:49 +03:00
Cohee
2a153e3b15 #866 Wait for group to stop generating before auto-summarizing 2023-08-03 17:17:58 +03:00
Cohee
249ab7106a Update bug_report.md 2023-08-03 17:15:30 +03:00
majick
d5e32af9b2 Proposed: Skull icon to delete past chat 2023-08-03 07:07:59 -07:00
SDS
65732f4406 del_char_checkbox unchecked by default
When you delete a character, you can always re-import them - but if you deleted the chat files, they're gone forever unless you backed them up manually. That's why I think this checkbox should be off by default, making chat file deletion a conscious effort.

With it off, worst case someone forgets to check the box and has some unnecessary files on their disk that they can then delete manually. That's much less of a problem than someone forgetting to uncheck the box (e. g. when importing a new version of the character) and then suffering data loss.
2023-08-03 15:39:15 +02:00
SDS
3da9438b63 Remote-Link.cmd to set up a cloudflare tunnel
Run this script to download the latest cloudflared.exe from Cloudflare (if it's not available in the same folder as the script already) and run it to set up a secure HTTPS tunnel to your SillyTavern instance instead of using insecure unencrypted HTTP.
2023-08-03 14:54:46 +02:00
RossAscends
f892931d44 Merge branch 'staging' of https://github.com/Cohee1207/SillyTavern into staging 2023-08-03 21:17:36 +09:00
RossAscends
a837b1e2fb fix movingUI observer states 2023-08-03 21:17:34 +09:00
50h100a
9cf4056b28 Do backflips trying to get error information from endpoints.
Then present it nicely.
2023-08-03 07:32:53 -04:00
Cohee
37e653dcf4 Add toggle for persona info notifications 2023-08-03 14:24:45 +03:00
Cohee
0a12fe0bdb Unhide expression holder on setting live2d sprite 2023-08-03 14:07:50 +03:00
joe
56a4a6eb83 Bug fix 2023-08-03 20:01:35 +09:00
Cohee
8446f10408 Merge pull request #861 from pyrater/staging
Setting Tracking for Live2d
2023-08-03 13:41:24 +03:00
joe
4b7c837fe3 Removed unneeded IF 2023-08-03 19:29:48 +09:00
joe
aebdd6cd42 Fallback expression and tweak 2023-08-03 19:23:15 +09:00
pyrater
73f15060c9 Merge branch 'SillyTavern:staging' into staging 2023-08-03 19:10:10 +09:00
Cohee
e381e1cefc Merge pull request #862 from StefanDanielSchwarz/presets-and-readme
Presets and readme
2023-08-03 13:09:01 +03:00
50h100a
2fc6813e66 code review adjustments part 1 2023-08-03 06:07:54 -04:00
pyrater
0ad3c86e17 Merge branch 'SillyTavern:staging' into staging 2023-08-03 19:06:04 +09:00
joe
9f44a72d76 Setting Tracking Live2d 2023-08-03 19:05:21 +09:00
SDS
c760447288 Update readme-zh_cn.md
Added myself to the credits list in the Readme doc [as suggested](https://github.com/SillyTavern/SillyTavern/pull/848#issuecomment-1662510842)
2023-08-03 11:56:01 +02:00
SDS
ea0fe349cd Update readme.md
Added myself to the credits list in the Readme doc [as suggested](https://github.com/SillyTavern/SillyTavern/pull/848#issuecomment-1662510842)
2023-08-03 11:55:09 +02:00
SDS
1f56f0d64a Update simple-proxy-for-tavern.settings
Go back down to 2048 tokens instead of 4096 to be in line with the other non-Llama 2-specific presets
2023-08-03 11:47:59 +02:00
SDS
905131c764 Update Deterministic.settings
Go back down to 2048 tokens instead of 4096 to be in line with the other non-Llama 2-specific presets
2023-08-03 11:46:53 +02:00
Cohee
31feaee805 Enter to submit dialogue popup input 2023-08-03 11:32:08 +03:00
Cohee
a07cbe5f7f Merge pull request #860 from gd1551/staging 2023-08-03 11:11:36 +03:00
gd1551
67fa7b9607 Update Custom Stop Strings to note NovelAI support 2023-08-03 11:06:29 +03:00
Cohee
1b005ef47f Merge pull request #856 from mweldon/preamble 2023-08-03 10:24:31 +03:00
Mike Weldon
c8b5b7da22 Use prose augmenter by default for Kayra 2023-08-02 23:07:17 -07:00
RossAscends
5a67d72fea /qr, /qrset & ctrl+1~9 hotkeys for QRs 2023-08-03 14:44:23 +09:00
RossAscends
68e5ae63d6 move closechat/togglepanels to slashcommands 2023-08-03 13:21:38 +09:00
city-unit
9712e4bbb0 Moved bulk edit from external to internal extension. 2023-08-03 00:15:09 -04:00
50h100a
61c0e3b08b Merge branch 'staging' of https://github.com/SillyTavern/SillyTavern into mancer-api 2023-08-02 23:46:03 -04:00
50h100a
d4278388f7 remove non-changes 2023-08-02 23:38:50 -04:00
50h100a
2fdec7eb03 Added authentication variant to WebUI API. 2023-08-02 23:25:24 -04:00
Mike Weldon
1d0f67c144 Add NAI preamble to start of chat buffer 2023-08-02 18:22:06 -07:00
Mike Weldon
14ef5d9a6b Add new NAI presets TeaTime and ProWriter 2023-08-02 18:21:14 -07:00
Cohee1207
143b4347c2 Extend cases for OAI status code message pulling 2023-08-02 23:46:09 +03:00
Cohee1207
2a08e199d2 Merge branch 'release' of http://github.com/cohee1207/SillyTavern into release 2023-08-02 23:46:05 +03:00
Cohee1207
f198f5eb6e Fix localization hiding Usage Stats button 2023-08-02 23:04:52 +03:00
Cohee1207
65a16970f4 Extend cases for OAI status code message pulling 2023-08-02 23:02:29 +03:00
Cohee1207
5a7c4947b3 Merge branch 'staging' of http://github.com/cohee1207/SillyTavern into staging 2023-08-02 23:00:55 +03:00
Cohee
bb3fc5be62 Merge pull request #853 from gd1551/staging
Add stop sequences support to NovelAI generations
2023-08-02 22:56:05 +03:00
gd1551
ea800d1550 Add stop sequences support to NovelAI generations 2023-08-02 22:42:11 +03:00
Cohee
90e08e08de Merge pull request #852 from StefanDanielSchwarz/fix-inconsistent-newline-trimming
Fix inconsistent newline trimming
2023-08-02 21:55:12 +03:00
SDS
9d99b89c9c Fix inconsistent newline trimming
Newlines weren't trimmed at first generation in a new chat, only on subsequent generations. By commenting out this check, it works consistently for all generations.

(I noticed because even with my deterministic preset, a regen/swipe would give a different output than the very first generation, so I went looking and found this if-clause as the source of the inconsistent behavior.)
2023-08-02 20:00:01 +02:00
Cohee
baddee8082 Merge pull request #850 from StefanDanielSchwarz/improved-roleplay-preset 2023-08-02 19:10:13 +03:00
Cohee
a51653e8b5 Merge pull request #851 from StefanDanielSchwarz/proxy-preset+some-fixes 2023-08-02 19:09:40 +03:00
SDS
7dfaf6f0b0 Fix Storywriter-Llama2.settings
Removed "can_use_streaming" since that should be determined by SillyTavern and not be hardcoded in the preset.
2023-08-02 18:06:06 +02:00
SDS
3f015f4bd2 Fix Deterministic.settings
Removed "can_use_streaming" since that should be determined by SillyTavern and not be hardcoded in the preset.
2023-08-02 18:05:27 +02:00
SDS
6f18c457fc simple-proxy-for-tavern default preset
These are the same settings as [simple-proxy-for-tavern's default preset](https://github.com/anon998/simple-proxy-for-tavern/blob/main/presets/default.json). I've fixed the sampler order and raised the context size to Llama 2's 4096 tokens.
2023-08-02 18:03:14 +02:00
SDS
c57de3d47b Improved Roleplay Preset
More like simple-proxy-for-tavern's default verbose prompt, and now works with SillyTavern's AutoFormat Overrides on or off, so this new and improved version is better and more compatible.
2023-08-02 17:42:42 +02:00
mashwell
cd02abe205 textgen simple-1 preset fix 2023-08-02 14:23:16 +03:00
50h100a
aac7525204 Add secret key storage 2023-08-02 03:31:17 -04:00
50h100a
42cc66f06e First pass UI for extending webui 2023-08-02 03:30:57 -04:00
Cohee
f6f51d21c5 Merge pull request #841 from ouoertheo/ouoertheo/objectives6
Objective: currentTask fix in MESSAGE_RECEIVED, ignore swipes
2023-08-02 01:45:20 +03:00
ouoertheo
9a4d62ca6f add lastMessageWasSwipe=false to resetState 2023-08-01 16:16:52 -05:00
Cohee
9d023dc3b1 Load live2d by posting a file 2023-08-01 23:57:04 +03:00
Cohee
ac98ebcc6c Fix npm audit 2023-08-01 23:25:36 +03:00
Cohee
29a3c5d590 Fix npm audit 2023-08-01 23:25:09 +03:00
SDS
0c0e24323c proxy-replacement-preset: Roleplay Instruct Mode Preset
In [[Feature Request] Add Simple Proxy functionality into Silly Tavern directly · Issue #831 · SillyTavern/SillyTavern](https://github.com/SillyTavern/SillyTavern/issues/831), I explained how to replace the [simple-proxy-for-tavern](https://github.com/anon998/simple-proxy-for-tavern) using built-in functionality of SillyTavern. To make this easier, here's an Instruct Mode Preset that helps setting this up.
2023-08-01 23:14:50 +03:00
SDS
32f605e413 Llama-2-KoboldAI-presets
Here are two presets I've found very useful for Llama 2-based models:

- Deterministic takes away the randomness and is good for testing/comparing models because same input equals same output.

- Storywriter-Llama2 is the Storywriter preset adjusted for Llama 2's 4K context size. It also works well against Llama 2's repetition/looping issues.
2023-08-01 23:13:37 +03:00
Cohee
af8c21fea2 Send middle-out transform strategy to OpenRouter 2023-08-01 18:49:03 +03:00
Cohee
72974d8a54 More clear message for character import failure 2023-08-01 18:13:50 +03:00
Cohee
7f86551ab4 Don't try to load live2d if variable is disabled or module is not loaded to Extras 2023-08-01 16:33:30 +03:00
Cohee
c5d87e4808 ParseImgDimensions for Showdown 2023-08-01 16:24:54 +03:00
Cohee
e5f3a70860 #843 Wait for group to stop generating before checking objectives 2023-08-01 15:53:10 +03:00
Cohee
7596d78322 #844 Properly handle KoboldAI errors 2023-08-01 15:22:51 +03:00
Cohee
5645432e9d #823 Allow arbitrary line breaks in instruct sequences and custom chat separators 2023-08-01 14:17:33 +03:00
Cohee
bad7892baa Adjust chromadb auto% for character description 2023-08-01 14:16:03 +03:00
Cohee
a0c8ac54dd Lower max context for Clio and Kayra presets 2023-08-01 13:30:30 +03:00
SDS
73fd306b8b Fixed persona_description linebreak
When persona description is positioned after character card, there are double linebreaks before and none behind the persona description, because storyString already brings a trailing one whereas persona_description doesn't. This fixes that by putting the linebreak where it belongs.
2023-08-01 12:54:46 +03:00
Cohee
72213add56 #833 Sort tags list alphabetically 2023-08-01 12:26:28 +03:00
ouoertheo
6f4fd15095 currentTask fix in MESSAGE_RECEIVED, ingore swipes 2023-08-01 04:24:55 -05:00
Cohee
78d62d7be2 Merge branch 'staging' of https://github.com/SillyLossy/TavernAI into staging 2023-08-01 11:58:42 +03:00
Cohee
99af6ed472 Update default settings 2023-08-01 11:57:25 +03:00
Cohee
16b45f1ea9 Reformat new code 2023-07-31 20:56:05 +03:00
Cohee
435d319090 Merge pull request #835 from pyrater/staging
Live2d Changes
2023-07-31 20:53:45 +03:00
Cohee
e7148c41a9 Merge pull request #839 from Tony-sama/staging
Restored speech-recognition streaming mode as a new provider "Streaming"
2023-07-31 20:51:02 +03:00
Tony Ribeiro
192c82b180 Restored streaming mode as a new provider "Streaming", recording is done on server side, voice detection with vosk and transcript with whisper. 2023-07-31 18:47:33 +02:00
Cohee
8aff89de30 Merge pull request #838 from ouoertheo/ouoertheo/objective-next-task-bugfix
Objective: Current task fixes, {{parent}} prompt template variable
2023-07-31 17:01:32 +03:00
ouoertheo
6768c56e2b fix regression on task selection 2023-07-31 07:56:49 -05:00
joe
4939387bbf Updated based on feedback 2023-07-31 19:14:15 +09:00
joe
4c14b8ee2d Updated Static URL 2023-07-31 19:01:45 +09:00
joe
0bbcf0db83 Updated non static URL Calls 2023-07-31 18:54:50 +09:00
Cohee
29d841a50b Bump package version 2023-07-31 12:54:49 +03:00
joe
0c919bf32d Talking Animation 2023-07-31 18:21:32 +09:00
pyrater
9f92b19004 Merge branch 'SillyTavern:staging' into staging 2023-07-31 16:10:22 +09:00
joe
7824a18103 Live2d Commits 2023-07-31 16:09:36 +09:00
Cohee
18e6e578dd Merge pull request #834 from ouoertheo/ouoertheo/objective-auto-check-fix 2023-07-31 09:54:22 +03:00
joe
0eef05908d live2d addition 2023-07-31 14:52:30 +09:00
ouoertheo
d5e40e0271 fix for allowing root task as. notify on manual check 2023-07-30 19:15:05 -05:00
Cohee
6b85f10818 Merge pull request #832 from mweldon/kayra-fix-fresh-coffee 2023-07-31 00:57:16 +03:00
Mike Weldon
73ea029acd Fix rep penalty parameter name 2023-07-30 14:14:17 -07:00
Mike Weldon
27c6e5ecff Fix some presets, bad words, rep allowlist, and banned tokens 2023-07-30 14:03:28 -07:00
Cohee
40f466b2c3 Summary plugin improvements: In-chat position, customizable template, force insert after X words 2023-07-30 23:10:37 +03:00
RossAscends
02667d3d1a Merge branch 'staging' of https://github.com/Cohee1207/SillyTavern into staging 2023-07-31 03:37:52 +09:00
RossAscends
9cb70817c7 fix ctrl+up when sys msg is last 2023-07-31 03:37:49 +09:00
Cohee
e172f50d4f Allow /cut 0 2023-07-30 21:35:36 +03:00
Cohee
49f7185c8c #829 Close chat option 2023-07-30 21:35:21 +03:00
RossAscends
638050a3de skill issue 2023-07-31 02:56:39 +09:00
RossAscends
a0f369d100 Merge branch 'staging' of https://github.com/Cohee1207/SillyTavern into staging 2023-07-31 02:41:41 +09:00
RossAscends
f9239c860d Escape hotkey to close panels/popups 2023-07-31 02:41:15 +09:00
Cohee
41a08fed2b Merge pull request #828 from ouoertheo/ouoertheo/objective-qol-improvements 2023-07-30 19:33:02 +03:00
RossAscends
95d43712d3 fix chat bg color when panels hidden 2023-07-31 01:08:24 +09:00
ouoertheo
4f76a8b0ce fix task counter decrement 2023-07-30 09:27:31 -05:00
ouoertheo
3fc2b81433 help text update 2023-07-30 09:19:17 -05:00
ouoertheo
23a514bba8 full templating for all prompts. prompt mgmt 2023-07-30 09:15:12 -05:00
Cohee1207
51449c4913 Rename Settings option 2023-07-30 16:40:45 +03:00
Cohee1207
18bb362578 Merge branch 'staging' of http://github.com/cohee1207/SillyTavern into staging 2023-07-30 16:38:50 +03:00
Cohee
6d9c2ec365 Merge pull request #819 from pyrater/staging
Show / Hide Top Bar
2023-07-30 16:38:44 +03:00
Cohee
ad26a1968b Merge pull request #818 from city-unit/feature/WI
Add optional alerting for WI debugging.
2023-07-30 16:29:33 +03:00
Cohee1207
52289acc62 Reformat index.html 2023-07-30 16:28:55 +03:00
Cohee1207
b970c69844 Merge branch 'staging' of http://github.com/cohee1207/SillyTavern into staging 2023-07-30 16:28:25 +03:00
Cohee
c8e456d773 Merge pull request #827 from Ahbahl/staging
Moves Claude Assistant prefill box below JB
2023-07-30 16:28:19 +03:00
Cohee1207
d00fbacec3 #822 Increase instruct sequences max lengths 2023-07-30 16:27:29 +03:00
Cohee1207
ccbec7715a #825 Set temperature slider min value to 0.0 2023-07-30 16:21:01 +03:00
Cohee1207
1b7973ec13 Merge branch 'staging' of http://github.com/cohee1207/SillyTavern into staging 2023-07-30 16:20:05 +03:00
Ahbahl
1f63fd9344 Moves Claude Assistant prefill below JB to be more in line with how prompts are structured. 2023-07-30 15:13:10 +02:00
joe
d8d5118283 removed padding 2023-07-30 11:57:21 +09:00
joe
11226ff7d6 add CSS to round corners 2023-07-30 11:56:17 +09:00
pyrater
844d9c86a4 Merge branch 'SillyTavern:staging' into staging 2023-07-30 11:50:09 +09:00
joe
0aee97ccca Added Option to toggle/Hide settings bar. 2023-07-30 11:47:17 +09:00
city-unit
fa06e5ee5a Add optional alerting for WI debugging. 2023-07-29 22:15:54 -04:00
Cohee
6794be4d1e Merge branch 'release' into staging 2023-07-30 03:10:12 +03:00
Cohee
4316396880 Update readme 2023-07-30 03:09:55 +03:00
Cohee
fd93bc7736 Merge pull request #817 from city-unit/feature/activechat
Persist active chat across devices.
2023-07-30 03:07:55 +03:00
city-unit
0cc88c17c7 Handle groups correctly, save settings. 2023-07-29 20:05:12 -04:00
city-unit
6829f5308f Persist active chat across devices. 2023-07-29 19:48:08 -04:00
Mike Weldon
1689913b43 Fix minor typo that broke phraseRepPen for NAI 2023-07-30 02:35:05 +03:00
Cohee
33b7e8fad1 Merge branch 'release' into staging 2023-07-30 02:33:48 +03:00
Cohee
17eb55731e Merge pull request #816 from mweldon/kayra-fix
Phrase Rep Pen typo fix
2023-07-30 02:32:18 +03:00
Mike Weldon
9dba57dc85 Fix minor typo that broke phraseRepPen for NAI 2023-07-29 15:59:18 -07:00
Cohee
dddc49c235 #798 Claude assistant prefill 2023-07-30 01:51:59 +03:00
RossAscends
faf9c4445f Create Default QR preset 2023-07-30 06:26:06 +09:00
RossAscends
a646aaa125 Merge branch 'staging' of https://github.com/Cohee1207/SillyTavern into staging 2023-07-30 06:22:05 +09:00
RossAscends
80a0cb2bc4 MPV of QuickReply Presets 2023-07-30 06:22:03 +09:00
Cohee1207
ac4fa0e035 Merge branch 'staging' of http://github.com/cohee1207/SillyTavern into staging 2023-07-29 21:18:56 +03:00
95 changed files with 2376 additions and 5113 deletions

View File

@@ -5,3 +5,4 @@ readme*
Start.bat
/dist
/backups/
cloudflared.exe

View File

@@ -7,7 +7,7 @@ assignees: ''
---
> **Warning**. Complete **all** the fields below. Otherwise your bug report will be **ignored**!
> **Warning**. Complete **all** the fields below. Otherwise, your bug report will be **ignored**!
**Have you searched for similar [bugs](https://github.com/SillyTavern/SillyTavern/issues?q=)?**
Yes/No
@@ -38,7 +38,7 @@ Providing the logs from the browser DevTools console (opened by pressing the F12
- Node.js version (if applicable): [run `node --version` in cmd]
- Browser [e.g. chrome, safari]
- Generation API [e.g. KoboldAI, OpenAI]
- Branch [main, dev]
- Branch [staging, release]
- Model [e.g. Pygmalion 6b, LLaMa 13b]
**Additional context**

View File

@@ -1,6 +1,8 @@
[English](readme.md) | 中文
![image](https://github.com/SillyTavern/SillyTavern/assets/18619528/8c41a061-7f72-4d2b-9d54-e6d058209e7b)
移动设备界面友好多种人工智能服务或模型支持KoboldAI/CPP, Horde, NovelAI, Ooba, OpenAI+proxies, WindowAI(Claude!)),类似 Galgame 的 老 婆 模 式Horde SD文本系统语音生成世界信息Lorebooks可定制的界面自动翻译和比你所需要的更多的 Prompt。附带扩展服务支持文本绘画生成与语音生成和基于向量数据库 ChromaDB 的聊天信息总结。
移动设备界面友好多种人工智能服务或模型支持KoboldAI/CPP, Horde, NovelAI, Ooba, OpenAI, OpenRouter, Claude, Scale),类似 Galgame 的 老 婆 模 式Horde SD文本系统语音生成世界信息Lorebooks可定制的界面自动翻译和比你所需要的更多的 Prompt。附带扩展服务支持文本绘画生成与语音生成和基于向量数据库 ChromaDB 的聊天信息总结。
基于 TavernAI 1.2.8 的分叉版本
@@ -282,25 +284,26 @@ SillyTavern 会将 API 密钥保存在目录中的 `secrets.json` 文件内。
## 许可证和贡献
** 发布本程序是希望它能有所帮助,但不做任何保证;甚至没有明示的性能、稳定性和其他任何特定用途的可用性保证。更多详情,请参阅 GNU Affero 通用公共许可证。 **
**发布本程序是希望它能有所帮助,但不做任何保证;甚至没有明示的性能、稳定性和其他任何特定用途的可用性保证。更多详情,请参阅 GNU Affero 通用公共许可证。**
** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. **
**This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.**
* TAI Base by Humi: Unknown license
* Cohee's modifications and derived code: AGPL v3
* RossAscends' additions: AGPL v3
* Portions of CncAnon's TavernAITurbo mod: Unknown license
* 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/)
* kingbri's various commits and suggestions (<https://github.com/bdashore3>)
* StefanDanielSchwarz's various commits and bug reports (<https://github.com/StefanDanielSchwarz>)
* 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
* KoboldAI Presets from KAI Lite: https://lite.koboldai.net/
* KoboldAI Presets from KAI Lite: <https://lite.koboldai.net/>
* Noto Sans font by Google (OFL license)
* Icon theme by Font Awesome https://fontawesome.com (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* AI Horde client library by ZeldaFan0225: https://github.com/ZeldaFan0225/ai_horde
* Icon theme by Font Awesome <https://fontawesome.com> (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* AI Horde client library by ZeldaFan0225: <https://github.com/ZeldaFan0225/ai_horde>
* Linux startup script by AlpinDale
* Thanks paniphons for providing a FAQ document
* 10K Discord Users Celebratory Background by @kallmeflocc
* Default content (characters and lore books) provided by @OtisAlejandro, @RossAscends and @kallmeflocc
* Korean translation by @doloroushyeonse
* Korean translation by @doloroushyeonse
* 中文翻译由 [@XXpE3](https://github.com/XXpE3) 完成,中文 ISSUES 可以联系 @XXpE3

9
.github/readme.md vendored
View File

@@ -1,6 +1,8 @@
English | [中文](readme-zh_cn.md)
![image](https://github.com/SillyTavern/SillyTavern/assets/18619528/8c41a061-7f72-4d2b-9d54-e6d058209e7b)
Mobile-friendly, Multi-API (KoboldAI/CPP, Horde, NovelAI, Ooba, OpenAI+proxies, WindowAI(Claude!)), VN-like Waifu Mode, Horde SD, System TTS, WorldInfo (lorebooks), customizable UI, auto-translate, and more prompt options than you'd ever want or need. Optional Extras server for more SD/TTS options + ChromaDB/Summarize.
Mobile-friendly, Multi-API (KoboldAI/CPP, Horde, NovelAI, Ooba, OpenAI, OpenRouter, Claude, Scale), VN-like Waifu Mode, Horde SD, System TTS, WorldInfo (lorebooks), customizable UI, auto-translate, and more prompt options than you'd ever want or need. Optional Extras server for more SD/TTS options + ChromaDB/Summarize.
Based on a fork of TavernAI 1.2.8
@@ -65,7 +67,7 @@ Get in touch with the developers directly:
* 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 rich lore or save tokens on your character card
* Window AI browser extension support (run models like Claude, GPT 4): <https://windowai.io/>
* [OpenRouter](https://openrouter.ai) connection for various APIs (Claude, GPT-4/3.5 and more)
* [Oobabooga's TextGen WebUI](https://github.com/oobabooga/text-generation-webui) API connection
* [AI Horde](https://horde.koboldai.net/) connection
* Prompt generation formatting tweaking
@@ -293,7 +295,7 @@ GNU Affero General Public License for more details.**
* 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>)
* StefanDanielSchwarz's various commits and bug reports (<https://github.com/StefanDanielSchwarz>)
* 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
@@ -307,3 +309,4 @@ GNU Affero General Public License for more details.**
* Default content (characters and lore books) provided by @OtisAlejandro, @RossAscends and @kallmeflocc
* Korean translation by @doloroushyeonse
* k_euler_a support for Horde by <https://github.com/Teashrock>
* Chinese translation by [@XXpE3](https://github.com/XXpE3), 中文 ISSUES 可以联系 @XXpE3

2
.gitignore vendored
View File

@@ -26,4 +26,6 @@ secrets.json
/dist
/backups/
public/movingUI/
public/QuickReplies/
content.log
cloudflared.exe

18
Remote-Link.cmd Normal file
View File

@@ -0,0 +1,18 @@
@echo off
echo ========================================================================================================================
echo WARNING: Cloudflare Tunnel!
echo ========================================================================================================================
echo This script downloads and runs the latest cloudflared.exe from Cloudflare to set up an HTTPS tunnel to your SillyTavern!
echo Using the randomly generated temporary tunnel URL, anyone can access your SillyTavern over the Internet while the tunnel
echo is active. Keep the URL safe and secure your SillyTavern installation by setting a username and password in config.conf!
echo.
echo See https://docs.sillytavern.app/usage/remoteconnections/ for more details about how to secure your SillyTavern install.
echo.
echo By continuing you confirm that you're aware of the potential dangers of having a tunnel open and take all responsibility
echo to properly use and secure it!
echo.
echo To abort, press Ctrl+C or close this window now!
echo.
pause
if not exist cloudflared.exe curl -Lo cloudflared.exe https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-windows-amd64.exe
cloudflared.exe tunnel --url localhost:8000

View File

@@ -14,6 +14,7 @@
"world_info_depth": 2,
"world_info_budget": 25,
"world_info_recursive": true,
"world_info_overflow_alert": false,
"world_info_case_sensitive": false,
"world_info_match_whole_words": false,
"world_info_character_strategy": 1,
@@ -27,6 +28,7 @@
"eta_cutoff": 0,
"typical_p": 1,
"rep_pen": 1.1,
"rep_pen_range": 0,
"no_repeat_ngram_size": 0,
"penalty_alpha": 0,
"num_beams": 1,
@@ -57,7 +59,7 @@
"trusted_workers_only": false
},
"power_user": {
"tokenizer": 3,
"tokenizer": 99,
"token_padding": 64,
"collapse_newlines": false,
"pygmalion_formatting": 0,
@@ -91,7 +93,7 @@
"sort_order": "asc",
"sort_rule": null,
"font_scale": 1,
"blur_strength": 5,
"blur_strength": 10,
"shadow_width": 2,
"main_text_color": "rgba(220, 220, 210, 1)",
"italics_text_color": "rgba(145, 145, 145, 1)",
@@ -144,7 +146,9 @@
"persona_descriptions": {},
"persona_description": "",
"persona_description_position": 0,
"custom_stopping_strings": ""
"custom_stopping_strings": "",
"custom_stopping_strings_macro": true,
"fuzzy_search": false
},
"extension_settings": {
"apiUrl": "http://localhost:5100",
@@ -183,7 +187,14 @@
"promptInterval": 10,
"promptMinInterval": 1,
"promptMaxInterval": 100,
"promptIntervalStep": 1
"promptIntervalStep": 1,
"template": "[Summary: {{summary}}]",
"position": 0,
"depth": 2,
"promptForceWords": 0,
"promptForceWordsStep": 100,
"promptMinForceWords": 0,
"promptMaxForceWords": 10000
},
"note": {
"default": "",
@@ -203,7 +214,8 @@
"ttsEnabled": false,
"currentProvider": "System",
"auto_generation": true,
"ElevenLabs": {}
"ElevenLabs": {},
"System": {}
},
"sd": {
"scale_min": 1,
@@ -228,7 +240,16 @@
"horde": true,
"horde_nsfw": false,
"horde_karras": true,
"refine_mode": false
"refine_mode": false,
"prompts": {
"0": "[In the next response I want you to provide only a detailed comma-delimited list of keywords and phrases which describe {{char}}. The list must include all of the following items in this order: name, species and race, gender, age, clothing, occupation, physical features and appearances. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase 'full body portrait,']",
"1": "[Pause your roleplay and provide a detailed description of {{user}}'s physical appearance from the perspective of {{char}} in the form of a comma-delimited list of keywords and phrases. The list must include all of the following items in this order: name, species and race, gender, age, clothing, occupation, physical features and appearances. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase 'full body portrait,'. Ignore the rest of the story when crafting this description. Do not roleplay as {{char}} when writing this description, and do not attempt to continue the story.]",
"2": "[Pause your roleplay and provide a detailed description for all of the following: a brief recap of recent events in the story, {{char}}'s appearance, and {{char}}'s surroundings. Do not roleplay while writing this description.]",
"3": "[Pause your roleplay and provide ONLY the last chat message string back to me verbatim. Do not write anything after the string. Do not roleplay at all in your response. Do not continue the roleplay story.]",
"4": "[Pause your roleplay. Your next response must be formatted as a single comma-delimited list of concise keywords. The list will describe of the visual details included in the last chat message.\n\n Only mention characters by using pronouns ('he','his','she','her','it','its') or neutral nouns ('male', 'the man', 'female', 'the woman').\n\n Ignore non-visible things such as feelings, personality traits, thoughts, and spoken dialog.\n\n Add keywords in this precise order:\n a keyword to describe the location of the scene,\n a keyword to mention how many characters of each gender or type are present in the scene (minimum of two characters:\n {{user}} and {{char}}, example: '2 men ' or '1 man 1 woman ', '1 man 3 robots'),\n\n keywords to describe the relative physical positioning of the characters to each other (if a commonly known term for the positioning is known use it instead of describing the positioning in detail) + 'POV',\n\n a single keyword or phrase to describe the primary act taking place in the last chat message,\n\n keywords to describe {{char}}'s physical appearance and facial expression,\n keywords to describe {{char}}'s actions,\n keywords to describe {{user}}'s physical appearance and actions.\n\n If character actions involve direct physical interaction with another character, mention specifically which body parts interacting and how.\n\n A correctly formatted example response would be:\n '(location),(character list by gender),(primary action), (relative character position) POV, (character 1's description and actions), (character 2's description and actions)']",
"5": "[In the next response I want you to provide only a detailed comma-delimited list of keywords and phrases which describe {{char}}. The list must include all of the following items in this order: name, species and race, gender, age, facial features and expressions, occupation, hair and hair accessories (if any), what they are wearing on their upper body (if anything). Do not describe anything below their neck. Do not include descriptions of non-visual qualities such as personality, movements, scents, mental traits, or anything which could not be seen in a still photograph. Do not write in full sentences. Prefix your description with the phrase 'close up facial portrait,']"
},
"character_prompts": {}
},
"chromadb": {},
"translate": {
@@ -237,7 +258,15 @@
"provider": "google",
"auto_mode": "none"
},
"objective": {},
"objective": {
"customPrompts": {
"default": {
"createTask": "Pause your roleplay and generate a list of tasks to complete an objective. Your next response must be formatted as a numbered list of plain text entries. Do not include anything but the numbered list. The list must be prioritized in the order that tasks must be completed.\n\nThe objective that you must make a numbered task list for is: [{{objective}}].\nThe tasks created should take into account the character traits of {{char}}. These tasks may or may not involve {{user}} directly. Be sure to include the objective as the final task.\n\nGiven an example objective of 'Make me a four course dinner', here is an example output:\n1. Determine what the courses will be\n2. Find recipes for each course\n3. Go shopping for supplies with {{user}}\n4. Cook the food\n5. Get {{user}} to set the table\n6. Serve the food\n7. Enjoy eating the meal with {{user}}\n ",
"checkTaskCompleted": "Pause your roleplay. Determine if this task is completed: [{{task}}].\nTo do this, examine the most recent messages. Your response must only contain either true or false, nothing other words.\nExample output:\ntrue\n ",
"currentTask": "Your current task is [{{task}}]. Balance existing roleplay with completing this task."
}
}
},
"quickReply": {
"quickReplyEnabled": false,
"numberOfSlots": 5,
@@ -273,6 +302,14 @@
"controls": [],
"fluctuation": 0.1,
"enabled": false
},
"speech_recognition": {
"currentProvider": "None",
"messageMode": "append",
"messageMappingText": "",
"messageMapping": [],
"messageMappingEnabled": false,
"None": {}
}
},
"context_settings": {
@@ -296,33 +333,46 @@
"1345561466591"
]
},
"temp_novel": 1.11,
"rep_pen_novel": 1.11,
"rep_pen_size_novel": 320,
"model_novel": "euterpe-v2",
"preset_settings_novel": "Classic-Euterpe",
"streaming_novel": false,
"temp": 1,
"rep_pen": 1.1,
"rep_pen_range": 600,
"top_p": 0.95,
"top_a": 0,
"top_k": 0,
"typical": 1,
"tfs": 1,
"rep_pen_slope": 0,
"single_line": false,
"use_stop_sequence": false,
"streaming_kobold": false,
"sampler_order": [
6,
0,
1,
2,
3,
4,
5
],
"nai_settings": {
"temperature": 0.63,
"repetition_penalty": 1.148125,
"repetition_penalty_range": 2048,
"repetition_penalty_slope": 0.09,
"repetition_penalty_frequency": 0,
"repetition_penalty_presence": 0,
"tail_free_sampling": 0.975,
"top_k": 0,
"top_p": 0.975,
"top_a": 1,
"typical_p": 1,
"min_length": 1,
"model_novel": "euterpe-v2",
"preset_settings_novel": "Classic-Euterpe",
"streaming_novel": false
},
"kai_settings": {
"temp": 1,
"rep_pen": 1.1,
"rep_pen_range": 600,
"top_p": 0.95,
"top_a": 0,
"top_k": 0,
"typical": 1,
"tfs": 1,
"rep_pen_slope": 0,
"single_line": false,
"use_stop_sequence": false,
"streaming_kobold": false,
"sampler_order": [
6,
0,
1,
2,
3,
4,
5
]
},
"preset_settings_openai": "Default",
"temp_openai": "0.9",
"freq_pen_openai": 0.7,
@@ -374,5 +424,8 @@
"legacy_streaming": false,
"chat_completion_source": "openai",
"max_context_unlocked": false,
"api_url_scale": ""
"api_url_scale": "",
"show_external_models": false,
"proxy_password": "",
"assistant_prefill": ""
}

61
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "sillytavern",
"version": "1.9.3",
"version": "1.9.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "sillytavern",
"version": "1.9.3",
"version": "1.9.6",
"license": "AGPL-3.0",
"dependencies": {
"@dqbd/tiktoken": "^1.0.2",
@@ -42,7 +42,6 @@
"sentencepiece-js": "^1.1.0",
"simple-git": "^3.19.1",
"uniqolor": "^1.1.0",
"user-agents": "^1.0.1444",
"webp-converter": "2.3.2",
"ws": "^8.13.0",
"yargs": "^17.7.1",
@@ -1223,14 +1222,6 @@
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/detect-indent": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.0.0.tgz",
"integrity": "sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA==",
"engines": {
"node": ">=8"
}
},
"node_modules/detect-libc": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz",
@@ -1260,32 +1251,11 @@
"node": ">=8"
}
},
"node_modules/docopt": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/docopt/-/docopt-0.6.2.tgz",
"integrity": "sha512-NqTbaYeE4gA/wU1hdKFdU+AFahpDOpgGLzHP42k6H6DKExJd0A55KEVWYhL9FEmHmgeLvEU2vuKXDuU+4yToOw==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/dom-walk": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
"integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w=="
},
"node_modules/dot-json": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/dot-json/-/dot-json-1.3.0.tgz",
"integrity": "sha512-Pu11Prog/Yjf2lBICow82/DSV46n3a2XT1Rqt/CeuhkO1fuacF7xydYhI0SwQx2Ue0jCyLtQzgKPFEO6ewv+bQ==",
"dependencies": {
"detect-indent": "~6.0.0",
"docopt": "~0.6.2",
"underscore-keypath": "~0.0.22"
},
"bin": {
"dot-json": "bin/dot-json.js"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -2037,11 +2007,6 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
},
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@@ -3456,19 +3421,6 @@
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
},
"node_modules/underscore": {
"version": "1.13.6",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz",
"integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A=="
},
"node_modules/underscore-keypath": {
"version": "0.0.22",
"resolved": "https://registry.npmjs.org/underscore-keypath/-/underscore-keypath-0.0.22.tgz",
"integrity": "sha512-fU7aYj1J2LQd+jqdQ67AlCOZKK3Pl+VErS8fGYcgZG75XB9/bY+RLM+F2xEcKHhHNtLvqqFyXAoZQlLYfec3Xg==",
"dependencies": {
"underscore": "*"
}
},
"node_modules/uniqolor": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/uniqolor/-/uniqolor-1.1.0.tgz",
@@ -3491,15 +3443,6 @@
"node": ">= 0.8"
}
},
"node_modules/user-agents": {
"version": "1.0.1444",
"resolved": "https://registry.npmjs.org/user-agents/-/user-agents-1.0.1444.tgz",
"integrity": "sha512-6WXJ0RZuUKgif1rW5FN02HnpoJ8EzH6COQoXCiVStZEVPz+YnAx3iA48etY3ZD4UwueYN9ALC7j4ayHvYEh7tA==",
"dependencies": {
"dot-json": "^1.3.0",
"lodash.clonedeep": "^4.5.0"
}
},
"node_modules/utf8-byte-length": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz",

View File

@@ -33,7 +33,6 @@
"sentencepiece-js": "^1.1.0",
"simple-git": "^3.19.1",
"uniqolor": "^1.1.0",
"user-agents": "^1.0.1444",
"webp-converter": "2.3.2",
"ws": "^8.13.0",
"yargs": "^17.7.1",
@@ -51,9 +50,10 @@
"type": "git",
"url": "https://github.com/SillyTavern/SillyTavern.git"
},
"version": "1.9.3",
"version": "1.9.6",
"scripts": {
"start": "node server.js",
"start-multi": "node server.js --disableCsrf",
"pkg": "pkg --compress Gzip --no-bytecode --public ."
},
"bin": {

View File

@@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 100,
"temp": 1.15,
"top_k": 0,
"top_p": 0.95,

View File

@@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 90,
"temp": 0.8,
"top_k": 28,
"top_p": 0.94,

View File

@@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 100,
"temp": 0.59,
"top_k": 0,
"top_p": 1,

View File

@@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 100,
"temp": 0.8,
"top_k": 100,
"top_p": 0.9,

View File

@@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 180,
"temp": 1.0,
"top_p": 0.9,
"top_k": 40,

View File

@@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 180,
"temp": 0.43,
"top_p": 0.96,
"top_k": 0,

View File

@@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 180,
"temp": 0.65,
"top_p": 0.9,
"top_k": 0,

View File

@@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 100,
"temp": 0.51,
"top_p": 1,
"top_k": 0,

View File

@@ -1,6 +1,4 @@
{
"max_length": 1600,
"genamt": 180,
"temp": 0.79,
"top_k": 0,
"top_p": 0.9,

View File

@@ -0,0 +1,22 @@
{
"temp": 0,
"rep_pen": 1.1,
"rep_pen_range": 2048,
"streaming_kobold": true,
"top_p": 0,
"top_a": 0,
"top_k": 1,
"typical": 1,
"tfs": 1,
"rep_pen_slope": 0.2,
"single_line": false,
"sampler_order": [
6,
0,
1,
3,
4,
2,
5
]
}

View File

@@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 180,
"temp": 0.79,
"top_p": 0.9,
"top_k": 0,

View File

@@ -1,6 +1,4 @@
{
"max_length": 1400,
"genamt": 180,
"temp": 0.65,
"top_p": 0.9,
"top_k": 0,

View File

@@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 100,
"temp": 0.63,
"top_k": 0,
"top_p": 0.98,

View File

@@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 100,
"temp": 0.7,
"top_k": 0,
"top_p": 0.5,

View File

@@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 100,
"temp": 0.7,
"top_k": 0,
"top_p": 1,

View File

@@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 90,
"temp": 0.8,
"top_p": 0.94,
"top_k": 15,

View File

@@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 100,
"temp": 0.66,
"top_k": 0,
"top_p": 1,

View File

@@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 100,
"temp": 0.94,
"top_k": 12,
"top_p": 1,

View File

@@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 100,
"temp": 1.5,
"top_k": 85,
"top_p": 0.24,

View File

@@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 100,
"temp": 1.05,
"top_k": 0,
"top_p": 0.95,

View File

@@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 100,
"temp": 1.07,
"top_k": 100,
"top_p": 1,

View File

@@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 100,
"temp": 0.44,
"top_k": 0,
"top_p": 1,

View File

@@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 100,
"temp": 1.35,
"top_k": 0,
"top_p": 1,

View File

@@ -1,6 +1,4 @@
{
"max_length": 1400,
"genamt": 80,
"temp": 1,
"top_p": 1,
"top_k": 0,

View File

@@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 200,
"temp": 1,
"top_k": 0,
"top_p": 0.95,

View File

@@ -0,0 +1,22 @@
{
"temp": 0.72,
"rep_pen": 1.1,
"rep_pen_range": 4096,
"streaming_kobold": true,
"top_p": 0.73,
"top_a": 0,
"top_k": 0,
"typical": 1,
"tfs": 1,
"rep_pen_slope": 0.2,
"single_line": false,
"sampler_order": [
6,
0,
1,
3,
4,
2,
5
]
}

View File

@@ -1,6 +1,4 @@
{
"max_length": 2048,
"genamt": 100,
"temp": 0.72,
"tfs": 1,
"top_a": 0,

View File

@@ -0,0 +1,22 @@
{
"temp": 0.65,
"rep_pen": 1.18,
"rep_pen_range": 2048,
"streaming_kobold": true,
"top_p": 0.47,
"top_a": 0,
"top_k": 42,
"typical": 1,
"tfs": 1,
"rep_pen_slope": 0,
"single_line": false,
"sampler_order": [
6,
0,
1,
3,
4,
2,
5
]
}

View File

@@ -13,6 +13,7 @@
"use_cache": false,
"return_full_text": false,
"prefix": "vanilla",
"cfg_scale": 1,
"phrase_rep_pen": "aggressive",
"max_context": 8192
"max_context": 7800
}

View File

@@ -15,5 +15,5 @@
"prefix": "vanilla",
"phrase_rep_pen": "medium",
"cfg_scale": 1.55,
"max_context": 8192
"max_context": 7800
}

View File

@@ -16,5 +16,5 @@
"prefix": "vanilla",
"phrase_rep_pen": "very_aggressive",
"cfg_scale": 1.3,
"max_context": 8192
"max_context": 7800
}

View File

@@ -15,6 +15,7 @@
"use_cache": false,
"return_full_text": false,
"prefix": "vanilla",
"cfg_scale": 1,
"phrase_rep_pen": "aggressive",
"max_context": 8192
"max_context": 7800
}

View File

@@ -1,23 +0,0 @@
{
"order": [
1,
3,
4,
0,
2
],
"temperature": 1.05,
"max_length": 90,
"min_length": 1,
"tail_free_sampling": 0.989,
"repetition_penalty": 1.5,
"repetition_penalty_range": 8192,
"repetition_penalty_frequency": 0.03,
"repetition_penalty_presence": 0.005,
"repetition_penalty_slope": 0,
"top_a": 0.075,
"top_k": 79,
"top_p": 0.95,
"typical_p": 1,
"max_context": 8192
}

View File

@@ -15,6 +15,7 @@
"use_cache": false,
"return_full_text": false,
"prefix": "vanilla",
"cfg_scale": 1,
"phrase_rep_pen": "very_light",
"max_context": 8192
"max_context": 7800
}

View File

@@ -16,5 +16,7 @@
"use_cache": false,
"return_full_text": false,
"prefix": "vanilla",
"max_context": 8192
"cfg_scale": 1,
"phrase_rep_pen": "very_light",
"max_context": 7800
}

View File

@@ -1,5 +1,5 @@
{
"order": [6, 0, 1, 2, 3],
"order": [0, 1, 2, 3],
"temperature": 1,
"max_length": 300,
"min_length": 1,
@@ -14,6 +14,7 @@
"use_cache": false,
"return_full_text": false,
"prefix": "vanilla",
"cfg_scale": 1,
"phrase_rep_pen": "off",
"max_context": 8192
"max_context": 7800
}

View File

@@ -15,5 +15,5 @@
"prefix": "vanilla",
"phrase_rep_pen": "aggressive",
"cfg_scale": 1.825,
"max_context": 8192
"max_context": 7800
}

View File

@@ -16,5 +16,7 @@
"use_cache": false,
"return_full_text": false,
"prefix": "vanilla",
"max_context": 8192
"cfg_scale": 1,
"phrase_rep_pen": "very_light",
"max_context": 7800
}

View File

@@ -16,5 +16,7 @@
"use_cache": false,
"return_full_text": false,
"prefix": "vanilla",
"max_context": 8192
"cfg_scale": 1,
"phrase_rep_pen": "very_light",
"max_context": 7800
}

View File

@@ -18,5 +18,5 @@
"prefix": "vanilla",
"phrase_rep_pen": "medium",
"cfg_scale": 1.35,
"max_context": 8192
"max_context": 7800
}

View File

@@ -0,0 +1,19 @@
{
"order": [3, 4, 0],
"temperature": 1.19,
"max_length": 300,
"min_length": 1,
"top_a": 0.116,
"tail_free_sampling": 0.958,
"repetition_penalty": 1.64,
"repetition_penalty_slope": 2.12,
"repetition_penalty_frequency": 0,
"repetition_penalty_presence": 0,
"repetition_penalty_range": 2048,
"use_cache": false,
"return_full_text": false,
"prefix": "vanilla",
"phrase_rep_pen": "medium",
"cfg_scale": 1.0,
"max_context": 7800
}

View File

@@ -13,5 +13,5 @@
"return_full_text": false,
"prefix": "vanilla",
"phrase_rep_pen": "aggressive",
"max_context": 8192
"max_context": 7800
}

View File

@@ -16,6 +16,7 @@
"use_cache": false,
"return_full_text": false,
"prefix": "vanilla",
"cfg_scale": 1,
"phrase_rep_pen": "very_light",
"max_context": 8192
"max_context": 7800
}

View File

@@ -0,0 +1,19 @@
{
"order": [5, 0, 4],
"temperature": 1,
"max_length": 300,
"min_length": 1,
"top_a": 0.017,
"typical_p": 0.975,
"repetition_penalty": 3,
"repetition_penalty_slope": 0.09,
"repetition_penalty_frequency": 0,
"repetition_penalty_presence": 0,
"repetition_penalty_range": 7680,
"use_cache": false,
"return_full_text": false,
"prefix": "vanilla",
"phrase_rep_pen": "aggressive",
"cfg_scale": 1.0,
"max_context": 7800
}

View File

@@ -14,5 +14,5 @@
"prefix": "vanilla",
"phrase_rep_pen": "aggressive",
"cfg_scale": 1.3,
"max_context": 8192
"max_context": 7800
}

View File

@@ -16,5 +16,7 @@
"use_cache": false,
"return_full_text": false,
"prefix": "vanilla",
"max_context": 8192
"cfg_scale": 1,
"phrase_rep_pen": "very_light",
"max_context": 7800
}

View File

@@ -15,5 +15,5 @@
"return_full_text": false,
"prefix": "vanilla",
"phrase_rep_pen": "very_aggressive",
"max_context": 8192
"max_context": 7800
}

View File

@@ -0,0 +1,23 @@
{
"name": "Default",
"quickReplyEnabled": true,
"quickReplySlots": [
{
"mes": "/?",
"label": "HELP",
"enabled": true
},
{
"mes": "/newchat",
"label": "New Chat",
"enabled": true
},
{
"mes": "/bgcol",
"label": "Match UI to Background",
"enabled": true
}
],
"numberOfSlots": 3,
"selectedPreset": "Default"
}

View File

@@ -3,7 +3,7 @@
"top_p": 0.9,
"top_k": 20,
"typical_p": 1,
"top_a": 0.75,
"top_a": 0,
"tfs": 1,
"epsilon_cutoff": 0,
"eta_cutoff": 0,

View File

@@ -0,0 +1,23 @@
{
"temp": 0.65,
"top_p": 0.47,
"top_k": 42,
"typical_p": 1,
"top_a": 0,
"tfs": 1,
"epsilon_cutoff": 0,
"eta_cutoff": 0,
"rep_pen": 1.18,
"rep_pen_range": 0,
"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,
"mirostat_mode": 0,
"mirostat_tau": 5,
"mirostat_eta": 0.1
}

View File

@@ -1523,7 +1523,7 @@
"Continue": "이어서 계속",
"Editing:": "편집:",
"AI reply prefix": "AI 답변 접두사",
"Custom Stopping Strings": "문장출력 중단 문자열 (KoboldAI/TextGen)",
"Custom Stopping Strings": "문장출력 중단 문자열 (KoboldAI/TextGen/NovelAI)",
"JSON serialized array of strings": "JSON 연속 문자배열",
"words you dont want generated separated by comma ','": "답변에 포함을 막으려는 단어 (쉼표','로 구분)",
"Extensions URL": "확장기능 URL",

View File

@@ -147,7 +147,7 @@
<i data-preset-manager-update="kobold" class="menu_button fa-solid fa-save" title="Update current preset" data-i18n="[title]Update current preset"></i>
<i data-preset-manager-new="kobold" class="menu_button fa-solid fa-plus" title="Create new preset" data-i18n="[title]Create new preset"></i>
<i data-preset-manager-import="kobold" class="menu_button fa-solid fa-upload" title="Import preset" data-i18n="[title]Import preset"></i>
<i data-preset-manager-export="kobold" class="menu_button fa-solid fa-download"title="Export preset" data-i18n="[title]Export preset"></i>
<i data-preset-manager-export="kobold" class="menu_button fa-solid fa-download" title="Export preset" data-i18n="[title]Export preset"></i>
<i data-preset-manager-delete="kobold" class="menu_button fa-solid fa-trash-can" title="Delete the preset" data-i18n="[title]Delete the preset"></i>
</div>
</div>
@@ -187,7 +187,7 @@
<i data-preset-manager-update="textgenerationwebui" class="menu_button fa-solid fa-save" title="Update current preset" data-i18n="[title]Update current preset"></i>
<i data-preset-manager-new="textgenerationwebui" class="menu_button fa-solid fa-plus" title="Create new preset" data-i18n="[title]Create new preset"></i>
<i data-preset-manager-import="textgenerationwebui" class="menu_button fa-solid fa-upload" title="Import preset" data-i18n="[title]Import preset"></i>
<i data-preset-manager-export="textgenerationwebui" class="menu_button fa-solid fa-download"title="Export preset" data-i18n="[title]Export preset"></i>
<i data-preset-manager-export="textgenerationwebui" class="menu_button fa-solid fa-download" title="Export preset" data-i18n="[title]Export preset"></i>
<i data-preset-manager-delete="textgenerationwebui" class="menu_button fa-solid fa-trash-can" title="Delete the preset" data-i18n="[title]Delete the preset"></i>
</div>
</div>
@@ -253,7 +253,7 @@
</div>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input type="range" id="temp" name="volume" min="0.1" max="2.0" step="0.01">
<input type="range" id="temp" name="volume" min="0.0" max="2.0" step="0.01">
</div>
<div class="range-block-counter">
<div contenteditable="true" data-for="temp" id="temp_counter">
@@ -328,7 +328,7 @@
</div>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input type="range" id="temp_novel" name="volume" min="0.1" max="2.0" step="0.01">
<input type="range" id="temp_novel" name="volume" min="0.1" max="2.50" step="0.01">
</div>
<div class="range-block-counter">
<div contenteditable="true" data-for="temp_novel" id="temp_counter_novel">
@@ -342,7 +342,7 @@
</div>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input type="range" id="rep_pen_novel" name="volume" min="1" max="1.5" step="0.01">
<input type="range" id="rep_pen_novel" name="volume" min="1" max="8" step="0.05">
</div>
<div class="range-block-counter">
<div contenteditable="true" data-for="rep_pen_novel" id="rep_pen_counter_novel">
@@ -387,7 +387,7 @@
</div>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input type="range" id="rep_pen_freq_novel" name="volume" min="0" max="1" step="0.00001">
<input type="range" id="rep_pen_freq_novel" name="volume" min="0" max="1" step="0.01">
</div>
<div class="range-block-counter">
<div contenteditable="true" data-for="rep_pen_freq_novel" id="rep_pen_freq_counter_novel">
@@ -402,7 +402,7 @@
</div>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input type="range" id="rep_pen_presence_novel" name="volume" min="0" max="1" step="0.001">
<input type="range" id="rep_pen_presence_novel" name="volume" min="0" max="1" step="0.01">
</div>
<div class="range-block-counter">
<div contenteditable="true" data-for="rep_pen_presence_novel" id="rep_pen_presence_counter_novel">
@@ -807,6 +807,22 @@
</div>
</div>
<div id="novel_api-settings">
<div class="range-block">
<div class="range-block-title openai_restorable">
<span data-i18n="Preamble">Preamble</span>
<div id="nai_preamble_restore" title="Restore default prompt" data-i18n="[title]Restore default prompt"
class="right_menu_button">
<div class="fa-solid fa-clock-rotate-left "></div>
</div>
</div>
<div class="toggle-description justifyLeft" data-i18n="Use style tags to modify the writing style of the output">
Use style tags to modify the writing style of the output
</div>
<div class="wide100p">
<textarea id="nai_preamble_textarea" class="text_pole textarea_compact" name="nai_preamble" rows="2"
placeholder=""></textarea>
</div>
</div>
<div class="range-block">
<div class="range-block-title" data-i18n="Top P">
Top P
@@ -1319,6 +1335,10 @@
<textarea id="jailbreak_prompt_textarea" class="text_pole textarea_compact" name="jailbreak_prompt" rows="6" placeholder=""></textarea>
</div>
</div>
<div class="range-block" data-source="claude">
<span data-i18n="Assistant Prefill">Assistant Prefill</span>
<textarea id="claude_assistant_prefill" class="text_pole textarea_compact" name="assistant_prefill" rows="3" maxlength="5000" placeholder="Start Claude's answer with..."></textarea>
</div>
<div class="inline-drawer wide100p">
<div class="inline-drawer-toggle inline-drawer-header margin-bot-10px">
@@ -1531,30 +1551,55 @@
</div>
</div>
<div id="textgenerationwebui_api" style="display: none;position: relative;">
<div class="flex-container">
<a href="https://github.com/oobabooga/text-generation-webui" target="_blank">
oobabooga/text-generation-webui
</a>
<span data-i18n="Make sure you run it with">
Make sure you run it with <tt>--api</tt> flag
</span>
</div>
<div>
<div class="flex-container flexFlowColumn">
<div class="flex1">
<h4 data-i18n="Blocking API url">Blocking API url</h4>
<small>Example: http://127.0.0.1:5000/</small>
<input id="textgenerationwebui_api_url_text" name="textgenerationwebui_api_url" class="text_pole wide100p" maxlength="500" value="" autocomplete="off">
</div>
<div class="flex1">
<h4 data-i18n="Streaming API url">Streaming API url</h4>
<small>Example: ws://127.0.0.1:5005/api/v1/stream</small>
<input id="streaming_url_textgenerationwebui" type="text" class="text_pole wide100p" maxlength="500" value="" autocomplete="off">
<form action="javascript:void(null);" method="post" enctype="multipart/form-data">
If you are using:
<div class="flex-container indent20p">
<a href="https://github.com/oobabooga/text-generation-webui" target="_blank">
oobabooga/text-generation-webui
</a>
<span data-i18n="Make sure you run it with">
Make sure you run it with <tt>--api</tt> flag
</span>
</div>
<div class="flex-container indent20p">
<a href="https://mancer.tech/" target="_blank">
Mancer AI
</a>
<label class="checkbox_label" for="use-mancer-api-checkbox">
<span data-i18n="Use API key (Only required for Mancer)">
Click this box (and add your API key!):
</span>
<input id="use-mancer-api-checkbox" type="checkbox" />
</label>
</div>
<div id="mancer-api-ui" style="display:none;">
<h4 data-i18n="Mancer API key">Mancer API key</h4>
<div class="flex-container">
<input id="api_key_mancer" name="api_key_mancer" class="text_pole flex1 wide100p" maxlength="500" size="35" type="text" autocomplete="off">
<div title="Clear your API key" data-i18n="[title]Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_mancer">
</div>
</div>
</div>
<input id="api_button_textgenerationwebui" class="menu_button" type="submit" value="Connect">
<div id="api_loading_textgenerationwebui" class="api-load-icon fa-solid fa-hourglass fa-spin"></div>
</div>
<div>
<div class="flex-container flexFlowColumn">
<div data-for="api_key_mancer" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">
For privacy reasons, your API key will be hidden after you reload the page.
</div>
<div class="flex1">
<h4 data-i18n="Blocking API url">Blocking API url</h4>
<small>Example: http://127.0.0.1:5000/</small>
<input id="textgenerationwebui_api_url_text" name="textgenerationwebui_api_url" class="text_pole wide100p" maxlength="500" value="" autocomplete="off">
</div>
<div class="flex1">
<h4 data-i18n="Streaming API url">Streaming API url</h4>
<small>Example: ws://127.0.0.1:5005/api/v1/stream</small>
<input id="streaming_url_textgenerationwebui" type="text" class="text_pole wide100p" maxlength="500" value="" autocomplete="off">
</div>
</div>
<input id="api_button_textgenerationwebui" class="menu_button" type="submit" value="Connect">
<div id="api_loading_textgenerationwebui" class="api-load-icon fa-solid fa-hourglass fa-spin"></div>
</div>
</form>
<div class="online_status4">
<div class="online_status_indicator4"></div>
<div class="online_status_text4" data-i18n="Not connected">Not connected</div>
@@ -1801,7 +1846,7 @@
<span data-i18n="Trim Incomplete Sentences">Trim Incomplete Sentences</span>
</label>
<!-- Add margin since this is a child of above -->
<label style="margin-left: 1em;" class="checkbox_label" for="include_newline_checkbox">
<label class="checkbox_label indent20p" for="include_newline_checkbox">
<input id="include_newline_checkbox" type="checkbox" />
<span data-i18n="Include Newline">Include Newline</span>
</label>
@@ -1810,7 +1855,7 @@
Custom Chat Separator
</h4>
<div>
<input id="custom_chat_separator" class="text_pole textarea_compact" type="text" placeholder="&lt;START&gt;" maxlength="100" />
<textarea id="custom_chat_separator" class="text_pole textarea_compact" type="text" placeholder="&lt;START&gt;" maxlength="500" rows="1"></textarea>
</div>
</div>
<div>
@@ -1845,6 +1890,10 @@
<input id="instruct_names" type="checkbox" />
<span data-i18n="Include Names">Include Names</span>
</label>
<label for="instruct_names_force_groups" class="checkbox_label indent20p">
<input id="instruct_names_force_groups" type="checkbox" />
<span data-i18n="Force for Groups and Personas">Force for Groups and Personas</span>
</label>
</div>
<label for="instruct_presets">
<span data-i18n="Presets">Presets</span>
@@ -1863,7 +1912,7 @@
<span data-i18n="Input Sequence">Input Sequence</span>
</label>
<div>
<input id="instruct_input_sequence" class="text_pole textarea_compact" type="text" maxlength="100" />
<textarea id="instruct_input_sequence" class="text_pole textarea_compact" type="text" maxlength="500" rows="1"></textarea>
</div>
</div>
<div class="flex1">
@@ -1871,7 +1920,7 @@
<span data-i18n="Output Sequence">Output Sequence</span>
</label>
<div>
<input id="instruct_output_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="100" />
<textarea id="instruct_output_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="500" rows="1"></textarea>
</div>
</div>
</div>
@@ -1881,7 +1930,7 @@
<small data-i18n="System Sequence">System Sequence</small>
</label>
<div>
<input id="instruct_system_sequence" class="text_pole textarea_compact" type="text" maxlength="100" />
<textarea id="instruct_system_sequence" class="text_pole textarea_compact" type="text" maxlength="500" rows="1"></textarea>
</div>
</div>
<div class="flex1">
@@ -1889,7 +1938,7 @@
<small data-i18n="Stop Sequence">Stop Sequence</small>
</label>
<div>
<input id="instruct_stop_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="100" />
<textarea id="instruct_stop_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="500" rows="1"></textarea>
</div>
</div>
<div class="flex1">
@@ -1897,7 +1946,7 @@
<small data-i18n="Separator">Separator</small>
</label>
<div>
<input id="instruct_separator_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="100" />
<textarea id="instruct_separator_sequence" class="text_pole wide100p textarea_compact" type="text" maxlength="500" rows="1"></textarea>
</div>
</div>
</div>
@@ -1912,6 +1961,7 @@
</a>
</h4>
<select id="tokenizer">
<option value="99">Best match (recommended)</option>
<option value="0">None / Estimated</option>
<option value="1">GPT-3 (OpenAI)</option>
<option value="2">GPT-3 (Alternative / Classic)</option>
@@ -1976,7 +2026,7 @@
</label>
<h4>
<span data-i18n="Custom Stopping Strings">
Custom Stopping Strings (KoboldAI/TextGen)
Custom Stopping Strings (KoboldAI/TextGen/NovelAI)
</span>
<div>
<small>
@@ -1988,6 +2038,12 @@
<div>
<textarea id="custom_stopping_strings" rows="2" class="text_pole textarea_compact" placeholder="[&quot;Ford&quot;, &quot;BMW&quot;, &quot;Fiat&quot;]"></textarea>
</div>
<label class="checkbox_label" for="custom_stopping_strings_macro">
<input id="custom_stopping_strings_macro" type="checkbox" checked>
<span data-i18n="Replace Macro in Custom Stopping Strings">
Replace Macro in Custom Stopping Strings
</span>
</label>
<h4>
<span data-i18n="Pygmalion Formatting">
Pygmalion Formatting
@@ -2137,6 +2193,12 @@
Match whole words
</small>
</label>
<label title="Alert if your world info is greater than the allocated budget." data-i18n="[title]Alert if your world info is greater than the allocated budget." class="checkbox_label">
<input id="world_info_overflow_alert" type="checkbox" />
<small data-i18n="Alert On Overflow">
Alert On Overflow
</small>
</label>
</div>
</div>
@@ -2487,6 +2549,9 @@
<label for="spoiler_free_mode"><input id="spoiler_free_mode" type="checkbox" />
<span data-i18n="Spoiler Free Mode">Spoiler Free Mode</span>
</label>
<label for="relaxed_api_urls" title="Reduce the formatting requirements on API URLS"><input id="relaxed_api_urls" type="checkbox" />
<span data-i18n="Relaxed API URLS">Relaxed API URLS</span>
</label>
<div class="inline-drawer wide100p flexFlowColumn">
<div class="inline-drawer-toggle inline-drawer-header">
@@ -2609,16 +2674,24 @@
<option value="3" data-i18n="Bottom of Author's Note">Bottom of Author's Note</option>
</select>
</div>
<div class="range-block">
<label for="persona_show_notifications" class="checkbox_label">
<input id="persona_show_notifications" type="checkbox" />
<span data-i18n="Show Notifications Show notifications on switching personas">
Show notifications on switching personas
</span>
</label>
</div>
</div>
<div class="flex1">
<h4 data-i18n="Your Avatar" class="title_restorable">
<span>Your Persona</span>
<h4 class="title_restorable">
<span data-i18n="Your Persona">Your Persona</span>
<button class="menu_button menu_button_icon user_stats_button" title="Click for stats!">
<i class="fa-solid fa-circle-info"></i>Usage Stats
<i class="fa-solid fa-circle-info"></i><span data-i18n="Usage Stats">Usage Stats</span>
</button>
<div id="create_dummy_persona" class="menu_button menu_button_icon" title="Create a dummy persona" data-i18n="[title]Create a dummy persona">
<i class="fa-solid fa-person-circle-question fa-fw"></i>
<span>Blank</span>
<span data-i18n="Blank">Blank</span>
</div>
</h4>
<div id="user_avatar_block">
@@ -3246,7 +3319,7 @@
<div title="Rename chat file" class="renameChatButton fa-solid fa-pen" data-i18n="[title]Rename chat file"></div>
<div title="Export JSONL chat file" data-format="jsonl" class="exportRawChatButton fa-solid fa-file-export" data-i18n="[title]Export JSONL chat file"></div>
<div title="Download chat as plain text document" data-format="txt" class="exportChatButton fa-solid fa-file-lines" data-i18n="[title]Download chat as plain text document"></div>
<div title="Delete chat file" file_name="" class="PastChat_cross fa-solid fa-circle-xmark" data-i18n="[title]Delete chat file"></div>
<div title="Delete chat file" file_name="" class="PastChat_cross fa-solid fa-skull" data-i18n="[title]Delete chat file"></div>
</div>
</div>
</div>
@@ -3608,7 +3681,7 @@
</div>
<div id="hotswap_template" class="template_element">
<div class="hotswapAvatar">
<div class="hotswapAvatar" title="Add a character/group to favorites to display it here!">
<img src="/img/ai4.png">
</div>
</div>
@@ -3766,6 +3839,14 @@
<!-- popups live outside sheld to avoid blur conflicts -->
<div id="options" class="font-family-reset" style="display: none;">
<div class="options-content">
<a id="option_close_chat" class="displayNone">
<i class="fa-lg fa-solid fa-times"></i>
<span data-i18n="Close chat">Close chat</span>
</a>
<a id="option_settings" class="displayNone">
<i class="fa-lg fa-solid fa-cog"></i>
<span data-i18n="Toggle Panels">Toggle Panels</span>
</a>
<a id="option_toggle_AN">
<i class="fa-lg fa-solid fa-note-sticky"></i>
<span data-i18n="Author's Note">Author's Note</span>

View File

@@ -0,0 +1,12 @@
{
"input_sequence": "User: ",
"macro": true,
"name": "OpenOrca/OpenChat",
"names": true,
"output_sequence": "<|end_of_turn|>\nAssistant: ",
"separator_sequence": "<|end_of_turn|>\n",
"stop_sequence": "",
"system_prompt": "You are a helpful assistant. Please answer truthfully and write out your thinking step by step to be sure you get the right answer. If you make a mistake or encounter an error in your thinking, say so out loud and attempt to correct it. If you don't know or aren't sure about something, say so clearly. You will act as a professional logician, mathematician, and physicist. You will also act as the most appropriate type of expert to answer any particular question or solve the relevant problem; state which expert type your are, if so. Also think of any particular named expert that would be ideal to answer the relevant question or solve the relevant problem; name and act as them, if appropriate.\n",
"system_sequence": "",
"wrap": false
}

View File

@@ -0,0 +1,12 @@
{
"input_sequence": "### Instruction:",
"macro": true,
"name": "Roleplay",
"names": false,
"output_sequence": "### Response (2 paragraphs, engaging, natural, authentic, descriptive, creative):",
"separator_sequence": "",
"stop_sequence": "",
"system_prompt": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}.\n\nAvoid repetition, don't loop. Develop the plot slowly, always stay in character. Describe all actions in full, elaborate, explicit, graphic, and vivid detail. Mention all relevant sensory perceptions.\n\n### Input:",
"system_sequence": "",
"wrap": true
}

View File

@@ -17,6 +17,7 @@ import {
loadTextGenSettings,
generateTextGenWithStreaming,
getTextGenGenerationData,
formatTextGenURL,
} from "./scripts/textgen-settings.js";
import {
@@ -26,6 +27,7 @@ import {
getWorldInfoPrompt,
setWorldInfoSettings,
world_info_recursive,
world_info_overflow_alert,
world_info_case_sensitive,
world_info_match_whole_words,
world_names,
@@ -134,6 +136,7 @@ import {
download,
isDataURL,
getCharaFilename,
isDigitsOnly,
} from "./scripts/utils.js";
import { extension_settings, getContext, loadExtensionSettings, runGenerationInterceptors, saveMetadataDebounced } from "./scripts/extensions.js";
@@ -302,7 +305,6 @@ export const comment_avatar = "img/quill.png";
export let CLIENT_VERSION = 'SillyTavern:UNKNOWN:Cohee#1207'; // For Horde header
let is_colab = false;
let is_checked_colab = false;
let is_mes_reload_avatar = false;
let optionsPopper = Popper.createPopper(document.getElementById('options_button'), document.getElementById('options'), {
placement: 'top-start'
});
@@ -362,10 +364,10 @@ const system_messages = {
mes:
`Hello there! Please select the help topic you would like to learn more about:
<ul>
<li><a href="javascript:displayHelp('1')">Slash Commands</a> (or <tt>/help slash</tt>)</li>
<li><a href="javascript:displayHelp('2')">Formatting</a> (or <tt>/help format</tt>)</li>
<li><a href="javascript:displayHelp('3')">Hotkeys</a> (or <tt>/help hotkeys</tt>)</li>
<li><a href="javascript:displayHelp('4')">{{Macros}}</a> (or <tt>/help macros</tt>)</li>
<li><a href="#" data-displayHelp="1">Slash Commands</a> (or <tt>/help slash</tt>)</li>
<li><a href="#" data-displayHelp="2">Formatting</a> (or <tt>/help format</tt>)</li>
<li><a href="#" data-displayHelp="3">Hotkeys</a> (or <tt>/help hotkeys</tt>)</li>
<li><a href="#" data-displayHelp="4">{{Macros}}</a> (or <tt>/help macros</tt>)</li>
</ul>
<br><b>Still got questions left? The <a target="_blank" href="https://docs.sillytavern.app/">Official SillyTavern Documentation Website</a> has much more information!</b>`
},
@@ -407,12 +409,25 @@ const system_messages = {
mes:
`Text formatting commands:
<ul>
<li><tt>{{text}}</tt> - sets a one-time behavioral bias for the AI. Resets when you send the next message.</li>
<li><tt>*text*</tt> - displays as <i>italics</i></li>
<li><tt>**text**</tt> - displays as <b>bold</b></li>
<li><tt>***text***</tt> - displays as <b><i>bold italics</i></b></li>
<li><tt>` + "```" + `text` + "```" + `</tt> - displays as a code block</li>
<li><tt>` + "`" + `text` + "`" + `</tt> - displays as inline code</li>
<li><tt>` + "```" + `text` + "```" + `</tt> - displays as a code block (new lines allowed between the backticks)</li>
<pre>
<code>
like
this
</code>
</pre>
<li><tt>` + "`" + `text` + "`" + `</tt> - displays as <code>inline code</code></li>
<li><tt>` + "> " + `text` + `</tt> - displays as a blockquote (note the space after >)</li>
<blockquote>like this</blockquote>
<li><tt>` + "# " + `text` + `</tt> - displays as a large header (note the space)</li>
<h1>like this</h1>
<li><tt>` + "## " + `text` + `</tt> - displays as a medium header (note the space)</li>
<h2>like this</h2>
<li><tt>` + "### " + `text` + `</tt> - displays as a small header (note the space)</li>
<h3>like this</h3>
<li><tt>$$ text $$</tt> - renders a LaTeX formula (if enabled)</li>
<li><tt>$ text $</tt> - renders an AsciiMath formula (if enabled)</li>
</ul>`
@@ -522,7 +537,11 @@ const system_messages = {
$(document).ajaxError(function myErrorHandler(_, xhr) {
if (xhr.status == 403) {
toastr.warning("doubleCsrf errors in console are NORMAL in this case. Just reload the page or close this tab.", "Looks like you've opened SillyTavern in another browser tab", { timeOut: 0, extendedTimeOut: 0, preventDuplicates: true });
toastr.warning(
"doubleCsrf errors in console are NORMAL in this case. If you want to run ST in multiple tabs, start the server with --disableCsrf option.",
"Looks like you've opened SillyTavern in another browser tab",
{ timeOut: 0, extendedTimeOut: 0, preventDuplicates: true },
);
}
});
@@ -544,8 +563,27 @@ async function getClientVersion() {
}
}
function getTokenizerBestMatch() {
if (main_api === 'novel') {
if (nai_settings.model_novel.includes('krake') || nai_settings.model_novel.includes('euterpe')) {
return tokenizers.CLASSIC;
}
if (nai_settings.model_novel.includes('clio')) {
return tokenizers.NERD;
}
if (nai_settings.model_novel.includes('kayra')) {
return tokenizers.NERD2;
}
}
if (main_api === 'kobold' || main_api === 'textgenerationwebui' || main_api === 'koboldhorde') {
return tokenizers.LLAMA;
}
return power_user.NONE;
}
function getTokenCount(str, padding = undefined) {
if (typeof str !== 'string') {
if (typeof str !== 'string' || !str?.length) {
return 0;
}
@@ -561,6 +599,10 @@ function getTokenCount(str, padding = undefined) {
}
}
if (tokenizerType === tokenizers.BEST_MATCH) {
tokenizerType = getTokenizerBestMatch();
}
if (padding === undefined) {
padding = 0;
}
@@ -602,11 +644,42 @@ function countTokensRemote(endpoint, str, padding) {
return tokenCount + padding;
}
function getTextTokensRemote(endpoint, str) {
let ids = [];
jQuery.ajax({
async: false,
type: 'POST',
url: endpoint,
data: JSON.stringify({ text: str }),
dataType: "json",
contentType: "application/json",
success: function (data) {
ids = data.ids;
}
});
return ids;
}
export function getTextTokens(tokenizerType, str) {
switch (tokenizerType) {
case tokenizers.LLAMA:
return getTextTokensRemote('/tokenize_llama', str);
case tokenizers.NERD:
return getTextTokensRemote('/tokenize_nerdstash', str);
case tokenizers.NERD2:
return getTextTokensRemote('/tokenize_nerdstash_v2', str);
default:
console.warn("Calling getTextTokens with unsupported tokenizer type", tokenizerType);
return [];
}
}
function reloadMarkdownProcessor(render_formulas = false) {
if (render_formulas) {
converter = new showdown.Converter({
emoji: "true",
underline: "true",
parseImgDimensions: "true",
extensions: [
showdownKatex(
{
@@ -622,6 +695,7 @@ function reloadMarkdownProcessor(render_formulas = false) {
converter = new showdown.Converter({
emoji: "true",
literalMidWordUnderscores: "true",
parseImgDimensions: "true",
});
}
@@ -688,6 +762,7 @@ let is_get_status = false;
let is_get_status_novel = false;
let is_api_button_press = false;
let is_api_button_press_novel = false;
let api_use_mancer_webui = false;
let is_send_press = false; //Send generation
let add_mes_without_animation = false;
@@ -737,6 +812,9 @@ let token;
var PromptArrayItemForRawPromptDisplay;
export let active_character = ""
export let active_group = ""
export function getRequestHeaders() {
return {
"Content-Type": "application/json",
@@ -786,6 +864,14 @@ function checkOnlineStatus() {
}
}
export function setActiveCharacter(character) {
active_character = character;
}
export function setActiveGroup(group) {
active_group = group;
}
async function getStatus() {
if (is_get_status) {
if (main_api == "koboldhorde") {
@@ -810,9 +896,9 @@ async function getStatus() {
type: "POST", //
url: "/getstatus", //
data: JSON.stringify({
api_server:
main_api == "kobold" ? api_server : api_server_textgenerationwebui,
api_server: main_api == "kobold" ? api_server : api_server_textgenerationwebui,
main_api: main_api,
use_mancer: main_api == "textgenerationwebui" ? api_use_mancer_webui : false,
}),
beforeSend: function () { },
cache: false,
@@ -839,6 +925,11 @@ async function getStatus() {
kai_settings.can_use_streaming = canUseKoboldStreaming(data.koboldVersion);
}
// We didn't get a 200 status code, but the endpoint has an explanation. Which means it DID connect, but I digress.
if (online_status == "no_connection" && data.response) {
toastr.error(data.response, "API Error", { timeOut: 5000, preventDuplicates: true })
}
//console.log(online_status);
resultCheckStatus();
if (online_status !== "no_connection") {
@@ -1178,8 +1269,8 @@ function messageFormatting(mes, ch_name, isSystem, isUser) {
mes = fixMarkdown(mes);
}
if (this_chid != undefined && !isSystem)
mes = mes.replaceAll("<", "&lt;").replaceAll(">", "&gt;"); //for welcome message
//if (this_chid != undefined && !isSystem)
// mes = mes.replaceAll("<", "&lt;").replaceAll(">", "&gt;"); //for welcome message
if ((this_chid === undefined || this_chid === "invalid-safety-id") && !selected_group) {
mes = mes
.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>")
@@ -1221,7 +1312,7 @@ function messageFormatting(mes, ch_name, isSystem, isUser) {
*/
if (!power_user.allow_name2_display && ch_name && !isUser && !isSystem) {
mes = mes.replaceAll(`${ch_name}:`, "");
mes = mes.replace(new RegExp(`(^|\n)${ch_name}:`, 'g'), "$1");
}
//function to hide any <tags> from AI response output
@@ -1320,9 +1411,6 @@ function addOneMessage(mes, { type = "normal", insertAfter = null, scroll = true
const momentDate = timestampToMoment(mes.send_date);
const timestamp = momentDate.isValid() ? momentDate.format('LL LT') : '';
if (mes?.extra?.display_text) {
messageText = mes.extra.display_text;
}
@@ -1352,9 +1440,6 @@ function addOneMessage(mes, { type = "normal", insertAfter = null, scroll = true
} else {
if (characters[this_chid].avatar != "none") {
avatarImg = getThumbnailUrl('avatar', characters[this_chid].avatar);
if (is_mes_reload_avatar !== false) {
avatarImg += "&" + is_mes_reload_avatar;
}
} else {
avatarImg = default_avatar;
}
@@ -1602,7 +1687,7 @@ function getTimeSinceLastMessage() {
}
function randomReplace(input, emptyListPlaceholder = '') {
const randomPattern = /{{random:([^}]+)}}/gi;
const randomPattern = /{{random[ : ]([^}]+)}}/gi;
return input.replace(randomPattern, (match, listString) => {
const list = listString.split(',').map(item => item.trim()).filter(item => item.length > 0);
@@ -1620,10 +1705,15 @@ function randomReplace(input, emptyListPlaceholder = '') {
}
function diceRollReplace(input, invalidRollPlaceholder = '') {
const randomPattern = /{{roll:([^}]+)}}/gi;
const rollPattern = /{{roll[ : ]([^}]+)}}/gi;
return input.replace(rollPattern, (match, matchValue) => {
let formula = matchValue.trim();
if (isDigitsOnly(formula)) {
formula = `1d${formula}`;
}
return input.replace(randomPattern, (match, matchValue) => {
const formula = matchValue.trim();
const isValid = droll.validate(formula);
if (!isValid) {
@@ -1676,7 +1766,11 @@ function getStoppingStrings(isImpersonate, addSpace) {
if (power_user.custom_stopping_strings) {
const customStoppingStrings = getCustomStoppingStrings();
result.push(...customStoppingStrings);
if (power_user.custom_stopping_strings_macro) {
result.push(...customStoppingStrings.map(x => substituteParams(x, name1, name2)));
} else {
result.push(...customStoppingStrings);
}
}
return addSpace ? result.map(x => `${x} `) : result;
@@ -1756,7 +1850,7 @@ export function extractMessageBias(message) {
const match = curMatch[1].trim();
// Ignore random/roll pattern matches
if (/^random:.+/i.test(match) || /^roll:.+/i.test(match)) {
if (/^random[ : ].+/i.test(match) || /^roll[ : ].+/i.test(match)) {
continue;
}
@@ -1812,7 +1906,7 @@ function getPersonaDescription(storyString) {
case persona_description_positions.BEFORE_CHAR:
return `${substituteParams(power_user.persona_description)}\n${storyString}`;
case persona_description_positions.AFTER_CHAR:
return `${storyString}\n${substituteParams(power_user.persona_description)}`;
return `${storyString}${substituteParams(power_user.persona_description)}\n`;
default:
if (shouldWIAddPrompt) {
const originalAN = extension_prompts[NOTE_MODULE_NAME].value
@@ -2475,15 +2569,15 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
}
if (i === arrMes.length - 1 && !item.trim().startsWith(name1 + ":")) {
if (textareaText == "") {
// Cohee: I think this was added to allow the model to continue
// where it left off by removing the trailing newline at the end
// that was added by chat2 generator. This causes problems with
// instruct mode that could not have a trailing newline. So we're
// removing a newline ONLY at the end of the string if it exists.
item = item.replace(/\n?$/, '');
//item = item.substr(0, item.length - 1);
}
//if (textareaText == "") {
// Cohee: I think this was added to allow the model to continue
// where it left off by removing the trailing newline at the end
// that was added by chat2 generator. This causes problems with
// instruct mode that could not have a trailing newline. So we're
// removing a newline ONLY at the end of the string if it exists.
item = item.replace(/\n?$/, '');
//item = item.substr(0, item.length - 1);
//}
}
if (is_pygmalion && !isInstruct) {
if (item.trim().startsWith(name1)) {
@@ -2618,8 +2712,11 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
setPromtString();
}
// add chat preamble
mesSendString = addChatsPreamble(mesSendString);
// add a custom dingus (if defined)
mesSendString = adjustChatsSeparator(mesSendString);
mesSendString = addChatsSeparator(mesSendString);
let finalPromt =
storyString +
@@ -2672,10 +2769,11 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
}
else if (main_api == 'textgenerationwebui') {
generate_data = getTextGenGenerationData(finalPromt, this_amount_gen, isImpersonate);
generate_data.use_mancer = api_use_mancer_webui;
}
else if (main_api == 'novel') {
const this_settings = novelai_settings[novelai_setting_names[nai_settings.preset_settings_novel]];
generate_data = getNovelGenerationData(finalPromt, this_settings, this_amount_gen);
generate_data = getNovelGenerationData(finalPromt, this_settings, this_amount_gen, isImpersonate);
}
else if (main_api == 'openai') {
let [prompt, counts] = await prepareOpenAIMessages({
@@ -2777,7 +2875,8 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
});
if (!response.ok) {
throw new Error(response.status);
const error = await response.json();
throw error;
}
const data = await response.json();
@@ -2933,6 +3032,13 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
activateSendButtons();
//console.log('runGenerate calling showSwipeBtns');
showSwipeButtons();
if (main_api == 'textgenerationwebui' && api_use_mancer_webui) {
const errorText = `<h3>Inferencer endpoint is unhappy!</h3>
Returned status <tt>${data.status}</tt> with the reason:<br/>
${data.response}`;
callPopup(errorText, 'text');
}
}
console.debug('/savechat called by /Generate');
@@ -2947,6 +3053,10 @@ async function Generate(type, { automatic_trigger, force_name2, resolve, reject,
};
function onError(exception) {
if (typeof exception?.error?.message === 'string') {
toastr.error(exception.error.message, 'Error', { timeOut: 10000, extendedTimeOut: 20000 });
}
reject(exception);
$("#send_textarea").removeAttr('disabled');
is_send_press = false;
@@ -3092,7 +3202,12 @@ function parseTokenCounts(counts, thisPromptBits) {
});
}
function adjustChatsSeparator(mesSendString) {
function addChatsPreamble(mesSendString) {
const preamble = main_api === 'novel' ? nai_settings.preamble : "";
return preamble + '\n' + mesSendString;
}
function addChatsSeparator(mesSendString) {
if (power_user.custom_chat_separator && power_user.custom_chat_separator.length) {
mesSendString = power_user.custom_chat_separator + '\n' + mesSendString;
}
@@ -3102,6 +3217,10 @@ function adjustChatsSeparator(mesSendString) {
mesSendString = mesSendString;
}
else if (main_api === 'novel') {
mesSendString = '\n***\n' + mesSendString;
}
// add non-pygma dingus
else if (!is_pygmalion) {
mesSendString = '\nThen the roleplay chat between ' + name1 + ' and ' + name2 + ' begins.\n' + mesSendString;
@@ -3215,21 +3334,20 @@ function promptItemize(itemizedPrompts, requestedMesId) {
}
//these happen regardless of API
var charPersonalityTokens = getTokenCount(itemizedPrompts[thisPromptSet].charPersonality);
var charDescriptionTokens = getTokenCount(itemizedPrompts[thisPromptSet].charDescription);
var charPersonalityTokens = getTokenCount(itemizedPrompts[thisPromptSet].charPersonality);
var scenarioTextTokens = getTokenCount(itemizedPrompts[thisPromptSet].scenarioText);
var userPersonaStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].userPersona);
var worldInfoStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].worldInfoString);
var allAnchorsTokens = getTokenCount(itemizedPrompts[thisPromptSet].allAnchors);
var summarizeStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].summarizeString);
var authorsNoteStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].authorsNoteString);
var smartContextStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].smartContextString);
var afterScenarioAnchorTokens = getTokenCount(itemizedPrompts[thisPromptSet].afterScenarioAnchor);
var zeroDepthAnchorTokens = getTokenCount(itemizedPrompts[thisPromptSet].zeroDepthAnchor);
var worldInfoStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].worldInfoString);
var thisPrompt_max_context = itemizedPrompts[thisPromptSet].this_max_context;
var thisPrompt_padding = itemizedPrompts[thisPromptSet].padding;
var promptBiasTokens = getTokenCount(itemizedPrompts[thisPromptSet].promptBias);
var this_main_api = itemizedPrompts[thisPromptSet].main_api;
var userPersonaStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].userPersona);
if (this_main_api == 'openai') {
//for OAI API
@@ -3271,6 +3389,7 @@ function promptItemize(itemizedPrompts, requestedMesId) {
var mesSendStringTokens = getTokenCount(itemizedPrompts[thisPromptSet].mesSendString)
var ActualChatHistoryTokens = mesSendStringTokens - (allAnchorsTokens - afterScenarioAnchorTokens) + power_user.token_padding;
var instructionTokens = getTokenCount(itemizedPrompts[thisPromptSet].instruction);
var promptBiasTokens = getTokenCount(itemizedPrompts[thisPromptSet].promptBias);
var totalTokensInPrompt =
storyStringTokens + //chardefs total
@@ -3708,13 +3827,15 @@ function cleanUpMessage(getMessage, isImpersonate, isContinue, displayIncomplete
getMessage = cleanGroupMessage(getMessage);
}
if (!power_user.allow_name2_display) {
getMessage = getMessage.replace(new RegExp(`(^|\n)${name2}:`, 'g'), "$1");
}
if (isImpersonate) {
getMessage = getMessage.trim();
}
const stoppingStrings = getStoppingStrings(isImpersonate, false);
//console.log('stopping on these strings: ');
//console.log(stoppingStrings);
for (const stoppingString of stoppingStrings) {
if (stoppingString.length) {
@@ -4149,9 +4270,18 @@ async function read_avatar_load(input) {
return;
}
$("#create_button").trigger('click');
await createOrEditCharacter();
await delay(durationSaveEdit);
const formData = new FormData($("#form_create").get(0));
await fetch(getThumbnailUrl('avatar', formData.get('avatar_url')), {
method: 'GET',
cache: 'no-cache',
headers: {
'pragma': 'no-cache',
'cache-control': 'no-cache',
}
});
$(".mes").each(async function () {
if ($(this).attr("is_system") == 'true') {
@@ -4169,22 +4299,12 @@ async function read_avatar_load(input) {
}
});
await delay(durationSaveEdit);
await fetch(getThumbnailUrl('avatar', formData.get('avatar_url')), {
method: 'GET',
cache: 'no-cache',
headers: {
'pragma': 'no-cache',
'cache-control': 'no-cache',
}
});
console.log('Avatar refreshed');
}
}
export function getCropPopup(src) {
return `<h3>Set the crop position of the avatar image and click Ok to confirm.</h3>
return `<h3>Set the crop position of the avatar image and click Accept to confirm.</h3>
<div id='avatarCropWrap'>
<img id='avatarToCrop' src='${src}'>
</div>`;
@@ -4497,7 +4617,9 @@ export function setUserName(value) {
name1 = default_user_name;
console.log(`User name changed to ${name1}`);
$("#your_name").val(name1);
toastr.success(`Your messages will now be sent as ${name1}`, 'Current persona updated');
if (power_user.persona_show_notifications) {
toastr.success(`Your messages will now be sent as ${name1}`, 'Current persona updated');
}
saveSettings("change_name");
}
@@ -4596,7 +4718,7 @@ function setUserAvatar() {
const personaName = power_user.personas[user_avatar];
if (personaName && name1 !== personaName) {
const lockedPersona = chat_metadata['persona'];
if (lockedPersona && lockedPersona !== user_avatar) {
if (lockedPersona && lockedPersona !== user_avatar && power_user.persona_show_notifications) {
toastr.info(
`To permanently set "${personaName}" as the selected persona, unlock and relock it using the "Lock" button. Otherwise, the selection resets upon reloading the chat.`,
`This chat is locked to a different persona (${power_user.personas[lockedPersona]}).`,
@@ -4703,7 +4825,9 @@ async function setDefaultPersona() {
}
console.log(`Removing default persona ${avatarId}`);
toastr.info('This persona will no longer be used by default when you open a new chat.', `Default persona removed`);
if (power_user.persona_show_notifications) {
toastr.info('This persona will no longer be used by default when you open a new chat.', `Default persona removed`);
}
delete power_user.default_persona;
} else {
const confirm = await callPopup(`<h3>Are you sure you want to set "${personaName}" as the default persona?</h3>
@@ -4715,7 +4839,9 @@ async function setDefaultPersona() {
}
power_user.default_persona = avatarId;
toastr.success('This persona will be used by default when you open a new chat.', `Default persona set to ${personaName}`);
if (power_user.persona_show_notifications) {
toastr.success('This persona will be used by default when you open a new chat.', `Default persona set to ${personaName}`);
}
}
saveSettingsDebounced();
@@ -4778,18 +4904,22 @@ function lockUserNameToChat() {
console.log(`Unlocking persona for this chat ${chat_metadata['persona']}`);
delete chat_metadata['persona'];
saveMetadata();
toastr.info('User persona is now unlocked for this chat. Click the "Lock" again to revert.', 'Persona unlocked');
if (power_user.persona_show_notifications) {
toastr.info('User persona is now unlocked for this chat. Click the "Lock" again to revert.', 'Persona unlocked');
}
updateUserLockIcon();
return;
}
if (!(user_avatar in power_user.personas)) {
console.log(`Creating a new persona ${user_avatar}`);
toastr.info(
'Creating a new persona for currently selected user name and avatar...',
'Persona not set for this avatar',
{ timeOut: 10000, extendedTimeOut: 20000, },
);
if (power_user.persona_show_notifications) {
toastr.info(
'Creating a new persona for currently selected user name and avatar...',
'Persona not set for this avatar',
{ timeOut: 10000, extendedTimeOut: 20000, },
);
}
power_user.personas[user_avatar] = name1;
power_user.persona_descriptions[user_avatar] = { description: '', position: persona_description_positions.BEFORE_CHAR };
}
@@ -4798,7 +4928,9 @@ function lockUserNameToChat() {
saveMetadata();
saveSettingsDebounced();
console.log(`Locking persona for this chat ${user_avatar}`);
toastr.success(`User persona is locked to ${name1} in this chat`);
if (power_user.persona_show_notifications) {
toastr.success(`User persona is locked to ${name1} in this chat`);
}
updateUserLockIcon();
}
@@ -5009,17 +5141,23 @@ async function getSettings(type) {
highlightSelectedAvatar();
setPersonaDescription();
//Load the active character and group
active_character = settings.active_character;
active_group = settings.active_group;
//Load the API server URL from settings
api_server = settings.api_server;
$("#api_url_text").val(api_server);
setWorldInfoSettings(settings, data);
api_server_textgenerationwebui =
settings.api_server_textgenerationwebui;
api_server_textgenerationwebui = settings.api_server_textgenerationwebui;
$("#textgenerationwebui_api_url_text").val(
api_server_textgenerationwebui
);
api_use_mancer_webui = settings.api_use_mancer_webui
$('#use-mancer-api-checkbox').prop("checked", api_use_mancer_webui);
$('#use-mancer-api-checkbox').trigger("change");
selected_button = settings.selected_button;
@@ -5049,8 +5187,11 @@ async function saveSettings(type) {
data: JSON.stringify({
firstRun: firstRun,
username: name1,
active_character: active_character,
active_group: active_group,
api_server: api_server,
api_server_textgenerationwebui: api_server_textgenerationwebui,
api_use_mancer_webui: api_use_mancer_webui,
preset_settings: preset_settings,
user_avatar: user_avatar,
amount_gen: amount_gen,
@@ -5060,6 +5201,7 @@ async function saveSettings(type) {
world_info_depth: world_info_depth,
world_info_budget: world_info_budget,
world_info_recursive: world_info_recursive,
world_info_overflow_alert: world_info_overflow_alert,
world_info_case_sensitive: world_info_case_sensitive,
world_info_match_whole_words: world_info_match_whole_words,
world_info_character_strategy: world_info_character_strategy,
@@ -6812,6 +6954,11 @@ function importCharacter(file) {
contentType: false,
processData: false,
success: async function (data) {
if (data.error) {
toastr.error('The file is likely invalid or corrupted.', 'Could not import character');
return;
}
if (data.file_name !== undefined) {
$('#character_search_bar').val('').trigger('input');
$("#rm_info_block").transition({ opacity: 0, duration: 0 });
@@ -6893,6 +7040,10 @@ function doCharListDisplaySwitch() {
updateVisibleDivs('#rm_print_characters_block', true);
}
function doCloseChat() {
$("#option_close_chat").trigger('click')
}
/**
* Function to handle the deletion of a character, given a specific popup type and character ID.
* If popup type equals "del_ch", it will proceed with deletion otherwise it will exit the function.
@@ -6902,13 +7053,13 @@ function doCharListDisplaySwitch() {
*
* @param {string} popup_type - The type of popup currently active.
* @param {string} this_chid - The character ID to be deleted.
* @param {boolean} delete_chats - Whether to delete chats or not.
*/
export async function handleDeleteCharacter(popup_type, this_chid) {
export async function handleDeleteCharacter(popup_type, this_chid, delete_chats) {
if (popup_type !== "del_ch") {
return;
}
const delete_chats = !!$("#del_char_checkbox").prop("checked");
const avatar = characters[this_chid].avatar;
const name = characters[this_chid].name;
@@ -6959,6 +7110,10 @@ export async function deleteCharacter(name, avatar) {
saveSettingsDebounced();
}
function doTogglePanels() {
$("#option_settings").trigger('click')
}
$(document).ready(function () {
@@ -6973,6 +7128,9 @@ $(document).ready(function () {
registerSlashCommand('api', connectAPISlash, [], "(kobold, horde, novel, ooba, oai, claude, windowai) connect to an API", true, true);
registerSlashCommand('impersonate', doImpersonate, ['imp'], "- calls an impersonation response", true, true);
registerSlashCommand('delchat', doDeleteChat, [], "- deletes the current chat", true, true);
registerSlashCommand('closechat', doCloseChat, [], "- closes the current chat", true, true);
registerSlashCommand('panels', doTogglePanels, ['togglepanels'], "- toggle UI panels on/off", true, true);
setTimeout(function () {
@@ -7339,7 +7497,8 @@ $(document).ready(function () {
}, 200);
}
if (popup_type == "del_ch") {
handleDeleteCharacter(popup_type, this_chid, characters);
const deleteChats = !!$("#del_char_checkbox").prop("checked");
await handleDeleteCharacter(popup_type, this_chid, deleteChats);
}
if (popup_type == "alternate_greeting" && menu_type !== "create") {
createOrEditCharacter();
@@ -7416,7 +7575,6 @@ $(document).ready(function () {
});
$("#add_avatar_button").change(function () {
is_mes_reload_avatar = Date.now();
read_avatar_load(this);
});
@@ -7428,7 +7586,7 @@ $(document).ready(function () {
<h3>Delete the character?</h3>
<b>THIS IS PERMANENT!<br><br>
<label for="del_char_checkbox" class="checkbox_label justifyCenter">
<input type="checkbox" id="del_char_checkbox" checked />
<input type="checkbox" id="del_char_checkbox" />
<span>Also delete the chat files</span>
</label><br></b>`
);
@@ -7611,16 +7769,28 @@ $(document).ready(function () {
}
});
$("#api_button_textgenerationwebui").click(function (e) {
$("#use-mancer-api-checkbox").on("change", function (e) {
const enabled = $("#use-mancer-api-checkbox").prop("checked");
$("#mancer-api-ui").toggle(enabled);
api_use_mancer_webui = enabled;
saveSettingsDebounced();
getStatus();
});
$("#api_button_textgenerationwebui").click(async function (e) {
e.stopPropagation();
if ($("#textgenerationwebui_api_url_text").val() != "") {
let value = formatKoboldUrl($("#textgenerationwebui_api_url_text").val().trim());
let value = formatTextGenURL($("#textgenerationwebui_api_url_text").val().trim(), api_use_mancer_webui);
if (!value) {
callPopup('Please enter a valid URL.', 'text');
callPopup("Please enter a valid URL.<br/>WebUI URLs should end with <tt>/api</tt><br/>Enable 'Relaxed API URLs' to allow other paths.", 'text');
return;
}
const mancer_key = $("#api_key_mancer").val().trim();
if (mancer_key.length) {
await writeSecret(SECRET_KEYS.MANCER, mancer_key);
}
$("#textgenerationwebui_api_url_text").val(value);
$("#api_loading_textgenerationwebui").css("display", "inline-block");
$("#api_button_textgenerationwebui").css("display", "none");
@@ -7732,6 +7902,51 @@ $(document).ready(function () {
else if (id == "option_delete_mes") {
setTimeout(openMessageDelete, animation_duration);
}
else if (id == "option_close_chat") {
if (is_send_press == false) {
clearChat();
chat.length = 0;
resetSelectedGroup();
setCharacterId(undefined);
setCharacterName('');
setActiveCharacter(null);
setActiveGroup(null);
this_edit_mes_id = undefined;
chat_metadata = {};
selected_button = "characters";
$("#rm_button_selected_ch").children("h2").text('');
select_rm_characters();
sendSystemMessage(system_message_types.WELCOME);
} else {
toastr.info("Please stop the message generation first.");
}
}
else if (id === "option_settings") {
//var checkBox = document.getElementById("waifuMode");
var topBar = document.getElementById("top-bar");
var topSettingsHolder = document.getElementById("top-settings-holder");
var divchat = document.getElementById("chat");
//if (checkBox.checked) {
if (topBar.style.display === "none") {
topBar.style.display = ""; // or "inline-block" if that's the original display value
topSettingsHolder.style.display = ""; // or "inline-block" if that's the original display value
divchat.style.borderRadius = "";
divchat.style.backgroundColor = "";
} else {
divchat.style.borderRadius = "10px"; // Adjust the value to control the roundness of the corners
divchat.style.backgroundColor = ""; // Set the background color to your preference
topBar.style.display = "none";
topSettingsHolder.style.display = "none";
}
//}
}
hideMenu();
});

View File

@@ -13,6 +13,10 @@ import {
menu_type,
max_context,
saveSettingsDebounced,
active_group,
active_character,
setActiveGroup,
setActiveCharacter,
} from "../script.js";
import {
@@ -330,11 +334,21 @@ export function RA_CountCharTokens() {
characterStatsHandler(characters, this_chid);
});
}
//Auto Load Last Charcter -- (fires when active_character is defined and auto_load_chat is true)
/**
* Auto load chat with the last active character or group.
* Fires when active_character is defined and auto_load_chat is true.
* The function first tries to find a character with a specific ID from the global settings.
* If it doesn't exist, it tries to find a group with a specific grid from the global settings.
* If the character list hadn't been loaded yet, it calls itself again after 100ms delay.
* The character or group is selected (clicked) if it is found.
*/
async function RA_autoloadchat() {
if (document.getElementById('CharID0') !== null) {
var charToAutoLoad = document.getElementById('CharID' + LoadLocal('ActiveChar'));
let groupToAutoLoad = document.querySelector(`.group_select[grid="${LoadLocal('ActiveGroup')}"]`);
// active character is the name, we should look it up in the character list and get the id
let active_character_id = Object.keys(characters).find(key => characters[key].avatar === active_character);
var charToAutoLoad = document.getElementById('CharID' + active_character_id);
let groupToAutoLoad = document.querySelector(`.group_select[grid="${active_group}"]`);
if (charToAutoLoad != null) {
$(charToAutoLoad).click();
}
@@ -342,7 +356,7 @@ async function RA_autoloadchat() {
$(groupToAutoLoad).click();
}
// if the charcter list hadn't been loaded yet, try again.
// if the character list hadn't been loaded yet, try again.
} else { setTimeout(RA_autoloadchat, 100); }
}
@@ -366,6 +380,7 @@ export async function favsToHotswap() {
thisHotSwapSlot.attr('grid', isGroup ? grid : '');
thisHotSwapSlot.attr('chid', isCharacter ? chid : '');
thisHotSwapSlot.data('id', isGroup ? grid : chid);
thisHotSwapSlot.attr('title', '');
if (isGroup) {
const group = groups.find(x => x.id === grid);
@@ -520,12 +535,14 @@ export function dragElement(elmnt) {
if (elmntHeader.length) {
elmntHeader.off('mousedown').on('mousedown', (e) => {
hasBeenDraggedByUser = true
observer.observe(elmnt.get(0), { attributes: true, attributeFilter: ['style'] });
dragMouseDown(e);
});
$(elmnt).off('mousedown').on('mousedown', () => { isMouseDown = true })
} else {
elmnt.off('mousedown').on('mousedown', dragMouseDown);
$(elmnt).off('mousedown').on('mousedown', () => {
isMouseDown = true
observer.observe(elmnt.get(0), { attributes: true, attributeFilter: ['style'] });
})
}
const observer = new MutationObserver((mutations) => {
@@ -604,7 +621,7 @@ export function dragElement(elmnt) {
}
//prevent resizing from top left into the top bar
if (top <= 40 && maxX >= topBarFirstX && left <= topBarFirstX
if (top < 40 && maxX >= topBarFirstX && left <= topBarFirstX
) {
console.debug('prevent topbar underlap resize')
elmnt.css('width', width - 1 + "px");
@@ -660,8 +677,6 @@ export function dragElement(elmnt) {
}
});
observer.observe(elmnt.get(0), { attributes: true, attributeFilter: ['style'] });
function dragMouseDown(e) {
if (e) {
@@ -731,6 +746,7 @@ export function dragElement(elmnt) {
$("body").css("overflow", "");
// Clear the "data-dragged" attribute
elmnt.attr('data-dragged', 'false');
observer.disconnect()
console.debug(`Saving ${elmntName} UI position`)
saveSettingsDebounced();
@@ -903,16 +919,22 @@ $("document").ready(function () {
$("#rm_button_characters").click(function () { SaveLocal('SelectedNavTab', 'rm_button_characters'); });
// when a char is selected from the list, save them as the auto-load character for next page load
// when a char is selected from the list, save their name as the auto-load character for next page load
$(document).on("click", ".character_select", function () {
SaveLocal('ActiveChar', $(this).attr('chid'));
SaveLocal('ActiveGroup', null);
setActiveCharacter($(this).find('.avatar').attr('title'));
setActiveGroup(null);
saveSettingsDebounced();
});
$(document).on("click", ".group_select", function () {
SaveLocal('ActiveChar', null);
SaveLocal('ActiveGroup', $(this).data('id'));
setActiveCharacter(null);
setActiveGroup($(this).data('id'));
saveSettingsDebounced();
});
//this makes the chat input text area resize vertically to match the text size (limited by CSS at 50% window height)
$('#send_textarea').on('input', function () {
this.style.height = '40px';
@@ -967,6 +989,12 @@ $("document").ready(function () {
Generate();
}
}
if ($(':focus').attr('id') === 'dialogue_popup_input' && !isMobile()) {
if (!event.shiftKey && !event.ctrlKey && event.key == "Enter") {
event.preventDefault();
$('#dialogue_popup_ok').trigger('click');
}
}
//ctrl+shift+up to scroll to context line
if (event.shiftKey && event.ctrlKey && event.key == "ArrowUp") {
event.preventDefault();
@@ -1028,11 +1056,10 @@ $("document").ready(function () {
}
if (event.ctrlKey && event.key == "ArrowUp") { //edits last USER message if chatbar is empty and focused
console.debug('got ctrl+uparrow input');
if (
$("#send_textarea").val() === '' &&
chatbarInFocus === true &&
$(".swipe_right:last").css('display') === 'flex' &&
($(".swipe_right:last").css('display') === 'flex' || $('.last_mes').attr('is_system') === 'true') &&
$("#character_popup").css("display") === "none" &&
$("#shadow_select_chat_popup").css("display") === "none"
) {
@@ -1040,7 +1067,7 @@ $("document").ready(function () {
const lastIsUserMes = isUserMesList[isUserMesList.length - 1];
const editMes = lastIsUserMes.querySelector('.mes_block .mes_edit');
if (editMes !== null) {
$(editMes).click();
$(editMes).trigger('click');
}
}
}
@@ -1061,5 +1088,65 @@ $("document").ready(function () {
}
}
}
if (event.key == "Escape") { //closes various panels
if ($("#curEditTextarea").is(":visible")) {
return
}
if ($("#dialogue_popup").is(":visible")) {
if ($("#dialogue_popup_cancel").is(":visible")) {
$("#dialogue_popup_cancel").trigger('click');
return
} else {
$("#dialogue_popup_ok").trigger('click')
return
}
}
if ($("#select_chat_popup").is(":visible")) {
$("#select_chat_cross").trigger('click');
return
}
if ($("#character_popup").is(":visible")) {
$("#character_cross").trigger('click');
return
}
if ($(".drawer-content")
.not('#WorldInfo')
.not('#left-nav-panel')
.not('#right-nav-panel')
.is(":visible")) {
let visibleDrawerContent = $(".drawer-content:visible")
.not('#WorldInfo')
.not('#left-nav-panel')
.not('#right-nav-panel')
$(visibleDrawerContent).parent().find('.drawer-icon').trigger('click');
return
}
if ($("#floatingPrompt").is(":visible")) {
$("#ANClose").trigger('click');
return
}
if ($("#WorldInfo").is(":visible")) {
$("#WIDrawerIcon").trigger('click');
return
}
if ($("#left-nav-panel").is(":visible")) {
$("#leftNavDrawerIcon").trigger('click');
return
}
if ($("#right-nav-panel").is(":visible")) {
$("#rightNavDrawerIcon").trigger('click');
return
}
}
if (event.ctrlKey && /^[1-9]$/.test(event.key)) {
// Your code here
event.preventDefault();
console.log("Ctrl +" + event.key + " pressed!");
}
}
});

View File

@@ -0,0 +1,117 @@
import { characters, getCharacters, handleDeleteCharacter, callPopup } from "../../../script.js";
let is_bulk_edit = false;
/**
* Toggles bulk edit mode on/off when the edit button is clicked.
*/
function onEditButtonClick() {
console.log("Edit button clicked");
// toggle bulk edit mode
if (is_bulk_edit) {
disableBulkSelect();
// hide the delete button
$("#bulkDeleteButton").hide();
is_bulk_edit = false;
} else {
enableBulkSelect();
// show the delete button
$("#bulkDeleteButton").show();
is_bulk_edit = true;
}
}
/**
* Deletes the character with the given chid.
*
* @param {string} this_chid - The chid of the character to delete.
*/
async function deleteCharacter(this_chid) {
await handleDeleteCharacter("del_ch", this_chid, false);
}
/**
* Deletes all characters that have been selected via the bulk checkboxes.
*/
async function onDeleteButtonClick() {
console.log("Delete button clicked");
// Create a mapping of chid to avatar
let toDelete = [];
$(".bulk_select_checkbox:checked").each((i, el) => {
const chid = $(el).parent().attr("chid");
const avatar = characters[chid].avatar;
// Add the avatar to the list of avatars to delete
toDelete.push(avatar);
});
const confirm = await callPopup('<h3>Are you sure you want to delete these characters?</h3>You would need to delete the chat files manually.<br>', 'confirm');
if (!confirm) {
console.log('User cancelled delete');
return;
}
// Delete the characters
for (const avatar of toDelete) {
console.log(`Deleting character with avatar ${avatar}`);
await getCharacters();
//chid should be the key of the character with the given avatar
const chid = Object.keys(characters).find((key) => characters[key].avatar === avatar);
console.log(`Deleting character with chid ${chid}`);
await deleteCharacter(chid);
}
}
/**
* Adds the bulk edit and delete buttons to the UI.
*/
function addButtons() {
const editButton = $(
"<i id='bulkEditButton' class='fa-solid fa-edit menu_button bulkEditButton' title='Bulk edit characters'></i>"
);
const deleteButton = $(
"<i id='bulkDeleteButton' class='fa-solid fa-trash menu_button bulkDeleteButton' title='Bulk delete characters' style='display: none;'></i>"
);
$("#charListGridToggle").after(editButton, deleteButton);
$("#bulkEditButton").on("click", onEditButtonClick);
$("#bulkDeleteButton").on("click", onDeleteButtonClick);
}
/**
* Enables bulk selection by adding a checkbox next to each character.
*/
function enableBulkSelect() {
$("#rm_print_characters_block .character_select").each((i, el) => {
const character = $(el).text();
const checkbox = $("<input type='checkbox' class='bulk_select_checkbox'>");
checkbox.on("change", () => {
// Do something when the checkbox is changed
});
$(el).prepend(checkbox);
});
$("#rm_print_characters_block").addClass("bulk_select");
// We also need to disable the default click event for the character_select divs
$(document).on("click", ".bulk_select_checkbox", function (event) {
event.stopImmediatePropagation();
});
}
/**
* Disables bulk selection by removing the checkboxes.
*/
function disableBulkSelect() {
$(".bulk_select_checkbox").remove();
$("#rm_print_characters_block").removeClass("bulk_select");
}
/**
* Entry point that runs on page load.
*/
jQuery(async () => {
addButtons();
// loadSettings();
});

View File

@@ -0,0 +1,11 @@
{
"display_name": "Bulk Card Editor",
"loading_order": 9,
"requires": [],
"optional": [],
"js": "index.js",
"css": "style.css",
"author": "city-unit",
"version": "1.0.0",
"homePage": "https://github.com/city-unit"
}

View File

@@ -0,0 +1,7 @@
.bulk_select_checkbox {
align-self: center;
}
#rm_print_characters_block.bulk_select .wide100pLess70px {
width: calc(100% - 85px);
}

View File

@@ -9,6 +9,7 @@ const MODULE_NAME = 'expressions';
const UPDATE_INTERVAL = 2000;
const FALLBACK_EXPRESSION = 'joy';
const DEFAULT_EXPRESSIONS = [
"live2d",
"admiration",
"amusement",
"anger",
@@ -392,6 +393,108 @@ function onExpressionsShowDefaultInput() {
}
}
async function unloadLiveChar() {
try {
const url = new URL(getApiUrl());
url.pathname = '/api/live2d/unload';
const loadResponse = await doExtrasFetch(url);
if (!loadResponse.ok) {
throw new Error(loadResponse.statusText);
}
const loadResponseText = await loadResponse.text();
//console.log(`Response: ${loadResponseText}`);
} catch (error) {
//console.error(`Error unloading - ${error}`);
}
}
async function loadLiveChar() {
if (!modules.includes('live2d')) {
console.debug('live2d module is disabled');
return;
}
const context = getContext();
let spriteFolderName = context.name2;
const message = getLastCharacterMessage();
const avatarFileName = getSpriteFolderName(message);
const expressionOverride = extension_settings.expressionOverrides.find((e) =>
e.name == avatarFileName
);
if (expressionOverride && expressionOverride.path) {
spriteFolderName = expressionOverride.path;
}
const live2dPath = `/characters/${encodeURIComponent(spriteFolderName)}/live2d.png`;
try {
const spriteResponse = await fetch(live2dPath);
if (!spriteResponse.ok) {
throw new Error(spriteResponse.statusText);
}
const spriteBlob = await spriteResponse.blob();
const spriteFile = new File([spriteBlob], 'live2d.png', { type: 'image/png' });
const formData = new FormData();
formData.append('file', spriteFile);
const url = new URL(getApiUrl());
url.pathname = '/api/live2d/load';
const loadResponse = await doExtrasFetch(url, {
method: 'POST',
body: formData,
});
if (!loadResponse.ok) {
throw new Error(loadResponse.statusText);
}
const loadResponseText = await loadResponse.text();
console.log(`Load live2d response: ${loadResponseText}`);
} catch (error) {
console.error(`Error loading live2d image: ${live2dPath} - ${error}`);
}
}
function handleImageChange() {
const imgElement = document.querySelector('img#expression-image.expression');
if (!imgElement) {
console.log("Cannot find addExpressionImage()");
return;
}
if (extension_settings.expressions.live2d) {
// Method get IP of endpoint
const live2dResultFeedSrc = `${getApiUrl()}/api/live2d/result_feed`;
$('#expression-holder').css({ display: '' });
if (imgElement.src !== live2dResultFeedSrc) {
const expressionImageElement = document.querySelector('.expression_list_image');
if (expressionImageElement) {
doExtrasFetch(expressionImageElement.src, {
method: 'HEAD',
})
.then(response => {
if (response.ok) {
imgElement.src = live2dResultFeedSrc;
}
})
.catch(error => {
console.error(error); // Log the error if necessary
});
}
}
} else {
imgElement.src = ""; //remove incase char doesnt have expressions
setExpression(getContext().name2, FALLBACK_EXPRESSION, true);
}
}
async function moduleWorker() {
const context = getContext();
@@ -405,6 +508,16 @@ async function moduleWorker() {
if (context.groupId !== lastCharacter && context.characterId !== lastCharacter) {
removeExpression();
spriteCache = {};
//clear expression
let imgElement = document.getElementById('expression-image');
imgElement.src = "";
//set checkbox to global var
$('#image_type_toggle').prop('checked', extension_settings.expressions.live2d);
if(extension_settings.expressions.live2d == true){
setLive2dState(extension_settings.expressions.live2d);
}
}
const vnMode = isVisualNovelMode();
@@ -507,6 +620,64 @@ async function moduleWorker() {
lastCharacter = context.groupId || context.characterId;
lastMessage = currentLastMessage.mes;
}
}
async function live2dcheck() {
const context = getContext();
let spriteFolderName = context.name2;
const message = getLastCharacterMessage();
const avatarFileName = getSpriteFolderName(message);
const expressionOverride = extension_settings.expressionOverrides.find((e) =>
e.name == avatarFileName
);
if (expressionOverride && expressionOverride.path) {
spriteFolderName = expressionOverride.path;
}
try {
await validateImages(spriteFolderName);
let live2dObj = spriteCache[spriteFolderName].find(obj => obj.label === 'live2d');
let live2dPath_f = live2dObj ? live2dObj.path : null;
if(live2dPath_f != null){
//console.log("live2dPath_f " + live2dPath_f);
return true;
} else {
//console.log("live2dPath_f is null");
unloadLiveChar();
return false;
}
} catch (err) {
return err;
}
}
function setLive2dState(switch_var){
extension_settings.expressions.live2d = switch_var; // Store setting
saveSettingsDebounced();
live2dcheck().then(result => {
if (result) {
//console.log("Live2d exists!");
if (extension_settings.expressions.live2d) {
loadLiveChar();
} else {
unloadLiveChar();
}
handleImageChange(switch_var); // Change image as needed
} else {
//console.log("Live2d does not exist.");
}
});
}
function getSpriteFolderName(message) {
@@ -654,7 +825,6 @@ async function getSpritesList(name) {
try {
const result = await fetch(`/get_sprites?name=${encodeURIComponent(name)}`);
let sprites = result.ok ? (await result.json()) : [];
return sprites;
}
@@ -697,114 +867,137 @@ async function getExpressionsList() {
}
async function setExpression(character, expression, force) {
console.debug('entered setExpressions');
await validateImages(character);
const img = $('img.expression');
const prevExpressionSrc = img.attr('src');
const expressionClone = img.clone()
if (extension_settings.expressions.live2d == false) {
const sprite = (spriteCache[character] && spriteCache[character].find(x => x.label === expression));
console.debug('checking for expression images to show..');
if (sprite) {
console.debug('setting expression from character images folder');
console.debug('entered setExpressions');
await validateImages(character);
const img = $('img.expression');
const prevExpressionSrc = img.attr('src');
const expressionClone = img.clone()
if (force && isVisualNovelMode()) {
const context = getContext();
const group = context.groups.find(x => x.id === context.groupId);
const sprite = (spriteCache[character] && spriteCache[character].find(x => x.label === expression));
console.debug('checking for expression images to show..');
if (sprite) {
console.debug('setting expression from character images folder');
for (const member of group.members) {
const groupMember = context.characters.find(x => x.avatar === member);
if (force && isVisualNovelMode()) {
const context = getContext();
const group = context.groups.find(x => x.id === context.groupId);
if (!groupMember) {
continue;
for (const member of group.members) {
const groupMember = context.characters.find(x => x.avatar === member);
if (!groupMember) {
continue;
}
if (groupMember.name == character) {
await setImage($(`.expression-holder[data-avatar="${member}"] img`), sprite.path);
return;
}
}
}
//only swap expressions when necessary
if (prevExpressionSrc !== sprite.path
&& !img.hasClass('expression-animating')) {
//clone expression
expressionClone.addClass('expression-clone')
//make invisible and remove id to prevent double ids
//must be made invisible to start because they share the same Z-index
expressionClone.attr('id', '').css({ opacity: 0 });
//add new sprite path to clone src
expressionClone.attr('src', sprite.path);
//add invisible clone to html
expressionClone.appendTo($("#expression-holder"))
if (groupMember.name == character) {
await setImage($(`.expression-holder[data-avatar="${member}"] img`), sprite.path);
return;
const duration = 200;
//add animation flags to both images
//to prevent multiple expression changes happening simultaneously
img.addClass('expression-animating');
// Set the parent container's min width and height before running the transition
const imgWidth = img.width();
const imgHeight = img.height();
const expressionHolder = img.parent();
expressionHolder.css('min-width', imgWidth > 100 ? imgWidth : 100);
expressionHolder.css('min-height', imgHeight > 100 ? imgHeight : 100);
//position absolute prevent the original from jumping around during transition
img.css('position', 'absolute');
expressionClone.addClass('expression-animating');
//fade the clone in
expressionClone.css({
opacity: 0
}).animate({
opacity: 1
}, duration)
//when finshed fading in clone, fade out the original
.promise().done(function () {
img.animate({
opacity: 0
}, duration);
//remove old expression
img.remove();
//replace ID so it becomes the new 'original' expression for next change
expressionClone.attr('id', 'expression-image');
expressionClone.removeClass('expression-animating');
// Reset the expression holder min height and width
expressionHolder.css('min-width', 100);
expressionHolder.css('min-height', 100);
});
expressionClone.removeClass('expression-clone');
expressionClone.removeClass('default');
expressionClone.off('error');
expressionClone.on('error', function () {
console.debug('Expression image error', sprite.path);
$(this).attr('src', '');
$(this).off('error');
if (force && extension_settings.expressions.showDefault) {
setDefault();
}
});
} else {
if (extension_settings.expressions.showDefault) {
setDefault();
}
}
}
//only swap expressions when necessary
if (prevExpressionSrc !== sprite.path
&& !img.hasClass('expression-animating')) {
//clone expression
expressionClone.addClass('expression-clone')
//make invisible and remove id to prevent double ids
//must be made invisible to start because they share the same Z-index
expressionClone.attr('id', '').css({ opacity: 0 });
//add new sprite path to clone src
expressionClone.attr('src', sprite.path);
//add invisible clone to html
expressionClone.appendTo($("#expression-holder"))
const duration = 200;
//add animation flags to both images
//to prevent multiple expression changes happening simultaneously
img.addClass('expression-animating');
// Set the parent container's min width and height before running the transition
const imgWidth = img.width();
const imgHeight = img.height();
const expressionHolder = img.parent();
expressionHolder.css('min-width', imgWidth > 100 ? imgWidth : 100);
expressionHolder.css('min-height', imgHeight > 100 ? imgHeight : 100);
//position absolute prevent the original from jumping around during transition
img.css('position', 'absolute');
expressionClone.addClass('expression-animating');
//fade the clone in
expressionClone.css({
opacity: 0
}).animate({
opacity: 1
}, duration)
//when finshed fading in clone, fade out the original
.promise().done(function () {
img.animate({
opacity: 0
}, duration);
//remove old expression
img.remove();
//replace ID so it becomes the new 'original' expression for next change
expressionClone.attr('id', 'expression-image');
expressionClone.removeClass('expression-animating');
// Reset the expression holder min height and width
expressionHolder.css('min-width', 100);
expressionHolder.css('min-height', 100);
});
expressionClone.removeClass('expression-clone');
expressionClone.removeClass('default');
expressionClone.off('error');
expressionClone.on('error', function () {
console.debug('Expression image error', sprite.path);
$(this).attr('src', '');
$(this).off('error');
if (force && extension_settings.expressions.showDefault) {
setDefault();
}
});
function setDefault() {
console.debug('setting default');
const defImgUrl = `/img/default-expressions/${expression}.png`;
//console.log(defImgUrl);
img.attr('src', defImgUrl);
img.addClass('default');
}
document.getElementById("expression-holder").style.display = '';
} else {
if (extension_settings.expressions.showDefault) {
setDefault();
}
}
function setDefault() {
console.debug('setting default');
const defImgUrl = `/img/default-expressions/${expression}.png`;
console.log(defImgUrl);
img.attr('src', defImgUrl);
img.addClass('default');
live2dcheck().then(result => {
if (result) {
// Find the <img> element with id="expression-image" and class="expression"
const imgElement = document.querySelector('img#expression-image.expression');
//console.log("searching");
if (imgElement) {
//console.log("setting value");
imgElement.src = getApiUrl() + '/api/live2d/result_feed';
}
} else {
//console.log("The fetch failed!");
}
});
}
document.getElementById("expression-holder").style.display = '';
}
function onClickExpressionImage() {
@@ -1052,7 +1245,6 @@ function setExpressionOverrideHtml(forceClear = false) {
$('body').append(element);
}
function addSettings() {
const html = `
<div class="expression_settings">
<div class="inline-drawer">
@@ -1060,8 +1252,16 @@ function setExpressionOverrideHtml(forceClear = false) {
<b>Character Expressions</b>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<div class="inline-drawer-content">
<div class="offline_mode">
<!-- Toggle button for aituber/static images -->
<div class="toggle_button">
<label class="switch">
<input id="image_type_toggle" type="checkbox">
<span class="slider round"></span>
<label for="image_type_toggle">Image Type - Live2d (extras)</label>
</div>
<div class="offline_mode">
<small>You are in offline mode. Click on the image below to set the expression.</small>
</div>
<div class="flex-container flexnowrap">
@@ -1090,6 +1290,7 @@ function setExpressionOverrideHtml(forceClear = false) {
</form>
</div>
`;
$('#extensions_settings').append(html);
$('#expression_override_button').on('click', onClickExpressionOverrideButton);
$('#expressions_show_default').on('input', onExpressionsShowDefaultInput);
@@ -1105,6 +1306,10 @@ function setExpressionOverrideHtml(forceClear = false) {
$(document).on('click', '.expression_list_delete', onClickExpressionDelete);
$(window).on("resize", updateVisualNovelModeDebounced);
$('.expression_settings').hide();
$('#image_type_toggle').on('click', function () {
setLive2dState(this.checked);
});
}
addExpressionImage();

View File

@@ -544,6 +544,30 @@ async function onSelectInjectFile(e) {
}
}
// Gets the length of character description in the current context
function getCharacterDataLength() {
const context = getContext();
const character = context.characters[context.characterId];
if (typeof character?.data !== 'object') {
return 0;
}
let characterDataLength = 0;
for (const [key, value] of Object.entries(character.data)) {
if (typeof value !== 'string') {
continue;
}
if (['description', 'personality', 'scenario'].includes(key)) {
characterDataLength += character.data[key].length;
}
}
return characterDataLength;
}
/*
* 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.
@@ -558,6 +582,10 @@ function doAutoAdjust(chat, maxContext) {
return;
}
// Adjust max context for character defs length
maxContext = Math.floor(maxContext - (getCharacterDataLength() / CHARACTERS_PER_TOKEN_RATIO));
console.debug('CHROMADB: Max context adjusted for character defs: %o', maxContext);
console.debug('CHROMADB: Mean message length (characters): %o', meanMessageLength);
// Convert to number of "tokens"
const meanMessageLengthTokens = Math.ceil(meanMessageLength / CHARACTERS_PER_TOKEN_RATIO);

View File

@@ -1,6 +1,7 @@
import { getStringHash, debounce, waitUntilCondition } from "../../utils.js";
import { getStringHash, debounce, waitUntilCondition, extractAllWords } 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";
import { is_group_generating, selected_group } from "../../group-chats.js";
export { MODULE_NAME };
const MODULE_NAME = '1_memory';
@@ -12,7 +13,21 @@ let lastMessageHash = null;
let lastMessageId = null;
let inApiCall = false;
const formatMemoryValue = (value) => value ? `Summary: ${value.trim()}` : '';
const formatMemoryValue = function (value) {
if (!value) {
return '';
}
value = value.trim();
if (extension_settings.memory.template) {
let result = extension_settings.memory.template.replace(/{{summary}}/i, value);
return substituteParams(result);
} else {
return `Summary: ${value}`;
}
}
const saveChatDebounced = debounce(() => getContext().saveChat(), 2000);
const summary_sources = {
@@ -21,6 +36,7 @@ const summary_sources = {
};
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 defaultTemplate = '[Summary: {{summary}}]';
const defaultSettings = {
minLongMemory: 16,
@@ -46,6 +62,9 @@ const defaultSettings = {
memoryFrozen: false,
source: summary_sources.extras,
prompt: defaultPrompt,
template: defaultTemplate,
position: extension_prompt_types.AFTER_SCENARIO,
depth: 2,
promptWords: 200,
promptMinWords: 25,
promptMaxWords: 1000,
@@ -54,6 +73,10 @@ const defaultSettings = {
promptMinInterval: 1,
promptMaxInterval: 100,
promptIntervalStep: 1,
promptForceWords: 0,
promptForceWordsStep: 100,
promptMinForceWords: 0,
promptMaxForceWords: 10000,
};
function loadSettings() {
@@ -61,20 +84,10 @@ 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;
for (const key of Object.keys(defaultSettings)) {
if (extension_settings.memory[key] === undefined) {
extension_settings.memory[key] = defaultSettings[key];
}
}
$('#summary_source').val(extension_settings.memory.source).trigger('change');
@@ -87,6 +100,10 @@ function loadSettings() {
$('#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');
$('#memory_template').val(extension_settings.memory.template).trigger('input');
$('#memory_depth').val(extension_settings.memory.depth).trigger('input');
$(`input[name="memory_position"][value="${extension_settings.memory.position}"]`).prop('checked', true).trigger('input');
$('#memory_prompt_words_force').val(extension_settings.memory.promptForceWords).trigger('input');
}
function onSummarySourceChange(event) {
@@ -170,6 +187,31 @@ function onMemoryPromptInput() {
saveSettingsDebounced();
}
function onMemoryTemplateInput() {
const value = $(this).val();
extension_settings.memory.template = value;
saveSettingsDebounced();
}
function onMemoryDepthInput() {
const value = $(this).val();
extension_settings.memory.depth = Number(value);
saveSettingsDebounced();
}
function onMemoryPositionChange(e) {
const value = e.target.value;
extension_settings.memory.position = value;
saveSettingsDebounced();
}
function onMemoryPromptWordsForceInput() {
const value = $(this).val();
extension_settings.memory.promptForceWords = Number(value);
$('#memory_prompt_words_force_value').text(extension_settings.memory.promptForceWords);
saveSettingsDebounced();
}
function saveLastValues() {
const context = getContext();
lastGroupId = context.groupId;
@@ -292,8 +334,12 @@ async function summarizeChat(context) {
async function summarizeChatMain(context, force) {
try {
// Wait for group to finish generating
if (selected_group) {
await waitUntilCondition(() => is_group_generating === false, 1000, 10);
}
// Wait for the send button to be released
waitUntilCondition(() => is_send_press === false, 10000, 100);
waitUntilCondition(() => is_send_press === false, 30000, 100);
} catch {
console.debug('Timeout waiting for is_send_press');
return;
@@ -310,19 +356,30 @@ async function summarizeChatMain(context, force) {
}
let messagesSinceLastSummary = 0;
let wordsSinceLastSummary = 0;
let conditionSatisfied = false;
for (let i = context.chat.length - 1; i >= 0; i--) {
if (context.chat[i].extra && context.chat[i].extra.memory) {
break;
}
messagesSinceLastSummary++;
wordsSinceLastSummary += extractAllWords(context.chat[i].mes).length;
}
if (messagesSinceLastSummary < extension_settings.memory.promptInterval && !force) {
console.debug(`Not enough messages since last summary (messages: ${messagesSinceLastSummary}, interval: ${extension_settings.memory.promptInterval}`);
if (messagesSinceLastSummary >= extension_settings.memory.promptInterval) {
conditionSatisfied = true;
}
if (extension_settings.memory.promptForceWords && wordsSinceLastSummary >= extension_settings.memory.promptForceWords) {
conditionSatisfied = true;
}
if (!conditionSatisfied && !force) {
console.debug(`Summary conditions not satisfied (messages: ${messagesSinceLastSummary}, interval: ${extension_settings.memory.promptInterval}, words: ${wordsSinceLastSummary}, force words: ${extension_settings.memory.promptForceWords})`);
return;
}
console.log('Summarizing chat, messages since last summary: ' + messagesSinceLastSummary);
console.log('Summarizing chat, messages since last summary: ' + messagesSinceLastSummary, 'words since last summary: ' + wordsSinceLastSummary);
const prompt = substituteParams(extension_settings.memory.prompt)
.replace(/{{words}}/gi, extension_settings.memory.promptWords);
@@ -458,9 +515,11 @@ function onMemoryContentInput() {
function setMemoryContext(value, saveToMessage) {
const context = getContext();
context.setExtensionPrompt(MODULE_NAME, formatMemoryValue(value), extension_prompt_types.AFTER_SCENARIO);
context.setExtensionPrompt(MODULE_NAME, formatMemoryValue(value), extension_settings.memory.position, extension_settings.memory.depth);
$('#memory_contents').val(value);
console.log('Memory set to: ' + value);
console.log('Summary set to: ' + value);
console.debug('Position: ' + extension_settings.memory.position);
console.debug('Depth: ' + extension_settings.memory.depth);
if (saveToMessage && context.chat.length) {
const idx = context.chat.length - 2;
@@ -496,6 +555,21 @@ jQuery(function () {
<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 class="memory_template">
<label for="memory_template">Injection template:</label>
<textarea id="memory_template" class="text_pole textarea_compact" rows="1" placeholder="Use {{summary}} macro to specify the position of summarized text."></textarea>
</div>
<label for="memory_position">Injection position:</label>
<div class="radio_group">
<label>
<input type="radio" name="memory_position" value="0" />
After scenario
</label>
<label>
<input type="radio" name="memory_position" value="1" />
In-chat @ Depth <input id="memory_depth" class="text_pole widthUnset" type="number" min="0" max="99" />
</label>
</div>
<div data-source="main" class="memory_contents_controls">
</div>
<div data-source="main">
@@ -511,6 +585,9 @@ jQuery(function () {
<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}" />
<label for="memory_prompt_words_force">Force update after (<span id="memory_prompt_words_force_value"></span> words)</label>
<small>Set to 0 to disable</small>
<input id="memory_prompt_words_force" type="range" value="${defaultSettings.promptForceWords}" min="${defaultSettings.promptMinForceWords}" max="${defaultSettings.promptMaxForceWords}" step="${defaultSettings.promptForceWordsStep}" />
</div>
<div data-source="extras">
<label for="memory_short_length">Chat to Summarize buffer length (<span id="memory_short_length_tokens"></span> tokens)</label>
@@ -542,6 +619,10 @@ jQuery(function () {
$('#memory_prompt_interval').on('input', onMemoryPromptIntervalInput);
$('#memory_prompt').on('input', onMemoryPromptInput);
$('#memory_force_summarize').on('click', forceSummarizeChat);
$('#memory_template').on('input', onMemoryTemplateInput);
$('#memory_depth').on('input', onMemoryDepthInput);
$('input[name="memory_position"]').on('change', onMemoryPositionChange);
$('#memory_prompt_words_force').on('input', onMemoryPromptWordsForceInput);
}
addExtensionControls();

View File

@@ -1,34 +1,26 @@
#memory_settings {
display: flex;
flex-direction: column;
}
#memory_settings textarea {
font-size: calc(var(--mainFontSize) * 0.9);
line-height: 1.2;
}
#memory_settings input[type="range"] {
margin-bottom: 20px;
}
#memory_settings label {
margin-bottom: 10px;
}
label[for="memory_frozen"] {
display: flex;
align-items: center;
margin: 0 !important;
}
label[for="memory_frozen"] input {
margin-right: 10px;
}
.memory_contents_controls {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
#memory_settings {
display: flex;
flex-direction: column;
}
#memory_settings textarea {
font-size: calc(var(--mainFontSize) * 0.9);
line-height: 1.2;
}
label[for="memory_frozen"] {
display: flex;
align-items: center;
margin: 0 !important;
}
label[for="memory_frozen"] input {
margin-right: 10px;
}
.memory_contents_controls {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}

View File

@@ -1,4 +1,4 @@
import { chat_metadata, callPopup, saveSettingsDebounced, getCurrentChatId } from "../../../script.js";
import { chat_metadata, callPopup, saveSettingsDebounced, is_send_press } from "../../../script.js";
import { getContext, extension_settings, saveMetadataDebounced } from "../../extensions.js";
import {
substituteParams,
@@ -7,6 +7,8 @@ import {
generateQuietPrompt,
} from "../../../script.js";
import { registerSlashCommand } from "../../slash-commands.js";
import { waitUntilCondition } from "../../utils.js";
import { is_group_generating, selected_group } from "../../group-chats.js";
const MODULE_NAME = "Objective"
@@ -17,6 +19,7 @@ let currentChatId = ""
let currentObjective = null
let currentTask = null
let checkCounter = 0
let lastMessageWasSwipe = false
const defaultPrompts = {
@@ -69,10 +72,20 @@ function getTaskByIdRecurse(taskId, task) {
return null;
}
function substituteParamsPrompts(content) {
content = content.replace(/{{objective}}/gi, currentObjective.description)
content = content.replace(/{{task}}/gi, currentTask.description)
if (currentTask.parent){
content = content.replace(/{{parent}}/gi, currentTask.parent.description)
}
content = substituteParams(content)
return content
}
// Call Quiet Generate to create task list using character context, then convert to tasks. Should not be called much.
async function generateTasks() {
const prompt = substituteParams(objectivePrompts.createTask.replace(/{{objective}}/gi, currentObjective.description));
const prompt = substituteParamsPrompts(objectivePrompts.createTask);
console.log(`Generating tasks for objective with prompt`)
toastr.info('Generating tasks for objective', 'Please wait...');
const taskResponse = await generateQuietPrompt(prompt)
@@ -89,8 +102,8 @@ async function generateTasks() {
}
updateUiTaskList();
setCurrentTask();
console.info(`Response for Objective: '${taskTree.description}' was \n'${taskResponse}', \nwhich created tasks \n${JSON.stringify(globalTasks.map(v => {return v.toSaveState()}), null, 2)} `)
toastr.success(`Generated ${globalTasks.length} tasks`, 'Done!');
console.info(`Response for Objective: '${currentObjective.description}' was \n'${taskResponse}', \nwhich created tasks \n${JSON.stringify(currentObjective.children.map(v => {return v.toSaveState()}), null, 2)} `)
toastr.success(`Generated ${currentObjective.children.length} tasks`, 'Done!');
}
// Call Quiet Generate to check if a task is completed
@@ -99,9 +112,23 @@ async function checkTaskCompleted() {
if (jQuery.isEmptyObject(currentTask)) {
return
}
checkCounter = $('#objective-check-frequency').val()
const prompt = substituteParams(objectivePrompts.checkTaskCompleted.replace(/{{task}}/gi, currentTask.description));
try {
// Wait for group to finish generating
if (selected_group) {
await waitUntilCondition(() => is_group_generating === false, 1000, 10);
}
// Another extension might be doing something with the chat, so wait for it to finish
await waitUntilCondition(() => is_send_press === false, 30000, 10);
} catch {
console.debug("Failed to wait for group to finish generating")
return;
}
checkCounter = $('#objective-check-frequency').val()
toastr.info("Checking for task completion.")
const prompt = substituteParamsPrompts(objectivePrompts.checkTaskCompleted);
const taskResponse = (await generateQuietPrompt(prompt)).toLowerCase()
// Check response if task complete
@@ -116,8 +143,10 @@ async function checkTaskCompleted() {
}
function getNextIncompleteTaskRecurse(task){
// Skip tasks with children, as they will have subtasks to determine completeness
if (task.completed === false && task.children.length === 0){
if (task.completed === false // Return task if incomplete
&& task.children.length === 0 // Ensure task has no children, it's subtasks will determine completeness
&& task.parentId !== "" // Must have parent id. Only root task will be missing this and we dont want that
){
return task
}
for (const childTask of task.children) {
@@ -149,21 +178,20 @@ function setCurrentTask(taskId = null) {
// Don't just check for a current task, check if it has data
const description = currentTask.description || null;
if (description) {
const extensionPromptText = objectivePrompts.currentTask.replace(/{{task}}/gi, description);
const extensionPromptText = substituteParamsPrompts(objectivePrompts.currentTask);
// Remove highlights
$('.objective-task').css({'border-color':'','border-width':''})
// Highlight current task
let highlightTask = currentTask
while (highlightTask.parentId !== ""){
if (highlightTask.descriptionSpan){
highlightTask.descriptionSpan.css({'border-color':'yellow','border-width':'2px'});
highlightTask.descriptionSpan.css({'border-color':'yellow','border-width':'2px'});
}
const parent = getTaskById(highlightTask.parentId)
highlightTask = parent
}
// Update the extension prompt
context.setExtensionPrompt(MODULE_NAME, extensionPromptText, 1, $('#objective-chat-depth').val());
console.info(`Current task in context.extensionPrompts.Objective is ${JSON.stringify(context.extensionPrompts.Objective)}`);
@@ -226,7 +254,7 @@ class ObjectiveTask {
))
saveState()
}
getIndex(){
if (this.parentId !== null) {
const parent = getTaskById(this.parentId)
@@ -290,7 +318,7 @@ class ObjectiveTask {
this.deleteButton = $(`#objective-task-delete-${this.id}`);
this.taskHtml = $(`#objective-task-label-${this.id}`);
this.branchButton = $(`#objective-task-add-branch-${this.id}`)
// Handle sub-task forking style
if (this.children.length > 0){
this.branchButton.css({'color':'#33cc33'})
@@ -369,6 +397,8 @@ function onEditPromptClick() {
let popupText = ''
popupText += `
<div class="objective_prompt_modal">
<small>Edit prompts used by Objective for this session. You can use {{objective}} or {{task}} plus any other standard template variables. Save template to persist changes.</small>
<br>
<div>
<label for="objective-prompt-generate">Generation Prompt</label>
<textarea id="objective-prompt-generate" type="text" class="text_pole textarea_compact" rows="8"></textarea>
@@ -378,12 +408,12 @@ function onEditPromptClick() {
<textarea id="objective-prompt-extension-prompt" type="text" class="text_pole textarea_compact" rows="8"></textarea>
</div>
<div class="objective_prompt_block">
<input id="objective-custom-prompt-name" style="flex-grow:2" type="text" class="flex1 heightFitContent text_pole widthNatural" maxlength="250" placeholder="Custom Prompt Name">
<input id="objective-custom-prompt-save" style="flex-grow:1" class="menu_button" type="submit" value="Save Prompt" />
<label for="objective-custom-prompt-select">Custom Prompt Select</label>
<select id="objective-custom-prompt-select"><select>
</div>
<div class="objective_prompt_block">
<label for="objective-prompt-load">Load Prompt</label>
<select id="objective-prompt-load"><select>
<input id="objective-custom-prompt-new" class="menu_button" type="submit" value="New Prompt" />
<input id="objective-custom-prompt-save" class="menu_button" type="submit" value="Save Prompt" />
<input id="objective-custom-prompt-delete" class="menu_button" type="submit" value="Delete Prompt" />
</div>
</div>`
@@ -394,7 +424,7 @@ function onEditPromptClick() {
$('#objective-prompt-generate').val(objectivePrompts.createTask)
$('#objective-prompt-check').val(objectivePrompts.checkTaskCompleted)
$('#objective-prompt-extension-prompt').val(objectivePrompts.currentTask)
// Handle value updates
$('#objective-prompt-generate').on('input', () => {
objectivePrompts.createTask = $('#objective-prompt-generate').val()
@@ -406,22 +436,27 @@ function onEditPromptClick() {
objectivePrompts.currentTask = $('#objective-prompt-extension-prompt').val()
})
// Handle new
$('#objective-custom-prompt-new').on('click', () => {
newCustomPrompt()
})
// Handle save
$('#objective-custom-prompt-save').on('click', () => {
saveCustomPrompt($('#objective-custom-prompt-name').val(), objectivePrompts)
saveCustomPrompt()
})
// Handle delete
$('#objective-custom-prompt-delete').on('click', () => {
const optionSelected = $("#objective-prompt-load").find(':selected').val()
deleteCustomPrompt(optionSelected)
deleteCustomPrompt()
})
// Handle load
$('#objective-prompt-load').on('change', loadCustomPrompt)
$('#objective-custom-prompt-select').on('change', loadCustomPrompt)
}
async function newCustomPrompt() {
const customPromptName = await callPopup('<h3>Custom Prompt name:</h3>', 'input');
function saveCustomPrompt(customPromptName, customPrompts) {
if (customPromptName == "") {
toastr.warning("Please set custom prompt name to save.")
return
@@ -431,12 +466,25 @@ function saveCustomPrompt(customPromptName, customPrompts) {
return
}
extension_settings.objective.customPrompts[customPromptName] = {}
Object.assign(extension_settings.objective.customPrompts[customPromptName], customPrompts)
Object.assign(extension_settings.objective.customPrompts[customPromptName], objectivePrompts)
saveSettingsDebounced()
populateCustomPrompts()
}
function deleteCustomPrompt(customPromptName){
function saveCustomPrompt() {
const customPromptName = $("#objective-custom-prompt-select").find(':selected').val()
if (customPromptName == "default"){
toastr.error("Cannot save over default prompt")
return
}
Object.assign(extension_settings.objective.customPrompts[customPromptName], objectivePrompts)
saveSettingsDebounced()
populateCustomPrompts()
}
function deleteCustomPrompt(){
const customPromptName = $("#objective-custom-prompt-select").find(':selected').val()
if (customPromptName == "default"){
toastr.error("Cannot delete default prompt")
return
@@ -448,8 +496,7 @@ function deleteCustomPrompt(customPromptName){
}
function loadCustomPrompt(){
const optionSelected = $("#objective-prompt-load").find(':selected').val()
console.log(optionSelected)
const optionSelected = $("#objective-custom-prompt-select").find(':selected').val()
Object.assign(objectivePrompts, extension_settings.objective.customPrompts[optionSelected])
$('#objective-prompt-generate').val(objectivePrompts.createTask)
@@ -459,13 +506,13 @@ function loadCustomPrompt(){
function populateCustomPrompts(){
// Populate saved prompts
$('#objective-prompt-load').empty()
$('#objective-custom-prompt-select').empty()
for (const customPromptName in extension_settings.objective.customPrompts){
const option = document.createElement('option');
option.innerText = customPromptName;
option.value = customPromptName;
option.selected = customPromptName
$('#objective-prompt-load').append(option)
$('#objective-custom-prompt-select').append(option)
}
}
@@ -485,6 +532,7 @@ const defaultSettings = {
// Convenient single call. Not much at the moment.
function resetState() {
lastMessageWasSwipe = false
loadSettings();
}
@@ -574,8 +622,10 @@ function onChatDepthInput() {
}
function onObjectiveTextFocusOut(){
currentObjective.description = $('#objective-text').val()
saveState()
if (currentObjective){
currentObjective.description = $('#objective-text').val()
saveState()
}
}
// Update how often we check for task completion
@@ -611,7 +661,7 @@ function loadSettings() {
// Reset Objectives and Tasks in memory
taskTree = null;
currentObjective = null;
// Init extension settings
if (Object.keys(extension_settings.objective).length === 0) {
Object.assign(extension_settings.objective, { 'customPrompts': {'default':defaultPrompts}})
@@ -750,9 +800,12 @@ jQuery(() => {
eventSource.on(event_types.CHAT_CHANGED, () => {
resetState()
});
eventSource.on(event_types.MESSAGE_SWIPED, () => {
lastMessageWasSwipe = true
})
eventSource.on(event_types.MESSAGE_RECEIVED, () => {
if (currentChatId == undefined) {
if (currentChatId == undefined || jQuery.isEmptyObject(currentTask) || lastMessageWasSwipe) {
lastMessageWasSwipe = false
return
}
if ($("#objective-check-frequency").val() > 0) {

View File

@@ -1,19 +1,49 @@
import { saveSettingsDebounced } from "../../../script.js";
import { saveSettingsDebounced, callPopup, getRequestHeaders } from "../../../script.js";
import { getContext, extension_settings } from "../../extensions.js";
import { initScrollHeight, resetScrollHeight } from "../../utils.js";
import { executeSlashCommands, getSlashCommandsHelp, registerSlashCommand } from "../../slash-commands.js";
export { MODULE_NAME };
const MODULE_NAME = 'quick-reply';
const UPDATE_INTERVAL = 1000;
let presets = [];
let selected_preset = '';
const defaultSettings = {
quickReplyEnabled: false,
quickReplyEnabled: true,
numberOfSlots: 5,
quickReplySlots: [],
}
async function loadSettings() {
//method from worldinfo
async function updateQuickReplyPresetList() {
var result = await fetch("/getsettings", {
method: "POST",
headers: getRequestHeaders(),
body: JSON.stringify({}),
});
if (result.ok) {
var data = await result.json();
presets = data.quickReplyPresets?.length ? data.quickReplyPresets : [];
console.log(presets)
$("#quickReplyPresets").find('option[value!=""]').remove();
if (presets !== undefined) {
presets.forEach((item, i) => {
$("#quickReplyPresets").append(`<option value='${item.name}'${selected_preset.includes(item.name) ? ' selected' : ''}>${item.name}</option>`);
});
}
}
}
async function loadSettings(type) {
if (type === 'init') {
await updateQuickReplyPresetList()
}
if (Object.keys(extension_settings.quickReply).length === 0) {
Object.assign(extension_settings.quickReply, defaultSettings);
}
@@ -111,6 +141,51 @@ async function moduleWorker() {
if (extension_settings.quickReply.quickReplyEnabled === true) {
$('#quickReplyBar').toggle(getContext().onlineStatus !== 'no_connection');
}
if (extension_settings.quickReply.selectedPreset) {
selected_preset = extension_settings.quickReply.selectedPreset;
}
}
async function saveQuickReplyPreset() {
const name = await callPopup('Enter a name for the Quick Reply Preset:', 'input');
if (!name) {
return;
}
const quickReplyPreset = {
name: name,
quickReplyEnabled: extension_settings.quickReply.quickReplyEnabled,
quickReplySlots: extension_settings.quickReply.quickReplySlots,
numberOfSlots: extension_settings.quickReply.numberOfSlots,
selectedPreset: name
}
const response = await fetch('/savequickreply', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify(quickReplyPreset)
});
if (response.ok) {
const quickReplyPresetIndex = presets.findIndex(x => x.name == name);
if (quickReplyPresetIndex == -1) {
presets.push(quickReplyPreset);
const option = document.createElement('option');
option.selected = true;
option.value = name;
option.innerText = name;
$('#quickReplyPresets').append(option);
}
else {
presets[quickReplyPresetIndex] = quickReplyPreset;
$(`#quickReplyPresets option[value="${name}"]`).attr('selected', true);
}
saveSettingsDebounced();
} else {
toastr.warning('Failed to save Quick Reply Preset.')
}
}
async function onQuickReplyNumberOfSlotsInput() {
@@ -178,6 +253,45 @@ function generateQuickReplyElements() {
});
}
async function applyQuickReplyPreset(name) {
const quickReplyPreset = presets.find(x => x.name == name);
if (!quickReplyPreset) {
toastr.warning(`error, QR preset '${name}' not found. Confirm you are using proper case sensitivity!`)
return;
}
extension_settings.quickReply = quickReplyPreset;
extension_settings.quickReply.selectedPreset = name;
saveSettingsDebounced()
loadSettings('init')
addQuickReplyBar();
moduleWorker();
$(`#quickReplyPresets option[value="${name}"]`).attr('selected', true);
console.debug('QR Preset applied: ' + name);
}
async function doQRPresetSwitch(_, text) {
text = String(text)
applyQuickReplyPreset(text)
}
async function doQR(_, text) {
if (!text) {
toastr.warning('must specify which QR # to use')
return
}
text = Number(text)
//use scale starting with 0
//ex: user inputs "/qr 2" >> qr with data-index 1 (but 2nd item displayed) gets triggered
let QRnum = Number(text - 1)
if (QRnum <= 0) { QRnum = 0 }
const whichQR = $("#quickReplies").find(`[data-index='${QRnum}']`);
whichQR.trigger('click')
}
jQuery(async () => {
moduleWorker();
@@ -190,11 +304,18 @@ 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 marginBot10">
<input id="quickReplyEnabled" type="checkbox" />
Enable Quick Replies
</label>
<label for="quickReplyNumberOfSlots">Number of slots:</label>
<div class="flex-container ">
<label class="checkbox_label marginBot10 wide100p flexnowrap">
<input id="quickReplyEnabled" type="checkbox" />
Enable Quick Replies
</label>
<div class="flex-container flexnowrap wide100p">
<select id="quickReplyPresets" name="quickreply-preset">
</select>
<i id="quickReplyPresetSaveButton" class="fa-solid fa-save"></i>
</div>
<label for="quickReplyNumberOfSlots">Number of slots:</label>
</div>
<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">
@@ -212,8 +333,23 @@ jQuery(async () => {
$('#quickReplyEnabled').on('input', onQuickReplyEnabledInput);
$('#quickReplyNumberOfSlotsApply').on('click', onQuickReplyNumberOfSlotsInput);
$("#quickReplyPresetSaveButton").on('click', saveQuickReplyPreset);
await loadSettings();
$("#quickReplyPresets").on('change', async function () {
const quickReplyPresetSelected = $(this).find(':selected').val();
extension_settings.quickReplyPreset = quickReplyPresetSelected;
applyQuickReplyPreset(quickReplyPresetSelected);
saveSettingsDebounced();
});
await loadSettings('init');
addQuickReplyBar();
});
$(document).ready(() => {
registerSlashCommand('qr', doQR, [], "- requires number argument, activates the specified QuickReply", true, true);
registerSlashCommand('qrset', doQRPresetSwitch, [], "- arg: QuickReply Preset Name, swaps to that QR preset", true, true);
})

View File

@@ -8,16 +8,21 @@ import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper
import { VoskSttProvider } from './vosk.js'
import { WhisperSttProvider } from './whisper.js'
import { BrowserSttProvider } from './browser.js'
import { StreamingSttProvider } from './streaming.js'
export { MODULE_NAME };
const MODULE_NAME = 'Speech Recognition';
const DEBUG_PREFIX = "<Speech Recognition module> "
const UPDATE_INTERVAL = 100;
let inApiCall = false;
let sttProviders = {
None: null,
Browser: BrowserSttProvider,
Whisper: WhisperSttProvider,
Vosk: VoskSttProvider,
Streaming: StreamingSttProvider,
}
let sttProvider = null
@@ -27,6 +32,82 @@ let audioRecording = false
const constraints = { audio: { sampleSize: 16, channelCount: 1, sampleRate: 16000 } };
let audioChunks = [];
async function moduleWorker() {
if (sttProviderName != "Streaming") {
return;
}
// API is busy
if (inApiCall) {
return;
}
try {
inApiCall = true;
const userMessageOriginal = await sttProvider.getUserMessage();
let userMessageFormatted = userMessageOriginal.trim();
if (userMessageFormatted.length > 0)
{
console.debug(DEBUG_PREFIX+"recorded transcript: \""+userMessageFormatted+"\"");
let userMessageLower = userMessageFormatted.toLowerCase();
// remove punctuation
let userMessageRaw = userMessageLower.replace(/[^\w\s\']|_/g, "").replace(/\s+/g, " ");
console.debug(DEBUG_PREFIX+"raw transcript:",userMessageRaw);
// Detect trigger words
let messageStart = -1;
if (extension_settings.speech_recognition.Streaming.triggerWordsEnabled) {
for (const triggerWord of extension_settings.speech_recognition.Streaming.triggerWords) {
const triggerPos = userMessageRaw.indexOf(triggerWord.toLowerCase());
// Trigger word not found or not starting message and just a substring
if (triggerPos == -1){ // | (triggerPos > 0 & userMessageFormatted[triggerPos-1] != " ")) {
console.debug(DEBUG_PREFIX+"trigger word not found: ", triggerWord);
}
else {
console.debug(DEBUG_PREFIX+"Found trigger word: ", triggerWord, " at index ", triggerPos);
if (triggerPos < messageStart | messageStart == -1) { // & (triggerPos + triggerWord.length) < userMessageFormatted.length)) {
messageStart = triggerPos; // + triggerWord.length + 1;
}
}
}
} else {
messageStart = 0;
}
if (messageStart == -1) {
console.debug(DEBUG_PREFIX+"message ignored, no trigger word preceding a message. Voice transcript: \""+ userMessageOriginal +"\"");
if (extension_settings.speech_recognition.Streaming.debug) {
toastr.info(
"No trigger word preceding a message. Voice transcript: \""+ userMessageOriginal +"\"",
DEBUG_PREFIX+"message ignored.",
{ timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true },
);
}
}
else{
userMessageFormatted = userMessageFormatted.substring(messageStart);
processTranscript(userMessageFormatted);
}
}
else
{
console.debug(DEBUG_PREFIX+"Received empty transcript, ignored");
}
}
catch (error) {
console.debug(error);
}
finally {
inApiCall = false;
}
}
async function processTranscript(transcript) {
try {
const transcriptOriginal = transcript;
@@ -198,13 +279,21 @@ function loadSttProvider(provider) {
if (sttProviderName == "Browser") {
sttProvider.processTranscriptFunction = processTranscript;
sttProvider.loadSettings(extension_settings.speech_recognition[sttProviderName]);
}
else {
sttProvider.loadSettings(extension_settings.speech_recognition[sttProviderName]);
loadNavigatorAudioRecording();
$("#microphone_button").show();
}
if (sttProviderName == "Vosk" | sttProviderName == "Whisper") {
sttProvider.loadSettings(extension_settings.speech_recognition[sttProviderName]);
loadNavigatorAudioRecording();
$("#microphone_button").show();
}
if (sttProviderName == "Streaming") {
sttProvider.loadSettings(extension_settings.speech_recognition[sttProviderName]);
$("#microphone_button").off('click');
$("#microphone_button").hide();
}
}
function onSttProviderChange() {
@@ -231,7 +320,7 @@ const defaultSettings = {
messageMode: "append",
messageMappingText: "",
messageMapping: [],
messageMappingEnabled: false
messageMappingEnabled: false,
}
function loadSettings() {
@@ -344,8 +433,7 @@ $(document).ready(function () {
addExtensionControls(); // No init dependencies
loadSettings(); // Depends on Extension Controls and loadTtsProvider
loadSttProvider(extension_settings.speech_recognition.currentProvider); // No dependencies
//const wrapper = new ModuleWorkerWrapper(moduleWorker);
//setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL); // Init depends on all the things
//moduleWorker();
const wrapper = new ModuleWorkerWrapper(moduleWorker);
setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL); // Init depends on all the things
moduleWorker();
})

View File

@@ -0,0 +1,102 @@
import { getApiUrl, doExtrasFetch, modules } from "../../extensions.js";
export { StreamingSttProvider }
const DEBUG_PREFIX = "<Speech Recognition module (streaming)> "
class StreamingSttProvider {
//########//
// Config //
//########//
settings
defaultSettings = {
triggerWordsText: "",
triggerWords : [],
triggerWordsEnabled : false,
debug : false,
}
get settingsHtml() {
let html = '\
<div id="speech_recognition_streaming_trigger_words_div">\
<span>Trigger words</span>\
<textarea id="speech_recognition_streaming_trigger_words" class="text_pole textarea_compact" type="text" rows="4" placeholder="Enter comma separated words that triggers new message, example:\nhey, hey aqua, record, listen"></textarea>\
<label class="checkbox_label" for="speech_recognition_streaming_trigger_words_enabled">\
<input type="checkbox" id="speech_recognition_streaming_trigger_words_enabled" name="speech_recognition_trigger_words_enabled">\
<small>Enable trigger words</small>\
</label>\
<label class="checkbox_label" for="speech_recognition_streaming_debug">\
<input type="checkbox" id="speech_recognition_streaming_debug" name="speech_recognition_streaming_debug">\
<small>Enable debug pop ups</small>\
</label>\
</div>\
'
return html
}
onSettingsChange() {
this.settings.triggerWordsText = $('#speech_recognition_streaming_trigger_words').val();
let array = $('#speech_recognition_streaming_trigger_words').val().split(",");
array = array.map(element => {return element.trim().toLowerCase();});
array = array.filter((str) => str !== '');
this.settings.triggerWords = array;
this.settings.triggerWordsEnabled = $("#speech_recognition_streaming_trigger_words_enabled").is(':checked');
this.settings.debug = $("#speech_recognition_streaming_debug").is(':checked');
console.debug(DEBUG_PREFIX+" Updated settings: ", this.settings);
this.loadSettings(this.settings);
}
loadSettings(settings) {
// Populate Provider UI given input settings
if (Object.keys(settings).length == 0) {
console.debug(DEBUG_PREFIX+"Using default Whisper STT extension settings")
}
// Only accept keys defined in defaultSettings
this.settings = this.defaultSettings
for (const key in settings){
if (key in this.settings){
this.settings[key] = settings[key]
} else {
throw `Invalid setting passed to STT extension: ${key}`
}
}
$("#speech_recognition_streaming_trigger_words").val(this.settings.triggerWordsText);
$("#speech_recognition_streaming_trigger_words_enabled").prop('checked',this.settings.triggerWordsEnabled);
$("#speech_recognition_streaming_debug").prop('checked',this.settings.debug);
console.debug(DEBUG_PREFIX+"streaming STT settings loaded")
}
async getUserMessage() {
// Return if module is not loaded
if (!modules.includes('streaming-stt')) {
console.debug(DEBUG_PREFIX+"Module streaming-stt must be activated in Sillytavern Extras for streaming user voice.")
return "";
}
const url = new URL(getApiUrl());
url.pathname = '/api/speech-recognition/streaming/record-and-transcript';
const apiResult = await doExtrasFetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Bypass-Tunnel-Reminder': 'bypass',
},
body: JSON.stringify({ text: "" }),
});
if (!apiResult.ok) {
toastr.error(apiResult.statusText, DEBUG_PREFIX+'STT Generation Failed (streaming)', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
throw new Error(`HTTP ${apiResult.status}: ${await apiResult.text()}`);
}
const data = await apiResult.json();
return data.transcript;
}
}

View File

@@ -1,5 +1,5 @@
import { callPopup, cancelTtsPlay, eventSource, event_types, isMultigenEnabled, is_send_press, saveSettingsDebounced } from '../../../script.js'
import { ModuleWorkerWrapper, extension_settings, getContext } from '../../extensions.js'
import { ModuleWorkerWrapper, doExtrasFetch, extension_settings, getApiUrl, getContext } from '../../extensions.js'
import { escapeRegex, getStringHash } from '../../utils.js'
import { EdgeTtsProvider } from './edge.js'
import { ElevenLabsTtsProvider } from './elevenlabs.js'
@@ -7,14 +7,13 @@ import { SileroTtsProvider } from './silerotts.js'
import { CoquiTtsProvider } from './coquitts.js'
import { SystemTtsProvider } from './system.js'
import { NovelTtsProvider } from './novel.js'
import { isMobile } from '../../RossAscends-mods.js'
import { power_user } from '../../power-user.js'
const UPDATE_INTERVAL = 1000
let voiceMap = {} // {charName:voiceid, charName2:voiceid2}
let audioControl
let storedvalue = false;
let lastCharacterId = null
let lastGroupId = null
let lastChatId = null
@@ -164,6 +163,20 @@ async function moduleWorker() {
ttsJobQueue.push(message)
}
function talkingAnimation(switchValue) {
const apiUrl = getApiUrl();
const animationType = switchValue ? "start" : "stop";
if (switchValue !== storedvalue) {
try {
console.log(animationType + " Talking Animation");
doExtrasFetch(`${apiUrl}/api/live2d/${animationType}_talking`);
storedvalue = switchValue; // Update the storedvalue to the current switchValue
} catch (error) {
// Handle the error here or simply ignore it to prevent logging
}
}
}
function resetTtsPlayback() {
// Stop system TTS utterance
@@ -291,8 +304,10 @@ function updateUiAudioPlayState() {
// Give user feedback that TTS is active by setting the stop icon if processing or playing
if (!audioElement.paused || isTtsProcessing()) {
img = 'fa-solid fa-stop-circle extensionsMenuExtensionButton'
talkingAnimation(true)
} else {
img = 'fa-solid fa-circle-play extensionsMenuExtensionButton'
talkingAnimation(false)
}
$('#tts_media_control').attr('class', img);
} else {
@@ -354,6 +369,7 @@ async function processAudioJobQueue() {
audioQueueProcessorReady = false
currentAudioJob = audioJobQueue.pop()
playAudioData(currentAudioJob)
talkingAnimation(true)
} catch (error) {
console.error(error)
audioQueueProcessorReady = true

View File

@@ -5,6 +5,7 @@ import {
delay,
isDataURL,
createThumbnail,
extractAllWords,
} from './utils.js';
import { RA_CountCharTokens, humanizedDateTime, dragElement } from "./RossAscends-mods.js";
import { sortCharactersList, sortGroupMembers, loadMovingUIState } from './power-user.js';
@@ -782,19 +783,6 @@ function activateNaturalOrder(members, input, lastMessage, allowSelfResponses, i
return memberIds;
}
function extractAllWords(value) {
const words = [];
if (!value) {
return words;
}
const matches = value.matchAll(/\b\w+\b/gim);
for (let match of matches) {
words.push(match[0].toLowerCase());
}
return words;
}
async function deleteGroup(id) {

View File

@@ -4,6 +4,10 @@ import {
getStoppingStrings,
} from "../script.js";
import {
power_user,
} from "./power-user.js";
export {
kai_settings,
loadKoboldSettings,
@@ -35,12 +39,12 @@ const MIN_STREAMING_KCPPVERSION = '1.30';
function formatKoboldUrl(value) {
try {
const url = new URL(value);
url.pathname = '/api';
if (!power_user.relaxed_api_urls) {
url.pathname = '/api';
}
return url.toString();
}
catch {
return null;
}
} catch { } // Just using URL as a validation check
return null;
}
function loadKoboldSettings(preset) {

View File

@@ -1,7 +1,10 @@
import {
getRequestHeaders,
saveSettingsDebounced,
getStoppingStrings,
getTextTokens
} from "../script.js";
import { tokenizers } from "./power-user.js";
export {
nai_settings,
@@ -10,6 +13,8 @@ export {
getNovelTier,
};
const default_preamble = "[ Style: chat, complex, sensory, visceral ]";
const nai_settings = {
temperature: 0.5,
repetition_penalty: 1,
@@ -26,6 +31,7 @@ const nai_settings = {
model_novel: "euterpe-v2",
preset_settings_novel: "Classic-Euterpe",
streaming_novel: false,
nai_preamble: default_preamble,
};
const nai_tiers = {
@@ -73,6 +79,7 @@ function loadNovelSettings(settings) {
$(`#model_novel_select option[value=${nai_settings.model_novel}]`).attr("selected", true);
$('#model_novel_select').val(nai_settings.model_novel);
if (settings.nai_preamble !== undefined) nai_settings.preamble = settings.nai_preamble;
nai_settings.preset_settings_novel = settings.preset_settings_novel;
nai_settings.temperature = settings.temperature;
nai_settings.repetition_penalty = settings.repetition_penalty;
@@ -102,7 +109,7 @@ const phraseRepPenStrings = [
]
function getPhraseRepPenString(phraseRepPenCounter) {
if (phraseRepPenCounter < 1 || phraseRepPenCounter > F5) {
if (phraseRepPenCounter < 1 || phraseRepPenCounter > 5) {
return null;
} else {
return phraseRepPenStrings[phraseRepPenCounter];
@@ -154,6 +161,7 @@ function loadNovelSettingsUi(ui_settings) {
$("#phrase_rep_pen_counter_novel").text(getPhraseRepPenCounter(ui_settings.phrase_rep_pen));
$("#min_length_novel").val(ui_settings.min_length);
$("#min_length_counter_novel").text(Number(ui_settings.min_length).toFixed(0));
$('#nai_preamble_textarea').val(ui_settings.nai_preamble);
$("#streaming_novel").prop('checked', ui_settings.streaming_novel);
}
@@ -245,8 +253,24 @@ const sliders = [
},
];
export function getNovelGenerationData(finalPromt, this_settings, this_amount_gen) {
const isNewModel = (nai_settings.model_novel.includes('clio') || nai_settings.model_novel.includes('kayra'));
export function getNovelGenerationData(finalPromt, this_settings, this_amount_gen, isImpersonate) {
const clio = nai_settings.model_novel.includes('clio');
const kayra = nai_settings.model_novel.includes('kayra');
const isNewModel = clio || kayra;
const tokenizerType = kayra ? tokenizers.NERD2 : (clio ? tokenizers.NERD : tokenizers.NONE);
const stopSequences = (tokenizerType !== tokenizers.NONE)
? getStoppingStrings(isImpersonate, false)
.map(t => getTextTokens(tokenizerType, t))
: undefined;
let useInstruct = false;
if (isNewModel) {
// NovelAI claims they scan backwards 1000 characters (not tokens!) to look for instruct brackets. That's really short.
const tail = finalPromt.slice(-1500);
useInstruct = tail.includes("}");
}
return {
"input": finalPromt,
"model": nai_settings.model_novel,
@@ -268,12 +292,13 @@ export function getNovelGenerationData(finalPromt, this_settings, this_amount_ge
"cfg_uc": "",
"phrase_rep_pen": nai_settings.phrase_rep_pen,
//"stop_sequences": {{187}},
"stop_sequences": stopSequences,
//bad_words_ids = {{50256}, {0}, {1}};
"generate_until_sentence": true,
"use_cache": false,
"use_string": true,
"return_full_text": false,
"prefix": isNewModel ? "special_instruct" : "vanilla",
"prefix": useInstruct ? "special_instruct" : (isNewModel ? "special_proseaugmenter" : "vanilla"),
"order": this_settings.order,
"streaming": nai_settings.streaming_novel,
};
@@ -321,6 +346,17 @@ export async function generateNovelWithStreaming(generate_data, signal) {
}
}
$("#nai_preamble_textarea").on('input', function () {
nai_settings.preamble = $('#nai_preamble_textarea').val();
saveSettingsDebounced();
});
$("#nai_preamble_restore").on('click', function () {
nai_settings.preamble = default_preamble;
$('#nai_preamble_textarea').val(nai_settings.preamble);
saveSettingsDebounced();
});
$(document).ready(function () {
sliders.forEach(slider => {
$(document).on("input", slider.sliderId, function () {

View File

@@ -143,6 +143,7 @@ const default_settings = {
api_url_scale: '',
show_external_models: false,
proxy_password: '',
assistant_prefill: '',
};
const oai_settings = {
@@ -180,6 +181,7 @@ const oai_settings = {
api_url_scale: '',
show_external_models: false,
proxy_password: '',
assistant_prefill: '',
};
let openai_setting_names;
@@ -775,6 +777,7 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
if (isClaude) {
generate_data['use_claude'] = true;
generate_data['top_k'] = parseFloat(oai_settings.top_k_openai);
generate_data['assistant_prefill'] = substituteParams(oai_settings.assistant_prefill);
}
if (isOpenRouter) {
@@ -1109,6 +1112,7 @@ function loadOpenAISettings(data, settings) {
oai_settings.api_url_scale = settings.api_url_scale ?? default_settings.api_url_scale;
oai_settings.show_external_models = settings.show_external_models ?? default_settings.show_external_models;
oai_settings.proxy_password = settings.proxy_password ?? default_settings.proxy_password;
oai_settings.assistant_prefill = settings.assistant_prefill ?? default_settings.assistant_prefill;
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;
@@ -1121,6 +1125,7 @@ function loadOpenAISettings(data, settings) {
$('#stream_toggle').prop('checked', oai_settings.stream_openai);
$('#api_url_scale').val(oai_settings.api_url_scale);
$('#openai_proxy_password').val(oai_settings.proxy_password);
$('#claude_assistant_prefill').val(oai_settings.assistant_prefill);
$('#model_openai_select').val(oai_settings.openai_model);
$(`#model_openai_select option[value="${oai_settings.openai_model}"`).attr('selected', true);
@@ -1323,6 +1328,7 @@ async function saveOpenAIPreset(name, settings) {
stream_openai: settings.stream_openai,
api_url_scale: settings.api_url_scale,
show_external_models: settings.show_external_models,
assistant_prefill: settings.assistant_prefill,
};
const savePresetSettings = await fetch(`/savepreset_openai?name=${name}`, {
@@ -1656,6 +1662,7 @@ function onSettingsPresetChange() {
api_url_scale: ['#api_url_scale', 'api_url_scale', false],
show_external_models: ['#openai_show_external_models', 'show_external_models', true],
proxy_password: ['#openai_proxy_password', 'proxy_password', false],
assistant_prefill: ['#claude_assistant_prefill', 'assistant_prefill', false],
};
for (const [key, [selector, setting, isCheckbox]] of Object.entries(settingsToUpdate)) {
@@ -2206,6 +2213,11 @@ $(document).ready(function () {
saveSettingsDebounced();
});
$('#claude_assistant_prefill').on('input', function () {
oai_settings.assistant_prefill = $(this).val();
saveSettingsDebounced();
});
$("#api_button_openai").on("click", onConnectButtonClick);
$("#openai_reverse_proxy").on("input", onReverseProxyInput);
$("#model_openai_select").on("change", onModelChange);

View File

@@ -27,7 +27,7 @@ import {
import { registerSlashCommand } from "./slash-commands.js";
import { delay, debounce } from "./utils.js";
import { delay } from "./utils.js";
export {
loadPowerUserSettings,
@@ -71,6 +71,7 @@ const tokenizers = {
NERD: 4,
NERD2: 5,
API: 6,
BEST_MATCH: 99,
}
const send_on_enter_options = {
@@ -87,7 +88,7 @@ export const persona_description_positions = {
}
let power_user = {
tokenizer: tokenizers.CLASSIC,
tokenizer: tokenizers.BEST_MATCH,
token_padding: 64,
collapse_newlines: false,
pygmalion_formatting: pygmalion_options.AUTO,
@@ -163,6 +164,7 @@ let power_user = {
prefer_character_jailbreak: true,
continue_on_send: false,
trim_spaces: true,
relaxed_api_urls: false,
instruct: {
enabled: false,
@@ -176,6 +178,7 @@ let power_user = {
preset: 'Alpaca',
separator_sequence: '',
macro: false,
names_force_groups: true,
},
personas: {},
@@ -184,8 +187,10 @@ let power_user = {
persona_description: '',
persona_description_position: persona_description_positions.BEFORE_CHAR,
persona_show_notifications: true,
custom_stopping_strings: '',
custom_stopping_strings_macro: true,
fuzzy_search: false,
};
@@ -670,6 +675,7 @@ function loadPowerUserSettings(settings, data) {
power_user.chat_width = 50;
}
$('#relaxed_api_urls').prop("checked", power_user.relaxed_api_urls);
$('#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);
@@ -677,7 +683,9 @@ function loadPowerUserSettings(settings, data) {
$('#auto_swipe_blacklist').val(power_user.auto_swipe_blacklist.join(", "));
$('#auto_swipe_blacklist_threshold').val(power_user.auto_swipe_blacklist_threshold);
$('#custom_stopping_strings').val(power_user.custom_stopping_strings);
$("#custom_stopping_strings_macro").prop("checked", power_user.custom_stopping_strings_macro);
$('#fuzzy_search_checkbox').prop("checked", power_user.fuzzy_search);
$('#persona_show_notifications').prop("checked", power_user.persona_show_notifications);
$("#console_log_prompts").prop("checked", power_user.console_log_prompts);
$('#auto_fix_generated_markdown').prop("checked", power_user.auto_fix_generated_markdown);
@@ -848,8 +856,13 @@ function loadInstructMode() {
{ id: "instruct_stop_sequence", property: "stop_sequence", isCheckbox: false },
{ id: "instruct_names", property: "names", isCheckbox: true },
{ id: "instruct_macro", property: "macro", isCheckbox: true },
{ id: "instruct_names_force_groups", property: "names_force_groups", isCheckbox: true },
];
if (power_user.instruct.names_force_groups === undefined) {
power_user.instruct.names_force_groups = true;
}
controls.forEach(control => {
const $element = $(`#${control.id}`);
@@ -860,7 +873,7 @@ function loadInstructMode() {
}
$element.on('input', function () {
power_user.instruct[control.property] = control.isCheckbox ? $(this).prop('checked') : $(this).val();
power_user.instruct[control.property] = control.isCheckbox ? !!$(this).prop('checked') : $(this).val();
saveSettingsDebounced();
});
});
@@ -924,7 +937,12 @@ export function fuzzySearchCharacters(searchValue) {
}
export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvatar, name1, name2) {
const includeNames = isNarrator ? false : (power_user.instruct.names || !!selected_group || !!forceAvatar);
let includeNames = isNarrator ? false : power_user.instruct.names;
if (!isNarrator && power_user.instruct.names_force_groups && (selected_group || forceAvatar)) {
includeNames = true;
}
let sequence = (isUser || isNarrator) ? power_user.instruct.input_sequence : power_user.instruct.output_sequence;
if (power_user.instruct.macro) {
@@ -952,7 +970,7 @@ export function formatInstructStoryString(story, systemPrompt) {
}
export function formatInstructModePrompt(name, isImpersonate, promptBias, name1, name2) {
const includeNames = power_user.instruct.names || !!selected_group;
const includeNames = power_user.instruct.names || (!!selected_group && power_user.instruct.names_force_groups);
let sequence = isImpersonate ? power_user.instruct.input_sequence : power_user.instruct.output_sequence;
if (power_user.instruct.macro) {
@@ -1224,12 +1242,6 @@ async function doMesCut(_, text) {
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}]`)
@@ -1983,6 +1995,12 @@ $(document).ready(() => {
saveSettingsDebounced();
});
$("#relaxed_api_urls").on("input", function () {
const value = !!$(this).prop('checked');
power_user.relaxed_api_urls = value;
saveSettingsDebounced();
});
$('#spoiler_free_mode').on('input', function () {
power_user.spoiler_free_mode = !!$(this).prop('checked');
switchSpoilerMode();
@@ -1999,11 +2017,21 @@ $(document).ready(() => {
saveSettingsDebounced();
});
$("#custom_stopping_strings_macro").change(function () {
power_user.custom_stopping_strings_macro = !!$(this).prop("checked");
saveSettingsDebounced();
});
$('#fuzzy_search_checkbox').on('input', function () {
power_user.fuzzy_search = !!$(this).prop('checked');
saveSettingsDebounced();
});
$('#persona_show_notifications').on('input', function () {
power_user.persona_show_notifications = !!$(this).prop('checked');
saveSettingsDebounced();
});
$(window).on('focus', function () {
browser_has_focus = true;
});

View File

@@ -2,6 +2,7 @@ import { callPopup, getRequestHeaders } from "../script.js";
export const SECRET_KEYS = {
HORDE: 'api_key_horde',
MANCER: 'api_key_mancer',
OPENAI: 'api_key_openai',
NOVEL: 'api_key_novel',
CLAUDE: 'api_key_claude',
@@ -11,6 +12,7 @@ export const SECRET_KEYS = {
const INPUT_MAP = {
[SECRET_KEYS.HORDE]: '#horde_api_key',
[SECRET_KEYS.MANCER]: '#api_key_mancer',
[SECRET_KEYS.OPENAI]: '#api_key_openai',
[SECRET_KEYS.NOVEL]: '#api_key_novel',
[SECRET_KEYS.CLAUDE]: '#api_key_claude',

View File

@@ -423,7 +423,11 @@ function helpCommandCallback(_, type) {
}
}
window['displayHelp'] = (page) => helpCommandCallback(null, page);
$(document).on('click', '[data-displayHelp]', function (e) {
e.preventDefault();
const page = String($(this).data('displayhelp'));
helpCommandCallback(null, page);
});
function setBackgroundCallback(_, bg) {
if (!bg) {

View File

@@ -499,7 +499,7 @@ function onViewTagsListClick() {
$(list).append('<h3>Tags</h3><i>Click on the tag name to edit it.</i><br>');
$(list).append('<i>Click on color box to assign new color.</i><br><br>');
for (const tag of tags) {
for (const tag of tags.slice().sort((a, b) => a?.name?.localeCompare(b?.name))) {
const count = everything.filter(x => x == tag.id).length;
const template = $('#tag_view_template .tag_view_item').clone();
template.attr('id', tag.id);

View File

@@ -6,10 +6,15 @@ import {
setGenerationParamsFromPreset,
} from "../script.js";
import {
power_user,
} from "./power-user.js";
export {
textgenerationwebui_settings,
loadTextGenSettings,
generateTextGenWithStreaming,
formatTextGenURL,
}
const textgenerationwebui_settings = {
@@ -94,6 +99,23 @@ function selectPreset(name) {
saveSettingsDebounced();
}
function formatTextGenURL(value, use_mancer) {
try {
const url = new URL(value);
if (!power_user.relaxed_api_urls) {
if (use_mancer) { // If Mancer is in use, only require the URL to *end* with `/api`.
if (!url.pathname.endsWith('/api')) {
return null;
}
} else {
url.pathname = '/api';
}
}
return url.toString();
} catch { } // Just using URL as a validation check
return null;
}
function convertPresets(presets) {
return Array.isArray(presets) ? presets.map(JSON.parse) : [];
}

View File

@@ -1,154 +0,0 @@
import cloneDeep from 'lodash.clonedeep';
import userAgents from './user-agents.json';
// Normalizes the total weight to 1 and constructs a cumulative distribution.
const makeCumulativeWeightIndexPairs = (weightIndexPairs) => {
const totalWeight = weightIndexPairs.reduce((sum, [weight]) => sum + weight, 0);
let sum = 0;
return weightIndexPairs.map(([weight, index]) => {
sum += weight / totalWeight;
return [sum, index];
});
};
// Precompute these so that we can quickly generate unfiltered user agents.
const defaultWeightIndexPairs = userAgents.map(({ weight }, index) => [weight, index]);
const defaultCumulativeWeightIndexPairs = makeCumulativeWeightIndexPairs(defaultWeightIndexPairs);
// Turn the various filter formats into a single filter function that acts on raw user agents.
const constructFilter = (filters, accessor = parentObject => parentObject) => {
let childFilters;
if (typeof filters === 'function') {
childFilters = [filters];
} else if (filters instanceof RegExp) {
childFilters = [
value => (
typeof value === 'object' && value && value.userAgent
? filters.test(value.userAgent)
: filters.test(value)
),
];
} else if (filters instanceof Array) {
childFilters = filters.map(childFilter => constructFilter(childFilter));
} else if (typeof filters === 'object') {
childFilters = Object.entries(filters).map(([key, valueFilter]) => (
constructFilter(valueFilter, parentObject => parentObject[key])
));
} else {
childFilters = [
value => (
typeof value === 'object' && value && value.userAgent
? filters === value.userAgent
: filters === value
),
];
}
return (parentObject) => {
try {
const value = accessor(parentObject);
return childFilters.every(childFilter => childFilter(value));
} catch (error) {
// This happens when a user-agent lacks a nested property.
return false;
}
};
};
// Construct normalized cumulative weight index pairs given the filters.
const constructCumulativeWeightIndexPairsFromFilters = (filters) => {
if (!filters) {
return defaultCumulativeWeightIndexPairs;
}
const filter = constructFilter(filters);
const weightIndexPairs = [];
userAgents.forEach((rawUserAgent, index) => {
if (filter(rawUserAgent)) {
weightIndexPairs.push([rawUserAgent.weight, index]);
}
});
return makeCumulativeWeightIndexPairs(weightIndexPairs);
};
const setCumulativeWeightIndexPairs = (userAgent, cumulativeWeightIndexPairs) => {
Object.defineProperty(userAgent, 'cumulativeWeightIndexPairs', {
configurable: true,
enumerable: false,
writable: false,
value: cumulativeWeightIndexPairs,
});
};
export default class UserAgent extends Function {
constructor(filters) {
super();
setCumulativeWeightIndexPairs(this, constructCumulativeWeightIndexPairsFromFilters(filters));
if (this.cumulativeWeightIndexPairs.length === 0) {
throw new Error('No user agents matched your filters.');
}
this.randomize();
return new Proxy(this, {
apply: () => this.random(),
get: (target, property, receiver) => {
const dataCandidate = target.data && typeof property === 'string'
&& Object.prototype.hasOwnProperty.call(target.data, property)
&& Object.prototype.propertyIsEnumerable.call(target.data, property);
if (dataCandidate) {
const value = target.data[property];
if (value !== undefined) {
return value;
}
}
return Reflect.get(target, property, receiver);
},
});
}
static random = (filters) => {
try {
return new UserAgent(filters);
} catch (error) {
return null;
}
};
//
// Standard Object Methods
//
[Symbol.toPrimitive] = () => (
this.data.userAgent
);
toString = () => (
this.data.userAgent
);
random = () => {
const userAgent = new UserAgent();
setCumulativeWeightIndexPairs(userAgent, this.cumulativeWeightIndexPairs);
userAgent.randomize();
return userAgent;
};
randomize = () => {
// Find a random raw random user agent.
const randomNumber = Math.random();
const [, index] = this.cumulativeWeightIndexPairs
.find(([cumulativeWeight]) => cumulativeWeight > randomNumber);
const rawUserAgent = userAgents[index];
this.data = cloneDeep(rawUserAgent);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,10 @@ export function onlyUnique(value, index, array) {
return array.indexOf(value) === index;
}
export function isDigitsOnly(str) {
return /^\d+$/.test(str);
}
export function shuffle(array) {
let currentIndex = array.length,
randomIndex;
@@ -470,6 +474,20 @@ export function getCharaFilename(chid) {
}
}
export function extractAllWords(value) {
const words = [];
if (!value) {
return words;
}
const matches = value.matchAll(/\b\w+\b/gim);
for (let match of matches) {
words.push(match[0].toLowerCase());
}
return words;
}
export function escapeRegex(string) {
return string.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&');
}

View File

@@ -10,6 +10,7 @@ export {
world_info_budget,
world_info_depth,
world_info_recursive,
world_info_overflow_alert,
world_info_case_sensitive,
world_info_match_whole_words,
world_info_character_strategy,
@@ -32,6 +33,7 @@ let world_names;
let world_info_depth = 2;
let world_info_budget = 25;
let world_info_recursive = false;
let world_info_overflow_alert = false;
let world_info_case_sensitive = false;
let world_info_match_whole_words = false;
let world_info_character_strategy = world_info_insertion_strategy.character_first;
@@ -70,6 +72,8 @@ function setWorldInfoSettings(settings, data) {
world_info_budget = Number(settings.world_info_budget);
if (settings.world_info_recursive !== undefined)
world_info_recursive = Boolean(settings.world_info_recursive);
if (settings.world_info_overflow_alert !== undefined)
world_info_overflow_alert = Boolean(settings.world_info_overflow_alert);
if (settings.world_info_case_sensitive !== undefined)
world_info_case_sensitive = Boolean(settings.world_info_case_sensitive);
if (settings.world_info_match_whole_words !== undefined)
@@ -102,6 +106,7 @@ function setWorldInfoSettings(settings, data) {
$("#world_info_budget").val(world_info_budget);
$("#world_info_recursive").prop('checked', world_info_recursive);
$("#world_info_overflow_alert").prop('checked', world_info_overflow_alert);
$("#world_info_case_sensitive").prop('checked', world_info_case_sensitive);
$("#world_info_match_whole_words").prop('checked', world_info_match_whole_words);
@@ -1020,6 +1025,10 @@ async function checkWorldInfo(chat, maxContext) {
if (textToScanTokens + getTokenCount(newContent) >= budget) {
console.debug(`WI budget reached, stopping`);
if (world_info_overflow_alert) {
console.log("Alerting");
toastr.warning(`World info budget reached after ${count} entries.`, 'World Info');
}
needsToScan = false;
break;
}
@@ -1501,6 +1510,11 @@ jQuery(() => {
saveSettingsDebounced();
});
$('#world_info_overflow_alert').on('change', function () {
world_info_overflow_alert = !!$(this).prop('checked');
saveSettingsDebounced();
});
$('#world_button').on('click', async function () {
const chid = $('#set_character_world').data('chid');

View File

@@ -217,12 +217,18 @@ table.responsiveTable {
font-weight: 500;
}
.mes_text q,
.mes_text blockquote {
.mes_text q {
color: var(--SmartThemeQuoteColor);
font-weight: 500;
}
.mes_text blockquote {
border-left: 3px solid var(--SmartThemeQuoteColor);
padding-left: 10px;
background-color: var(--black30a);
margin: 0;
}
.mes_text strong em,
.mes_text strong,
.mes_text h2,
@@ -609,7 +615,7 @@ hr {
display: flex;
column-gap: 10px;
cursor: pointer;
align-items: baseline;
}
#extensionsMenu>div,
@@ -948,10 +954,6 @@ select {
white-space: nowrap;
}
#rm_ch_create_block textarea {
min-height: 190px;
}
.margin-bot-10px,
.marginBot10 {
margin-bottom: 10px;
@@ -1293,7 +1295,8 @@ body.charListGrid #rm_print_characters_block .tags_inline {
}
.floating_prompt_radio_group {
.floating_prompt_radio_group,
.radio_group {
display: flex;
flex-direction: column;
}
@@ -1717,11 +1720,10 @@ body.big-avatars .ch_description {
#form_create {
display: grid;
display: flex;
flex-direction: column;
height: 100%;
overflow-y: auto;
grid-template-rows:
[avatar] min-content [hr] min-content [descriptionHeader] min-content [description] auto [firstmessageHeader] min-content [firstMessage] auto [hidden] min-content;
}
.avatar_div {
@@ -1752,7 +1754,6 @@ body.big-avatars #avatar_div_div.avatar img {
#avatar-and-name-block {
justify-content: space-between;
display: flex;
flex: 0 0 100%;
flex-wrap: wrap;
/* margin-bottom: 4px; */
}
@@ -3040,9 +3041,23 @@ h5 {
opacity: 0.4;
}
.PastChat_cross:hover {
color: red;
filter: drop-shadow(0 0 2px red);
-webkit-animation: infinite-spinning 1s ease-out 0s infinite normal;
animation: infinite-spinning 1s ease-out 0s infinite normal;
}
/* HEINOUS */
@keyframes infinite-spinning {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
#export_character_div {
@@ -4409,6 +4424,10 @@ toolcool-color-picker {
width: 50px;
}
.indent20p {
margin-left: 20px;
}
.wi-enter-footer-text {
font-size: calc(var(--mainFontSize) * 0.8);
color: var(--SmartThemeBodyColor);
@@ -5083,11 +5102,6 @@ body.waifuMode .zoomed_avatar {
margin: 5px auto;
}
#form_create {
grid-template-rows:
[avatar] min-content [hr] min-content [descriptionHeader] min-content [description] auto [firstmessageHeader] min-content [firstMessage] auto;
}
#result_info {
font-size: calc(var(--mainFontSize) - .1rem);
}
@@ -5103,10 +5117,6 @@ body.waifuMode .zoomed_avatar {
height: calc(100% - 40px);
}
#rm_ch_create_block textarea {
max-height: 190px;
}
.drawer25pWidth {
flex-basis: max(calc(100% / 4 - 10px), 190px);
}

266
server.js
View File

@@ -34,7 +34,11 @@ if (net.setDefaultAutoSelectFamily) {
}
const cliArguments = yargs(hideBin(process.argv))
.option('ssl', {
.option('disableCsrf', {
type: 'boolean',
default: false,
describe: 'Disables CSRF protection'
}).option('ssl', {
type: 'boolean',
default: false,
describe: 'Enables SSL'
@@ -119,10 +123,15 @@ const allowKeysExposure = config.allowKeysExposure;
const axios = require('axios');
const tiktoken = require('@dqbd/tiktoken');
const WebSocket = require('ws');
const AIHorde = require("./src/horde");
const ai_horde = new AIHorde({
client_agent: getVersion()?.agent || 'SillyTavern:UNKNOWN:Cohee#1207',
});
function getHordeClient() {
const AIHorde = require("./src/horde");
const ai_horde = new AIHorde({
client_agent: getVersion()?.agent || 'SillyTavern:UNKNOWN:Cohee#1207',
});
return ai_horde;
}
const ipMatching = require('ip-matching');
const yauzl = require('yauzl');
@@ -146,6 +155,14 @@ let response_getstatus;
let first_run = true;
function get_mancer_headers() {
const api_key_mancer = readSecret(SECRET_KEYS.MANCER);
return api_key_mancer ? { "X-API-KEY": api_key_mancer } : {};
}
//RossAscends: Added function to format dates used in files and chat timestamps to a humanized format.
//Mostly I wanted this to be for file names, but couldn't figure out exactly where the filename save code was as everything seemed to be connected.
//During testing, this performs the same as previous date.now() structure.
@@ -178,13 +195,19 @@ async function loadSentencepieceTokenizer(modelPath) {
async function countSentencepieceTokens(spp, text) {
// Fallback to strlen estimation
if (!spp) {
return Math.ceil(text.length / CHARS_PER_TOKEN);
return {
ids: [],
count: Math.ceil(text.length / CHARS_PER_TOKEN)
};
}
let cleaned = cleanText(text);
let cleaned = text; // cleanText(text); <-- cleaning text can result in an incorrect tokenization
let ids = spp.encodeIds(cleaned);
return ids.length;
return {
ids,
count: ids.length
};
}
async function loadClaudeTokenizer(modelPath) {
@@ -290,34 +313,44 @@ const directories = {
instruct: 'public/instruct',
context: 'public/context',
backups: 'backups/',
quickreplies: 'public/QuickReplies'
};
// CSRF Protection //
const doubleCsrf = require('csrf-csrf').doubleCsrf;
if (cliArguments.disableCsrf === false) {
const doubleCsrf = require('csrf-csrf').doubleCsrf;
const CSRF_SECRET = crypto.randomBytes(8).toString('hex');
const COOKIES_SECRET = crypto.randomBytes(8).toString('hex');
const CSRF_SECRET = crypto.randomBytes(8).toString('hex');
const COOKIES_SECRET = crypto.randomBytes(8).toString('hex');
const { generateToken, doubleCsrfProtection } = doubleCsrf({
getSecret: () => CSRF_SECRET,
cookieName: "X-CSRF-Token",
cookieOptions: {
httpOnly: true,
sameSite: "strict",
secure: false
},
size: 64,
getTokenFromRequest: (req) => req.headers["x-csrf-token"]
});
app.get("/csrf-token", (req, res) => {
res.json({
"token": generateToken(res)
const { generateToken, doubleCsrfProtection } = doubleCsrf({
getSecret: () => CSRF_SECRET,
cookieName: "X-CSRF-Token",
cookieOptions: {
httpOnly: true,
sameSite: "strict",
secure: false
},
size: 64,
getTokenFromRequest: (req) => req.headers["x-csrf-token"]
});
});
app.use(cookieParser(COOKIES_SECRET));
app.use(doubleCsrfProtection);
app.get("/csrf-token", (req, res) => {
res.json({
"token": generateToken(res)
});
});
app.use(cookieParser(COOKIES_SECRET));
app.use(doubleCsrfProtection);
} else {
console.warn("\nCSRF protection is disabled. This will make your server vulnerable to CSRF attacks.\n");
app.get("/csrf-token", (req, res) => {
res.json({
"token": 'disabled'
});
});
}
// CORS Settings //
const cors = require('cors');
@@ -505,8 +538,16 @@ app.post("/generate", jsonParser, async function (request, response_generate = r
return response.body.pipe(response_generate);
} else {
if (!response.ok) {
console.log(`Kobold returned error: ${response.status} ${response.statusText} ${await response.text()}`);
return response.status(response.status).send({ error: true });
const errorText = await response.text();
console.log(`Kobold returned error: ${response.status} ${response.statusText} ${errorText}`);
try {
const errorJson = JSON.parse(errorText);
const message = errorJson?.detail?.msg || errorText;
return response_generate.status(400).send({ error: { message } });
} catch {
return response_generate.status(400).send({ error: { message: errorText } });
}
}
const data = await response.json();
@@ -625,13 +666,22 @@ app.post("/generate_textgenerationwebui", jsonParser, async function (request, r
signal: controller.signal,
};
if (request.body.use_mancer) {
args.headers = Object.assign(args.headers, get_mancer_headers());
}
try {
const data = await postAsync(api_server + "/v1/generate", args);
console.log(data);
return response_generate.send(data);
} catch (error) {
retval = { error: true, status: error.status, response: error.statusText };
console.log(error);
return response_generate.send({ error: true });
try {
retval.response = await error.json();
retval.response = retval.response.result;
} catch { }
return response_generate.send(retval);
}
}
});
@@ -695,6 +745,11 @@ app.post("/getstatus", jsonParser, async function (request, response_getstatus =
var args = {
headers: { "Content-Type": "application/json" }
};
if (main_api == 'textgenerationwebui' && request.body.use_mancer) {
args.headers = Object.assign(args.headers, get_mancer_headers());
}
var url = api_server + "/v1/model";
let version = '';
let koboldVersion = {};
@@ -715,18 +770,18 @@ app.post("/getstatus", jsonParser, async function (request, response_getstatus =
};
}
}
client.get(url, args, function (data, response) {
client.get(url, args, async function (data, response) {
if (typeof data !== 'object') {
data = {};
}
if (response.statusCode == 200) {
data.version = version;
data.koboldVersion = koboldVersion;
if (data.result != "ReadOnly") {
} else {
if (data.result == "ReadOnly") {
data.result = "no_connection";
}
} else {
data.response = data.result;
data.result = "no_connection";
}
response_getstatus.send(data);
@@ -1132,7 +1187,7 @@ app.post("/deletecharacter", jsonParser, async function (request, response) {
return response.sendStatus(403);
}
if (request.body.delete_chats == 'true') {
if (request.body.delete_chats == true) {
try {
await fs.promises.rm(path.join(chatsPath, sanitize(dir_name)), { recursive: true, force: true })
} catch (err) {
@@ -1600,6 +1655,8 @@ app.post('/getsettings', jsonParser, (request, response) => {
const themes = readAndParseFromDirectory(directories.themes);
const movingUIPresets = readAndParseFromDirectory(directories.movingUI);
const quickReplyPresets = readAndParseFromDirectory(directories.quickreplies);
const instruct = readAndParseFromDirectory(directories.instruct);
const context = readAndParseFromDirectory(directories.context);
@@ -1616,6 +1673,7 @@ app.post('/getsettings', jsonParser, (request, response) => {
textgenerationwebui_preset_names,
themes,
movingUIPresets,
quickReplyPresets,
instruct,
context,
enable_extensions: enableExtensions,
@@ -1672,6 +1730,17 @@ app.post('/savemovingui', jsonParser, (request, response) => {
return response.sendStatus(200);
});
app.post('/savequickreply', jsonParser, (request, response) => {
if (!request.body || !request.body.name) {
return response.sendStatus(400);
}
const filename = path.join(directories.quickreplies, sanitize(request.body.name) + '.json');
fs.writeFileSync(filename, JSON.stringify(request.body, null, 4), 'utf8');
return response.sendStatus(200);
});
function convertWorldInfoToCharacterBook(name, entries) {
const result = { entries: [], name };
@@ -1740,13 +1809,12 @@ app.post("/getstatus_novelai", jsonParser, function (request, response_getstatus
const api_key_novel = readSecret(SECRET_KEYS.NOVEL);
if (!api_key_novel) {
return response_generate_novel.sendStatus(401);
return response_getstatus_novel.sendStatus(401);
}
var data = {};
var args = {
data: data,
headers: { "Content-Type": "application/json", "Authorization": "Bearer " + api_key_novel }
};
client.get(api_novelai + "/user/subscription", args, function (data, response) {
@@ -1754,17 +1822,15 @@ app.post("/getstatus_novelai", jsonParser, function (request, response_getstatus
//console.log(data);
response_getstatus_novel.send(data);//data);
}
if (response.statusCode == 401) {
console.log('Access Token is incorrect.');
response_getstatus_novel.send({ error: true });
}
if (response.statusCode == 500 || response.statusCode == 501 || response.statusCode == 501 || response.statusCode == 503 || response.statusCode == 507) {
else {
if (response.statusCode == 401) {
console.log('Access Token is incorrect.');
}
console.log(data);
response_getstatus_novel.send({ error: true });
}
}).on('error', function () {
//console.log('');
//console.log('something went wrong on the request', err.request.options);
response_getstatus_novel.send({ error: true });
});
});
@@ -1784,9 +1850,9 @@ app.post("/generate_novelai", jsonParser, async function (request, response_gene
controller.abort();
});
console.log(request.body);
const bw = require('./src/bad-words');
const novelai = require('./src/novelai');
const isNewModel = (request.body.model.includes('clio') || request.body.model.includes('kayra'));
const isKrake = request.body.model.includes('krake');
const data = {
"input": request.body.input,
"model": request.body.model,
@@ -1801,6 +1867,7 @@ app.post("/generate_novelai", jsonParser, async function (request, response_gene
"repetition_penalty_slope": request.body.repetition_penalty_slope,
"repetition_penalty_frequency": request.body.repetition_penalty_frequency,
"repetition_penalty_presence": request.body.repetition_penalty_presence,
"repetition_penalty_whitelist": isNewModel ? novelai.repPenaltyAllowList : null,
"top_a": request.body.top_a,
"top_p": request.body.top_p,
"top_k": request.body.top_k,
@@ -1808,16 +1875,20 @@ app.post("/generate_novelai", jsonParser, async function (request, response_gene
"cfg_scale": request.body.cfg_scale,
"cfg_uc": request.body.cfg_uc,
"phrase_rep_pen": request.body.phrase_rep_pen,
"stop_sequences": request.body.stop_sequences,
//"stop_sequences": {{187}},
"bad_words_ids": isNewModel ? bw.clioBadWordsId : bw.badWordIds,
"bad_words_ids": isNewModel ? novelai.badWordsList : (isKrake ? novelai.krakeBadWordsList : novelai.euterpeBadWordsList),
"logit_bias_exp": isNewModel ? novelai.logitBiasExp : null,
//generate_until_sentence = true;
"use_cache": request.body.use_cache,
"use_string": true,
"return_full_text": request.body.return_full_text,
"prefix": isNewModel ? "special_instruct" : request.body.prefix,
"prefix": request.body.prefix,
"order": request.body.order
}
};
const util = require('util');
console.log(util.inspect(data, { depth: 4 }))
const args = {
body: JSON.stringify(data),
@@ -1845,8 +1916,19 @@ app.post("/generate_novelai", jsonParser, async function (request, response_gene
});
} else {
if (!response.ok) {
console.log(`Novel API returned error: ${response.status} ${response.statusText} ${await response.text()}`);
return response.status(response.status).send({ error: true });
const text = await response.text();
let message = text;
console.log(`Novel API returned error: ${response.status} ${response.statusText} ${text}`);
try {
const data = JSON.parse(text);
message = data.message;
}
catch {
// ignore
}
return response_generate_novel.status(response.status).send({ error: { message } });
}
const data = await response.json();
@@ -1904,14 +1986,16 @@ app.post("/getallchatsofcharacter", jsonParser, function (request, response) {
ii--;
if (lastLine) {
let jsonData = json5.parse(lastLine);
if (jsonData.name !== undefined || jsonData.character_name !== undefined) {
let jsonData = tryParse(lastLine);
if (jsonData && (jsonData.name !== undefined || jsonData.character_name !== undefined)) {
chatData[i] = {};
chatData[i]['file_name'] = file;
chatData[i]['file_size'] = fileSizeInKB;
chatData[i]['chat_items'] = itemCounter - 1;
chatData[i]['mes'] = jsonData['mes'] || '[The chat is empty]';
chatData[i]['last_mes'] = jsonData['send_date'] || Date.now();
} else {
console.log('Found an invalid or corrupted chat file: ' + fullPathAndFile);
}
}
if (ii === 0) {
@@ -3077,7 +3161,12 @@ async function sendClaudeRequest(request, response) {
controller.abort();
});
const requestPrompt = convertClaudePrompt(request.body.messages, true, true);
let requestPrompt = convertClaudePrompt(request.body.messages, true, true);
if (request.body.assistant_prefill) {
requestPrompt += request.body.assistant_prefill;
}
console.log('Claude request:', requestPrompt);
const generateResponse = await fetch(api_url + '/complete', {
@@ -3150,16 +3239,19 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
let api_url;
let api_key_openai;
let headers;
let bodyParams;
if (!request.body.use_openrouter) {
api_url = new URL(request.body.reverse_proxy || api_openai).toString();
api_key_openai = request.body.reverse_proxy ? request.body.proxy_password : readSecret(SECRET_KEYS.OPENAI);
headers = {};
bodyParams = {};
} 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 };
bodyParams = { 'transforms': ["middle-out"] };
}
if (!api_key_openai && !request.body.reverse_proxy) {
@@ -3196,7 +3288,8 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
"top_p": request.body.top_p,
"top_k": request.body.top_k,
"stop": request.body.stop,
"logit_bias": request.body.logit_bias
"logit_bias": request.body.logit_bias,
...bodyParams,
},
signal: controller.signal,
};
@@ -3234,7 +3327,22 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
makeRequest(config, response_generate_openai, request, retries - 1);
}, timeout);
} else {
handleError(error, response_generate_openai, request);
let errorData = error?.response?.data;
if (request.body.stream) {
try {
const chunks = await readAllChunks(errorData);
const blob = new Blob(chunks, { type: 'application/json' });
const text = await blob.text();
errorData = JSON.parse(text);
} catch {
console.warn('Error parsing streaming response');
}
} else {
errorData = typeof errorData === 'string' ? tryParse(errorData) : errorData;
}
handleError(error, response_generate_openai, errorData);
}
}
}
@@ -3246,27 +3354,28 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
}
}
function handleError(error, response_generate_openai, request) {
console.error('Error:', error.message);
function handleError(error, response_generate_openai, errorData) {
console.error('Error:', error?.message);
let message = error?.response?.statusText;
switch (error?.response?.status) {
case 402:
message = 'Credit limit reached';
console.log(message);
break;
case 403:
message = 'API key disabled or exhausted';
console.log(message);
break;
case 451:
message = error?.response?.data?.error?.message || 'Unavailable for legal reasons';
console.log(message);
break;
const statusMessages = {
400: 'Bad request',
401: 'Unauthorized',
402: 'Credit limit reached',
403: 'Forbidden',
404: 'Not found',
429: 'Too many requests',
451: 'Unavailable for legal reasons',
};
const status = error?.response?.status;
if (statusMessages.hasOwnProperty(status)) {
message = errorData?.error?.message || statusMessages[status];
console.log(message);
}
const quota_error = error?.response?.status === 429 && error?.response?.data?.error?.type === 'insufficient_quota';
const quota_error = error?.response?.status === 429 && errorData?.error?.type === 'insufficient_quota';
const response = { error: { message }, quota_error: quota_error }
if (!response_generate_openai.headersSent) {
response_generate_openai.send(response);
@@ -3391,8 +3500,8 @@ function createTokenizationHandler(getTokenizerFn) {
const text = request.body.text || '';
const tokenizer = getTokenizerFn();
const count = await countSentencepieceTokens(tokenizer, text);
return response.send({ count });
const { ids, count } = await countSentencepieceTokens(tokenizer, text);
return response.send({ ids, count });
};
}
@@ -3411,6 +3520,10 @@ app.post("/tokenize_via_api", jsonParser, async function (request, response) {
headers: { "Content-Type": "application/json" }
};
if (main_api == 'textgenerationwebui' && request.body.use_mancer) {
args.headers = Object.assign(args.headers, get_mancer_headers());
}
const data = await postAsync(api_server + "/v1/token-count", args);
console.log(data);
return response.send({ count: data['results'][0]['tokens'] });
@@ -3616,6 +3729,7 @@ const SECRETS_FILE = './secrets.json';
const SETTINGS_FILE = './public/settings.json';
const SECRET_KEYS = {
HORDE: 'api_key_horde',
MANCER: 'api_key_mancer',
OPENAI: 'api_key_openai',
NOVEL: 'api_key_novel',
CLAUDE: 'api_key_claude',
@@ -3747,6 +3861,7 @@ app.post('/viewsecrets', jsonParser, async (_, response) => {
app.post('/horde_samplers', jsonParser, async (_, response) => {
try {
const ai_horde = getHordeClient();
const samplers = Object.values(ai_horde.ModelGenerationInputStableSamplers);
response.send(samplers);
} catch (error) {
@@ -3757,6 +3872,7 @@ app.post('/horde_samplers', jsonParser, async (_, response) => {
app.post('/horde_models', jsonParser, async (_, response) => {
try {
const ai_horde = getHordeClient();
const models = await ai_horde.getModels();
response.send(models);
} catch (error) {
@@ -3773,6 +3889,7 @@ app.post('/horde_userinfo', jsonParser, async (_, response) => {
}
try {
const ai_horde = getHordeClient();
const user = await ai_horde.findUser({ token: api_key_horde });
return response.send(user);
} catch (error) {
@@ -3788,6 +3905,7 @@ app.post('/horde_generateimage', jsonParser, async (request, response) => {
console.log('Stable Horde request:', request.body);
try {
const ai_horde = getHordeClient();
const generation = await ai_horde.postAsyncImageGenerate(
{
prompt: `${request.body.prompt_prefix} ${request.body.prompt} ### ${request.body.negative_prompt}`,

View File

@@ -1,326 +0,0 @@
const badWordIds = [
[60],
[62],
[544],
[683],
[696],
[880],
[905],
[1008],
[1019],
[1084],
[1092],
[1181],
[1184],
[1254],
[1447],
[1570],
[1656],
[2194],
[2470],
[2479],
[2498],
[2947],
[3138],
[3291],
[3455],
[3725],
[3851],
[3891],
[3921],
[3951],
[4207],
[4299],
[4622],
[4681],
[5013],
[5032],
[5180],
[5218],
[5290],
[5413],
[5456],
[5709],
[5749],
[5774],
[6038],
[6257],
[6334],
[6660],
[6904],
[7082],
[7086],
[7254],
[7444],
[7748],
[8001],
[8088],
[8168],
[8562],
[8605],
[8795],
[8850],
[9014],
[9102],
[9259],
[9318],
[9336],
[9502],
[9686],
[9793],
[9855],
[9899],
[9955],
[10148],
[10174],
[10943],
[11326],
[11337],
[11661],
[12004],
[12084],
[12159],
[12520],
[12977],
[13380],
[13488],
[13663],
[13811],
[13976],
[14412],
[14598],
[14767],
[15640],
[15707],
[15775],
[15830],
[16079],
[16354],
[16369],
[16445],
[16595],
[16614],
[16731],
[16943],
[17278],
[17281],
[17548],
[17555],
[17981],
[18022],
[18095],
[18297],
[18413],
[18736],
[18772],
[18990],
[19181],
[20095],
[20197],
[20481],
[20629],
[20871],
[20879],
[20924],
[20977],
[21375],
[21382],
[21391],
[21687],
[21810],
[21828],
[21938],
[22367],
[22372],
[22734],
[23405],
[23505],
[23734],
[23741],
[23781],
[24237],
[24254],
[24345],
[24430],
[25416],
[25896],
[26119],
[26635],
[26842],
[26991],
[26997],
[27075],
[27114],
[27468],
[27501],
[27618],
[27655],
[27720],
[27829],
[28052],
[28118],
[28231],
[28532],
[28571],
[28591],
[28653],
[29013],
[29547],
[29650],
[29925],
[30522],
[30537],
[30996],
[31011],
[31053],
[31096],
[31148],
[31258],
[31350],
[31379],
[31422],
[31789],
[31830],
[32214],
[32666],
[32871],
[33094],
[33376],
[33440],
[33805],
[34368],
[34398],
[34417],
[34418],
[34419],
[34476],
[34494],
[34607],
[34758],
[34761],
[34904],
[34993],
[35117],
[35138],
[35237],
[35487],
[35830],
[35869],
[36033],
[36134],
[36320],
[36399],
[36487],
[36586],
[36676],
[36692],
[36786],
[37077],
[37594],
[37596],
[37786],
[37982],
[38475],
[38791],
[39083],
[39258],
[39487],
[39822],
[40116],
[40125],
[41000],
[41018],
[41256],
[41305],
[41361],
[41447],
[41449],
[41512],
[41604],
[42041],
[42274],
[42368],
[42696],
[42767],
[42804],
[42854],
[42944],
[42989],
[43134],
[43144],
[43189],
[43521],
[43782],
[44082],
[44162],
[44270],
[44308],
[44479],
[44524],
[44965],
[45114],
[45301],
[45382],
[45443],
[45472],
[45488],
[45507],
[45564],
[45662],
[46265],
[46267],
[46275],
[46295],
[46462],
[46468],
[46576],
[46694],
[47093],
[47384],
[47389],
[47446],
[47552],
[47686],
[47744],
[47916],
[48064],
[48167],
[48392],
[48471],
[48664],
[48701],
[49021],
[49193],
[49236],
[49550],
[49694],
[49806],
[49824],
[50001],
[50256],
[0],
[1],
]
const clioBadWordsId = [
[3],
[49356],
[1431],
[31715],
[34387],
[20765],
[30702],
[10691],
[49333],
[1266],
[19438],
[43145],
[26523],
[41471],
[2936],
]
module.exports = {
badWordIds,
clioBadWordsId,
};

View File

@@ -61,6 +61,11 @@ const parse = async (cardUrl, format) => {
return PNGtext.decode(chunk.data);
});
if (textChunks.length === 0) {
console.error('PNG metadata does not contain any character data.');
throw new Error('No PNG metadata.');
}
return Buffer.from(textChunks[0].text, 'base64').toString('utf8');
default:
break;

75
src/novelai.js Normal file
View File

@@ -0,0 +1,75 @@
// Ban bracket generation, plus defaults
const euterpeBadWordsList = [
[8162], [17202], [8162], [17202], [8162], [17202], [8162], [17202], [8162], [17202], [46256, 224], [2343, 223, 224],
[46256, 224], [2343, 223, 224], [46256, 224], [2343, 223, 224], [46256, 224], [2343, 223, 224], [46256, 224],
[2343, 223, 224], [58], [60], [90], [92], [685], [1391], [1782], [2361], [3693], [4083], [4357], [4895], [5512],
[5974], [7131], [8183], [8351], [8762], [8964], [8973], [9063], [11208], [11709], [11907], [11919], [12878], [12962],
[13018], [13412], [14631], [14692], [14980], [15090], [15437], [16151], [16410], [16589], [17241], [17414], [17635],
[17816], [17912], [18083], [18161], [18477], [19629], [19779], [19953], [20520], [20598], [20662], [20740], [21476],
[21737], [22133], [22241], [22345], [22935], [23330], [23785], [23834], [23884], [25295], [25597], [25719], [25787],
[25915], [26076], [26358], [26398], [26894], [26933], [27007], [27422], [28013], [29164], [29225], [29342], [29565],
[29795], [30072], [30109], [30138], [30866], [31161], [31478], [32092], [32239], [32509], [33116], [33250], [33761],
[34171], [34758], [34949], [35944], [36338], [36463], [36563], [36786], [36796], [36937], [37250], [37913], [37981],
[38165], [38362], [38381], [38430], [38892], [39850], [39893], [41832], [41888], [42535], [42669], [42785], [42924],
[43839], [44438], [44587], [44926], [45144], [45297], [46110], [46570], [46581], [46956], [47175], [47182], [47527],
[47715], [48600], [48683], [48688], [48874], [48999], [49074], [49082], [49146], [49946], [10221], [4841], [1427],
[2602, 834], [29343], [37405], [35780], [2602], [50256],
]
// Ban bracket generation, plus defaults
const krakeBadWordsList = [
[9264], [14244], [9264], [14244], [9264], [14244], [9264], [14244], [9264], [14244], [25086, 213], [27344, 213],
[25086, 213], [27344, 213], [25086, 213], [27344, 213], [25086, 213], [27344, 213], [25086, 213], [27344, 213], [60],
[62], [544], [683], [696], [880], [905], [1008], [1019], [1084], [1092], [1181], [1184], [1254], [1447], [1570], [1656],
[2194], [2470], [2479], [2498], [2947], [3138], [3291], [3455], [3725], [3851], [3891], [3921], [3951], [4207], [4299],
[4622], [4681], [5013], [5032], [5180], [5218], [5290], [5413], [5456], [5709], [5749], [5774], [6038], [6257], [6334],
[6660], [6904], [7082], [7086], [7254], [7444], [7748], [8001], [8088], [8168], [8562], [8605], [8795], [8850], [9014],
[9102], [9259], [9318], [9336], [9502], [9686], [9793], [9855], [9899], [9955], [10148], [10174], [10943], [11326],
[11337], [11661], [12004], [12084], [12159], [12520], [12977], [13380], [13488], [13663], [13811], [13976], [14412],
[14598], [14767], [15640], [15707], [15775], [15830], [16079], [16354], [16369], [16445], [16595], [16614], [16731],
[16943], [17278], [17281], [17548], [17555], [17981], [18022], [18095], [18297], [18413], [18736], [18772], [18990],
[19181], [20095], [20197], [20481], [20629], [20871], [20879], [20924], [20977], [21375], [21382], [21391], [21687],
[21810], [21828], [21938], [22367], [22372], [22734], [23405], [23505], [23734], [23741], [23781], [24237], [24254],
[24345], [24430], [25416], [25896], [26119], [26635], [26842], [26991], [26997], [27075], [27114], [27468], [27501],
[27618], [27655], [27720], [27829], [28052], [28118], [28231], [28532], [28571], [28591], [28653], [29013], [29547],
[29650], [29925], [30522], [30537], [30996], [31011], [31053], [31096], [31148], [31258], [31350], [31379], [31422],
[31789], [31830], [32214], [32666], [32871], [33094], [33376], [33440], [33805], [34368], [34398], [34417], [34418],
[34419], [34476], [34494], [34607], [34758], [34761], [34904], [34993], [35117], [35138], [35237], [35487], [35830],
[35869], [36033], [36134], [36320], [36399], [36487], [36586], [36676], [36692], [36786], [37077], [37594], [37596],
[37786], [37982], [38475], [38791], [39083], [39258], [39487], [39822], [40116], [40125], [41000], [41018], [41256],
[41305], [41361], [41447], [41449], [41512], [41604], [42041], [42274], [42368], [42696], [42767], [42804], [42854],
[42944], [42989], [43134], [43144], [43189], [43521], [43782], [44082], [44162], [44270], [44308], [44479], [44524],
[44965], [45114], [45301], [45382], [45443], [45472], [45488], [45507], [45564], [45662], [46265], [46267], [46275],
[46295], [46462], [46468], [46576], [46694], [47093], [47384], [47389], [47446], [47552], [47686], [47744], [47916],
[48064], [48167], [48392], [48471], [48664], [48701], [49021], [49193], [49236], [49550], [49694], [49806], [49824],
[50001], [50256], [0], [1]
]
// Ban bracket generation, plus defaults
const badWordsList = [
[23], [49209, 23], [23], [49209, 23], [23], [49209, 23], [23], [49209, 23], [23], [49209, 23], [21], [49209, 21],
[21], [49209, 21], [21], [49209, 21], [21], [49209, 21], [21], [49209, 21], [3], [49356], [1431], [31715], [34387],
[20765], [30702], [10691], [49333], [1266], [26523], [41471], [2936], [85, 85], [49332], [7286], [1115]
]
// Used for phrase repetition penalty
const repPenaltyAllowList = [
[49256, 49264, 49231, 49230, 49287, 85, 49255, 49399, 49262, 336, 333, 432, 363, 468, 492, 745, 401, 426, 623, 794,
1096, 2919, 2072, 7379, 1259, 2110, 620, 526, 487, 16562, 603, 805, 761, 2681, 942, 8917, 653, 3513, 506, 5301,
562, 5010, 614, 10942, 539, 2976, 462, 5189, 567, 2032, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 588,
803, 1040, 49209, 4, 5, 6, 7, 8, 9, 10, 11, 12]
]
// Ban the dinkus and asterism
const logitBiasExp = [
{ "sequence": [23], "bias": -0.08, "ensure_sequence_finish": false, "generate_once": false },
{ "sequence": [21], "bias": -0.08, "ensure_sequence_finish": false, "generate_once": false }
]
module.exports = {
euterpeBadWordsList,
krakeBadWordsList,
badWordsList,
repPenaltyAllowList,
logitBiasExp
};