Compare commits

..

305 Commits

Author SHA1 Message Date
Cohee
c9c4f30637 Merge pull request #1223 from SillyTavern/staging
Staging
2023-10-08 21:08:26 +03:00
RossAscends
1c095415a4 fix WI panel display on autoimport char lorebooks. 2023-10-08 23:04:53 +09:00
RossAscends
19acc05516 subsort WI Depth sort by Order to match prompt loc 2023-10-08 20:12:14 +09:00
Cohee
2cbde8b55c #1221 Button to refresh WI sorts 2023-10-08 12:30:12 +03:00
Cohee
15cf87ef01 Fix new group avatar upload 2023-10-08 11:34:39 +03:00
Cohee
57bf499ce9 Fix group expressions 2023-10-08 11:22:48 +03:00
Cohee
4fbbd34db9 Add presets for Story and Adventure 2023-10-07 23:18:00 +03:00
Cohee
9b04e43b4c Remove examples separator from ChatML 2023-10-07 22:36:53 +03:00
SDS
5848ec498b Assorted fixes and improvements (#1208)
* Kobold Presets fixed

* Help texts fixed

* Scale API for connectAPISlash

* Quick Reply checkboxes fixed

* New Instruct Mode Presets

* More date/time macros

* ChatML context template and instruct prompt format

* Mistral context template and instruct prompt format

* Removed use_default_badwordsids from kobold presets

* Renamed ChatML to Mistral-OpenOrca (ChatML)

* Renamed Mistral-OpenOrca (removed ChatML)

* Removed single_line from kobold presets

* Removed obsolete use_stop_sequence setting

* Ban EOS Token off by default

* Split AI Resp. Conf. in global and preset-specific settings

* Resolve conflicts

* Fix title

* Add translations for new help texts

* Fix i18n.json whitespace

* Make Mistral-OpenOrca system prompt more generic

* Renamed "Mistral-OpenOrca" to "ChatML" again

* More (UI) fixes and improvements

* Sendas hint fixed

---------

Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2023-10-07 19:25:36 +03:00
Cohee
5a570ff283 Merge branch 'staging' of https://github.com/SillyTavern/SillyTavern into staging 2023-10-07 18:30:19 +03:00
Cohee
1dd6fa4b6a Add Sd.next source 2023-10-07 18:30:06 +03:00
Cohee
32377fd131 Unhide constant WI probability 2023-10-06 18:47:03 +03:00
Cohee
2f294b9f2b [chore] Reformat code 2023-10-06 15:55:11 +03:00
yuxiang
0b8295926e add bing translate 2023-10-06 15:51:04 +03:00
Cohee
27ce0b5eb7 Stabilize WI sorting order 2023-10-06 01:18:50 +03:00
RossAscends
2797b4bd89 Merge branch 'staging' of https://github.com/Cohee1207/SillyTavern into staging 2023-10-06 06:27:54 +09:00
RossAscends
e9ba20f13c WI Entry transitions: all >>> opacity 2023-10-06 06:27:49 +09:00
Cohee
1ef68a34fe Fix bamboozling UI hint 2023-10-06 00:13:16 +03:00
RossAscends
4189da19b1 Merge branch 'staging' of https://github.com/Cohee1207/SillyTavern into staging 2023-10-06 05:56:47 +09:00
RossAscends
ad3f8e7bf4 Merge branch 'staging' of https://github.com/Cohee1207/SillyTavern into staging 2023-10-06 05:56:45 +09:00
Cohee
0dfe46a162 Add WI title backfill button 2023-10-05 23:56:31 +03:00
RossAscends
edeb804128 fix WI Entry control titles for mobile 2023-10-06 05:55:39 +09:00
Cohee
b49f234185 Merge branch 'staging' of https://github.com/SillyTavern/SillyTavern into staging 2023-10-05 23:30:23 +03:00
Cohee
85a63b3bb3 Add WI entry sorting. Add new entry flash 2023-10-05 23:30:18 +03:00
RossAscends
04a0d012fc WI: quick cope for mobile entry display 2023-10-06 05:27:36 +09:00
RossAscends
eaa33f2544 WI Entry titles on left, controls on right. 2023-10-06 03:40:39 +09:00
RossAscends
d5004d79bd remove double WI Entry header padding 2023-10-06 03:34:34 +09:00
Cohee
96be2ebf35 Merge branch 'staging' of https://github.com/SillyTavern/SillyTavern into staging 2023-10-05 21:13:23 +03:00
Cohee
fc90843207 Add WI sort dropdown 2023-10-05 21:13:18 +03:00
RossAscends
679d40b3ce Merge branch 'staging' of https://github.com/Cohee1207/SillyTavern into staging 2023-10-06 03:02:45 +09:00
RossAscends
5307ee5d17 WI: Prob%, Memo to header, Keywords to main 2023-10-06 03:02:43 +09:00
Cohee
dd753498fa Unscrew zoomed mobile avatars width 2023-10-05 18:01:11 +03:00
RossAscends
30e5a31591 Position, Depth, Order Inputs to WI Entry header 2023-10-05 23:40:48 +09:00
RossAscends
2befd69c31 Merge branch 'staging' of https://github.com/Cohee1207/SillyTavern into staging 2023-10-05 20:10:19 +09:00
RossAscends
995b4d07ff WI: 🔵>🟢 & 🟡>🟢 2023-10-05 20:10:16 +09:00
valden80
788bbe969f Feature, ability to add banned words sequences from anywhere through {{banned "..."}} macro. (#1202)
* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Fix constant assignment, reformat code

---------

Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2023-10-05 13:10:41 +03:00
Cohee
99a89a7329 Less discrete embedded lorebook prompt 2023-10-04 22:41:10 +03:00
Cohee
f8b5fe8b1e Add Character note to char.card embeds 2023-10-04 22:13:56 +03:00
majick
368cba1149 Add Lightning 1.1 Context and Instruct templates (#1214)
* Add Lightning 1.1 Context and Instruct templates

* Remove double white space

* Fix fancy apostrophe

---------

Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2023-10-03 20:05:15 +03:00
RossAscends
dac6639cc1 Merge branch 'staging' of https://github.com/Cohee1207/SillyTavern into staging 2023-10-02 09:29:18 +09:00
RossAscends
345a0e04fc Add '/nar' as alias for /sys 2023-10-02 09:28:25 +09:00
Cohee
82182015e2 Allow group nudge in chat completions. 2023-10-02 00:24:16 +03:00
RossAscends
384e8d43a6 further improve WI entry state display 2023-10-02 02:43:23 +09:00
Cohee
b203b22d4d Remove commented-out code 2023-10-01 19:36:53 +03:00
AlexVeeBee
b9ea0061d5 Fix Dynamic Audio player looks
- Removed unnecessary text for audio player
- Fixed styling for audio player
2023-10-01 15:49:53 +00:00
Cohee
e9afb39a4a Merge branch 'release' into staging 2023-10-01 16:37:18 +03:00
Xrystal
df55a66153 Add preset settings to Docker public (#1204) 2023-10-01 16:36:52 +03:00
valden80
6df0c2acf3 Better wi 2 (#1203)
* Update world-info.css

* Update index.html

* V-center Wi delete

---------

Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2023-10-01 15:46:06 +03:00
Cohee
046bf20592 Update default objective prompts 2023-10-01 15:39:31 +03:00
RossAscends
2879e2701f Merge branch 'staging' of https://github.com/Cohee1207/SillyTavern into staging 2023-10-01 10:23:39 +09:00
RossAscends
7771ecb0d0 WI disable/constant checkboxes to tristate select 2023-10-01 10:21:19 +09:00
valden80
cc9f452699 Better wi cards (#1200) 2023-09-30 23:19:49 +03:00
yukikun-dev
a8fb2d9bee Fix GPT4-32k model (#1198) 2023-09-30 23:18:31 +03:00
Cohee
165997c791 #1199 I'm not even sure anymore 2023-09-30 15:42:37 +03:00
Cohee
22e3c25e12 Doc string for bias 2023-09-29 15:06:19 +03:00
AlpinDale
495b7eebbf chore: fix href for aphrodite (#1196)
The current href leads to text-generation-webui, this PR changes that to the correct URL.
2023-09-28 22:44:19 +03:00
Cohee
4c39a32be1 Don't reset instruct preset when toggling instruct mode 2023-09-28 21:59:03 +03:00
Cohee
584d7961eb Close message editor before regenerating. #1195 2023-09-28 21:47:55 +03:00
Cohee
977421edb7 Add comment 2023-09-28 21:33:03 +03:00
Cohee
f0b48aabff #1194 Fix chat width misbehaving in Firefox 2023-09-28 21:30:41 +03:00
Cohee
191c180946 Fix streaming url validation 2023-09-28 20:47:33 +03:00
Cohee
03fe8f88f9 Merge branch 'staging' of https://github.com/SillyTavern/SillyTavern into staging 2023-09-28 19:10:04 +03:00
Cohee
bb47712696 Split oobabooga/mancer sources. Add aphrodite support 2023-09-28 19:10:00 +03:00
RossAscends
f466ff3243 update WI Pos/Depth/Order hint on value change 2023-09-29 00:27:12 +09:00
Cohee
306cf51da4 #1180 Add custom token bans. Return grammar strings 2023-09-27 22:09:09 +03:00
Cohee
5857823c3b Revert "More Mexican Spanish Translation (#1184)" (#1190)
This reverts commit 02418d2d97.
2023-09-27 09:53:56 +03:00
Call me Jim
02418d2d97 More Mexican Spanish Translation (#1184)
* More spanish translation, fixed typo on index

Praying that typo fix won't mess with the other translations. Changed "idel" for "idle".

* Changed "es-spa" to "es-mx"

For Mexican Spanish. Spanish has a ridiculous amount of variants and the wording that might seem fine to me might be weird or maybe offensive in other variants.

* Trying to translate the View API key pop up

I'm also failing badly

* "Mirostat Mode" was repeated twice. Not anymore.

Also more Mexican Spanish translation.

* Mirostat Tau and Mirostat Era aren't repeated anymore.

* Partially translated AI Response Formatting to es-MX
2023-09-27 09:52:49 +03:00
Cohee
0569b706dc Add Palm model icon 2023-09-26 19:17:50 +03:00
Brian Dashore
bfda5a5492 Extra fixes (#1185) 2023-09-26 09:53:04 +03:00
Cohee
03e5ca054d Limit number of custom stop strings for Palm API 2023-09-25 23:12:14 +03:00
Cohee
3e29d39f05 Fix Palm when streaming is enabled 2023-09-25 20:24:56 +03:00
Cohee
01f14b461f Fix embedded lorebook depths 2023-09-25 20:11:16 +03:00
Cohee
edb79d8c53 Synchronize max depths for plugins 2023-09-25 19:29:24 +03:00
kalomaze
db71b87309 Basic Mirostat option descriptions (#1155)
* Basic miro desc

* Fix formatting

---------

Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2023-09-25 15:32:53 +03:00
theunstableman
8f426a0184 [FIXED] Add PaLM vectorization source (#1182)
* Add PaLM vectorization source

* FIX

* Add API key check. Fix comment

---------

Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2023-09-25 15:29:28 +03:00
Cohee
773d42b886 Suspend ooba grammar 2023-09-25 14:07:13 +03:00
RossAscends
1b7c51ebde Merge branch 'staging' of https://github.com/Cohee1207/SillyTavern into staging 2023-09-25 18:03:18 +09:00
RossAscends
e2d4f9dc8c streamline WI display 2023-09-25 18:03:10 +09:00
qHiyokop
0a9b595f80 Italian Translation Update #2 (#1177)
Co-authored-by: qHiyokop <caroligattini@gmail.com>
2023-09-25 11:26:46 +03:00
Cohee
86e9974410 Add grammar for ooba llama.cpp 2023-09-24 17:57:00 +03:00
Cohee
423f88a458 Skill issue 2023-09-24 15:04:02 +03:00
Cohee
9bc3e4e47c #1175 Substitute macro params in KCPP grammar 2023-09-24 14:58:35 +03:00
Cohee
1838ca1994 Replace magic numbers with named constants 2023-09-24 14:45:04 +03:00
Brian Dashore
2411006fdb World Info: Add insertion order at depth (#1174)
Depth previously injected entries randomly. This is not ideal for
world info architectures that rely on insertion order to function
properly.

Redo depth injection to have its own parameter and redo how it's
handled in generate.

Signed-off-by: kingbri <bdashore3@proton.me>
2023-09-24 14:41:56 +03:00
Brian Dashore
5421925d6c Add character specific locks for WI and LB (#1170)
* World Info: Add character locking for entries

With the addition of depth, world info has become more important than
ever. However, there is a conflict that can occur when using multiple
world info or lorebooks on a character. Add the ability to lock
entries to a specific character (or characters) if needed.

This allows for adequate separation especially when there are character
cards each with their separate lorebooks that build off of a core lorebook.

Signed-off-by: kingbri <bdashore3@proton.me>

* World Info: Add entry-specific character exclusions

This is the opposite of locking an entry to a specific character.
Exclusions allows the entry to get included for everyone except the
specified characters in the list.

Signed-off-by: kingbri <bdashore3@proton.me>

---------

Signed-off-by: kingbri <bdashore3@proton.me>
2023-09-23 22:35:51 +03:00
Cohee
d9b2b9f753 Bump package version 2023-09-23 20:51:55 +03:00
Cohee
a081f78bd8 (WIP) PaLM API 2023-09-23 20:48:56 +03:00
Cohee
5275e0c904 Full granularity of font scale slider 2023-09-23 12:09:34 +03:00
Cohee
2903167fad Remove unnecessary font-weight definitons in message text styles 2023-09-23 11:51:15 +03:00
RossAscends
a25cb024fe allow blur/shadow sliders to re-enable. 2023-09-23 11:56:24 +09:00
Cohee
6203d82f48 Update docker-publish.yml 2023-09-23 01:12:47 +03:00
Cohee
21ebe0c148 Update docker-publish.yml 2023-09-23 01:09:51 +03:00
Cohee
43510d5626 Update feature-request.yml 2023-09-23 00:38:59 +03:00
Cohee
3cd97b0772 Update bug-report.yml 2023-09-23 00:37:30 +03:00
Cohee
bd72f0eeb0 Update bug-report.yml 2023-09-23 00:37:11 +03:00
Cohee
565327fe1e Merge pull request #1168 from SillyTavern/staging
Staging
2023-09-22 23:07:52 +03:00
Cohee
2e5bbf0445 Fix server crash on local captioning 2023-09-22 23:04:26 +03:00
Cohee
ec6b6ab8d4 Restyle custom CSS input 2023-09-22 21:49:30 +03:00
Randall Fitzgerald
654a34f932 Added custom CSS box to UI Theme settings (#1166)
* Added custom CSS box to UI Theme settings

* Update index.html

Merged against release instead of staging. Whoops.

* Added an import stripper regex so that imports will be removed and show a toastr to inform the user.

* Moved import remove code to applyCustomCSS. Updated localStorage. Not re-running saveSettingsDebounced()
2023-09-22 21:13:58 +03:00
Cohee
3d1312c13a Restyle chat width slider 2023-09-22 16:56:01 +03:00
RossAscends
52cf684444 Merge branch 'staging' of https://github.com/Cohee1207/SillyTavern into staging 2023-09-22 22:27:08 +09:00
RossAscends
54c37e945b fix themes application, refactor user settings 2023-09-22 22:16:24 +09:00
Cohee
ecab8a6cb4 Fix mes.id and mes.tokens theme checkboxes not saving 2023-09-22 12:44:36 +03:00
RossAscends
b1ab1451ec separate chat and menu background color selection 2023-09-22 16:54:51 +09:00
RossAscends
b84fbed800 Merge pull request #1165 from city-unit/patch-2
Update readme.md
2023-09-22 14:16:40 +09:00
RossAscends
ccf66e6343 Merge pull request #1164 from city-unit/staging
Quick and dirty stat re-creator button
2023-09-22 14:16:29 +09:00
city-unit
5e8fc39735 Update readme.md
add city_unit
2023-09-21 19:59:01 -04:00
city-unit
cd1a8c9224 Quick and dirty stat re-creator button 2023-09-21 17:34:09 -04:00
Cohee
ae4a9a7b14 Remove legacy chat lazy load 2023-09-21 22:07:56 +03:00
Cohee
6ae1b7a72b Filter out "undefined" stopping strings 2023-09-21 22:02:51 +03:00
Cohee
d8380a390a Set numeric limits on WI order/depth 2023-09-21 21:26:30 +03:00
Cohee
5fbb232d69 Fix sprites plugin console spam 2023-09-21 21:15:05 +03:00
Cohee
25d818ecbd Reinsert summary extension prompt instantly when changing position 2023-09-21 21:13:24 +03:00
Cohee
5dd9a87dc9 Add position prop to prompt object 2023-09-21 20:57:28 +03:00
Cohee
857ce2c577 Merge branch 'staging' of https://github.com/SillyTavern/SillyTavern into staging 2023-09-21 20:46:11 +03:00
Cohee
902acc44a2 Support "before main prompt" extension position in prompt manager 2023-09-21 20:46:08 +03:00
RossAscends
22a0bf9451 fix extension update endpoint 2023-09-22 02:42:06 +09:00
Cohee
ad95be2500 Merge branch 'staging' of https://github.com/SillyTavern/SillyTavern into staging 2023-09-21 19:30:23 +03:00
Cohee
071b901f87 Add before story position for A/N and summary 2023-09-21 19:30:20 +03:00
kalomaze
ad9382a98c Custom --grammar support [for koboldcpp] (#1161)
* Basic kobold grammar implementation

This is probably jank as all hell, I don't write js, but I think I got it to work.

* No value by default

* Visual cleanup

+ reworded it a bit

* Conditionally enable Grammar based on version flag. Fix layout

---------

Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2023-09-21 15:21:59 +03:00
RossAscends
49c26f3810 keep input focus while adding tags 2023-09-21 17:17:15 +09:00
RossAscends
86c7a7a058 @Depth insertion for WI Entries 2023-09-21 16:04:34 +09:00
RossAscends
0178c95f6f Merge pull request #1162 from city-unit/feature/branch
Add branching as distinct from bookmarking
2023-09-21 13:57:25 +09:00
city-unit
440ecfc991 Fix toasts 2023-09-21 00:40:38 -04:00
city-unit
e0b5df97c4 Add branching as distinct from bookmarking 2023-09-20 22:48:05 -04:00
Cohee
e3f760a9dd Add assistant postfix to converted ChatML => text completion prompts. 2023-09-20 21:50:14 +03:00
Tony Ribeiro
c9783640c0 Dynamic Audio UI: more controls (#1127)
* Added control to audio ui to select bgm/ambient and lock selection to overide dynamic audio update. Load both assets and char specific audio assets

* correct ambient label and default value when no assets available.

* add padding in audio select

* Correct audio change of background ambient when locked. Updated CSS of audio ui for mobile friendly.

* add space between mixer

* Add checkbox to enable dynamic bgm/ambient switch

* correct background ambient fadout

* continue debuging ambient audio update

* finish debuging

* Fix BGM console error on first run. Reformat plugin code

* Changed audio bgm lock into loop on/off. Added random pick button for bgm. Moved ambient lock button to right.

* Add mouse wheel event handler on volume controls

* Change bgm select to only contain current chat character bgm (solo/group). When enable expression bgm is off, any of the char+asset bgm can play next if not looping.

* Corrected bgm looping at start. Force random to play another song if there is any.

* Format code

---------

Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2023-09-20 21:36:50 +03:00
RossAscends
f4d1e2a46e Merge pull request #1158 from bdashore3/staging
Add before story string/prompt option
2023-09-20 14:27:27 +09:00
kingbri
b52f71dcce Vectors: Add before story string/prompt option
This makes the most sense for placement of memories. Add the functionality
for other extension prompt placements as well.

Signed-off-by: kingbri <bdashore3@proton.me>
2023-09-20 00:06:43 -04:00
Maks
158aa79aed add model gpt-3.5-turbo-instruct and 0914 variant (#1154) 2023-09-19 23:50:27 +03:00
Daleth Darko
3fe2b21686 Update docker-compose.yml to keep images (#1153) 2023-09-19 21:36:47 +03:00
Cohee
dfbeb41afa #1152 Filter repeating message hashes when inserting vectors 2023-09-19 17:12:22 +03:00
Cohee
3d4054f10e Merge branch 'release' of https://github.com/SillyTavern/SillyTavern into release 2023-09-19 14:08:57 +03:00
Cohee
6c6f914655 Update colab 2023-09-19 14:08:53 +03:00
Cohee
3de5cdd7e8 Apply UI bg color to dialogue popup 2023-09-19 12:21:32 +03:00
RossAscends
eb6e987f55 search bar for user settings panel 2023-09-19 14:19:22 +09:00
Cohee
10f27f41d1 Better Horde error reporting 2023-09-18 18:00:11 +03:00
Cohee
3d83d1d5b7 Add a note that idle timer is in seconds 2023-09-18 17:49:23 +03:00
RossAscends
cfd6a26881 Merge pull request #1150 from city-unit/patch-1
Add idle slash command toggle
2023-09-18 13:59:01 +09:00
city-unit
e92d4a3dbf Add idle slash command toggle 2023-09-18 00:53:23 -04:00
RossAscends
1189734c62 Merge pull request #1148 from city-unit/patch/collab
Fix collab to generate an api key
2023-09-18 13:23:48 +09:00
city-unit
a78bb82b44 Fix collab to generate an api key 2023-09-17 23:53:11 -04:00
Cohee
3a8383ab79 Append continue message timer instead of rewriting 2023-09-18 02:12:06 +03:00
Cohee
dc1c477d62 Uncomment page size selector in WI editor 2023-09-18 01:52:41 +03:00
Cohee
e9c459690f Don't classify when no sprites/default. H-center sprite in non-waifu mode 2023-09-18 01:49:00 +03:00
Cohee
43de36b331 Properly center send buttons 2023-09-18 01:24:29 +03:00
Cohee
39567cf278 Slash command to summarize chat 2023-09-18 01:02:02 +03:00
city-unit
0033090a93 Idle Response / Continuous Generation (#1132)
* Initial idle stuff

* Much closer, can now quietly send as user to get a char response.

* Tweaks

* Better, reset the count of getting a message back, don't send while prompt is waiting.

* Allow selecting who is being prompted

* Comments and cleaup

* Remove char name for the moment (needs something here probably)

* Add random time period and "Always add character's name to prompt" respect

* Tooltips

* Load/unload listeners

* Reduce log spam

* Add inline prompt inclusion

* Add full loud prompting

* Comments

* Fix instruct newline (I think)

* Don't reset count on continue

* add quietToLoud for script.js

* add quietToLoud for slashcommands.js

* Keep instruct directives

* Removed some logging, don't do the Novel formatting if Q2L

* Logspam begone.

* Removed a bit more logging

* Add alignment style

* Reformat files. Add comments

* Reorder extensions

* Fix repeat logic to prompt once then only repeat the number specified

* Make repeat count more clear

---------

Co-authored-by: RossAscends <124905043+RossAscends@users.noreply.github.com>
Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2023-09-17 22:00:10 +03:00
RossAscends
ef8c347a95 Merge branch 'staging' of https://github.com/Cohee1207/SillyTavern into staging 2023-09-17 23:00:25 +09:00
RossAscends
535ec8c42d horde model selection QoL 2023-09-17 23:00:23 +09:00
majick
66911160c0 The popular mirostat "Gold/Silver/Bronze" community settings (#1146)
* The popular mirostat "Gold/Silver/Bronze" community settings

These settings have been popular in the community and are good
candidates for defaults.

* Update Miro Bronze.json

* Update Miro Gold.json

* Update Miro Bronze.json

* Update Miro Silver.json

* Rename Kobold presets

* Update textgen presets

---------

Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2023-09-17 16:39:02 +03:00
Cohee
d861c59f27 Merge branch 'release' into staging 2023-09-17 16:11:27 +03:00
Cohee
938f89cd1a Merge pull request #1143 from Xrystallized/async-getchat
Asynchronously fetch chats
2023-09-17 16:10:59 +03:00
RossAscends
acbd01407d Merge branch 'staging' of https://github.com/Cohee1207/SillyTavern into staging 2023-09-17 21:25:20 +09:00
RossAscends
254339af34 justify center default expressions in normal mode 2023-09-17 21:25:19 +09:00
Cohee
6c9cabfb57 Merge pull request #1145 from SillyTavern/release
Remove header #1144
2023-09-17 15:23:25 +03:00
Cohee
359277deb5 Remove header #1144 2023-09-17 15:21:26 +03:00
Cohee
11e7ca76e1 Use simpleGit to get app version 2023-09-17 14:27:41 +03:00
Cohee
dc1121b72a Remove tensorflow vector source. 2023-09-17 14:09:24 +03:00
RossAscends
323493962a fix oversized zoomed avatars again 2023-09-17 19:55:33 +09:00
RossAscends
751c0723dc skill issue: dont hide draggables on every keydown 2023-09-17 19:41:17 +09:00
Xrystal
23b08173ff Asynchronously fetch chats 2023-09-17 13:41:36 +08:00
RossAscends
9f15e67856 make zoomed avatars detectable by Escape hotkey 2023-09-17 05:17:02 +09:00
Cohee
2c84c93f3d Add thumbnails quality config 2023-09-16 21:53:30 +03:00
Cohee
bfdd071001 Move tokenizer endpoint and functions to separate file 2023-09-16 18:48:06 +03:00
Cohee
ab9aa28fe4 Move missed endpoints 2023-09-16 18:03:31 +03:00
Cohee
61995bb33f Move preset management into a separate file 2023-09-16 17:36:54 +03:00
Cohee
38b63b07f5 Extract sprite and custom content endpoints to a separate files. Update constants references 2023-09-16 17:28:28 +03:00
Cohee
d185e143a8 Merge branch 'staging' of https://github.com/SillyTavern/SillyTavern into staging 2023-09-16 16:39:16 +03:00
Cohee
4e1630c17d Extract endpoints for secrets and assets to separate files 2023-09-16 16:39:07 +03:00
RossAscends
2214f284fa free coloring even Blur turned off 2023-09-16 22:36:05 +09:00
Cohee
6e562bd1ff Extract server endpoints for thumbnails and extensions into separate files 2023-09-16 16:16:48 +03:00
RossAscends
2d774f32b2 custom colors for tag text 2023-09-16 18:37:19 +09:00
RossAscends
5ab449d8a1 missed one. 2023-09-16 15:48:28 +09:00
RossAscends
124658a006 Add Border Color Control 2023-09-16 15:42:26 +09:00
RossAscends
57de6229f9 Merge branch 'staging' of https://github.com/Cohee1207/SillyTavern into staging 2023-09-16 12:50:11 +09:00
RossAscends
e162df67fa partially fix quietPrompts (/sysgen) for Instruct 2023-09-16 12:48:14 +09:00
Cohee
dae09d58d7 Fix scroll top offset for new import flashing 2023-09-16 00:46:27 +03:00
Cohee
6dd09858d4 Don't remove names from the past chat. Fix non-streaming auto-continue.
We took fair criticism from the NovelAI dev.
2023-09-15 23:32:01 +03:00
Cohee
985c2bcfb1 Merge branch 'staging' of https://github.com/SillyTavern/SillyTavern into staging 2023-09-15 22:26:56 +03:00
Cohee
fc7a4538e9 Cancel auto-continue if user input is not empty 2023-09-15 22:26:53 +03:00
Cohee
72c672c2c2 Merge pull request #1136 from duncannah/fix-docker-post-install
Fix Docker by copying post-install.js into image
2023-09-15 21:59:27 +03:00
Cohee
77c8bc8eb5 Don't trigger auto-continue after quiet gens 2023-09-15 21:49:52 +03:00
duncannah
1edc2b08f2 Fix Docker by copying post-install.js into image 2023-09-15 20:44:48 +02:00
Cohee
d34f7d3e1a Replace multigen with auto-continue 2023-09-15 21:34:41 +03:00
Cohee
eaca6ddaf0 Don't try to resolve unknown tiktoken models 2023-09-15 19:31:17 +03:00
Cohee
aa89a74901 Merge branch 'staging' of https://github.com/SillyTavern/SillyTavern into staging 2023-09-15 17:59:42 +03:00
Cohee
599904d589 Move NovelAI endpoints to separate file 2023-09-15 17:54:13 +03:00
Chris Kaiser
ba302e4aa0 Added publishing of docker images on release (#1135)
* Added publishing of docker images on release

* Use proper project name

---------

Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2023-09-15 16:27:42 +03:00
Cohee
0f1a0963fd Merge branch 'release' into staging 2023-09-15 15:06:13 +03:00
Cohee
2b3055a84a Webp cards format is no longer supported 2023-09-15 14:56:15 +03:00
RossAscends
1fed8ba4f7 hotswap: instructions if no faves; no placeholders 2023-09-15 17:55:16 +09:00
Cohee
5827f9638f Fix /emote in groups 2023-09-15 01:17:24 +03:00
Cohee
6ad786f348 Add alternative local vectors source.
x5 speed boost!!
2023-09-14 23:40:13 +03:00
Cohee
0cc048cb64 Refactor transformers.js usage 2023-09-14 23:12:33 +03:00
Cohee
cb8d9ac71b Unset SD gen timeout 2023-09-14 21:49:29 +03:00
Cohee
b24509ef43 Visual touchup of custom expressions 2023-09-14 21:41:30 +03:00
Cohee
7553efc308 Custom char expressions 2023-09-14 21:30:02 +03:00
Cohee
9fb4b3425e Costume subfolders 2023-09-14 19:12:54 +03:00
Cohee
182216e711 /costume slash command 2023-09-14 18:37:13 +03:00
Cohee
52891898d2 Pass max length to Kobold GUI settings 2023-09-14 18:20:12 +03:00
Cohee
f6c29c61df Adjust Horde timeout to exactly 20 minutes (Horde's own limit). 2023-09-14 18:10:01 +03:00
Cohee
17a5d629ea Auto-set hotswap slot number based on the screen width. 2023-09-14 15:56:01 +03:00
Cohee
688551ffa6 #1128 Auto-Expand Message Actions 2023-09-14 15:23:51 +03:00
Cohee
ece34dc337 Move default files creation to post-install 2023-09-14 14:21:38 +03:00
Cohee
179de92231 Use transformers WASM binaries from a local folder 2023-09-14 14:11:37 +03:00
deffcolony
c4c962aeb9 issue template (#1126)
* seperated languages into structured folders

i18n.js needs to be connected to index.json so it fetches the common json files

* Update index.json

* New Launcher + security file

* cancel locales feature temporary

* added secrets to backup

* replaced download with winget

* fixed restoring backup bug

fixed bug that creates sillytavern\public folder inside sillytavern\public

* fixes date format

* launcher update + icon

* issue template

* old template no longer needed

* Delete .github/CODE_OF_CONDUCT.md

* Update bug-report.yml

* Update feature-request.yml

---------

Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2023-09-14 01:35:29 +03:00
Cohee
d0182c47de Fix expression BGM switch 2023-09-14 01:28:44 +03:00
Cohee
0d30d8244f Fix not applying ooba streaming url from server history 2023-09-13 18:07:06 +03:00
Cohee
2e67ebd881 Fix top bar gap (again) 2023-09-13 15:24:55 +03:00
Cohee
d62cdffcc0 Unblock amount_gen in Kobold GUI preset 2023-09-13 15:19:44 +03:00
Cohee
77a28c7131 Don't hide expressions menu if no chat open 2023-09-13 15:19:10 +03:00
Cohee
c60d4e5bb9 Merge branch 'staging' of https://github.com/SillyTavern/SillyTavern into staging 2023-09-13 13:21:47 +03:00
Cohee
990e08ba2d Fix top bar gap in mobile view 2023-09-13 13:21:42 +03:00
Cohee
bf0cf10403 Update hotswap state on fav 2023-09-13 13:12:19 +03:00
Jason Wu
7a3869c476 Enable Smart Context (ChromaDB) support within OpenAI API (#1125)
* Add JetBrains IDE .idea folder to .gitignore

* Enable Smart Context (ChromaDB) support within OpenAI API
2023-09-13 13:01:56 +03:00
mweldon
e74090139c Add copy to clipboard button on prompt itemization popup (#1124)
* Add copy to clipboard button on prompt itemization popup

* Minor fix for NovelAI Summarize and new bad words
2023-09-13 11:52:37 +03:00
Cohee
09fc42a787 Lock extensions display order in top and context menus 2023-09-13 01:40:01 +03:00
Cohee
c0e5d7efae Save chat scroll position when user input overflows the line 2023-09-13 00:51:21 +03:00
Cohee
66ec17620f Move Horde and SD endpoints into separate files 2023-09-12 20:45:36 +03:00
Cohee
51e2a3afcf Fix not being able to close the gallery on SD gens. 2023-09-12 19:23:33 +03:00
Cohee
abc1555c19 Merge branch 'vectors' into staging 2023-09-12 18:10:47 +03:00
Cohee
6c29879f12 Filter out undefined messages for past chats search 2023-09-12 18:05:37 +03:00
Cohee
2f8f6844fe Merge branch 'vectors' into staging 2023-09-12 15:49:47 +03:00
Cohee
dc4a6e862b Add local caption pipeline to UI plugin 2023-09-12 00:15:21 +03:00
RossAscends
4bf91c7772 clamp zoomed avatar widths to 400px as base 2023-09-12 02:09:18 +09:00
Cohee
004baf7b87 Reserve 3 extra tokens for each chat completion 2023-09-11 17:24:54 +03:00
Cohee
6c8bd06308 Reserve 3 extra tokens for each chat completion 2023-09-11 17:23:37 +03:00
Cohee
65b4551864 Reserve 3 extra tokens for each chat completion 2023-09-11 17:22:31 +03:00
Cohee
7f55d108cf Don't use talkinghead with local classification model 2023-09-11 13:01:45 +03:00
Cohee
c9a9dab523 Don't synchronize vectors when opening chat 2023-09-11 12:35:34 +03:00
Cohee
f149fc9aaa Endpoint for local captioning pipeline 2023-09-11 04:47:14 +03:00
Cohee
7aeb098212 Fix config access. Add Top K to classification results 2023-09-11 01:49:47 +03:00
Cohee
c76c76410c Add ability to override local classification model 2023-09-11 01:25:22 +03:00
Cohee
5cc6a2dca6 (WIP) Lazier chat loading 2023-09-11 01:07:45 +03:00
Cohee
0bdd350b8d Don't synchronize vectors while streaming 2023-09-10 20:21:23 +03:00
Cohee
9a5e667674 Throttle classification requests during streaming 2023-09-10 20:14:57 +03:00
Cohee
c9d8d7ba64 Fix vectorize all with no chat selected 2023-09-10 19:47:41 +03:00
Cohee
17367f2b17 Bump package version 2023-09-10 19:06:36 +03:00
Cohee
6362f76812 Set default DNS resolution order to IPv4 first 2023-09-10 19:04:11 +03:00
Cohee
74d627f674 Set default DNS resolution order to IPv4 first 2023-09-10 19:03:18 +03:00
Cohee
599261dc31 Set default DNS resolution order to IPv4 first 2023-09-10 19:02:58 +03:00
Cohee
d19c151669 Add DeepLX translation provider #1111 2023-09-10 18:53:52 +03:00
Cohee
b30d7ad51c Change net workaround for node 20 2023-09-10 18:24:32 +03:00
Cohee
4fdc533bd7 Change net workaround for node 20 2023-09-10 18:23:50 +03:00
Cohee
d17ac770e6 Change net workaround for node 20 2023-09-10 18:22:39 +03:00
Cohee
70071312d3 Add OneRingTranslator #521 2023-09-10 17:27:50 +03:00
Cohee
ec23356c99 Move translation endpoints into a separate file 2023-09-10 16:41:36 +03:00
Cohee
98cc969d18 Merge branch 'staging' into vectors 2023-09-10 16:06:15 +03:00
Cohee
dc5deaf47c Mobile doesn't like select2 2023-09-10 14:39:49 +03:00
Cohee
d81c94de0b Fix sort by date 2023-09-10 14:30:29 +03:00
Cohee
e2e32da4e6 Unrestrict console logs display limit 2023-09-10 04:12:14 +03:00
Cohee
0480acebcd #1059 Mancer model selector 2023-09-10 04:08:32 +03:00
Cohee
dbac2704f3 Rename control 2023-09-10 02:44:20 +03:00
Cohee
f8d90c1933 Merge branch 'staging' into vectors 2023-09-10 02:42:42 +03:00
Cohee
3dbdd1258e Don't close the panels when dismissing toasts 2023-09-10 02:30:23 +03:00
Cohee
f92249790f The return of permanent tokens display 2023-09-10 00:58:37 +03:00
Cohee
23951b8c8a Prevent sync and generation at the same time 2023-09-10 00:15:02 +03:00
Cohee
af38971a01 Delete vectors on deleting chats 2023-09-09 22:15:47 +03:00
Cohee
ed6417ebcd Display vectorization error in toast 2023-09-09 21:36:04 +03:00
Cohee
2fa038f91d Add advanced vector controls 2023-09-09 21:26:04 +03:00
Cohee
31beb05aa1 Substitute macro in Novel preamble 2023-09-09 18:19:01 +03:00
Cohee
4cf6a1f7da Cache and sample classification results 2023-09-09 17:31:27 +03:00
Cohee
180dcefe40 Patch onnx to always use wasm 2023-09-09 16:55:54 +03:00
Cohee
307e666c27 onnx runtime to web 2023-09-09 16:42:16 +03:00
Cohee
b605b940eb Replace transformers.js with patched version 2023-09-09 16:29:11 +03:00
Cohee
967a084aad (WIP) Local emotion classification pipeline 2023-09-09 15:14:16 +03:00
Cohee
4d08e3e9be Decrease batch size. Add browser console log 2023-09-09 15:12:54 +03:00
Cohee
da34517943 Merge branch 'staging' into vectors 2023-09-09 01:21:03 +03:00
kalomaze
7ffe3d21f8 Old Default context template (#1121)
Mirrors the old prompting style to a T. Can only be merged after the PR to add macros to the Example Separator + Chat Start is merged.
2023-09-08 23:04:51 +03:00
Cohee
ea01247bcf #1090 Save Kobold/ooba servers history 2023-09-08 22:44:06 +03:00
IkariDevGIT
786b87952e Quick-reply enhancements +fix (#1118)
* Update index.js

* change manual replace to substituteParams

* Update index.js
2023-09-08 21:38:31 +03:00
Cohee
26ddfd1a08 Continue with AltGr+Enter 2023-09-08 21:27:33 +03:00
Cohee
6f3947226f Merge pull request #1116 from ThisIsPIRI/macro
Replace macros in example separator and chat start
2023-09-08 21:10:20 +03:00
Cohee
42fd317188 Merge branch 'staging' into vectors 2023-09-08 16:41:26 +03:00
Cohee
2411b17279 Merge branch 'release' into staging 2023-09-08 16:40:45 +03:00
Cohee
ab460199ab #1117 Fix typing indicator and auto-scroll breaking mobile layout 2023-09-08 16:36:00 +03:00
Cohee
3a3ff89047 Add button to vectorize all chat 2023-09-08 15:25:10 +03:00
Cohee
a5acc7872d Add OpenAI vector source. 2023-09-08 13:57:27 +03:00
ThisIsPIRI
2688d980c1 Replace macros in example separator and chat start 2023-09-08 19:25:17 +09:00
Cohee
3abee9e37a Merge pull request #1115 from SillyTavern/release
Release
2023-09-08 12:48:37 +03:00
Cohee
5b63d0ff40 Merge pull request #1114 from ThisIsPIRI/tokenapi
Make API tokenization work again for ooba
2023-09-08 12:48:00 +03:00
ThisIsPIRI
a96aad6073 Make API tokenization work again for ooba 2023-09-08 18:31:30 +09:00
Cohee
02bdd56e20 Make printMessages async 2023-09-08 12:10:41 +03:00
Cohee
b5a6257352 Forgot that groups exist 2023-09-08 10:51:59 +03:00
Cohee
408a1fe846 Merge pull request #1112 from IkariDevGIT/gallerycommand
Add command for opening the gallery
2023-09-08 10:44:57 +03:00
RossAscends
44ba1cba59 Merge pull request #1113 from Hakirus/staging
Quick Reply Mod
2023-09-08 09:30:19 +09:00
Cohee
96df705409 Change insertion strategy to an extension block 2023-09-08 01:26:26 +03:00
Cohee
40f95bf842 Fix HypeBot plugin settings not saving 2023-09-08 00:30:31 +03:00
Cohee
9d45c0a018 Add UI plugin for vectors 2023-09-08 00:28:06 +03:00
EvilFear
470da71b3b Update index.js 2023-09-07 17:08:21 -04:00
Cohee
92ab17b58b Fix HypeBot plugin settings not saving 2023-09-07 22:28:53 +03:00
Cohee
8c00f38a1f Add local vectors storage 2023-09-07 21:53:47 +03:00
Cohee
89705391d1 Fix getSpriteFolderName function call 2023-09-07 17:52:37 +03:00
Cohee
bbe52886da Slash command to set sprite / emote. Also allow to do it per click even in online mode 2023-09-07 17:48:12 +03:00
IkariDevGIT
ef68dd07ac Add command for opening the gallery 2023-09-07 16:02:34 +02:00
anmelus
f2cae64b0d Added loop-around for swipe-left when multiple swipes exist (#1104)
* Added loop-around for swipe-left when multiple swipes exist

* Added loop around for alternate greetings in swipe_right(),
Disabled toast for add more alternate greetings

* Debounce saving swipes

---------

Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2023-09-07 16:45:19 +03:00
EvilFear
868778b079 Update index.js
Quick Reply Mod
2023-09-06 22:27:03 -04:00
Cohee
e681f1f36f #1069 Fix sort by date for v2 cards 2023-09-06 20:59:59 +03:00
Memerlin
1832145645 More spanish translation, fixed typo on index (#1103)
* More spanish translation, fixed typo on index

Praying that typo fix won't mess with the other translations. Changed "idel" for "idle".

* Update index.html

---------

Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2023-09-06 18:10:00 +03:00
Cohee
af4f60a4af Update Pygmalion context template. Add Pygmalion instruct template 2023-09-06 17:56:49 +03:00
Cohee
fa147f71a3 Merge branch 'release' into staging 2023-09-06 16:07:04 +03:00
Cohee
3830347d81 Bump package version 2023-09-06 16:06:23 +03:00
Cohee
902676262a Delete Launcher.bat 2023-09-06 16:02:52 +03:00
Cohee
fe813d5469 Delete Launcher.bat 2023-09-06 16:02:16 +03:00
173 changed files with 12041 additions and 5911 deletions

90
.github/ISSUE_TEMPLATE/bug-report.yml vendored Normal file
View File

@@ -0,0 +1,90 @@
name: Bug Report 🐛
description: Report something that's not working the intended way. Support requests for external programs (reverse proxies, 3rd party servers, other peoples' forks) will be refused!
title: '[BUG] <title>'
labels: ['bug']
body:
- type: dropdown
id: environment
attributes:
label: Environment
description: Where are you running SillyTavern?
options:
- Self-Hosted (Bare Metal)
- Self-Hosted (Docker)
- Android (Termux)
- Cloud Service (Static)
- Other (Specify below)
validations:
required: true
- type: input
id: system
attributes:
label: System
description: >-
For deployment issues, specify your [distro or OS](https://whatsmyos.com/) and/ or Docker version.
For client-side issues, include your [browser version](https://www.whatsmybrowser.org/)
placeholder: e.g. Firefox 101, Manjaro Linux 21.3.0, Docker 20.10.16
validations:
required: true
- type: input
id: version
attributes:
label: Version
description: What version of SillyTavern are you running?
placeholder: (check User Settings to see the version)
validations:
required: true
- type: textarea
id: desktop
attributes:
label: Desktop Information
description: Please provide details about your desktop environment.
placeholder: |
- Node.js version (if applicable): [run `node --version` in cmd]
- Generation API [e.g. KoboldAI, OpenAI]
- Branch [staging, release]
- Model [e.g. Pygmalion 6b, LLaMa 13b]
validations:
required: false
- type: textarea
id: repro
attributes:
label: Describe the problem
description: Please describe exactly what is not working, include the steps to reproduce, actual result and expected result
placeholder: When doing ABC then DEF, I expect to see XYZ, but I actually see ZYX
validations:
required: true
- type: textarea
id: logs
attributes:
label: Additional info
description: Logs? Screenshots? Yes, please.
placeholder: If the issue happens during build-time, include terminal logs. For run-time errors, include browser logs which you can view in the Dev Tools (F12), under the Console tab. Take care to blank out any personal info.
validations:
required: false
- type: checkboxes
id: idiot-check
attributes:
label: Please tick the boxes
description: Before submitting, please ensure that
options:
- label: You have explained the issue clearly, and included all relevant info
required: true
- label: You've checked that this [issue hasn't already been raised](https://github.com/SillyTavern/SillyTavern/issues?q=is%3Aissue)
required: true
- label: You've checked the [docs](https://docs.sillytavern.app/) ![important](https://img.shields.io/badge/Important!-F6094E)
required: true
- type: markdown
attributes:
value: |-
## Thanks 🙏
Thank you for raising this ticket - in doing so you are helping to make SillyTavern better for everyone.
validations:
required: false

View File

@@ -1,45 +0,0 @@
---
name: Bug report
about: "Create a report to help us improve. PAY ATTENTION: Support requests for external programs (reverse proxies, 3rd party servers, other peoples' forks) will be refused!"
title: "[BUG]"
labels: ''
assignees: ''
---
> **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
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Logs**
Providing the logs from the browser DevTools console (opened by pressing the F12 key) or SillyTavern command line window will be highly appreciated.
**Desktop (please complete the following information):**
- OS/Device: [e.g. Windows 11]
- Environment: [cloud, local]
- Node.js version (if applicable): [run `node --version` in cmd]
- Browser [e.g. chrome, safari]
- Generation API [e.g. KoboldAI, OpenAI]
- Branch [staging, release]
- Model [e.g. Pygmalion 6b, LLaMa 13b]
**Additional context**
Add any other context about the problem here.

View File

@@ -0,0 +1,91 @@
name: Feature Request ✨
description: Suggest an idea for future development of this project
title: '[FEATURE_REQUEST] <title>'
labels: ['enhancement']
body:
# Field 1 - Did the user searched for similar requests
- type: dropdown
id: similarRequest
attributes:
label: Have you searched for similar requests?
description:
options:
- 'No'
- 'Yes'
validations:
required: false
# Field 2 - Is it bug-related
- type: textarea
id: issue
attributes:
label: Is your feature request related to a problem? If so, please describe.
description:
placeholder: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
validations:
required: false
# Field 3 - Describe feature
- type: textarea
id: solution
attributes:
label: Describe the solution you'd like
placeholder: An outline of how you would like this to be implemented, include as much details as possible
validations:
required: true
# Field 4 - Describe alternatives
- type: textarea
id: alternatives
attributes:
label: Describe alternatives you've considered
placeholder: A clear and concise description of any alternative solutions or features you've considered.
validations:
required: false
# Field 5 - Additional context
- type: textarea
id: addcontext
attributes:
label: Additional context
placeholder: Add any other context or screenshots about the feature request here.
validations:
required: false
# Field 6 - Priority
- type: dropdown
id: priority
attributes:
label: Priority
description: How urgent is the development of this feature
options:
- Low (Nice-to-have)
- Medium (Would be very useful)
- High (The app does not function without it)
validations:
required: true
# Field 7 - Can the user implement
- type: dropdown
id: canImplement
attributes:
label: Is this something you would be keen to implement?
description: Are you raising this ticket in order to get an issue number for your PR?
options:
- 'No'
- 'Maybe'
- 'Yes!'
validations:
required: false
# Final text
- type: markdown
attributes:
value: |-
## Thanks 🙏
Thank you for your feature suggestion.
Please note that there is no guarantee that your idea will be implemented.
validations:
required: false

View File

@@ -1,23 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[Feature Request] "
labels: ''
assignees: ''
---
**Have you searched for similar [requests](https://github.com/SillyTavern/SillyTavern/issues?q=)?**
Yes/No
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -41,8 +41,6 @@ SillyTavern 本身并无用处,因为它只是一个用户聊天界面。你
<https://rentry.org/STAI-Termux>
Termux 不支持**.Webp 字符卡的导入/导出。请使用 JSON 或 PNG 格式**。
## 有问题或建议?
### 我们现在有了 Discord 社区

4
.github/readme.md vendored
View File

@@ -41,8 +41,6 @@ Since Tavern is only a user interface, it has tiny hardware requirements, it wil
<https://rentry.org/STAI-Termux>
**.webp character cards import/export is not supported in Termux. Use either JSON or PNG formats instead.**
## Questions or suggestions?
### We now have a community Discord server
@@ -71,7 +69,6 @@ Get in touch with the developers directly:
* [Oobabooga's TextGen WebUI](https://github.com/oobabooga/text-generation-webui) API connection
* [AI Horde](https://horde.koboldai.net/) connection
* Prompt generation formatting tweaking
* webp character card interoperability (PNG is still an internal format)
## Extensions
@@ -295,6 +292,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>)
* city_unit's extensions and various QoL features (<https://github.com/city-unit>)
* 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!

45
.github/workflows/docker-publish.yml vendored Normal file
View File

@@ -0,0 +1,45 @@
# This workflow will publish a docker image for every full release to the GitHub package repository
name: Create Docker Image on Release
on:
release:
# Only runs on full releases not pre releases
types: [released]
env:
# This should allow creation of docker images even in forked repositories
# Image name may not contain uppercase characters, so we can not use the repository name
# Creates a string like: ghcr.io/SillyTavern/sillytavern
image_name: ghcr.io/sillytavern/sillytavern
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
# Build docker image using dockerfile and tag it with branch name
# Assumes branch name is the version number
- name: Build the Docker image
run: |
docker build . --file Dockerfile --tag $image_name:${{ github.ref_name }}
# Login into package repository as the person who created the release
- name: Login to GitHub Container Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Assumes release is the latest and marks image as such
- name: Docker Tag and Push
run: |
docker tag $image_name:${{ github.ref_name }} $image_name:latest
docker push $image_name:${{ github.ref_name }}
docker push $image_name:latest

3
.gitignore vendored
View File

@@ -26,6 +26,7 @@ public/settings.json
/thumbnails
whitelist.txt
.vscode
.idea/
secrets.json
/dist
/backups/
@@ -35,3 +36,5 @@ content.log
cloudflared.exe
public/assets/
access.log
/vectors/
/cache/

View File

@@ -13,7 +13,7 @@ ENTRYPOINT [ "tini", "--" ]
WORKDIR ${APP_HOME}
# Install app dependencies
COPY package*.json ./
COPY package*.json post-install.js ./
RUN \
echo "*** Install npm packages ***" && \
npm install && npm cache clean --force
@@ -23,7 +23,7 @@ COPY . ./
# Copy default chats, characters and user avatars to <folder>.default folder
RUN \
IFS="," RESOURCES="characters,chats,groups,group chats,User Avatars,worlds" && \
IFS="," RESOURCES="characters,chats,groups,group chats,User Avatars,worlds,OpenAI Settings,NovelAI Settings,KoboldAI Settings,TextGen Settings" && \
\
echo "*** Store default $RESOURCES in <folder>.default ***" && \
for R in $RESOURCES; do mv "public/$R" "public/$R.default"; done && \

View File

@@ -1,641 +0,0 @@
@echo off
REM --------------------------------------------
REM This script was created by: Deffcolony
REM --------------------------------------------
title SillyTavern Launcher
setlocal
REM ANSI Escape Code for Colors
set "reset="
REM Strong Foreground Colors
set "white_fg_strong="
set "red_fg_strong="
set "green_fg_strong="
set "yellow_fg_strong="
set "blue_fg_strong="
set "magenta_fg_strong="
set "cyan_fg_strong="
REM Normal Background Colors
set "red_bg="
set "blue_bg="
REM Environment Variables (TOOLBOX 7-Zip)
set "zip7version=7z2301-x64"
set "zip7_install_path=%ProgramFiles%\7-Zip"
set "zip7_download_path=%TEMP%\%zip7version%.exe"
REM Environment Variables (TOOLBOX FFmpeg)
set "ffmpeg_url=https://www.gyan.dev/ffmpeg/builds/ffmpeg-git-full.7z"
set "ffdownload_path=%TEMP%\ffmpeg.7z"
set "ffextract_path=C:\ffmpeg"
set "bin_path=%ffextract_path%\bin"
REM Environment Variables (TOOLBOX Node.js)
set "node_installer_path=%temp%\NodejsInstaller.msi"
REM Environment Variables (winget)
set "winget_path=%userprofile%\AppData\Local\Microsoft\WindowsApps"
REM Environment Variables (TOOLBOX Install Extras)
set "miniconda_path=%userprofile%\miniconda"
REM Check if Winget is installed; if not, then install it
winget --version > nul 2>&1
if %errorlevel% neq 0 (
echo %yellow_fg_strong%[WARN] Winget is not installed on this system.
echo %blue_fg_strong%[INFO]%reset% Installing Winget...
bitsadmin /transfer "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe" /download /priority FOREGROUND "https://github.com/microsoft/winget-cli/releases/download/v1.5.2201/Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle" "%temp%\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle"
start "" "%temp%\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle"
echo %green_fg_strong%Winget is now installed.%reset%
) else (
echo %blue_fg_strong%[INFO] Winget is already installed.%reset%
)
rem Get the current PATH value from the registry
for /f "tokens=2*" %%A in ('reg query "HKCU\Environment" /v PATH') do set "current_path=%%B"
rem Check if the paths are already in the current PATH
echo %current_path% | find /i "%winget_path%" > nul
set "ff_path_exists=%errorlevel%"
rem Append the new paths to the current PATH only if they don't exist
if %ff_path_exists% neq 0 (
set "new_path=%current_path%;%winget_path%"
rem Update the PATH value in the registry
reg add "HKCU\Environment" /v PATH /t REG_EXPAND_SZ /d "%new_path%" /f
rem Update the PATH value for the current session
setx PATH "%new_path%" > nul
echo %green_fg_strong%winget added to PATH.%reset%
) else (
set "new_path=%current_path%"
echo %blue_fg_strong%[INFO] winget already exists in PATH.%reset%
)
REM Check if Git is installed if not then install git
git --version > nul 2>&1
if %errorlevel% neq 0 (
echo %yellow_fg_strong%[WARN] Git is not installed on this system.%reset%
echo %blue_fg_strong%[INFO]%reset% Installing Git using Winget...
winget install -e --id Git.Git
echo %green_fg_strong%Git is installed. Please restart the Launcher.%reset%
pause
exit
) else (
echo %blue_fg_strong%[INFO] Git is already installed.%reset%
)
REM Check for updates
git fetch origin
for /f %%i in ('git rev-list HEAD...origin/%current_branch%') do (
set "update_status=%yellow_fg_strong%Update Available%reset%"
goto :found_update
)
set "update_status=%green_fg_strong%Up to Date%reset%"
:found_update
REM Home - frontend
:home
cls
echo %blue_fg_strong%/ Home%reset%
echo -------------------------------------
echo What would you like to do?
echo 1. Start SillyTavern
echo 2. Start SillyTavern + Extras
echo 3. Update
echo 4. Backup
echo 5. Switch branch
echo 6. Toolbox
echo 7. Exit
REM Get the current Git branch
for /f %%i in ('git branch --show-current') do set current_branch=%%i
echo ======== VERSION STATUS =========
echo SillyTavern branch: %cyan_fg_strong%%current_branch%%reset%
echo Update Status: %update_status%
echo =================================
set "choice="
set /p "choice=Choose Your Destiny (default is 1): "
REM Default to choice 1 if no input is provided
if not defined choice set "choice=1"
REM Home - backend
if "%choice%"=="1" (
call :start
) else if "%choice%"=="2" (
call :start_extras
) else if "%choice%"=="3" (
call :update
) else if "%choice%"=="4" (
call :backup_menu
) else if "%choice%"=="5" (
call :switchbrance_menu
) else if "%choice%"=="6" (
call :toolbox
) else if "%choice%"=="7" (
exit
) else (
color 6
echo WARNING: Invalid number. Please insert a valid number.
pause
goto :home
)
:start
REM Check if Node.js is installed
node --version > nul 2>&1
if %errorlevel% neq 0 (
echo %red_fg_strong%[ERROR] node command not found in PATH%reset%
echo %red_bg%Please make sure Node.js is installed and added to your PATH.%reset%
echo %blue_bg%To install Node.js go to Toolbox%reset%
pause
goto :home
)
echo %blue_fg_strong%[INFO]%reset% A new window has been launched.
start /wait cmd /c start.bat
goto :home
:start_extras
REM Run conda activate from the Miniconda installation
call "%miniconda_path%\Scripts\activate.bat"
REM Activate the sillytavernextras environment
call conda activate sillytavernextras
REM Start SillyTavern Extras with desired configurations
python server.py --coqui-gpu --rvc-save-file --cuda-device=0 --max-content-length=1000 --enable-modules=caption,summarize,classify,rvc,coqui-tts --classification-model=joeddav/distilbert-base-uncased-go-emotions-student --share
goto :home
:update
echo Updating...
pushd %~dp0
REM Check if git is installed
git --version > nul 2>&1
if %errorlevel% neq 0 (
echo %red_fg_strong%[ERROR] git command not found in PATH. Skipping update.%reset%
echo %red_bg%Please make sure Git is installed and added to your PATH.%reset%
echo %blue_bg%To install Git go to Toolbox%reset%
) else (
call git pull --rebase --autostash
if %errorlevel% neq 0 (
REM incase there is still something wrong
echo There were errors while updating. Please download the latest version manually.
)
)
pause
goto :home
REM Switch Brance - frontend
:switchbrance_menu
cls
echo %blue_fg_strong%/ Home / Switch Branch%reset%
echo -------------------------------------
echo What would you like to do?
echo 1. Switch to Release - SillyTavern
echo 2. Switch to Staging - SillyTavern
echo 3. Switch to Main - Extras
echo 4. Switch to Neo - Extras
echo 5. Back to Home
REM Get the current Git branch
for /f %%i in ('git branch --show-current') do set current_branch=%%i
echo ======== VERSION STATUS =========
echo SillyTavern branch: %cyan_fg_strong%%current_branch%%reset%
echo Extras branch: %cyan_fg_strong%%current_branch%%reset%
echo =================================
set /p brance_choice=Choose Your Destiny:
REM Switch Brance - backend
if "%brance_choice%"=="1" (
call :switch_release_st
) else if "%brance_choice%"=="2" (
call :switch_staging_st
) else if "%brance_choice%"=="3" (
call :switch_main_ste
) else if "%brance_choice%"=="4" (
call :switch_neo_ste
) else if "%brance_choice%"=="5" (
goto :home
) else (
color 6
echo WARNING: Invalid number. Please insert a valid number.
pause
goto :switchbrance_menu
)
:switch_release_st
echo %blue_fg_strong%[INFO]%reset% Switching to release branch...
git switch release
pause
goto :switchbrance_menu
:switch_staging_st
echo %blue_fg_strong%[INFO]%reset% Switching to staging branch...
git switch staging
pause
goto :switchbrance_menu
:switch_main_ste
echo %blue_fg_strong%[INFO]%reset% Switching to main branch...
cd SillyTavern-extras
git switch main
pause
goto :switchbrance_menu
:switch_neo_ste
echo %blue_fg_strong%[INFO]%reset% Switching to neo branch...
cd SillyTavern-extras
git switch neo
pause
goto :switchbrance_menu
REM Backup - Frontend
:backup_menu
REM Check if 7-Zip is installed
7z > nul 2>&1
if %errorlevel% neq 0 (
echo %red_fg_strong%[ERROR] 7z command not found in PATH%reset%
echo %red_bg%Please make sure 7-Zip is installed and added to your PATH.%reset%
echo %blue_bg%To install 7-Zip go to Toolbox%reset%
pause
goto :home
)
cls
echo %blue_fg_strong%/ Home / Backup%reset%
echo -------------------------------------
echo What would you like to do?
REM color 7
echo 1. Create Backup
echo 2. Restore Backup
echo 3. Back to Home
set /p backup_choice=Choose Your Destiny:
REM Backup - Backend
if "%backup_choice%"=="1" (
call :create_backup
) else if "%backup_choice%"=="2" (
call :restore_backup
) else if "%backup_choice%"=="3" (
goto :home
) else (
color 6
echo WARNING: Invalid number. Please insert a valid number.
pause
goto :backup_menu
)
:create_backup
REM Create a backup using 7zip
7z a "backups\backup_.7z" ^
"public\assets\*" ^
"public\Backgrounds\*" ^
"public\Characters\*" ^
"public\Chats\*" ^
"public\context\*" ^
"public\Group chats\*" ^
"public\Groups\*" ^
"public\instruct\*" ^
"public\KoboldAI Settings\*" ^
"public\movingUI\*" ^
"public\NovelAI Settings\*" ^
"public\OpenAI Settings\*" ^
"public\QuickReplies\*" ^
"public\TextGen Settings\*" ^
"public\themes\*" ^
"public\User Avatars\*" ^
"public\user\*" ^
"public\worlds\*" ^
"public\settings.json" ^
"secrets.json"
REM Get current date and time components
for /f "tokens=1-3 delims=/- " %%d in ("%date%") do (
set "day=%%d"
set "month=%%e"
set "year=%%f"
)
for /f "tokens=1-2 delims=:." %%h in ("%time%") do (
set "hour=%%h"
set "minute=%%i"
)
REM Pad single digits with leading zeros
setlocal enabledelayedexpansion
set "day=0!day!"
set "month=0!month!"
set "hour=0!hour!"
set "minute=0!minute!"
set "formatted_date=%month:~-2%-%day:~-2%-%year%_%hour:~-2%%minute:~-2%"
REM Rename the backup file with the formatted date and time
rename "backups\backup_.7z" "backup_%formatted_date%.7z"
endlocal
echo %green_fg_strong%Backup created successfully!%reset%
pause
endlocal
goto :backup_menu
:restore_backup
REM Restore a backup using 7zip
echo List of available backups:
echo =========================
setlocal enabledelayedexpansion
set "backup_count=0"
for %%F in ("backups\backup_*.7z") do (
set /a "backup_count+=1"
set "backup_files[!backup_count!]=%%~nF"
echo !backup_count!. %cyan_fg_strong%%%~nF%reset%
)
echo =========================
set /p "restore_choice=Enter number of backup to restore: "
if "%restore_choice%" geq "1" (
if "%restore_choice%" leq "%backup_count%" (
set "selected_backup=!backup_files[%restore_choice%]!"
echo Restoring backup !selected_backup!...
REM Extract the contents of the "public" folder directly into the existing "public" folder
7z x "backups\!selected_backup!.7z" -o"temp" -aoa
xcopy /y /e "temp\public\*" "public\"
rmdir /s /q "temp"
echo %green_fg_strong%!selected_backup! restored successfully.%reset%
) else (
color 6
echo WARNING: Invalid backup number. Please insert a valid number.
)
) else (
color 6
echo WARNING: Invalid number. Please insert a valid number.
)
pause
goto :backup_menu
REM Toolbox - Frontend
:toolbox
cls
echo %blue_fg_strong%/ Home / Toolbox%reset%
echo -------------------------------------
echo What would you like to do?
REM color 7
echo 1. Install 7-Zip
echo 2. Install FFmpeg
echo 3. Install Node.js
echo 4. Edit Environment
echo 5. Reinstall SillyTavern
echo 6. Reinstall Extras
echo 7. Back to Home
set /p toolbox_choice=Choose Your Destiny:
REM Toolbox - Backend
if "%toolbox_choice%"=="1" (
call :install7zip
) else if "%toolbox_choice%"=="2" (
call :installffmpeg
) else if "%toolbox_choice%"=="3" (
call :installnodejs
) else if "%toolbox_choice%"=="4" (
call :editenvironment
) else if "%toolbox_choice%"=="5" (
call :reinstallsillytavern
) else if "%toolbox_choice%"=="6" (
call :reinstallextras
) else if "%toolbox_choice%"=="7" (
goto :home
) else (
color 6
echo WARNING: Invalid number. Please insert a valid number.
pause
goto :toolbox
)
:install7zip
echo %blue_fg_strong%[INFO]%reset% Installing 7-Zip...
winget install -e --id 7zip.7zip
rem Get the current PATH value from the registry
for /f "tokens=2*" %%A in ('reg query "HKCU\Environment" /v PATH') do set "current_path=%%B"
rem Check if the paths are already in the current PATH
echo %current_path% | find /i "%zip7_install_path%" > nul
set "zip7_path_exists=%errorlevel%"
rem Append the new paths to the current PATH only if they don't exist
if %zip7_path_exists% neq 0 (
set "new_path=%current_path%;%zip7_install_path%"
echo %green_fg_strong%7-Zip added to PATH.%reset%
) else (
set "new_path=%current_path%"
echo %blue_fg_strong%[INFO] 7-Zip already exists in PATH.%reset%
)
rem Update the PATH value in the registry
reg add "HKCU\Environment" /v PATH /t REG_EXPAND_SZ /d "%new_path%" /f
rem Update the PATH value for the current session
setx PATH "%new_path%"
echo %green_fg_strong%7-Zip is installed. Please restart the Launcher.%reset%
pause
exit
:installffmpeg
REM Check if 7-Zip is installed
7z > nul 2>&1
if %errorlevel% neq 0 (
echo %red_fg_strong%[ERROR] 7z command not found in PATH%reset%
echo %red_bg%Please make sure 7-Zip is installed and added to your PATH.%reset%
echo %blue_bg%To install 7-Zip go to Toolbox%reset%
pause
goto :toolbox
)
echo %blue_fg_strong%[INFO]%reset% Downloading FFmpeg archive...
rem bitsadmin /transfer "ffmpeg" /download /priority FOREGROUND "%ffmpeg_url%" "%ffdownload_path%"
curl -o "%ffdownload_path%" "%ffmpeg_url%"
echo %blue_fg_strong%[INFO]%reset% Creating ffmpeg directory if it doesn't exist...
if not exist "%ffextract_path%" (
mkdir "%ffextract_path%"
)
echo %blue_fg_strong%[INFO]%reset% Extracting FFmpeg archive...
7z x "%ffdownload_path%" -o"%ffextract_path%"
echo %blue_fg_strong%[INFO]%reset% Moving FFmpeg contents to C:\ffmpeg...
for /d %%i in ("%ffextract_path%\ffmpeg-*-full_build") do (
xcopy "%%i\bin" "%ffextract_path%\bin" /E /I /Y
xcopy "%%i\doc" "%ffextract_path%\doc" /E /I /Y
xcopy "%%i\presets" "%ffextract_path%\presets" /E /I /Y
rd "%%i" /S /Q
)
rem Get the current PATH value from the registry
for /f "tokens=2*" %%A in ('reg query "HKCU\Environment" /v PATH') do set "current_path=%%B"
rem Check if the paths are already in the current PATH
echo %current_path% | find /i "%bin_path%" > nul
set "ff_path_exists=%errorlevel%"
rem Append the new paths to the current PATH only if they don't exist
if %ff_path_exists% neq 0 (
set "new_path=%current_path%;%bin_path%"
echo %green_fg_strong%ffmpeg added to PATH.%reset%
) else (
set "new_path=%current_path%"
echo %blue_fg_strong%[INFO] ffmpeg already exists in PATH.%reset%
)
rem Update the PATH value in the registry
reg add "HKCU\Environment" /v PATH /t REG_EXPAND_SZ /d "%new_path%" /f
rem Update the PATH value for the current session
setx PATH "%new_path%" > nul
del "%ffdownload_path%"
echo %green_fg_strong%FFmpeg is installed. Please restart the Launcher.%reset%
pause
exit
:installnodejs
echo %blue_fg_strong%[INFO]%reset% Installing Node.js...
winget install -e --id OpenJS.NodeJS
echo %green_fg_strong%Node.js is installed. Please restart the Launcher.%reset%
pause
exit
:editenvironment
rundll32.exe sysdm.cpl,EditEnvironmentVariables
goto :toolbox
:reinstallsillytavern
setlocal enabledelayedexpansion
chcp 65001 > nul
REM Define the names of items to be excluded
set "script_name=%~nx0"
set "excluded_folders=backups"
set "excluded_files=!script_name!"
REM Confirm with the user before proceeding
echo.
echo %red_bg%╔════ DANGER ZONE ══════════════════════════════════════════════════════════════════════════════╗%reset%
echo %red_bg%║ WARNING: This will delete all data in the current branch except the Backups. ║%reset%
echo %red_bg%║ If you want to keep any data, make sure to create a backup before proceeding. ║%reset%
echo %red_bg%╚═══════════════════════════════════════════════════════════════════════════════════════════════╝%reset%
echo.
echo Are you sure you want to proceed? [Y/N]
set /p "confirmation="
if /i "!confirmation!"=="Y" (
REM Remove non-excluded folders
for /d %%D in (*) do (
set "exclude_folder="
for %%E in (!excluded_folders!) do (
if "%%D"=="%%E" set "exclude_folder=true"
)
if not defined exclude_folder (
rmdir /s /q "%%D" 2>nul
)
)
REM Remove non-excluded files
for %%F in (*) do (
set "exclude_file="
for %%E in (!excluded_files!) do (
if "%%F"=="%%E" set "exclude_file=true"
)
if not defined exclude_file (
del /f /q "%%F" 2>nul
)
)
REM Clone repo into %temp% folder
git clone https://github.com/SillyTavern/SillyTavern.git "%temp%\SillyTavernTemp"
REM Move the contents of the temporary folder to the current directory
xcopy /e /y "%temp%\SillyTavernTemp\*" .
REM Clean up the temporary folder
rmdir /s /q "%temp%\SillyTavernTemp"
echo %green_fg_strong%SillyTavern reinstalled successfully!%reset%
) else (
echo Reinstall canceled.
)
endlocal
pause
goto :toolbox
:reinstallextras
cls
echo %blue_fg_strong%SillyTavern Extras%reset%
echo ---------------------------------------------------------------
echo %blue_fg_strong%[INFO]%reset% Installing SillyTavern Extras...
echo --------------------------------
echo %cyan_fg_strong%This may take a while. Please be patient.%reset%
winget install -e --id Anaconda.Miniconda3
REM Run conda activate from the Miniconda installation
call "%miniconda_path%\Scripts\activate.bat"
REM Create a Conda environment named sillytavernextras
call conda create -n sillytavernextras -y
REM Activate the sillytavernextras environment
call conda activate sillytavernextras
REM Install Python 3.11 and Git in the sillytavernextras environment
call conda install python=3.11 git -y
REM Clone the SillyTavern Extras repository
git clone https://github.com/SillyTavern/SillyTavern-extras
REM Navigate to the SillyTavern-extras directory
cd SillyTavern-extras
REM Install Python dependencies from requirements files
pip install -r requirements-complete.txt
pip install -r requirements-rvc.txt
REM Start SillyTavern Extras with desired configurations
python server.py --coqui-gpu --rvc-save-file --cuda-device=0 --max-content-length=1000 --enable-modules=caption,summarize,classify,rvc,coqui-tts --classification-model=joeddav/distilbert-base-uncased-go-emotions-student --share
echo.
echo %green_fg_strong%SillyTavern Extras have been successfully installed.%reset%
pause
goto :toolbox

View File

@@ -73,6 +73,7 @@
"#@markdown Enables ChromaDB for Infinity Context plugin\n",
"\n",
"import subprocess\n",
"import secrets\n",
"\n",
"# ---\n",
"# SillyTavern extras\n",
@@ -116,6 +117,7 @@
"!npm install -g localtunnel\n",
"!pip install -r requirements-complete.txt\n",
"!pip install tensorflow==2.12\n",
"!pip install colorama\n",
"!pip install Flask-Cors\n",
"!pip install Flask-Compress\n",
"!pip install transformers\n",
@@ -130,6 +132,13 @@
"!wget https://github.com/cloudflare/cloudflared/releases/download/2023.5.0/cloudflared-linux-amd64 -O /tmp/cloudflared-linux-amd64\n",
"!chmod +x /tmp/cloudflared-linux-amd64\n",
"\n",
"# Generate a random API key\n",
"api_key = secrets.token_hex(5)\n",
"\n",
"# Write the API key to api_key.txt\n",
"with open('./api_key.txt', 'w') as f:\n",
" f.write(api_key)\n",
"print(f\"API Key generated: {api_key}\")\n",
"\n",
"cmd = f\"python server.py {' '.join(params)}\"\n",
"print(cmd)\n",

View File

@@ -9,13 +9,28 @@ const enableExtensions = true; //Enables support for TavernAI-extras project
const listen = true; // If true, Can be access from other device or PC. otherwise can be access only from hosting machine.
const allowKeysExposure = false; // If true, private API keys could be fetched to the frontend.
const skipContentCheck = false; // If true, no new default content will be delivered to you.
const thumbnailsQuality = 95; // Quality of thumbnails. 0-100
// If true, Allows insecure settings for listen, whitelist, and authentication.
// Change this setting only on "trusted networks". Do not change this value unless you are aware of the issues that can arise from changing this setting and configuring a insecure setting.
const securityOverride = false;
// Additional settings for extra modules / extensions
const extras = {
// Disables auto-download of models from the HuggingFace Hub.
// You will need to manually download the models and put them into the /cache folder.
disableAutoDownload: false,
// Text classification model for sentiment analysis. HuggingFace ID of a model in ONNX format.
classificationModel: 'Cohee/distilbert-base-uncased-go-emotions-onnx',
// Image captioning model. HuggingFace ID of a model in ONNX format.
captioningModel: 'Xenova/vit-gpt2-image-captioning',
// Feature extraction model. HuggingFace ID of a model in ONNX format.
embeddingModel: 'Xenova/all-mpnet-base-v2',
};
// Request overrides for additional headers
// Format is an array of objects:
// { hosts: [ "<url>" ], headers: { <header>: "<value>" } }
const requestOverrides = [];
module.exports = {
@@ -32,4 +47,6 @@ module.exports = {
securityOverride,
skipContentCheck,
requestOverrides,
thumbnailsQuality,
extras,
};

View File

@@ -75,9 +75,6 @@
"always_force_name2": true,
"user_prompt_bias": "",
"show_user_prompt_bias": true,
"multigen": false,
"multigen_first_chunk": 50,
"multigen_next_chunks": 30,
"markdown_escape_strings": "",
"fast_ui_mode": false,
"avatar_style": 0,
@@ -167,7 +164,6 @@
"custom_stopping_strings_macro": true,
"fuzzy_search": true,
"encode_tags": false,
"lazy_load": 100,
"ui_mode": 1
},
"extension_settings": {
@@ -410,7 +406,6 @@
"tfs": 1,
"rep_pen_slope": 0,
"single_line": false,
"use_stop_sequence": false,
"streaming_kobold": false,
"sampler_order": [
6,
@@ -420,7 +415,12 @@
3,
4,
5
]
],
"mirostat": 0,
"mirostat_tau": 5,
"mirostat_eta": 0.1,
"use_default_badwordsids": false,
"grammar": ""
},
"oai_settings": {
"preset_settings_openai": "Default",

View File

@@ -9,4 +9,5 @@ services:
- "8000:8000"
volumes:
- "./config:/home/node/app/config"
- "./user:/home/node/app/public/user"
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"
IFS="," RESOURCES="characters,chats,groups,group chats,User Avatars,worlds,OpenAI Settings,NovelAI Settings,KoboldAI Settings,TextGen Settings"
for R in $RESOURCES; do
if [ ! -e "config/$R" ]; then
echo "Resource not found, copying from defaults: $R"

1197
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,29 +9,27 @@
"cors": "^2.8.5",
"csrf-csrf": "^2.2.3",
"device-detector-js": "^3.0.3",
"exifreader": "^4.12.0",
"express": "^4.18.2",
"google-translate-api-browser": "^3.0.1",
"bing-translate-api": "^2.9.1",
"gpt3-tokenizer": "^1.1.5",
"ip-matching": "^2.1.2",
"ipaddr.js": "^2.0.1",
"jimp": "^0.22.7",
"jquery": "^3.6.4",
"jimp": "^0.22.10",
"json5": "^2.2.3",
"lodash": "^4.17.21",
"mime-types": "^2.1.35",
"multer": "^1.4.5-lts.1",
"node-fetch": "^2.6.11",
"open": "^8.4.2",
"piexifjs": "^1.0.6",
"png-chunk-text": "^1.0.0",
"png-chunks-encode": "^1.0.0",
"png-chunks-extract": "^1.0.0",
"response-time": "^2.3.2",
"sanitize-filename": "^1.6.3",
"sillytavern-transformers": "^2.7.3",
"simple-git": "^3.19.1",
"uniqolor": "^1.1.0",
"webp-converter": "2.3.2",
"vectra": "^0.2.2",
"write-file-atomic": "^5.0.1",
"ws": "^8.13.0",
"yargs": "^17.7.1",
@@ -49,11 +47,12 @@
"type": "git",
"url": "https://github.com/SillyTavern/SillyTavern.git"
},
"version": "1.10.1",
"version": "1.10.5",
"scripts": {
"start": "node server.js",
"start-multi": "node server.js --disableCsrf",
"pkg": "pkg --compress Gzip --no-bytecode --public ."
"pkg": "pkg --compress Gzip --no-bytecode --public .",
"postinstall": "node post-install.js"
},
"bin": {
"sillytavern": "./server.js"
@@ -78,6 +77,7 @@
]
},
"devDependencies": {
"jquery": "^3.6.4",
"pkg": "^5.8.1",
"pkg-fetch": "^3.5.2"
}

81
post-install.js Normal file
View File

@@ -0,0 +1,81 @@
/**
* Scripts to be done before starting the server for the first time.
*/
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
/**
* Creates the default config files if they don't exist yet.
*/
function createDefaultFiles() {
const files = {
settings: './public/settings.json',
bg_load: './public/css/bg_load.css',
config: './config.conf',
};
for (const file of Object.values(files)) {
try {
if (!fs.existsSync(file)) {
const defaultFilePath = path.join('./default', path.parse(file).base);
fs.copyFileSync(defaultFilePath, file);
console.log(`Created default file: ${file}`);
}
} catch (error) {
console.error(`FATAL: Could not write default file: ${file}`, error);
}
}
}
/**
* Returns the MD5 hash of the given data.
* @param {Buffer} data Input data
* @returns {string} MD5 hash of the input data
*/
function getMd5Hash(data) {
return crypto
.createHash('md5')
.update(data)
.digest('hex');
}
/**
* Copies the WASM binaries from the sillytavern-transformers package to the dist folder.
*/
function copyWasmFiles() {
if (!fs.existsSync('./dist')) {
fs.mkdirSync('./dist');
}
const listDir = fs.readdirSync('./node_modules/sillytavern-transformers/dist');
for (const file of listDir) {
if (file.endsWith('.wasm')) {
const sourcePath = `./node_modules/sillytavern-transformers/dist/${file}`;
const targetPath = `./dist/${file}`;
// Don't copy if the file already exists and is the same checksum
if (fs.existsSync(targetPath)) {
const sourceChecksum = getMd5Hash(fs.readFileSync(sourcePath));
const targetChecksum = getMd5Hash(fs.readFileSync(targetPath));
if (sourceChecksum === targetChecksum) {
continue;
}
}
fs.copyFileSync(sourcePath, targetPath);
console.log(`${file} successfully copied to ./dist/${file}`);
}
}
}
try {
// 1. Create default config files
createDefaultFiles();
// 2. Copy transformers WASM binaries from node_modules
copyWasmFiles();
} catch (error) {
console.error(error);
}

View File

@@ -1,12 +1,12 @@
{
"temp": 1.15,
"top_k": 0,
"top_p": 0.95,
"top_a": 0,
"typical": 1,
"tfs": 0.8,
"rep_pen": 1.05,
"rep_pen_range": 2048,
"top_p": 0.95,
"top_a": 0,
"top_k": 0,
"typical": 1,
"tfs": 0.8,
"rep_pen_slope": 7,
"sampler_order": [
6,
@@ -16,5 +16,9 @@
5,
1,
4
]
],
"mirostat": 0,
"mirostat_tau": 5,
"mirostat_eta": 0.1,
"grammar": ""
}

View File

@@ -1,12 +1,12 @@
{
"temp": 0.59,
"top_k": 0,
"top_p": 1,
"top_a": 0,
"typical": 1,
"tfs": 0.87,
"rep_pen": 1.1,
"rep_pen_range": 2048,
"top_p": 1,
"top_a": 0,
"top_k": 0,
"typical": 1,
"tfs": 0.87,
"rep_pen_slope": 0.3,
"sampler_order": [
6,
@@ -16,5 +16,9 @@
3,
1,
4
]
],
"mirostat": 0,
"mirostat_tau": 5,
"mirostat_eta": 0.1,
"grammar": ""
}

View File

@@ -1,12 +1,12 @@
{
"temp": 0.8,
"top_k": 100,
"top_p": 0.9,
"top_a": 0,
"typical": 1,
"tfs": 1,
"rep_pen": 1.15,
"rep_pen_range": 2048,
"top_p": 0.9,
"top_a": 0,
"top_k": 100,
"typical": 1,
"tfs": 1,
"rep_pen_slope": 3.4,
"sampler_order": [
6,
@@ -16,5 +16,9 @@
3,
1,
4
]
],
"mirostat": 0,
"mirostat_tau": 5,
"mirostat_eta": 0.1,
"grammar": ""
}

View File

@@ -1,12 +1,12 @@
{
"temp": 0.51,
"top_p": 1,
"top_k": 0,
"tfs": 0.99,
"top_a": 0,
"typical": 1,
"rep_pen": 1.2,
"rep_pen_range": 2048,
"top_p": 1,
"top_a": 0,
"top_k": 0,
"typical": 1,
"tfs": 0.99,
"rep_pen_slope": 0,
"sampler_order": [
6,
@@ -16,5 +16,9 @@
3,
1,
4
]
],
"mirostat": 0,
"mirostat_tau": 5,
"mirostat_eta": 0.1,
"grammar": ""
}

View File

@@ -8,7 +8,6 @@
"typical": 1,
"tfs": 1,
"rep_pen_slope": 0,
"single_line": false,
"sampler_order": [
6,
0,
@@ -17,5 +16,9 @@
4,
2,
5
]
}
],
"mirostat": 0,
"mirostat_tau": 5,
"mirostat_eta": 0.1,
"grammar": ""
}

View File

@@ -1,12 +1,12 @@
{
"temp": 0.63,
"top_k": 0,
"top_p": 0.98,
"top_a": 0,
"typical": 1,
"tfs": 0.98,
"rep_pen": 1.05,
"rep_pen_range": 2048,
"top_p": 0.98,
"top_a": 0,
"top_k": 0,
"typical": 1,
"tfs": 0.98,
"rep_pen_slope": 0.1,
"sampler_order": [
6,
@@ -16,5 +16,9 @@
5,
1,
4
]
],
"mirostat": 0,
"mirostat_tau": 5,
"mirostat_eta": 0.1,
"grammar": ""
}

View File

@@ -1,12 +1,12 @@
{
"temp": 0.7,
"top_k": 0,
"top_p": 0.5,
"top_a": 0.75,
"typical": 0.19,
"tfs": 0.97,
"rep_pen": 1.1,
"rep_pen_range": 1024,
"top_p": 0.5,
"top_a": 0.75,
"top_k": 0,
"typical": 0.19,
"tfs": 0.97,
"rep_pen_slope": 0.7,
"sampler_order": [
6,
@@ -16,5 +16,9 @@
2,
1,
0
]
],
"mirostat": 0,
"mirostat_tau": 5,
"mirostat_eta": 0.1,
"grammar": ""
}

View File

@@ -1,12 +1,12 @@
{
"temp": 0.7,
"top_k": 0,
"top_p": 1,
"top_a": 0,
"typical": 1,
"tfs": 0.9,
"rep_pen": 1.1,
"rep_pen_range": 1024,
"top_p": 1,
"top_a": 0,
"top_k": 0,
"typical": 1,
"tfs": 0.9,
"rep_pen_slope": 0.7,
"sampler_order": [
6,
@@ -16,5 +16,9 @@
3,
4,
5
]
],
"mirostat": 0,
"mirostat_tau": 5,
"mirostat_eta": 0.1,
"grammar": ""
}

View File

@@ -1,12 +1,12 @@
{
"temp": 0.66,
"top_k": 0,
"top_p": 1,
"top_a": 0.96,
"typical": 0.6,
"tfs": 1,
"rep_pen": 1.1,
"rep_pen_range": 1024,
"top_p": 1,
"top_a": 0.96,
"top_k": 0,
"typical": 0.6,
"tfs": 1,
"rep_pen_slope": 0.7,
"sampler_order": [
6,
@@ -16,5 +16,9 @@
0,
2,
3
]
],
"mirostat": 0,
"mirostat_tau": 5,
"mirostat_eta": 0.1,
"grammar": ""
}

View File

@@ -1,12 +1,12 @@
{
"temp": 0.94,
"top_k": 12,
"top_p": 1,
"top_a": 0,
"typical": 1,
"tfs": 0.94,
"rep_pen": 1.05,
"rep_pen_range": 2048,
"top_p": 1,
"top_a": 0,
"top_k": 12,
"typical": 1,
"tfs": 0.94,
"rep_pen_slope": 0.2,
"sampler_order": [
6,
@@ -16,5 +16,9 @@
3,
1,
4
]
],
"mirostat": 0,
"mirostat_tau": 5,
"mirostat_eta": 0.1,
"grammar": ""
}

View File

@@ -1,12 +1,12 @@
{
"temp": 1.5,
"top_k": 85,
"top_p": 0.24,
"top_a": 0,
"typical": 1,
"tfs": 1,
"rep_pen": 1.1,
"rep_pen_range": 2048,
"top_p": 0.24,
"top_a": 0,
"top_k": 85,
"typical": 1,
"tfs": 1,
"rep_pen_slope": 0,
"sampler_order": [
6,
@@ -16,5 +16,9 @@
3,
1,
4
]
],
"mirostat": 0,
"mirostat_tau": 5,
"mirostat_eta": 0.1,
"grammar": ""
}

View File

@@ -1,12 +1,12 @@
{
"temp": 1.05,
"top_k": 0,
"top_p": 0.95,
"top_a": 0,
"typical": 1,
"tfs": 1,
"rep_pen": 1.1,
"rep_pen_range": 1024,
"top_p": 0.95,
"top_a": 0,
"top_k": 0,
"typical": 1,
"tfs": 1,
"rep_pen_slope": 0.7,
"sampler_order": [
6,
@@ -16,5 +16,9 @@
3,
4,
5
]
],
"mirostat": 0,
"mirostat_tau": 5,
"mirostat_eta": 0.1,
"grammar": ""
}

View File

@@ -0,0 +1,24 @@
{
"temp": 1.06,
"rep_pen": 1,
"rep_pen_range": 0,
"top_p": 1,
"top_a": 0,
"top_k": 0,
"typical": 1,
"tfs": 1,
"rep_pen_slope": 0.9,
"sampler_order": [
6,
0,
1,
3,
4,
2,
5
],
"mirostat": 2,
"mirostat_tau": 9.61,
"mirostat_eta": 1,
"grammar": ""
}

View File

@@ -0,0 +1,24 @@
{
"temp": 1.17,
"rep_pen": 1,
"rep_pen_range": 0,
"top_p": 1,
"top_a": 0,
"top_k": 0,
"typical": 1,
"tfs": 1,
"rep_pen_slope": 0.9,
"sampler_order": [
6,
0,
1,
3,
4,
2,
5
],
"mirostat": 2,
"mirostat_tau": 9.91,
"mirostat_eta": 1,
"grammar": ""
}

View File

@@ -0,0 +1,24 @@
{
"temp": 1.17,
"rep_pen": 1,
"rep_pen_range": 0,
"top_p": 1,
"top_a": 0,
"top_k": 0,
"typical": 1,
"tfs": 1,
"rep_pen_slope": 0.9,
"sampler_order": [
6,
0,
1,
3,
4,
2,
5
],
"mirostat": 2,
"mirostat_tau": 9.62,
"mirostat_eta": 1,
"grammar": ""
}

View File

@@ -1,12 +1,12 @@
{
"temp": 1.07,
"top_k": 100,
"top_p": 1,
"top_a": 0,
"typical": 1,
"tfs": 0.93,
"rep_pen": 1.05,
"rep_pen_range": 404,
"top_p": 1,
"top_a": 0,
"top_k": 100,
"typical": 1,
"tfs": 0.93,
"rep_pen_slope": 0.8,
"sampler_order": [
6,
@@ -16,5 +16,9 @@
2,
1,
4
]
],
"mirostat": 0,
"mirostat_tau": 5,
"mirostat_eta": 0.1,
"grammar": ""
}

View File

@@ -1,12 +1,12 @@
{
"temp": 0.44,
"top_k": 0,
"top_p": 1,
"top_a": 0,
"typical": 1,
"tfs": 0.9,
"rep_pen": 1.15,
"rep_pen_range": 2048,
"top_p": 1,
"top_a": 0,
"top_k": 0,
"typical": 1,
"tfs": 0.9,
"rep_pen_slope": 6.8,
"sampler_order": [
6,
@@ -16,5 +16,9 @@
3,
1,
4
]
],
"mirostat": 0,
"mirostat_tau": 5,
"mirostat_eta": 0.1,
"grammar": ""
}

View File

@@ -1,12 +1,12 @@
{
"temp": 1.35,
"top_k": 0,
"top_p": 1,
"top_a": 0,
"typical": 1,
"tfs": 0.69,
"rep_pen": 1.15,
"rep_pen_range": 2048,
"top_p": 1,
"top_a": 0,
"top_k": 0,
"typical": 1,
"tfs": 0.69,
"rep_pen_slope": 0.1,
"sampler_order": [
6,
@@ -16,5 +16,9 @@
0,
1,
4
]
],
"mirostat": 0,
"mirostat_tau": 5,
"mirostat_eta": 0.1,
"grammar": ""
}

View File

@@ -1,12 +1,12 @@
{
"temp": 1,
"top_k": 0,
"top_p": 0.95,
"top_a": 0,
"typical": 1,
"tfs": 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,
"sampler_order": [
6,
@@ -16,5 +16,9 @@
3,
4,
5
]
}
],
"mirostat": 0,
"mirostat_tau": 5,
"mirostat_eta": 0.1,
"grammar": ""
}

View File

@@ -8,7 +8,6 @@
"typical": 1,
"tfs": 1,
"rep_pen_slope": 0,
"single_line": false,
"sampler_order": [
6,
0,
@@ -17,5 +16,9 @@
4,
2,
5
]
}
],
"mirostat": 0,
"mirostat_tau": 5,
"mirostat_eta": 0.1,
"grammar": ""
}

View File

@@ -1,12 +1,12 @@
{
"temp": 0.72,
"tfs": 1,
"top_a": 0,
"top_k": 0,
"top_p": 0.73,
"typical": 1,
"rep_pen": 1.1,
"rep_pen_range": 2048,
"top_p": 0.73,
"top_a": 0,
"top_k": 0,
"typical": 1,
"tfs": 1,
"rep_pen_slope": 0.2,
"sampler_order": [
6,
@@ -16,5 +16,9 @@
3,
1,
4
]
],
"mirostat": 0,
"mirostat_tau": 5,
"mirostat_eta": 0.1,
"grammar": ""
}

View File

@@ -8,7 +8,6 @@
"typical": 1,
"tfs": 0.95,
"rep_pen_slope": 0,
"single_line": false,
"sampler_order": [
6,
0,
@@ -17,5 +16,9 @@
4,
2,
5
]
}
],
"mirostat": 0,
"mirostat_tau": 5,
"mirostat_eta": 0.1,
"grammar": ""
}

View File

@@ -8,7 +8,6 @@
"typical": 1,
"tfs": 1,
"rep_pen_slope": 0,
"single_line": false,
"sampler_order": [
6,
0,
@@ -17,5 +16,9 @@
4,
2,
5
]
}
],
"mirostat": 0,
"mirostat_tau": 5,
"mirostat_eta": 0.1,
"grammar": ""
}

View File

@@ -8,7 +8,6 @@
"typical": 1,
"tfs": 1,
"rep_pen_slope": 0,
"single_line": false,
"sampler_order": [
6,
0,
@@ -17,5 +16,9 @@
4,
2,
5
]
}
],
"mirostat": 0,
"mirostat_tau": 5,
"mirostat_eta": 0.1,
"grammar": ""
}

