Compare commits

..

312 Commits
1.9.2 ... 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
Cohee
675cabb7e3 Merge branch 'staging' into release 2023-07-29 22:58:58 +03:00
Cohee
f36cf088a1 Bump package version 2023-07-29 22:50:52 +03:00
Cohee
df8d7075a8 Handle 451 from OpenRouter 2023-07-29 22:50:07 +03:00
Cohee
314b194891 Remove preset reloading when setting is NaN 2023-07-29 21:24:29 +03:00
Cohee1207
ac4fa0e035 Merge branch 'staging' of http://github.com/cohee1207/SillyTavern into staging 2023-07-29 21:18:56 +03:00
Cohee
0e7c10b26f Merge pull request #812 from ouoertheo/ouoertheo/objective5-tree 2023-07-29 13:04:47 +03:00
Cohee
64914e778d Merge pull request #807 from 50h100a/tilde-fix 2023-07-29 13:02:58 +03:00
Cohee
763a443680 Merge pull request #809 from mweldon/kayra 2023-07-29 13:02:39 +03:00
ouoertheo
27eb95cb36 add migration 2023-07-29 01:58:26 -05:00
Mike Weldon
732707ae2b Turn on instruct mode for NAI 2023-07-28 22:56:31 -07:00
ouoertheo
ee81a8d88c add in objective tree 2023-07-28 23:21:55 -05:00
Mike Weldon
b107848ccb Add Kayra presets 2023-07-28 17:23:28 -07:00
Cohee
ec270378d4 Merge pull request #808 from 50h100a/token-counter-flicker 2023-07-29 01:59:27 +03:00
50h100a
7c3b598eea Don't flicker the token counter when recounting tokens. 2023-07-28 18:45:55 -04:00
50h100a
aed0dab95d Markdown fix: Don't eat verticals/newlines 2023-07-28 18:35:36 -04:00
50h100a
311249c5e0 Tilde does nothing in our markdown, remove it.
Match begin/end formatting symbol counts.
2023-07-28 18:09:54 -04:00
Cohee
88f3e01f72 Fix logspam in WI parser 2023-07-28 21:58:18 +03:00
Cohee
8e06d18664 #804 Add kayra model 2023-07-28 21:45:44 +03:00
Cohee
3fb4756c03 #803 Add a separate field for proxy password 2023-07-28 21:33:29 +03:00
Cohee1207
707ce62017 Advanced Character Search #786 2023-07-28 00:38:43 +03:00
Cohee1207
ddf485f354 #787 Display character version in the list 2023-07-28 00:04:21 +03:00
Cohee1207
c3f61af18a #788 Fix NovelAI tokenizer labels 2023-07-27 23:47:29 +03:00
Cohee1207
3eea8a714c #789 Add sorting by char.data strings size ("tokens") 2023-07-27 23:45:25 +03:00
Cohee1207
49186dbfc1 #796 Round portrait height to nearest 64 2023-07-27 23:38:08 +03:00
Cohee1207
87b9da11c6 Merge branch 'staging' of http://github.com/cohee1207/SillyTavern into staging 2023-07-27 23:35:02 +03:00
Cohee
77124056b8 Merge pull request #795 from Tony-sama/staging
Feature: Speech-to-text module using Vosk, Whisper. (one-hand mod only)
2023-07-27 23:29:57 +03:00
Tony Ribeiro
8d794ed03f merge new speech-recognition module with staging branch. 2023-07-27 19:29:36 +02:00
Cohee
d4e05c99e3 Merge pull request #792 from ouoertheo/ouoertheo/objective5
Objective: simple css fix for prompt edit
2023-07-27 15:28:14 +03:00
ouoertheo
111b91b1ed simple css fix for prompt edit 2023-07-27 07:26:05 -05:00
Cohee
bed6b2d777 Merge pull request #791 from pyrater/staging
Fix for dropdown list not populating
2023-07-27 15:15:25 +03:00
joe
99a18be5fc Fix for dropdown list not populating 2023-07-27 20:04:01 +09:00
Cohee
ccf5454750 Update readme.md 2023-07-27 12:17:27 +03:00
Cohee
62e5f71cf9 Reformat coqui code 2023-07-26 22:57:05 +03:00
Cohee
f56834bb96 Merge pull request #775 from pyrater/staging
Coqui TTS Addition
2023-07-26 21:41:47 +03:00
Cohee
68819a1afc Add openrouter to /api slash command 2023-07-26 21:22:57 +03:00
Cohee
e4e2ff20a2 #781 Don't add You stopping string for non-Pygmalion models 2023-07-26 21:08:25 +03:00
Cohee
5c66c34414 #778 Slash command to add a persona message without generation 2023-07-26 21:00:36 +03:00
Cohee
1055673865 Merge pull request #783 from XXpE3/staging
add readme-zh_cn.md
2023-07-26 12:40:09 +03:00
XXpE3
7a4b8abde7 add readme-zh_cn.md 2023-07-26 17:34:22 +08:00
Cohee
1ad361593f Merge pull request #779 from ouoertheo/ouoertheo/objective4
Objective: Custom Prompts, Current Task Highlight
2023-07-25 16:58:40 +03:00
ouoertheo
ae850fdde7 protect default objective prompt, cleanup 2023-07-25 08:45:07 -05:00
ouoertheo
c8b3bce8b9 current task highlight/edit prompts 2023-07-25 08:36:27 -05:00
Cohee
c3d43a7d05 Merge pull request #776 from bdashore3/staging 2023-07-25 09:34:13 +03:00
kingbri
40f7aa47ad Generate: Ignore bias addition on continue
Continue adds to an existing prompt, so the correct parts of a
message are already present.

Signed-off-by: kingbri <bdashore3@proton.me>
2023-07-24 23:45:53 -04:00
kingbri
0e45aa7e58 Regex: Fix saving logic
- Don't save if the name is empty or undefined
- Warn if the find regex isn't found
- Warn if the "Affects" checkbox isn't selected

This allows for edits to be preserved even if a user incorrectly
changes something with the regex script itself.

Signed-off-by: kingbri <bdashore3@proton.me>
2023-07-24 23:21:54 -04:00
joe
ce3d76d662 Coqui TTS Addition 2023-07-25 09:59:08 +09:00
Cohee1207
c4209b9448 Fix naming 2023-07-25 01:13:32 +03:00
Cohee1207
83cb3456fc Run showdown output through sanitize 2023-07-25 00:07:52 +03:00
Cohee1207
e15c739e64 Merge branch 'staging' of http://github.com/cohee1207/SillyTavern into staging 2023-07-25 00:01:07 +03:00
Cohee
0df8c4b6a2 Merge pull request #774 from city-unit/feature/delete
More general/functionalized char delete methods
2023-07-24 22:45:41 +03:00
city-unit
8fdea22379 Move delete calls to functions, generalize delete endpoint. 2023-07-24 15:05:27 -04:00
Cohee
260bb0af52 Fix opening character with id = '0' in /go and /random commands 2023-07-24 17:22:51 +03:00
Cohee
cf3baccc5e Fix spelling mistakes 2023-07-24 11:18:03 +03:00
Cohee
1bc9bdd64f Merge pull request #769 from city-unit/feature/docker 2023-07-24 09:34:24 +03:00
city-unit
b2dc4c6c1b Add git to Dockerfile 2023-07-24 00:46:07 -04:00
Cohee
130559d499 #746 Add preset manager for ooba/kobold 2023-07-23 23:52:31 +03:00
Cohee
06899581db Update gitignore 2023-07-23 22:10:11 +03:00
Cohee
e9288874b1 Update RR preset 2023-07-23 22:09:59 +03:00
Cohee
b6243cdbe1 Merge branch 'release' into staging 2023-07-23 21:19:34 +03:00
Cohee
ed86e8de04 #765 Fix docker startup 2023-07-23 16:52:25 +03:00
Cohee
1b9ca4c9d9 Unhide advanced parameters for Novel 2023-07-23 02:09:03 +03:00
Cohee
dd69fd3934 Auto-scrollheight for SD char prefix 2023-07-23 00:28:23 +03:00
Cohee
3f47386cba Adapt GH actions for new branches 2023-07-23 00:28:02 +03:00
Cohee
83252617be #612 Character-specific prompt prefixes for SD 2023-07-22 23:57:48 +03:00
Cohee
a9f8506218 Possible chroma auto-adjust fix 2023-07-22 22:21:37 +03:00
Cohee
ea68e70ac7 Refine message for dupe confirm 2023-07-22 22:14:16 +03:00
Cohee
e38a9df538 Confirm character duplication 2023-07-22 21:50:21 +03:00
Cohee
d3fe20c119 Add editable prompt templates 2023-07-22 21:12:23 +03:00
Cohee
3feffaabe5 SD refine mode for paintbrush button 2023-07-22 20:32:58 +03:00
Cohee
52b2c89d64 Merge pull request #764 from toby3d/patch-1 2023-07-22 19:27:33 +03:00
Maxim Lebedev
59100a0076 Fixed tags order in Llama2 instruct preset
I didn't notice that the prompt wording implies impersonation, which is incorrect for common usage. Changed the order of tags used to generate the character's response to the user, rather than the vice versa.
2023-07-22 21:31:44 +06:00
Cohee
80fad96b1f Fix chat width when switching themes 2023-07-22 18:09:50 +03:00
Cohee
eccfadb14e #763 Fix "Continue on Send" 2023-07-22 17:46:59 +03:00
Cohee1207
01f1f67de3 Clean-up JB prompt of caret returns and dangling spaces 2023-07-22 16:50:34 +03:00
113 changed files with 5681 additions and 5648 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**

309
.github/readme-zh_cn.md vendored Normal file
View File