View File

@@ -0,0 +1,24 @@
{
"temp": 1.06,
"top_p": 1,
"top_k": 0,
"typical_p": 1,
"top_a": 0,
"tfs": 1,
"epsilon_cutoff": 0,
"eta_cutoff": 0,
"rep_pen": 1,
"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": 2,
"mirostat_tau": 9.61,
"mirostat_eta": 1,
"rep_pen_size": 0
}

View File

@@ -0,0 +1,24 @@
{
"temp": 1.17,
"top_p": 1,
"top_k": 0,
"typical_p": 1,
"top_a": 0,
"tfs": 1,
"epsilon_cutoff": 0,
"eta_cutoff": 0,
"rep_pen": 1,
"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": 2,
"mirostat_tau": 9.91,
"mirostat_eta": 1,
"rep_pen_size": 0
}

View File

@@ -0,0 +1,24 @@
{
"temp": 1.17,
"top_p": 1,
"top_k": 0,
"typical_p": 1,
"top_a": 0,
"tfs": 1,
"epsilon_cutoff": 0,
"eta_cutoff": 0,
"rep_pen": 1,
"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": 2,
"mirostat_tau": 9.62,
"mirostat_eta": 1,
"rep_pen_size": 0
}

View File

@@ -0,0 +1,6 @@
{
"story_string": "{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{personality}}\n{{/if}}{{#if scenario}}{{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}",
"chat_start": "",
"example_separator": "",
"name": "Adventure"
}

View File

@@ -0,0 +1,6 @@
{
"story_string": "<|im_start|>system\n{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}<|im_end|>",
"chat_start": "",
"example_separator": "",
"name": "ChatML"
}

View File

@@ -0,0 +1,6 @@
{
"story_string": "### Instruction:\nWrite {{char}}'s next reply in this roleplay with {{user}}. Use the provided character sheet and example dialogue for formatting direction and character speech patterns.\n\n{{#if system}}{{system}}\n\n{{/if}}### Character Sheet:\n{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}",
"chat_start": "### START ROLEPLAY:",
"example_separator": "### Example:",
"name": "Libra-32B"
}

View File

@@ -0,0 +1,6 @@
{
"story_string": "{{system}}\n{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{char}}'s description:{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality:{{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{user}}'s persona: {{persona}}\n{{/if}}",
"chat_start": "This is the history of the roleplay:",
"example_separator": "Example of an interaction:",
"name": "Lightning 1.1"
}

View File

@@ -0,0 +1,6 @@
{
"story_string": "[INST] {{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}[/INST]",
"chat_start": "",
"example_separator": "Examples:",
"name": "Mistral"
}

View File

@@ -0,0 +1,6 @@
{
"story_string": "{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Circumstances and context of the dialogue: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}",
"chat_start": "\nThen the roleplay chat between {{user}} and {{char}} begins.\n",
"example_separator": "This is how {{char}} should talk",
"name": "OldDefault"
}