@@ -0,0 +1,309 @@
[English](readme.md) | 中文
![image](https://github.com/SillyTavern/SillyTavern/assets/18619528/8c41a061-7f72-4d2b-9d54-e6d058209e7b)
移动设备界面友好多种人工智能服务或模型支持KoboldAI/CPP, Horde, NovelAI, Ooba, OpenAI, OpenRouter, Claude, Scale类似 Galgame 的 老 婆 模 式Horde SD文本系统语音生成世界信息Lorebooks可定制的界面自动翻译和比你所需要的更多的 Prompt。附带扩展服务支持文本绘画生成与语音生成和基于向量数据库 ChromaDB 的聊天信息总结。
基于 TavernAI 1.2.8 的分叉版本
### 由 Cohee、RossAscends 和 SillyTavern 社区为您呈现
注意:我们创建了一个 [帮助文档](https://docs.sillytavern.app/) 网站来回答各类问题与帮助您开始使用。
### SillyTavern 或 TavernAI 是什么?
SillyTavern 是一个可以安装在电脑(和安卓手机)上的用户界面,让您可以与文本生成的人工智能互动,并与您或社区创建的角色聊天/玩角色扮演游戏。
SillyTavern 是 TavernAI 1.2.8 的一个分支,正在进行更积极地开发,并添加了许多重要功能。在这一点上,它可以被视为完全独立的程序。
### 分支
SillyTavern 采用双分支进行开发,以确保所有用户都能获得流畅的使用体验。
* release -🌟 **推荐给大多数用户。** 这是最稳定、最推荐的分支,只有在重大版本推送时才会更新。适合大多数用户使用。
* staging - ⚠️ **不建议随意使用。** 该分支拥有最新功能,但要谨慎,因为它随时可能崩溃。仅适用于高级用户和爱好者。
如果你不熟悉使用 Git 命令或者不了解什么是分支别担心release 分支始终是您的首选。
### 除了 SillyTavern我还需要什么
SillyTavern 本身并无用处因为它只是一个用户聊天界面。你必须接入一个能充当角色扮演的人工智能系统。支持的人工智能系统有多种OpenAPI API (GPT)、KoboldAI可在本地或 Google Colab 上运行)等。您可以在 [常见问题](https://docs.sillytavern.app/usage/faq/) 中阅读更多相关信息。
### 我需要一台性能强大的电脑来运行 SillyTavern 吗?
由于 SillyTavern 只是一个用户聊天界面,它对硬件性能的要求很低,可以在任何电脑上运行。需要强大性能的是人工智能系统。
### 移动设备支持
> 注意
> **此分叉可使用 Termux 在安卓手机上原生运行。请参考 ArroganceComplex#2659 编写的指南:**
<https://rentry.org/STAI-Termux>
Termux 不支持**.Webp 字符卡的导入/导出。请使用 JSON 或 PNG 格式**。
## 有问题或建议?
### 我们现在有了 Discord 社区
获取支持,或分享喜爱的角色和 Prompt
### [加入 Discord 社区](https://discord.gg/RZdyAEUPvj)
***
直接与开发人员联系:
* Discord: cohee 或 rossascends
* Reddit/u/RossAscends 或 /u/sillylossy
* [发布 GitHub 问题](https://github.com/SillyTavern/SillyTavern/issues)
## 此版本包括
* 经过大量修改的 TavernAI 1.2.8(超过 50% 的代码经过重写或优化)
* 根据自定义规则自动重新生成消息
* 群聊:多机器人房间,供角色与你或彼此交谈
* 聊天书签/分支(复制当前状态下的对话)
* 先进的 KoboldAI / TextGen 生成设置,包含大量社区预设
* 支持世界信息Lorebooks创建丰富的传说
* 支持 Window AI 浏览器扩展(运行 Claude、GPT 4 等模型):<https://windowai.io/>
* [Oobabooga's TextGen WebUI](https://github.com/oobabooga/text-generation-webui) API 连接
* 连接 [AI Horde](https://horde.koboldai.net/)
* Prompt 生成格式调整
* Webp 角色卡支持PNG 仍是内部格式)
## 扩展
SillyTavern 支持扩展服务,一些额外的人工智能模块可通过 [SillyTavern Extras API](https://github.com/SillyTavern/SillyTavern-extras) 提供。
* 作者注释/角色偏见
* 角色情绪识别
* 聊天记录自动摘要
* 在聊天窗口发送图片,并由人工智能解释图片内容
* 文本图像生成5 预设,以及 "自由模式"
* 聊天信息的文字转语音(通过 ElevenLabs、Silero 或操作系统的语音生成)
* ChromaDB 向量数据库,用于更智能的聊天 Prompt
扩展服务的完整功能介绍和使用教程,请参阅 [Docs](https://docs.sillytavern.app/extras/extensions/)。
## 界面/CSS/性能,由 RossAscends 调整并优化
* 针对 iOS 系统优化了界面,并支持将快捷方式保存到主屏幕,在全屏模式下打开。
* 热键
* 上 = 编辑聊天中的最后一条信息
* Ctrl+P = 编辑聊天中最后一条用户信息
* 左 = 向左滑动
* 右 = 向右滑动(注意:当聊天窗口输入内容时,轻扫快捷键将被禁用)
* Ctrl+左 = 查看本地存储的变量(在浏览器控制台窗口中)
* 回车(选择聊天栏)= 向人工智能发送信息
* Ctrl+Enter = 重新生成人工智能最后的回复
* 用户名更改和角色删除不再强制重新刷新页面。
* 增加在页面加载时自动连接 API 的选项。
* 增加选项,在页面加载时自动加载最近的聊天信息。
* 更好的 Tokens 计算器 - 适用于未保存的文字,并显示永久和临时 Tokens 数量
* 更好的聊天历史查询窗口
* 聊天的文件名以"(角色卡名称)+(创建时间)"的可读格式保存
* 聊天历史预览从 40 个字符增加到 300 个字符。
* 聊天历史排序有多种选择(按名称、创建日期、聊天记录大小)。
* 默认情况下,左侧和右侧弹出的设置面板会在点击其他区域时自动关闭。
* 点击导航面板上的 "锁按钮" 将保持弹出面板打开,并在不同聊天中记住此设置。
* 导航面板的打开或关闭状态也会跨聊天保存。
* 自定义聊天界面:
* 收到新消息时播放提示音
* 切换圆形或长方形头像样式
* 在台式电脑上拥有更宽的聊天窗口
* 可选的半透明玻璃效果聊天窗口
* 可定制 "主文本"、"引用文本 "和 "斜体文本 "的字体颜色。
* 可定制聊天界面的背景颜色和透明模糊程度
## 安装
*注意SillyTavern 用于本地安装,尚未在 Colab 或其他云服务上进行全面测试。
> **警告**
> 切勿安装到任何受 Windows 控制的系统文件夹Program Files, System32, etc中。
> 不要以管理员权限运行 start.bat
### Windows
通过 Git 安装(推荐使用,便于更新)
附有精美图片示例的简易指南:
<https://docs.sillytavern.app/installation/windows/>
1. 安装 [NodeJS](https://nodejs.org/en)(建议使用最新的 LTS 版本)
2. 安装 [GitHub 客户端](https://central.github.com/deployments/desktop/desktop/latest/win32)
3. 打开 Windows 资源管理器 (`Win+E`)
4. 浏览或创建一个不受 Windows 控制或监控的文件夹。(例如C:\MySpecialFolder\)
5. 点击顶部的 "地址栏",在该文件夹内打开命令提示符,输入 `cmd`,然后按回车。
6. 弹出黑框CMD 命令提示符)后,键入以下其中一项并按 Enter
* 稳定分支:`git clone https://github.com/SillyTavern/SillyTavern -b release`
* 开发分支: `git clone https://github.com/SillyTavern/SillyTavern -b staging`
7. 等待 Git 克隆完成后,双击文件夹中的 `Start.bat` 将启动 NodeJS 并开始自动安装需要的软件包。
8. 然后 SillyTavern 服务就会自动启动,同时在浏览器新标签页中自动打开。
通过压缩包下载安装(不推荐)
1. 安装 [NodeJS](https://nodejs.org/en)(建议使用最新的 LTS 版本)
2. 从该 GitHub 仓库下载压缩包。(从 [Releases](https://github.com/SillyTavern/SillyTavern/releases/latest) 获取 "Source codezip")。
3. 将压缩包解压到您选择的文件夹中
4. 双击或在命令行中运行 `Start.bat`
5. SillyTavern 服务自动为你准备好一切后,会在你的浏览器中打开一个新标签页。
### Linux
1.运行 `start.sh` 脚本。
2.等待自动完成,然后开始享受
## API 密钥管理
SillyTavern 会将 API 密钥保存在目录中的 `secrets.json` 文件内。
默认情况下,输入密钥并重新加载页面后,密钥会自动隐藏以保证安全。
如果要想通过点击 API 输入框旁边的按钮来查看密钥,请按照以下设置:
1. 打开 `config.conf` 文件,将里面的 `allowKeysExposure` 设置为 `true`
2. 然后重启 SillyTavern 服务。
## 远程访问
这通常是为那些想在手机上使用 SillyTavern 的人准备的,而他们的电脑和手机在同一个局域网中。
不过SillyTavern 也可以被设置为允许从任何地方进行远程访问。
**重要提示SillyTavern 是单用户程序,因此任何人登录后都能看到所有的角色卡和聊天内容,并能更改任何设置。
### 1.管理白名单 IP
* 在你的 SillyTavern 文件夹中新建一个文本文件,名为 `whitelist.txt`
* 用文本编辑器打开该文件,添加你希望允许连接的 IP 地址列表。
* 接受单个 IP 地址和 IP 范围,示例:
```
192.168.0.1
192.168.0.20
```
或者
```
192.168.0.*
```
(上述 IP 范围将允许局域网中的任何设备连接)
也接受子网掩码设置(如 10.0.0.0/24
* 保存`whitelist.txt`文件。
* 重启 SillyTavern 服务。
然后,文件中设置的 IP 就可以访问 SillyTavern 了。
*注意:"config.conf" 文件内也有一个 "whitelist" 设置,你可以用同样的方法设置它,但如果 "whitelist.txt" 文件存在,这个设置将被忽略。
### 2.获取 SillyTavern 服务的 IP 地址
白名单设置完成后,您需要 SillyTavern 服务的 IP 地址。
如果 SillyTavern 服务设备在同一个局域网上,则使用安装 SillyTavern 服务的电脑的局域网 IP 地址:
* WindowsWindows 按钮 > 在搜索栏中输入 `cmd.exe` > 在打开的控制台中输入 `ipconfig`,回车 > 然后在输出中查找 `IPv4` 地址。
如果您(或其他人)想在互联网中访问你自己的 SillyTavern 服务,则需要运行 SillyTavern 服务的设备的互联网 IP 地址。
* 使用运行 SillyTavern 的设备,访问 [this page](https://whatismyipaddress.com/) 并查找 `IPv4`。这是您从互联网访问时要用到的。
### 3. 使用其他设备访问 SillyTavern 服务
无论你最终使用的是什么 IP 地址,都要将该 IP 地址和端口号输入其他设备网络浏览器。
同一局域网中的 SillyTavern 服务的典型默认地址如下:
`http://192.168.0.5:8000`
使用 http:// 而不是 https://
### 向所有 IP 开放您的 SillyTavern 服务
我们不建议这样做,但您可以打开 `config.conf` 并将里面的 `whitelist` 设置改为 `false`
你必须删除或重命名SillyTavern 文件夹中的 `whitelist.txt` 文件(如果有的话)。
这通常是不安全的做法,所以我们要求在这样做时必须设置用户名和密码。
用户名和密码在`config.conf`文件中设置。
重启 SillyTavern 服务后,只要知道用户名和密码,任何设备都可以访问。
### 还是无法访问?
*`config.conf` 文件中的端口创建一条入站/出站防火墙规则。切勿将此误认为是路由器上的端口转发,否则,有人可能会发现你的聊天隐私,那就大错特错了。
* 在 "设置" > "网络和 Internet" > "以太网" 中启用 "专用网络" 配置。这对 Windows 11 非常重要,否则即使添加了上述防火墙规则也无法连接。
### 性能问题?
尝试在用户设置面板上关闭模糊效果(快速用户界面)模式。
## 我喜欢你的项目!我该如何贡献自己的力量?
### 应该
1. 发送 Fork 请求
2. 使用规定的模板发送功能建议和问题报告
3. 在提出任何问题之前,请先阅读 Readme 文件和文档
#### 不应该
1. 提供金钱捐赠
2. 发送错误报告而不提供任何详细信息
3. 提出已经回答过无数次的问题
## 我在哪里可以找到以前的聊天背景图片?
我们正在实行 100% 原创内容的政策,因此旧的背景图片已从该资源库中删除。
不过你可以在这里找到它们的存档:
<https://files.catbox.moe/1xevnc.zip>
## 屏幕截图
<img width="400" alt="image" src="https://user-images.githubusercontent.com/18619528/228649245-8061c60f-63dc-488e-9325-f151b7a3ec2d.png">
<img width="400" alt="image" src="https://user-images.githubusercontent.com/18619528/228649856-fbdeef05-d727-4d5a-be80-266cbbc6b811.png">
## 许可证和贡献
**发布本程序是希望它能有所帮助,但不做任何保证;甚至没有明示的性能、稳定性和其他任何特定用途的可用性保证。更多详情,请参阅 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.**
* 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>)
* 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/>
* Noto Sans font by Google (OFL license)
* Icon theme by Font Awesome <https://fontawesome.com> (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* AI Horde client library by ZeldaFan0225: <https://github.com/ZeldaFan0225/ai_horde>
* Linux startup script by AlpinDale
* Thanks paniphons for providing a FAQ document
* 10K Discord Users Celebratory Background by @kallmeflocc
* Default content (characters and lore books) provided by @OtisAlejandro, @RossAscends and @kallmeflocc
* Korean translation by @doloroushyeonse
* 中文翻译由 [@XXpE3](https://github.com/XXpE3) 完成,中文 ISSUES 可以联系 @XXpE3

10
.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
@@ -306,3 +308,5 @@ GNU Affero General Public License for more details.**
* 10K Discord Users Celebratory Background by @kallmeflocc
* 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

View File

@@ -1,9 +1,9 @@
name: Build and Publish Release (Dev)
name: Build and Publish Release (Release)
on:
push:
branches:
- dev
- release
jobs:
build_and_publish:
@@ -30,8 +30,8 @@ jobs:
uses: softprops/action-gh-release@v1
with:
files: dist/*
tag_name: ci-dev
name: Continuous Release (Dev)
tag_name: ci-release
name: Continuous Release (Release)
prerelease: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,9 +1,9 @@
name: Build and Publish Release (Main)
name: Build and Publish Release (Staging)
on:
push:
branches:
- main
- staging
jobs:
build_and_publish:
@@ -30,8 +30,8 @@ jobs:
uses: softprops/action-gh-release@v1
with:
files: dist/*
tag_name: ci-main
name: Continuous Release (Main)
tag_name: ci-staging
name: Continuous Release (Staging)
prerelease: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

6
.gitignore vendored
View File

@@ -9,12 +9,14 @@ public/worlds/
public/css/bg_load.css
public/themes/
public/OpenAI Settings/
public/KoboldAI Settings/
public/TextGen Settings/
public/scripts/extensions/third-party/
public/stats.json
/uploads/
*.jsonl
/config.conf
/docker/config.conf
/docker/config
.DS_Store
public/settings.json
/thumbnails
@@ -24,4 +26,6 @@ secrets.json
/dist
/backups/
public/movingUI/
public/QuickReplies/
content.log
cloudflared.exe

View File

@@ -4,7 +4,7 @@ FROM node:19.1.0-alpine3.16
ARG APP_HOME=/home/node/app
# Install system dependencies
RUN apk add gcompat tini
RUN apk add gcompat tini git
# Ensure proper handling of kernel signals
ENTRYPOINT [ "tini", "--" ]
@@ -23,13 +23,17 @@ COPY . ./
# Copy default chats, characters and user avatars to <folder>.default folder
RUN \
IFS="," RESOURCES="characters,chats,groups,group chats,User Avatars,worlds,settings.json" && \
IFS="," RESOURCES="characters,chats,groups,group chats,User Avatars,worlds" && \
\
echo "*** Store default $RESOURCES in <folder>.default ***" && \
for R in $RESOURCES; do mv "public/$R" "public/$R.default"; done && \
\
echo "*** Create symbolic links to config directory ***" && \
for R in $RESOURCES; do ln -s "../config/$R" "public/$R"; done && \
# rm "config.conf" "public/settings.json" "public/css/bg_load.css" && \
ln -s "./config/config.conf" "config.conf" && \
ln -s "../config/settings.json" "public/settings.json" && \
ln -s "../../config/bg_load.css" "public/css/bg_load.css" && \
mkdir "config"
# Cleanup unnecessary files

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 338 KiB

After

Width:  |  Height:  |  Size: 338 KiB

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": ""
}

View File

@@ -9,5 +9,4 @@ services:
- "8000:8000"
volumes:
- "./config:/home/node/app/config"
- "./config.conf:/home/node/app/config.conf"
restart: unless-stopped

View File

@@ -1,7 +1,7 @@
#!/bin/sh
# Initialize missing user files
IFS="," RESOURCES="characters,groups,group chats,chats,User Avatars,worlds,settings.json"
IFS="," RESOURCES="characters,groups,group chats,chats,User Avatars,worlds"
for R in $RESOURCES; do
if [ ! -e "config/$R" ]; then
echo "Resource not found, copying from defaults: $R"
@@ -9,5 +9,20 @@ for R in $RESOURCES; do
fi
done
if [ ! -e "config/config.conf" ]; then
echo "Resource not found, copying from defaults: config.conf"
cp -r "default/config.conf" "config/config.conf"
fi
if [ ! -e "config/settings.json" ]; then
echo "Resource not found, copying from defaults: settings.json"
cp -r "default/settings.json" "config/settings.json"
fi
if [ ! -e "config/bg_load.css" ]; then
echo "Resource not found, copying from defaults: bg_load.css"
cp -r "default/bg_load.css" "config/bg_load.css"
fi
# Start the server
exec node server.js

61
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "sillytavern",
"version": "1.9.2",
"version": "1.9.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "sillytavern",
"version": "1.9.2",
"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.2",
"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": 100,
"genamt": 100,
"temp": 1,
"top_k": 0,
"top_p": 0.95,
@@ -19,4 +17,4 @@
4,
5
]
}
}

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

@@ -0,0 +1,19 @@
{
"order": [5, 0, 1, 3],
"temperature": 1.23,
"max_length": 300,
"min_length": 1,
"top_k": 200,
"typical_p": 0.966,
"tail_free_sampling": 0.982,
"repetition_penalty": 1.74,
"repetition_penalty_range": 4000,
"repetition_penalty_frequency": 0,
"repetition_penalty_presence": 0.02,
"use_cache": false,
"return_full_text": false,
"prefix": "vanilla",
"cfg_scale": 1,
"phrase_rep_pen": "aggressive",
"max_context": 7800
}

View File

@@ -0,0 +1,19 @@
{
"order": [6, 0, 1, 2, 3],
"temperature": 1,
"max_length": 300,
"min_length": 1,
"top_k": 25,
"top_p": 1,
"tail_free_sampling": 0.925,
"repetition_penalty": 1.6,
"repetition_penalty_frequency": 0.001,
"repetition_penalty_range": 0,
"repetition_penalty_presence": 0,
"use_cache": false,
"return_full_text": false,
"prefix": "vanilla",
"phrase_rep_pen": "medium",
"cfg_scale": 1.55,
"max_context": 7800
}

View File

@@ -0,0 +1,20 @@
{
"order": [6, 2, 3, 1, 0],
"temperature": 1,
"max_length": 300,
"min_length": 1,
"top_k": 0,
"top_p": 0.96,
"tail_free_sampling": 0.96,
"repetition_penalty": 2,
"repetition_penalty_slope": 1,
"repetition_penalty_frequency": 0.02,
"repetition_penalty_range": 0,
"repetition_penalty_presence": 0.3,
"use_cache": false,
"return_full_text": false,
"prefix": "vanilla",
"phrase_rep_pen": "very_aggressive",
"cfg_scale": 1.3,
"max_context": 7800
}

View File

@@ -0,0 +1,21 @@
{
"order": [2, 3, 0, 4, 1],
"temperature": 1.35,
"max_length": 300,
"min_length": 1,
"top_k": 12,
"top_p": 0.85,
"top_a": 0.1,
"tail_free_sampling": 0.915,
"repetition_penalty": 2.8,
"repetition_penalty_range": 2048,
"repetition_penalty_slope": 0.02,
"repetition_penalty_frequency": 0.02,
"repetition_penalty_presence": 0,
"use_cache": false,
"return_full_text": false,
"prefix": "vanilla",
"cfg_scale": 1,
"phrase_rep_pen": "aggressive",
"max_context": 7800
}

View File

@@ -1,20 +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,
"top_a": 0.075,
"top_k": 79,
"top_p": 0.95
}

View File

@@ -0,0 +1,21 @@
{
"order": [4, 0, 5, 3, 2],
"temperature": 1.09,
"max_length": 300,
"min_length": 1,
"top_p": 0.969,
"top_a": 0.09,
"typical_p": 0.99,
"tail_free_sampling": 0.969,
"repetition_penalty": 1.09,
"repetition_penalty_range": 8192,
"repetition_penalty_slope": 0.069,
"repetition_penalty_frequency": 0.006,
"repetition_penalty_presence": 0.009,
"use_cache": false,
"return_full_text": false,
"prefix": "vanilla",
"cfg_scale": 1,
"phrase_rep_pen": "very_light",
"max_context": 7800
}

View File

@@ -5,6 +5,8 @@
"min_length": 1,
"top_k": 25,
"top_p": 1,
"top_a": 0,
"typical_p": 1,
"tail_free_sampling": 0.925,
"repetition_penalty": 1.9,
"repetition_penalty_range": 768,
@@ -14,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

@@ -0,0 +1,20 @@
{
"order": [0, 1, 2, 3],
"temperature": 1,
"max_length": 300,
"min_length": 1,
"top_k": 25,
"top_p": 1,
"tail_free_sampling": 0.925,
"repetition_penalty": 1.9,
"repetition_penalty_range": 768,
"repetition_penalty_slope": 1,
"repetition_penalty_frequency": 0.0025,
"repetition_penalty_presence": 0.001,
"use_cache": false,
"return_full_text": false,
"prefix": "vanilla",
"cfg_scale": 1,
"phrase_rep_pen": "off",
"max_context": 7800
}

View File

@@ -0,0 +1,19 @@
{
"order": [6, 1, 0, 5, 3],
"temperature": 1.25,
"max_length": 300,
"min_length": 1,
"top_k": 70,
"typical_p": 0.9,
"tail_free_sampling": 0.925,
"repetition_penalty": 2,
"repetition_penalty_range": 1632,
"repetition_penalty_frequency": 0,
"repetition_penalty_presence": 0,
"use_cache": false,
"return_full_text": false,
"prefix": "vanilla",
"phrase_rep_pen": "aggressive",
"cfg_scale": 1.825,
"max_context": 7800
}

View File

@@ -4,6 +4,8 @@
"max_length": 40,
"min_length": 1,
"top_a": 0.022,
"top_k": 0,
"top_p": 1,
"typical_p": 0.9,
"tail_free_sampling": 0.956,
"repetition_penalty": 1.25,
@@ -14,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

@@ -5,6 +5,7 @@
"min_length": 1,
"top_k": 25,
"top_a": 0.3,
"top_p": 1,
"typical_p": 0.96,
"tail_free_sampling": 0.895,
"repetition_penalty": 1.0125,
@@ -15,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

@@ -0,0 +1,22 @@
{
"order": [6, 0, 4, 1, 2, 5, 3],
"temperature": 1.31,
"max_length": 300,
"min_length": 1,
"top_k": 25,
"top_p": 0.97,
"top_a": 0.18,
"typical_p": 0.98,
"tail_free_sampling": 1,
"repetition_penalty": 1.55,
"repetition_penalty_frequency": 0.00075,
"repetition_penalty_presence": 0.00085,
"repetition_penalty_range": 8192,
"repetition_penalty_slope": 1.8,
"use_cache": false,
"return_full_text": false,
"prefix": "vanilla",
"phrase_rep_pen": "medium",
"cfg_scale": 1.35,
"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

@@ -0,0 +1,17 @@
{
"order": [3, 0, 5],
"temperature": 2.5,
"max_length": 300,
"min_length": 1,
"typical_p": 0.966,
"tail_free_sampling": 0.933,
"repetition_penalty": 1,
"repetition_penalty_range": 2048,
"repetition_penalty_frequency": 0,
"repetition_penalty_presence": 0,
"use_cache": false,
"return_full_text": false,
"prefix": "vanilla",
"phrase_rep_pen": "aggressive",
"max_context": 7800
}

View File

@@ -1,19 +1,22 @@
{
"order": [1, 3, 4, 0, 2],
"temperature": 1.05,
"max_length": 40,
"order": [1, 5, 0, 2, 3, 4],
"temperature": 1.5,
"max_length": 300,
"min_length": 1,
"top_k": 79,
"top_p": 0.95,
"top_a": 0.075,
"tail_free_sampling": 0.989,
"repetition_penalty": 1.5,
"top_k": 10,
"top_p": 0.75,
"top_a": 0.08,
"typical_p": 0.975,
"tail_free_sampling": 0.967,
"repetition_penalty": 2.25,
"repetition_penalty_range": 8192,
"repetition_penalty_slope": 3.33,
"repetition_penalty_frequency": 0.03,
"repetition_penalty_slope": 0.09,
"repetition_penalty_frequency": 0,
"repetition_penalty_presence": 0.005,
"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

@@ -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

@@ -0,0 +1,18 @@
{
"order": [6, 0, 5],
"temperature": 0.895,
"max_length": 300,
"min_length": 1,
"typical_p": 0.9,
"repetition_penalty": 2,
"repetition_penalty_slope": 3.2,
"repetition_penalty_frequency": 0,
"repetition_penalty_presence": 0,
"repetition_penalty_range": 4048,
"use_cache": false,
"return_full_text": false,
"prefix": "vanilla",
"phrase_rep_pen": "aggressive",
"cfg_scale": 1.3,
"max_context": 7800
}

View File

@@ -5,6 +5,7 @@
"min_length": 1,
"top_k": 0,
"top_p": 0.912,
"top_a": 1,
"typical_p": 0.912,
"tail_free_sampling": 0.921,
"repetition_penalty": 1.21,
@@ -15,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

@@ -0,0 +1,19 @@
{
"order": [6, 1, 0, 5, 3, 2],
"temperature": 1.5,
"max_length": 300,
"min_length": 1,
"top_k": 70,
"top_p": 0.95,
"typical_p": 0.95,
"tail_free_sampling": 0.95,
"repetition_penalty": 1.6,
"repetition_penalty_range": 2016,
"repetition_penalty_frequency": 0,
"repetition_penalty_presence": 0,
"use_cache": false,
"return_full_text": false,
"prefix": "vanilla",
"phrase_rep_pen": "very_aggressive",
"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

@@ -95,6 +95,7 @@
<script type="module" src="scripts/context-template.js"></script>
<script type="module" src="scripts/extensions.js"></script>
<script type="module" src="scripts/authors-note.js"></script>
<script type="module" src="scripts/preset-manager.js"></script>
<script type="text/javascript" src="scripts/toolcool-color-picker.js"></script>
<title>SillyTavern</title>
@@ -131,15 +132,24 @@
<div class="scrollableInner">
<div class="flex-container" id="ai_response_configuration">
<div id="respective-presets-block" class="width100p">
<input type="file" hidden data-preset-manager-file="" accept=".json, .settings">
<div id="kobold_api-presets">
<h3><span data-i18n="kobldpresets">Kobold Presets</span>
<a href="https://docs.sillytavern.app/usage/api-connections/koboldai/" class="notes-link" target="_blank">
<span class="note-link-span">?</span>
</a>
</h3>
<select id="settings_perset">
<option value="gui" data-i18n="guikoboldaisettings">GUI KoboldAI Settings</option>
</select>
<div class="preset_buttons">
<select id="settings_perset" data-preset-manager-for="kobold">
<option value="gui" data-i18n="guikoboldaisettings">GUI KoboldAI Settings</option>
</select>
<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-delete="kobold" class="menu_button fa-solid fa-trash-can" title="Delete the preset" data-i18n="[title]Delete the preset"></i>
</div>
</div>
<div id="novel_api-presets">
<h3>
@@ -148,7 +158,7 @@
<span class="note-link-span">?</span>
</a>
</h3>
<select id="settings_perset_novel">
<select id="settings_perset_novel" data-preset-manager-for="novel">
<option value="gui" data-i18n="default">Default</option>
</select>
</div>
@@ -171,8 +181,15 @@
<div id="textgenerationwebui_api-presets">
<h3><span data-i18n="text gen webio(ooba)preset">Text Gen WebUI (ooba) presets</span>
</h3>
<select id="settings_preset_textgenerationwebui">
</select>
<div class="preset_buttons">
<select id="settings_preset_textgenerationwebui" data-preset-manager-for="textgenerationwebui">
</select>
<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-delete="textgenerationwebui" class="menu_button fa-solid fa-trash-can" title="Delete the preset" data-i18n="[title]Delete the preset"></i>
</div>
</div>
<hr>
</div>
@@ -236,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">
@@ -311,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">
@@ -325,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">
@@ -370,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">
@@ -385,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">
@@ -528,6 +545,19 @@
<input id="openai_reverse_proxy" type="text" class="text_pole" placeholder="https://api.openai.com/v1" maxlength="100" />
</div>
</div>
<div class="range-block" data-source="openai,claude">
<div class="range-block-title justifyLeft" data-i18n="Proxy Password">
Proxy Password
</div>
<div class="toggle-description justifyLeft">
<span data-i18n="Will be used as a password for the proxy instead of API key.">
Will be used as a password for the proxy instead of API key.<br>
</span>
</div>
<div class="wide100p">
<input id="openai_proxy_password" type="text" class="text_pole" placeholder="" maxlength="200" />
</div>
</div>
<div class="range-block" data-source="openai,claude">
<div class="range-block-title justifyLeft">
<label for="legacy_streaming" class="checkbox_label">
@@ -777,6 +807,127 @@
</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
</div>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input type="range" id="top_p_novel" name="volume" min="0" max="1" step="0.01">
</div>
<div class="range-block-counter">
<div contenteditable="true" data-for="top_p_novel" id="top_p_counter_novel">
select
</div>
</div>
</div>
</div>
<div class="range-block">
<div class="range-block-title" data-i18n="Top A">
Top A
</div>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input type="range" id="top_a_novel" name="volume" min="0" max="1" step="0.01">
</div>
<div class="range-block-counter">
<div contenteditable="true" data-for="top_a_novel" id="top_a_counter_novel">
select
</div>
</div>
</div>
</div>
<div class="range-block">
<div class="range-block-title" data-i18n="Top K">
Top K
</div>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input type="range" id="top_k_novel" name="volume" min="0" max="300" step="1">
</div>
<div class="range-block-counter">
<div contenteditable="true" data-for="top_k_novel" id="top_k_counter_novel">
select
</div>
</div>
</div>
</div>
<div class="range-block">
<div class="range-block-title" data-i18n="Typical P">
Typical P
</div>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input type="range" id="typical_p_novel" name="volume" min="0" max="1" step="0.01">
</div>
<div class="range-block-counter">
<div contenteditable="true" data-for="typical_p_novel" id="typical_p_counter_novel">
select
</div>
</div>
</div>
</div>
<div class="range-block">
<div class="range-block-title" data-i18n="CFG Scale">
CFG Scale
</div>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input type="range" id="cfg_scale_novel" name="volume" min="1" max="3" step="0.05">
</div>
<div class="range-block-counter">
<div contenteditable="true" data-for="cfg_scale_novel" id="cfg_scale_counter_novel">
select
</div>
</div>
</div>
</div>
<div class="range-block">
<div class="range-block-title" data-i18n="Phrase Repetition Penalty">
Phrase Repetition Penalty
</div>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input type="range" id="phrase_rep_pen_novel" name="volume" min="0" max="5" step="1">
</div>
<div class="range-block-counter">
<div contenteditable="true" data-for="phrase_rep_pen_novel" id="phrase_rep_pen_counter_novel">
select
</div>
</div>
</div>
</div>
<div class="range-block">
<div class="range-block-title" data-i18n="Min Length">
Min Length
</div>
<div class="range-block-range-and-counter">
<div class="range-block-range">
<input type="range" id="min_length_novel" name="volume" min="0" max="1024" step="1">
</div>
<div class="range-block-counter">
<div contenteditable="true" data-for="min_length_novel" id="min_length_counter_novel">
select
</div>
</div>
</div>
</div>
</div>
<div id="textgenerationwebui_api-settings">
<div class="range-block">
@@ -1184,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">
@@ -1387,6 +1542,7 @@
<option value="euterpe-v2">Euterpe</option>
<option value="krake-v2">Krake</option>
<option value="clio-v1">Clio</option>
<option value="kayra-v1">Kayra</option>
</select>
</form>
<div id="online_status3">
@@ -1395,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>
@@ -1458,6 +1639,11 @@
<input id="api_key_openai" name="api_key_openai" class="text_pole flex1" maxlength="500" value="" 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_openai"></div>
</div>
<div id="ReverseProxyWarningMessage2" class="reverse_proxy_warning">
<b data-i18n="Use Proxy password field instead. This input will be ignored.">
Use "Proxy password" field instead. This input will be ignored.
</b>
</div>
<div data-for="api_key_openai" class="neutral_warning">
For privacy reasons, your API key will be hidden after you reload the page.
</div>
@@ -1660,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>
@@ -1669,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>
@@ -1704,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>
@@ -1722,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">
@@ -1730,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>
@@ -1740,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">
@@ -1748,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">
@@ -1756,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>
@@ -1771,12 +1961,13 @@
</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>
<option value="3">Sentencepiece (LLaMA)</option>
<option value="4">NerdStash (NovelAI Krake)</option>
<option value="5">NerdStash v2 (NovelAI Clio)</option>
<option value="4">NerdStash (NovelAI Clio)</option>
<option value="5">NerdStash v2 (NovelAI Kayra)</option>
<option value="6">API (WebUI)</option>
</select>
</div>
@@ -1835,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>
@@ -1847,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
@@ -1996,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>
@@ -2280,6 +2483,10 @@
<input id="swipes-checkbox" type="checkbox" />
<span data-i18n="Swipes">Swipes</span>
</label>
<label for="fuzzy_search_checkbox">
<input id="fuzzy_search_checkbox" type="checkbox" />
<span data-i18n="Advanced Character Search">Advanced Character Search</span>
</label>
<label for="prefer_character_prompt" title="If checked and the character card contains a prompt override (System Prompt), use that instead." data-i18n="[title]If checked and the character card contains a prompt override (System Prompt), use that instead." class="checkbox_label">
<input id="prefer_character_prompt" type="checkbox" />
<span data-i18n="Prefer Character Card Prompt">Prefer Char. Prompt</span>
@@ -2340,7 +2547,10 @@
<span data-i18n="Confirm message deletion">Confirm message deletion</span>
</label>
<label for="spoiler_free_mode"><input id="spoiler_free_mode" type="checkbox" />
<span data-i18n="Spoiler Free Mode">Spolier Free Mode</span>
<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">
@@ -2464,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">
@@ -2780,6 +2998,8 @@
<option data-field="date_last_chat" data-order="desc" data-i18n="Recent">Recent</option>
<option data-field="chat_size" data-order="desc" data-i18n="Most chats">Most chats</option>
<option data-field="chat_size" data-order="asc" data-i18n="Least chats">Least chats</option>
<option data-field="data_size" data-order="desc" data-i18n="Most tokens">Most tokens</option>
<option data-field="data_size" data-order="asc" data-i18n="Least tokens">Least tokens</option>
<option data-field="name" data-order="random" data-i18n="Random">Random</option>
</select>
</form>
@@ -3099,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>
@@ -3300,7 +3520,8 @@
<div class="flex-container wide100pLess70px character_select_container">
<div class="wide100p character_name_block">
<span class="ch_name"></span>
<i class="ch_avatar_url"></i>
<small class="character_version"></small>
<small class="ch_avatar_url"></small>
</div>
<i class="ch_fav_icon fa-solid fa-star"></i>
<input class="ch_fav" value="" hidden />
@@ -3460,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>
@@ -3618,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

@@ -1,6 +1,6 @@
{
"name": "Llama 2",
"system_prompt": "Write {{user}}'s next reply in this fictional roleplay with {{char}}.\n<</SYS>>\n",
"system_prompt": "Write {{char}}'s next reply in this fictional roleplay with {{user}}.\n<</SYS>>\n",
"system_sequence": "[INST] <<SYS>>\n",
"stop_sequence": "",
"input_sequence": "[INST]",

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
}

File diff suppressed because it is too large Load Diff

View File

@@ -13,6 +13,10 @@ import {
menu_type,
max_context,
saveSettingsDebounced,
active_group,
active_character,
setActiveGroup,
setActiveCharacter,
} from "../script.js";
import {
@@ -239,7 +243,6 @@ $("#rm_ch_create_block").on("input", function () { countTokensDebounced(); });
$("#character_popup").on("input", function () { countTokensDebounced(); });
//function:
export function RA_CountCharTokens() {
$("#result_info").html("");
//console.log('RA_TC -- starting with this_chid = ' + this_chid);
if (menu_type === "create") { //if new char
function saveFormVariables() {
@@ -331,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();
}
@@ -343,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); }
}
@@ -367,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);
@@ -448,8 +462,8 @@ function RA_autoconnect(PrevApi) {
}
break;
case 'openai':
if ((secret_state[SECRET_KEYS.OPENAI] && oai_settings.chat_completion_source == chat_completion_sources.OPENAI)
|| (secret_state[SECRET_KEYS.CLAUDE] && oai_settings.chat_completion_source == chat_completion_sources.CLAUDE)
if (((secret_state[SECRET_KEYS.OPENAI] || oai_settings.reverse_proxy) && oai_settings.chat_completion_source == chat_completion_sources.OPENAI)
|| ((secret_state[SECRET_KEYS.CLAUDE] || oai_settings.reverse_proxy) && oai_settings.chat_completion_source == chat_completion_sources.CLAUDE)
|| (secret_state[SECRET_KEYS.SCALE] && oai_settings.chat_completion_source == chat_completion_sources.SCALE)
|| (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI)
|| (secret_state[SECRET_KEYS.OPENROUTER] && oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER)
@@ -521,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) => {
@@ -605,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");
@@ -661,8 +677,6 @@ export function dragElement(elmnt) {
}
});
observer.observe(elmnt.get(0), { attributes: true, attributeFilter: ['style'] });
function dragMouseDown(e) {
if (e) {
@@ -732,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();
@@ -904,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';
@@ -968,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();
@@ -1029,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"
) {
@@ -1041,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');
}
}
}
@@ -1062,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

@@ -60,7 +60,10 @@ const extension_settings = {
dice: {},
regex: [],
tts: {},
sd: {},
sd: {
prompts: {},
character_prompts: {},
},
chromadb: {},
translate: {},
objective: {},
@@ -70,6 +73,7 @@ const extension_settings = {
fluctuation: 0.1,
enabled: false,
},
speech_recognition: {},
};
let modules = [];

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.
@@ -551,13 +575,17 @@ async function onSelectInjectFile(e) {
function doAutoAdjust(chat, maxContext) {
console.debug('CHROMADB: Auto-adjusting sliders (messages: %o, maxContext: %o)', chat.length, maxContext);
// Get mean message length
const meanMessageLength = chat.reduce((acc, cur) => acc + cur.mes.length, 0) / chat.length;
const meanMessageLength = chat.reduce((acc, cur) => acc + (cur?.mes?.length ?? 0), 0) / chat.length;
if (Number.isNaN(meanMessageLength)) {
console.debug('CHROMADB: Mean message length is NaN, aborting auto-adjust');
if (Number.isNaN(meanMessageLength) || meanMessageLength === 0) {
console.debug('CHROMADB: Mean message length is zero or NaN, aborting auto-adjust');
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);
@@ -611,7 +639,7 @@ window.chromadb_interceptGeneration = async (chat, maxContext) => {
console.debug("CHROMADB: Messages to store length vs keep context: %o vs %o", messagesToStore.length, extension_settings.chromadb.keep_context);
await addMessages(currentChatId, messagesToStore);
}
const lastMessage = chat[chat.length - 1];
let queriedMessages;
@@ -811,15 +839,15 @@ jQuery(async () => {
<textarea id="chromadb_custom_msg" hidden class="text_pole textarea_compact" rows="2" placeholder="${defaultSettings.chroma_default_msg}" style="height: 61px; display: none;"></textarea>
<label for="chromadb_custom_depth" hidden><small>How deep should the memory messages be injected?: (<span id="chromadb_custom_depth_value"></span>)</small></label>
<input id="chromadb_custom_depth" type="range" min="${defaultSettings.chroma_depth_min}" max="${defaultSettings.chroma_depth_max}" step="${defaultSettings.chroma_depth_step}" value="${defaultSettings.chroma_depth}" hidden/>
<label for="chromadb_hhaa_wrapperfmt" hidden><small>Custom wrapper format:</small></label>
<textarea id="chromadb_hhaa_wrapperfmt" hidden class="text_pole textarea_compact" rows="2" placeholder="${defaultSettings.chroma_default_hhaa_wrapper}" style="height: 61px; display: none;"></textarea>
<label for="chromadb_hhaa_memoryfmt" hidden><small>Custom memory format:</small></label>
<textarea id="chromadb_hhaa_memoryfmt" hidden class="text_pole textarea_compact" rows="2" placeholder="${defaultSettings.chroma_default_hhaa_memory}" style="height: 61px; display: none;"></textarea>
<label for="chromadb_hhaa_token_limit" hidden><small>Maximum tokens allowed for memories: (<span id="chromadb_hhaa_token_limit_value"></span>)</small></label>
<input id="chromadb_hhaa_token_limit" type="range" min="0" max="2048" step="64" value="${defaultSettings.hhaa_token_limit}" hidden/>
<span>Memory Recall Strategy</span>
<select id="chromadb_recall_strategy">
<option value="original">Recall only from this chat</option>
@@ -834,7 +862,7 @@ jQuery(async () => {
<input id="chromadb_keep_context" type="range" min="${defaultSettings.keep_context_min}" max="${defaultSettings.keep_context_max}" step="${defaultSettings.keep_context_step}" value="${defaultSettings.keep_context}" />
<label for="chromadb_n_results"><small>Maximum number of ChromaDB 'memories' to inject: (<span id="chromadb_n_results_value"></span>) messages</small></label>
<input id="chromadb_n_results" type="range" min="${defaultSettings.n_results_min}" max="${defaultSettings.n_results_max}" step="${defaultSettings.n_results_step}" value="${defaultSettings.n_results}" />
<label for="chromadb_keep_context_proportion"><small>Keep (<span id="chromadb_keep_context_proportion_value"></span>%) of in-context chat messages; replace the rest with memories</small></label>
<input id="chromadb_keep_context_proportion" type="range" min="${defaultSettings.keep_context_proportion_min}" max="${defaultSettings.keep_context_proportion_max}" step="${defaultSettings.keep_context_proportion_step}" value="${defaultSettings.keep_context_proportion}" />
<label for="chromadb_split_length"><small>Max length for each 'memory' pulled from the current chat history: (<span id="chromadb_split_length_value"></span>) characters</small></label>

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 } 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,97 +7,103 @@ 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"
let globalObjective = ""
let taskTree = null
let globalTasks = []
let currentChatId = ""
let currentObjective = null
let currentTask = null
let checkCounter = 0
let lastMessageWasSwipe = false
const objectivePrompts = {
const defaultPrompts = {
"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.
The objective that you must make a numbered task list for is: [{{objective}}].
The 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.
The objective that you must make a numbered task list for is: [{{objective}}].
The 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.
Given an example objective of 'Make me a four course dinner', here is an example output:
1. Determine what the courses will be
2. Find recipes for each course
3. Go shopping for supplies with {{user}}
4. Cook the food
5. Get {{user}} to set the table
6. Serve the food
7. Enjoy eating the meal with {{user}}
Given an example objective of 'Make me a four course dinner', here is an example output:
1. Determine what the courses will be
2. Find recipes for each course
3. Go shopping for supplies with {{user}}
4. Cook the food
5. Get {{user}} to set the table
6. Serve the food
7. Enjoy eating the meal with {{user}}
`,
"checkTaskCompleted": `Pause your roleplay. Determine if this task is completed: [{{task}}].
To do this, examine the most recent messages. Your response must only contain either true or false, nothing other words.
Example output:
true
`
To do this, examine the most recent messages. Your response must only contain either true or false, nothing other words.
Example output:
true
`,
'currentTask':`Your current task is [{{task}}]. Balance existing roleplay with completing this task.`
}
const extensionPrompt = "Your current task is [{{task}}]. Balance existing roleplay with completing this task."
let objectivePrompts = defaultPrompts
//###############################//
//# Task Management #//
//###############################//
// Accepts optional index. Defaults to adding to end of list.
function addTask(description, index = null) {
index = index != null ? index: index = globalTasks.length
globalTasks.splice(index, 0, new ObjectiveTask(
{description: description}
))
saveState()
}
// Return the task and index or throw an error
function getTaskById(taskId){
if (taskId == null) {
throw `Null task id`
}
const index = globalTasks.findIndex((task) => task.id === taskId);
if (index !== -1) {
return { task: globalTasks[index], index: index };
} else {
throw `Cannot find task with ${taskId}`
}
return getTaskByIdRecurse(taskId, taskTree)
}
function deleteTask(taskId){
const { task, index } = getTaskById(taskId)
function getTaskByIdRecurse(taskId, task) {
if (task.id == taskId){
return task
}
for (const childTask of task.children) {
const foundTask = getTaskByIdRecurse(taskId, childTask);
if (foundTask != null) {
return foundTask;
}
}
return null;
}
globalTasks.splice(index, 1)
setCurrentTask()
updateUiTaskList()
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, globalObjective));
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)
// Clear all existing global tasks when generating
globalTasks = []
// Clear all existing objective tasks when generating
currentObjective.children = []
const numberedListPattern = /^\d+\./
// Create tasks from generated task list
for (const task of taskResponse.split('\n').map(x => x.trim())) {
if (task.match(numberedListPattern) != null) {
addTask(task.replace(numberedListPattern,"").trim())
currentObjective.addTask(task.replace(numberedListPattern,"").trim())
}
}
updateUiTaskList()
console.info(`Response for Objective: '${globalObjective}' was \n'${taskResponse}', \nwhich created tasks \n${JSON.stringify(globalTasks.map(v => {return v.toSaveState()}), null, 2)} `)
toastr.success(`Generated ${globalTasks.length} tasks`, 'Done!');
updateUiTaskList();
setCurrentTask();
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
@@ -106,14 +112,28 @@ 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
if (taskResponse.includes("true")) {
console.info(`Character determined task '${JSON.stringify(currentTask.toSaveState())} is completed.`)
console.info(`Character determined task '${currentTask.description} is completed.`)
currentTask.completeTask()
} else if (!(taskResponse.includes("false"))) {
console.warn(`checkTaskCompleted response did not contain true or false. taskResponse: ${taskResponse}`)
@@ -122,27 +142,57 @@ async function checkTaskCompleted() {
}
}
function getNextIncompleteTaskRecurse(task){
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) {
if (childTask.completed === true){ // Don't recurse into completed tasks
continue
}
const foundTask = getNextIncompleteTaskRecurse(childTask);
if (foundTask != null) {
return foundTask;
}
}
return null;
}
// Set a task in extensionPrompt context. Defaults to first incomplete
function setCurrentTask(taskId = null) {
const context = getContext();
// TODO: Should probably null this rather than set empty object
currentTask = {};
// Set current task to either the next incomplete task, or the index
// Find the task, either next incomplete, or by provided taskId
if (taskId === null) {
currentTask = globalTasks.find(task => !task.completed) || {};
currentTask = getNextIncompleteTaskRecurse(taskTree) || {};
} else {
const { _, index } = getTaskById(taskId)
currentTask = globalTasks[index];
currentTask = getTaskById(taskId);
}
// Get the task description and add to extension prompt
// Don't just check for a current task, check if it has data
const description = currentTask.description || null;
// Now update the extension prompt
if (description) {
const extensionPromptText = extensionPrompt.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'});
}
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)}`);
} else {
@@ -153,22 +203,26 @@ function setCurrentTask(taskId = null) {
saveState();
}
let taskIdCounter = 0
function getNextTaskId(){
// Make sure id does not exist
while (globalTasks.find(task => task.id == taskIdCounter) != undefined) {
taskIdCounter += 1
function getHighestTaskIdRecurse(task) {
let nextId = task.id;
for (const childTask of task.children) {
const childId = getHighestTaskIdRecurse(childTask);
if (childId > nextId) {
nextId = childId;
}
}
const nextId = taskIdCounter
console.log(`TaskID assigned: ${nextId}`)
taskIdCounter += 1
return nextId
return nextId;
}
//###############################//
//# Task Class #//
//###############################//
class ObjectiveTask {
id
description
completed
parent
parentId
children
// UI Elements
@@ -178,25 +232,67 @@ class ObjectiveTask {
deleteTaskButton
addTaskButton
constructor ({id=undefined, description, completed=false, parent=null}) {
constructor ({id=undefined, description, completed=false, parentId=""}) {
this.description = description
this.parent = parent
this.parentId = parentId
this.children = []
this.completed = completed
// Generate a new ID if none specified
if (id==undefined){
this.id = getNextTaskId()
this.id = getHighestTaskIdRecurse(taskTree) + 1
} else {
this.id=id
}
}
// Accepts optional index. Defaults to adding to end of list.
addTask(description, index = null) {
index = index != null ? index: index = this.children.length
this.children.splice(index, 0, new ObjectiveTask(
{description: description, parentId: this.id}
))
saveState()
}
getIndex(){
if (this.parentId !== null) {
const parent = getTaskById(this.parentId)
const index = parent.children.findIndex(task => task.id === this.id)
if (index === -1){
throw `getIndex failed: Task '${this.description}' not found in parent task '${parent.description}'`
}
return index
} else {
throw `getIndex failed: Task '${this.description}' has no parent`
}
}
// Used to set parent to complete when all child tasks are completed
checkParentComplete() {
let all_completed = true;
if (this.parentId !== ""){
const parent = getTaskById(this.parentId);
for (const child of parent.children){
if (!child.completed){
all_completed = false;
break;
}
}
if (all_completed){
parent.completed = true;
console.info(`Parent task '${parent.description}' completed after all child tasks complated.`)
} else {
parent.completed = false;
}
}
}
// Complete the current task, setting next task to next incomplete task
completeTask() {
this.completed = true
console.info(`Task successfully completed: ${JSON.stringify(this.description)}`)
this.checkParentComplete()
setCurrentTask()
updateUiTaskList()
}
@@ -206,9 +302,10 @@ class ObjectiveTask {
const template = `
<div id="objective-task-label-${this.id}" class="flex1 checkbox_label">
<input id="objective-task-complete-${this.id}" type="checkbox">
<span class="text_pole" style="display: block" id="objective-task-description-${this.id}" contenteditable>${this.description}</span>
<span class="text_pole objective-task" style="display: block" id="objective-task-description-${this.id}" contenteditable>${this.description}</span>
<div id="objective-task-delete-${this.id}" class="objective-task-button fa-solid fa-xmark fa-2x" title="Delete Task"></div>
<div id="objective-task-add-${this.id}" class="objective-task-button fa-solid fa-plus fa-2x" title="Add Task"></div>
<div id="objective-task-add-branch-${this.id}" class="objective-task-button fa-solid fa-code-fork fa-2x" title="Branch Task"></div>
</div><br>
`;
@@ -219,6 +316,15 @@ class ObjectiveTask {
this.descriptionSpan = $(`#objective-task-description-${this.id}`);
this.addButton = $(`#objective-task-add-${this.id}`);
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'})
} else {
this.branchButton.css({'color':''})
}
// Add event listeners and set properties
$(`#objective-task-complete-${this.id}`).prop('checked', this.completed);
@@ -227,56 +333,206 @@ class ObjectiveTask {
$(`#objective-task-description-${this.id}`).on('focusout', () => (this.onDescriptionFocusout()));
$(`#objective-task-delete-${this.id}`).on('click', () => (this.onDeleteClick()));
$(`#objective-task-add-${this.id}`).on('click', () => (this.onAddClick()));
this.branchButton.on('click', () => (this.onBranchClick()))
}
onBranchClick() {
currentObjective = this
updateUiTaskList();
setCurrentTask();
}
onCompleteClick(){
this.completed = this.completedCheckbox.prop('checked')
this.checkParentComplete()
setCurrentTask();
}
onDescriptionUpdate(){
this.description = this.descriptionSpan.text();
}
onDescriptionFocusout(){
setCurrentTask();
}
onDeleteClick(){
deleteTask(this.id);
const index = this.getIndex()
const parent = getTaskById(this.parentId)
parent.children.splice(index, 1)
updateUiTaskList()
setCurrentTask()
}
onAddClick(){
const {_, index} = getTaskById(this.id)
addTask("", index + 1);
setCurrentTask();
const index = this.getIndex()
const parent = getTaskById(this.parentId)
parent.addTask("", index + 1);
updateUiTaskList();
setCurrentTask();
}
toSaveState() {
toSaveStateRecurse() {
let children = []
if (this.children.length > 0){
for (const child of this.children){
children.push(child.toSaveStateRecurse())
}
}
return {
"id":this.id,
"description":this.description,
"completed":this.completed,
"parent": this.parent,
"parentId": this.parentId,
"children": children,
}
}
}
//###############################//
//# Custom Prompts #//
//###############################//
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>
<label for="objective-prompt-check">Completion Check Prompt</label>
<textarea id="objective-prompt-check" type="text" class="text_pole textarea_compact" rows="8"></textarea>
<label for="objective-prompt-extension-prompt">Injected Prompt</label>
<textarea id="objective-prompt-extension-prompt" type="text" class="text_pole textarea_compact" rows="8"></textarea>
</div>
<div class="objective_prompt_block">
<label for="objective-custom-prompt-select">Custom Prompt Select</label>
<select id="objective-custom-prompt-select"><select>
</div>
<div class="objective_prompt_block">
<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>`
callPopup(popupText, 'text')
populateCustomPrompts()
// Set current values
$('#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()
})
$('#objective-prompt-check').on('input', () => {
objectivePrompts.checkTaskCompleted = $('#objective-prompt-check').val()
})
$('#objective-prompt-extension-prompt').on('input', () => {
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()
})
// Handle delete
$('#objective-custom-prompt-delete').on('click', () => {
deleteCustomPrompt()
})
// Handle load
$('#objective-custom-prompt-select').on('change', loadCustomPrompt)
}
async function newCustomPrompt() {
const customPromptName = await callPopup('<h3>Custom Prompt name:</h3>', 'input');
if (customPromptName == "") {
toastr.warning("Please set custom prompt name to save.")
return
}
if (customPromptName == "default"){
toastr.error("Cannot save over default prompt")
return
}
extension_settings.objective.customPrompts[customPromptName] = {}
Object.assign(extension_settings.objective.customPrompts[customPromptName], objectivePrompts)
saveSettingsDebounced()
populateCustomPrompts()
}
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
}
delete extension_settings.objective.customPrompts[customPromptName]
saveSettingsDebounced()
populateCustomPrompts()
loadCustomPrompt()
}
function loadCustomPrompt(){
const optionSelected = $("#objective-custom-prompt-select").find(':selected').val()
Object.assign(objectivePrompts, extension_settings.objective.customPrompts[optionSelected])
$('#objective-prompt-generate').val(objectivePrompts.createTask)
$('#objective-prompt-check').val(objectivePrompts.checkTaskCompleted)
$('#objective-prompt-extension-prompt').val(objectivePrompts.currentTask)
}
function populateCustomPrompts(){
// Populate saved prompts
$('#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-custom-prompt-select').append(option)
}
}
//###############################//
//# UI AND Settings #//
//###############################//
const defaultSettings = {
objective: "",
tasks: [],
currentObjectiveId: null,
taskTree: null,
chatDepth: 2,
checkFrequency: 3,
hideTasks: false
hideTasks: false,
prompts: defaultPrompts,
}
// Convenient single call. Not much at the moment.
function resetState() {
lastMessageWasSwipe = false
loadSettings();
}
@@ -288,15 +544,13 @@ function saveState() {
currentChatId = context.chatId
}
// Convert globalTasks for saving
const tasks = globalTasks.map(task => {return task.toSaveState()})
chat_metadata['objective'] = {
objective: globalObjective,
tasks: tasks,
currentObjectiveId: currentObjective.id,
taskTree: taskTree.toSaveStateRecurse(),
checkFrequency: $('#objective-check-frequency').val(),
chatDepth: $('#objective-chat-depth').val(),
hideTasks: $('#objective-hide-tasks').prop('checked'),
prompts: objectivePrompts,
}
saveMetadataDebounced();
@@ -305,12 +559,12 @@ function saveState() {
// Dump core state
function debugObjectiveExtension() {
console.log(JSON.stringify({
"currentTask": currentTask.toSaveState(),
"currentChatId": currentChatId,
"checkCounter": checkCounter,
"globalObjective": globalObjective,
"globalTasks": globalTasks.map(v => {return v.toSaveState()}),
"extension_settings": chat_metadata['objective'],
"currentTask": currentTask,
"currentObjective": currentObjective,
"taskTree": taskTree.toSaveStateRecurse(),
"chat_metadata": chat_metadata['objective'],
"extension_settings": extension_settings['objective'],
"prompts": objectivePrompts
}, null, 2))
}
@@ -320,9 +574,20 @@ window.debugObjectiveExtension = debugObjectiveExtension
// Populate UI task list
function updateUiTaskList() {
$('#objective-tasks').empty()
// Show tasks if there are any
if (globalTasks.length > 0){
for (const task of globalTasks) {
// Show button to navigate back to parent objective if parent exists
if (currentObjective){
if (currentObjective.parentId !== "") {
$('#objective-parent').show()
} else {
$('#objective-parent').hide()
}
}
$('#objective-text').val(currentObjective.description)
if (currentObjective.children.length > 0){
// Show tasks if there are any to show
for (const task of currentObjective.children) {
task.addUiElement()
}
} else {
@@ -331,17 +596,21 @@ function updateUiTaskList() {
<input id="objective-task-add-first" type="button" class="menu_button" value="Add Task">
`)
$("#objective-task-add-first").on('click', () => {
addTask("")
currentObjective.addTask("")
setCurrentTask()
updateUiTaskList()
})
}
}
function onParentClick() {
currentObjective = getTaskById(currentObjective.parentId)
updateUiTaskList()
setCurrentTask()
}
// Trigger creation of new tasks with given objective.
async function onGenerateObjectiveClick() {
globalObjective = $('#objective-text').val()
await generateTasks()
saveState()
}
@@ -352,6 +621,13 @@ function onChatDepthInput() {
setCurrentTask() // Ensure extension prompt is updated
}
function onObjectiveTextFocusOut(){
if (currentObjective){
currentObjective.description = $('#objective-text').val()
saveState()
}
}
// Update how often we check for task completion
function onCheckFrequencyInput() {
checkCounter = $("#objective-check-frequency").val()
@@ -364,10 +640,33 @@ function onHideTasksInput() {
saveState()
}
function loadTaskChildrenRecurse(savedTask) {
let tempTaskTree = new ObjectiveTask({
id: savedTask.id,
description: savedTask.description,
completed: savedTask.completed,
parentId: savedTask.parentId,
})
for (const task of savedTask.children){
const childTask = loadTaskChildrenRecurse(task)
tempTaskTree.children.push(childTask)
}
return tempTaskTree
}
function loadSettings() {
// Load/Init settings for chatId
currentChatId = getContext().chatId
// 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}})
}
// Bail on home screen
if (currentChatId == undefined) {
return
@@ -375,6 +674,7 @@ function loadSettings() {
// Migrate existing settings
if (currentChatId in extension_settings.objective) {
// TODO: Remove this soon
chat_metadata['objective'] = extension_settings.objective[currentChatId];
delete extension_settings.objective[currentChatId];
}
@@ -383,26 +683,52 @@ function loadSettings() {
Object.assign(chat_metadata, { objective: defaultSettings });
}
// Update globals
globalObjective = chat_metadata['objective'].objective
globalTasks = chat_metadata['objective'].tasks.map(task => {
return new ObjectiveTask({
id: task.id,
description: task.description,
completed: task.completed,
parent: task.parent,
})
});
// Migrate legacy flat objective to new objectiveTree and currentObjective
if ('objective' in chat_metadata.objective) {
// Create root objective from legacy objective
taskTree = new ObjectiveTask({id:0, description: chat_metadata.objective.objective});
currentObjective = taskTree;
// Populate root objective tree from legacy tasks
if ('tasks' in chat_metadata.objective) {
let idIncrement = 0;
taskTree.children = chat_metadata.objective.tasks.map(task => {
idIncrement += 1;
return new ObjectiveTask({
id: idIncrement,
description: task.description,
completed: task.completed,
parentId: taskTree.id,
})
});
}
saveState();
delete chat_metadata.objective.objective;
delete chat_metadata.objective.tasks;
} else {
// Load Objectives and Tasks (Normal path)
if (chat_metadata.objective.taskTree){
taskTree = loadTaskChildrenRecurse(chat_metadata.objective.taskTree)
}
}
// Make sure there's a root task
if (!taskTree) {
taskTree = new ObjectiveTask({id:0,description:$('#objective-text').val()})
}
currentObjective = taskTree
checkCounter = chat_metadata['objective'].checkFrequency
// Update UI elements
$('#objective-counter').text(checkCounter)
$("#objective-text").text(globalObjective)
$("#objective-text").text(taskTree.description)
updateUiTaskList()
$('#objective-chat-depth').val(chat_metadata['objective'].chatDepth)
$('#objective-check-frequency').val(chat_metadata['objective'].checkFrequency)
$('#objective-hide-tasks').prop('checked', chat_metadata['objective'].hideTasks)
onHideTasksInput()
$('#objective-tasks').prop('hidden', $('#objective-hide-tasks').prop('checked'))
setCurrentTask()
}
@@ -420,35 +746,44 @@ jQuery(() => {
<div class="objective-settings">
<div class="inline-drawer">
<div class="inline-drawer-toggle inline-drawer-header">
<b>Objective</b>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<div class="inline-drawer-content">
<label for="objective-text"><small>Enter an objective and generate tasks. The AI will attempt to complete tasks autonomously</small></label>
<textarea id="objective-text" type="text" class="text_pole textarea_compact" rows="4"></textarea>
<div class="objective_block flex-container">
<input id="objective-generate" class="menu_button" type="submit" value="Auto-Generate Tasks" />
<label class="checkbox_label"><input id="objective-hide-tasks" type="checkbox"> Hide Tasks</label>
<b>Objective</b>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<div id="objective-tasks"> </div>
<div class="objective_block margin-bot-10px">
<div class="objective_block objective_block_control flex1 flexFlowColumn">
<label for="objective-chat-depth">Position in Chat</label>
<input id="objective-chat-depth" class="text_pole widthUnset" type="number" min="0" max="99" />
<div class="inline-drawer-content">
<label for="objective-text"><small>Enter an objective and generate tasks. The AI will attempt to complete tasks autonomously</small></label>
<textarea id="objective-text" type="text" class="text_pole textarea_compact" rows="4"></textarea>
<div class="objective_block flex-container">
<input id="objective-generate" class="menu_button" type="submit" value="Auto-Generate Tasks" />
<label class="checkbox_label"><input id="objective-hide-tasks" type="checkbox"> Hide Tasks</label>
</div>
<br>
<div class="objective_block objective_block_control flex1">
<label for="objective-check-frequency">Task Check Frequency</label>
<input id="objective-check-frequency" class="text_pole widthUnset" type="number" min="0" max="99" />
<small>(0 = disabled)</small>
<div id="objective-parent" class="objective_block flex-container">
<i class="objective-task-button fa-solid fa-circle-left fa-2x" title="Go to Parent"></i>
<small>Go to parent task</small>
</div>
<div id="objective-tasks"> </div>
<div class="objective_block margin-bot-10px">
<div class="objective_block objective_block_control flex1 flexFlowColumn">
<label for="objective-chat-depth">Position in Chat</label>
<input id="objective-chat-depth" class="text_pole widthUnset" type="number" min="0" max="99" />
</div>
<br>
<div class="objective_block objective_block_control flex1">
<label for="objective-check-frequency">Task Check Frequency</label>
<input id="objective-check-frequency" class="text_pole widthUnset" type="number" min="0" max="99" />
<small>(0 = disabled)</small>
</div>
</div>
<span> Messages until next AI task completion check <span id="objective-counter">0</span></span>
<div class="objective_block flex-container">
<input id="objective_prompt_edit" class="menu_button" type="submit" value="Edit Prompts" />
</div>
<hr class="sysHR">
</div>
<span> Messages until next AI task completion check <span id="objective-counter">0</span></span>
<hr class="sysHR">
</div>
</div>`;
</div>
`;
addManualTaskCheckUi()
$('#extensions_settings').append(settingsHtml);
@@ -456,14 +791,21 @@ jQuery(() => {
$('#objective-chat-depth').on('input', onChatDepthInput)
$("#objective-check-frequency").on('input', onCheckFrequencyInput)
$('#objective-hide-tasks').on('click', onHideTasksInput)
$('#objective_prompt_edit').on('click', onEditPromptClick)
$('#objective-parent').hide()
$('#objective-parent').on('click',onParentClick)
$('#objective-text').on('focusout',onObjectiveTextFocusOut)
loadSettings()
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

@@ -10,6 +10,13 @@
flex-wrap: wrap;
}
.objective_prompt_block {
display: flex;
align-items: baseline;
column-gap: 5px;
flex-wrap: wrap;
}
.objective_block_control {
align-items: baseline;
}

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

@@ -5,13 +5,14 @@ import { regex_placement } from "./engine.js";
async function saveRegexScript(regexScript, existingScriptIndex) {
// If not editing
if (existingScriptIndex === -1) {
// Is the script name undefined?
if (!regexScript.scriptName) {
toastr.error(`Could not save regex script: The script name was undefined or empty!`);
return;
}
// Is the script name undefined or empty?
if (!regexScript.scriptName) {
toastr.error(`Could not save regex script: The script name was undefined or empty!`);
return;
}
if (existingScriptIndex === -1) {
// Does the script name already exist?
if (extension_settings.regex.find((e) => e.scriptName === regexScript.scriptName)) {
toastr.error(`Could not save regex script: A script with name ${regexScript.scriptName} already exists.`);
@@ -29,14 +30,12 @@ async function saveRegexScript(regexScript, existingScriptIndex) {
// Is a find regex present?
if (regexScript.findRegex.length === 0) {
toastr.error(`Could not save regex script: A find regex is required!`);
return;
toastr.warning(`This regex script will not work, but was saved anyway: A find regex isn't present.`);
}
// Is there someplace to place results?
if (regexScript.placement.length === 0) {
toastr.error(`Could not save regex script: One placement checkbox must be selected!`);
return;
toastr.warning(`This regex script will not work, but was saved anyway: One "Affects" checkbox must be selected!`);
}
if (existingScriptIndex !== -1) {
@@ -140,7 +139,7 @@ async function onRegexEditorOpenClick(existingId) {
.prop("checked", true);
editorHtml
.find(`input[name="replace_position"][value="0"]`)
.find(`input[name="replace_position"][value="1"]`)
.prop("checked", true);
}

View File

@@ -0,0 +1,233 @@
// Borrowed from Agnai (AGPLv3)
// https://github.com/agnaistic/agnai/blob/dev/web/pages/Chat/components/SpeechRecognitionRecorder.tsx
// First version by Cohee#1207
// Adapted by Tony-sama
export { BrowserSttProvider }
const DEBUG_PREFIX = "<Speech Recognition module (Browser)> "
class BrowserSttProvider {
//########//
// Config //
//########//
settings = {
language: ""
}
defaultSettings = {
language: "en-US",
}
processTranscriptFunction = null;
get settingsHtml() {
let html = ' \
<span>Language</span> </br> \
<select id="speech_recognition_browser_provider_language"> \
<option value="ar-SA">ar-SA: Arabic (Saudi Arabia)</option> \
<option value="bn-BD">bn-BD: Bangla (Bangladesh)</option> \
<option value="bn-IN">bn-IN: Bangla (India)</option> \
<option value="cs-CZ">cs-CZ: Czech (Czech Republic)</option> \
<option value="da-DK">da-DK: Danish (Denmark)</option> \
<option value="de-AT">de-AT: German (Austria)</option> \
<option value="de-CH">de-CH: German (Switzerland)</option> \
<option value="de-DE">de-DE: German (Germany)</option> \
<option value="el-GR">el-GR: Greek (Greece)</option> \
<option value="en-AU">en-AU: English (Australia)</option> \
<option value="en-CA">en-CA: English (Canada)</option> \
<option value="en-GB">en-GB: English (United Kingdom)</option> \
<option value="en-IE">en-IE: English (Ireland)</option> \
<option value="en-IN">en-IN: English (India)</option> \
<option value="en-NZ">en-NZ: English (New Zealand)</option> \
<option value="en-US">en-US: English (United States)</option> \
<option value="en-ZA">en-ZA: English (South Africa)</option> \
<option value="es-AR">es-AR: Spanish (Argentina)</option> \
<option value="es-CL">es-CL: Spanish (Chile)</option> \
<option value="es-CO">es-CO: Spanish (Columbia)</option> \
<option value="es-ES">es-ES: Spanish (Spain)</option> \
<option value="es-MX">es-MX: Spanish (Mexico)</option> \
<option value="es-US">es-US: Spanish (United States)</option> \
<option value="fi-FI">fi-FI: Finnish (Finland)</option> \
<option value="fr-BE">fr-BE: French (Belgium)</option> \
<option value="fr-CA">fr-CA: French (Canada)</option> \
<option value="fr-CH">fr-CH: French (Switzerland)</option> \
<option value="fr-FR">fr-FR: French (France)</option> \
<option value="he-IL">he-IL: Hebrew (Israel)</option> \
<option value="hi-IN">hi-IN: Hindi (India)</option> \
<option value="hu-HU">hu-HU: Hungarian (Hungary)</option> \
<option value="id-ID">id-ID: Indonesian (Indonesia)</option> \
<option value="it-CH">it-CH: Italian (Switzerland)</option> \
<option value="it-IT">it-IT: Italian (Italy)</option> \
<option value="ja-JP">ja-JP: Japanese (Japan)</option> \
<option value="ko-KR">ko-KR: Korean (Republic of Korea)</option> \
<option value="nl-BE">nl-BE: Dutch (Belgium)</option> \
<option value="nl-NL">nl-NL: Dutch (The Netherlands)</option> \
<option value="no-NO">no-NO: Norwegian (Norway)</option> \
<option value="pl-PL">pl-PL: Polish (Poland)</option> \
<option value="pt-BR">pt-BR: Portugese (Brazil)</option> \
<option value="pt-PT">pt-PT: Portugese (Portugal)</option> \
<option value="ro-RO">ro-RO: Romanian (Romania)</option> \
<option value="ru-RU">ru-RU: Russian (Russian Federation)</option> \
<option value="sk-SK">sk-SK: Slovak (Slovakia)</option> \
<option value="sv-SE">sv-SE: Swedish (Sweden)</option> \
<option value="ta-IN">ta-IN: Tamil (India)</option> \
<option value="ta-LK">ta-LK: Tamil (Sri Lanka)</option> \
<option value="th-TH">th-TH: Thai (Thailand)</option> \
<option value="tr-TR">tr-TR: Turkish (Turkey)</option> \
<option value="zh-CN">zh-CN: Chinese (China)</option> \
<option value="zh-HK">zh-HK: Chinese (Hond Kong)</option> \
<option value="zh-TW">zh-TW: Chinese (Taiwan)</option> \
</select> \
'
return html
}
onSettingsChange() {
// Used when provider settings are updated from UI
this.settings.language = $("#speech_recognition_browser_provider_language").val();
console.debug(DEBUG_PREFIX+"Change language to",this.settings.language);
this.loadSettings(this.settings);
}
static capitalizeInterim(interimTranscript) {
let capitalizeIndex = -1;
if (interimTranscript.length > 2 && interimTranscript[0] === ' ') capitalizeIndex = 1;
else if (interimTranscript.length > 1) capitalizeIndex = 0;
if (capitalizeIndex > -1) {
const spacing = capitalizeIndex > 0 ? ' '.repeat(capitalizeIndex - 1) : '';
const capitalized = interimTranscript[capitalizeIndex].toLocaleUpperCase();
const rest = interimTranscript.substring(capitalizeIndex + 1);
interimTranscript = spacing + capitalized + rest;
}
return interimTranscript;
}
static composeValues(previous, interim) {
let spacing = '';
if (previous.endsWith('.')) spacing = ' ';
return previous + spacing + interim;
}
loadSettings(settings) {
const processTranscript = this.processTranscriptFunction;
// Populate Provider UI given input settings
if (Object.keys(settings).length == 0) {
console.debug(DEBUG_PREFIX+"Using default browser STT settings")
}
// Initialise as 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 Speech recogniton extension (browser): ${key}`
}
}
$("#speech_recognition_browser_provider_language").val(this.settings.language);
const speechRecognitionSettings = $.extend({
grammar: '' // Custom grammar
}, options);
const speechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
const speechRecognitionList = window.SpeechGrammarList || window.webkitSpeechGrammarList;
if (!speechRecognition) {
console.warn(DEBUG_PREFIX+'Speech recognition is not supported in this browser.');
$("#microphone_button").hide();
toastr.error("Speech recognition is not supported in this browser, use another browser or another provider of SillyTavern-extras Speech recognition extension.", "Speech recognition activation Failed (Browser)", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
return;
}
const recognition = new speechRecognition();
if (speechRecognitionSettings.grammar && speechRecognitionList) {
speechRecognitionList.addFromString(speechRecognitionSettings.grammar, 1);
recognition.grammars = speechRecognitionList;
}
recognition.continuous = true;
recognition.interimResults = true;
recognition.lang = this.settings.language;
const textarea = $('#send_textarea');
const button = $('#microphone_button');
let listening = false;
button.off('click').on("click", function () {
if (listening) {
recognition.stop();
} else {
recognition.start();
}
listening = !listening;
});
let initialText = '';
recognition.onresult = function (speechEvent) {
let finalTranscript = '';
let interimTranscript = ''
for (let i = speechEvent.resultIndex; i < speechEvent.results.length; ++i) {
const transcript = speechEvent.results[i][0].transcript;
if (speechEvent.results[i].isFinal) {
let interim = BrowserSttProvider.capitalizeInterim(transcript);
if (interim != '') {
let final = finalTranscript;
final = BrowserSttProvider.composeValues(final, interim);
if (final.slice(-1) != '.' & final.slice(-1) != '?') final += '.';
finalTranscript = final;
recognition.abort();
listening = false;
}
interimTranscript = ' ';
} else {
interimTranscript += transcript;
}
}
interimTranscript = BrowserSttProvider.capitalizeInterim(interimTranscript);
textarea.val(initialText + finalTranscript + interimTranscript);
};
recognition.onerror = function (event) {
console.error('Error occurred in recognition:', event.error);
//if ($('#speech_recognition_debug').is(':checked'))
// toastr.error('Error occurred in recognition:'+ event.error, 'STT Generation error (Browser)', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
};
recognition.onend = function () {
listening = false;
button.toggleClass('fa-microphone fa-microphone-slash');
const newText = textarea.val().substring(initialText.length);
textarea.val(textarea.val().substring(0,initialText.length));
processTranscript(newText);
};
recognition.onstart = function () {
initialText = textarea.val();
button.toggleClass('fa-microphone fa-microphone-slash');
if ($("#speech_recognition_message_mode").val() == "replace") {
textarea.val("");
initialText = ""
}
};
$("#microphone_button").show();
console.debug(DEBUG_PREFIX+"Browser STT settings loaded")
}
}

View File

@@ -1,110 +1,439 @@
// Borrowed from Agnai (AGPLv3)
// https://github.com/agnaistic/agnai/blob/dev/web/pages/Chat/components/SpeechRecognitionRecorder.tsx
function capitalizeInterim(interimTranscript) {
let capitalizeIndex = -1;
if (interimTranscript.length > 2 && interimTranscript[0] === ' ') capitalizeIndex = 1;
else if (interimTranscript.length > 1) capitalizeIndex = 0;
if (capitalizeIndex > -1) {
const spacing = capitalizeIndex > 0 ? ' '.repeat(capitalizeIndex - 1) : '';
const capitalized = interimTranscript[capitalizeIndex].toLocaleUpperCase();
const rest = interimTranscript.substring(capitalizeIndex + 1);
interimTranscript = spacing + capitalized + rest;
/*
TODO:
- try pseudo streaming audio by just sending chunk every X seconds and asking VOSK if it is full text.
*/
import { saveSettingsDebounced } from "../../../script.js";
import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch } from "../../extensions.js";
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
let sttProviderName = "None"
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;
}
return interimTranscript;
}
function composeValues(previous, interim) {
let spacing = '';
if (previous.endsWith('.')) spacing = ' ';
return previous + spacing + interim;
async function processTranscript(transcript) {
try {
const transcriptOriginal = transcript;
let transcriptFormatted = transcriptOriginal.trim();
if (transcriptFormatted.length > 0)
{
console.debug(DEBUG_PREFIX+"recorded transcript: \""+transcriptFormatted+"\"");
const messageMode = extension_settings.speech_recognition.messageMode;
console.debug(DEBUG_PREFIX+"mode: "+messageMode);
let transcriptLower = transcriptFormatted.toLowerCase()
// remove punctuation
let transcriptRaw = transcriptLower.replace(/[^\w\s\']|_/g, "").replace(/\s+/g, " ");
// Check message mapping
if (extension_settings.speech_recognition.messageMappingEnabled) {
console.debug(DEBUG_PREFIX+"Start searching message mapping into:",transcriptRaw)
for (const key in extension_settings.speech_recognition.messageMapping) {
console.debug(DEBUG_PREFIX+"message mapping searching: ", key,"=>",extension_settings.speech_recognition.messageMapping[key]);
if (transcriptRaw.includes(key)) {
var message = extension_settings.speech_recognition.messageMapping[key];
console.debug(DEBUG_PREFIX+"message mapping found: ", key,"=>",extension_settings.speech_recognition.messageMapping[key]);
$("#send_textarea").val(message);
if (messageMode == "auto_send") await getContext().generate();
return;
}
}
}
console.debug(DEBUG_PREFIX+"no message mapping found, processing transcript as normal message");
switch (messageMode) {
case "auto_send":
$('#send_textarea').val("") // clear message area to avoid double message
console.debug(DEBUG_PREFIX+"Sending message")
const context = getContext();
const messageText = transcriptFormatted;
const message = {
name: context.name1,
is_user: true,
is_name: true,
send_date: Date.now(),
mes: messageText,
};
context.chat.push(message);
context.addOneMessage(message);
await context.generate();
$('#debug_output').text("<SST-module DEBUG>: message sent: \""+ transcriptFormatted +"\"");
break;
case "replace":
console.debug(DEBUG_PREFIX+"Replacing message")
$('#send_textarea').val(transcriptFormatted);
break;
case "append":
console.debug(DEBUG_PREFIX+"Appending message")
$('#send_textarea').val($('#send_textarea').val()+" "+transcriptFormatted);
break;
default:
console.debug(DEBUG_PREFIX+"Not supported stt message mode: "+messageMode)
}
}
else
{
console.debug(DEBUG_PREFIX+"Empty transcript, do nothing");
}
}
catch (error) {
console.debug(error);
}
}
(function ($) {
$.fn.speechRecognitionPlugin = function (options) {
const settings = $.extend({
grammar: '' // Custom grammar
}, options);
const speechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
const speechRecognitionList = window.SpeechGrammarList || window.webkitSpeechGrammarList;
if (!speechRecognition) {
console.warn('Speech recognition is not supported in this browser.');
return;
function loadNavigatorAudioRecording() {
if (navigator.mediaDevices.getUserMedia) {
console.debug(DEBUG_PREFIX+' getUserMedia supported by browser.');
let onSuccess = function(stream) {
const mediaRecorder = new MediaRecorder(stream);
$("#microphone_button").off('click').on("click", function() {
if (!audioRecording) {
mediaRecorder.start();
console.debug(mediaRecorder.state);
console.debug("recorder started");
audioRecording = true;
$("#microphone_button").toggleClass('fa-microphone fa-microphone-slash');
}
else {
mediaRecorder.stop();
console.debug(mediaRecorder.state);
console.debug("recorder stopped");
audioRecording = false;
$("#microphone_button").toggleClass('fa-microphone fa-microphone-slash');
}
});
mediaRecorder.onstop = async function() {
console.debug(DEBUG_PREFIX+"data available after MediaRecorder.stop() called: ", audioChunks.length, " chunks");
const audioBlob = new Blob(audioChunks, { type: "audio/wav; codecs=0" });
audioChunks = [];
const transcript = await sttProvider.processAudio(audioBlob);
// TODO: lock and release recording while processing?
console.debug(DEBUG_PREFIX+"received transcript:", transcript);
processTranscript(transcript);
}
mediaRecorder.ondataavailable = function(e) {
audioChunks.push(e.data);
}
}
const recognition = new speechRecognition();
if (settings.grammar && speechRecognitionList) {
speechRecognitionList.addFromString(settings.grammar, 1);
recognition.grammars = speechRecognitionList;
let onError = function(err) {
console.debug(DEBUG_PREFIX+"The following error occured: " + err);
}
navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError);
} else {
console.debug(DEBUG_PREFIX+"getUserMedia not supported on your browser!");
toastr.error("getUserMedia not supported", DEBUG_PREFIX+"not supported for your browser.", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
}
}
recognition.continuous = true;
recognition.interimResults = true;
// TODO: This should be configurable.
recognition.lang = 'en-US'; // Set the language to English (US).
//##############//
// STT Provider //
//##############//
const $textarea = this;
const $button = $('<div class="fa-solid fa-microphone speech-toggle" title="Click to speak"></div>');
function loadSttProvider(provider) {
//Clear the current config and add new config
$("#speech_recognition_provider_settings").html("");
// Init provider references
extension_settings.speech_recognition.currentProvider = provider;
sttProviderName = provider;
if (!(sttProviderName in extension_settings.speech_recognition)) {
console.warn(`Provider ${sttProviderName} not in Extension Settings, initiatilizing provider in settings`);
extension_settings.speech_recognition[sttProviderName] = {};
}
$('#speech_recognition_provider').val(sttProviderName);
if (sttProviderName == "None") {
$("#microphone_button").hide();
$("#speech_recognition_message_mode_div").hide();
$("#speech_recognition_message_mapping_div").hide();
return;
}
$("#speech_recognition_message_mode_div").show();
$("#speech_recognition_message_mapping_div").show();
sttProvider = new sttProviders[sttProviderName]
// Init provider settings
$('#speech_recognition_provider_settings').append(sttProvider.settingsHtml);
// Use microphone button as push to talk
if (sttProviderName == "Browser") {
sttProvider.processTranscriptFunction = processTranscript;
sttProvider.loadSettings(extension_settings.speech_recognition[sttProviderName]);
$("#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() {
const sttProviderSelection = $('#speech_recognition_provider').val();
loadSttProvider(sttProviderSelection);
saveSettingsDebounced();
}
function onSttProviderSettingsInput() {
sttProvider.onSettingsChange();
// Persist changes to SillyTavern stt extension settings
extension_settings.speech_recognition[sttProviderName] = sttProvider.settings;
saveSettingsDebounced();
console.info(`Saved settings ${sttProviderName} ${JSON.stringify(sttProvider.settings)}`);
}
//#############################//
// Extension UI and Settings //
//#############################//
const defaultSettings = {
currentProvider: "None",
messageMode: "append",
messageMappingText: "",
messageMapping: [],
messageMappingEnabled: false,
}
function loadSettings() {
if (Object.keys(extension_settings.speech_recognition).length === 0) {
Object.assign(extension_settings.speech_recognition, defaultSettings)
}
$('#speech_recognition_enabled').prop('checked',extension_settings.speech_recognition.enabled);
$('#speech_recognition_message_mode').val(extension_settings.speech_recognition.messageMode);
if (extension_settings.speech_recognition.messageMappingText.length > 0) {
$('#speech_recognition_message_mapping').val(extension_settings.speech_recognition.messageMappingText);
}
$('#speech_recognition_message_mapping_enabled').prop('checked',extension_settings.speech_recognition.messageMappingEnabled);
}
async function onMessageModeChange() {
extension_settings.speech_recognition.messageMode = $('#speech_recognition_message_mode').val();
if(sttProviderName != "Browser" & extension_settings.speech_recognition.messageMode == "auto_send") {
$("#speech_recognition_wait_response_div").show()
}
else {
$("#speech_recognition_wait_response_div").hide()
}
saveSettingsDebounced();
}
async function onMessageMappingChange() {
let array = $('#speech_recognition_message_mapping').val().split(",");
array = array.map(element => {return element.trim();});
array = array.filter((str) => str !== '');
extension_settings.speech_recognition.messageMapping = {};
for (const text of array) {
if (text.includes("=")) {
const pair = text.toLowerCase().split("=")
extension_settings.speech_recognition.messageMapping[pair[0].trim()] = pair[1].trim()
console.debug(DEBUG_PREFIX+"Added mapping", pair[0],"=>", extension_settings.speech_recognition.messageMapping[pair[0]]);
}
else {
console.debug(DEBUG_PREFIX+"Wrong syntax for message mapping, no '=' found in:", text);
}
}
$("#speech_recognition_message_mapping_status").text("Message mapping updated to: "+JSON.stringify(extension_settings.speech_recognition.messageMapping))
console.debug(DEBUG_PREFIX+"Updated message mapping", extension_settings.speech_recognition.messageMapping);
extension_settings.speech_recognition.messageMappingText = $('#speech_recognition_message_mapping').val()
saveSettingsDebounced();
}
async function onMessageMappingEnabledClick() {
extension_settings.speech_recognition.messageMappingEnabled = $('#speech_recognition_message_mapping_enabled').is(':checked');
saveSettingsDebounced()
}
$(document).ready(function () {
function addExtensionControls() {
const settingsHtml = `
<div id="speech_recognition_settings">
<div class="inline-drawer">
<div class="inline-drawer-toggle inline-drawer-header">
<b>Speech Recognition</b>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<div class="inline-drawer-content">
<div>
<span>Select Speech-to-text Provider</span> </br>
<select id="speech_recognition_provider">
</select>
</div>
<div id="speech_recognition_message_mode_div">
<span>Message Mode</span> </br>
<select id="speech_recognition_message_mode">
<option value="append">Append</option>
<option value="replace">Replace</option>
<option value="auto_send">Auto send</option>
</select>
</div>
<div id="speech_recognition_message_mapping_div">
<span>Message Mapping</span>
<textarea id="speech_recognition_message_mapping" class="text_pole textarea_compact" type="text" rows="4" placeholder="Enter comma separated phrases mapping, example:\ncommand delete = /del 2,\nslash delete = /del 2,\nsystem roll = /roll 2d6,\nhey continue = /continue"></textarea>
<span id="speech_recognition_message_mapping_status"></span>
<label class="checkbox_label" for="speech_recognition_message_mapping_enabled">
<input type="checkbox" id="speech_recognition_message_mapping_enabled" name="speech_recognition_message_mapping_enabled">
<small>Enable messages mapping</small>
</label>
</div>
<form id="speech_recognition_provider_settings" class="inline-drawer-content">
</form>
</div>
</div>
</div>
`;
$('#extensions_settings').append(settingsHtml);
$('#speech_recognition_provider_settings').on('input', onSttProviderSettingsInput);
for (const provider in sttProviders) {
$('#speech_recognition_provider').append($("<option />").val(provider).text(provider));
console.debug(DEBUG_PREFIX+"added option "+provider);
}
$('#speech_recognition_provider').on('change', onSttProviderChange);
$('#speech_recognition_message_mode').on('change', onMessageModeChange);
$('#speech_recognition_message_mapping').on('change', onMessageMappingChange);
$('#speech_recognition_message_mapping_enabled').on('click', onMessageMappingEnabledClick);
const $button = $('<div id="microphone_button" class="fa-solid fa-microphone speech-toggle" title="Click to speak"></div>');
$('#send_but_sheld').prepend($button);
let listening = false;
$button.on('click', function () {
if (listening) {
recognition.stop();
} else {
recognition.start();
}
listening = !listening;
});
let initialText = '';
recognition.onresult = function (speechEvent) {
let finalTranscript = '';
let interimTranscript = ''
for (let i = speechEvent.resultIndex; i < speechEvent.results.length; ++i) {
const transcript = speechEvent.results[i][0].transcript;
if (speechEvent.results[i].isFinal) {
let interim = capitalizeInterim(transcript);
if (interim != '') {
let final = finalTranscript;
final = composeValues(final, interim) + '.';
finalTranscript = final;
recognition.abort();
listening = false;
}
interimTranscript = ' ';
} else {
interimTranscript += transcript;
}
}
interimTranscript = capitalizeInterim(interimTranscript);
$textarea.val(initialText + finalTranscript + interimTranscript);
};
recognition.onerror = function (event) {
console.error('Error occurred in recognition:', event.error);
};
recognition.onend = function () {
listening = false;
$button.toggleClass('fa-microphone fa-microphone-slash');
};
recognition.onstart = function () {
initialText = $textarea.val();
$button.toggleClass('fa-microphone fa-microphone-slash');
};
};
}(jQuery));
jQuery(() => {
const $textarea = $('#send_textarea');
$textarea.speechRecognitionPlugin();
});
}
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();
})

View File

@@ -2,10 +2,13 @@
"display_name": "Speech Recognition",
"loading_order": 13,
"requires": [],
"optional": [],
"optional": [
"vosk-speech-recognition",
"whisper-speech-recognition"
],
"js": "index.js",
"css": "style.css",
"author": "Cohee#1207",
"version": "1.0.0",
"author": "Cohee#1207 and Keij#6799",
"version": "1.1.0",
"homePage": "https://github.com/SillyTavern/SillyTavern"
}

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

@@ -0,0 +1,65 @@
import { getApiUrl, doExtrasFetch } from "../../extensions.js";
export { VoskSttProvider }
const DEBUG_PREFIX = "<Speech Recognition module (Vosk)> "
class VoskSttProvider {
//########//
// Config //
//########//
settings
defaultSettings = {
}
get settingsHtml() {
let html = ""
return html
}
onSettingsChange() {
// Used when provider settings are updated from UI
}
loadSettings(settings) {
// Populate Provider UI given input settings
if (Object.keys(settings).length == 0) {
console.debug(DEBUG_PREFIX+"Using default vosk 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}`
}
}
console.debug(DEBUG_PREFIX+"Vosk STT settings loaded")
}
async processAudio(audioblob) {
var requestData = new FormData();
requestData.append('AudioFile', audioblob, 'record.wav');
const url = new URL(getApiUrl());
url.pathname = '/api/speech-recognition/vosk/process-audio';
const apiResult = await doExtrasFetch(url, {
method: 'POST',
body: requestData,
});
if (!apiResult.ok) {
toastr.error(apiResult.statusText, 'STT Generation Failed (Vosk)', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
throw new Error(`HTTP ${apiResult.status}: ${await apiResult.text()}`);
}
const result = await apiResult.json();
return result.transcript;
}
}

View File

@@ -0,0 +1,67 @@
import { getApiUrl, doExtrasFetch } from "../../extensions.js";
export { WhisperSttProvider }
const DEBUG_PREFIX = "<Speech Recognition module (Vosk)> "
class WhisperSttProvider {
//########//
// Config //
//########//
settings
defaultSettings = {
//model_path: "",
}
get settingsHtml() {
let html = ""
return html
}
onSettingsChange() {
// Used when provider settings are updated from UI
}
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}`
}
}
console.debug(DEBUG_PREFIX+"Whisper STT settings loaded")
}
async processAudio(audioblob) {
var requestData = new FormData();
requestData.append('AudioFile', audioblob, 'record.wav');
const url = new URL(getApiUrl());
url.pathname = '/api/speech-recognition/whisper/process-audio';
const apiResult = await doExtrasFetch(url, {
method: 'POST',
body: requestData,
});
if (!apiResult.ok) {
toastr.error(apiResult.statusText, 'STT Generation Failed (Whisper)', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
throw new Error(`HTTP ${apiResult.status}: ${await apiResult.text()}`);
}
const result = await apiResult.json();
return result.transcript;
}
}

View File

@@ -8,10 +8,13 @@ import {
getRequestHeaders,
event_types,
eventSource,
appendImageToMessage
appendImageToMessage,
generateQuietPrompt,
this_chid,
} from "../../../script.js";
import { getApiUrl, getContext, extension_settings, doExtrasFetch, modules } from "../../extensions.js";
import { stringFormat, initScrollHeight, resetScrollHeight, timestampToMoment } from "../../utils.js";
import { selected_group } from "../../group-chats.js";
import { stringFormat, initScrollHeight, resetScrollHeight, timestampToMoment, getCharaFilename } from "../../utils.js";
export { MODULE_NAME };
// Wraps a string into monospace font-face span
@@ -39,6 +42,15 @@ const generationMode = {
FREE: 6,
}
const modeLabels = {
[generationMode.CHARACTER]: 'Character ("Yourself")',
[generationMode.FACE]: 'Portrait ("Your Face")',
[generationMode.USER]: 'User ("Me")',
[generationMode.SCENARIO]: 'Scenario ("The Whole Story")',
[generationMode.NOW]: 'Last Message',
[generationMode.RAW_LAST]: 'Raw Last Message',
}
const triggerWords = {
[generationMode.CHARACTER]: ['you'],
[generationMode.USER]: ['me'],
@@ -48,7 +60,7 @@ const triggerWords = {
[generationMode.FACE]: ['face'],
}
const quietPrompts = {
const promptTemplates = {
/*OLD: [generationMode.CHARACTER]: "Pause your roleplay and provide comma-delimited list of phrases and keywords which describe {{char}}'s physical appearance and clothing. Ignore {{char}}'s personality traits, and chat history when crafting this description. End your response once the comma-delimited list is complete. Do not roleplay when writing this description, and do not attempt to continue the story.", */
[generationMode.CHARACTER]: "[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,']",
//face-specific prompt
@@ -134,6 +146,8 @@ const defaultSettings = {
// Refine mode
refine_mode: false,
prompts: promptTemplates,
}
async function loadSettings() {
@@ -141,6 +155,14 @@ async function loadSettings() {
Object.assign(extension_settings.sd, defaultSettings);
}
if (extension_settings.sd.prompts === undefined) {
extension_settings.sd.prompts = promptTemplates;
}
if (extension_settings.sd.character_prompts === undefined) {
extension_settings.sd.character_prompts = {};
}
$('#sd_scale').val(extension_settings.sd.scale).trigger('input');
$('#sd_steps').val(extension_settings.sd.steps).trigger('input');
$('#sd_prompt_prefix').val(extension_settings.sd.prompt_prefix).trigger('input');
@@ -154,9 +176,104 @@ async function loadSettings() {
$('#sd_enable_hr').prop('checked', extension_settings.sd.enable_hr);
$('#sd_refine_mode').prop('checked', extension_settings.sd.refine_mode);
addPromptTemplates();
await Promise.all([loadSamplers(), loadModels()]);
}
function addPromptTemplates() {
$('#sd_prompt_templates').empty();
for (const [name, prompt] of Object.entries(extension_settings.sd.prompts)) {
const label = $('<label></label>')
.text(modeLabels[name])
.attr('for', `sd_prompt_${name}`);
const textarea = $('<textarea></textarea>')
.addClass('textarea_compact text_pole')
.attr('id', `sd_prompt_${name}`)
.attr('rows', 6)
.val(prompt).on('input', () => {
extension_settings.sd.prompts[name] = textarea.val();
saveSettingsDebounced();
});
const button = $('<button></button>')
.addClass('menu_button fa-solid fa-undo')
.attr('title', 'Restore default')
.on('click', () => {
textarea.val(promptTemplates[name]);
extension_settings.sd.prompts[name] = promptTemplates[name];
saveSettingsDebounced();
});
const container = $('<div></div>')
.addClass('title_restorable')
.append(label)
.append(button)
$('#sd_prompt_templates').append(container);
$('#sd_prompt_templates').append(textarea);
}
}
async function refinePrompt(prompt) {
if (extension_settings.sd.refine_mode) {
const refinedPrompt = await callPopup('<h3>Review and edit the prompt:</h3>Press "Cancel" to abort the image generation.', 'input', prompt, { rows: 5, okButton: 'Generate' });
if (refinedPrompt) {
return refinedPrompt;
} else {
throw new Error('Generation aborted by user.');
}
}
return prompt;
}
function onChatChanged() {
if (this_chid === undefined || selected_group) {
$('#sd_character_prompt_block').hide();
return;
}
$('#sd_character_prompt_block').show();
const key = getCharaFilename(this_chid);
$('#sd_character_prompt').val(key ? (extension_settings.sd.character_prompts[key] || '') : '');
}
function onCharacterPromptInput() {
const key = getCharaFilename(this_chid);
extension_settings.sd.character_prompts[key] = $('#sd_character_prompt').val();
resetScrollHeight($(this));
saveSettingsDebounced();
}
function getCharacterPrefix() {
if (selected_group) {
return '';
}
const key = getCharaFilename(this_chid);
if (key) {
return extension_settings.sd.character_prompts[key] || '';
}
return '';
}
function combinePrefixes(str1, str2) {
if (!str2) {
return str1;
}
// Remove leading/trailing white spaces and commas from the strings
str1 = str1.trim().replace(/^,|,$/g, '');
str2 = str2.trim().replace(/^,|,$/g, '');
// Combine the strings with a comma between them
var result = `${str1}, ${str2},`;
return result;
}
function onRefineModeInput() {
extension_settings.sd.refine_mode = !!$('#sd_refine_mode').prop('checked');
saveSettingsDebounced();
@@ -383,7 +500,7 @@ function getQuietPrompt(mode, trigger) {
return trigger;
}
return substituteParams(stringFormat(quietPrompts[mode], trigger));
return substituteParams(stringFormat(extension_settings.sd.prompts[mode], trigger));
}
function processReply(str) {
@@ -438,7 +555,8 @@ async function generatePicture(_, trigger, message, callback) {
const prevSDHeight = extension_settings.sd.height;
if (generationType == generationMode.FACE) {
extension_settings.sd.height = extension_settings.sd.width * 1.5;
// Round to nearest multiple of 64
extension_settings.sd.height = Math.round(extension_settings.sd.height * 1.5 / 64) * 64;
}
try {
@@ -475,31 +593,16 @@ async function getPrompt(generationType, message, trigger, quiet_prompt) {
break;
}
if (generationType !== generationMode.FREE) {
prompt = await refinePrompt(prompt);
}
return prompt;
}
async function generatePrompt(quiet_prompt) {
let reply = processReply(await new Promise(
async function promptPromise(resolve, reject) {
try {
await getContext().generate('quiet', { resolve, reject, quiet_prompt, force_name2: true, });
}
catch {
reject();
}
}));
if (extension_settings.sd.refine_mode) {
const refinedPrompt = await callPopup('<h3>Review and edit the generated prompt:</h3>Press "Cancel" to abort the image generation.', 'input', reply, { rows: 5, okButton: 'Generate' });
if (refinedPrompt) {
reply = refinedPrompt;
} else {
throw new Error('Generation aborted by user.');
}
}
return reply;
const reply = await generateQuietPrompt(quiet_prompt);
return processReply(reply);
}
async function sendGenerationRequest(prompt, callback) {
@@ -524,7 +627,7 @@ async function generateExtrasImage(prompt, callback) {
scale: extension_settings.sd.scale,
width: extension_settings.sd.width,
height: extension_settings.sd.height,
prompt_prefix: extension_settings.sd.prompt_prefix,
prompt_prefix: combinePrefixes(extension_settings.sd.prompt_prefix, getCharacterPrefix()),
negative_prompt: extension_settings.sd.negative_prompt,
restore_faces: !!extension_settings.sd.restore_faces,
enable_hr: !!extension_settings.sd.enable_hr,
@@ -552,7 +655,7 @@ async function generateHordeImage(prompt, callback) {
scale: extension_settings.sd.scale,
width: extension_settings.sd.width,
height: extension_settings.sd.height,
prompt_prefix: extension_settings.sd.prompt_prefix,
prompt_prefix: combinePrefixes(extension_settings.sd.prompt_prefix, getCharacterPrefix()),
negative_prompt: extension_settings.sd.negative_prompt,
model: extension_settings.sd.model,
nsfw: extension_settings.sd.horde_nsfw,
@@ -690,7 +793,9 @@ async function sdMessageButton(e) {
try {
setBusyIcon(true);
if (hasSavedImage) {
const prompt = message?.extra?.title;
const prompt = await refinePrompt(message.extra.title);
message.extra.title = prompt;
console.log('Regenerating an image, using existing prompt:', prompt);
await sendGenerationRequest(prompt, saveGeneratedImage);
}
@@ -763,60 +868,74 @@ jQuery(async () => {
<div class="sd_settings">
<div class="inline-drawer">
<div class="inline-drawer-toggle inline-drawer-header">
<b>Stable Diffusion</b>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
<b>Stable Diffusion</b>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<div class="inline-drawer-content">
<small><i>Use slash commands or the bottom Paintbrush button to generate images. Type <span class="monospace">/help</span> in chat for more details</i></small>
<br>
<small><i>Hint: Save an API key in Horde KoboldAI API settings to use it here.</i></small>
<label for="sd_refine_mode" class="checkbox_label" title="Allow to edit prompts manually before sending them to generation API">
<input id="sd_refine_mode" type="checkbox" />
Edit prompts before generation
</label>
<div class="flex-container flexGap5 marginTop10 margin-bot-10px">
<label class="checkbox_label">
<input id="sd_horde" type="checkbox" />
Use Stable Horde
</label>
<label style="margin-left:1em;" class="checkbox_label">
<input id="sd_horde_nsfw" type="checkbox" />
Allow NSFW images from Horde
</label>
</div>
<label for="sd_scale">CFG Scale (<span id="sd_scale_value"></span>)</label>
<input id="sd_scale" type="range" min="${defaultSettings.scale_min}" max="${defaultSettings.scale_max}" step="${defaultSettings.scale_step}" value="${defaultSettings.scale}" />
<label for="sd_steps">Sampling steps (<span id="sd_steps_value"></span>)</label>
<input id="sd_steps" type="range" min="${defaultSettings.steps_min}" max="${defaultSettings.steps_max}" step="${defaultSettings.steps_step}" value="${defaultSettings.steps}" />
<label for="sd_width">Width (<span id="sd_width_value"></span>)</label>
<input id="sd_width" type="range" max="${defaultSettings.dimension_max}" min="${defaultSettings.dimension_min}" step="${defaultSettings.dimension_step}" value="${defaultSettings.width}" />
<label for="sd_height">Height (<span id="sd_height_value"></span>)</label>
<input id="sd_height" type="range" max="${defaultSettings.dimension_max}" min="${defaultSettings.dimension_min}" step="${defaultSettings.dimension_step}" value="${defaultSettings.height}" />
<div><small>Only for Horde or remote Stable Diffusion Web UI:</small></div>
<div class="flex-container marginTop10 margin-bot-10px">
<label class="flex1 checkbox_label">
<input id="sd_restore_faces" type="checkbox" />
Restore Faces
</label>
<label class="flex1 checkbox_label">
<input id="sd_enable_hr" type="checkbox" />
Hires. Fix
</label>
</div>
<label for="sd_model">Stable Diffusion model</label>
<select id="sd_model"></select>
<label for="sd_sampler">Sampling method</label>
<select id="sd_sampler"></select>
<div class="flex-container flexGap5 margin-bot-10px">
<label class="checkbox_label">
<input id="sd_horde_karras" type="checkbox" />
Karras (only for Horde, not all samplers supported)
</label>
</div>
<label for="sd_prompt_prefix">Common prompt prefix</label>
<textarea id="sd_prompt_prefix" class="text_pole textarea_compact" rows="3"></textarea>
<div id="sd_character_prompt_block">
<label for="sd_character_prompt">Character-specific prompt prefix</label>
<small>Won't be used in groups.</small>
<textarea id="sd_character_prompt" class="text_pole textarea_compact" rows="3" placeholder="Any characteristics that describe the currently selected character. Will be added after a common prefix.&#10;Example: female, green eyes, brown hair, pink shirt"></textarea>
</div>
<label for="sd_negative_prompt">Negative prompt</label>
<textarea id="sd_negative_prompt" class="text_pole textarea_compact" rows="3"></textarea>
</div>
</div>
<div class="inline-drawer-content">
<small><i>Use slash commands or the bottom Paintbrush button to generate images. Type <span class="monospace">/help</span> in chat for more details</i></small>
<br>
<small><i>Hint: Save an API key in Horde KoboldAI API settings to use it here.</i></small>
<label for="sd_refine_mode" class="checkbox_label" title="Allow to edit prompts manually before sending them to generation API">
<input id="sd_refine_mode" type="checkbox" />
Edit prompts before generation
</label>
<div class="flex-container flexGap5 marginTop10 margin-bot-10px">
<label class="checkbox_label">
<input id="sd_horde" type="checkbox" />
Use Stable Horde
</label>
<label style="margin-left:1em;" class="checkbox_label">
<input id="sd_horde_nsfw" type="checkbox" />
Allow NSFW images from Horde
</label>
<div class="inline-drawer">
<div class="inline-drawer-toggle inline-drawer-header">
<b>SD Prompt Templates</b>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<label for="sd_scale">CFG Scale (<span id="sd_scale_value"></span>)</label>
<input id="sd_scale" type="range" min="${defaultSettings.scale_min}" max="${defaultSettings.scale_max}" step="${defaultSettings.scale_step}" value="${defaultSettings.scale}" />
<label for="sd_steps">Sampling steps (<span id="sd_steps_value"></span>)</label>
<input id="sd_steps" type="range" min="${defaultSettings.steps_min}" max="${defaultSettings.steps_max}" step="${defaultSettings.steps_step}" value="${defaultSettings.steps}" />
<label for="sd_width">Width (<span id="sd_width_value"></span>)</label>
<input id="sd_width" type="range" max="${defaultSettings.dimension_max}" min="${defaultSettings.dimension_min}" step="${defaultSettings.dimension_step}" value="${defaultSettings.width}" />
<label for="sd_height">Height (<span id="sd_height_value"></span>)</label>
<input id="sd_height" type="range" max="${defaultSettings.dimension_max}" min="${defaultSettings.dimension_min}" step="${defaultSettings.dimension_step}" value="${defaultSettings.height}" />
<div><small>Only for Horde or remote Stable Diffusion Web UI:</small></div>
<div class="flex-container marginTop10 margin-bot-10px">
<label class="flex1 checkbox_label">
<input id="sd_restore_faces" type="checkbox" />
Restore Faces
</label>
<label class="flex1 checkbox_label">
<input id="sd_enable_hr" type="checkbox" />
Hires. Fix
</label>
<div id="sd_prompt_templates" class="inline-drawer-content">
</div>
<label for="sd_model">Stable Diffusion model</label>
<select id="sd_model"></select>
<label for="sd_sampler">Sampling method</label>
<select id="sd_sampler"></select>
<div class="flex-container flexGap5 margin-bot-10px">
<label class="checkbox_label">
<input id="sd_horde_karras" type="checkbox" />
Karras (only for Horde, not all samplers supported)
</label>
</div>
<label for="sd_prompt_prefix">Generated prompt prefix</label>
<textarea id="sd_prompt_prefix" class="text_pole textarea_compact" rows="2"></textarea>
<label for="sd_negative_prompt">Negative prompt</label>
<textarea id="sd_negative_prompt" class="text_pole textarea_compact" rows="2"></textarea>
</div>
</div>`;
@@ -835,16 +954,21 @@ jQuery(async () => {
$('#sd_restore_faces').on('input', onRestoreFacesInput);
$('#sd_enable_hr').on('input', onHighResFixInput);
$('#sd_refine_mode').on('input', onRefineModeInput);
$('#sd_character_prompt').on('input', onCharacterPromptInput);
$('#sd_character_prompt_block').hide();
$('.sd_settings .inline-drawer-toggle').on('click', function () {
initScrollHeight($("#sd_prompt_prefix"));
initScrollHeight($("#sd_negative_prompt"));
initScrollHeight($("#sd_character_prompt"));
})
eventSource.on(event_types.EXTRAS_CONNECTED, async () => {
await Promise.all([loadSamplers(), loadModels()]);
});
eventSource.on(event_types.CHAT_CHANGED, onChatChanged);
await loadSettings();
$('body').addClass('sd');
});

View File

@@ -24,6 +24,6 @@
}
#sd_dropdown {
z-index: 3000;
z-index: 30000;
backdrop-filter: blur(--SmartThemeBlurStrength);
}
}

View File

@@ -0,0 +1,403 @@
import { eventSource, event_types } from "../../../script.js"
import { doExtrasFetch, getApiUrl, modules } from "../../extensions.js"
export { CoquiTtsProvider }
function throwIfModuleMissing() {
if (!modules.includes('coqui-tts')) {
toastr.error(`Coqui TTS module not loaded. Add coqui-tts to enable-modules and restart the Extras API.`)
throw new Error(`Coqui TTS module not loaded.`)
}
}
class CoquiTtsProvider {
//########//
// Config //
//########//
settings
voices = []
separator = ' .. '
defaultSettings = {
voiceMap: {}
}
get settingsHtml() {
let html = `
<div class="flex wide100p flexGap10 alignitemscenter">
<div style="flex: 80%;">
<label for="coqui_model">Model:</label>
<select id="coqui_model">
<option value="none">Select Model</option>
<!-- Add more model options here -->
</select>
</div>
<div class="flex justifyCenter" style="flex: 20%;">
<button id="coqui_preview" class="menu_button menu_button_icon wide100p" type="button">
</button>
</div>
</div>
<div class="flex wide100p flexGap10">
<div class="flex1">
<label for="coqui_speaker">Speaker:</label>
<select id="coqui_speaker">
<!-- Add more speaker options here -->
</select>
</div>
<div class="flex1">
<label for="coqui_language">Language:</label>
<select id="coqui_language">
<!-- Add more language options here -->
</select>
</div>
</div>
`
return html
}
onSettingsChange() {
}
loadSettings(settings) {
// Pupulate Provider UI given input settings
if (Object.keys(settings).length == 0) {
console.info("Using default TTS Provider settings")
}
const modelSelect = document.getElementById('coqui_model');
const previewButton = document.getElementById('coqui_preview');
previewButton.addEventListener('click', () => {
const selectedModel = modelSelect.value;
this.sampleTtsVoice(selectedModel);
});//add event listener to button
previewButton.disabled = true;
previewButton.innerText = "Select Model";
// 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 TTS Provider: ${key}`
}
}
const textexample = document.getElementById('tts_voice_map');
textexample.placeholder = 'Enter comma separated map of charName:ttsName[speakerID][langID]. Example: \nAqua:tts_models--en--ljspeech--glow-tts\model_file.pth,\nDarkness:tts_models--multilingual--multi-dataset--your_tts\model_file.pth[2][3]';
//Load models function
eventSource.on(event_types.EXTRAS_CONNECTED, () => {
this.getModels();
});
this.onttsCoquiHideButtons();
console.info("Settings loaded")
}
async onttsCoquiHideButtons() {
// Get references to the select element and the two input elements
const ttsProviderSelect = document.getElementById('tts_provider');
const ttsVoicesInput = document.getElementById('tts_voices');
const ttsPreviewInput = document.getElementById('tts_preview');
ttsProviderSelect.addEventListener('click', () => {
this.getModels();
});
// Add an event listener to the 'change' event of the tts_provider select element
ttsProviderSelect.addEventListener('change', () => {
// Check if the selected value is 'Coqui'
if (ttsProviderSelect.value === 'Coqui') {
ttsVoicesInput.style.display = 'none'; // Hide the tts_voices input
ttsPreviewInput.style.display = ''; // Show the tts_preview input
} else {
ttsVoicesInput.style.display = ''; // Show the tts_voices input
ttsPreviewInput.style.display = 'none'; // Hide the tts_preview input
}
});
}
async onApplyClick() {
return
}
async getLang() {
try {
const response = await doExtrasFetch(`${getApiUrl()}/api/coqui-tts/multlang`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const voiceData = await response.json();
const languageSelect = document.getElementById('coqui_language');
languageSelect.innerHTML = ''; // Clear existing options
if (Object.keys(voiceData).length === 0) {
const option = document.createElement('option');
option.value = 'none';
option.textContent = 'None';
languageSelect.appendChild(option);
} else {
for (const [key, value] of Object.entries(voiceData)) {
const option = document.createElement('option');
option.value = key;
option.textContent = key + ": " + value;
languageSelect.appendChild(option);
}
}
} catch (error) {
//console.error('Error fetching voice data:', error);
// Remove all options except "None"
const languageSelect = document.getElementById('coqui_language');
languageSelect.innerHTML = '';
const option = document.createElement('option');
option.value = 'none';
option.textContent = 'None';
languageSelect.appendChild(option);
}
}
async getSpeakers() {
try {
const response = await doExtrasFetch(`${getApiUrl()}/api/coqui-tts/multspeaker`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const voiceData = await response.json();
const speakerSelect = document.getElementById('coqui_speaker');
speakerSelect.innerHTML = ''; // Clear existing options
if (Object.keys(voiceData).length === 0) {
const option = document.createElement('option');
option.value = 'none';
option.textContent = 'None';
speakerSelect.appendChild(option);
} else {
for (const [index, name] of Object.entries(voiceData)) {
const option = document.createElement('option');
option.value = index;
option.textContent = index + ": " + name;
speakerSelect.appendChild(option);
}
}
} catch (error) {
//console.error('Error fetching voice data:', error);
// Remove all options except "None"
const speakerSelect = document.getElementById('coqui_speaker');
speakerSelect.innerHTML = '';
const option = document.createElement('option');
option.value = 'none';
option.textContent = 'None';
speakerSelect.appendChild(option);
}
}
async getModels() {
try {
throwIfModuleMissing();
const response = await doExtrasFetch(`${getApiUrl()}/api/coqui-tts/list`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const voiceIds = await response.json();
const modelSelect = document.getElementById('coqui_model');
if (voiceIds.length === 0) {
const option = document.createElement('option');
option.value = 'none';
option.textContent = 'Select Model';
modelSelect.appendChild(option);
} else {
voiceIds.forEach(voiceId => {
const option = document.createElement('option');
option.value = voiceId;
option.textContent = voiceId;
modelSelect.appendChild(option);
});
}
// Update provider endpoint on model selection change
modelSelect.addEventListener('change', () => {
const selectedModel = modelSelect.value;
this.LoadModel(selectedModel);
});
} catch (error) {
console.error('Error fetching voice IDs:', error);
// Add "None" option when the request fails or the response is empty
const modelSelect = document.getElementById('coqui_model');
const option = document.createElement('option');
option.value = 'none';
option.textContent = 'None';
modelSelect.appendChild(option);
}
}
async LoadModel(selectedModel) {
const previewButton = document.getElementById('coqui_preview');
previewButton.disabled = true;
previewButton.innerText = "Loading";
try {
throwIfModuleMissing();
const response = await doExtrasFetch(`${getApiUrl()}/api/coqui-tts/load?_model=${selectedModel}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
this.getSpeakers();
this.getLang();
const previewButton = document.getElementById('coqui_preview');
previewButton.disabled = false;
previewButton.innerText = "Play";
} catch (error) {
console.error('Error updating provider endpoint:', error);
}
}
async getVoice(voiceName) {
//tts_models--multilingual--multi-dataset--your_tts\model_file.pth[2][1]
//tts_models--en--ljspeech--glow-tts\model_file.pth
let _voiceNameOrg = voiceName; // Store the original voiceName in a variable _voiceNameOrg
voiceName = voiceName.replace(/(\[\d+\])+$/, ''); // For example, converts 'model[2][1]' to 'model'
this.voices = []; //reset for follow up runs
if (this.voices.length === 0) { this.voices = await this.fetchCheckMap(); }
// Search for a voice object in the 'this.voices' array where the 'name' property matches the provided 'voiceName'
//const match = this.voices.find((CoquiVoice) => CoquiVoice.name === voiceName);
const match = this.voices.find((CoquiVoice) => CoquiVoice.name === voiceName);
// If no match is found, throw an error indicating that the TTS Voice name was not found
if (!match) {
throw new Error(`TTS Voice name ${voiceName} not found`);
} else {
match.name = _voiceNameOrg;
match.voice_id = _voiceNameOrg;
}
// Return the matched voice object (with the 'name' property updated if a match was found)
return match;
}
async fetchCheckMap() {
const endpoint = `${getApiUrl()}/api/coqui-tts/checkmap`;
const response = await doExtrasFetch(endpoint);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.json()}`);
}
const voiceData = await response.json();
const voices = voiceData.map((voice) => ({
id: voice.name,
name: voice.id, // this is the issue!!!
voice_id: voice.id, // this is the issue!!!
//preview_url: false,
lang: voice.lang,
}));
return voices;
}
async fetchTtsVoiceIds() {
throwIfModuleMissing();
const endpoint = `${getApiUrl()}/api/coqui-tts/speaker_id`;
const response = await doExtrasFetch(endpoint);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.json()}`);
}
const voiceData = await response.json();
const voices = voiceData.map((voice) => ({
id: voice.name,
name: voice.id, //add filename here
voice_id: voice.id,
//preview_url: false,
//preview_url: `${getApiUrl()}/api/coqui-tts/download?model=${voice.id}`,
//http://localhost:5100/api/coqui-tts/speaker_id/tts_models/en/ljspeech/speedy-speech
lang: voice.lang,
}));
return voices;
}
sampleTtsVoice(voiceId) {
// Get the selected values of speaker and language
const speakerSelect = document.getElementById('coqui_speaker');
const languageSelect = document.getElementById('coqui_language');
const selectedSpeaker = speakerSelect.value;
const selectedLanguage = languageSelect.value;
// Construct the URL with the selected values
const url = `${getApiUrl()}/api/coqui-tts/tts?text=The%20Quick%20Brown%20Fox%20Jumps%20Over%20the%20Lazy%20Dog.&speaker_id=${voiceId}&style_wav=&language_id=${selectedLanguage}&mspker=${selectedSpeaker}`;
doExtrasFetch(url)
.then(response => response.blob())
.then(blob => {
const audioUrl = URL.createObjectURL(blob);
// Play the audio
const audio = new Audio(audioUrl);
audio.play();
})
.catch(error => {
console.error('Error performing TTS request:', error);
});
}
previewTtsVoice(voiceId) { //button on avail voices
throwIfModuleMissing();
const url = `${getApiUrl()}/api/coqui-tts/download?model=${voiceId}`;
doExtrasFetch(url)
.then(response => response.text()) // Expecting a text response
.then(responseText => {
const isResponseTrue = responseText.trim().toLowerCase() === 'true';
if (isResponseTrue) {
console.log("Downloading Model") //if true
} else {
console.error('Already Installed'); //if false
}
})
.catch(error => {
console.error('Error performing download:', error);
});
}
async generateTts(text, voiceId) {
const response = await this.fetchTtsGeneration(text, voiceId)
return response
}
async fetchTtsGeneration(inputText, voiceId) {
throwIfModuleMissing();
console.info(`Generating new TTS for voice_id ${voiceId}`);
const response = await doExtrasFetch(`${getApiUrl()}/api/coqui-tts/tts?text=${encodeURIComponent(inputText)}&speaker_id=${voiceId}`);
if (!response.ok) {
toastr.error(response.statusText, 'TTS Generation Failed');
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
if (!response.ok) {
toastr.error(response.statusText, 'TTS Generation Failed');
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
return response
}
async fetchTtsFromHistory(history_item_id) {
return Promise.resolve(history_item_id);
}
}

View File

@@ -1,19 +1,19 @@
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'
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
@@ -64,6 +64,7 @@ let ttsProviders = {
ElevenLabs: ElevenLabsTtsProvider,
Silero: SileroTtsProvider,
System: SystemTtsProvider,
Coqui: CoquiTtsProvider,
Edge: EdgeTtsProvider,
Novel: NovelTtsProvider,
}
@@ -162,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
@@ -289,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 {
@@ -352,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,17 +13,25 @@ export {
getNovelTier,
};
const default_preamble = "[ Style: chat, complex, sensory, visceral ]";
const nai_settings = {
temp_novel: 0.5,
rep_pen_novel: 1,
rep_pen_size_novel: 100,
rep_pen_slope_novel: 0,
rep_pen_freq_novel: 0,
rep_pen_presence_novel: 0,
tail_free_sampling_novel: 0.68,
temperature: 0.5,
repetition_penalty: 1,
repetition_penalty_range: 100,
repetition_penalty_slope: 0,
repetition_penalty_frequency: 0,
repetition_penalty_presence: 0,
tail_free_sampling: 0.68,
top_k: 0,
top_p: 1,
top_a: 1,
typical_p: 1,
min_length: 0,
model_novel: "euterpe-v2",
preset_settings_novel: "Classic-Euterpe",
streaming_novel: false,
nai_preamble: default_preamble,
};
const nai_tiers = {
@@ -45,13 +56,20 @@ function loadNovelPreset(preset) {
$("#max_context_counter").text(`${preset.max_context}`);
$("#rep_pen_size_novel").attr('max', preset.max_context);
nai_settings.temp_novel = preset.temperature;
nai_settings.rep_pen_novel = preset.repetition_penalty;
nai_settings.rep_pen_size_novel = preset.repetition_penalty_range;
nai_settings.rep_pen_slope_novel = preset.repetition_penalty_slope;
nai_settings.rep_pen_freq_novel = preset.repetition_penalty_frequency;
nai_settings.rep_pen_presence_novel = preset.repetition_penalty_presence;
nai_settings.tail_free_sampling_novel = preset.tail_free_sampling;
nai_settings.temperature = preset.temperature;
nai_settings.repetition_penalty = preset.repetition_penalty;
nai_settings.repetition_penalty_range = preset.repetition_penalty_range;
nai_settings.repetition_penalty_slope = preset.repetition_penalty_slope;
nai_settings.repetition_penalty_frequency = preset.repetition_penalty_frequency;
nai_settings.repetition_penalty_presence = preset.repetition_penalty_presence;
nai_settings.tail_free_sampling = preset.tail_free_sampling;
nai_settings.top_k = preset.top_k;
nai_settings.top_p = preset.top_p;
nai_settings.top_a = preset.top_a;
nai_settings.typical_p = preset.typical_p;
nai_settings.min_length = preset.min_length;
nai_settings.cfg_scale = preset.cfg_scale;
nai_settings.phrase_rep_pen = preset.phrase_rep_pen;
loadNovelSettingsUi(nai_settings);
}
@@ -59,33 +77,92 @@ function loadNovelSettings(settings) {
//load the rest of the Novel settings without any checks
nai_settings.model_novel = settings.model_novel;
$(`#model_novel_select option[value=${nai_settings.model_novel}]`).attr("selected", true);
$('#model_novel_select').val(nai_settings.model_novel);
nai_settings.temp_novel = settings.temp_novel;
nai_settings.rep_pen_novel = settings.rep_pen_novel;
nai_settings.rep_pen_size_novel = settings.rep_pen_size_novel;
nai_settings.rep_pen_slope_novel = settings.rep_pen_slope_novel;
nai_settings.rep_pen_freq_novel = settings.rep_pen_freq_novel;
nai_settings.rep_pen_presence_novel = settings.rep_pen_presence_novel;
nai_settings.tail_free_sampling_novel = settings.tail_free_sampling_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;
nai_settings.repetition_penalty_range = settings.repetition_penalty_range;
nai_settings.repetition_penalty_slope = settings.repetition_penalty_slope;
nai_settings.repetition_penalty_frequency = settings.repetition_penalty_frequency;
nai_settings.repetition_penalty_presence = settings.repetition_penalty_presence;
nai_settings.tail_free_sampling = settings.tail_free_sampling;
nai_settings.top_k = settings.top_k;
nai_settings.top_p = settings.top_p;
nai_settings.top_a = settings.top_a;
nai_settings.typical_p = settings.typical_p;
nai_settings.min_length = settings.min_length;
nai_settings.phrase_rep_pen = settings.phrase_rep_pen;
nai_settings.cfg_scale = settings.cfg_scale;
nai_settings.streaming_novel = !!settings.streaming_novel;
loadNovelSettingsUi(nai_settings);
}
const phraseRepPenStrings = [
null,
"very_light",
"light",
"medium",
"aggressive",
"very_aggressive"
]
function getPhraseRepPenString(phraseRepPenCounter) {
if (phraseRepPenCounter < 1 || phraseRepPenCounter > 5) {
return null;
} else {
return phraseRepPenStrings[phraseRepPenCounter];
}
}
function getPhraseRepPenCounter(phraseRepPenString) {
if (phraseRepPenString === phraseRepPenStrings[1]) {
return 1;
} else if (phraseRepPenString === phraseRepPenStrings[2]) {
return 2;
} else if (phraseRepPenString === phraseRepPenStrings[3]) {
return 3;
} else if (phraseRepPenString === phraseRepPenStrings[4]) {
return 4;
} else if (phraseRepPenString === phraseRepPenStrings[5]) {
return 5;
} else {
return 0;
}
}
function loadNovelSettingsUi(ui_settings) {
$("#temp_novel").val(ui_settings.temp_novel);
$("#temp_counter_novel").text(Number(ui_settings.temp_novel).toFixed(2));
$("#rep_pen_novel").val(ui_settings.rep_pen_novel);
$("#rep_pen_counter_novel").text(Number(ui_settings.rep_pen_novel).toFixed(2));
$("#rep_pen_size_novel").val(ui_settings.rep_pen_size_novel);
$("#rep_pen_size_counter_novel").text(Number(ui_settings.rep_pen_size_novel).toFixed(0));
$("#rep_pen_slope_novel").val(ui_settings.rep_pen_slope_novel);
$("#rep_pen_slope_counter_novel").text(Number(`${ui_settings.rep_pen_slope_novel}`).toFixed(2));
$("#rep_pen_freq_novel").val(ui_settings.rep_pen_freq_novel);
$("#rep_pen_freq_counter_novel").text(Number(ui_settings.rep_pen_freq_novel).toFixed(5));
$("#rep_pen_presence_novel").val(ui_settings.rep_pen_presence_novel);
$("#rep_pen_presence_counter_novel").text(Number(ui_settings.rep_pen_presence_novel).toFixed(3));
$("#tail_free_sampling_novel").val(ui_settings.tail_free_sampling_novel);
$("#tail_free_sampling_counter_novel").text(Number(ui_settings.tail_free_sampling_novel).toFixed(3));
$("#temp_novel").val(ui_settings.temperature);
$("#temp_counter_novel").text(Number(ui_settings.temperature).toFixed(2));
$("#rep_pen_novel").val(ui_settings.repetition_penalty);
$("#rep_pen_counter_novel").text(Number(ui_settings.repetition_penalty).toFixed(2));
$("#rep_pen_size_novel").val(ui_settings.repetition_penalty_range);
$("#rep_pen_size_counter_novel").text(Number(ui_settings.repetition_penalty_range).toFixed(0));
$("#rep_pen_slope_novel").val(ui_settings.repetition_penalty_slope);
$("#rep_pen_slope_counter_novel").text(Number(`${ui_settings.repetition_penalty_slope}`).toFixed(2));
$("#rep_pen_freq_novel").val(ui_settings.repetition_penalty_frequency);
$("#rep_pen_freq_counter_novel").text(Number(ui_settings.repetition_penalty_frequency).toFixed(5));
$("#rep_pen_presence_novel").val(ui_settings.repetition_penalty_presence);
$("#rep_pen_presence_counter_novel").text(Number(ui_settings.repetition_penalty_presence).toFixed(3));
$("#tail_free_sampling_novel").val(ui_settings.tail_free_sampling);
$("#tail_free_sampling_counter_novel").text(Number(ui_settings.tail_free_sampling).toFixed(3));
$("#top_k_novel").val(ui_settings.top_k);
$("#top_k_counter_novel").text(Number(ui_settings.top_k).toFixed(0));
$("#top_p_novel").val(ui_settings.top_p);
$("#top_p_counter_novel").text(Number(ui_settings.top_p).toFixed(2));
$("#top_a_novel").val(ui_settings.top_a);
$("#top_a_counter_novel").text(Number(ui_settings.top_a).toFixed(2));
$("#typical_p_novel").val(ui_settings.typical_p);
$("#typical_p_counter_novel").text(Number(ui_settings.typical_p).toFixed(2));
$("#cfg_scale_novel").val(ui_settings.cfg_scale);
$("#cfg_scale_counter_novel").text(Number(ui_settings.cfg_scale).toFixed(2));
$("#phrase_rep_pen_novel").val(getPhraseRepPenCounter(ui_settings.phrase_rep_pen));
$("#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);
}
@@ -94,71 +171,134 @@ const sliders = [
sliderId: "#temp_novel",
counterId: "#temp_counter_novel",
format: (val) => Number(val).toFixed(2),
setValue: (val) => { nai_settings.temp_novel = Number(val).toFixed(2); },
setValue: (val) => { nai_settings.temperature = Number(val).toFixed(2); },
},
{
sliderId: "#rep_pen_novel",
counterId: "#rep_pen_counter_novel",
format: (val) => Number(val).toFixed(2),
setValue: (val) => { nai_settings.rep_pen_novel = Number(val).toFixed(2); },
setValue: (val) => { nai_settings.repetition_penalty = Number(val).toFixed(2); },
},
{
sliderId: "#rep_pen_size_novel",
counterId: "#rep_pen_size_counter_novel",
format: (val) => `${val}`,
setValue: (val) => { nai_settings.rep_pen_size_novel = Number(val).toFixed(0); },
setValue: (val) => { nai_settings.repetition_penalty_range = Number(val).toFixed(0); },
},
{
sliderId: "#rep_pen_slope_novel",
counterId: "#rep_pen_slope_counter_novel",
format: (val) => `${val}`,
setValue: (val) => { nai_settings.rep_pen_slope_novel = Number(val).toFixed(2); },
setValue: (val) => { nai_settings.repetition_penalty_slope = Number(val).toFixed(2); },
},
{
sliderId: "#rep_pen_freq_novel",
counterId: "#rep_pen_freq_counter_novel",
format: (val) => `${val}`,
setValue: (val) => { nai_settings.rep_pen_freq_novel = Number(val).toFixed(5); },
setValue: (val) => { nai_settings.repetition_penalty_frequency = Number(val).toFixed(5); },
},
{
sliderId: "#rep_pen_presence_novel",
counterId: "#rep_pen_presence_counter_novel",
format: (val) => `${val}`,
setValue: (val) => { nai_settings.rep_pen_presence_novel = Number(val).toFixed(3); },
setValue: (val) => { nai_settings.repetition_penalty_presence = Number(val).toFixed(3); },
},
{
sliderId: "#tail_free_sampling_novel",
counterId: "#tail_free_sampling_counter_novel",
format: (val) => `${val}`,
setValue: (val) => { nai_settings.tail_free_sampling_novel = Number(val).toFixed(3); },
setValue: (val) => { nai_settings.tail_free_sampling = Number(val).toFixed(3); },
},
{
sliderId: "#top_k_novel",
counterId: "#top_k_counter_novel",
format: (val) => `${val}`,
setValue: (val) => { nai_settings.top_k = Number(val).toFixed(0); },
},
{
sliderId: "#top_p_novel",
counterId: "#top_p_counter_novel",
format: (val) => Number(val).toFixed(2),
setValue: (val) => { nai_settings.top_p = Number(val).toFixed(2); },
},
{
sliderId: "#top_a_novel",
counterId: "#top_a_counter_novel",
format: (val) => Number(val).toFixed(2),
setValue: (val) => { nai_settings.top_a = Number(val).toFixed(2); },
},
{
sliderId: "#typical_p_novel",
counterId: "#typical_p_counter_novel",
format: (val) => Number(val).toFixed(2),
setValue: (val) => { nai_settings.typical_p = Number(val).toFixed(2); },
},
{
sliderId: "#cfg_scale_novel",
counterId: "#cfg_scale_counter_novel",
format: (val) => `${val}`,
setValue: (val) => { nai_settings.cfg_scale = Number(val).toFixed(2); },
},
{
sliderId: "#phrase_rep_pen_novel",
counterId: "#phrase_rep_pen_counter_novel",
format: (val) => `${val}`,
setValue: (val) => { nai_settings.phrase_rep_pen = getPhraseRepPenString(Number(val).toFixed(0)); },
},
{
sliderId: "#min_length_novel",
counterId: "#min_length_counter_novel",
format: (val) => `${val}`,
setValue: (val) => { nai_settings.min_length = Number(val).toFixed(0); },
},
];
export function getNovelGenerationData(finalPromt, this_settings, this_amount_gen) {
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,
"use_string": true,
"temperature": parseFloat(nai_settings.temp_novel),
"temperature": parseFloat(nai_settings.temperature),
"max_length": this_amount_gen, // this_settings.max_length, // <= why?
"min_length": this_settings.min_length,
"tail_free_sampling": parseFloat(nai_settings.tail_free_sampling_novel),
"repetition_penalty": parseFloat(nai_settings.rep_pen_novel),
"repetition_penalty_range": parseInt(nai_settings.rep_pen_size_novel),
"repetition_penalty_slope": parseFloat(nai_settings.rep_pen_slope_novel),
"repetition_penalty_frequency": parseFloat(nai_settings.rep_pen_freq_novel),
"repetition_penalty_presence": parseFloat(nai_settings.rep_pen_presence_novel),
"top_a": this_settings.top_a,
"top_p": this_settings.top_p,
"top_k": this_settings.top_k,
"typical_p": this_settings.typical_p,
"min_length": parseInt(nai_settings.min_length),
"tail_free_sampling": parseFloat(nai_settings.tail_free_sampling),
"repetition_penalty": parseFloat(nai_settings.repetition_penalty),
"repetition_penalty_range": parseInt(nai_settings.repetition_penalty_range),
"repetition_penalty_slope": parseFloat(nai_settings.repetition_penalty_slope),
"repetition_penalty_frequency": parseFloat(nai_settings.repetition_penalty_frequency),
"repetition_penalty_presence": parseFloat(nai_settings.repetition_penalty_presence),
"top_a": parseFloat(nai_settings.top_a),
"top_p": parseFloat(nai_settings.top_p),
"top_k": parseInt(nai_settings.top_k),
"typical_p": parseFloat(nai_settings.typical_p),
"cfg_scale": parseFloat(nai_settings.cfg_scale),
"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": "vanilla",
"prefix": useInstruct ? "special_instruct" : (isNewModel ? "special_proseaugmenter" : "vanilla"),
"order": this_settings.order,
"streaming": nai_settings.streaming_novel,
};
@@ -206,13 +346,24 @@ 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 () {
const value = $(this).val();
const formattedValue = slider.format(value);
slider.setValue(value);
$(slider.counterId).html(formattedValue);
$(slider.counterId).text(formattedValue);
console.log('saving');
saveSettingsDebounced();
});

View File

@@ -142,6 +142,8 @@ const default_settings = {
max_context_unlocked: false,
api_url_scale: '',
show_external_models: false,
proxy_password: '',
assistant_prefill: '',
};
const oai_settings = {
@@ -178,6 +180,8 @@ const oai_settings = {
max_context_unlocked: false,
api_url_scale: '',
show_external_models: false,
proxy_password: '',
assistant_prefill: '',
};
let openai_setting_names;
@@ -400,7 +404,8 @@ async function prepareOpenAIMessages({ systemPrompt, name2, storyString, worldIn
const jailbreak = power_user.prefer_character_jailbreak && jailbreakPrompt ? jailbreakPrompt : oai_settings.jailbreak_prompt;
if (oai_settings.jailbreak_system && jailbreak) {
const jailbreakMessage = { "role": "system", "content": substituteParams(jailbreak, name1, name2, oai_settings.jailbreak_prompt) };
const jbContent = substituteParams(jailbreak, name1, name2, oai_settings.jailbreak_prompt).replace(/\r/gm, '').trim();
const jailbreakMessage = { "role": "system", "content": jbContent };
openai_msgs.push(jailbreakMessage);
total_count += handler_instance.count([jailbreakMessage], true, 'jailbreak');
@@ -766,11 +771,13 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI].includes(oai_settings.chat_completion_source)) {
validateReverseProxy();
generate_data['reverse_proxy'] = oai_settings.reverse_proxy;
generate_data['proxy_password'] = oai_settings.proxy_password;
}
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) {
@@ -1104,6 +1111,8 @@ function loadOpenAISettings(data, settings) {
oai_settings.chat_completion_source = settings.chat_completion_source ?? default_settings.chat_completion_source;
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;
@@ -1115,6 +1124,8 @@ 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);
@@ -1168,9 +1179,7 @@ function loadOpenAISettings(data, settings) {
if (settings.reverse_proxy !== undefined) oai_settings.reverse_proxy = settings.reverse_proxy;
$('#openai_reverse_proxy').val(oai_settings.reverse_proxy);
if (oai_settings.reverse_proxy !== '') {
$("#ReverseProxyWarningMessage").css('display', 'block');
}
$(".reverse_proxy_warning").toggle(oai_settings.reverse_proxy !== '');
$('#openai_logit_bias_preset').empty();
for (const preset of Object.keys(oai_settings.bias_presets)) {
@@ -1211,6 +1220,7 @@ async function getStatusOpen() {
let data = {
reverse_proxy: oai_settings.reverse_proxy,
proxy_password: oai_settings.proxy_password,
use_openrouter: oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER,
};
@@ -1310,6 +1320,7 @@ async function saveOpenAIPreset(name, settings) {
impersonation_prompt: settings.impersonation_prompt,
bias_preset_selected: settings.bias_preset_selected,
reverse_proxy: settings.reverse_proxy,
proxy_password: settings.proxy_password,
legacy_streaming: settings.legacy_streaming,
max_context_unlocked: settings.max_context_unlocked,
nsfw_avoidance_prompt: settings.nsfw_avoidance_prompt,
@@ -1317,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}`, {
@@ -1649,6 +1661,8 @@ function onSettingsPresetChange() {
stream_openai: ['#stream_toggle', 'stream_openai', true],
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)) {
@@ -1857,9 +1871,7 @@ async function onNewPresetClick() {
function onReverseProxyInput() {
oai_settings.reverse_proxy = $(this).val();
if (oai_settings.reverse_proxy == '') {
$("#ReverseProxyWarningMessage").css('display', 'none');
} else { $("#ReverseProxyWarningMessage").css('display', 'block'); }
$(".reverse_proxy_warning").toggle(oai_settings.reverse_proxy != '');
saveSettingsDebounced();
}
@@ -1911,7 +1923,7 @@ async function onConnectButtonClick(e) {
await writeSecret(SECRET_KEYS.CLAUDE, api_key_claude);
}
if (!secret_state[SECRET_KEYS.CLAUDE]) {
if (!secret_state[SECRET_KEYS.CLAUDE] && !oai_settings.reverse_proxy) {
console.log('No secret key saved for Claude');
return;
}
@@ -1924,7 +1936,7 @@ async function onConnectButtonClick(e) {
await writeSecret(SECRET_KEYS.OPENAI, api_key_openai);
}
if (!secret_state[SECRET_KEYS.OPENAI]) {
if (!secret_state[SECRET_KEYS.OPENAI] && !oai_settings.reverse_proxy) {
console.log('No secret key saved for OpenAI');
return;
}
@@ -2196,6 +2208,16 @@ $(document).ready(function () {
saveSettingsDebounced();
});
$('#openai_proxy_password').on('input', function () {
oai_settings.proxy_password = $(this).val();
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,
@@ -43,7 +43,7 @@ export {
send_on_enter_options,
};
const MAX_CONTEXT_DEFAULT = 4096;
export const MAX_CONTEXT_DEFAULT = 4096;
const MAX_CONTEXT_UNLOCKED = 65536;
const avatar_styles = {
@@ -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,11 @@ 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,
};
let themes = [];
@@ -255,7 +261,7 @@ function fixMarkdown(text) {
// i.e. "^example * text* * harder problem *\n" -> "^example *text* *harder problem*\n"
// Find pairs of formatting characters and capture the text in between them
const format = /(\*|_|~){1,2}([\s\S]*?)\1{1,2}/gm;
const format = /([\*_]{1,2})([\s\S]*?)\1/gm;
let matches = [];
let match;
while ((match = format.exec(text)) !== null) {
@@ -266,7 +272,7 @@ function fixMarkdown(text) {
let newText = text;
for (let i = matches.length - 1; i >= 0; i--) {
let matchText = matches[i][0];
let replacementText = matchText.replace(/(\*|_|~)(\s+)|(\s+)(\*|_|~)/g, '$1$4');
let replacementText = matchText.replace(/(\*|_)([\t \u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\ufeff]+)|([\t \u00a0\u1680\u2000-\u200a\u202f\u205f\u3000\ufeff]+)(\*|_)/g, '$1$4');
newText = newText.slice(0, matches[i].index) + replacementText + newText.slice(matches[i].index + matchText.length);
}
@@ -534,6 +540,11 @@ async function applyTheme(name) {
{
key: 'chat_width',
action: async () => {
// If chat width is not set, set it to 50
if (!power_user.chat_width) {
power_user.chat_width = 50;
}
localStorage.setItem(storage_keys.chat_width, power_user.chat_width);
applyChatWidth();
}
@@ -660,6 +671,11 @@ function loadPowerUserSettings(settings, data) {
power_user.waifuMode = false;
}
if (power_user.chat_width === '') {
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);
@@ -667,6 +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);
@@ -837,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}`);
@@ -849,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();
});
});
@@ -887,8 +911,38 @@ function loadInstructMode() {
});
}
export function fuzzySearchCharacters(searchValue) {
const fuse = new Fuse(characters, {
keys: [
{ name: 'data.name', weight: 5 },
{ name: 'data.description', weight: 3 },
{ name: 'data.mes_example', weight: 3 },
{ name: 'data.scenario', weight: 2 },
{ name: 'data.personality', weight: 2 },
{ name: 'data.first_mes', weight: 2 },
{ name: 'data.creator_notes', weight: 2 },
{ name: 'data.creator', weight: 1 },
{ name: 'data.tags', weight: 1 },
{ name: 'data.alternate_greetings', weight: 1 }
],
includeScore: true,
ignoreLocation: true,
threshold: 0.2,
});
const results = fuse.search(searchValue);
console.debug('Fuzzy search results for ' + searchValue, results)
const indices = results.map(x => x.refIndex);
return indices;
}
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) {
@@ -916,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) {
@@ -1151,10 +1205,10 @@ async function resetMovablePanels(type) {
//if happening as part of preset application, do it quietly.
if (type === 'quiet') {
return
//if happening due to resize, tell user.
//if happening due to resize, tell user.
} else if (type === 'resize') {
toastr.warning('Panel positions reset due to zoom/resize');
//if happening due to manual button press
//if happening due to manual button press
} else {
toastr.success('Panel positions reset');
}
@@ -1173,7 +1227,7 @@ function doNewChat() {
function doRandomChat() {
resetSelectedGroup();
setCharacterId(Math.floor(Math.random() * characters.length));
setCharacterId(Math.floor(Math.random() * characters.length).toString());
setTimeout(() => {
reloadCurrentChat();
}, 1);
@@ -1188,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}]`)
@@ -1947,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();
@@ -1963,6 +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

@@ -0,0 +1,351 @@
import {
amount_gen,
callPopup,
characters,
eventSource,
event_types,
getRequestHeaders,
koboldai_setting_names,
koboldai_settings,
main_api,
max_context,
nai_settings,
novelai_setting_names,
novelai_settings,
saveSettingsDebounced,
this_chid,
} from "../script.js";
import { groups, selected_group } from "./group-chats.js";
import { kai_settings } from "./kai-settings.js";
import {
textgenerationwebui_preset_names,
textgenerationwebui_presets,
textgenerationwebui_settings,
} from "./textgen-settings.js";
import { download, parseJsonFile, waitUntilCondition } from "./utils.js";
const presetManagers = {};
function autoSelectPreset() {
const presetManager = getPresetManager();
if (!presetManager) {
console.debug(`Preset Manager not found for API: ${main_api}`);
return;
}
const name = selected_group ? groups.find(x => x.id == selected_group)?.name : characters[this_chid]?.name;
if (!name) {
console.debug(`Preset candidate not found for API: ${main_api}`);
return;
}
const preset = presetManager.findPreset(name);
const selectedPreset = presetManager.getSelectedPreset();
if (preset === selectedPreset) {
console.debug(`Preset already selected for API: ${main_api}, name: ${name}`);
return;
}
if (preset !== undefined && preset !== null) {
console.log(`Preset found for API: ${main_api}, name: ${name}`);
presetManager.selectPreset(preset);
}
}
function getPresetManager() {
const apiId = main_api == 'koboldhorde' ? 'kobold' : main_api;
if (!Object.keys(presetManagers).includes(apiId)) {
return null;
}
return presetManagers[apiId];
}
function registerPresetManagers() {
$('select[data-preset-manager-for]').each((_, e) => {
const forData = $(e).data("preset-manager-for");
for (const apiId of forData.split(",")) {
console.debug(`Registering preset manager for API: ${apiId}`);
presetManagers[apiId] = new PresetManager($(e), apiId);
}
});
}
class PresetManager {
constructor(select, apiId) {
this.select = select;
this.apiId = apiId;
}
findPreset(name) {
return $(this.select).find(`option:contains(${name})`).val();
}
getSelectedPreset() {
return $(this.select).find("option:selected").val();
}
getSelectedPresetName() {
return $(this.select).find("option:selected").text();
}
selectPreset(preset) {
$(this.select).find(`option[value=${preset}]`).prop('selected', true);
$(this.select).val(preset).trigger("change");
}
async updatePreset() {
const selected = $(this.select).find("option:selected");
if (selected.val() == 'gui') {
toastr.info('Cannot update GUI preset');
return;
}
const name = selected.text();
await this.savePreset(name);
toastr.success('Preset updated');
}
async savePresetAs() {
const popupText = `
<h3>Preset name:</h3>
<h4>Hint: Use a character/group name to bind preset to a specific chat.</h4>`;
const name = await callPopup(popupText, "input");
await this.savePreset(name);
toastr.success('Preset saved');
}
async savePreset(name, settings) {
const preset = settings ?? this.getPresetSettings();
const res = await fetch(`/save_preset`, {
method: "POST",
headers: getRequestHeaders(),
body: JSON.stringify({ preset, name, apiId: this.apiId })
});
if (!res.ok) {
toastr.error('Failed to save preset');
}
const data = await res.json();
name = data.name;
this.updateList(name, preset);
}
getPresetList() {
let presets = [];
let preset_names = {};
switch (this.apiId) {
case "koboldhorde":
case "kobold":
presets = koboldai_settings;
preset_names = koboldai_setting_names;
break;
case "novel":
presets = novelai_settings;
preset_names = novelai_setting_names;
break;
case "textgenerationwebui":
presets = textgenerationwebui_presets;
preset_names = textgenerationwebui_preset_names;
break;
default:
console.warn(`Unknown API ID ${this.apiId}`);
}
return { presets, preset_names };
}
updateList(name, preset) {
const { presets, preset_names } = this.getPresetList();
const presetExists = this.apiId == "textgenerationwebui" ? preset_names.includes(name) : Object.keys(preset_names).includes(name);
if (presetExists) {
if (this.apiId == "textgenerationwebui") {
presets[preset_names.indexOf(name)] = preset;
$(this.select).find(`option[value="${name}"]`).prop('selected', true);
$(this.select).val(name).trigger("change");
}
else {
const value = preset_names[name];
presets[value] = preset;
$(this.select).find(`option[value="${value}"]`).prop('selected', true);
$(this.select).val(value).trigger("change");
}
}
else {
presets.push(preset);
const value = presets.length - 1;
// ooba is reversed
if (this.apiId == "textgenerationwebui") {
preset_names[value] = name;
const option = $('<option></option>', { value: name, text: name, selected: true });
$(this.select).append(option);
$(this.select).val(name).trigger("change");
} else {
preset_names[name] = value;
const option = $('<option></option>', { value: value, text: name, selected: true });
$(this.select).append(option);
$(this.select).val(value).trigger("change");
}
}
}
getPresetSettings() {
function getSettingsByApiId(apiId) {
switch (apiId) {
case "koboldhorde":
case "kobold":
return kai_settings;
case "novel":
return nai_settings;
case "textgenerationwebui":
return textgenerationwebui_settings;
default:
console.warn(`Unknown API ID ${apiId}`);
return {};
}
}
const filteredKeys = ['preset', 'streaming_url', 'stopping_strings', 'use_stop_sequence'];
const settings = Object.assign({}, getSettingsByApiId(this.apiId));
for (const key of filteredKeys) {
if (settings.hasOwnProperty(key)) {
delete settings[key];
}
}
settings['genamt'] = amount_gen;
settings['max_length'] = max_context;
return settings;
}
async deleteCurrentPreset() {
const { presets, preset_names } = this.getPresetList();
const value = this.getSelectedPreset();
const nameToDelete = this.getSelectedPresetName();
if (value == 'gui') {
toastr.info('Cannot delete GUI preset');
return;
}
$(this.select).find(`option[value="${value}"]`).remove();
if (this.apiId == "textgenerationwebui") {
preset_names.splice(preset_names.indexOf(value), 1);
} else {
delete preset_names[nameToDelete];
}
if (Object.keys(preset_names).length) {
const nextPresetName = Object.keys(preset_names)[0];
const newValue = preset_names[nextPresetName];
$(this.select).find(`option[value="${newValue}"]`).attr('selected', true);
$(this.select).trigger('change');
}
const response = await fetch('/delete_preset', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify({ name: nameToDelete, apiId: this.apiId }),
});
if (!response.ok) {
toastr.warning('Preset was not deleted from server');
} else {
toastr.success('Preset deleted');
}
}
}
jQuery(async () => {
await waitUntilCondition(() => eventSource !== undefined);
eventSource.on(event_types.CHAT_CHANGED, autoSelectPreset);
registerPresetManagers();
$(document).on("click", "[data-preset-manager-update]", async function () {
const presetManager = getPresetManager();
if (!presetManager) {
return;
}
await presetManager.updatePreset();
});
$(document).on("click", "[data-preset-manager-new]", async function () {
const presetManager = getPresetManager();
if (!presetManager) {
return;
}
await presetManager.savePresetAs();
});
$(document).on("click", "[data-preset-manager-export]", async function () {
const presetManager = getPresetManager();
if (!presetManager) {
return;
}
const selected = $(presetManager.select).find("option:selected");
const name = selected.text();
const preset = presetManager.getPresetSettings();
const data = JSON.stringify(preset, null, 4);
download(data, `${name}.json`, "application/json");
});
$(document).on("click", "[data-preset-manager-import]", async function () {
$('[data-preset-manager-file]').trigger('click');
});
$(document).on("change", "[data-preset-manager-file]", async function (e) {
const presetManager = getPresetManager();
if (!presetManager) {
return;
}
const file = e.target.files[0];
if (!file) {
return;
}
const name = file.name.replace('.json', '').replace('.settings', '');
const data = await parseJsonFile(file);
await presetManager.savePreset(name, data);
toastr.success('Preset imported');
e.target.value = null;
});
$(document).on("click", "[data-preset-manager-delete]", async function () {
const presetManager = getPresetManager();
if (!presetManager) {
return;
}
const confirm = await callPopup('Delete the preset? This action is irreversible and your current settings will be overwritten.', 'confirm');
if (!confirm) {
return;
}
await presetManager.deleteCurrentPreset();
saveSettingsDebounced();
});
})

Some files were not shown because too many files have changed in this diff Show More