View File

@@ -1,6 +1,6 @@
{
"name": "Pygmalion",
"story_string": "{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{{char}}}'s Persona: {{description}}\n{{/if}}{{#if personality}}Personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}",
"chat_start": "<START>",
"example_separator": "<START>"
"story_string": "{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}",
"chat_start": "",
"example_separator": ""
}

View File

@@ -0,0 +1,6 @@
{
"story_string": "{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{personality}}\n{{/if}}{{#if scenario}}{{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}",
"chat_start": "",
"example_separator": "",
"name": "Story"
}

View File

@@ -93,4 +93,118 @@ input.extension_missing[type="checkbox"] {
.update-button {
margin-right: 10px;
display: inline-flex;
}
}
/* Fixes order of settings for extensions */
#extensions_settings,
#extensions_settings2 {
display: flex;
flex-direction: column;
}
/** LEFT COLUMN **/
#extensions_settings>.expression_settings {
order: 1;
}
#extensions_settings>.background_settings {
order: 2;
}
#extensions_settings>.sd_settings {
order: 3;
}
#extensions_settings>#tts_settings {
order: 4;
}
#extensions_settings>#rvc_settings {
order: 5;
}
#extensions_settings>.objective-settings {
order: 6;
}
#extensions_settings>#speech_recognition_settings {
order: 7;
}
#extensions_settings>#audio_settings {
order: 8;
}
#extensions_settings>#assets_ui {
order: 9;
}
/** RIGHT COLUMN **/
#extensions_settings2>.translation_settings {
order: 1;
}
#extensions_settings2>.caption_settings {
order: 2;
}
#extensions_settings2>.quickReplySettings {
order: 3;
}
#extensions_settings2>.idle-settings {
order: 4;
}
#extensions_settings2>#memory_settings {
order: 5;
}
#extensions_settings2>.hypebot_settings {
order: 6;
}
#extensions_settings2>.regex_settings {
order: 7;
}
#extensions_settings2>.vectors_settings {
order: 8;
}
#extensions_settings2>.chromadb_settings {
order: 9;
}
#extensions_settings2>.randomizer_settings {
order: 10;
}
/** WAND MENU **/
#extensionsMenu>#ttsExtensionMenuItem {
order: 1;
}
#extensionsMenu>#sd_gen {
order: 2;
}
#extensionsMenu>#send_picture {
order: 3;
}
#extensionsMenu>#token_counter {
order: 4;
}
#extensionsMenu>#objective-task-manual-check-menu-item {
order: 5;
}
#extensionsMenu>#roll_dice {
order: 6;
}
#extensionsMenu>#translate_chat {
order: 7;
}

View File

@@ -35,7 +35,7 @@
max-width: 90svw;
}
.world_entry_thin_controls,
/* .world_entry_thin_controls, */
#persona-management-block,
#character_popup .flex-container {
flex-direction: column;
@@ -63,6 +63,15 @@
display: none;
}
.world_entry .inline-drawer-toggle {
padding-bottom: 5px;
}
#worldInfoScanningCheckboxes {
flex-flow: row;
flex-wrap: wrap;
}
body {
touch-action: none;
overflow: hidden;
@@ -70,6 +79,10 @@
}
.world_entry_form_control {
/* width: 100%; */
}
.drawer-content {
min-width: unset;
width: 100%;
@@ -78,7 +91,7 @@
position: fixed;
left: 0;
top: 5px;
border: 1px solid var(--grey30);
border: 1px solid var(--SmartThemeBorderColor);
}
#select_chat_popup {
@@ -91,7 +104,6 @@
#top-settings-holder,
#top-bar {
position: fixed;
padding-top: 3px;
width: 100vw;
width: 100svw;
}
@@ -114,18 +126,32 @@
/* ,
#world_popup */
{
max-height: calc(100vh - 36px);
max-height: calc(100svh - 36px);
/*max-height: calc(100vh - 36px);
max-height: calc(100svh - 36px);*/
width: 100% !important;
margin: 0 auto;
max-width: 100%;
left: 0 !important;
resize: none !important;
top: 36px;
top: var(--topBarBlockSize);
}
.wi-settings {
flex-direction: column;
gap: 5px !important;
}
.WIEntryTitleAndStatus,
.WIEntryHeaderControls {
width: 100%;
}
#WIEntryHeaderTitlesPC {
display: none;
}
.WIEntryHeaderTitleMobile {
display: block !important;
}
#character_popup,
@@ -135,15 +161,15 @@
#character_popup,
#send_form {
border: 1px solid var(--grey30);
border: 1px solid var(--SmartThemeBorderColor);
backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2));
max-width: 100dvw;
}
#chat {
border-left: 1px solid var(--grey30);
border-right: 1px solid var(--grey30);
border-bottom: 1px solid var(--grey30);
border-left: 1px solid var(--SmartThemeBorderColor);
border-right: 1px solid var(--SmartThemeBorderColor);
border-bottom: 1px solid var(--SmartThemeBorderColor);
align-items: start;
align-content: start;
overflow-y: auto;
@@ -161,6 +187,7 @@
}
#showRawPrompt,
#copyPromptToClipboard,
#groupCurrentMemberPopoutButton {
display: none;
}
@@ -175,11 +202,11 @@
width: 100% !important;
max-width: 100% !important;
overflow-y: hidden;
border-left: 1px solid var(--grey30);
border-right: 1px solid var(--grey30);
border-bottom: 1px solid var(--grey30);
border-left: 1px solid var(--SmartThemeBorderColor);
border-right: 1px solid var(--SmartThemeBorderColor);
border-bottom: 1px solid var(--SmartThemeBorderColor);
border-radius: 0 0 20px 20px;
top: 36px !important;
top: var(--topBarBlockSize) !important;
left: 0 !important;
backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2));
}
@@ -271,6 +298,7 @@
}
@media screen and (min-width: 1001px) {
#PygOverrides,
#ContextFormatting,
#UI-Theme-Block,
@@ -310,8 +338,7 @@
min-width: 100px;
min-height: 100px;
max-height: 50vh;
max-width: 50vh;
width: 50vw;
max-width: 90vw;
position: absolute;
padding: 0;
filter: drop-shadow(2px 2px 2px #51515199);
@@ -417,4 +444,4 @@
#horde_model {
height: unset;
}
}
}

View File

@@ -13,7 +13,7 @@
grid-column-end: 4;
width: 100%;
margin: 0.5em 0;
background-image: linear-gradient(90deg, var(--transparent), var(--white30a), var(--transparent));
background-image: linear-gradient(90deg, var(--transparent), var(--SmartThemeBorderColor), var(--transparent));
min-height: 1px;
}
@@ -54,7 +54,7 @@
#completion_prompt_manager #completion_prompt_manager_list li.completion_prompt_manager_prompt {
align-items: center;
padding: 0.5em;
border: 1px solid var(--white30a);
border: 1px solid var(--SmartThemeBorderColor);
}
#completion_prompt_manager #completion_prompt_manager_list li.completion_prompt_manager_prompt .prompt_manager_prompt_controls {
@@ -109,7 +109,7 @@
#completion_prompt_manager_popup .completion_prompt_manager_prompt {
margin: 1em 0;
padding: 0.5em;
border: 1px solid var(--white30a);
border: 1px solid var(--SmartThemeBorderColor);
}
#completion_prompt_manager_popup .completion_prompt_manager_popup_header {
@@ -265,7 +265,7 @@
top: var(--topBarBlockSize);
box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
padding: 1em;
border: 1px solid #333333;
border: 1px solid var(--SmartThemeBorderColor);
flex-direction: column;
z-index: 3010 !important;
border-radius: 0 0 20px 20px;

View File

@@ -14,7 +14,8 @@
margin-top: auto;
margin-bottom: auto;
color: rgb(188, 193, 200, 1);
border: 1px solid #333;
border: 1px solid var(--SmartThemeBorderColor);
;
background-color: rgba(0, 0, 0, 0.3);
padding: 6px;
border-radius: 10px;
@@ -61,7 +62,8 @@
#rm_group_add_members {
margin-top: 0.25rem;
margin-bottom: 0.5rem;
border: 1px solid grey;
border: 1px solid var(--SmartThemeBorderColor);
;
border-radius: 10px;
background-color: var(--black30a);
}

View File

@@ -6,7 +6,7 @@
/* Customize the dropdown */
.select2-dropdown {
background-color: var(--SmartThemeBlurTintColor);
border: 1px solid var(--white30a) !important;
border: 1px solid var(--SmartThemeBorderColor) !important;
border-radius: 10px;
box-shadow: 0 0 5px black;
text-shadow: 0px 0px calc(var(--shadowWidth) * 1px) var(--SmartThemeShadowColor);
@@ -19,11 +19,24 @@
color: var(--SmartThemeBodyColor);
}
.select2-container .select2-search__field {
opacity: 0.8;
}
.select2-container .select2-selection--single .select2-selection__rendered {
color: var(--SmartThemeBodyColor);
line-height: revert;
padding-left: unset;
}
.select2-container .select2-results>.select2-results__options {
max-height: 300px;
}
.select2-container .select2-selection--multiple .select2-selection__choice__remove {
padding: revert;
border-right: 1px solid var(--white30a);
border-right: 1px solid var(--SmartThemeBorderColor);
font-size: 1.1em;
}
.select2-container .select2-selection--multiple .select2-selection__choice__display {
@@ -34,7 +47,7 @@
.select2-search__field {
background-color: var(--black30a);
color: var(--SmartThemeBodyColor);
border: 1px solid var(--white30a);
border: 1px solid var(--SmartThemeBorderColor);
border-radius: 7px;
font-family: "Noto Sans", "Noto Color Emoji", sans-serif;
padding: 3px 5px;
@@ -58,27 +71,30 @@
background-color: var(--SmartThemeBodyColor);
}
.select2-container .select2-selection--multiple {
.select2-container .select2-selection--multiple,
.select2-container .select2-selection--single {
background-color: var(--black30a);
color: var(--SmartThemeBodyColor);
border: 1px solid var(--white30a);
border: 1px solid var(--SmartThemeBorderColor);
border-radius: 7px;
font-family: "Noto Sans", "Noto Color Emoji", sans-serif;
padding: 3px 5px;
}
.select2-container.select2-container--focus .select2-selection--multiple {
border: 1px solid var(--white30a);
.select2-container.select2-container--focus .select2-selection--multiple,
.select2-container.select2-container--focus .select2-selection--single {
border: 1px solid var(--SmartThemeBorderColor);
}
.select2-container .select2-selection--multiple .select2-selection__choice {
.select2-container .select2-selection--multiple .select2-selection__choice,
.select2-container .select2-selection--single .select2-selection__choice {
border-radius: 5px;
border-style: solid;
border-width: 1px;
box-sizing: border-box;
color: var(--SmartThemeBodyColor);
background-color: var(--black30a);
border-color: var(--white30a);
border-color: var(--SmartThemeBorderColor);
font-size: calc(var(--mainFontSize) - 5%);
text-shadow: none !important;
}
@@ -114,12 +130,13 @@
margin-top: -7px;
width: 14px;
height: 14px;
border: 1px solid var(--white30a);
border: 1px solid var(--SmartThemeBorderColor);
background-color: var(--SmartThemeBlurTintColor);
border-radius: 2px;
}
.select2-container .select2-selection--multiple .select2-selection__choice__remove {
.select2-container .select2-selection--multiple .select2-selection__choice__remove,
.select2-container .select2-selection--single .select2-selection__choice__remove {
color: var(--SmartThemeBodyColor);
}

View File

@@ -6,6 +6,10 @@
color: var(--fullred);
}
.m-t-0 {
margin-top: 0;
}
.m-t-1 {
margin-top: 1em;
}
@@ -99,6 +103,10 @@
align-self: start;
}
.gap3px {
gap: 3px !important;
}
.gap5px {
gap: 5px !important;
}
@@ -120,6 +128,10 @@
max-width: 100px;
}
.width100px {
width: 100px;
}
.widthUnset {
width: unset;
}
@@ -147,6 +159,10 @@
font-weight: 600;
}
.textAlignCenter {
text-align: center;
}
.margin-right-10px {
margin-right: 10px;
}
@@ -230,6 +246,10 @@
overflow: hidden;
}
.padding0 {
padding: 0;
}
.padding5 {
padding: 5px;
}
@@ -262,6 +282,10 @@
flex-flow: column;
}
.flexFlowRow {
flex-flow: row;
}
.wideMinContent {
width: min-content;
}
@@ -362,6 +386,11 @@
cursor: pointer;
}
input:disabled,
textarea:disabled {
cursor: not-allowed;
}
.debug-red {
border: 1px solid red !important;
}
@@ -390,6 +419,11 @@
font-size: calc(var(--mainFontSize) * 0.6) !important;
}
.paddingBottom5px {
padding: unset;
padding-bottom: 5px;
}
.paddingTopBot5 {
padding: 5px 0;
}
@@ -417,4 +451,4 @@
.opacity1 {
opacity: 1 !important;
}
}

View File

@@ -109,6 +109,7 @@
overflow: hidden;
text-align: left;
white-space: nowrap;
text-shadow: none !important;
}
.tags_inline .tag {
@@ -128,6 +129,13 @@
cursor: pointer;
opacity: 0.6;
filter: brightness(0.8);
transition: opacity 200ms;
}
.rm_tag_filter .tag:hover {
opacity: 1;
filter: brightness(1);
}
.tags_view,
@@ -163,4 +171,4 @@
-1px 1px 0px black,
1px -1px 0px black;
opacity: 1;
}
}

View File

@@ -117,7 +117,7 @@ body.big-avatars .avatar img {
height: 90px;
object-fit: cover;
object-position: center;
border: 1px solid var(--black30a);
border: 1px solid var(--SmartThemeBorderColor);
border-radius: 10px;
}
@@ -191,7 +191,7 @@ body.bubblechat .mes {
border-radius: 10px;
background-color: var(--SmartThemeBotMesBlurTintColor);
margin-bottom: 5px;
border: 1px solid var(--white30a);
border: 1px solid var(--SmartThemeBorderColor);
}
body.bubblechat .mes[is_user="true"] {
@@ -257,31 +257,16 @@ body.no-blur #bg_custom {
}
body:not(.bubblechat).no-blur #chat,
body.no-blur #top-bar,
body.no-blur #send_form {
background-color: var(--SmartThemeBlurTintColor) !important;
}
body.no-blur #options,
body.no-blur .ui-widget-content,
body.no-blur #floatingPrompt,
body.no-blur #extensionsMenu,
body.no-blur .list-group,
body.no-blur #character_popup,
body.no-blur #world_popup,
body.no-blur #dialogue_popup,
body.no-blur #select_chat_popup,
body.no-blur .drawer-content,
body.no-blur .select2-results__options {
background-color: black !important;
}
/* wAIfu mode*/
body.waifuMode #top-bar {
border-radius: 0 0 20px 20px;
border: 1px solid var(--grey30a);
border: 1px solid var(--SmartThemeBorderColor);
}
body.waifuMode #sheld {
@@ -292,7 +277,7 @@ body.waifuMode #sheld {
}
body.waifuMode #chat {
border-top: 1px solid var(--grey30a);
border-top: 1px solid var(--SmartThemeBorderColor);
border-radius: 20px 20px 0 0;
}
@@ -353,6 +338,7 @@ body.movingUI #groupMemberListPopout {
height: 120px;
margin-top: 0;
top: 50px;
justify-content: center;
}
/*No Text Shadows Mode*/
@@ -360,3 +346,11 @@ body.movingUI #groupMemberListPopout {
body.noShadows * {
text-shadow: none !important;
}
body.expandMessageActions .mes .mes_buttons .extraMesButtons {
display: inherit !important;
}
body.expandMessageActions .mes .mes_buttons .extraMesButtonsHint {
display: none !important;
}

View File

@@ -101,7 +101,7 @@
height: auto;
margin-top: 0;
margin-bottom: 0;
min-height: 32px;
min-height: calc(var(--mainFontSize) + 13px);
}
.delete_entry_button {
@@ -157,6 +157,37 @@
width: 10em;
}
#world_info_search {
width: 10em;
#world_info_search,
#world_info_sort_order {
width: 7em;
}
.wi-card-entry {
border: 1px solid;
border-color: var(--SmartThemeBorderColor);
border-radius: 10px;
padding: 0 5px;
margin-bottom: 1px;
}
.world_entry {
transition: opacity 500ms;
}
.disabledWIEntry {
opacity: 0.4;
filter: grayscale(1);
}
.disabledWIEntry:not(input):hover {
opacity: 1;
filter: grayscale(0.5);
}
.height32px {
height: 32px;
}
.WIEntryHeaderTitleMobile {
display: none;
}

File diff suppressed because it is too large Load Diff

67
public/img/palm.svg Normal file
View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 27.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Standard_product_icon__x28_1:1_x29_"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="192px" height="192px" viewBox="0 0 192 192" enable-background="new 0 0 192 192" xml:space="preserve">
<symbol id="material_x5F_product_x5F_standard_x5F_icon_x5F_keylines_00000077318920148093339210000006245950728745084294_" viewBox="-96 -96 192 192">
<g opacity="0.4">
<defs>
<path id="SVGID_1_" opacity="0.4" d="M-96,96V-96H96V96H-96z"/>
</defs>
<clipPath id="SVGID_00000071517564283228984050000017848131202901217410_">
<use xlink:href="#SVGID_1_" overflow="visible"/>
</clipPath>
<g clip-path="url(#SVGID_00000071517564283228984050000017848131202901217410_)">
<g>
<path d="M95.75,95.75v-191.5h-191.5v191.5H95.75 M96,96H-96V-96H96V96L96,96z"/>
</g>
<circle fill="none" stroke="#000000" stroke-width="0.25" stroke-miterlimit="10" cx="0" cy="0" r="64"/>
</g>
<circle clip-path="url(#SVGID_00000071517564283228984050000017848131202901217410_)" fill="none" stroke="#000000" stroke-width="0.25" stroke-miterlimit="10" cx="0" cy="0" r="88"/>
<path clip-path="url(#SVGID_00000071517564283228984050000017848131202901217410_)" fill="none" stroke="#000000" stroke-width="0.25" stroke-miterlimit="10" d="
M64,76H-64c-6.6,0-12-5.4-12-12V-64c0-6.6,5.4-12,12-12H64c6.6,0,12,5.4,12,12V64C76,70.6,70.6,76,64,76z"/>
<path clip-path="url(#SVGID_00000071517564283228984050000017848131202901217410_)" fill="none" stroke="#000000" stroke-width="0.25" stroke-miterlimit="10" d="
M52,88H-52c-6.6,0-12-5.4-12-12V-76c0-6.6,5.4-12,12-12H52c6.6,0,12,5.4,12,12V76C64,82.6,58.6,88,52,88z"/>
<path clip-path="url(#SVGID_00000071517564283228984050000017848131202901217410_)" fill="none" stroke="#000000" stroke-width="0.25" stroke-miterlimit="10" d="
M76,64H-76c-6.6,0-12-5.4-12-12V-52c0-6.6,5.4-12,12-12H76c6.6,0,12,5.4,12,12V52C88,58.6,82.6,64,76,64z"/>
</g>
</symbol>
<rect id="bounding_box_1_" display="none" fill="none" width="192" height="192"/>
<g id="art_layer">
<g>
<path d="M96,181.92L96,181.92c6.63,0,12-5.37,12-12v-104H84v104C84,176.55,89.37,181.92,96,181.92z"/>
<g>
<path d="M143.81,103.87C130.87,90.94,111.54,88.32,96,96l51.37,51.37c2.12,2.12,5.77,1.28,6.67-1.57
C158.56,131.49,155.15,115.22,143.81,103.87z"/>
</g>
<g>
<path d="M48.19,103.87C61.13,90.94,80.46,88.32,96,96l-51.37,51.37c-2.12,2.12-5.77,1.28-6.67-1.57
C33.44,131.49,36.85,115.22,48.19,103.87z"/>
</g>
<g>
<path d="M140,64c-20.44,0-37.79,13.4-44,32h81.24c3.33,0,5.55-3.52,4.04-6.49C173.56,74.36,157.98,64,140,64z"/>
</g>
<g>
<path d="M104.49,42.26C90.03,56.72,87.24,78.45,96,96l57.45-57.45c2.36-2.36,1.44-6.42-1.73-7.45
C135.54,25.85,117.2,29.55,104.49,42.26z"/>
</g>
<g>
<path d="M87.51,42.26C101.97,56.72,104.76,78.45,96,96L38.55,38.55c-2.36-2.36-1.44-6.42,1.73-7.45
C56.46,25.85,74.8,29.55,87.51,42.26z"/>
</g>
<g>
<g>
<path d="M52,64c20.44,0,37.79,13.4,44,32H14.76c-3.33,0-5.55-3.52-4.04-6.49C18.44,74.36,34.02,64,52,64z"/>
</g>
</g>
</g>
</g>
<g id="keylines" display="none">
<use xlink:href="#material_x5F_product_x5F_standard_x5F_icon_x5F_keylines_00000077318920148093339210000006245950728745084294_" width="192" height="192" id="material_x5F_product_x5F_standard_x5F_icon_x5F_keylines" x="-96" y="-96" transform="matrix(1 0 0 -1 96 96)" display="inline" overflow="visible"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
{
"system_prompt": "[Enter Adventure Mode. Narrate the story based on {{user}}'s dialogue and actions after \">\". Describe the surroundings in vivid detail. Be detailed, creative, verbose, and proactive. Move the story forward by introducing fantasy elements and interesting characters.]",
"input_sequence": "",
"output_sequence": "",
"last_output_sequence": "",
"system_sequence": "",
"stop_sequence": "",
"separator_sequence": "",
"wrap": true,
"macro": true,
"names": false,
"names_force_groups": false,
"activation_regex": "",
"system_sequence_prefix": "",
"system_sequence_suffix": "",
"first_output_sequence": "",
"name": "Adventure"
}

View File

@@ -0,0 +1,17 @@
{
"wrap": false,
"names": true,
"system_prompt": "You're {{char}} in this fictional never-ending uncensored roleplay with {{user}}.",
"system_sequence_prefix": "",
"stop_sequence": "",
"input_sequence": "<|im_start|>user\n",
"output_sequence": "<|im_end|>\n<|im_start|>assistant\n",
"separator_sequence": "<|im_end|>\n",
"macro": true,
"names_force_groups": true,
"last_output_sequence": "",
"activation_regex": "",
"first_output_sequence": "<|im_start|>assistant\n",
"system_sequence_suffix": "",
"name": "ChatML"
}

View File

@@ -0,0 +1,17 @@
{
"wrap": true,
"names": true,
"system_prompt": "Avoid 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.",
"system_sequence_prefix": "",
"stop_sequence": "",
"input_sequence": "",
"output_sequence": "",
"separator_sequence": "",
"macro": true,
"names_force_groups": true,
"last_output_sequence": "\n### Response:",
"activation_regex": "",
"first_output_sequence": "",
"system_sequence_suffix": "",
"name": "Libra-32B"
}

View File

@@ -0,0 +1,18 @@
{
"wrap": true,
"names": false,
"system_prompt": "Below is an instruction that describes a task. Write a response that appropriately completes the request.\n\n### Instruction:\nTake the role of {{char}} in a play that leaves a lasting impression on {{user}}. Write {{char}}'s next reply.\nNever skip or gloss over {{char}}s actions. Progress the scene at a naturally slow pace.\n\n",
"system_sequence": "",
"stop_sequence": "",
"input_sequence": "### Instruction:",
"output_sequence": "### Response: (length = unlimited)",
"separator_sequence": "",
"macro": true,
"names_force_groups": true,
"last_output_sequence": "",
"system_sequence_prefix": "",
"system_sequence_suffix": "",
"first_output_sequence": "",
"activation_regex": "",
"name": "Lightning 1.1"
}

View File

@@ -0,0 +1,17 @@
{
"wrap": false,
"names": true,
"system_prompt": "Write {{char}}'s next reply in this fictional roleplay with {{user}}.",
"system_sequence_prefix": "",
"stop_sequence": "",
"input_sequence": "[INST] ",
"output_sequence": " [/INST]\n",
"separator_sequence": "\n",
"macro": true,
"names_force_groups": true,
"last_output_sequence": "",
"activation_regex": "",
"first_output_sequence": "\n",
"system_sequence_suffix": "",
"name": "Mistral"
}

View File

@@ -0,0 +1,17 @@
{
"name": "Pygmalion",
"system_prompt": "Enter RP mode. You shall reply to {{user}} while staying in character. Your responses must be detailed, creative, immersive, and drive the scenario forward. You will follow {{char}}'s persona.",
"input_sequence": "<|user|>",
"output_sequence": "<|model|>",
"first_output_sequence": "",
"last_output_sequence": "",
"system_sequence_prefix": "<|system|>",
"system_sequence_suffix": "",
"stop_sequence": "<|user|>",
"separator_sequence": "",
"wrap": false,
"macro": true,
"names": true,
"names_force_groups": true,
"activation_regex": ""
}

View File

@@ -0,0 +1,18 @@
{
"system_prompt": "",
"input_sequence": "",
"output_sequence": "",
"last_output_sequence": "",
"system_sequence": "",
"stop_sequence": "",
"separator_sequence": "",
"wrap": true,
"macro": true,
"names": false,
"names_force_groups": false,
"activation_regex": "",
"system_sequence_prefix": "",
"system_sequence_suffix": "",
"first_output_sequence": "",
"name": "Story"
}

View File

@@ -0,0 +1,17 @@
{
"wrap": false,
"names": false,
"system_prompt": "Elaborate on the topic using a Tree of Thoughts and backtrack when necessary to construct a clear, cohesive Chain of Thought reasoning. Always answer without hesitation.",
"system_sequence_prefix": "SYSTEM: ",
"stop_sequence": "",
"input_sequence": "USER: ",
"output_sequence": "\nASSISTANT: ",
"separator_sequence": "\n",
"macro": true,
"names_force_groups": true,
"last_output_sequence": "",
"activation_regex": "",
"first_output_sequence": "ASSISTANT: ",
"system_sequence_suffix": "",
"name": "Synthia"
}

View File

@@ -0,0 +1,25 @@
(function($) {
var Defaults = $.fn.select2.amd.require('select2/defaults');
$.extend(Defaults.defaults, {
searchInputPlaceholder: '',
searchInputCssClass: '',
});
var SearchDropdown = $.fn.select2.amd.require('select2/dropdown/search');
var _renderSearchDropdown = SearchDropdown.prototype.render;
SearchDropdown.prototype.render = function(decorated) {
// invoke parent method
var $rendered = _renderSearchDropdown.apply(this, Array.prototype.slice.apply(arguments));
this.$search.attr('placeholder', this.options.get('searchInputPlaceholder'));
this.$search.addClass(this.options.get('searchInputCssClass'));
return $rendered;
};
})(window.jQuery);

File diff suppressed because it is too large Load Diff

View File

@@ -60,7 +60,7 @@ const registerPromptManagerMigration = () => {
* Represents a prompt.
*/
class Prompt {
identifier; role; content; name; system_prompt;
identifier; role; content; name; system_prompt; position;
/**
* Create a new Prompt instance.
@@ -71,13 +71,15 @@ class Prompt {
* @param {string} param0.content - The content of the prompt.
* @param {string} param0.name - The name of the prompt.
* @param {boolean} param0.system_prompt - Indicates if the prompt is a system prompt.
* @param {string} param0.position - The position of the prompt in the prompt list.
*/
constructor({ identifier, role, content, name, system_prompt } = {}) {
constructor({ identifier, role, content, name, system_prompt, position } = {}) {
this.identifier = identifier;
this.role = role;
this.content = content;
this.name = name;
this.system_prompt = system_prompt;
this.position = position;
}
}

View File

@@ -31,7 +31,7 @@ import {
SECRET_KEYS,
secret_state,
} from "./secrets.js";
import { debounce, delay, getStringHash, waitUntilCondition } from "./utils.js";
import { debounce, delay, getStringHash, isUrlOrAPIKey, waitUntilCondition } from "./utils.js";
import { chat_completion_sources, oai_settings } from "./openai.js";
import { getTokenCount } from "./tokenizers.js";
@@ -210,10 +210,12 @@ $("#character_popup").on("input", function () { countTokensDebounced(); });
//function:
export function RA_CountCharTokens() {
let total_tokens = 0;
let permanent_tokens = 0;
$('[data-token-counter]').each(function () {
const counter = $(this);
const input = $(document.getElementById(counter.data('token-counter')));
const isPermanent = counter.data('token-permanent') === true;
const value = String(input.val());
if (input.length === 0) {
@@ -230,10 +232,12 @@ export function RA_CountCharTokens() {
if (input.data('last-value-hash') === valueHash) {
total_tokens += Number(counter.text());
permanent_tokens += isPermanent ? Number(counter.text()) : 0;
} else {
const tokens = getTokenCount(value);
counter.text(tokens);
total_tokens += tokens;
permanent_tokens += isPermanent ? tokens : 0;
input.data('last-value-hash', valueHash);
}
});
@@ -242,6 +246,7 @@ export function RA_CountCharTokens() {
const tokenLimit = Math.max(((main_api !== 'openai' ? max_context : oai_settings.openai_max_context) / 2), 1024);
const showWarning = (total_tokens > tokenLimit);
$('#result_info_total_tokens').text(total_tokens);
$('#result_info_permanent_tokens').text(permanent_tokens);
$('#result_info_text').toggleClass('neutral_warning', showWarning);
$('#chartokenwarning').toggle(showWarning);
}
@@ -274,10 +279,16 @@ export async function favsToHotswap() {
const entities = getEntitiesList({ doFilter: false });
const container = $('#right-nav-panel .hotswap');
const template = $('#hotswap_template .hotswapAvatar');
container.empty();
const maxCount = 6;
const DEFAULT_COUNT = 6;
const WIDTH_PER_ITEM = 60; // 50px + 5px gap + 5px padding
const containerWidth = container.outerWidth();
const maxCount = containerWidth > 0 ? Math.floor(containerWidth / WIDTH_PER_ITEM) : DEFAULT_COUNT;
let count = 0;
const promises = [];
const newContainer = container.clone();
newContainer.empty();
for (const entity of entities) {
if (count >= maxCount) {
break;
@@ -310,23 +321,39 @@ export async function favsToHotswap() {
}
if (isCharacter) {
const avatarUrl = getThumbnailUrl('avatar', entity.item.avatar);
$(slot).find('img').attr('src', avatarUrl);
$(slot).attr('title', entity.item.avatar);
const imgLoadPromise = new Promise((resolve) => {
const avatarUrl = getThumbnailUrl('avatar', entity.item.avatar);
$(slot).find('img').attr('src', avatarUrl).on('load', resolve);
$(slot).attr('title', entity.item.avatar);
});
// if the image doesn't load in 500ms, resolve the promise anyway
promises.push(Promise.race([imgLoadPromise, delay(500)]));
}
$(slot).css('cursor', 'pointer');
container.append(slot);
newContainer.append(slot);
count++;
}
// there are 6 slots in total,
if (count < maxCount) { //if any are left over
// don't fill leftover spaces with avatar placeholders
// just evenly space the selected avatars instead
/*
if (count < maxCount) { //if any space is left over
let leftOverSlots = maxCount - count;
for (let i = 1; i <= leftOverSlots; i++) {
container.append(template.clone());
newContainer.append(template.clone());
}
}
*/
await Promise.allSettled(promises);
//helpful instruction message if no characters are favorited
if (count === 0) { container.html(`<small><span><i class="fa-solid fa-star"></i> Favorite characters to add them to HotSwaps</span></small>`) }
//otherwise replace with fav'd characters
if (count > 0) {
container.replaceWith(newContainer);
}
}
//changes input bar and send button display depending on connection status
@@ -388,6 +415,7 @@ function RA_autoconnect(PrevApi) {
|| (oai_settings.chat_completion_source == chat_completion_sources.WINDOWAI)
|| (secret_state[SECRET_KEYS.OPENROUTER] && oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER)
|| (secret_state[SECRET_KEYS.AI21] && oai_settings.chat_completion_source == chat_completion_sources.AI21)
|| (secret_state[SECRET_KEYS.PALM] && oai_settings.chat_completion_source == chat_completion_sources.PALM)
) {
$("#api_button_openai").click();
}
@@ -403,15 +431,6 @@ function RA_autoconnect(PrevApi) {
}
}
function isUrlOrAPIKey(string) {
try {
new URL(string);
return true;
} catch (_) {
// return pattern.test(string);
}
}
function OpenNavPanels() {
const deviceInfo = getDeviceInfo();
if (deviceInfo && deviceInfo.device.type === 'desktop') {
@@ -867,8 +886,12 @@ export function initRossMods() {
//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 () {
const chatBlock = $('#chat');
const originalScrollBottom = chatBlock[0].scrollHeight - (chatBlock.scrollTop() + chatBlock.outerHeight());
this.style.height = window.getComputedStyle(this).getPropertyValue('min-height');
this.style.height = (this.scrollHeight) + 'px';
const newScrollTop = chatBlock[0].scrollHeight - (chatBlock.outerHeight() + originalScrollBottom);
chatBlock.scrollTop(newScrollTop);
});
//Regenerate if user swipes on the last mesage in chat
@@ -906,10 +929,13 @@ export function initRossMods() {
}
$(document).on('keydown', function (event) {
processHotkeys(event);
processHotkeys(event.originalEvent);
});
//Additional hotkeys CTRL+ENTER and CTRL+UPARROW
/**
* @param {KeyboardEvent} event
*/
function processHotkeys(event) {
//Enter to send when send_textarea in focus
if ($(':focus').attr('id') === 'send_textarea') {
@@ -943,6 +969,14 @@ export function initRossMods() {
}, 300);
}
// Alt+Enter or AltGr+Enter to Continue
if ((event.altKey || (event.altKey && event.ctrlKey)) && event.key == "Enter") {
if (is_send_press == false) {
console.debug("Continuing with Alt+Enter");
$('#option_continue').trigger('click');
}
}
// Ctrl+Enter for Regeneration Last Response. If editing, accept the edits instead
if (event.ctrlKey && event.key == "Enter") {
const editMesDone = $(".mes_edit_done:visible");
@@ -958,14 +992,6 @@ export function initRossMods() {
}
}
// Alt+Enter to Continue
if (event.altKey && event.key == "Enter") {
if (is_send_press == false) {
console.debug("Continuing with Alt+Enter");
$('#option_continue').trigger('click');
}
}
// Helper function to check if nanogallery2's lightbox is active
function isNanogallery2LightboxActive() {
// Check if the body has the 'nGY2On' class, adjust this based on actual behavior
@@ -1097,13 +1123,14 @@ export function initRossMods() {
$("#rightNavDrawerIcon").trigger('click');
return
}
if ($(".draggable").is(":visible")) {
// Remove the first matched element
$('.draggable:first').remove();
return;
}
}
if ($(".draggable").is(":visible")) {
// Remove the first matched element
$('.draggable:first').remove();
return;
}
if (event.ctrlKey && /^[1-9]$/.test(event.key)) {

View File

@@ -133,6 +133,39 @@ async function saveBookmarkMenu() {
return createNewBookmark(chat.length - 1);
}
export async function createBranch(mesId) {
if (!chat.length) {
toastr.warning('The chat is empty.', 'Branch creation failed');
return;
}
if (mesId < 0 || mesId >= chat.length) {
toastr.warning('Invalid message ID.', 'Branch creation failed');
return;
}
const lastMes = chat[mesId];
const mainChat = selected_group ? groups?.find(x => x.id == selected_group)?.chat_id : characters[this_chid].chat;
const newMetadata = { main_chat: mainChat };
let name = `Branch #${mesId} - ${humanizedDateTime()}`
if (selected_group) {
await saveGroupBookmarkChat(selected_group, name, newMetadata, mesId);
} else {
await saveChat(name, newMetadata, mesId);
}
// append to branches list if it exists
// otherwise create it
if (typeof lastMes.extra !== 'object') {
lastMes.extra = {};
}
if (typeof lastMes.extra['branches'] !== 'object') {
lastMes.extra['branches'] = [];
}
lastMes.extra['branches'].push(name);
return name;
}
async function createNewBookmark(mesId) {
if (!chat.length) {
toastr.warning('The chat is empty.', 'Bookmark creation failed');
@@ -280,7 +313,6 @@ async function convertSoloToGroupChat() {
message.name = character.name;
message.original_avatar = character.avatar;
message.force_avatar = getThumbnailUrl('avatar', character.avatar);
message.is_name = true;
// Allow regens of a single message in group
if (typeof message.extra !== 'object') {

View File

@@ -134,7 +134,10 @@ const extension_settings = {
caption: {
refine_mode: false,
},
expressions: {},
expressions: {
/** @type {string[]} */
custom: [],
},
dice: {},
regex: [],
tts: {},
@@ -153,6 +156,8 @@ const extension_settings = {
},
speech_recognition: {},
rvc: {},
hypebot: {},
vectors: {},
};
let modules = [];
@@ -192,7 +197,6 @@ async function doExtrasFetch(endpoint, args) {
}
Object.assign(args.headers, {
'Authorization': `Bearer ${extension_settings.apiKey}`,
'Bypass-Tunnel-Reminder': 'bypass'
});
const response = await fetch(endpoint, args);
@@ -201,7 +205,7 @@ async function doExtrasFetch(endpoint, args) {
async function discoverExtensions() {
try {
const response = await fetch('/discover_extensions');
const response = await fetch('/api/extensions/discover');
if (response.ok) {
const extensions = await response.json();
@@ -606,7 +610,7 @@ async function showExtensionsDetails() {
async function onUpdateClick() {
const extensionName = $(this).data('name');
try {
const response = await fetch('/update_extension', {
const response = await fetch('/api/extensions/update', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify({ extensionName })
@@ -626,7 +630,7 @@ async function onUpdateClick() {
/**
* Handles the click event for the delete button of an extension.
* This function makes a POST request to '/delete_extension' with the extension's name.
* This function makes a POST request to '/api/extensions/delete' with the extension's name.
* If the extension is deleted, it displays a success message.
* Creates a popup for the user to confirm before delete.
*/
@@ -636,7 +640,7 @@ async function onDeleteClick() {
const confirmation = await callPopup(`Are you sure you want to delete ${extensionName}?`, 'delete_extension');
if (confirmation) {
try {
const response = await fetch('/delete_extension', {
const response = await fetch('/api/extensions/delete', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify({ extensionName })
@@ -663,7 +667,7 @@ async function onDeleteClick() {
*/
async function getExtensionVersion(extensionName) {
try {
const response = await fetch('/get_extension_version', {
const response = await fetch('/api/extensions/version', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify({ extensionName })

View File

@@ -150,7 +150,7 @@ async function installAsset(url, assetType, filename) {
const category = assetType;
try {
const body = { url, category, filename };
const result = await fetch('/asset_download', {
const result = await fetch('/api/assets/download', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify(body),
@@ -171,7 +171,7 @@ async function deleteAsset(assetType, filename) {
const category = assetType;
try {
const body = { category, filename };
const result = await fetch('/asset_delete', {
const result = await fetch('/api/assets/delete', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify(body),
@@ -194,7 +194,7 @@ async function deleteAsset(assetType, filename) {
async function updateCurrentAssets() {
console.debug(DEBUG_PREFIX, "Checking installed assets...")
try {
const result = await fetch(`/get_assets`, {
const result = await fetch(`/api/assets/get`, {
method: 'POST',
headers: getRequestHeaders(),
});

View File

@@ -1,6 +1,9 @@
/*
Ideas:
- Clean design of new ui
- change select text versus options for playing: audio
- cross fading between bgm / start a different time
- fading should appear before end when switching randomly
- Background based ambient sounds
- import option on background UI ?
- Allow background music edition using background menu
@@ -59,6 +62,8 @@ const DEFAULT_EXPRESSIONS = [
];
const SPRITE_DOM_ID = "#expression-image";
let current_chat_id = null
let fallback_BGMS = null; // Initialized only once with module workers
let ambients = null; // Initialized only once with module workers
let characterMusics = {}; // Updated with module workers
@@ -69,16 +74,27 @@ let currentBackground = null;
let cooldownBGM = 0;
let bgmEnded = true;
//#############################//
// Extension UI and Settings //
//#############################//
const defaultSettings = {
enabled: false,
dynamic_bgm_enabled: false,
//dynamic_ambient_enabled: false,
bgm_locked: true,
bgm_muted: true,
ambient_muted: true,
bgm_volume: 50,
bgm_selected: null,
ambient_locked: true,
ambient_muted: true,
ambient_volume: 50,
ambient_selected: null,
bgm_cooldown: 30
}
@@ -90,6 +106,8 @@ function loadSettings() {
Object.assign(extension_settings.audio, defaultSettings)
}
$("#audio_enabled").prop('checked', extension_settings.audio.enabled);
$("#audio_dynamic_bgm_enabled").prop('checked', extension_settings.audio.dynamic_bgm_enabled);
//$("#audio_dynamic_ambient_enabled").prop('checked', extension_settings.audio.dynamic_ambient_enabled);
$("#audio_bgm_volume").text(extension_settings.audio.bgm_volume);
$("#audio_ambient_volume").text(extension_settings.audio.ambient_volume);
@@ -103,20 +121,55 @@ function loadSettings() {
$("#audio_bgm_mute").addClass("redOverlayGlow");
$("#audio_bgm").prop("muted", true);
}
else{
else {
$("#audio_bgm_mute_icon").addClass("fa-volume-high");
$("#audio_bgm_mute_icon").removeClass("fa-volume-mute");
$("#audio_bgm_mute").removeClass("redOverlayGlow");
$("#audio_bgm").prop("muted", false);
}
if (extension_settings.audio.bgm_locked) {
//$("#audio_bgm_lock_icon").removeClass("fa-lock-open");
//$("#audio_bgm_lock_icon").addClass("fa-lock");
$("#audio_bgm").attr("loop", true);
$("#audio_bgm_lock").addClass("redOverlayGlow");
}
else {
//$("#audio_bgm_lock_icon").removeClass("fa-lock");
//$("#audio_bgm_lock_icon").addClass("fa-lock-open");
$("#audio_bgm").attr("loop", false);
$("#audio_bgm_lock").removeClass("redOverlayGlow");
}
/*
if (extension_settings.audio.bgm_selected !== null) {
$("#audio_bgm_select").append(new Option(extension_settings.audio.bgm_selected, extension_settings.audio.bgm_selected));
$("#audio_bgm_select").val(extension_settings.audio.bgm_selected);
}*/
if (extension_settings.audio.ambient_locked) {
$("#audio_ambient_lock_icon").removeClass("fa-lock-open");
$("#audio_ambient_lock_icon").addClass("fa-lock");
$("#audio_ambient_lock").addClass("redOverlayGlow");
}
else {
$("#audio_ambient_lock_icon").removeClass("fa-lock");
$("#audio_ambient_lock_icon").addClass("fa-lock-open");
}
/*
if (extension_settings.audio.ambient_selected !== null) {
$("#audio_ambient_select").append(new Option(extension_settings.audio.ambient_selected, extension_settings.audio.ambient_selected));
$("#audio_ambient_select").val(extension_settings.audio.ambient_selected);
}*/
if (extension_settings.audio.ambient_muted) {
$("#audio_ambient_mute_icon").removeClass("fa-volume-high");
$("#audio_ambient_mute_icon").addClass("fa-volume-mute");
$("#audio_ambient_mute").addClass("redOverlayGlow");
$("#audio_ambient").prop("muted", true);
}
else{
else {
$("#audio_ambient_mute_icon").addClass("fa-volume-high");
$("#audio_ambient_mute_icon").removeClass("fa-volume-mute");
$("#audio_ambient_mute").removeClass("redOverlayGlow");
@@ -125,7 +178,7 @@ function loadSettings() {
$("#audio_bgm_cooldown").val(extension_settings.audio.bgm_cooldown);
$("#audio_debug_div").hide(); // DBG
$("#audio_debug_div").hide(); // DBG: comment to see debug mode
}
async function onEnabledClick() {
@@ -142,6 +195,51 @@ async function onEnabledClick() {
saveSettingsDebounced();
}
async function onDynamicBGMEnabledClick() {
extension_settings.audio.dynamic_bgm_enabled = $('#audio_dynamic_bgm_enabled').is(':checked');
currentCharacterBGM = null;
currentExpressionBGM = null;
cooldownBGM = 0;
saveSettingsDebounced();
}
/*
async function onDynamicAmbientEnabledClick() {
extension_settings.audio.dynamic_ambient_enabled = $('#audio_dynamic_ambient_enabled').is(':checked');
currentBackground = null;
saveSettingsDebounced();
}
*/
async function onBGMLockClick() {
extension_settings.audio.bgm_locked = !extension_settings.audio.bgm_locked;
if (extension_settings.audio.bgm_locked) {
extension_settings.audio.bgm_selected = $("#audio_bgm_select").val();
$("#audio_bgm").attr("loop", true);
}
else {
$("#audio_bgm").attr("loop", false);
}
//$("#audio_bgm_lock_icon").toggleClass("fa-lock");
//$("#audio_bgm_lock_icon").toggleClass("fa-lock-open");
$("#audio_bgm_lock").toggleClass("redOverlayGlow");
saveSettingsDebounced();
}
async function onBGMRandomClick() {
var select = document.getElementById('audio_bgm_select');
var items = select.getElementsByTagName('option');
if (items.length < 2)
return;
var index;
do {
index = Math.floor(Math.random() * items.length);
} while (index == select.selectedIndex);
select.selectedIndex = index;
onBGMSelectChange();
}
async function onBGMMuteClick() {
extension_settings.audio.bgm_muted = !extension_settings.audio.bgm_muted;
$("#audio_bgm_mute_icon").toggleClass("fa-volume-high");
@@ -151,6 +249,20 @@ async function onBGMMuteClick() {
saveSettingsDebounced();
}
async function onAmbientLockClick() {
extension_settings.audio.ambient_locked = !extension_settings.audio.ambient_locked;
if (extension_settings.audio.ambient_locked)
extension_settings.audio.ambient_selected = $("#audio_ambient_select").val();
else {
extension_settings.audio.ambient_selected = null;
currentBackground = null;
}
$("#audio_ambient_lock_icon").toggleClass("fa-lock");
$("#audio_ambient_lock_icon").toggleClass("fa-lock-open");
$("#audio_ambient_lock").toggleClass("redOverlayGlow");
saveSettingsDebounced();
}
async function onAmbientMuteClick() {
extension_settings.audio.ambient_muted = !extension_settings.audio.ambient_muted;
$("#audio_ambient_mute_icon").toggleClass("fa-volume-high");
@@ -176,6 +288,20 @@ async function onAmbientVolumeChange() {
//console.debug(DEBUG_PREFIX,"UPDATED Ambient MAX TO",extension_settings.audio.ambient_volume);
}
async function onBGMSelectChange() {
extension_settings.audio.bgm_selected = $("#audio_bgm_select").val();
updateBGM(true);
saveSettingsDebounced();
//console.debug(DEBUG_PREFIX,"UPDATED BGM MAX TO",extension_settings.audio.bgm_volume);
}
async function onAmbientSelectChange() {
extension_settings.audio.ambient_selected = $("#audio_ambient_select").val();
updateAmbient(true);
saveSettingsDebounced();
//console.debug(DEBUG_PREFIX,"UPDATED BGM MAX TO",extension_settings.audio.bgm_volume);
}
async function onBGMCooldownInput() {
extension_settings.audio.bgm_cooldown = ~~($("#audio_bgm_cooldown").val());
cooldownBGM = extension_settings.audio.bgm_cooldown * 1000;
@@ -191,7 +317,7 @@ async function getAssetsList(type) {
console.debug(DEBUG_PREFIX, "getting assets of type", type);
try {
const result = await fetch(`/get_assets`, {
const result = await fetch(`/api/assets/get`, {
method: 'POST',
headers: getRequestHeaders(),
});
@@ -209,7 +335,7 @@ async function getCharacterBgmList(name) {
console.debug(DEBUG_PREFIX, "getting bgm list for", name);
try {
const result = await fetch(`/get_character_assets_list?name=${encodeURIComponent(name)}&category=${CHARACTER_BGM_FOLDER}`, {
const result = await fetch(`/api/assets/character?name=${encodeURIComponent(name)}&category=${CHARACTER_BGM_FOLDER}`, {
method: 'POST',
headers: getRequestHeaders(),
});
@@ -222,11 +348,42 @@ async function getCharacterBgmList(name) {
}
}
//#############################//
// Module Worker //
//#############################//
function fillBGMSelect() {
let found_last_selected_bgm = false;
// Update bgm list in UI
$("#audio_bgm_select")
.find('option')
.remove();
for (const file of fallback_BGMS) {
$('#audio_bgm_select').append(new Option("asset: " + file.replace(/^.*[\\\/]/, '').replace(/\.[^/.]+$/, ""), file));
if (file === extension_settings.audio.bgm_selected) {
$('#audio_bgm_select').val(extension_settings.audio.bgm_selected);
found_last_selected_bgm = true;
}
}
// Update bgm list in UI
for (const char in characterMusics)
for (const e in characterMusics[char])
for (const file of characterMusics[char][e]) {
$('#audio_bgm_select').append(new Option(char + ": " + file.replace(/^.*[\\\/]/, '').replace(/\.[^/.]+$/, ""), file));
if (file === extension_settings.audio.bgm_selected) {
$('#audio_bgm_select').val(extension_settings.audio.bgm_selected);
found_last_selected_bgm = true;
}
}
if (!found_last_selected_bgm) {
$('#audio_bgm_select').val($("#audio_bgm_select option:first").val());
extension_settings.audio.bgm_selected = null;
}
}
/*
- Update ambient sound
- Update character BGM
@@ -246,6 +403,8 @@ async function moduleWorker() {
fallback_BGMS = await getAssetsList(ASSETS_BGM_FOLDER);
fallback_BGMS = fallback_BGMS.filter((filename) => filename != ".placeholder")
console.debug(DEBUG_PREFIX, "Detected assets:", fallback_BGMS);
fillBGMSelect();
}
if (ambients == null) {
@@ -253,10 +412,33 @@ async function moduleWorker() {
ambients = await getAssetsList(ASSETS_AMBIENT_FOLDER);
ambients = ambients.filter((filename) => filename != ".placeholder")
console.debug(DEBUG_PREFIX, "Detected assets:", ambients);
// Update bgm list in UI
$("#audio_ambient_select")
.find('option')
.remove();
if (extension_settings.audio.ambient_selected !== null) {
let ambient_label = extension_settings.audio.ambient_selected;
if (ambient_label.includes("assets"))
ambient_label = "asset: " + ambient_label.replace(/^.*[\\\/]/, '').replace(/\.[^/.]+$/, "");
else {
ambient_label = ambient_label.substring("/characters/".length);
ambient_label = ambient_label.substring(0, ambient_label.indexOf("/")) + ": " + ambient_label.substring(ambient_label.indexOf("/") + "/bgm/".length);
ambient_label = ambient_label.replace(/\.[^/.]+$/, "");
}
$('#audio_ambient_select').append(new Option(ambient_label, extension_settings.audio.ambient_selected));
}
for (const file of ambients) {
if (file !== extension_settings.audio.ambient_selected)
$("#audio_ambient_select").append(new Option("asset: " + file.replace(/^.*[\\\/]/, '').replace(/\.[^/.]+$/, ""), file));
}
}
// 1) Update ambient audio
// ---------------------------
//if (extension_settings.audio.dynamic_ambient_enabled) {
let newBackground = $("#bg1").css("background-image");
const custom_background = getContext()["chatMetadata"]["custom_background"];
@@ -275,6 +457,7 @@ async function moduleWorker() {
updateAmbient();
}
}
//}
const context = getContext();
//console.debug(DEBUG_PREFIX,context);
@@ -288,6 +471,14 @@ async function moduleWorker() {
// 1) Update BGM (single chat)
// -----------------------------
if (!chatIsGroup) {
// Reset bgm list on new chat
if (context.chatId != current_chat_id) {
current_chat_id = context.chatId;
characterMusics = {};
cooldownBGM = 0;
}
newCharacter = context.name2;
//console.log(DEBUG_PREFIX,"SOLO CHAT MODE"); // DBG
@@ -307,7 +498,7 @@ async function moduleWorker() {
if (currentCharacterBGM !== newCharacter) {
currentCharacterBGM = newCharacter;
try {
await updateBGM();
await updateBGM(false, true);
cooldownBGM = extension_settings.audio.bgm_cooldown * 1000;
}
catch (error) {
@@ -329,9 +520,9 @@ async function moduleWorker() {
}
try {
currentExpressionBGM = newExpression;
await updateBGM();
cooldownBGM = extension_settings.audio.bgm_cooldown * 1000;
currentExpressionBGM = newExpression;
console.debug(DEBUG_PREFIX, "(SOLO) Updated current character expression to", currentExpressionBGM, "cooldown", cooldownBGM);
}
catch (error) {
@@ -346,6 +537,34 @@ async function moduleWorker() {
// 2) Update BGM (group chat)
// -----------------------------
// Load current chat character bgms
// Reset bgm list on new chat
if (context.chatId != current_chat_id) {
current_chat_id = context.chatId;
characterMusics = {};
cooldownBGM = 0;
for (const message of context.chat) {
if (characterMusics[message.name] === undefined)
await loadCharacterBGM(message.name);
}
try {
newCharacter = context.chat[context.chat.length - 1].name;
currentCharacterBGM = newCharacter;
await updateBGM(false, true);
cooldownBGM = extension_settings.audio.bgm_cooldown * 1000;
currentCharacterBGM = newCharacter;
currentExpressionBGM = FALLBACK_EXPRESSION;
console.debug(DEBUG_PREFIX, "(GROUP) Updated current character BGM to", currentExpressionBGM, "cooldown", cooldownBGM);
}
catch (error) {
console.debug(DEBUG_PREFIX, "Error while trying to update BGM group, will try again");
currentCharacterBGM = null
}
return;
}
newCharacter = context.chat[context.chat.length - 1].name;
const userName = context.name1;
@@ -353,17 +572,17 @@ async function moduleWorker() {
//console.log(DEBUG_PREFIX,"GROUP CHAT MODE"); // DBG
// 2.1) First time character appear
// 2.1) New character appear
if (characterMusics[newCharacter] === undefined) {
await loadCharacterBGM(newCharacter);
return;
}
// 2.2) Switched chat
// 2.2) Switched char
if (currentCharacterBGM !== newCharacter) {
// Check cooldown
if (cooldownBGM > 0) {
//console.debug(DEBUG_PREFIX,"(GROUP) BGM switch on cooldown:",cooldownBGM);
console.debug(DEBUG_PREFIX, "(GROUP) BGM switch on cooldown:", cooldownBGM);
return;
}
@@ -429,6 +648,8 @@ async function loadCharacterBGM(newCharacter) {
characterMusics[newCharacter][e].push(i);
}
console.debug(DEBUG_PREFIX, "Updated BGM map of", newCharacter, "to", characterMusics[newCharacter]);
fillBGMSelect();
}
function getNewExpression() {
@@ -458,24 +679,52 @@ function getNewExpression() {
return newExpression;
}
async function updateBGM() {
let audio_files = characterMusics[currentCharacterBGM][currentExpressionBGM];// Try char expression BGM
if (audio_files === undefined || audio_files.length == 0) {
console.debug(DEBUG_PREFIX, "No BGM for", currentCharacterBGM, currentExpressionBGM);
audio_files = characterMusics[currentCharacterBGM][FALLBACK_EXPRESSION]; // Try char FALLBACK BGM
if (audio_files === undefined || audio_files.length == 0) {
console.debug(DEBUG_PREFIX, "No default BGM for", currentCharacterBGM, FALLBACK_EXPRESSION, "switch to ST BGM");
audio_files = fallback_BGMS; // ST FALLBACK BGM
if (audio_files.length == 0) {
console.debug(DEBUG_PREFIX, "No default BGM file found, bgm folder may be empty.");
return;
}
}
async function updateBGM(isUserInput = false, newChat = false) {
if (!isUserInput && !extension_settings.audio.dynamic_bgm_enabled && $("#audio_bgm").attr("src") != "" && !bgmEnded && !newChat) {
console.debug(DEBUG_PREFIX, "BGM already playing and dynamic switch disabled, no update done");
return;
}
let audio_file_path = ""
if (isUserInput || (extension_settings.audio.bgm_locked && extension_settings.audio.bgm_selected !== null)) {
audio_file_path = extension_settings.audio.bgm_selected;
if (isUserInput)
console.debug(DEBUG_PREFIX, "User selected BGM", audio_file_path);
if (extension_settings.audio.bgm_locked)
console.debug(DEBUG_PREFIX, "BGM locked keeping current audio", audio_file_path);
}
else {
let audio_files = null;
if (extension_settings.audio.dynamic_bgm_enabled) {
extension_settings.audio.bgm_selected = null;
saveSettingsDebounced();
audio_files = characterMusics[currentCharacterBGM][currentExpressionBGM];// Try char expression BGM
if (audio_files === undefined || audio_files.length == 0) {
console.debug(DEBUG_PREFIX, "No BGM for", currentCharacterBGM, currentExpressionBGM);
audio_files = characterMusics[currentCharacterBGM][FALLBACK_EXPRESSION]; // Try char FALLBACK BGM
if (audio_files === undefined || audio_files.length == 0) {
console.debug(DEBUG_PREFIX, "No default BGM for", currentCharacterBGM, FALLBACK_EXPRESSION, "switch to ST BGM");
audio_files = fallback_BGMS; // ST FALLBACK BGM
if (audio_files.length == 0) {
console.debug(DEBUG_PREFIX, "No default BGM file found, bgm folder may be empty.");
return;
}
}
}
}
else {
audio_files = [];
$("#audio_bgm_select option").each(function () { audio_files.push($(this).val()); });
}
audio_file_path = audio_files[Math.floor(Math.random() * audio_files.length)];
}
const audio_file_path = audio_files[Math.floor(Math.random() * audio_files.length)];
console.log(DEBUG_PREFIX, "Updating BGM");
console.log(DEBUG_PREFIX, "Checking file", audio_file_path);
try {
@@ -485,20 +734,30 @@ async function updateBGM() {
console.log(DEBUG_PREFIX, "File not found!")
}
else {
console.log(DEBUG_PREFIX, "Switching BGM to", currentExpressionBGM)
console.log(DEBUG_PREFIX, "Switching BGM to", currentExpressionBGM);
$("#audio_bgm_select").val(audio_file_path);
const audio = $("#audio_bgm");
if (audio.attr("src") == audio_file_path) {
if (audio.attr("src") == audio_file_path && !bgmEnded) {
console.log(DEBUG_PREFIX, "Already playing, ignored");
return;
}
audio.animate({ volume: 0.0 }, 2000, function () {
let fade_time = 2000;
bgmEnded = false;
if (isUserInput || extension_settings.audio.bgm_locked) {
audio.attr("src", audio_file_path);
audio[0].play();
audio.volume = extension_settings.audio.bgm_volume * 0.01;
audio.animate({ volume: extension_settings.audio.bgm_volume * 0.01 }, 2000);
})
}
else {
audio.animate({ volume: 0.0 }, fade_time, function () {
audio.attr("src", audio_file_path);
audio[0].play();
audio.volume = extension_settings.audio.bgm_volume * 0.01;
audio.animate({ volume: extension_settings.audio.bgm_volume * 0.01 }, fade_time);
});
}
}
} catch (error) {
@@ -506,18 +765,30 @@ async function updateBGM() {
}
}
async function updateAmbient() {
async function updateAmbient(isUserInput = false) {
let audio_file_path = null;
for (const i of ambients) {
console.debug(i)
if (i.includes(currentBackground)) {
audio_file_path = i;
break;
if (isUserInput || extension_settings.audio.ambient_locked) {
audio_file_path = extension_settings.audio.ambient_selected;
if (isUserInput)
console.debug(DEBUG_PREFIX, "User selected Ambient", audio_file_path);
if (extension_settings.audio.bgm_locked)
console.debug(DEBUG_PREFIX, "Ambient locked keeping current audio", audio_file_path);
}
else {
extension_settings.audio.ambient_selected = null;
for (const i of ambients) {
console.debug(i)
if (i.includes(currentBackground)) {
audio_file_path = i;
break;
}
}
}
if (audio_file_path === null) {
console.debug(DEBUG_PREFIX, "No ambient file found for background", currentBackground);
console.debug(DEBUG_PREFIX, "No bgm file found for background", currentBackground);
const audio = $("#audio_ambient");
audio.attr("src", "");
audio[0].pause();
@@ -527,45 +798,87 @@ async function updateAmbient() {
//const audio_file_path = AMBIENT_FOLDER+currentBackground+".mp3";
console.log(DEBUG_PREFIX, "Updating ambient");
console.log(DEBUG_PREFIX, "Checking file", audio_file_path);
$("#audio_ambient_select").val(audio_file_path);
let fade_time = 2000;
if (isUserInput)
fade_time = 0;
const audio = $("#audio_ambient");
audio.animate({ volume: 0.0 }, 2000, function () {
if (audio.attr("src") == audio_file_path) {
console.log(DEBUG_PREFIX, "Already playing, ignored");
return;
}
audio.animate({ volume: 0.0 }, fade_time, function () {
audio.attr("src", audio_file_path);
audio[0].play();
audio.volume = extension_settings.audio.ambient_volume * 0.01;
audio.animate({ volume: extension_settings.audio.ambient_volume * 0.01 }, 2000);
audio.animate({ volume: extension_settings.audio.ambient_volume * 0.01 }, fade_time);
});
}
/**
* Handles wheel events on volume sliders.
* @param {WheelEvent} e Event
*/
function onVolumeSliderWheelEvent(e) {
const slider = $(this);
e.preventDefault();
e.stopPropagation();
const delta = e.deltaY / 20;
const sliderVal = Number(slider.val());
let newVal = sliderVal - delta;
if (newVal < 0) {
newVal = 0;
} else if (newVal > 100) {
newVal = 100;
}
slider.val(newVal).trigger('input');
}
//#############################//
// Extension load //
//#############################//
// This function is called when the extension is loaded
jQuery(async () => {
// This is an example of loading HTML from a file
const windowHtml = $(await $.get(`${extensionFolderPath}/window.html`));
$('#extensions_settings').append(windowHtml);
loadSettings();
$("#audio_bgm").attr("loop", true);
$("#audio_enabled").on("click", onEnabledClick);
$("#audio_dynamic_bgm_enabled").on("click", onDynamicBGMEnabledClick);
//$("#audio_dynamic_ambient_enabled").on("click", onDynamicAmbientEnabledClick);
//$("#audio_bgm").attr("loop", false);
$("#audio_ambient").attr("loop", true);
$("#audio_bgm").hide();
$("#audio_ambient").hide();
$("#audio_bgm_lock").on("click", onBGMLockClick);
$("#audio_bgm_mute").on("click", onBGMMuteClick);
$("#audio_ambient_mute").on("click", onAmbientMuteClick);
$("#audio_enabled").on("click", onEnabledClick);
$("#audio_bgm_volume_slider").on("input", onBGMVolumeChange);
$("#audio_bgm_random").on("click", onBGMRandomClick);
$("#audio_ambient").hide();
$("#audio_ambient_lock").on("click", onAmbientLockClick);
$("#audio_ambient_mute").on("click", onAmbientMuteClick);
$("#audio_ambient_volume_slider").on("input", onAmbientVolumeChange);
document.getElementById('audio_ambient_volume_slider').addEventListener('wheel', onVolumeSliderWheelEvent, { passive: false });
document.getElementById('audio_bgm_volume_slider').addEventListener('wheel', onVolumeSliderWheelEvent, { passive: false });
$("#audio_bgm_cooldown").on("input", onBGMCooldownInput);
// Reset assets container, will be redected like if ST restarted
$("#audio_refresh_assets").on("click", function(){
console.debug(DEBUG_PREFIX,"Refreshing audio assets");
$("#audio_refresh_assets").on("click", function () {
console.debug(DEBUG_PREFIX, "Refreshing audio assets");
current_chat_id = null
fallback_BGMS = null;
ambients = null;
characterMusics = {};
@@ -574,6 +887,9 @@ jQuery(async () => {
currentBackground = null;
})
$("#audio_bgm_select").on("change", onBGMSelectChange);
$("#audio_ambient_select").on("change", onAmbientSelectChange);
// DBG
$("#audio_debug").on("click", function () {
if ($("#audio_debug").is(':checked')) {
@@ -587,6 +903,14 @@ jQuery(async () => {
});
//
$("#audio_bgm").on("ended", function () {
console.debug(DEBUG_PREFIX, "END OF BGM")
if (!extension_settings.audio.bgm_locked) {
bgmEnded = true;
updateBGM();
}
});
const wrapper = new ModuleWorkerWrapper(moduleWorker);
setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL);
moduleWorker();

View File

@@ -1,15 +1,46 @@
.mixer-div {
.audio-ui-block {
margin-bottom: 1em;
}
.audio-mixer-div {
display: flex;
flex-direction: row;
padding: 5px;
background-color: rgba(38, 38, 38, 0.5);
border: 1px rgb(75, 75, 75) solid;
border-radius: 10px;
}
.audio-label {
display: block;
text-align: center;
}
.audio-volume-div {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding: 5px;
}
.audio-lock-button {
width: 100%;
height: 2em;
}
.audio-random-button {
width: 100%;
height: 2em;
}
.audio-mute-button {
padding: 5px;
width: 50px;
height: 30px;
width: 100%;
height: 2em;
}
.audio-slider {
width: 100% !important;
vertical-align: center;
}
.audio-mute-button-muted {
@@ -19,4 +50,43 @@
#audio_refresh_assets {
width: 50px;
height: 30px;
}
}
.audio-mixer-mute {
width: 10%;
}
.audio-mixer-lock {
width: 10%;
}
.audio-mixer-random {
width: 10%;
}
.audio-container {
display: flex;
gap: 10px;
align-items: center;
}
.audio-container>.vol {
width: 100px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.audio-container>.vol>input {
width: 100%;
}
.audio-container>.playlist {
flex-grow: 1;
}
.audio-container>.playlist>select {
height: 100%;
margin: 0 !important;
}

View File

@@ -10,6 +10,12 @@
<input type="checkbox" id="audio_enabled" name="audio_enabled">
<small>Enabled</small>
</label>
<div id="audio_bgm_dynamic_enable_div">
<label class="checkbox_label" for="audio_dynamic_bgm_enabled">
<input type="checkbox" id="audio_dynamic_bgm_enabled" name="audio_dynamic_bgm_enabled">
<small>Enable expression BGM switch (req. character expression)</small>
</label>
</div>
<div id="audio_debug_div">
<label class="checkbox_label" for="audio_debug">
<input type="checkbox" id="audio_debug" name="audio_debug">
@@ -24,23 +30,54 @@
</div>
</div>
<div>
<div>
<label for="audio_bgm_volume_slider">Music <span id="audio_bgm_volume"></span></label>
<div class="mixer-div">
<div id="audio_bgm_mute" class="menu_button audio-mute-button">
<i class="fa-solid fa-volume-high fa-lg" id="audio_bgm_mute_icon"></i>
<div class="audio-ui-block">
<label for="audio_bgm_volume_slider">Music</label>
<div class="audio-mixer-div audio-container">
<div class="audio-mixer-element audio-mixer-mute">
<div id="audio_bgm_mute" class="menu_button audio-mute-button">
<i class="fa-solid fa-volume-high fa-lg fa-fw" id="audio_bgm_mute_icon"></i>
</div>
</div>
<div class="audio-mixer-element vol audio-mixer-volume">
<input type="range" class ="audio-slider" id ="audio_bgm_volume_slider" value = "0" maxlength ="100">
</div>
<div class="audio-mixer-element playlist audio-mixer-playlist">
<select id="audio_bgm_select">
</select>
</div>
<div class="audio-mixer-element audio-mixer-lock">
<div id="audio_bgm_lock" class="menu_button audio-lock-button">
<i class="fa-solid fa-repeat fa-lg fa-fw" id="audio_bgm_lock_icon"></i>
</div>
</div>
<div class="audio-mixer-element audio-mixer-random">
<div id="audio_bgm_random" class="menu_button audio-random-button">
<i class="fa-solid fa-random fa-lg fa-fw" id="audio_bgm_random_icon"></i>
</div>
</div>
<input type="range" class ="slider" id ="audio_bgm_volume_slider" value = "0" maxlength ="100">
</div>
<audio id="audio_bgm" controls src="">
</div>
<div>
<label for="audio_ambient_volume_slider">Ambient <span id="audio_ambient_volume"></span></label>
<div class="mixer-div">
<div id="audio_ambient_mute" class="menu_button audio-mute-button">
<i class="fa-solid fa-volume-high fa-lg" id="audio_ambient_mute_icon"></i>
<label for="audio_ambient_volume_slider">Ambient</label>
<div class="audio-mixer-div audio-container">
<div class="audio-mixer-element audio-mixer-mute">
<div id="audio_ambient_mute" class="menu_button audio-mute-button">
<i class="fa-solid fa-volume-high fa-lg fa-fw" id="audio_ambient_mute_icon"></i>
</div>
</div>
<div class="audio-mixer-element vol audio-mixer-volume">
<input type="range" class ="audio-slider" id ="audio_ambient_volume_slider" value = "0" maxlength ="100">
</div>
<div class="audio-mixer-element playlist audio-mixer-playlist">
<select id="audio_ambient_select">
</select>
</div>
<div class="audio-mixer-element">
<div id="audio_ambient_lock" class="menu_button audio-lock-button">
<i class="fa-solid fa-lock-open fa-lg fa-fw" id="audio_ambient_lock_icon"></i>
</div>
</div>
<input type="range" class ="slider" id ="audio_ambient_volume_slider" value = "0" maxlength ="100">
</div>
<audio id="audio_ambient" controls src="">
</div>
@@ -58,9 +95,9 @@
Create a folder name <b>bgm</b> inside of it.
Put bgm music with expressions there. File names should follow the pattern:
<it>[expression_label]_[number].mp3</it>
By default one of the <it>neutral_[number].mp3</it> will play if classify module is not active.
By default one of the <it>neutral_[number].mp3</it> will play if classify module is not active.
</i>
</div>
</div>
</div>
</div>
</div>

View File

@@ -170,9 +170,9 @@ $(document).ready(function () {
}
addSettings();
registerSlashCommand('lockbg', onLockBackgroundClick, ['bglock'], " locks a background for the currently selected chat", true, true);
registerSlashCommand('unlockbg', onUnlockBackgroundClick, ['bgunlock'], ' unlocks a background for the currently selected chat', true, true);
registerSlashCommand('autobg', autoBackgroundCommand, ['bgauto'], ' automatically changes the background based on the chat context using the AI request prompt', true, true);
registerSlashCommand('lockbg', onLockBackgroundClick, ['bglock'], " locks a background for the currently selected chat", true, true);
registerSlashCommand('unlockbg', onUnlockBackgroundClick, ['bgunlock'], ' unlocks a background for the currently selected chat', true, true);
registerSlashCommand('autobg', autoBackgroundCommand, ['bgauto'], ' automatically changes the background based on the chat context using the AI request prompt', true, true);
eventSource.on(event_types.FORCE_SET_BACKGROUND, forceSetBackground);
eventSource.on(event_types.CHAT_CHANGED, moduleWorker);
});

View File

@@ -6,7 +6,7 @@
background-attachment: fixed;
background-size: cover;
border-radius: 20px;
border: 1px solid var(--black50a);
border: 1px solid var(--SmartThemeBorderColor);
box-shadow: 0 0 7px var(--black50a);
margin: 5px;
}

View File

@@ -1,6 +1,6 @@
import { getBase64Async } from "../../utils.js";
import { getContext, getApiUrl, doExtrasFetch, extension_settings } from "../../extensions.js";
import { callPopup, saveSettingsDebounced } from "../../../script.js";
import { getBase64Async, saveBase64AsFile } from "../../utils.js";
import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules } from "../../extensions.js";
import { callPopup, getRequestHeaders, saveSettingsDebounced } from "../../../script.js";
import { getMessageTimeStamp } from "../../RossAscends-mods.js";
export { MODULE_NAME };
@@ -8,7 +8,8 @@ const MODULE_NAME = 'caption';
const UPDATE_INTERVAL = 1000;
async function moduleWorker() {
$('#send_picture').toggle(getContext().onlineStatus !== 'no_connection');
const hasConnection = getContext().onlineStatus !== 'no_connection';
$('#send_picture').toggle(hasConnection);
}
async function setImageIcon() {
@@ -52,7 +53,6 @@ async function sendCaptionedMessage(caption, image) {
const message = {
name: context.name1,
is_user: true,
is_name: true,
send_date: getMessageTimeStamp(),
mes: messageText,
extra: {
@@ -65,16 +65,21 @@ async function sendCaptionedMessage(caption, image) {
await context.generate('caption');
}
async function onSelectImage(e) {
setSpinnerIcon();
const file = e.target.files[0];
async function doCaptionRequest(base64Img) {
if (extension_settings.caption.local) {
const apiResult = await fetch('/api/extra/caption', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify({ image: base64Img })
});
if (!file) {
return;
}
if (!apiResult.ok) {
throw new Error('Failed to caption image via local pipeline.');
}
try {
const base64Img = await getBase64Async(file);
const data = await apiResult.json();
return data;
} else if (modules.includes('caption')) {
const url = new URL(getApiUrl());
url.pathname = '/api/caption';
@@ -84,17 +89,42 @@ async function onSelectImage(e) {
'Content-Type': 'application/json',
'Bypass-Tunnel-Reminder': 'bypass',
},
body: JSON.stringify({ image: base64Img.split(',')[1] })
body: JSON.stringify({ image: base64Img })
});
if (apiResult.ok) {
const data = await apiResult.json();
const caption = data.caption;
const imageToSave = data.thumbnail ? `data:image/jpeg;base64,${data.thumbnail}` : base64Img;
await sendCaptionedMessage(caption, imageToSave);
if (!apiResult.ok) {
throw new Error('Failed to caption image via Extras.');
}
const data = await apiResult.json();
return data;
} else {
throw new Error('No captioning module is available.');
}
}
async function onSelectImage(e) {
setSpinnerIcon();
const file = e.target.files[0];
if (!file || !(file instanceof File)) {
return;
}
try {
const context = getContext();
const fileData = await getBase64Async(file);
const base64Format = fileData.split(',')[0].split(';')[0].split('/')[1];
const base64Data = fileData.split(',')[1];
const data = await doCaptionRequest(base64Data);
const caption = data.caption;
const imageToSave = data.thumbnail ? data.thumbnail : base64Data;
const format = data.thumbnail ? 'jpeg' : base64Format;
const imagePath = await saveBase64AsFile(imageToSave, context.name2, '', format);
await sendCaptionedMessage(caption, imagePath);
}
catch (error) {
toastr.error('Failed to caption image.');
console.log(error);
}
finally {
@@ -113,12 +143,21 @@ jQuery(function () {
const sendButton = $(`
<div id="send_picture" class="list-group-item flex-container flexGap5">
<div class="fa-solid fa-image extensionsMenuExtensionButton"></div>
Send a picture
Send a Picture
</div>`);
$('#extensionsMenu').prepend(sendButton);
$(sendButton).hide();
$(sendButton).on('click', () => $('#img_file').trigger('click'));
$(sendButton).on('click', () => {
const hasCaptionModule = modules.includes('caption') || extension_settings.caption.local;
if (!hasCaptionModule) {
toastr.error('No captioning module is available. Either enable the local captioning pipeline or connect to Extras.');
return;
}
$('#img_file').trigger('click');
});
}
function addPictureSendForm() {
const inputHtml = `<input id="img_file" type="file" accept="image/*">`;
@@ -131,13 +170,17 @@ jQuery(function () {
}
function addSettings() {
const html = `
<div class="background_settings">
<div class="caption_settings">
<div class="inline-drawer">
<div class="inline-drawer-toggle inline-drawer-header">
<b>Image Captioning</b>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<div class="inline-drawer-content">
<label class="checkbox_label" for="caption_local">
<input id="caption_local" type="checkbox" class="checkbox">
Use local captioning pipeline
</label>
<label class="checkbox_label" for="caption_refine_mode">
<input id="caption_refine_mode" type="checkbox" class="checkbox">
Edit captions before generation
@@ -155,6 +198,11 @@ jQuery(function () {
setImageIcon();
moduleWorker();
$('#caption_refine_mode').prop('checked', !!(extension_settings.caption.refine_mode));
$('#caption_local').prop('checked', !!(extension_settings.caption.local));
$('#caption_refine_mode').on('input', onRefineModeInput);
$('#caption_local').on('input', () => {
extension_settings.caption.local = !!$('#caption_local').prop('checked');
saveSettingsDebounced();
});
setInterval(moduleWorker, UPDATE_INTERVAL);
});

View File

@@ -1,10 +1,10 @@
{
"display_name": "Image Captioning",
"loading_order": 4,
"requires": [
"requires": [],
"optional": [
"caption"
],
"optional": [],
"js": "index.js",
"css": "style.css",
"author": "Cohee#1207",

View File

@@ -1,3 +1,3 @@
#img_form {
display: none;
}
#img_form {
display: none;
}

View File

@@ -0,0 +1,14 @@
<h3>
Enter a name for the custom expression:
</h3>
<h4>
Requirements:
</h4>
<ol class="justifyLeft">
<li>
The name must be unique and not already in use by the default expression.
</li>
<li>
The name must contain only letters, numbers, dashes and underscores. Don't include any file extensions.
</li>
</ol>

View File

@@ -2,11 +2,13 @@ import { callPopup, eventSource, event_types, getRequestHeaders, saveSettingsDeb
import { dragElement, isMobile } from "../../RossAscends-mods.js";
import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplate } from "../../extensions.js";
import { loadMovingUIState, power_user } from "../../power-user.js";
import { onlyUnique, debounce, getCharaFilename } from "../../utils.js";
import { registerSlashCommand } from "../../slash-commands.js";
import { onlyUnique, debounce, getCharaFilename, trimToEndSentence, trimToStartSentence } from "../../utils.js";
export { MODULE_NAME };
const MODULE_NAME = 'expressions';
const UPDATE_INTERVAL = 2000;
const STREAMING_UPDATE_INTERVAL = 6000;
const FALLBACK_EXPRESSION = 'joy';
const DEFAULT_EXPRESSIONS = [
"talkinghead",
@@ -45,6 +47,7 @@ let lastCharacter = undefined;
let lastMessage = null;
let spriteCache = {};
let inApiCall = false;
let lastServerResponseTime = 0;
function isVisualNovelMode() {
return Boolean(!isMobile() && power_user.waifuMode && getContext().groupId);
@@ -125,15 +128,7 @@ async function visualNovelSetCharacterSprites(container, name, expression) {
continue;
}
let spriteFolderName = character.name;
const avatarFileName = getSpriteFolderName({ original_avatar: character.avatar });
const expressionOverride = extension_settings.expressionOverrides.find((e) =>
e.name == avatarFileName
);
if (expressionOverride && expressionOverride.path) {
spriteFolderName = expressionOverride.path;
}
const spriteFolderName = getSpriteFolderName({ original_avatar: character.avatar }, character.name);
// download images if not downloaded yet
if (spriteCache[spriteFolderName] === undefined) {
@@ -270,16 +265,7 @@ async function setLastMessageSprite(img, avatar, labels) {
if (lastMessage) {
const text = lastMessage.mes || '';
let spriteFolderName = lastMessage.name;
const avatarFileName = getSpriteFolderName(lastMessage);
const expressionOverride = extension_settings.expressionOverrides.find((e) =>
e.name == avatarFileName
);
if (expressionOverride && expressionOverride.path) {
spriteFolderName = expressionOverride.path;
}
const spriteFolderName = getSpriteFolderName(lastMessage, lastMessage.name);
const sprites = spriteCache[spriteFolderName] || [];
const label = await getExpressionLabel(text);
const path = labels.includes(label) ? sprites.find(x => x.label === label)?.path : '';
@@ -365,7 +351,7 @@ async function setImage(img, path) {
expressionClone.removeClass('default');
expressionClone.off('error');
expressionClone.on('error', function () {
console.debug('Expression image error', sprite.path);
console.debug('Expression image error', path);
$(this).attr('src', '');
$(this).off('error');
resolve();
@@ -419,17 +405,7 @@ async function loadLiveChar() {
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 spriteFolderName = getSpriteFolderName();
const talkingheadPath = `/characters/${encodeURIComponent(spriteFolderName)}/talkinghead.png`;
@@ -468,19 +444,19 @@ async function loadLiveChar() {
function handleImageChange() {
const imgElement = document.querySelector('img#expression-image.expression');
if (!imgElement) {
if (!imgElement || !(imgElement instanceof HTMLImageElement)) {
console.log("Cannot find addExpressionImage()");
return;
}
if (extension_settings.expressions.talkinghead) {
if (extension_settings.expressions.talkinghead && !extension_settings.expressions.local) {
// Method get IP of endpoint
const talkingheadResultFeedSrc = `${getApiUrl()}/api/talkinghead/result_feed`;
$('#expression-holder').css({ display: '' });
if (imgElement.src !== talkingheadResultFeedSrc) {
const expressionImageElement = document.querySelector('.expression_list_image');
if (expressionImageElement) {
if (expressionImageElement && expressionImageElement instanceof HTMLImageElement) {
doExtrasFetch(expressionImageElement.src, {
method: 'HEAD',
})
@@ -503,6 +479,14 @@ function handleImageChange() {
async function moduleWorker() {
const context = getContext();
// Hide and disable talkinghead while in local mode
$('#image_type_block').toggle(!extension_settings.expressions.local);
if (extension_settings.expressions.local && extension_settings.expressions.talkinghead) {
$('#image_type_toggle').prop('checked', false);
setTalkingHeadState(false);
}
// non-characters not supported
if (!context.groupId && (context.characterId === undefined || context.characterId === 'invalid-safety-id')) {
removeExpression();
@@ -516,12 +500,14 @@ async function moduleWorker() {
//clear expression
let imgElement = document.getElementById('expression-image');
imgElement.src = "";
if (imgElement && imgElement instanceof HTMLImageElement) {
imgElement.src = "";
}
//set checkbox to global var
$('#image_type_toggle').prop('checked', extension_settings.expressions.talkinghead);
if (extension_settings.expressions.talkinghead) {
settalkingheadState(extension_settings.expressions.talkinghead);
setTalkingHeadState(extension_settings.expressions.talkinghead);
}
}
@@ -545,15 +531,7 @@ async function moduleWorker() {
}
const currentLastMessage = getLastCharacterMessage();
let spriteFolderName = currentLastMessage.name;
const avatarFileName = getSpriteFolderName(currentLastMessage);
const expressionOverride = extension_settings.expressionOverrides.find((e) =>
e.name == avatarFileName
);
if (expressionOverride && expressionOverride.path) {
spriteFolderName = expressionOverride.path;
}
let spriteFolderName = getSpriteFolderName(currentLastMessage, currentLastMessage.name);
// character has no expressions or it is not loaded
if (Object.keys(spriteCache).length === 0) {
@@ -562,8 +540,9 @@ async function moduleWorker() {
}
const offlineMode = $('.expression_settings .offline_mode');
if (!modules.includes('classify')) {
$('.expression_settings').show();
if (!modules.includes('classify') && !extension_settings.expressions.local) {
$('#open_chat_expressions').show();
$('#no_chat_expressions').hide();
offlineMode.css('display', 'block');
lastCharacter = context.groupId || context.characterId;
@@ -584,9 +563,19 @@ async function moduleWorker() {
await forceUpdateVisualNovelMode();
}
if (context.groupId && !Array.isArray(spriteCache[spriteFolderName])) {
await validateImages(spriteFolderName, true);
await forceUpdateVisualNovelMode();
}
offlineMode.css('display', 'none');
}
// Don't bother classifying if current char has no sprites and no default expressions are enabled
if ((!Array.isArray(spriteCache[spriteFolderName]) || spriteCache[spriteFolderName].length === 0) && !extension_settings.expressions.showDefault) {
return;
}
// check if last message changed
if ((lastCharacter === context.characterId || lastCharacter === context.groupId)
&& lastMessage === currentLastMessage.mes) {
@@ -595,9 +584,21 @@ async function moduleWorker() {
// API is busy
if (inApiCall) {
console.debug('Classification API is busy');
return;
}
// Throttle classification requests during streaming
if (!context.groupId && context.streamingProcessor && !context.streamingProcessor.isFinished) {
const now = Date.now();
const timeSinceLastServerResponse = now - lastServerResponseTime;
if (timeSinceLastServerResponse < STREAMING_UPDATE_INTERVAL) {
console.log('Streaming in progress: throttling expression update. Next update at ' + new Date(lastServerResponseTime + STREAMING_UPDATE_INTERVAL));
return;
}
}
try {
inApiCall = true;
let expression = await getExpressionLabel(currentLastMessage.mes);
@@ -615,7 +616,6 @@ async function moduleWorker() {
}
await sendExpressionCall(spriteFolderName, expression, force, vnMode);
}
catch (error) {
console.log(error);
@@ -624,21 +624,12 @@ async function moduleWorker() {
inApiCall = false;
lastCharacter = context.groupId || context.characterId;
lastMessage = currentLastMessage.mes;
lastServerResponseTime = Date.now();
}
}
async function talkingheadcheck() {
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;
}
async function talkingHeadCheck() {
let spriteFolderName = getSpriteFolderName();
try {
await validateImages(spriteFolderName);
@@ -659,11 +650,29 @@ async function talkingheadcheck() {
}
}
function settalkingheadState(switch_var) {
function getSpriteFolderName(characterMessage = null, characterName = null) {
const context = getContext();
let spriteFolderName = characterName ?? context.name2;
const message = characterMessage ?? getLastCharacterMessage();
const avatarFileName = getFolderNameByMessage(message);
const expressionOverride = extension_settings.expressionOverrides.find(e => e.name == avatarFileName);
if (expressionOverride && expressionOverride.path) {
spriteFolderName = expressionOverride.path;
}
return spriteFolderName;
}
function setTalkingHeadState(switch_var) {
extension_settings.expressions.talkinghead = switch_var; // Store setting
saveSettingsDebounced();
talkingheadcheck().then(result => {
if (extension_settings.expressions.local) {
return;
}
talkingHeadCheck().then(result => {
if (result) {
//console.log("talkinghead exists!");
@@ -672,7 +681,7 @@ function settalkingheadState(switch_var) {
} else {
unloadLiveChar();
}
handleImageChange(switch_var); // Change image as needed
handleImageChange(); // Change image as needed
} else {
@@ -681,7 +690,7 @@ function settalkingheadState(switch_var) {
});
}
function getSpriteFolderName(message) {
function getFolderNameByMessage(message) {
const context = getContext();
let avatarPath = '';
@@ -712,27 +721,122 @@ async function sendExpressionCall(name, expression, force, vnMode) {
}
}
async function setSpriteSetCommand(_, folder) {
if (!folder) {
console.log('Clearing sprite set');
folder = '';
}
if (folder.startsWith('/') || folder.startsWith('\\')) {
folder = folder.slice(1);
const currentLastMessage = getLastCharacterMessage();
folder = `${currentLastMessage.name}/${folder}`;
}
$("#expression_override").val(folder.trim());
onClickExpressionOverrideButton();
removeExpression();
moduleWorker();
}
async function setSpriteSlashCommand(_, spriteId) {
if (!spriteId) {
console.log('No sprite id provided');
return;
}
spriteId = spriteId.trim().toLowerCase();
const currentLastMessage = getLastCharacterMessage();
const spriteFolderName = getSpriteFolderName(currentLastMessage, currentLastMessage.name);
await validateImages(spriteFolderName);
// Fuzzy search for sprite
const fuse = new Fuse(spriteCache[spriteFolderName], { keys: ['label'] });
const results = fuse.search(spriteId);
const spriteItem = results[0]?.item;
if (!spriteItem) {
console.log('No sprite found for search term ' + spriteId);
return;
}
const vnMode = isVisualNovelMode();
await sendExpressionCall(spriteFolderName, spriteItem.label, true, vnMode);
}
/**
* Processes the classification text to reduce the amount of text sent to the API.
* Quotes and asterisks are to be removed. If the text is less than 300 characters, it is returned as is.
* If the text is more than 300 characters, the first and last 150 characters are returned.
* The result is trimmed to the end of sentence.
* @param {string} text The text to process.
* @returns {string}
*/
function sampleClassifyText(text) {
if (!text) {
return text;
}
// Remove asterisks and quotes
let result = text.replace(/[\*\"]/g, '');
const SAMPLE_THRESHOLD = 300;
const HALF_SAMPLE_THRESHOLD = SAMPLE_THRESHOLD / 2;
if (text.length < SAMPLE_THRESHOLD) {
result = trimToEndSentence(result);
} else {
result = trimToEndSentence(result.slice(0, HALF_SAMPLE_THRESHOLD)) + ' ' + trimToStartSentence(result.slice(-HALF_SAMPLE_THRESHOLD));
}
return result.trim();
}
async function getExpressionLabel(text) {
// Return if text is undefined, saving a costly fetch request
if (!modules.includes('classify') || !text) {
if ((!modules.includes('classify') && !extension_settings.expressions.local) || !text) {
return FALLBACK_EXPRESSION;
}
const url = new URL(getApiUrl());
url.pathname = '/api/classify';
text = sampleClassifyText(text);
const apiResult = await doExtrasFetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Bypass-Tunnel-Reminder': 'bypass',
},
body: JSON.stringify({ text: text }),
});
try {
if (extension_settings.expressions.local) {
// Local transformers pipeline
const apiResult = await fetch('/api/extra/classify', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify({ text: text }),
});
if (apiResult.ok) {
const data = await apiResult.json();
return data.classification[0].label;
if (apiResult.ok) {
const data = await apiResult.json();
return data.classification[0].label;
}
} else {
// Extras
const url = new URL(getApiUrl());
url.pathname = '/api/classify';
const apiResult = await doExtrasFetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Bypass-Tunnel-Reminder': 'bypass',
},
body: JSON.stringify({ text: text }),
});
if (apiResult.ok) {
const data = await apiResult.json();
return data.classification[0].label;
}
}
} catch (error) {
console.log(error);
return FALLBACK_EXPRESSION;
}
}
@@ -756,7 +860,8 @@ function removeExpression() {
$('img.expression').off('error');
$('img.expression').prop('src', '');
$('img.expression').removeClass('default');
$('.expression_settings').hide();
$('#open_chat_expressions').hide();
$('#no_chat_expressions').show();
}
async function validateImages(character, forceRedrawCached) {
@@ -782,9 +887,11 @@ async function validateImages(character, forceRedrawCached) {
function drawSpritesList(character, labels, sprites) {
let validExpressions = [];
$('.expression_settings').show();
$('#no_chat_expressions').hide();
$('#open_chat_expressions').show();
$('#image_list').empty();
$('#image_list').data('name', character);
$('#image_list_header_name').text(character);
if (!Array.isArray(labels)) {
return [];
@@ -792,27 +899,36 @@ function drawSpritesList(character, labels, sprites) {
labels.sort().forEach((item) => {
const sprite = sprites.find(x => x.label == item);
const isCustom = extension_settings.expressions.custom.includes(item);
if (sprite) {
validExpressions.push(sprite);
$('#image_list').append(getListItem(item, sprite.path, 'success'));
$('#image_list').append(getListItem(item, sprite.path, 'success', isCustom));
}
else {
$('#image_list').append(getListItem(item, '/img/No-Image-Placeholder.svg', 'failure'));
$('#image_list').append(getListItem(item, '/img/No-Image-Placeholder.svg', 'failure', isCustom));
}
});
return validExpressions;
}
function getListItem(item, imageSrc, textClass) {
return renderExtensionTemplate(MODULE_NAME, 'list-item', { item, imageSrc, textClass });
/**
* Renders a list item template for the expressions list.
* @param {string} item Expression name
* @param {string} imageSrc Path to image
* @param {'success' | 'failure'} textClass 'success' or 'failure'
* @param {boolean} isCustom If expression is added by user
* @returns {string} Rendered list item template
*/
function getListItem(item, imageSrc, textClass, isCustom) {
return renderExtensionTemplate(MODULE_NAME, 'list-item', { item, imageSrc, textClass, isCustom });
}
async function getSpritesList(name) {
console.debug('getting sprites list');
try {
const result = await fetch(`/get_sprites?name=${encodeURIComponent(name)}`);
const result = await fetch(`/api/sprites/get?name=${encodeURIComponent(name)}`);
let sprites = result.ok ? (await result.json()) : [];
return sprites;
}
@@ -822,40 +938,84 @@ async function getSpritesList(name) {
}
}
async function getExpressionsList() {
// get something for offline mode (default images)
if (!modules.includes('classify')) {
return DEFAULT_EXPRESSIONS;
function renderCustomExpressions() {
if (!Array.isArray(extension_settings.expressions.custom)) {
extension_settings.expressions.custom = [];
}
const customExpressions = extension_settings.expressions.custom.sort((a, b) => a.localeCompare(b));
$('#expression_custom').empty();
for (const expression of customExpressions) {
const option = document.createElement('option');
option.value = expression;
option.text = expression;
$('#expression_custom').append(option);
}
if (customExpressions.length === 0) {
$('#expression_custom').append('<option value="" disabled selected>[ No custom expressions ]</option>');
}
}
async function getExpressionsList() {
// Return cached list if available
if (Array.isArray(expressionsList)) {
return expressionsList;
}
const url = new URL(getApiUrl());
url.pathname = '/api/classify/labels';
/**
* Returns the list of expressions from the API or fallback in offline mode.
* @returns {Promise<string[]>}
*/
async function resolveExpressionsList() {
// get something for offline mode (default images)
if (!modules.includes('classify') && !extension_settings.expressions.local) {
return DEFAULT_EXPRESSIONS;
}
try {
const apiResult = await doExtrasFetch(url, {
method: 'GET',
headers: { 'Bypass-Tunnel-Reminder': 'bypass' },
});
try {
if (extension_settings.expressions.local) {
const apiResult = await fetch('/api/extra/classify/labels', {
method: 'POST',
headers: getRequestHeaders(),
});
if (apiResult.ok) {
if (apiResult.ok) {
const data = await apiResult.json();
expressionsList = data.labels;
return expressionsList;
}
} else {
const url = new URL(getApiUrl());
url.pathname = '/api/classify/labels';
const data = await apiResult.json();
expressionsList = data.labels;
return expressionsList;
const apiResult = await doExtrasFetch(url, {
method: 'GET',
headers: { 'Bypass-Tunnel-Reminder': 'bypass' },
});
if (apiResult.ok) {
const data = await apiResult.json();
expressionsList = data.labels;
return expressionsList;
}
}
}
catch (error) {
console.log(error);
return [];
}
}
catch (error) {
console.log(error);
return [];
}
const result = await resolveExpressionsList();
result.push(...extension_settings.expressions.custom);
return result;
}
async function setExpression(character, expression, force) {
if (!extension_settings.expressions.talkinghead) {
if (extension_settings.expressions.local || !extension_settings.expressions.talkinghead) {
console.debug('entered setExpressions');
await validateImages(character);
const img = $('img.expression');
@@ -968,12 +1128,12 @@ async function setExpression(character, expression, force) {
} else {
talkingheadcheck().then(result => {
talkingHeadCheck().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) {
if (imgElement && imgElement instanceof HTMLImageElement) {
//console.log("setting value");
imgElement.src = getApiUrl() + '/api/talkinghead/result_feed';
}
@@ -988,18 +1148,76 @@ async function setExpression(character, expression, force) {
}
function onClickExpressionImage() {
// online mode doesn't need force set
if (modules.includes('classify')) {
const expression = $(this).attr('id');
setSpriteSlashCommand({}, expression);
}
async function onClickExpressionAddCustom() {
let expressionName = await callPopup(renderExtensionTemplate(MODULE_NAME, 'add-custom-expression'), 'input');
if (!expressionName) {
console.debug('No custom expression name provided');
return;
}
const expression = $(this).attr('id');
const name = getLastCharacterMessage().name;
expressionName = expressionName.trim().toLowerCase();
if ($(this).find('.failure').length === 0) {
setExpression(name, expression, true);
// a-z, 0-9, dashes and underscores only
if (!/^[a-z0-9-_]+$/.test(expressionName)) {
toastr.info('Invalid custom expression name provided');
return;
}
// Check if expression name already exists in default expressions
if (DEFAULT_EXPRESSIONS.includes(expressionName)) {
toastr.info('Expression name already exists');
return;
}
// Check if expression name already exists in custom expressions
if (extension_settings.expressions.custom.includes(expressionName)) {
toastr.info('Custom expression already exists');
return;
}
// Add custom expression into settings
extension_settings.expressions.custom.push(expressionName);
renderCustomExpressions();
saveSettingsDebounced();
// Force refresh sprites list
expressionsList = null;
spriteCache = {};
moduleWorker();
}
async function onClickExpressionRemoveCustom() {
const selectedExpression = $('#expression_custom').val();
if (!selectedExpression) {
console.debug('No custom expression selected');
return;
}
const confirmation = await callPopup(renderExtensionTemplate(MODULE_NAME, 'remove-custom-expression', { expression: selectedExpression }), 'confirm');
if (!confirmation) {
console.debug('Custom expression removal cancelled');
return;
}
// Remove custom expression from settings
const index = extension_settings.expressions.custom.indexOf(selectedExpression);
extension_settings.expressions.custom.splice(index, 1);
renderCustomExpressions();
saveSettingsDebounced();
// Force refresh sprites list
expressionsList = null;
spriteCache = {};
moduleWorker();
}
async function handleFileUpload(url, formData) {
try {
const data = await jQuery.ajax({
@@ -1042,7 +1260,7 @@ async function onClickExpressionUpload(event) {
formData.append('label', id);
formData.append('avatar', file);
await handleFileUpload('/upload_sprite', formData);
await handleFileUpload('/api/sprites/upload', formData);
// Reset the input
e.target.form.reset();
@@ -1057,7 +1275,7 @@ async function onClickExpressionUpload(event) {
async function onClickExpressionOverrideButton() {
const context = getContext();
const currentLastMessage = getLastCharacterMessage();
const avatarFileName = getSpriteFolderName(currentLastMessage);
const avatarFileName = getFolderNameByMessage(currentLastMessage);
// If the avatar name couldn't be found, abort.
if (!avatarFileName) {
@@ -1066,7 +1284,7 @@ async function onClickExpressionOverrideButton() {
return;
}
const overridePath = $("#expression_override").val();
const overridePath = String($("#expression_override").val());
const existingOverrideIndex = extension_settings.expressionOverrides.findIndex((e) =>
e.name == avatarFileName
);
@@ -1148,7 +1366,7 @@ async function onClickExpressionUploadPackButton() {
formData.append('name', name);
formData.append('avatar', file);
const { count } = await handleFileUpload('/upload_sprite_pack', formData);
const { count } = await handleFileUpload('/api/sprites/upload-zip', formData);
toastr.success(`Uploaded ${count} image(s) for ${name}`);
// Reset the input
@@ -1175,7 +1393,7 @@ async function onClickExpressionDelete(event) {
const name = $('#image_list').data('name');
try {
await fetch('/delete_sprite', {
await fetch('/api/sprites/delete', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify({ name, label: id }),
@@ -1191,7 +1409,7 @@ async function onClickExpressionDelete(event) {
function setExpressionOverrideHtml(forceClear = false) {
const currentLastMessage = getLastCharacterMessage();
const avatarFileName = getSpriteFolderName(currentLastMessage);
const avatarFileName = getFolderNameByMessage(currentLastMessage);
if (!avatarFileName) {
return;
}
@@ -1237,6 +1455,11 @@ function setExpressionOverrideHtml(forceClear = false) {
$('#expressions_show_default').on('input', onExpressionsShowDefaultInput);
$('#expression_upload_pack_button').on('click', onClickExpressionUploadPackButton);
$('#expressions_show_default').prop('checked', extension_settings.expressions.showDefault).trigger('input');
$('#expression_local').prop('checked', extension_settings.expressions.local).on('input', function () {
extension_settings.expressions.local = !!$(this).prop('checked');
moduleWorker();
saveSettingsDebounced();
});
$('#expression_override_cleanup_button').on('click', onClickExpressionOverrideRemoveAllButton);
$(document).on('dragstart', '.expression', (e) => {
e.preventDefault()
@@ -1246,11 +1469,18 @@ function setExpressionOverrideHtml(forceClear = false) {
$(document).on('click', '.expression_list_upload', onClickExpressionUpload);
$(document).on('click', '.expression_list_delete', onClickExpressionDelete);
$(window).on("resize", updateVisualNovelModeDebounced);
$('.expression_settings').hide();
$("#open_chat_expressions").hide();
$('#image_type_toggle').on('click', function () {
settalkingheadState(this.checked);
if (this instanceof HTMLInputElement) {
setTalkingHeadState(this.checked);
}
});
renderCustomExpressions();
$('#expression_custom_add').on('click', onClickExpressionAddCustom);
$('#expression_custom_remove').on('click', onClickExpressionRemoveCustom);
}
addExpressionImage();
@@ -1270,4 +1500,6 @@ function setExpressionOverrideHtml(forceClear = false) {
});
eventSource.on(event_types.MOVABLE_PANELS_RESET, updateVisualNovelModeDebounced);
eventSource.on(event_types.GROUP_UPDATED, updateVisualNovelModeDebounced);
registerSlashCommand('sprite', setSpriteSlashCommand, ['emote'], '<span class="monospace">(spriteId)</span> force sets the sprite for the current character', true, true);
registerSlashCommand('spriteoverride', setSpriteSetCommand, ['costume'], '<span class="monospace">(optional folder)</span> sets an override sprite folder for the current character. If the name starts with a slash or a backslash, selects a sub-folder in the character-named folder. Empty value to reset to default.', true, true);
})();

View File

@@ -7,6 +7,11 @@
<i class="fa-solid fa-trash"></i>
</div>
</div>
<span class="expression_list_title {{textClass}}">{{item}}</span>
<div class="expression_list_title {{textClass}}">
<span>{{item}}</span>
{{#if isCustom}}
<small class="expression_list_custom">(custom)</small>
{{/if}}
</div>
<img class="expression_list_image" src="{{imageSrc}}" />
</div>

View File

@@ -0,0 +1,7 @@
<h3>
Are you sure you want to remove the expression <tt>&quot;{{expression}}&quot;</tt>?
</h3>
<div>
Uploaded images will not be deleted, but will no longer be used by the extension.
</div>
<br>

View File

@@ -6,35 +6,57 @@
</div>
<div class="inline-drawer-content">
<!-- 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 - talkinghead (extras)</label>
</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">
<input id="expression_override" type="text" class="text_pole" placeholder="Override folder name" />
<input id="expression_override_button" class="menu_button" type="submit" value="Submit" />
</div>
<div id="image_list"></div>
<div class="expression_buttons flex-container spaceEvenly">
<div id="expression_upload_pack_button" class="menu_button">
<i class="fa-solid fa-file-zipper"></i>
<span>Upload sprite pack (ZIP)</span>
</div>
<div id="expression_override_cleanup_button" class="menu_button">
<i class="fa-solid fa-trash-can"></i>
<span>Remove all image overrides</span>
<label class="checkbox_label" for="expression_local" title="Use classification model without the Extras server.">
<input id="expression_local" type="checkbox" />
<span data-i18n="Local server classification">Local server classification</span>
</label>
<label class="checkbox_label" for="expressions_show_default">
<input id="expressions_show_default" type="checkbox">
<span>Show default images (emojis) if sprite missing</span>
</label>
<label id="image_type_block" class="checkbox_label" for="image_type_toggle">
<input id="image_type_toggle" type="checkbox">
<span>Image Type - talkinghead (extras)</span>
</label>
<div class="expression_custom_block m-b-1 m-t-1">
<label for="expression_custom">Custom Expressions</label>
<small>Can be set manually or with an <tt>/emote</tt> slash command.</small>
<div class="flex-container">
<select id="expression_custom" class="flex1 margin0"><select>
<i id="expression_custom_add" class="menu_button fa-solid fa-plus margin0" title="Add"></i>
<i id="expression_custom_remove" class="menu_button fa-solid fa-xmark margin0" title="Remove"></i>
</div>
</div>
<p class="hint"><b>Hint:</b> <i>Create new folder in the <b>public/characters/</b> folder and name it as the name of the character.
Put images with expressions there. File names should follow the pattern: <tt>[expression_label].[image_format]</tt></i></p>
<label for="expressions_show_default"><input id="expressions_show_default" type="checkbox">Show default images (emojis) if missing</label>
<div id="no_chat_expressions">
Open a chat to see the character expressions.
</div>
<div id="open_chat_expressions">
<div class="offline_mode">
<small>You are in offline mode. Click on the image below to set the expression.</small>
</div>
<label for="expression_override">Sprite Folder Override</label>
<small>Use a forward slash to specify a subfolder. Example: <tt>Bob/formal</tt></small>
<div class="flex-container flexnowrap">
<input id="expression_override" type="text" class="text_pole" placeholder="Override folder name" />
<input id="expression_override_button" class="menu_button" type="submit" value="Submit" />
</div>
<h3 id="image_list_header">
<strong>Sprite set:</strong>&nbsp;<span id="image_list_header_name"></span>
</h3>
<div id="image_list"></div>
<div class="expression_buttons flex-container spaceEvenly">
<div id="expression_upload_pack_button" class="menu_button">
<i class="fa-solid fa-file-zipper"></i>
<span>Upload sprite pack (ZIP)</span>
</div>
<div id="expression_override_cleanup_button" class="menu_button">
<i class="fa-solid fa-trash-can"></i>
<span>Remove all image overrides</span>
</div>
</div>
<p class="hint"><b>Hint:</b> <i>Create new folder in the <b>public/characters/</b> folder and name it as the name of the character.
Put images with expressions there. File names should follow the pattern: <tt>[expression_label].[image_format]</tt></i></p>
</div>
</div>
</div>
<form>

View File

@@ -49,6 +49,18 @@
overflow: hidden;
resize: both;
display: flex;
justify-content: center;
}
#no_chat_expressions {
text-align: center;
margin: 10px 0;
font-weight: bold;
opacity: 0.8;
}
#image_list_header_name {
font-weight: 400;
}
img.expression {
@@ -112,6 +124,8 @@ img.expression.default {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
line-height: 1;
}
.expression_list_buttons {
@@ -180,4 +194,4 @@ img.expression.default {
div.expression {
display: none;
}
}
}

View File

@@ -7,7 +7,8 @@ import {
import { selected_group } from "../../group-chats.js";
import { loadFileToDocument } from "../../utils.js";
import { loadMovingUIState } from '../../power-user.js';
import { dragElement } from '../../RossAscends-mods.js'
import { dragElement } from '../../RossAscends-mods.js';
import { registerSlashCommand } from "../../slash-commands.js";
const extensionName = "gallery";
const extensionFolderPath = `scripts/extensions/${extensionName}/`;
@@ -367,6 +368,21 @@ function makeDragImg(id, url) {
});
}
/**
* Sanitizes a given ID to ensure it can be used as an HTML ID.
* This function replaces spaces and non-word characters with dashes.
* It also removes any non-ASCII characters.
* @param {string} id - The ID to be sanitized.
* @returns {string} - The sanitized ID.
*/
function sanitizeHTMLId(id){
// Replace spaces and non-word characters
id = id.replace(/\s+/g, '-')
.replace(/[^\x00-\x7F]/g, '-')
.replace(/\W/g, '');
return id;
}
/**
* Processes a list of items (containing URLs) and creates a draggable box for the first item.
@@ -379,10 +395,17 @@ function makeDragImg(id, url) {
*/
function viewWithDragbox(items) {
if (items && items.length > 0) {
var url = items[0].responsiveURL(); // Get the URL of the clicked image/video
const url = items[0].responsiveURL(); // Get the URL of the clicked image/video
// ID should just be the last part of the URL, removing the extension
var id = url.substring(url.lastIndexOf('/') + 1, url.lastIndexOf('.'));
const id = sanitizeHTMLId(url.substring(url.lastIndexOf('/') + 1, url.lastIndexOf('.')));
makeDragImg(id, url);
}
}
// Registers a simple command for opening the char gallery.
registerSlashCommand("show-gallery", showGalleryCommand, ["sg"], " shows the gallery", true, true);
function showGalleryCommand(args) {
showCharGallery();
}

View File

@@ -62,11 +62,11 @@ const generateDebounced = debounce(() => generateHypeBot(), 500);
* @param {string} text Text to set
*/
function setHypeBotText(text) {
const blockA = $('#chat');
var originalScrollBottom = blockA[0].scrollHeight - (blockA.scrollTop() + blockA.outerHeight());
const chatBlock = $('#chat');
const originalScrollBottom = chatBlock[0].scrollHeight - (chatBlock.scrollTop() + chatBlock.outerHeight());
hypeBotBar.html(DOMPurify.sanitize(text));
var newScrollTop = blockA[0].scrollHeight - (blockA.outerHeight() + originalScrollBottom);
blockA.scrollTop(newScrollTop);
const newScrollTop = chatBlock[0].scrollHeight - (chatBlock.outerHeight() + originalScrollBottom);
chatBlock.scrollTop(newScrollTop);
}
/**
@@ -159,7 +159,7 @@ async function generateHypeBot() {
abortController = new AbortController();
const response = await fetch('/generate_novelai', {
const response = await fetch('/api/novelai/generate', {
headers: getRequestHeaders(),
body: JSON.stringify(parameters),
method: 'POST',
@@ -191,11 +191,13 @@ jQuery(() => {
settings.enabled = $('#hypebot_enabled').prop('checked');
hypeBotBar.toggle(settings.enabled);
abortController?.abort();
Object.assign(extension_settings.hypebot, settings);
saveSettingsDebounced();
});
$('#hypebot_name').val(settings.name).on('input', () => {
settings.name = String($('#hypebot_name').val());
Object.assign(extension_settings.hypebot, settings);
saveSettingsDebounced();
});

View File

@@ -0,0 +1,54 @@
<div class="idle-settings">
<div class="inline-drawer">
<div class="inline-drawer-toggle inline-drawer-header" title="Indicates the settings for the idle feature.">
<b>Idle</b>
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<div class="inline-drawer-content">
<div class="idle_block flex-container">
<input id="idle_enabled" type="checkbox" title="Toggle to enable or disable the idle feature." />
<label for="idle_enabled">Enabled</label>
</div>
<div class="idle_block flex-container">
<input id="idle_repeats" class="text_pole widthUnset" type="number" min="0" max="100000" step="1" title="The number of times the idle action will be prompted." />
<label for="idle_repeats">Idle Prompt Count</label>
</div>
<div class="idle_block flex-container" style="display: none;">
<input id="idle_timer_min" class="text_pole widthUnset" type="number" min="0" max="600000" step="1" title="The minimum amount of time in seconds before the idle action is triggered." />
<label for="idle_timer_min">Idle Timer Minimum (seconds)</label>
</div>
<div class="idle_block flex-container">
<input id="idle_timer" class="text_pole widthUnset" type="number" min="0" max="600000" step="1" title="The amount of time in seconds before the idle action is triggered." />
<label for="idle_timer">Idle Timer (seconds)</label>
</div>
<div class="idle_block flex-container">
<label for="idle_prompts">Idle Prompts</label>
<textarea id="idle_prompts" class="text_pole textarea_compact" rows="6" title="The prompts to be sent to initial the idle reply (newline seperated)."></textarea>
</div>
<div class="idle_block flex-container">
<input id="idle_use_continuation" type="checkbox" title="Indicates whether the idle action will just use the 'Continue' function instead of a prompt." />
<label for="idle_use_continuation">Use Continuation</label>
</div>
<div class="idle_block flex-container">
<input id="idle_random_time" type="checkbox" title="Indicates if the idle time should be randomized between a min/max value." />
<label for="idle_random_time">Randomize Time</label>
</div>
<div class="idle_block flex-container">
<input id="idle_include_prompt" type="checkbox" title="Indicates if the idle prompting should be included in context. (Sends as user)" />
<label for="idle_include_prompt">Include Idle Prompt</label>
</div>
<div class="idle_block flex-container">
<label for="idle_sendAs">Send As</label>
<select id="idle_sendAs" class="text_pole" title="Determines how the idle message prompting is sent; as a user, character, system, or raw message.">
<option value="user">User</option>
<option value="char">Character</option>
<option value="sys">System</option>
<option value="raw">Raw</option>
</select>
</div>
<hr class="sysHR" />
</div>
</div>
</div>

View File

@@ -0,0 +1,329 @@
import {
saveSettingsDebounced,
substituteParams
} from "../../../script.js";
import { debounce } from "../../utils.js";
import { promptQuietForLoudResponse, sendMessageAs, sendNarratorMessage } from "../../slash-commands.js";
import { extension_settings, getContext, renderExtensionTemplate } from "../../extensions.js";
import { registerSlashCommand } from "../../slash-commands.js";
const extensionName = "idle";
let idleTimer = null;
let repeatCount = 0;
let defaultSettings = {
enabled: false,
timer: 120,
prompts: [
"*stands silently, looking deep in thought*",
"*pauses, eyes wandering over the surroundings*",
"*hesitates, appearing lost for a moment*",
"*takes a deep breath, collecting their thoughts*",
"*gazes into the distance, seemingly distracted*",
"*remains still, absorbing the ambiance*",
"*lingers in silence, a contemplative look on their face*",
"*stops, fingers brushing against an old memory*",
"*seems to drift into a momentary daydream*",
"*waits quietly, allowing the weight of the moment to settle*",
],
useContinuation: true,
repeats: 2, // 0 = infinite
sendAs: "user",
randomTime: false,
timeMin: 60,
includePrompt: false,
};
//TODO: Can we make this a generic function?
/**
* Load the extension settings and set defaults if they don't exist.
*/
async function loadSettings() {
if (!extension_settings.idle) {
console.log("Creating extension_settings.idle");
extension_settings.idle = {};
}
for (const [key, value] of Object.entries(defaultSettings)) {
if (!extension_settings.idle.hasOwnProperty(key)) {
console.log(`Setting default for: ${key}`);
extension_settings.idle[key] = value;
}
}
populateUIWithSettings();
}
//TODO: Can we make this a generic function too?
/**
* Populate the UI components with values from the extension settings.
*/
function populateUIWithSettings() {
$("#idle_timer").val(extension_settings.idle.timer).trigger("input");
$("#idle_prompts").val(extension_settings.idle.prompts.join("\n")).trigger("input");
$("#idle_use_continuation").prop("checked", extension_settings.idle.useContinuation).trigger("input");
$("#idle_enabled").prop("checked", extension_settings.idle.enabled).trigger("input");
$("#idle_repeats").val(extension_settings.idle.repeats).trigger("input");
$("#idle_sendAs").val(extension_settings.idle.sendAs).trigger("input");
$("#idle_random_time").prop("checked", extension_settings.idle.randomTime).trigger("input");
$("#idle_timer_min").val(extension_settings.idle.timerMin).trigger("input");
$("#idle_include_prompt").prop("checked", extension_settings.idle.includePrompt).trigger("input");
}
/**
* Reset the idle timer based on the extension settings and context.
*/
function resetIdleTimer() {
console.debug("Resetting idle timer");
if (idleTimer) clearTimeout(idleTimer);
let context = getContext();
if (!context.characterId && !context.groupID) return;
if (!extension_settings.idle.enabled) return;
if (extension_settings.idle.randomTime) {
// ensure these are ints
let min = extension_settings.idle.timerMin;
let max = extension_settings.idle.timer;
min = parseInt(min);
max = parseInt(max);
let randomTime = (Math.random() * (max - min + 1)) + min;
idleTimer = setTimeout(sendIdlePrompt, 1000 * randomTime);
} else {
idleTimer = setTimeout(sendIdlePrompt, 1000 * extension_settings.idle.timer);
}
}
/**
* Send a random idle prompt to the AI based on the extension settings.
* Checks conditions like if the extension is enabled and repeat conditions.
*/
async function sendIdlePrompt() {
if (!extension_settings.idle.enabled) return;
// Check repeat conditions and waiting for a response
if (repeatCount >= extension_settings.idle.repeats || $('#mes_stop').is(':visible')) {
//console.debug("Not sending idle prompt due to repeat conditions or waiting for a response.");
resetIdleTimer();
return;
}
const randomPrompt = extension_settings.idle.prompts[
Math.floor(Math.random() * extension_settings.idle.prompts.length)
];
sendPrompt(randomPrompt);
repeatCount++;
resetIdleTimer();
}
/**
* Add our prompt to the chat and then send the chat to the backend.
* @param {string} sendAs - The type of message to send. "user", "char", or "sys".
* @param {string} prompt - The prompt text to send to the AI.
*/
function sendLoud(sendAs, prompt) {
if (sendAs === "user") {
prompt = substituteParams(prompt);
$("#send_textarea").val(prompt);
// Set the focus back to the textarea
$("#send_textarea").focus();
$("#send_but").trigger('click');
} else if (sendAs === "char") {
sendMessageAs("", `${getContext().name2}\n${prompt}`);
promptQuietForLoudResponse(sendAs, "");
} else if (sendAs === "sys") {
sendNarratorMessage("", prompt);
promptQuietForLoudResponse(sendAs, "");
}
else {
console.error(`Unknown sendAs value: ${sendAs}`);
}
}
/**
* Send the provided prompt to the AI. Determines method based on continuation setting.
* @param {string} prompt - The prompt text to send to the AI.
*/
function sendPrompt(prompt) {
clearTimeout(idleTimer);
$("#send_textarea").off("input");
if (extension_settings.idle.useContinuation) {
$('#option_continue').trigger('click');
console.debug("Sending idle prompt with continuation");
} else {
console.debug("Sending idle prompt");
console.log(extension_settings.idle);
if (extension_settings.idle.includePrompt) {
sendLoud(extension_settings.idle.sendAs, prompt);
}
else {
promptQuietForLoudResponse(extension_settings.idle.sendAs, prompt);
}
}
}
/**
* Load the settings HTML and append to the designated area.
*/
async function loadSettingsHTML() {
const settingsHtml = renderExtensionTemplate(extensionName, "dropdown");
$("#extensions_settings2").append(settingsHtml);
}
/**
* Update a specific setting based on user input.
* @param {string} elementId - The HTML element ID tied to the setting.
* @param {string} property - The property name in the settings object.
* @param {boolean} [isCheckbox=false] - Whether the setting is a checkbox.
*/
function updateSetting(elementId, property, isCheckbox = false) {
let value = $(`#${elementId}`).val();
if (isCheckbox) {
value = $(`#${elementId}`).prop('checked');
}
if (property === "prompts") {
value = value.split("\n");
}
extension_settings.idle[property] = value;
saveSettingsDebounced();
}
/**
* Attach an input listener to a UI component to update the corresponding setting.
* @param {string} elementId - The HTML element ID tied to the setting.
* @param {string} property - The property name in the settings object.
* @param {boolean} [isCheckbox=false] - Whether the setting is a checkbox.
*/
function attachUpdateListener(elementId, property, isCheckbox = false) {
$(`#${elementId}`).on('input', debounce(() => {
updateSetting(elementId, property, isCheckbox);
}, 250));
}
/**
* Handle the enabling or disabling of the idle extension.
* Adds or removes the idle listeners based on the checkbox's state.
*/
function handleIdleEnabled() {
if (!extension_settings.idle.enabled) {
clearTimeout(idleTimer);
removeIdleListeners();
} else {
resetIdleTimer();
attachIdleListeners();
}
}
/**
* Setup input listeners for the various settings and actions related to the idle extension.
*/
function setupListeners() {
const settingsToWatch = [
['idle_timer', 'timer'],
['idle_prompts', 'prompts'],
['idle_use_continuation', 'useContinuation', true],
['idle_enabled', 'enabled', true],
['idle_repeats', 'repeats'],
['idle_sendAs', 'sendAs'],
['idle_random_time', 'randomTime', true],
['idle_timer_min', 'timerMin'],
['idle_include_prompt', 'includePrompt', true]
];
settingsToWatch.forEach(setting => {
attachUpdateListener(...setting);
});
// Idleness listeners, could be made better
$('#idle_enabled').on('input', debounce(handleIdleEnabled, 250));
// Add the idle listeners initially if the idle feature is enabled
if (extension_settings.idle.enabled) {
attachIdleListeners();
}
//show/hide timer min parent div
$('#idle_random_time').on('input', function () {
if ($(this).prop('checked')) {
$('#idle_timer_min').parent().show();
} else {
$('#idle_timer_min').parent().hide();
}
$('#idle_timer').trigger('input');
});
// if we're including the prompt, hide raw from the sendAs dropdown
$('#idle_include_prompt').on('input', function () {
if ($(this).prop('checked')) {
$('#idle_sendAs option[value="raw"]').hide();
} else {
$('#idle_sendAs option[value="raw"]').show();
}
});
//make sure timer min is less than timer
$('#idle_timer').on('input', function () {
if ($('#idle_random_time').prop('checked')) {
if ($(this).val() < $('#idle_timer_min').val()) {
$('#idle_timer_min').val($(this).val());
$('#idle_timer_min').trigger('input');
}
}
});
}
const debouncedActivityHandler = debounce((event) => {
// Check if the event target (or any of its parents) has the id "option_continue"
if ($(event.target).closest('#option_continue').length) {
return; // Do not proceed if the click was on (or inside) an element with id "option_continue"
}
console.debug("Activity detected, resetting idle timer");
resetIdleTimer();
repeatCount = 0;
}, 250);
function attachIdleListeners() {
$(document).on("click keypress", debouncedActivityHandler);
document.addEventListener('keydown', debouncedActivityHandler);
}
/**
* Remove idle-specific listeners.
*/
function removeIdleListeners() {
$(document).off("click keypress", debouncedActivityHandler);
document.removeEventListener('keydown', debouncedActivityHandler);
}
function toggleIdle() {
extension_settings.idle.enabled = !extension_settings.idle.enabled;
$('#idle_enabled').prop('checked', extension_settings.idle.enabled);
$('#idle_enabled').trigger('input');
toastr.info(`Idle mode ${extension_settings.idle.enabled ? "enabled" : "disabled"}.`);
resetIdleTimer();
}
jQuery(async () => {
await loadSettingsHTML();
loadSettings();
setupListeners();
if (extension_settings.idle.enabled) {
resetIdleTimer();
}
// once the doc is ready, check if random time is checked and hide/show timer min
if ($('#idle_random_time').prop('checked')) {
$('#idle_timer_min').parent().show();
}
registerSlashCommand('idle', toggleIdle, [], ' toggles idle mode', true, true);
});

View File

@@ -0,0 +1,12 @@
{
"display_name": "Idle",
"loading_order": 6,
"requires": [],
"optional": [
],
"js": "index.js",
"css": "style.css",
"author": "City-Unit",
"version": "1.0.0",
"homePage": "https://github.com/SillyTavern/SillyTavern"
}

View File

@@ -0,0 +1,3 @@
.idle_block {
align-items: center;
}

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